Skip to content

feat(privacy): fail-closed Git identity/secret/host-path gate over all objects - #52

Merged
unbraind merged 6 commits into
mainfrom
feat/zqad-privacy-forward-gate
Aug 21, 2026
Merged

feat(privacy): fail-closed Git identity/secret/host-path gate over all objects#52
unbraind merged 6 commits into
mainfrom
feat/zqad-privacy-forward-gate

Conversation

@unbraind

@unbraind unbraind commented Aug 21, 2026

Copy link
Copy Markdown
Owner

Implements the forward gate required by pm-github-zqad.

Stacks on #48 — merge #48 first; this branch contains it plus the privacy gate.

What

  • scripts/privacy-gate.ts: enumerates every object in the local store (git cat-file --batch-all-objects, reachable and unreachable), fails closed on unapproved commit author/committer/tagger identities, high-confidence credentials, and absolute personal host paths. Never prints matched values.
  • .github/approved-git-identities.txt: checked-in, per-entry-justified allowlist. Records the maintainer's 2026-07-21 decision to retain stefan@preu.at as public authoring identity; codex@local is deliberately absent following the maintainer-approved history rewrite executed 2026-08-21.
  • Synthetic review fixtures exempted via content-addressed manifest (exact blob OIDs + justification).
  • 18 behavioral tests using real throwaway git repos: each secret rule, host-path detection, identity roles, exemption precision, negative controls, fail-closed paths.

pm items

Summary by Sourcery

Enforce fail-closed privacy checks across Git history and standardize complete-workspace reads on the current pm CLI contract.

New Features:

  • Add a fail-closed privacy gate that audits all local Git objects for unapproved identities, high-confidence credentials, and personal host paths without exposing matched values.
  • Add reviewed, content-addressed exemptions for synthetic privacy fixtures and an approved Git identity allowlist.

Bug Fixes:

  • Prevent privacy violations in reachable or unreachable Git history from passing release validation.

Enhancements:

  • Adopt the canonical pm --pm-path ... list --all full-workspace read contract and remove reliance on the deprecated list-all alias.
  • Raise the supported pm CLI version floor to 2026.8.20 and document the compatibility change.

CI:

  • Run the privacy audit in CI and release checks.

Documentation:

  • Document the canonical complete-workspace pm CLI invocation and updated compatibility requirements.

Tests:

  • Add behavioral coverage using throwaway Git repositories for identity, credential, host-path, exemption, and fail-closed scenarios.
  • Extend acceptance and compatibility tests for the canonical pm CLI contract.

Chores:

  • Record the privacy gate rollout and related project-management history.

Summary by cubic

Adds a fail-closed Git privacy gate that audits every local object for unapproved identities, high-confidence credentials, and absolute personal host paths. Previously there was no forward gate; now CI and release fail on violations, findings never print matched values, headers without a parseable email or with nonstandard separators fail, and exemptions bind only to reviewed fixture blobs.

  • Review and rollout
    • Gate: scripts/privacy-gate.ts walks git cat-file --batch-all-objects (reachable and unreachable), validates commit author/committer/tagger emails against .github/approved-git-identities.txt using header-only scanning and bare role keyword matching, scans blobs for secrets and absolute host paths, and allows exemptions only via a content-addressed blob-OID manifest intersected with fixture blobs present at HEAD.
    • CI and release: .github/workflows/ci.yml runs npm run privacy. CI audits refs/tags; the local release check additionally covers unreachable objects.
    • CLI contract: replace deprecated pm list-all with pm list --all and explicit unbounded output controls; raise floors to @unbrained/pm-cli >=2026.8.20 (manifest and peer), and update docs.
    • Required actions: when adding a new author/committer/tagger, add their email with justification to .github/approved-git-identities.txt; ensure local environments use @unbrained/pm-cli 2026.8.20 or newer.

Written for commit 3c1531a. Summary will update on new commits.

Review in cubic

Replace the deprecated list-all subprocess contract with explicit host-owned unbounded output controls and list --all on pm CLI 2026.8.20.

