Redact our own tokens, not just other people's

dd8d3e51fedb · AtlantisPleb · · parent d9700f86d0cc

Redact our own tokens, not just other people's

`openagents trace redact` is the command we tell people to run before sharing a
trace. It removed a Stripe key and left an OpenAgents one, then printed
`Nothing matched the redaction rules` — which reads as an all-clear, so the
trace gets shared.

`trace-store.ts` grew its own rule list, separate from `packages/atif`. Its
`api_key` rule covers third-party prefixes — `sk-`, `ghp_`, `github_pat_`,
`gho_`, `glpat-`, `xox*`, `AKIA`, `AIza` — and stops there. Nothing in the list
covered `oa_pat_`, `oa_token`, `oa_agent_`, `oa-x-`, or `smct_`. A token was
caught only when it happened to sit under a key matching `secret_field`, or in
a `SHOUTING_CASE=` assignment. In prose or argv it passed straight through.

Verified before the fix, against the built CLI:

    $ openagents trace redact probe.jsonl
    Nothing matched the redaction rules.
    $ cat probe.redacted.jsonl
    …oa_pat_REALTOKEN456 oa_agent_ABCDEF123456 oa-x-QQQQ1234 smct_machine-secret…

and after:

    Redacted 5 matches: api_key 1, oa_agent_token 1, x_code 1, oa_token 1, machine_token 1
    …[REDACTED:oa_token] [REDACTED:oa_agent_token] [REDACTED:x_code] [REDACTED:machine_token]…

`smct_` machine tokens were missing from **both** lists, so they also survived
an ATIF export — the export path was not as covered as it looked. They carry a
hyphen, which every other token rule stops at.

The four new cases are added to the planted-secret table, which asserts the
token *body* is absent rather than that a marker appeared: a marker check
passes for a prefix swap that leaves the secret in place, which is a defect
this repo has shipped before. Checked by deleting the new rules — all four fail
— and restoring them.

The two rule lists still exist separately and will drift again. Folding
`trace-store.ts` onto the atif rules is the real fix and is not in this change.

Closes openagents#97.

Deploy story

What this commit did to the running system — joined from the forge receipt chain, the part a commit page elsewhere cannot show.

Not deployed through the forge lane

No push, promotion, build, or deploy receipt references this commit (receipts are scanned over a bounded recent window). Changes shipped by full node replacement carry their proof in the release gate receipt instead.

Changed files

  • modified packages/atif/src/redaction.test.ts
  • modified packages/atif/src/redaction.ts
  • modified packages/openagents-cli/src/trace-store.ts
  • modified packages/openagents-cli/test/trace-store.test.ts

Diff

4 files changed, +69 -0

packages/atif/src/redaction.test.ts modified +8

@@ -71,6 +71,14 @@ const SECRET_FIXTURES: ReadonlyArray<{

71 71
    leak: "oa_pat_abc123def456",
72 72
    category: "oa_token",
73 73
  },
74
  {
75
    // Minted by computer pairing. Hyphenated, which every other token rule
76
    // stops at, so it survived an export intact.
77
    label: "machine pairing token",
78
    raw: "the machine token is smct_machine-secret today",
79
    leak: "smct_machine-secret",
80
    category: "machine_token",
81
  },
