Skip to content

Commit ab24801

Browse files
authored
fix(trace): redact browser trace URL query data (#784)
1 parent 5bec0c3 commit ab24801

2 files changed

Lines changed: 93 additions & 1 deletion

File tree

internal/clientrt/assets/gowdk.js

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,24 @@
948948
return url;
949949
}
950950

951+
function traceURLPath(url) {
952+
var value = traceInputURL(url);
953+
try {
954+
return new URL(value, window.location.href).pathname || '/';
955+
} catch (error) {
956+
var text = String(value || '');
957+
var fragment = text.indexOf('#');
958+
if (fragment >= 0) {
959+
text = text.slice(0, fragment);
960+
}
961+
var query = text.indexOf('?');
962+
if (query >= 0) {
963+
text = text.slice(0, query);
964+
}
965+
return text;
966+
}
967+
}
968+
951969
function traceInputHeaders(url, options) {
952970
if (options && options.headers) {
953971
return options.headers;
@@ -971,7 +989,7 @@
971989
return fetch(url, options);
972990
}
973991
var span = startTraceSpan(meta && meta.name || 'fetch', meta && meta.lane || 'api', [
974-
{ key: 'url.path', value: String(url || '') }
992+
{ key: 'url.path', value: traceURLPath(url) }
975993
]);
976994
var traced = Object.assign({}, options || {});
977995
if (sameOriginURL(url)) {

internal/clientrt/runtime_test.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ func TestSourceTraceFetchNormalizesRequestInputs(t *testing.T) {
135135
`function traceInputURL(url)`,
136136
`typeof Request !== 'undefined' && url instanceof Request`,
137137
`return url.url || '';`,
138+
`function traceURLPath(url)`,
139+
`return new URL(value, window.location.href).pathname || '/';`,
140+
`{ key: 'url.path', value: traceURLPath(url) }`,
138141
`return new URL(traceInputURL(url), window.location.href).origin === window.location.origin;`,
139142
`function traceInputHeaders(url, options)`,
140143
`url && typeof url === 'object' && url.headers`,
@@ -146,6 +149,20 @@ func TestSourceTraceFetchNormalizesRequestInputs(t *testing.T) {
146149
}
147150
}
148151

152+
func TestTraceFetchExportsPathOnlyURL(t *testing.T) {
153+
node, err := exec.LookPath("node")
154+
if err != nil {
155+
t.Skip("node is not installed")
156+
}
157+
script := filepath.Join(t.TempDir(), "gowdk-trace-url-test.js")
158+
if err := os.WriteFile(script, []byte(traceURLHarnessScript(string(Source()))), 0o600); err != nil {
159+
t.Fatal(err)
160+
}
161+
if output, err := exec.Command(node, script).CombinedOutput(); err != nil {
162+
t.Fatalf("trace URL harness failed: %v\n%s", err, output)
163+
}
164+
}
165+
149166
func TestFilename(t *testing.T) {
150167
if Filename != "gowdk.js" {
151168
t.Fatalf("unexpected runtime filename %q", Filename)
@@ -184,6 +201,63 @@ func TestEmbeddedRuntimeSourceFilesParseWithNode(t *testing.T) {
184201
}
185202
}
186203

204+
func traceURLHarnessScript(runtime string) string {
205+
return `
206+
'use strict';
207+
208+
const assert = require('node:assert/strict');
209+
210+
const requests = [];
211+
global.document = {
212+
documentElement: { hasAttribute() { return false; } },
213+
addEventListener() {},
214+
querySelector() { return null; },
215+
querySelectorAll() { return []; }
216+
};
217+
global.window = {
218+
location: {
219+
href: 'http://example.test/account?session=server-secret',
220+
origin: 'http://example.test'
221+
},
222+
addEventListener() {},
223+
__gowdkTraceEnabled: true
224+
};
225+
global.fetch = async function(url, options) {
226+
requests.push({ url: String(url), options: options || {} });
227+
return {
228+
ok: true,
229+
status: 204,
230+
headers: { get() { return ''; } },
231+
text: async () => ''
232+
};
233+
};
234+
235+
` + runtime + `
236+
237+
(async function() {
238+
await window.__gowdkTrace.fetch('/api/patients?token=client-secret&code=123#frag', {}, {
239+
name: 'fetch patients',
240+
lane: 'api'
241+
});
242+
await new Promise(resolve => setImmediate(resolve));
243+
244+
assert.equal(requests[0].url, '/api/patients?token=client-secret&code=123#frag');
245+
const traceRequest = requests.find(request => request.url === '/_gowdk/traces/browser');
246+
assert.ok(traceRequest, 'trace span was not posted');
247+
const payload = String(traceRequest.options.body || '');
248+
assert.ok(!payload.includes('client-secret'), payload);
249+
assert.ok(!payload.includes('code=123'), payload);
250+
assert.ok(!payload.includes('#frag'), payload);
251+
const span = JSON.parse(payload);
252+
const urlPath = span.attributes.find(attr => attr.key === 'url.path');
253+
assert.deepEqual(urlPath, { key: 'url.path', value: '/api/patients' });
254+
})().catch(error => {
255+
console.error(error && error.stack || error);
256+
process.exit(1);
257+
});
258+
`
259+
}
260+
187261
func TestRuntimeTemplatesReplacePlaceholders(t *testing.T) {
188262
rendered := []string{
189263
IslandJSSource(IslandJSOptions{

0 commit comments

Comments
 (0)