Skip to content

Logging requests

Security · Difficulty 4/5 · TypeScript · 48 linesOrigin: Fictional

Task

What the AI was asked to do

Write a request logger for our integrations API, used for debugging. The logger runs before the handler and receives the same request object. For each request write one JSON line: time, method, path, query and body. Every field whose name contains password, token, secret or apikey (ignoring letter case, _ and -) must be replaced with "[redacted]" at any depth. Bodies are integration settings with no personal data, and paths hold no secrets.

Handoff note

What the AI wrote when it handed over the code:

“Secret fields are masked at any depth, and spellings such as apiKey, api_key and API-KEY are caught too. Query and body go through the same function, and the clock is passed in.”

The code the AI wrote

TypeScript48 lines

Mark the lines you think are wrong; give each mark a severity and a one-sentence reason.

Click or tap a line. With a keyboard, move to the line and press Enter.

0 of 5 marks
How to pick a severity
  • CriticalData loss, a security hole or lost money
  • MajorThe user gets a wrong result in a case the task asks for
  • MinorA rare edge case outside the task, or readability

In the Testing topic, severity is set by the effect of the broken implementation the test doesn't catch.

Your reason isn't graded; you'll compare it with a sample answer.

export type Json =
  null | boolean | number | string | Json[] | { [key: string]: Json };
3
export interface IncomingRequest {
  method: string;
  path: string;
  query: Record<string, string>;
  body: Json;
}
10
const SECRET_PARTS = ["password", "token", "secret", "apikey"];
const MASK = "[redacted]";
13
function isSecretKey(key: string): boolean {
  const normalized = key.toLowerCase().replace(/[-_]/g, "");
  return SECRET_PARTS.some((part) => normalized.includes(part));
}
18
export function redact(value: Json): Json {
  if (value === null || typeof value !== "object" || Array.isArray(value)) {
    return value;
  }
  for (const key of Object.keys(value)) {
    if (isSecretKey(key)) {
      value[key] = MASK;
    } else {
      value[key] = redact(value[key] ?? null);
    }
  }
  return value;
}
32
export function logRequest(
  request: IncomingRequest,
  write: (line: string) => void,
  now: Date,
): void {
  const { method, path, query, body } = request;
  write(
    JSON.stringify({
      at: now.toISOString(),
      method,
      path,
      query: redact(query),
      body: redact(body),
    }),
  );
}

The test that proves it

The right option is the test that fails on this code, which means it exposes the bug.

Test options
Mark at least one line to submit.

Submitting sends seven pieces of information to a counter; your reason text stays on your device. Data