74 82
  {
75 83
    label: "X verification code",
76 84
    raw: "Code: oa-x-9f2bc-defG",
packages/atif/src/redaction.ts modified +8

@@ -11,6 +11,7 @@ export type RedactionCategory =

11 11
  | "oa_agent_token"
12 12
  | "x_code"
13 13
  | "oa_token"
14
  | "machine_token"
14 15
  | "aws_key"
15 16
  | "google_key"
16 17
  | "slack_token"

@@ -267,6 +268,13 @@ const RULES: ReadonlyArray<Rule> = [

267 268
    pattern: /\boa_(?:live|test|sk|key|secret|tok|token|pat)?_?[A-Za-z0-9]{12,}\b/g,
268 269
    replace: () => tag("oa_token"),
269 270
  },
271
  {
272
    // Machine tokens minted by computer pairing. They carry a hyphen, which
273
    // every rule above stops at, so they survived an ATIF export intact.
274
    category: "machine_token",
275
    pattern: /\bsmct_[A-Za-z0-9_-]{6,}\b/g,
276
    replace: () => tag("machine_token"),
277
  },
270 278
  {
271 279
    category: "owner_id",
272 280
    pattern: /\b(github|gh|x|twitter|discord|telegram|nostr):\d{3,}\b/gi,
packages/openagents-cli/src/trace-store.ts modified +27

@@ -406,6 +406,33 @@ export const redactionRules = (home: string): ReadonlyArray<RedactionRule> => [

406 406
      /\b(?:sk-[A-Za-z0-9_-]{16,}|ghp_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|gho_[A-Za-z0-9]{20,}|glpat-[A-Za-z0-9_-]{16,}|xox[baprs]-[A-Za-z0-9-]{10,}|AKIA[A-Z0-9]{16}|AIza[A-Za-z0-9_-]{30,})\b/g,
407 407
    replacement: "[REDACTED:api_key]",
408 408
  },
409
  // The `api_key` rule above covers other people's credentials and stopped
410
  // there, so this command redacted a Stripe key and left an OpenAgents one.
411
  // These are our own token families, and they are the ones most likely to be
412
  // in an OpenAgents trace. Ordered narrowest first so `oa_agent_` is not
413
  // consumed by the general rule.
414
  {
415
    category: "oa_agent_token",
416
    pattern: /\boa_agent_[A-Za-z0-9_-]{6,}\b/g,
417
    replacement: "[REDACTED:oa_agent_token]",
418
  },
419
  {
420
    category: "x_code",
421
    pattern: /\boa-x-[A-Za-z0-9_-]{4,}\b/g,
422
    replacement: "[REDACTED:x_code]",
423
  },
424
  {
425
    category: "oa_token",
426
    pattern: /\boa_(?:live|test|sk|key|secret|tok|token|pat)?_?[A-Za-z0-9]{12,}\b/g,
427
    replacement: "[REDACTED:oa_token]",
428
  },
429
  {
430
    // Machine tokens minted by computer pairing. They carry a hyphen, which
431
    // the token rules above stop at.
432
    category: "machine_token",
433
    pattern: /\bsmct_[A-Za-z0-9_-]{6,}\b/g,
434
    replacement: "[REDACTED:machine_token]",
435
  },
409 436
  {
410 437
    category: "jwt",
411 438
    pattern: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/g,
packages/openagents-cli/test/trace-store.test.ts modified +26

@@ -249,6 +249,32 @@ describe("trace redaction", () => {

249 249
      text: "signing with nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5 today",
250 250
      secret: "nsec1vl029mgpspedva04g90vltkh6fvh240zqtv9k0t9af8935ke9laqsnlfe5",
251 251
    },
252
    // Our own token families. The `api_key` rule covers other people's
253
    // credentials -- sk-, ghp_, AKIA -- and stopped there, so this command
254
    // redacted a Stripe key and left an OpenAgents one in place while
255
    // reporting "Nothing matched the redaction rules."
256
    {
257
      category: "oa_token",
258
      text: "called the API with oa_pat_REALTOKEN456 just now",
259
      secret: "oa_pat_REALTOKEN456",
260
    },
261
    {
262
      category: "oa_agent_token",
263
      text: "the child ran as oa_agent_ABCDEF123456 here",
264
      secret: "oa_agent_ABCDEF123456",
265
    },
266
    {
267
      category: "x_code",
268
      text: "paired with oa-x-QQQQ1234 yesterday",
269
      secret: "oa-x-QQQQ1234",
270
    },
271
    {
272
      // Minted by computer pairing, and hyphenated, which every other token
273
      // rule stops at.
274
      category: "machine_token",
275
      text: "the machine token is smct_machine-secret today",
276
      secret: "smct_machine-secret",
277
    },
252 278
  ];
253 279
254 280
  it.each(plantedSecrets)("removes a planted $category", ({ category, secret, text }) => {

This page updates live while a promote is in flight · changelog