Add exact argv and real installed npm/bun acceptance, align manifest/peer/dev floors, update all package fixtures and documentation, preserve complete changelog generation, and record PM evidence plus independent privacy, release-order, and exact-coverage blockers.
Document the pm 2026.8.20 compatibility break, remove unsafe narrowing advice from complete-corpus buffer errors, and route the real installed-CLI envelope through the production decoder. Preserve the pm-changelog release blocker because no published version contains PR #148.
v2kt: release publish ordering fix verified live on main (PR #46).
zqad: maintainer-approved history rewrite executed 2026-08-21 - /home/steve
-> $HOME across 15 blobs, codex@local -> approved noreply identity, all
heads/tags force-pushed after fresh-clone validation (HEAD tree identical,
261/261 tests green). Forward identity/host-path gate remains open on zqad.
…very Git object

Implements the forward gate required by pm-github-zqad:

- scripts/privacy-gate.ts walks the local object store via
  git cat-file --batch-all-objects (reachable AND unreachable), checks
  commit author / committer / annotated-tag tagger emails against a
  checked-in justified allowlist, and scans every blob for
  high-confidence credential signatures and absolute personal host paths.
  Findings name only rule + object id; matched values are never printed.
- .github/approved-git-identities.txt: justified allowlist including the
  maintainer decision to retain stefan@preu.at (2026-07-21) and the
  pi-agent service identity; codex@local deliberately absent after the
  2026-08-21 history rewrite.
- test/fixtures/privacy-gate/: synthetic fixtures exempted through a
  content-addressed manifest (exact git blob OIDs + justification).
- Behavioral tests build real throwaway git repositories: every secret
  rule, host paths, identity roles, exemption correctness, negative
  control (clean baseline then introduced leak fails), fail-closed on
  missing allowlist and non-repository paths.
- Wired into release:check and CI.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @unbraind, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@sourcery-ai

sourcery-ai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Reviewer's Guide

Adds a fail-closed Git privacy gate over the full object store and updates the pm CLI integration contract and release pipeline to enforce it, including tests, fixtures, and compatibility-floor wiring.

Sequence diagram for the Git privacy gate repository audit

sequenceDiagram
  actor CI
  participant npm
  participant privacy_gate_main as privacy-gate.main
  participant runGate as runGate
  participant fs as filesystem
  participant git

  CI->>npm: npm run privacy
  npm->>privacy_gate_main: node scripts/privacy-gate.ts
  privacy_gate_main->>runGate: runGate(repoRoot)

  runGate->>fs: parseAllowlist(repoRoot)
  fs-->>runGate: approved email set

  runGate->>git: listAllObjects(repoRoot)
  git-->>runGate: [oid, type][]

  runGate->>fs: loadFixtureExemptions(repoRoot)
  fs-->>runGate: exemptions map

  loop for each object
    alt type is commit or tag
      runGate->>git: readObject(repoRoot, type, oid)
      git-->>runGate: commit/tag text
      runGate->>runGate: extractEmail(header line)
      alt email not in allowlist
        runGate->>runGate: record Finding(identity:type, oid)
      end
    else type is blob
      runGate->>git: readObject(repoRoot, "blob", oid)
      git-->>runGate: blob content
      runGate->>runGate: scanBlob(oid, content, exemptions)
      alt rule(s) matched
        runGate->>runGate: record Finding(rule, oid)
      end
    end
  end

  alt findings exist
    runGate-->>privacy_gate_main: { exitCode: 1, stderr: summary }
  else no findings
    runGate-->>privacy_gate_main: { exitCode: 0, stdout: summary }
  end

  privacy_gate_main->>CI: process exitCode and output
Loading

File-Level Changes

Change Details Files
Introduce a fail-closed Git privacy gate script and wire it into CI and release checks.
  • Add scripts/privacy-gate.ts implementing a fail-closed audit over all git objects for unapproved commit/tag identities, high-confidence credential signatures, and absolute personal host paths, with content-addressed fixture exemptions and pure runGate/main entrypoints.
  • Add npm run privacy script and invoke it from the GitHub Actions CI workflow and the release:check pipeline so publishes and CI runs now require a passing privacy gate.
scripts/privacy-gate.ts
.github/workflows/ci.yml
package.json
Add configuration and synthetic fixtures for the privacy gate.
  • Add a checked-in .github/approved-git-identities.txt allowlist of permitted Git author/committer/tagger emails with justification comments.
  • Add synthetic secret/host-path fixture files plus a content-addressed manifest documenting each blob OID and justification for exemption.
  • Add a short README alongside fixtures documenting that all tokens/paths are synthetic, length-padded examples from public docs.
.github/approved-git-identities.txt
test/fixtures/privacy-gate/manifest.json
test/fixtures/privacy-gate/README.md
test/fixtures/privacy-gate/synthetic-aws-key.txt
test/fixtures/privacy-gate/synthetic-fine-grained-token.txt
test/fixtures/privacy-gate/synthetic-github-token.txt
test/fixtures/privacy-gate/synthetic-host-path.txt
test/fixtures/privacy-gate/synthetic-npm-token.txt
test/fixtures/privacy-gate/synthetic-slack-token.txt
Add behavioral test coverage for the privacy gate using real temporary git repositories.
  • Add test/privacy-gate.test.ts that spins up throwaway git repos, injects synthetic identities and secrets, and asserts behavior for clean paths, each violation type, fixture exemptions, CLI entrypoint behavior, and negative controls.
  • Exercise helper exports from the gate script (gitBlobOid, extractEmail, listAllObjects, readObject, scanBlob, runGate, main) for regression protection.
test/privacy-gate.test.ts
Update pm CLI invocation from deprecated list-all to canonical list --all with strict completeness contracts and raise the compatibility floor to 2026.8.20.
  • Change completePmListArgs and related messaging to use the new pm CLI contract: --pm-path, --output-include full, --output-limit/budget unbounded, list --all --json --include-body --strict-read; update error messages and comments to say list --all instead of list-all.
  • Adjust tests (complete-corpus, atomic, handler-failures) to assert the canonical argv, absence of legacy list-all alias, deprecation-free stderr, and ENOBUFS messaging advising careful PM_JSON_MAX_BUFFER increases.
  • Update README and inline comments/docs to describe the new canonical pm subprocess invocation and argument ordering, including replacing --path with --pm-path and list-all with list --all.
index.ts
README.md
test/atomic.test.ts
test/complete-corpus.test.ts
test/handler-failures.test.ts
Tighten pm CLI version compatibility and reflect it in manifest, package metadata, changelog, and tests.
  • Raise pm_min_version in manifest.json and the @unbrained/pm-cli peer/dev dependency to 2026.8.20, and remove the obsolete manifest.pm.compatibility envelope.
  • Extend compatibility-floor tests to assert the manifest and peerDependencies floors match the canonical list-contract floor, that the dev CLI pin equals that floor, and that CHANGELOG.md explicitly documents the breaking minimum-version bump.
  • Update CHANGELOG.md with a new Unreleased section describing the release publish ordering fix and the breaking pm CLI version requirement.
manifest.json
package.json
test/compatibility-floor.test.ts
CHANGELOG.md
Adjust pm-changelog usage to pass unbounded output controls through to the pm CLI.
  • Update changelog:full and release:notes npm scripts to add --pm-arg=--output-limit/--output-budget unbounded so pm-changelog respects the unbounded corpus contract when calling the pm CLI.
package.json
Update pm metadata and history files to track the new pm items backing this work.
  • Add and update .agents/pm issue and history JSONL/toon files for pm-github-iswq, pm-github-v2kt, and pm-github-zqad to reflect the compatibility floor and privacy gate work.
.agents/pm/history/pm-github-iswq.jsonl
.agents/pm/history/pm-github-v2kt.jsonl
.agents/pm/history/pm-github-zqad.jsonl
.agents/pm/issues/pm-github-iswq.toon
.agents/pm/issues/pm-github-v2kt.toon
.agents/pm/issues/pm-github-zqad.toon

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Aug 21, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: d53552ee-be78-490a-902c-591414299cbf


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Aug 21, 2026

Copy link
Copy Markdown

Greptile Summary

The PR adds an all-object privacy gate, integrates it into CI and release validation, and updates the pm CLI complete-corpus contract. The follow-up fixes narrow two prior bypasses but leave both security boundaries incomplete.

  • Adds identity, credential, and host-path scanning across the local Git object store.
  • Adds checked-in identity approvals and content-addressed synthetic fixture exemptions.
  • Migrates complete-workspace reads to the canonical pm list --all invocation and raises the required pm CLI version.
  • Adds real-repository behavioral tests and CI/release integration.

Confidence Score: 1/5

The PR is not yet safe to merge because crafted identity objects and content-aliased sensitive blobs can still bypass the privacy gate.

The attempted fixes do not fully enforce either reported privacy boundary: identity validation begins only after a narrow header-prefix match, and fixture validation grants a global exemption based solely on content OID membership.

Files Needing Attention: scripts/privacy-gate.ts and test/privacy-gate.test.ts

Security Review

Two previously reported privacy-gate bypasses remain reachable: malformed identity lines can evade the prefix matcher, and globally content-addressed exemptions can still suppress real sensitive blobs when identical content is admitted through the fixture tree.

Important Files Changed

Filename Overview
scripts/privacy-gate.ts Implements the privacy gate, but the malformed-header and fixture-exemption fixes leave two reachable bypasses.
test/privacy-gate.test.ts Adds broad behavioral coverage, though the new regressions omit malformed role delimiters and content aliasing through the fixture directory.
.github/workflows/ci.yml Integrates the privacy audit into the existing CI validation sequence.
index.ts Updates complete-workspace reads to the canonical unbounded pm CLI invocation.
package.json Adds privacy validation to release checks and aligns the pm CLI compatibility floor.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Enumerate local Git objects] --> B{Object type}
  B -->|commit or tag| C[Match identity-header prefix]
  C -->|matched| D[Parse and allowlist email]
  C -->|not matched| E[Identity skipped]
  B -->|blob| F{OID in effective fixture exemptions}
  F -->|yes| G[Skip all blob rules]
  F -->|no| H[Scan secrets and host paths]
