Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .agents/pm/extensions/.managed-extensions.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
43 changes: 43 additions & 0 deletions .agents/pm/history/pm-y3w0ld.jsonl

Large diffs are not rendered by default.

68 changes: 68 additions & 0 deletions .agents/pm/issues/pm-y3w0ld.toon
Original file line number Diff line number Diff line change
@@ -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: ""
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
2 changes: 1 addition & 1 deletion docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<id>`) compacts many streams in one audited pass. Select with `--ids <a,b,c>` (an explicit list — used on its own, not combined with the scan selectors below), or a scan: `--all-over <N>` (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 <N>` (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.
Expand Down
9 changes: 8 additions & 1 deletion sdk/public-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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",
Expand Down
6 changes: 5 additions & 1 deletion src/cli/argv-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Loading