feat(secrets): git leak guard — pre-push scan blocks pushes containing secret values #106

Closed
opened 2026-07-10 17:36:59 +02:00 by dominik.polakovics · 1 comment

What to build

Extend the bare reference repo's pre-push hook (the incogni guard site, seeder-installed, fires for pushes from every linked worktree) with a secrets leak check: the hook shells out to a new labctl secret scan <range>, which sends the outgoing diff to the lab server; the server scans it against the repo's decrypted secret values via the matcher from #104 (exact + base64/hex/urlencoded). A hit blocks the push with a message naming the matching secret and offending file — name only, never the value — so the agent can fix and re-push.

Secret values never appear in the hook script or on disk outside the vault.

Failure split (grill decision):

  • LAB_TOKEN present but scan fails (server down, 5xx): fail closed — an agent push must never skip the guard.
  • No LAB_TOKEN in env (a human pushing by hand from a parked worktree): skip the scan with a loud one-line warning — the threat model is the sloppy agent, not the operator.
  • Repo has zero secrets: server returns pass cheaply; push proceeds silently.

Respect the existing hook rules: lab's marker identifies its own hook, a foreign pre-push hook is never overwritten, and the incogni checks in the same hook keep working.

Acceptance criteria

  • Push containing a secret value (any matched form) in the diff is blocked; error names secret + file, never the value
  • Clean push proceeds; zero-secret repos scan as pass
  • With LAB_TOKEN set and server unreachable, push is blocked with a clear error
  • Without LAB_TOKEN, push proceeds with a warning line
  • Hook coexists with the incogni guard (golden-file test updated per the pattern in internal/seeder/hook_golden_test.go)
  • Blocked pushes surface secret names only — value appears in no error, log, or server response

Blocked by

  • #104 (secrets + matcher + run-token surface must exist)
## What to build Extend the bare reference repo's pre-push hook (the incogni guard site, seeder-installed, fires for pushes from every linked worktree) with a secrets leak check: the hook shells out to a new `labctl secret scan <range>`, which sends the outgoing diff to the lab server; the server scans it against the repo's decrypted secret values via the matcher from #104 (exact + base64/hex/urlencoded). A hit **blocks the push** with a message naming the matching secret and offending file — name only, never the value — so the agent can fix and re-push. Secret values never appear in the hook script or on disk outside the vault. Failure split (grill decision): - `LAB_TOKEN` present but scan fails (server down, 5xx): **fail closed** — an agent push must never skip the guard. - No `LAB_TOKEN` in env (a human pushing by hand from a parked worktree): **skip the scan** with a loud one-line warning — the threat model is the sloppy agent, not the operator. - Repo has zero secrets: server returns pass cheaply; push proceeds silently. Respect the existing hook rules: lab's marker identifies its own hook, a foreign pre-push hook is never overwritten, and the incogni checks in the same hook keep working. ## Acceptance criteria - [ ] Push containing a secret value (any matched form) in the diff is blocked; error names secret + file, never the value - [ ] Clean push proceeds; zero-secret repos scan as pass - [ ] With LAB_TOKEN set and server unreachable, push is blocked with a clear error - [ ] Without LAB_TOKEN, push proceeds with a warning line - [ ] Hook coexists with the incogni guard (golden-file test updated per the pattern in internal/seeder/hook_golden_test.go) - [ ] Blocked pushes surface secret names only — value appears in no error, log, or server response ## Blocked by - #104 (secrets + matcher + run-token surface must exist)
Author
Owner

This was generated by AI during triage.

Agent Brief

Category: enhancement
Summary: Extend the seeder-installed pre-push hook in the bare reference repo with a secrets leak check: a new labctl secret scan <range> sends the outgoing diff to the lab server, which scans it against the repo's decrypted secret values; a hit blocks the push, naming the secret and file but never the value.