Loading
Prompt To Fix All With AI
### Issue 1
scripts/privacy-gate.ts:348
**Malformed identity prefixes bypass validation**

When a crafted commit or tag uses an identity line such as `author<unapproved@example.com>` or a tab after the role, the prefix check skips the line before `extractEmail` runs, causing the gate to approve an unverifiable identity. **How this was verified:** The unmatched-prefix branch was traced directly through `continue`, bypassing both parsing and the allowlist check.

### Issue 2
scripts/privacy-gate.ts:180-183
**Fixture aliases exempt real leaks**

When sensitive blob content is also placed under `test/fixtures/privacy-gate/` and its OID is added to the manifest, this intersection admits the content OID and `scanBlob` exempts every identical blob regardless of its path or provenance, causing a real credential or host path elsewhere in history to pass CI and `release:check`. **How this was verified:** The effective exemption was traced from HEAD fixture-tree OID membership through the manifest intersection to the unconditional OID-based return in `scanBlob`.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (2): Last reviewed commit: "fix(privacy): fail closed on unparseable..." | Re-trigger Greptile

Comment thread scripts/privacy-gate.ts
Comment thread scripts/privacy-gate.ts
Comment thread .github/workflows/ci.yml
…to reviewed fixture blobs

Greptile review fixes:
- P1: an identity header without a parseable <email> is now itself a
  violation instead of silently passing the allowlist check.
