diff --git a/.agents/pm/extensions/.managed-extensions.json b/.agents/pm/extensions/.managed-extensions.json index e13f9e172..d303fe266 100644 --- a/.agents/pm/extensions/.managed-extensions.json +++ b/.agents/pm/extensions/.managed-extensions.json @@ -1,6 +1,6 @@ { "version": 1, - "updated_at": "2026-08-01T23:16:46.916Z", + "updated_at": "2026-08-02T02:01:11.649Z", "entries": [ { "name": "pm-changelog", diff --git a/.agents/pm/history/pm-y3w0ld.jsonl b/.agents/pm/history/pm-y3w0ld.jsonl new file mode 100644 index 000000000..80ad893f3 --- /dev/null +++ b/.agents/pm/history/pm-y3w0ld.jsonl @@ -0,0 +1,43 @@ +{"ts":"2026-08-02T00:40:15.547Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"create","patch":[{"op":"add","path":"/metadata/id","value":"pm-y3w0ld"},{"op":"add","path":"/metadata/title","value":"GH-864: history-redact output must never echo literal secrets"},{"op":"add","path":"/metadata/description","value":"GitHub issue #864 demonstrates that the audited redaction mutation rewrites history safely but returns the original literal matcher under patterns.literals, copying the value into terminal logs, CI output, agent transcripts, and SDK/MCP responses. Make the result contract disclosure-safe across applied, dry-run, no-op, and failure paths while preserving deterministic matcher counts and auditability."},{"op":"add","path":"/metadata/type","value":"Issue"},{"op":"add","path":"/metadata/status","value":"open"},{"op":"add","path":"/metadata/priority","value":1},{"op":"add","path":"/metadata/tags","value":["area:sdk","area:security","history","privacy","redaction"]},{"op":"add","path":"/metadata/created_at","value":"2026-08-02T00:40:15.547Z"},{"op":"add","path":"/metadata/updated_at","value":"2026-08-02T00:40:15.547Z"},{"op":"add","path":"/metadata/author","value":"harness:codex"},{"op":"add","path":"/metadata/acceptance_criteria","value":"Raw literal matcher values are absent from TOON, JSON, SDK, MCP, dry-run, no-op, error, telemetry, and profile outputs; regex matcher source receives equally safe treatment because pattern text may itself be sensitive; results retain deterministic matcher-kind counts and replacement/change counts without reversible matcher material; regression tests use canary matchers and assert absence from serialized success, dry-run, no-op, and failure surfaces; public SDK types and operator documentation define the disclosure-safe result contract; full 100/100/100/100 coverage, security scan, contract gates, and isolated temporary-directory acceptance pass"},{"op":"add","path":"/metadata/goal","value":"project management = context management"},{"op":"add","path":"/metadata/objective","value":"A secret supplied for audited removal is never repeated by the command that removes it"},{"op":"add","path":"/metadata/value","value":"History redaction must reduce disclosure, never multiply it into output and observability surfaces"},{"op":"add","path":"/metadata/impact","value":"Changes the public HistoryRedactResult pattern summary to non-reversible metadata and hardens CLI, SDK, MCP, telemetry, and tests against canary disclosure"},{"op":"add","path":"/metadata/outcome","value":"Applied, preview, no-op, and rejected redaction invocations expose useful counts without returning matcher content"},{"op":"add","path":"/metadata/parent","value":"pm-4a7m"},{"op":"add","path":"/metadata/risk","value":"high"},{"op":"add","path":"/metadata/confidence","value":"high"}],"before_hash":"3cc22dff72be7b14824654a7a64ea62b04799939b2fee54c1b5f52ca60bf6df0","after_hash":"74f74af61783bfa537b24d35f7bb25305b0b0f25f0a7d43ac5371dcabff3e5eb","message":"Duplicate check: all-status keyword searches for history-redact and redaction secrets found canonical delivered command pm-xk39, SDK owner pm-4a7m, and broader write-time guard gap pm-wwooxx, but no item for output echoing supplied matcher content. Creating the narrow security regression item and linking it to those existing lineages plus GH-864."} +{"ts":"2026-08-02T00:41:03.894Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:03.894Z"},{"op":"add","path":"/metadata/dependencies","value":[{"id":"pm-4a7m","kind":"verifies","created_at":"2026-08-02T00:41:03.670Z","author":"harness:codex","source_kind":"cli:update:dep","author_source":"detected"},{"id":"pm-qwoy","kind":"implements","created_at":"2026-08-02T00:41:03.670Z","author":"harness:codex","source_kind":"cli:update:dep","author_source":"detected"},{"id":"pm-wwooxx","kind":"related_to","created_at":"2026-08-02T00:41:03.670Z","author":"harness:codex","source_kind":"cli:update:dep","author_source":"detected"},{"id":"pm-xk39","kind":"discovered_from","created_at":"2026-08-02T00:41:03.670Z","author":"harness:codex","source_kind":"cli:update:dep","author_source":"detected"}]}],"before_hash":"74f74af61783bfa537b24d35f7bb25305b0b0f25f0a7d43ac5371dcabff3e5eb","after_hash":"c2ed915af945582aa3c77500ff6b3d3f74faedf94371008919c4eb5396973974","message":"Typed lineage: regression discovered from the original history-redact delivery, verifies the public SDK history-maintenance feature, implements the security capability, and relates to the broader secret-guard convergence work."} +{"ts":"2026-08-02T00:41:04.875Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:04.875Z"},{"op":"add","path":"/metadata/assignee","value":"harness:codex"},{"op":"add","path":"/metadata/claim_principal","value":"harness:codex#7d49453d0d45fcc94817af12"}],"before_hash":"c2ed915af945582aa3c77500ff6b3d3f74faedf94371008919c4eb5396973974","after_hash":"7887225bb5501f4badb760cefc14aeef8404651f8bd2609fe0f86f1ee98605cd"} +{"ts":"2026-08-02T00:41:05.924Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:05.924Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"7887225bb5501f4badb760cefc14aeef8404651f8bd2609fe0f86f1ee98605cd","after_hash":"def784a2d0f9b7cb95b95282ffded5926a7d7f5404df7bd04303405d1c1ccacc","message":"Active implementation begins after all-status duplicate search and full canonical history/SDK/security lineage review."} +{"ts":"2026-08-02T00:41:07.100Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:07.100Z"},{"op":"add","path":"/metadata/files","value":[{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}]}],"before_hash":"def784a2d0f9b7cb95b95282ffded5926a7d7f5404df7bd04303405d1c1ccacc","after_hash":"af975dc5d1f9a75135ffcbad8c549112f704e548eb3ae13239eb234f178164a5"} +{"ts":"2026-08-02T00:41:08.026Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"docs_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:08.026Z"},{"op":"add","path":"/metadata/docs","value":[{"path":"docs/COMMANDS.md","scope":"project","note":"Safe redaction output semantics"}]}],"before_hash":"af975dc5d1f9a75135ffcbad8c549112f704e548eb3ae13239eb234f178164a5","after_hash":"0133566e7db992ea6c36549d7c618d3f318d29a700af7d91c8e84fe0bffdf52d"} +{"ts":"2026-08-02T00:41:08.860Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:08.860Z"},{"op":"add","path":"/metadata/tests","value":[{"command":"node scripts/run-tests.mjs test -- tests/unit/sdk/history-read-primitives.spec.ts tests/unit/commands/history-activity-command.spec.ts","scope":"project","timeout_seconds":300}]}],"before_hash":"0133566e7db992ea6c36549d7c618d3f318d29a700af7d91c8e84fe0bffdf52d","after_hash":"5d296eeab2f0a9620fbd19a0ccf1c87e037b0d771e3c11d9bb6617be72212ce8"} +{"ts":"2026-08-02T00:41:09.749Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/0/timeout_seconds","value":900},{"op":"replace","path":"/metadata/tests/0/command","value":"node scripts/run-tests.mjs coverage -- --testTimeout=120000"},{"op":"add","path":"/metadata/tests/1","value":{"command":"node scripts/run-tests.mjs test -- tests/unit/sdk/history-read-primitives.spec.ts tests/unit/commands/history-activity-command.spec.ts","scope":"project","timeout_seconds":300}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T00:41:09.749Z"}],"before_hash":"5d296eeab2f0a9620fbd19a0ccf1c87e037b0d771e3c11d9bb6617be72212ce8","after_hash":"8e9ca6600efa5128bf62902e2431e28c38b482eb0cd94585a457f0634408b364"} +{"ts":"2026-08-02T01:11:08.628Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/0/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/0/path","value":"src/sdk/command-line.ts"},{"op":"add","path":"/metadata/files/1","value":{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:08.628Z"}],"before_hash":"8e9ca6600efa5128bf62902e2431e28c38b482eb0cd94585a457f0634408b364","after_hash":"1c4cde10c1867bc6ba386077cad4604a6f94d0128db92b1ef42f443f569abcae"} +{"ts":"2026-08-02T01:11:09.604Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/1/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/1/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/0/note","value":"CLI export for recovery sanitization"},{"op":"replace","path":"/metadata/files/0/path","value":"src/cli/argv-utils.ts"},{"op":"add","path":"/metadata/files/2","value":{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:09.604Z"}],"before_hash":"1c4cde10c1867bc6ba386077cad4604a6f94d0128db92b1ef42f443f569abcae","after_hash":"4335e079bf82680b5df8ef67f971a9780518f6e85952d99d57026e95f4d71a05"} +{"ts":"2026-08-02T01:11:10.485Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/2/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/2/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/1/note","value":"Sanitized recovery and typo diagnostics"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/main.ts"},{"op":"add","path":"/metadata/files/3","value":{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:10.485Z"}],"before_hash":"4335e079bf82680b5df8ef67f971a9780518f6e85952d99d57026e95f4d71a05","after_hash":"11d4e6048dafe2f178a814a2ab5382b8d6e16bbb2e25dee7bd71325c38933cd7"} +{"ts":"2026-08-02T01:11:11.363Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/3/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/3/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/2/note","value":"Sensitive redaction-option telemetry filtering"},{"op":"replace","path":"/metadata/files/2/path","value":"src/core/telemetry/runtime.ts"},{"op":"add","path":"/metadata/files/4","value":{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:11.363Z"}],"before_hash":"11d4e6048dafe2f178a814a2ab5382b8d6e16bbb2e25dee7bd71325c38933cd7","after_hash":"8f8b497d1fe2992e33d5d82ad73f72390aa892e026861f3b7884852a3a92bbc7"} +{"ts":"2026-08-02T01:11:12.221Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/4/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/4/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/3/note","value":"Sensitive redaction-option telemetry filtering"},{"op":"replace","path":"/metadata/files/3/path","value":"src/core/telemetry/runtime.ts"},{"op":"replace","path":"/metadata/files/2/note","value":"Sanitized recovery and typo diagnostics"},{"op":"replace","path":"/metadata/files/2/path","value":"src/cli/main.ts"},{"op":"replace","path":"/metadata/files/1/note","value":"CLI export for recovery sanitization"},{"op":"replace","path":"/metadata/files/1/path","value":"src/cli/argv-utils.ts"},{"op":"replace","path":"/metadata/files/0/note","value":"Acknowledged security-hardening SDK contract change"},{"op":"replace","path":"/metadata/files/0/path","value":"sdk/public-surface.json"},{"op":"add","path":"/metadata/files/5","value":{"path":"src/sdk/history-redact.ts","scope":"project","note":"Disclosure-safe public result contract and serialization"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:12.221Z"}],"before_hash":"8f8b497d1fe2992e33d5d82ad73f72390aa892e026861f3b7884852a3a92bbc7","after_hash":"834d6207b0c7f9d4c73048efe291c2247fcd0ecaa74641eb56f77154e760e0af"} +{"ts":"2026-08-02T01:11:13.096Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/6","value":{"path":"tests/unit/cli/argv-utils.spec.ts","scope":"project","note":"Recovery argv canary coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:13.096Z"}],"before_hash":"834d6207b0c7f9d4c73048efe291c2247fcd0ecaa74641eb56f77154e760e0af","after_hash":"ea5ee2f9a84adb70728ba27f2be3df6a372a0b379ed9bdc2496745cde59299c4"} +{"ts":"2026-08-02T01:11:13.988Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/7","value":{"path":"tests/unit/commands/history-activity-command.spec.ts","scope":"project","note":"CLI success dry-run no-op and error disclosure coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:13.988Z"}],"before_hash":"ea5ee2f9a84adb70728ba27f2be3df6a372a0b379ed9bdc2496745cde59299c4","after_hash":"3018d2c4e4a96259281c88fbcbc1d751d921fc1415be0da019ed51af44746bef"} +{"ts":"2026-08-02T01:11:14.858Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/8","value":{"path":"tests/unit/mcp/mcp-server-nested-options.spec.ts","scope":"project","note":"MCP disclosure-safe integration coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:14.858Z"}],"before_hash":"3018d2c4e4a96259281c88fbcbc1d751d921fc1415be0da019ed51af44746bef","after_hash":"d1962882cc3df3a773712541cc3985387217a2e58f272b7d43c9d6f65b70fef4"} +{"ts":"2026-08-02T01:11:15.702Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"add","path":"/metadata/files/9","value":{"path":"tests/unit/sdk/history-read-primitives.spec.ts","scope":"project","note":"Public SDK result contract coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:15.702Z"}],"before_hash":"d1962882cc3df3a773712541cc3985387217a2e58f272b7d43c9d6f65b70fef4","after_hash":"ea39394ed190d771a122fab88b4006fc23c64d7938928b52b1b52ff46ea10a09"} +{"ts":"2026-08-02T01:11:16.668Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/9/note","value":"MCP disclosure-safe integration coverage"},{"op":"replace","path":"/metadata/files/9/path","value":"tests/unit/mcp/mcp-server-nested-options.spec.ts"},{"op":"replace","path":"/metadata/files/8/note","value":"Telemetry sensitive-input regression coverage"},{"op":"replace","path":"/metadata/files/8/path","value":"tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts"},{"op":"add","path":"/metadata/files/10","value":{"path":"tests/unit/sdk/history-read-primitives.spec.ts","scope":"project","note":"Public SDK result contract coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:16.668Z"}],"before_hash":"ea39394ed190d771a122fab88b4006fc23c64d7938928b52b1b52ff46ea10a09","after_hash":"4105b8481518e79e635aa139d25618b0b1605072ca5049bb204291bd46fb1b7a"} +{"ts":"2026-08-02T01:11:23.961Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/2","value":{"command":"pnpm lint","scope":"project","timeout_seconds":300}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:23.961Z"}],"before_hash":"4105b8481518e79e635aa139d25618b0b1605072ca5049bb204291bd46fb1b7a","after_hash":"dfbeeaeec4a311f61cf7e8f42a89ff42c84a12e34002fe79a1256e4290ba23d6"} +{"ts":"2026-08-02T01:11:24.823Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/2/command","value":"pnpm contracts:check"},{"op":"add","path":"/metadata/tests/3","value":{"command":"pnpm lint","scope":"project","timeout_seconds":300}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:24.823Z"}],"before_hash":"dfbeeaeec4a311f61cf7e8f42a89ff42c84a12e34002fe79a1256e4290ba23d6","after_hash":"4f8493596a755db88c3aa8fba1c5cc3c573ca89f2f331baa7edb45bb0207a149"} +{"ts":"2026-08-02T01:11:25.635Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"add","path":"/metadata/tests/4","value":{"command":"pnpm security:scan","scope":"project","timeout_seconds":120}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:25.635Z"}],"before_hash":"4f8493596a755db88c3aa8fba1c5cc3c573ca89f2f331baa7edb45bb0207a149","after_hash":"cd91c8b664c858264e55c4bf4fb31d9a7e52d4a6e797bac204f496387acc60ae"} +{"ts":"2026-08-02T01:11:26.461Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/4/timeout_seconds","value":300},{"op":"replace","path":"/metadata/tests/4/command","value":"pnpm quality:context-eval"},{"op":"add","path":"/metadata/tests/5","value":{"command":"pnpm security:scan","scope":"project","timeout_seconds":120}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:26.461Z"}],"before_hash":"cd91c8b664c858264e55c4bf4fb31d9a7e52d4a6e797bac204f496387acc60ae","after_hash":"6f4f2620e45152448fa09ab2558d8bbbe77aee87f62122e24554501464636219"} +{"ts":"2026-08-02T01:11:27.269Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"tests_add","patch":[{"op":"replace","path":"/metadata/tests/5/command","value":"pnpm quality:docs-links"},{"op":"add","path":"/metadata/tests/6","value":{"command":"pnpm security:scan","scope":"project","timeout_seconds":120}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:11:27.269Z"}],"before_hash":"6f4f2620e45152448fa09ab2558d8bbbe77aee87f62122e24554501464636219","after_hash":"702b678fbcf52e00b58b9862ac517b8aefb8d44a177cbb6cbe25d5f73af128c3"} +{"ts":"2026-08-02T01:25:27.533Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:27.533Z"},{"op":"add","path":"/metadata/comments","value":[{"created_at":"2026-08-02T01:25:27.533Z","author":"harness:codex","text":"Implementation evidence: public HistoryRedactResult now exposes matcher-kind counts and replacement_is_default only; CLI recovery and typo guidance sanitize history-redact argv; telemetry redacts literal/regex/replacement inputs while retaining safe count fields; CLI, SDK, MCP, telemetry, dry-run, no-op, applied, and rejected paths have canary non-disclosure coverage. The public SDK surface records the acknowledged GH-864 security contract change."}]}],"before_hash":"702b678fbcf52e00b58b9862ac517b8aefb8d44a177cbb6cbe25d5f73af128c3","after_hash":"c821ca27d64fd50da54eba4cedfaefbdecbf60435aa09fcbef6ea80a00f40673"} +{"ts":"2026-08-02T01:25:29.335Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"note_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:29.335Z"},{"op":"add","path":"/metadata/notes","value":[{"created_at":"2026-08-02T01:25:29.335Z","author":"harness:codex","text":"Manual acceptance used an unrelated temporary tracker and exercised dry-run, applied, no-op, and invalid-regex paths. All matcher and replacement canaries were absent from captured stdout/stderr; applied history verified successfully and added the audit marker only when a rewrite occurred."}]}],"before_hash":"c821ca27d64fd50da54eba4cedfaefbdecbf60435aa09fcbef6ea80a00f40673","after_hash":"a10c2b26226ea3bf22bb2cf2a6d6d44b49e0d78f88d9275e63e9b53b47109a8d"} +{"ts":"2026-08-02T01:25:31.961Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"learning_add","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:31.961Z"},{"op":"add","path":"/metadata/learnings","value":[{"created_at":"2026-08-02T01:25:31.961Z","author":"harness:codex","text":"Inputs supplied to a redaction primitive are sensitive by purpose, even when they do not resemble credentials. Disclosure safety must cover success results, failures, recovery bundles, telemetry, profiling, and transport adapters; counts are the useful non-reversible substitute."}]}],"before_hash":"a10c2b26226ea3bf22bb2cf2a6d6d44b49e0d78f88d9275e63e9b53b47109a8d","after_hash":"6e33b146da463c6f4ecca58dfe3e462083da2c2219901d39a69e1041c5d46048"} +{"ts":"2026-08-02T01:25:33.533Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/1","value":{"created_at":"2026-08-02T01:25:33.533Z","author":"harness:codex","text":"Verification evidence: final source head passes 417/417 test files and 6691/6691 tests with exact 100% statements (47379/47379), branches (34105/34105), functions (9641/9641), and lines (45975/45975). pnpm lint passed with zero ESLint warnings and zero duplication clones; static quality passed 100% docstring/export/member coverage and SDK/package/token/context gates; contracts, secrets, docs links, context/retrieval/agent-task token evaluations, SDK surface, typecheck, and focused 63-test matrix passed."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:33.533Z"}],"before_hash":"6e33b146da463c6f4ecca58dfe3e462083da2c2219901d39a69e1041c5d46048","after_hash":"6d06ba75763a87e18dd9c02424db929a13a81a66bcb7676d1be1a73de6f0e0a1"} +{"ts":"2026-08-02T01:25:47.270Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:47.270Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-08-02T01:25:47.246Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-08-02T01:25:47.246Z"},{"op":"add","path":"/metadata/close_reason","value":"Resolved GH-864 by replacing secret-bearing history-redact result fields with non-reversible counts, sanitizing recovery and telemetry surfaces, documenting the contract, and proving CLI/SDK/MCP/manual behavior with exact 100% coverage and all strict gates."}],"before_hash":"6d06ba75763a87e18dd9c02424db929a13a81a66bcb7676d1be1a73de6f0e0a1","after_hash":"5ac50b7f659b9417ce15ef303c3dfd36b3fd2ff3fc28ae606846098cb940b092"} +{"ts":"2026-08-02T01:25:48.977Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:25:48.977Z"}],"before_hash":"5ac50b7f659b9417ce15ef303c3dfd36b3fd2ff3fc28ae606846098cb940b092","after_hash":"f5d30dbf12f7c9419e529122c8f2cc204e96a15bad9126b2a6150c2cd7cad78e"} +{"ts":"2026-08-02T01:26:26.965Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:26:26.965Z"},{"op":"add","path":"/metadata/resolution","value":"Disclosure-safe history-redact output across CLI, SDK, MCP, recovery, telemetry, and profiling"},{"op":"add","path":"/metadata/expected_result","value":"No supplied literal, regex source, or replacement is reflected by any success or failure surface; useful non-reversible counts remain"},{"op":"add","path":"/metadata/actual_result","value":"Canary acceptance and 63 focused tests prove non-disclosure; 6691 tests and exact 100/100/100/100 coverage plus strict gates pass"}],"before_hash":"f5d30dbf12f7c9419e529122c8f2cc204e96a15bad9126b2a6150c2cd7cad78e","after_hash":"4d81d68c3ec4ac8097fa1e82e46b9ca2489d69e9ca290e0fb8c92f7751abaf2f","message":"Backfill strict closure fields from completed evidence"} +{"ts":"2026-08-02T01:28:07.678Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/10/note","value":"MCP disclosure-safe integration coverage"},{"op":"replace","path":"/metadata/files/10/path","value":"tests/unit/mcp/mcp-server-nested-options.spec.ts"},{"op":"replace","path":"/metadata/files/9/note","value":"Telemetry sensitive-input regression coverage"},{"op":"replace","path":"/metadata/files/9/path","value":"tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts"},{"op":"replace","path":"/metadata/files/8/note","value":"CLI success dry-run no-op and error disclosure coverage"},{"op":"replace","path":"/metadata/files/8/path","value":"tests/unit/commands/history-activity-command.spec.ts"},{"op":"replace","path":"/metadata/files/7/note","value":"Recovery argv canary coverage"},{"op":"replace","path":"/metadata/files/7/path","value":"tests/unit/cli/argv-utils.spec.ts"},{"op":"replace","path":"/metadata/files/6/note","value":"Disclosure-safe public result contract and serialization"},{"op":"replace","path":"/metadata/files/6/path","value":"src/sdk/history-redact.ts"},{"op":"replace","path":"/metadata/files/5/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/5/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/4/note","value":"Sensitive redaction-option telemetry filtering"},{"op":"replace","path":"/metadata/files/4/path","value":"src/core/telemetry/runtime.ts"},{"op":"replace","path":"/metadata/files/3/note","value":"Sanitized recovery and typo diagnostics"},{"op":"replace","path":"/metadata/files/3/path","value":"src/cli/main.ts"},{"op":"replace","path":"/metadata/files/2/note","value":"CLI export for recovery sanitization"},{"op":"replace","path":"/metadata/files/2/path","value":"src/cli/argv-utils.ts"},{"op":"replace","path":"/metadata/files/1/note","value":"Acknowledged security-hardening SDK contract change"},{"op":"replace","path":"/metadata/files/1/path","value":"sdk/public-surface.json"},{"op":"replace","path":"/metadata/files/0/note","value":"Package-owned unreleased security changelog entry"},{"op":"replace","path":"/metadata/files/0/path","value":"CHANGELOG.md"},{"op":"add","path":"/metadata/files/11","value":{"path":"tests/unit/sdk/history-read-primitives.spec.ts","scope":"project","note":"Public SDK result contract coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:28:07.678Z"}],"before_hash":"4d81d68c3ec4ac8097fa1e82e46b9ca2489d69e9ca290e0fb8c92f7751abaf2f","after_hash":"4c7afbd2d524cff4532c2326e4f7ffce59c425c3de72e1aad8358aad1c5b89f1"} +{"ts":"2026-08-02T01:28:08.941Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/11/note","value":"MCP disclosure-safe integration coverage"},{"op":"replace","path":"/metadata/files/11/path","value":"tests/unit/mcp/mcp-server-nested-options.spec.ts"},{"op":"replace","path":"/metadata/files/10/note","value":"Telemetry sensitive-input regression coverage"},{"op":"replace","path":"/metadata/files/10/path","value":"tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts"},{"op":"replace","path":"/metadata/files/9/note","value":"CLI success dry-run no-op and error disclosure coverage"},{"op":"replace","path":"/metadata/files/9/path","value":"tests/unit/commands/history-activity-command.spec.ts"},{"op":"replace","path":"/metadata/files/8/note","value":"Recovery argv canary coverage"},{"op":"replace","path":"/metadata/files/8/path","value":"tests/unit/cli/argv-utils.spec.ts"},{"op":"replace","path":"/metadata/files/7/note","value":"Disclosure-safe public result contract and serialization"},{"op":"replace","path":"/metadata/files/7/path","value":"src/sdk/history-redact.ts"},{"op":"replace","path":"/metadata/files/6/note","value":"Disclosure-safe recovery argv primitive"},{"op":"replace","path":"/metadata/files/6/path","value":"src/sdk/command-line.ts"},{"op":"replace","path":"/metadata/files/5/note","value":"Sensitive redaction-option telemetry filtering"},{"op":"replace","path":"/metadata/files/5/path","value":"src/core/telemetry/runtime.ts"},{"op":"replace","path":"/metadata/files/4/note","value":"Sanitized recovery and typo diagnostics"},{"op":"replace","path":"/metadata/files/4/path","value":"src/cli/main.ts"},{"op":"replace","path":"/metadata/files/3/note","value":"CLI export for recovery sanitization"},{"op":"replace","path":"/metadata/files/3/path","value":"src/cli/argv-utils.ts"},{"op":"replace","path":"/metadata/files/2/note","value":"Acknowledged security-hardening SDK contract change"},{"op":"replace","path":"/metadata/files/2/path","value":"sdk/public-surface.json"},{"op":"replace","path":"/metadata/files/1/note","value":"Package-owned unreleased security changelog entry"},{"op":"replace","path":"/metadata/files/1/path","value":"CHANGELOG.md"},{"op":"replace","path":"/metadata/files/0/note","value":"Latest pm-changelog 2026.8.1 install receipt"},{"op":"replace","path":"/metadata/files/0/path","value":".agents/pm/extensions/.managed-extensions.json"},{"op":"add","path":"/metadata/files/12","value":{"path":"tests/unit/sdk/history-read-primitives.spec.ts","scope":"project","note":"Public SDK result contract coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:28:08.941Z"}],"before_hash":"4c7afbd2d524cff4532c2326e4f7ffce59c425c3de72e1aad8358aad1c5b89f1","after_hash":"b0cd5a902e76ca7dbc23033821098c366cb7af83591c4d6a78fc5103ce5dffa7"} +{"ts":"2026-08-02T01:28:11.945Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/2","value":{"created_at":"2026-08-02T01:28:11.945Z","author":"harness:codex","text":"Changelog evidence: npm registry latest pm-changelog is 2026.8.1; package-owned generation added the Unreleased security entry and changelog:pm:check reproduced it byte-for-byte."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:28:11.945Z"}],"before_hash":"b0cd5a902e76ca7dbc23033821098c366cb7af83591c4d6a78fc5103ce5dffa7","after_hash":"f3cccbe86744d6fa7fd3d5a0deada2f4697e633afcf6965e027aaa7536488a79"} +{"ts":"2026-08-02T01:29:54.208Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/3","value":{"created_at":"2026-08-02T01:29:54.208Z","author":"harness:codex","text":"PR evidence: https://github.com/unbraind/pm-cli/pull/869 opened from exact implementation commit 0761e4fd8. The PR links this canonical item and closes GitHub issue #864; final exact-head review/check evidence will be appended after CI and bot review."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:29:54.208Z"}],"before_hash":"f3cccbe86744d6fa7fd3d5a0deada2f4697e633afcf6965e027aaa7536488a79","after_hash":"e0a18d6271bfb53807e1f1c4ec59510028b6c200cf993d2b4c73fa1f61cd31ff"} +{"ts":"2026-08-02T01:36:44.377Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"remove","path":"/metadata/close_reason"},{"op":"remove","path":"/metadata/completed_at"},{"op":"remove","path":"/metadata/closed_at"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:36:44.377Z"},{"op":"replace","path":"/metadata/status","value":"in_progress"}],"before_hash":"e0a18d6271bfb53807e1f1c4ec59510028b6c200cf993d2b4c73fa1f61cd31ff","after_hash":"979210192b5cd3ec8f970236755fa9e78c81b4cbdc73dffb284d29869aaf5917","message":"Reopen to address exact-head CodeRabbit security findings on PR #869"} +{"ts":"2026-08-02T01:36:45.424Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"claim","patch":[{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:36:45.424Z"},{"op":"add","path":"/metadata/assignee","value":"harness:codex"},{"op":"add","path":"/metadata/claim_principal","value":"harness:codex#7d49453d0d45fcc94817af12"}],"before_hash":"979210192b5cd3ec8f970236755fa9e78c81b4cbdc73dffb284d29869aaf5917","after_hash":"11596e00de239b0860bc3031add091439f6fc3d0c136a067ad78bdab8c9826d9"} +{"ts":"2026-08-02T01:36:46.411Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/4","value":{"created_at":"2026-08-02T01:36:46.411Z","author":"harness:codex","text":"Review round 1: CodeRabbit reported four actionable findings at head 178917ac6: plural inline telemetry assignments, inherited recovery payload fields, sensitive equals-form flag typos, and value-less sensitive flags followed by another option. All four were verified against current code and are being fixed; CI otherwise passed every required check."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:36:46.411Z"}],"before_hash":"11596e00de239b0860bc3031add091439f6fc3d0c136a067ad78bdab8c9826d9","after_hash":"a64369189bb38f689afb13ace315a00ffacca2d01b6408f74ef36ffa044469d2"} +{"ts":"2026-08-02T01:40:18.842Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"files_add","patch":[{"op":"replace","path":"/metadata/files/12/note","value":"MCP disclosure-safe integration coverage"},{"op":"replace","path":"/metadata/files/12/path","value":"tests/unit/mcp/mcp-server-nested-options.spec.ts"},{"op":"replace","path":"/metadata/files/11/note","value":"Telemetry sensitive-input regression coverage"},{"op":"replace","path":"/metadata/files/11/path","value":"tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts"},{"op":"replace","path":"/metadata/files/10/note","value":"CLI success dry-run no-op and error disclosure coverage"},{"op":"replace","path":"/metadata/files/10/path","value":"tests/unit/commands/history-activity-command.spec.ts"},{"op":"replace","path":"/metadata/files/9/note","value":"Inherited recovery and sensitive typo canary coverage"},{"op":"replace","path":"/metadata/files/9/path","value":"tests/unit/cli/cli-main-errors.spec.ts"},{"op":"add","path":"/metadata/files/13","value":{"path":"tests/unit/sdk/history-read-primitives.spec.ts","scope":"project","note":"Public SDK result contract coverage"}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:40:18.842Z"}],"before_hash":"a64369189bb38f689afb13ace315a00ffacca2d01b6408f74ef36ffa044469d2","after_hash":"37b5c564a579099343ae8a51ebb4ee1e1c5038da08a8d862d50bfe41383f7273"} +{"ts":"2026-08-02T01:40:20.781Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/5","value":{"created_at":"2026-08-02T01:40:20.781Z","author":"harness:codex","text":"Review fixes implemented: plural inline assignment patterns are redacted; existing compact/expanded recovery fields are rebuilt from sanitized argv; sensitive typo source and canonical equals-form values are redacted; and value-less sensitive flags preserve following options. Focused ESLint, typecheck, and 136 tests pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T01:40:20.781Z"}],"before_hash":"37b5c564a579099343ae8a51ebb4ee1e1c5038da08a8d862d50bfe41383f7273","after_hash":"f7b60fdcb33e0021692b78353573ce797ef39773d16e09ab8d28dd5116f1df92"} +{"ts":"2026-08-02T02:00:45.358Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"comment_add","patch":[{"op":"add","path":"/metadata/comments/6","value":{"created_at":"2026-08-02T02:00:45.358Z","author":"harness:codex","text":"Review-fix verification: final refactored head passes 417/417 files and 6692/6692 tests with exact 100% statements (47395/47395), branches (34132/34132), functions (9645/9645), and lines (45991/45991). Full lint/static/docstring/zero-duplicate, contracts, security, docs links, SDK surface/import cost, CLI transport, token budget, and context calibration gates pass."}},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T02:00:45.358Z"}],"before_hash":"f7b60fdcb33e0021692b78353573ce797ef39773d16e09ab8d28dd5116f1df92","after_hash":"9dd2008609a5793cf74ac12fbf01ee38ca2de6a1ab559c172468730c3368978a"} +{"ts":"2026-08-02T02:00:46.072Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"update","patch":[{"op":"replace","path":"/metadata/actual_result","value":"Canary acceptance and 136 focused tests prove non-disclosure including four review edge cases; 6692 tests and exact 100/100/100/100 coverage plus strict gates pass"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T02:00:46.072Z"}],"before_hash":"9dd2008609a5793cf74ac12fbf01ee38ca2de6a1ab559c172468730c3368978a","after_hash":"47f3b3586a3f962ae014b4a63391505d38f0d5c4b21c79eb53c18bd8dc4004e7","message":"Refresh actual result after review remediation"} +{"ts":"2026-08-02T02:00:52.180Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"close","patch":[{"op":"remove","path":"/metadata/assignee"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T02:00:52.180Z"},{"op":"replace","path":"/metadata/status","value":"closed"},{"op":"add","path":"/metadata/closed_at","value":"2026-08-02T02:00:52.175Z"},{"op":"add","path":"/metadata/completed_at","value":"2026-08-02T02:00:52.175Z"},{"op":"add","path":"/metadata/close_reason","value":"Resolved GH-864 and all four CodeRabbit review findings with disclosure-safe results, recovery, typo, telemetry, and option-boundary handling, backed by exact 100% coverage and strict repository gates."}],"before_hash":"47f3b3586a3f962ae014b4a63391505d38f0d5c4b21c79eb53c18bd8dc4004e7","after_hash":"6e8f8f846166c238ed45923d5fca72ff85c46abc29f37acf8ece8adb42148702"} +{"ts":"2026-08-02T02:00:53.021Z","author":"harness:codex","author_source":"detected","agent_harness":"codex","agent_instance":"7d49453d0d45fcc94817af12","agent_provenance":{"model":null,"effort":null,"role":null,"topic":null},"op":"release","patch":[{"op":"remove","path":"/metadata/claim_principal"},{"op":"replace","path":"/metadata/updated_at","value":"2026-08-02T02:00:53.021Z"}],"before_hash":"6e8f8f846166c238ed45923d5fca72ff85c46abc29f37acf8ece8adb42148702","after_hash":"a40e10653fef2a65cbd632dbdf5222da7ced41acc6682378bec696f89201b9b2"} diff --git a/.agents/pm/issues/pm-y3w0ld.toon b/.agents/pm/issues/pm-y3w0ld.toon new file mode 100644 index 000000000..ed4c9671b --- /dev/null +++ b/.agents/pm/issues/pm-y3w0ld.toon @@ -0,0 +1,68 @@ +id: pm-y3w0ld +title: "GH-864: history-redact output must never echo literal secrets" +description: "GitHub issue #864 demonstrates that the audited redaction mutation rewrites history safely but returns the original literal matcher under patterns.literals, copying the value into terminal logs, CI output, agent transcripts, and SDK/MCP responses. Make the result contract disclosure-safe across applied, dry-run, no-op, and failure paths while preserving deterministic matcher counts and auditability." +type: Issue +status: closed +priority: 1 +tags[5]: "area:sdk","area:security",history,privacy,redaction +created_at: "2026-08-02T00:40:15.547Z" +updated_at: "2026-08-02T02:00:53.021Z" +closed_at: "2026-08-02T02:00:52.175Z" +completed_at: "2026-08-02T02:00:52.175Z" +author: "harness:codex" +acceptance_criteria: "Raw literal matcher values are absent from TOON, JSON, SDK, MCP, dry-run, no-op, error, telemetry, and profile outputs; regex matcher source receives equally safe treatment because pattern text may itself be sensitive; results retain deterministic matcher-kind counts and replacement/change counts without reversible matcher material; regression tests use canary matchers and assert absence from serialized success, dry-run, no-op, and failure surfaces; public SDK types and operator documentation define the disclosure-safe result contract; full 100/100/100/100 coverage, security scan, contract gates, and isolated temporary-directory acceptance pass" +goal: project management = context management +objective: A secret supplied for audited removal is never repeated by the command that removes it +value: "History redaction must reduce disclosure, never multiply it into output and observability surfaces" +impact: "Changes the public HistoryRedactResult pattern summary to non-reversible metadata and hardens CLI, SDK, MCP, telemetry, and tests against canary disclosure" +outcome: "Applied, preview, no-op, and rejected redaction invocations expose useful counts without returning matcher content" +parent: pm-4a7m +risk: high +confidence: high +resolution: "Disclosure-safe history-redact output across CLI, SDK, MCP, recovery, telemetry, and profiling" +expected_result: "No supplied literal, regex source, or replacement is reflected by any success or failure surface; useful non-reversible counts remain" +actual_result: Canary acceptance and 136 focused tests prove non-disclosure including four review edge cases; 6692 tests and exact 100/100/100/100 coverage plus strict gates pass +dependencies[4]{id,kind,created_at,author,source_kind,author_source}: + pm-4a7m,verifies,"2026-08-02T00:41:03.670Z","harness:codex","cli:update:dep",detected + pm-qwoy,implements,"2026-08-02T00:41:03.670Z","harness:codex","cli:update:dep",detected + pm-wwooxx,related_to,"2026-08-02T00:41:03.670Z","harness:codex","cli:update:dep",detected + pm-xk39,discovered_from,"2026-08-02T00:41:03.670Z","harness:codex","cli:update:dep",detected +comments[7]{created_at,author,text}: + "2026-08-02T01:25:27.533Z","harness:codex","Implementation evidence: public HistoryRedactResult now exposes matcher-kind counts and replacement_is_default only; CLI recovery and typo guidance sanitize history-redact argv; telemetry redacts literal/regex/replacement inputs while retaining safe count fields; CLI, SDK, MCP, telemetry, dry-run, no-op, applied, and rejected paths have canary non-disclosure coverage. The public SDK surface records the acknowledged GH-864 security contract change." + "2026-08-02T01:25:33.533Z","harness:codex","Verification evidence: final source head passes 417/417 test files and 6691/6691 tests with exact 100% statements (47379/47379), branches (34105/34105), functions (9641/9641), and lines (45975/45975). pnpm lint passed with zero ESLint warnings and zero duplication clones; static quality passed 100% docstring/export/member coverage and SDK/package/token/context gates; contracts, secrets, docs links, context/retrieval/agent-task token evaluations, SDK surface, typecheck, and focused 63-test matrix passed." + "2026-08-02T01:28:11.945Z","harness:codex","Changelog evidence: npm registry latest pm-changelog is 2026.8.1; package-owned generation added the Unreleased security entry and changelog:pm:check reproduced it byte-for-byte." + "2026-08-02T01:29:54.208Z","harness:codex","PR evidence: https://github.com/unbraind/pm-cli/pull/869 opened from exact implementation commit 0761e4fd8. The PR links this canonical item and closes GitHub issue #864; final exact-head review/check evidence will be appended after CI and bot review." + "2026-08-02T01:36:46.411Z","harness:codex","Review round 1: CodeRabbit reported four actionable findings at head 178917ac6: plural inline telemetry assignments, inherited recovery payload fields, sensitive equals-form flag typos, and value-less sensitive flags followed by another option. All four were verified against current code and are being fixed; CI otherwise passed every required check." + "2026-08-02T01:40:20.781Z","harness:codex","Review fixes implemented: plural inline assignment patterns are redacted; existing compact/expanded recovery fields are rebuilt from sanitized argv; sensitive typo source and canonical equals-form values are redacted; and value-less sensitive flags preserve following options. Focused ESLint, typecheck, and 136 tests pass." + "2026-08-02T02:00:45.358Z","harness:codex","Review-fix verification: final refactored head passes 417/417 files and 6692/6692 tests with exact 100% statements (47395/47395), branches (34132/34132), functions (9645/9645), and lines (45991/45991). Full lint/static/docstring/zero-duplicate, contracts, security, docs links, SDK surface/import cost, CLI transport, token budget, and context calibration gates pass." +notes[1]{created_at,author,text}: + "2026-08-02T01:25:29.335Z","harness:codex","Manual acceptance used an unrelated temporary tracker and exercised dry-run, applied, no-op, and invalid-regex paths. All matcher and replacement canaries were absent from captured stdout/stderr; applied history verified successfully and added the audit marker only when a rewrite occurred." +learnings[1]{created_at,author,text}: + "2026-08-02T01:25:31.961Z","harness:codex","Inputs supplied to a redaction primitive are sensitive by purpose, even when they do not resemble credentials. Disclosure safety must cover success results, failures, recovery bundles, telemetry, profiling, and transport adapters; counts are the useful non-reversible substitute." +files[14]{path,scope,note}: + .agents/pm/extensions/.managed-extensions.json,project,Latest pm-changelog 2026.8.1 install receipt + CHANGELOG.md,project,Package-owned unreleased security changelog entry + sdk/public-surface.json,project,Acknowledged security-hardening SDK contract change + src/cli/argv-utils.ts,project,CLI export for recovery sanitization + src/cli/main.ts,project,Sanitized recovery and typo diagnostics + src/core/telemetry/runtime.ts,project,Sensitive redaction-option telemetry filtering + src/sdk/command-line.ts,project,Disclosure-safe recovery argv primitive + src/sdk/history-redact.ts,project,Disclosure-safe public result contract and serialization + tests/unit/cli/argv-utils.spec.ts,project,Recovery argv canary coverage + tests/unit/cli/cli-main-errors.spec.ts,project,Inherited recovery and sensitive typo canary coverage + tests/unit/commands/history-activity-command.spec.ts,project,CLI success dry-run no-op and error disclosure coverage + tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts,project,Telemetry sensitive-input regression coverage + tests/unit/mcp/mcp-server-nested-options.spec.ts,project,MCP disclosure-safe integration coverage + tests/unit/sdk/history-read-primitives.spec.ts,project,Public SDK result contract coverage +tests[7]{command,scope,timeout_seconds}: + node scripts/run-tests.mjs coverage -- --testTimeout=120000,project,900 + node scripts/run-tests.mjs test -- tests/unit/sdk/history-read-primitives.spec.ts tests/unit/commands/history-activity-command.spec.ts,project,300 + "pnpm contracts:check",project,300 + pnpm lint,project,300 + "pnpm quality:context-eval",project,300 + "pnpm quality:docs-links",project,120 + "pnpm security:scan",project,120 +docs[1]{path,scope,note}: + docs/COMMANDS.md,project,Safe redaction output semantics +close_reason: "Resolved GH-864 and all four CodeRabbit review findings with disclosure-safe results, recovery, typo, telemetry, and option-boundary handling, backed by exact 100% coverage and strict repository gates." +body: "" diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f453765b..87c9b5b59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Security + +- GH-864: history-redact output must never echo literal secrets ([pm-y3w0ld](https://github.com/unbraind/pm-cli/blob/main/.agents/pm/issues/pm-y3w0ld.toon)) + ## 2026.8.2 - 2026-08-02 ### Added diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 2000aee8d..23fb42baa 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -914,7 +914,7 @@ pm stats --by-tag --tag-prefix domain: --json ``` For governance dashboards, `--metadata-coverage` adds a `metadata_coverage` block reporting per-field `present`/`applicable`/`percent` for `acceptance_criteria`, `estimated_minutes`, `resolution`, `tags`, and `parent` — overall and `by_type` (resolution coverage is scoped to terminal items, its only applicable population). `--field-utilization` adds a `field_utilization` block reporting `present`/`total`/`percent` for each content field (`notes`, `learnings`, `files`, `docs`, `tests`, `comments`, `deps`, `body`, `linked_command`) across all items, so under-documented content dimensions are visible at a glance and pair naturally with the `--has-*`/`--no-*` list filters for drill-down. `--by-assignee`, `--by-tag`, and `--by-priority` add a `breakdowns` block with lifecycle-bucketed rows (`open`/`in_progress`/`blocked`/`draft`/`closed`/`canceled`/`other` + `total`) per group; blank keys render an explicit `(unassigned)`/`(untagged)` label. `--by-tag` accepts `--tag-prefix` to restrict counting to a tag namespace (for example `domain:`). All of these sections are gated behind their flags so the default `pm stats` stays token-light; the per-status/per-type distributions (already in `by_status`/`by_type`) zero-fill every configured state so underutilized lifecycle states and item types are visible at a glance. -`history-redact` rewrites matching history payloads deterministically, recomputes hash chains, and appends an auditable `history_redact` marker entry when changes are applied. +`history-redact` rewrites matching history payloads deterministically, recomputes hash chains, and appends an auditable `history_redact` marker entry when changes are applied. Its result reports only `literal_count`, `regex_count`, `total_count`, and whether the default replacement was selected; literal values, regex source text, and replacement text are never echoed in CLI, SDK, MCP, recovery, profile, or telemetry output. Treat regex text as sensitive input because it can contain the exact material being removed. `history-compact` rewrites long streams into a synthetic checkpoint baseline plus a retained tail (`--before` accepts a 1-based version or ISO timestamp), re-anchors hashes, verifies integrity, and appends an auditable `history_compact` marker when applied. `history-compact` bulk mode (mutually exclusive with a positional ``) compacts many streams in one audited pass. Select with `--ids ` (an explicit list — used on its own, not combined with the scan selectors below), or a scan: `--all-over ` (every stream with more than N entries) and/or a lifecycle filter `--closed` (terminal items only) or `--all-streams` (every stream). `--closed` and `--all-streams` are mutually exclusive. `--min-entries ` (default 3) skips already-compact streams; when `history.compact_policy` is enabled and `--all-over` is omitted, the policy's `max_entries` becomes the default threshold. `--before` is single-id only and is rejected in bulk mode. Each selected stream runs the same single-item compaction; one failing stream never aborts the rest — the result reports `totals` (`streams_considered`/`selected`/`items_compacted`/`items_skipped`/`items_errored`) plus one row per stream (`compacted`/`skipped` with a `skip_reason`/`errored`), and the command exits non-zero only if any stream errored. `history-repair` re-anchors a drifted history chain when `pm health`/`pm validate --check-history-drift` report stale hashes: it replays the stream, recomputes every before/after hash, repairs legacy patch ops that no longer strictly apply, reconciles the latest hash with the on-disk item, and appends an auditable `history_repair` marker. It never modifies item content and is a safe no-op on a clean stream. diff --git a/sdk/public-surface.json b/sdk/public-surface.json index bfbf6b41d..26ba71f2e 100644 --- a/sdk/public-surface.json +++ b/sdk/public-surface.json @@ -540,6 +540,13 @@ ], "package_version": "2026.8.1", "reason": "Additive optional tokenAccounting output instrumentation flag shared by CLI SDK and MCP" + }, + { + "changes": [ + "signature ./sdk:HistoryRedactResult" + ], + "package_version": "2026.8.2", + "reason": "Remove secret-bearing matcher and replacement fields from history-redact results (GH-864)" } ], "entrypoints": { @@ -5152,7 +5159,7 @@ "classification": "advanced_export", "kind": "interface", "name": "HistoryRedactResult", - "signature": "export interface HistoryRedactResult { id: string; dry_run: boolean; changed: boolean; patterns: { literals: string[]; regex: string[]; replacement: string; }; history: { path: string; entries_scanned: number; entries_changed: number; replacements: number; hash_mismatches_before: number; hash_mismatches_after: number; preexisting_hash_mismatches: number; audit_entry_added: boolean; verify_ok: boolean; verify_errors: string[]; }; item: { existed_before: boolean; exists_after: boolean; path_before: string | null; path_after: string | null; changed: boolean; }; warnings: string[]; generated_at: string; }" + "signature": "export interface HistoryRedactResult { id: string; dry_run: boolean; changed: boolean; patterns: { literal_count: number; regex_count: number; total_count: number; replacement_is_default: boolean; }; history: { path: string; entries_scanned: number; entries_changed: number; replacements: number; hash_mismatches_before: number; hash_mismatches_after: number; preexisting_hash_mismatches: number; audit_entry_added: boolean; verify_ok: boolean; verify_errors: string[]; }; item: { existed_before: boolean; exists_after: boolean; path_before: string | null; path_after: string | null; changed: boolean; }; warnings: string[]; generated_at: string; }" }, { "classification": "advanced_export", diff --git a/src/cli/argv-utils.ts b/src/cli/argv-utils.ts index f355d23b2..7fc461dc7 100644 --- a/src/cli/argv-utils.ts +++ b/src/cli/argv-utils.ts @@ -37,4 +37,8 @@ export function extractProvidedOptionFlags(argv: string[]): string[] { return ordered; } -export { quoteCommandArg, renderPmCommand } from "../sdk/command-line.js"; +export { + quoteCommandArg, + redactSensitiveCommandArgs, + renderPmCommand, +} from "../sdk/command-line.js"; diff --git a/src/cli/main.ts b/src/cli/main.ts index f38e5d4c7..6bab3266a 100644 --- a/src/cli/main.ts +++ b/src/cli/main.ts @@ -92,7 +92,12 @@ import { } from "./extension-command-options.js"; import { attachRichHelpText } from "./help-content.js"; import { finishActiveTelemetryCommand, recordAfterCommandContextUsage } from "./after-command-context-usage.js"; -import { extractProvidedOptionFlags, normalizeLongOptionFlag, renderPmCommand } from "./argv-utils.js"; +import { + extractProvidedOptionFlags, + normalizeLongOptionFlag, + redactSensitiveCommandArgs, + renderPmCommand, +} from "./argv-utils.js"; import { classifyCommanderError, classifyPmCliError, @@ -404,8 +409,23 @@ function buildRecoveryPayload(params: { } function buildPmCliRecoveryContext(context: PmCliErrorContext | undefined, invocationArgv: string[], rawMessage: string): PmCliErrorContext { - const explainRequested = invocationArgv.includes("--explain"); - const existingRecovery = context?.recovery; + const safeInvocationArgv = redactSensitiveCommandArgs(invocationArgv); + const explainRequested = safeInvocationArgv.includes("--explain"); + const rawExistingRecovery = context?.recovery; + const existingRecovery = rawExistingRecovery && safeInvocationArgv.includes("history-redact") + ? { + ...rawExistingRecovery, + ...(rawExistingRecovery.attempted_command + ? { attempted_command: renderAttemptedCommand(safeInvocationArgv) } + : {}), + ...(rawExistingRecovery.normalized_args + ? { normalized_args: [...safeInvocationArgv] } + : {}), + ...(rawExistingRecovery.suggested_retry + ? { suggested_retry: renderAttemptedCommand(safeInvocationArgv) } + : {}), + } + : rawExistingRecovery; if (existingRecovery?.recovery_mode === "compact" && !explainRequested) { return { /* c8 ignore next */ @@ -413,12 +433,12 @@ function buildPmCliRecoveryContext(context: PmCliErrorContext | undefined, invoc recovery: existingRecovery, }; } - const attemptedCommand = renderAttemptedCommand(invocationArgv); - const providedFields = extractProvidedOptionFlags(invocationArgv); - const inferredMissing = inferMissingFieldsForRecovery(rawMessage, invocationArgv, existingRecovery); - const suggestedRetry = resolveRecoverySuggestedRetry(invocationArgv, attemptedCommand, inferredMissing, existingRecovery); + const attemptedCommand = renderAttemptedCommand(safeInvocationArgv); + const providedFields = extractProvidedOptionFlags(safeInvocationArgv); + const inferredMissing = inferMissingFieldsForRecovery(rawMessage, safeInvocationArgv, existingRecovery); + const suggestedRetry = resolveRecoverySuggestedRetry(safeInvocationArgv, attemptedCommand, inferredMissing, existingRecovery); const recovery = buildRecoveryPayload({ - invocationArgv, + invocationArgv: safeInvocationArgv, attemptedCommand, providedFields, inferredMissing, @@ -2398,6 +2418,50 @@ function shouldRegisterRuntimeSchemaFlags(invocationArgv: string[]): boolean { return RUNTIME_SCHEMA_FLAG_BOOTSTRAP_COMMANDS.has(commandName); } +/** + * Project a typo trace and its canonical target into disclosure-safe source, + * target, and retry values before they are copied into error diagnostics. + */ +function redactSensitiveFlagTypo(params: { + argv: string[]; + normalizedDisplay: string; + normalizedTokens: string[]; + rawFrom: unknown; +}): { argv: string[]; normalizedDisplay: string; sourceDisplay: string } { + const rawTypoToken = String(params.rawFrom ?? ""); + const rawTypoEqualsIndex = rawTypoToken.indexOf("="); + const rawTypoFlag = rawTypoEqualsIndex >= 0 ? rawTypoToken.slice(0, rawTypoEqualsIndex) : rawTypoToken; + const sensitiveCanonicalFlag = params.normalizedTokens + .map((token) => { + const equalsIndex = token.indexOf("="); + return equalsIndex >= 0 ? token.slice(0, equalsIndex) : token; + }) + .find((token) => token === "--literal" || token === "--regex" || token === "--replacement"); + const typoNormalizedArgv = sensitiveCanonicalFlag + ? params.argv.map((token) => { + const equalsIndex = token.indexOf("="); + const flag = equalsIndex >= 0 ? token.slice(0, equalsIndex) : token; + return flag === rawTypoFlag + ? equalsIndex >= 0 + ? `${sensitiveCanonicalFlag}${token.slice(equalsIndex)}` + : sensitiveCanonicalFlag + : token; + }) + : params.argv; + const safeArgv = redactSensitiveCommandArgs(typoNormalizedArgv); + const safeTypoDisplay = sensitiveCanonicalFlag && rawTypoEqualsIndex >= 0 + ? `${rawTypoFlag}=[redacted]` + : rawTypoToken; + const safeNormalizedDisplay = sensitiveCanonicalFlag && params.normalizedDisplay.includes("=") + ? `${sensitiveCanonicalFlag}=[redacted]` + : params.normalizedDisplay; + return { + argv: safeArgv, + normalizedDisplay: safeNormalizedDisplay, + sourceDisplay: safeTypoDisplay, + }; +} + function enforceExplicitRetryForFlagTypos(bootstrapInvocation: ReturnType): void { const commandName = bootstrapInvocation.commandName; if (!commandName) { @@ -2412,16 +2476,22 @@ function enforceExplicitRetryForFlagTypos(bootstrapInvocation: ReturnType token.length > 0); - return SENSITIVE_KEYWORDS.some( - (keyword) => - normalized === keyword || - normalized.endsWith(`_${keyword}`) || - tokens.includes(keyword), + return ( + REDACTION_INPUT_KEYS.has(normalized) || + SENSITIVE_KEYWORDS.some( + (keyword) => + normalized === keyword || + normalized.endsWith(`_${keyword}`) || + tokens.includes(keyword), + ) ); } diff --git a/src/sdk/command-line.ts b/src/sdk/command-line.ts index 789c891a5..eed6904ac 100644 --- a/src/sdk/command-line.ts +++ b/src/sdk/command-line.ts @@ -46,3 +46,44 @@ export const renderPmCommand = ( const args = argv.map((token) => quoteCommandArg(token, platform)).join(" "); return args.length > 0 ? `pm ${args}` : "pm"; }; + +const HISTORY_REDACT_SENSITIVE_FLAGS = new Set([ + "--literal", + "--regex", + "--replacement", +]); + +/** + * Replace history-redaction matcher and replacement values before an argv + * vector is copied into diagnostics or recovery guidance. The values are + * inputs to a disclosure-removal operation and therefore remain sensitive + * even when they do not resemble a conventional credential. + */ +export function redactSensitiveCommandArgs(argv: readonly string[]): string[] { + if (!argv.includes("history-redact")) { + return [...argv]; + } + const redacted: string[] = []; + let redactNext = false; + for (const token of argv) { + if (redactNext && !token.startsWith("-")) { + redacted.push("[redacted]"); + redactNext = false; + continue; + } + redactNext = false; + const equalsIndex = token.indexOf("="); + const flag = equalsIndex >= 0 ? token.slice(0, equalsIndex) : token; + if (!HISTORY_REDACT_SENSITIVE_FLAGS.has(flag)) { + redacted.push(token); + continue; + } + if (equalsIndex >= 0) { + redacted.push(`${flag}=[redacted]`); + continue; + } + redacted.push(flag); + redactNext = true; + } + return redacted; +} diff --git a/src/sdk/history-redact.ts b/src/sdk/history-redact.ts index 97110dffb..5e0a9212c 100644 --- a/src/sdk/history-redact.ts +++ b/src/sdk/history-redact.ts @@ -68,13 +68,11 @@ interface RegexRule { kind: "regex"; source: string; flags: string; - label: string; } interface LiteralRule { kind: "literal"; value: string; - label: string; } type RedactionRule = RegexRule | LiteralRule; @@ -124,9 +122,14 @@ export interface HistoryRedactResult { changed: boolean; /** Value that configures or reports patterns for this contract. */ patterns: { - literals: string[]; - regex: string[]; - replacement: string; + /** Number of unique literal matchers accepted without disclosing their values. */ + literal_count: number; + /** Number of unique regular-expression matchers accepted without disclosing their source. */ + regex_count: number; + /** Total number of accepted matchers across both matcher kinds. */ + total_count: number; + /** Whether the canonical default replacement was selected implicitly. */ + replacement_is_default: boolean; }; /** Value that configures or reports history for this contract. */ history: { @@ -208,10 +211,10 @@ function parseRegexRule(spec: string): RegexRule { } try { new RegExp(source, flags); - } catch (error) { + } catch { /* c8 ignore start -- RegExp constructor failures are normalized in higher-level parser tests. */ throw new PmCliError( - `Invalid --regex value "${spec}": ${error instanceof Error ? error.message : String(error)}`, + "Invalid --regex value; provide a valid JavaScript regular expression.", EXIT_CODE.USAGE, ); /* c8 ignore stop */ @@ -221,7 +224,6 @@ function parseRegexRule(spec: string): RegexRule { kind: "regex", source, flags, - label: `/${source}/${flags}`, }; } @@ -238,7 +240,6 @@ function buildRedactionRules( .map((entry) => ({ kind: "literal", value: entry, - label: entry, })); const regexRules = [ ...new Set( @@ -526,7 +527,7 @@ function resolveHistoryRedactNextItem(params: { ); if (canonical.metadata.id !== params.subject.id) { throw new PmCliError( - `history-redact would change item id from ${params.subject.id} to ${canonical.metadata.id}; narrow your patterns.`, + "history-redact would change the item id; narrow your patterns.", EXIT_CODE.USAGE, ); } @@ -777,10 +778,9 @@ export async function runHistoryRedact( settings, getActiveExtensionRegistrations(), ); - const replacement = - typeof options.replacement === "string" && options.replacement.length > 0 - ? options.replacement - : "[redacted]"; + const replacementInput = options.replacement ?? ""; + const replacementIsDefault = replacementInput.length === 0; + const replacement = replacementIsDefault ? "[redacted]" : replacementInput; const rules = buildRedactionRules(options.literal, options.regex); const subject = await resolveHistorySubject( pmRoot, @@ -881,13 +881,10 @@ export async function runHistoryRedact( dry_run: dryRun, changed, patterns: { - literals: rules - .filter((rule): rule is LiteralRule => rule.kind === "literal") - .map((rule) => rule.value), - regex: rules - .filter((rule): rule is RegexRule => rule.kind === "regex") - .map((rule) => `/${rule.source}/${rule.flags}`), - replacement, + literal_count: rules.filter((rule) => rule.kind === "literal").length, + regex_count: rules.filter((rule) => rule.kind === "regex").length, + total_count: rules.length, + replacement_is_default: replacementIsDefault, }, history: { path: subject.historyPath, diff --git a/tests/unit/cli/argv-utils.spec.ts b/tests/unit/cli/argv-utils.spec.ts index c7b3cfdc5..da917e116 100644 --- a/tests/unit/cli/argv-utils.spec.ts +++ b/tests/unit/cli/argv-utils.spec.ts @@ -5,6 +5,7 @@ import { normalizeLongFlag, normalizeLongOptionFlag, quoteCommandArg, + redactSensitiveCommandArgs, renderPmCommand, } from "../../../src/cli/argv-utils.js"; @@ -20,6 +21,46 @@ describe("argv-utils.normalizeLongOptionFlag", () => { }); }); +describe("argv-utils.redactSensitiveCommandArgs", () => { + it("preserves ordinary commands and redacts every history-redact input form", () => { + expect(redactSensitiveCommandArgs(["get", "pm-example"])).toEqual([ + "get", + "pm-example", + ]); + expect( + redactSensitiveCommandArgs([ + "history-redact", + "pm-example", + "--literal", + "literal-canary", + "--regex=regex-canary", + "--replacement", + "replacement-canary", + "--dry-run", + ]), + ).toEqual([ + "history-redact", + "pm-example", + "--literal", + "[redacted]", + "--regex=[redacted]", + "--replacement", + "[redacted]", + "--dry-run", + ]); + expect(redactSensitiveCommandArgs(["history-redact", "--literal"])).toEqual( + ["history-redact", "--literal"], + ); + expect( + redactSensitiveCommandArgs([ + "history-redact", + "--literal", + "--dry-run", + ]), + ).toEqual(["history-redact", "--literal", "--dry-run"]); + }); +}); + describe("argv-utils.normalizeLongFlag", () => { it("normalizes camelCase and snake_case into canonical long flags", () => { expect(normalizeLongFlag("dryRun")).toBe("--dry-run"); diff --git a/tests/unit/cli/cli-main-errors.spec.ts b/tests/unit/cli/cli-main-errors.spec.ts index c83cb6b8d..1a881ce19 100644 --- a/tests/unit/cli/cli-main-errors.spec.ts +++ b/tests/unit/cli/cli-main-errors.spec.ts @@ -579,6 +579,41 @@ describe("CLI main error helpers", () => { }); }); + it("sanitizes inherited history-redact recovery fields in compact and expanded modes", () => { + const canary = "inherited-recovery-canary-864"; + for (const explain of [false, true]) { + const invocationArgv = [ + "history-redact", + "pm-example", + `--literal=${canary}`, + ...(explain ? ["--explain"] : []), + ]; + const context = _testOnly.buildPmCliRecoveryContext( + { + recovery: { + recovery_mode: "compact", + attempted_command: `pm history-redact pm-example --literal=${canary}`, + normalized_args: [ + "history-redact", + "pm-example", + `--literal=${canary}`, + ], + suggested_retry: `pm history-redact pm-example --literal=${canary}`, + }, + }, + invocationArgv, + "Invalid history-redact input", + ); + + expect(JSON.stringify(context)).not.toContain(canary); + expect(context.recovery).toMatchObject({ + attempted_command: expect.stringContaining("--literal=[redacted]"), + normalized_args: expect.arrayContaining(["--literal=[redacted]"]), + suggested_retry: expect.stringContaining("--literal=[redacted]"), + }); + } + }); + it("expands compact recovery payloads when explain is requested", () => { const context = _testOnly.buildPmCliRecoveryContext( { @@ -3502,6 +3537,91 @@ export default { trace: [{ reason: "flag_typo", from: "--stats", to: undefined } as never], }), ).toThrow("to the canonical flag"); + + for (const { typo, canonical } of [ + { typo: "--litteral", canonical: "--literal" }, + { typo: "--regx", canonical: "--regex" }, + { typo: "--replacment", canonical: "--replacement" }, + ]) { + const canary = `${canonical.slice(2)}-typo-canary-864`; + let thrown: unknown; + try { + _testOnly.enforceExplicitRetryForFlagTypos({ + commandName: "history-redact", + argv: ["history-redact", "pm-example", `${typo}=${canary}`], + trace: [ + { + reason: "flag_typo", + from: `${typo}=${canary}`, + to: `${canonical}=${canary}`, + }, + ], + }); + } catch (error) { + thrown = error; + } + expect(thrown).toBeDefined(); + expect(JSON.stringify(thrown)).not.toContain(canary); + expect(thrown).toMatchObject({ + message: expect.not.stringContaining(canary), + }); + expect(thrown).toMatchObject({ + message: expect.stringContaining(`${typo}=[redacted]`), + context: { + examples: [expect.stringContaining(`${canonical}=[redacted]`)], + recovery: { + normalized_args: expect.arrayContaining([ + `${canonical}=[redacted]`, + ]), + suggested_retry: expect.stringContaining( + `${canonical}=[redacted]`, + ), + }, + }, + }); + } + + const separateValueCanary = "separate-typo-canary-864"; + let separateValueError: unknown; + try { + _testOnly.enforceExplicitRetryForFlagTypos({ + commandName: "history-redact", + argv: [ + "history-redact", + "pm-example", + "--litteral", + separateValueCanary, + ], + trace: [ + { reason: "flag_typo", from: "--litteral", to: "--literal" }, + ], + }); + } catch (error) { + separateValueError = error; + } + expect(separateValueError).toMatchObject({ + context: { + recovery: { + normalized_args: expect.arrayContaining([ + "--literal", + "[redacted]", + ]), + }, + }, + }); + expect(JSON.stringify(separateValueError)).not.toContain( + separateValueCanary, + ); + + expect(() => + _testOnly.enforceExplicitRetryForFlagTypos({ + commandName: "history-redact", + argv: ["history-redact", "--literal", "value"], + trace: [ + { reason: "flag_typo", from: undefined, to: "--literal" } as never, + ], + }), + ).toThrow("Refusing to auto-correct"); }); }); diff --git a/tests/unit/commands/history-activity-command.spec.ts b/tests/unit/commands/history-activity-command.spec.ts index 19a11edc1..97034138a 100644 --- a/tests/unit/commands/history-activity-command.spec.ts +++ b/tests/unit/commands/history-activity-command.spec.ts @@ -106,12 +106,16 @@ describe("runHistory and runActivity", () => { [], ); expect(historyRedactInternals.normalizeRegexFlags("igi")).toBe("ig"); - expect(historyRedactInternals.parseRegexRule("plain").label).toBe( - "/plain/g", - ); - expect(historyRedactInternals.parseRegexRule("/plain/i").label).toBe( - "/plain/ig", - ); + expect(historyRedactInternals.parseRegexRule("plain")).toEqual({ + kind: "regex", + source: "plain", + flags: "g", + }); + expect(historyRedactInternals.parseRegexRule("/plain/i")).toEqual({ + kind: "regex", + source: "plain", + flags: "ig", + }); expect(() => historyRedactInternals.parseRegexRule(" ")).toThrow( PmCliError, ); @@ -123,9 +127,9 @@ describe("runHistory and runActivity", () => { [" secret ", "secret"], "/token-[0-9]+/", ); - expect(rules.map((rule) => rule.label)).toEqual([ - "secret", - "/token-[0-9]+/g", + expect(rules).toEqual([ + { kind: "literal", value: "secret" }, + { kind: "regex", source: "token-[0-9]+", flags: "g" }, ]); expect(() => historyRedactInternals.buildRedactionRules(" ", [])).toThrow( PmCliError, @@ -835,6 +839,8 @@ describe("runHistory and runActivity", () => { expect(dryRunResult.changed).toBe(true); expect(dryRunResult.dry_run).toBe(true); expect(dryRunResult.history.audit_entry_added).toBe(false); + expect(JSON.stringify(dryRunResult)).not.toContain(leakedToken); + expect(JSON.stringify(dryRunResult)).not.toContain("[redacted_token]"); const historyPath = path.join(context.pmPath, "history", `${id}.jsonl`); const historyRaw = await readFile(historyPath, "utf8"); @@ -871,10 +877,15 @@ describe("runHistory and runActivity", () => { { path: context.pmPath }, ); expect(redacted.changed).toBe(true); - expect(redacted.patterns.regex).toEqual([ - "/secret-[0-9]+/ig", - "/secret-456/g", - ]); + expect(redacted.patterns).toEqual({ + literal_count: 0, + regex_count: 2, + total_count: 2, + replacement_is_default: false, + }); + expect(JSON.stringify(redacted)).not.toContain("secret-[0-9]+"); + expect(JSON.stringify(redacted)).not.toContain("secret-456"); + expect(JSON.stringify(redacted)).not.toContain("[secret]"); expect(redacted.history.replacements).toBeGreaterThanOrEqual(2); const noMatchHistoryBefore = await readFile( @@ -893,6 +904,8 @@ describe("runHistory and runActivity", () => { expect(noMatch.changed).toBe(false); expect(noMatch.history.audit_entry_added).toBe(false); expect(noMatch.warnings).toContain("history_redact_no_matches"); + expect(JSON.stringify(noMatch)).not.toContain("definitely-not-present"); + expect(JSON.stringify(noMatch)).not.toContain("[none]"); expect( await readFile( path.join(context.pmPath, "history", `${id}.jsonl`), @@ -963,6 +976,24 @@ describe("runHistory and runActivity", () => { message: expect.stringContaining("Invalid --regex value"), }); + const sensitiveInvalidRegex = "canary-sensitive-regex-["; + const cliFailure = context.runCli([ + "history-redact", + id, + "--regex", + sensitiveInvalidRegex, + "--replacement=canary-sensitive-replacement", + "--json", + ]); + expect(cliFailure.code).toBe(EXIT_CODE.USAGE); + expect(`${cliFailure.stdout}\n${cliFailure.stderr}`).not.toContain( + sensitiveInvalidRegex, + ); + expect(`${cliFailure.stdout}\n${cliFailure.stderr}`).not.toContain( + "canary-sensitive-replacement", + ); + expect(cliFailure.stderr).toContain("[redacted]"); + await expect( runHistoryRedact( id, @@ -975,7 +1006,7 @@ describe("runHistory and runActivity", () => { ), ).rejects.toMatchObject({ exitCode: EXIT_CODE.USAGE, - message: expect.stringContaining("would change item id"), + message: expect.stringContaining("would change the item id"), }); }); }); diff --git a/tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts b/tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts new file mode 100644 index 000000000..7e26c6b80 --- /dev/null +++ b/tests/unit/core/telemetry/history-redact-sensitive-options.spec.ts @@ -0,0 +1,58 @@ +import { describe, expect, it } from "vitest"; +import { _testOnly } from "../../../../src/core/telemetry/runtime.js"; + +describe("history-redact telemetry disclosure safety", () => { + it("redacts matcher and replacement values from argv and option payloads", () => { + const literal = "telemetry-literal-canary-864"; + const regex = "telemetry-regex-canary-864"; + const replacement = "telemetry-replacement-canary-864"; + + const sanitizedArgs = _testOnly.sanitizeCommandArgs([ + "history-redact", + "pm-example", + "--literal", + literal, + `--regex=${regex}`, + "--replacement", + replacement, + ]); + expect(sanitizedArgs).toEqual([ + "history-redact", + "pm-example", + "--literal", + "[redacted]", + "--regex=[redacted]", + "--replacement", + "[redacted]", + ]); + expect( + _testOnly.sanitizeValue({ + literal: [literal], + regex: [regex], + replacement, + }), + ).toEqual({ + literal: "[redacted]", + regex: "[redacted]", + replacement: "[redacted]", + }); + expect( + _testOnly.sanitizeValue({ + literal_count: 1, + regex_count: 1, + total_count: 2, + replacement_is_default: false, + }), + ).toEqual({ + literal_count: 1, + regex_count: 1, + total_count: 2, + replacement_is_default: false, + }); + expect( + _testOnly.sanitizeValue( + `literals=${literal} regexes:${regex} replacements=${replacement}`, + ), + ).toBe("literals=[redacted] regexes:[redacted] replacements=[redacted]"); + }); +}); diff --git a/tests/unit/mcp/mcp-server-nested-options.spec.ts b/tests/unit/mcp/mcp-server-nested-options.spec.ts index d4b15a39c..1bb84f4c6 100644 --- a/tests/unit/mcp/mcp-server-nested-options.spec.ts +++ b/tests/unit/mcp/mcp-server-nested-options.spec.ts @@ -126,6 +126,65 @@ describe("mcp nested option-key validation (pm-upi0)", () => { }); }); + it("never reflects history-redact matcher material through MCP", async () => { + const server = await import("../../../src/mcp/server.js"); + await withTempPmPath(async (context) => { + const canary = "mcp-redaction-canary-864"; + const created = context.runCli( + [ + "create", + "--json", + "--title", + `MCP ${canary}`, + "--description", + "MCP redaction disclosure fixture", + "--type", + "Task", + "--status", + "open", + "--author", + "mcp-redaction-spec", + ], + { expectJson: true }, + ); + const id = (created.json as { item: { id: string } }).item.id; + const response = await server.handleRequest({ + jsonrpc: "2.0", + id: 864, + method: "tools/call", + params: { + name: "pm_run", + arguments: { + action: "history-redact", + id, + path: context.pmPath, + options: { + literal: [canary], + replacement: "[removed]", + dryRun: true, + }, + }, + }, + }); + const serialized = JSON.stringify(response); + expect(serialized).not.toContain(canary); + expect(serialized).not.toContain("[removed]"); + expect(response).toMatchObject({ + structuredContent: { + result: { + changed: true, + patterns: { + literal_count: 1, + regex_count: 0, + total_count: 1, + replacement_is_default: false, + }, + }, + }, + }); + }); + }); + it("resolves contract keys per action and skips unknown or extension actions", async () => { const server = await import("../../../src/mcp/server.js"); const detect = server._testOnly.detectUnexpectedOptionKeys; diff --git a/tests/unit/sdk/history-read-primitives.spec.ts b/tests/unit/sdk/history-read-primitives.spec.ts index d9b7a0c5b..9d7fbda98 100644 --- a/tests/unit/sdk/history-read-primitives.spec.ts +++ b/tests/unit/sdk/history-read-primitives.spec.ts @@ -141,7 +141,16 @@ describe("public SDK history and rich-read primitives", () => { expect( await redactHistory(id, { literal: ["secret"], dryRun: true }), - ).toMatchObject({ id, dry_run: true }); + ).toMatchObject({ + id, + dry_run: true, + patterns: { + literal_count: 1, + regex_count: 0, + total_count: 1, + replacement_is_default: true, + }, + }); expect(await repairHistory(id)).toMatchObject({ id }); expect(await repairAllHistory()).toMatchObject({ all: true }); expect(await compactHistory(id)).toMatchObject({ id });