AppMap CLI 3.203.0 (appmap --version), source at packages/cli/src/lib/sanitizeAppMap.ts.
What happens
sanitizeHeaders masks a header only when its value is a string:
function sanitizeHeaders(headers: Record<string, unknown> | undefined, masker: ValueMasker): void {
if (!headers) return;
for (const name of Object.keys(headers)) {
const value = headers[name];
if (typeof value === 'string') headers[name] = masker.mask(value);
}
}
Rack (the Ruby agent) records multi-valued response headers, set-cookie above all, as an array
of strings. Those are skipped, so the session cookie the sanitizer exists to remove stays in the
file, while the request's Cookie header (a string) is tokenized.
Repro
Minimal AppMap (two events):
{"version":"1.12","metadata":{"name":"repro"},"classMap":[],"events":[ {"id":1,"event":"call","thread_id":1,"http_server_request":{"request_method":"GET","path_info":"/x","headers":{"Cookie":"_gumroad_app_session_test=SECRETCOOKIE"}}}, {"id":2,"event":"return","thread_id":1,"parent_id":1,"http_server_response":{"status":200,"headers":{"etag":"W/\"abc\"","set-cookie":["_gumroad_guid=1de65c26-uuid; path=/","_gumroad_app_session_test=SECRETCOOKIE; httponly"]}}}]}
in.appmap.json in this folder, two events, one request and its response.
cp in.appmap.json out.appmap.json
appmap sanitize out.appmap.json
Output: 2 distinct value(s) tokenized. Result:
request headers: {"Cookie": "<v1>"}
response headers: {"etag": "<v2>",
"set-cookie": ["_gumroad_guid=1de65c26-uuid; path=/",
"_gumroad_app_session_test=SECRETCOOKIE; httponly"]}
Expected: every element of the array tokenized, e.g. ["<v3>", "<v4>"] (or, keeping the cookie
name as schema, ["_gumroad_guid=<v3>", "_gumroad_app_session_test=<v4>"]).
Seen in the wild
Gumroad request-spec recordings: every response that sets a session carries the encrypted
_gumroad_app_session_test cookie in set-cookie, untouched after appmap sanitize. Found while
packaging evlawler/goldtrace-examples PR 53; the same values are in the baselines of PR 51.
Suggested fix
if (typeof value === 'string') headers[name] = masker.mask(value);
else if (Array.isArray(value))
headers[name] = value.map((v) => (typeof v === 'string' ? masker.mask(v) : v));
Found while sanitizing Gumroad request-spec recordings for a gold-trace review corpus.
AppMap CLI 3.203.0 (
appmap --version), source atpackages/cli/src/lib/sanitizeAppMap.ts.What happens
sanitizeHeadersmasks a header only when its value is a string:Rack (the Ruby agent) records multi-valued response headers,
set-cookieabove all, as an arrayof strings. Those are skipped, so the session cookie the sanitizer exists to remove stays in the
file, while the request's
Cookieheader (a string) is tokenized.Repro
Minimal AppMap (two events):
{"version":"1.12","metadata":{"name":"repro"},"classMap":[],"events":[ {"id":1,"event":"call","thread_id":1,"http_server_request":{"request_method":"GET","path_info":"/x","headers":{"Cookie":"_gumroad_app_session_test=SECRETCOOKIE"}}}, {"id":2,"event":"return","thread_id":1,"parent_id":1,"http_server_response":{"status":200,"headers":{"etag":"W/\"abc\"","set-cookie":["_gumroad_guid=1de65c26-uuid; path=/","_gumroad_app_session_test=SECRETCOOKIE; httponly"]}}}]}in.appmap.jsonin this folder, two events, one request and its response.Output:
2 distinct value(s) tokenized. Result:Expected: every element of the array tokenized, e.g.
["<v3>", "<v4>"](or, keeping the cookiename as schema,
["_gumroad_guid=<v3>", "_gumroad_app_session_test=<v4>"]).Seen in the wild
Gumroad request-spec recordings: every response that sets a session carries the encrypted
_gumroad_app_session_testcookie inset-cookie, untouched afterappmap sanitize. Found whilepackaging evlawler/goldtrace-examples PR 53; the same values are in the baselines of PR 51.
Suggested fix
Found while sanitizing Gumroad request-spec recordings for a gold-trace review corpus.