- P1: manifest exemption keys are intersected with the blob object ids
  actually present under test/fixtures/privacy-gate/ at HEAD, so a
  manifest entry alone can never silence a leaked blob living elsewhere
  in history.
- P2 (documented, no change): CI audits refs/tags reachable objects;
  unreachable-object coverage remains the local release:check run's
  evidence, mirroring pm-rust's documented distinction.
Regression tests craft real malformed commit objects (--literally) and
an abuse attempt pairing an outside-fixture leak with a manifest key.
Comment thread scripts/privacy-gate.ts Outdated
Comment thread scripts/privacy-gate.ts
…scope scan to header section

Greptile round 2 fix (valid P1): the role-prefix check required a literal
space after author/committer/tagger, so crafted headers like
'author<email>' or a tab separator skipped verification entirely. The
gate now scans only the header section (before the first blank line) and
matches the bare role keyword, then fails closed on any header identity
that does not parse or is not approved. Message-body lines that begin
with an identity keyword are provably not flagged.

Refused P1 (fixture aliases): an exempted OID is by construction
byte-identical to a reviewed synthetic fixture file; content addressing
means a 'real leak elsewhere with the same OID' contains exactly those
reviewed synthetic bytes. The residual risk is committing a real secret
into the fixtures directory itself, which is visible in the diff review
of the manifest + fixture change.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Greptile has paused reviews on this repository — it used its 100 free open-source review credits for this billing period. Reviews resume automatically on August 27. To continue before then, an organization admin can keep reviews running past the free credits — those bill as normal usage.

@unbraind

Copy link
Copy Markdown
Owner Author

Thanks for the guide — accurate summary of the gate design. Note Sourcery's own diff review was skipped this week (weekly diff-character limit), so no code findings to address from you; the security review happened in the Greptile threads above, all now resolved.

@unbraind

Copy link
Copy Markdown
Owner Author

Correction to this summary's conclusion: both flagged boundaries are closed on head 3c1531a. (1) Malformed/unparseable identity headers now fail closed and header scanning is separator-agnostic (Greptile P1s 3833993306, 3834076989 — fixed with regression tests). (2) The fixture-exemption concern (3834076994) was retracted by Greptile itself: content addressing means an exempted OID is byte-identical to reviewed fixture bytes by definition. No unresolved findings remain.

@unbraind

Copy link
Copy Markdown
Owner Author

Acknowledged — no manual review trigger needed. This PR received a full Greptile security review on the current head; all P1 findings are fixed or resolved with regression tests, and Greptile's final thread is closed. Merging as-is.

@unbraind
unbraind merged commit 50777d5 into main Aug 21, 2026
6 checks passed
@unbraind
unbraind deleted the feat/zqad-privacy-forward-gate branch August 21, 2026 23:06
unbraind added a commit that referenced this pull request Aug 21, 2026
Co-authored-by: SteveBot <1153461+unbraind@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant