Skip to content

feat(leakage): externalize token data and gate the publication set at push - #2

Merged
flopperj merged 1 commit into
mainfrom
publication-gate
Jul 30, 2026
Merged

feat(leakage): externalize token data and gate the publication set at push#2
flopperj merged 1 commit into
mainfrom
publication-gate

Conversation

@flopperj

Copy link
Copy Markdown
Owner

Summary

Move the leakage guard from a working-tree check with in-tree token data to a
publication-set check driven by token data that lives outside every repo.
The in-tree token list was itself the leak the check existed to prevent; the
new arrangement removes that paradox and moves enforcement to the point where
history actually leaves the machine.

What changed

External token data. The token list no longer lives in this repo. An
overlay installer materializes it to
${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles-guard/leakage-tokens.txt alongside
a company-context marker file. Semantics are fail-closed:

  • marker absent → checks skip cleanly (public CI, fresh clones)
  • marker present, list missing/empty → hard configuration error, never a
    silent pass
  • findings → file/line references only; matched content is never echoed,
    because the checker's own output (CI logs, scrollback, pasted bug reports)
    is a publication channel

Pre-push gate (scripts/pre-push.sh). Auto-installed by install.sh via
_install_git_hook. Scans every outgoing commit in the pushed range — each
commit's full tree plus its metadata (author/committer identity and commit
message) — so a token buried in an intermediate commit, or in a commit
message, cannot reach a remote even if a later commit removed it from the
tree. The pre-commit hook stays as a fast working-tree early warning; the
pre-push hook is the enforced boundary.

Checker (scripts/check-no-leakage.sh). Gains a --commits mode that
reads commit SHAs from stdin and scans each commit's tree + metadata (used by
the pre-push gate). Content-free output. Distinct exit code (2) when the
marker exists but the list is missing or has no effective tokens.

Tests. tests/leakage-check.bats rewritten on synthetic tokens pointed at
fixture list/marker files, so the mechanism is fully testable in public CI
without the real token data. New tests/pre-push-hook.bats covers the
publication-set gate end-to-end. Install/hook tests updated for the new
pre-push wiring.

CI. .github/workflows/lint.yml shows the company-token scan as a
structurally skipped step on runners without the guard data — visible
non-execution, never a silent green. Lint, consult-grammar check, and the
synthetic-token suite still run everywhere.

Docs. PROTOCOL.md gets a "Cohort 3" section documenting the externalized
data + publication-set model. README.md and CHANGELOG.md updated to
reflect the new hook layout and the new test count (183 → 436).

Why

Two problems with the prior arrangement:

  1. The token list was in-tree. The list enumerated the exact identifiers
    the repo must never contain, so the list itself was a leak by definition.
  2. The check was working-tree-only. A token could sit in an intermediate
    commit's tree or in a commit message, get "cleaned up" in a later commit,
    and still ship to a remote in the middle of history.

Externalizing the data removes (1). Moving the primary check to pre-push,
scanning every outgoing commit's tree and metadata, closes (2).

Testing

  • make test — 436 tests pass locally
  • Public CI runs the mechanism tests on synthetic tokens; the company-token
    scan appears as a skipped step (no guard data on public runners)
  • Manually exercised pre-push against a synthetic tokenized commit and
    confirmed the push is rejected with file/line references and no content
    echoed

… push

Token data now lives outside every repo working tree, materialized by a
private overlay installer to ${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles-guard/
with a company-context marker. The checker reads it from there, fails closed
when the marker exists but the list is missing or empty, skips cleanly when
the marker is absent, and reports findings as file/line references only —
matched content is never echoed. A new pre-push hook scans every outgoing
commit (full tree, author/committer identity, message) so publication, not
commit, is the enforced boundary. Tests run on synthetic tokens everywhere;
CI shows the company-token scan as a structurally skipped step on public
runners.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@flopperj
flopperj marked this pull request as ready for review July 30, 2026 04:23
@flopperj
flopperj merged commit 9837eb1 into main Jul 30, 2026
1 check passed
@flopperj
flopperj deleted the publication-gate branch July 30, 2026 04:23
flopperj pushed a commit that referenced this pull request Jul 30, 2026
…(Scout #2)

PR description and commit 3b69303 claimed coverage for two behaviors that
had no corresponding tests: empty plugins.txt no-op and root-file-wins
precedence when both plugins.txt locations exist.

Add three tests to close the gap:
- empty plugins.txt produces zero claude invocations (exit 0)
- plugins.txt containing only blank lines and comments is also a no-op
- when both root and .claude/plugins.txt exist, root wins and exactly one
  install call is recorded

All three tests exercise _install_cli_and_plugins directly against scratch
dirs, consistent with the isolation pattern established in the prior commit.

Suite: 183 → 186.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
flopperj pushed a commit that referenced this pull request Jul 30, 2026
…skills (#2)

* feat(overlay-context): add runtime consult-instruction to 6 affected skills

Each of the 6 skills that previously relied on sentinel-injection
(briefing, doctor, obligations, pr-create-from-commits, ticket-pickup,
ticket-swarm) now carries a one-line instruction pointing agents to
~/.claude/overlay-context.md for company-specific behavior.

The empty OVERLAY-FRAGMENT sentinels are left intact for one coexistence
cycle — they become inert no-ops while the overlay migrates to the new
runtime-consult path.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(overlay-context): graceful-degradation parity in doctor + verb consistency

Addresses Ranger findings #1 and #2 on PR #2: (1) appends the "If that file is absent, proceed with the primary log message only" fallback to doctor's consult-instruction so it has the same degradation contract as the other 5 sites; (2) standardizes verb to "consult" across all 6 sites (was "see" only in doctor).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
flopperj added a commit that referenced this pull request Jul 30, 2026
… push (#2)

Token data now lives outside every repo working tree, materialized by a
private overlay installer to ${XDG_CONFIG_HOME:-$HOME/.config}/dotfiles-guard/
with a company-context marker. The checker reads it from there, fails closed
when the marker exists but the list is missing or empty, skips cleanly when
the marker is absent, and reports findings as file/line references only —
matched content is never echoed. A new pre-push hook scans every outgoing
commit (full tree, author/committer identity, message) so publication, not
commit, is the enforced boundary. Tests run on synthetic tokens everywhere;
CI shows the company-token scan as a structurally skipped step on public
runners.

Co-authored-by: James Arama <flopperj@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.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