Current behavior:
The seeder installs a lab-marked pre-push hook in the bare reference repo (the incogni guard site); it fires for pushes from every linked worktree, never overwrites a foreign hook, and is covered by a golden-file test in the seeder package. It performs no secrets check. labctl has secret list and secret exec (#104) but no scan subcommand, and the server has no diff-scanning endpoint.

Desired behavior:
The hook shells out to labctl secret scan <range> for the outgoing range. The server scans the diff against the repo's decrypted secret values using the shared streaming matcher (exact + base64/hex/URL-encoded forms). On a hit the push is blocked with a message naming the matching secret and offending file — name only, never the value. Secret values never appear in the hook script, on disk outside the vault, or in any error/log/response.

Failure split (grill decision):

  • LAB_TOKEN present but the scan fails (server down, 5xx): fail closed — an agent push must never skip the guard.
  • No LAB_TOKEN in env (a human pushing by hand from a parked worktree): skip with a loud one-line warning — the threat model is the sloppy agent, not the operator.
  • Repo has zero secrets: server returns pass cheaply; push proceeds silently.

Key interfaces:

  • The seeder's pre-push hook template and its marker/coexistence rules — the incogni checks in the same hook must keep working; update the golden-file test alongside
  • New labctl secret scan <range> subcommand — collects the outgoing diff and submits it via LAB_TOKEN
  • New run-token (agentapi) scan endpoint — decrypts the repo's secret values server-side and runs secrets.NewMatcher/Feed over the submitted diff; response carries secret names + file paths only
  • Run-token scoping as in #104: an instance can only scan against its own repo's secrets

Acceptance criteria:

  • A push containing a secret value (any matched form) in the diff is blocked; the error names secret + file, never the value
  • A clean push proceeds; zero-secret repos scan as pass
  • With LAB_TOKEN set and the server unreachable, the push is blocked with a clear error
  • Without LAB_TOKEN, the push proceeds with a warning line
  • The hook coexists with the incogni guard (seeder golden-file test updated per the existing pattern)
  • Blocked pushes surface secret names only — the value appears in no error, log, or server response

Out of scope:

  • Scanning full repository history (only the outgoing push range)
  • Blocking operator (token-less) pushes — they skip with a warning by design
  • Client-side matching (values must never leave the server or reach the hook)
  • Broker redaction (#105), tracker-write sanitization (#107), exposure detection (#108)
> *This was generated by AI during triage.* ## Agent Brief **Category:** enhancement **Summary:** Extend the seeder-installed pre-push hook in the bare reference repo with a secrets leak check: a new `labctl secret scan <range>` sends the outgoing diff to the lab server, which scans it against the repo's decrypted secret values; a hit blocks the push, naming the secret and file but never the value. **Current behavior:** The seeder installs a lab-marked pre-push hook in the bare reference repo (the incogni guard site); it fires for pushes from every linked worktree, never overwrites a foreign hook, and is covered by a golden-file test in the seeder package. It performs no secrets check. `labctl` has `secret list` and `secret exec` (#104) but no `scan` subcommand, and the server has no diff-scanning endpoint. **Desired behavior:** The hook shells out to `labctl secret scan <range>` for the outgoing range. The server scans the diff against the repo's decrypted secret values using the shared streaming matcher (exact + base64/hex/URL-encoded forms). On a hit the push is blocked with a message naming the matching secret and offending file — name only, never the value. Secret values never appear in the hook script, on disk outside the vault, or in any error/log/response. Failure split (grill decision): - `LAB_TOKEN` present but the scan fails (server down, 5xx): **fail closed** — an agent push must never skip the guard. - No `LAB_TOKEN` in env (a human pushing by hand from a parked worktree): **skip** with a loud one-line warning — the threat model is the sloppy agent, not the operator. - Repo has zero secrets: server returns pass cheaply; push proceeds silently. **Key interfaces:** - The seeder's pre-push hook template and its marker/coexistence rules — the incogni checks in the same hook must keep working; update the golden-file test alongside - New `labctl secret scan <range>` subcommand — collects the outgoing diff and submits it via `LAB_TOKEN` - New run-token (agentapi) scan endpoint — decrypts the repo's secret values server-side and runs `secrets.NewMatcher`/`Feed` over the submitted diff; response carries secret names + file paths only - Run-token scoping as in #104: an instance can only scan against its own repo's secrets **Acceptance criteria:** - [ ] A push containing a secret value (any matched form) in the diff is blocked; the error names secret + file, never the value - [ ] A clean push proceeds; zero-secret repos scan as pass - [ ] With `LAB_TOKEN` set and the server unreachable, the push is blocked with a clear error - [ ] Without `LAB_TOKEN`, the push proceeds with a warning line - [ ] The hook coexists with the incogni guard (seeder golden-file test updated per the existing pattern) - [ ] Blocked pushes surface secret names only — the value appears in no error, log, or server response **Out of scope:** - Scanning full repository history (only the outgoing push range) - Blocking operator (token-less) pushes — they skip with a warning by design - Client-side matching (values must never leave the server or reach the hook) - Broker redaction (#105), tracker-write sanitization (#107), exposure detection (#108)
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab#106
No description provided.