Skip to content

fix: close nine contracts that could only check what they were told - #146

Merged
rldyourmnd merged 9 commits into
mainfrom
fixtures/reprove-secret-scan-container-guard
Aug 13, 2026
Merged

fix: close nine contracts that could only check what they were told#146
rldyourmnd merged 9 commits into
mainfrom
fixtures/reprove-secret-scan-container-guard

Conversation

@rldyourmnd

Copy link
Copy Markdown
Contributor

What this is

Nine main-line defects found by auditing main against its own contracts. Every
one was invisible to a fully green gate — validate_all all tiers, actionlint,
and zizmor at both personas all passed before this branch.

The theme is the same in most of them: a contract that could only ever check
what someone remembered to tell it.

The defects

Was Now
.claude/CLAUDE.md Claude Code's only auto-loaded file told every agent to run a command that aborts with ModuleNotFoundError after #144 corrected, registered, and discoverable
execution contract read prose only from a closed 15-entry allowlist, so an unlisted document was never opened discovers every document that launches a real scripts/ subject; unlisted or stale-exempt fails
capability.schema.yaml never executed — the validator only checked the file existed, so the schema forbade runtime_requirements (which two capabilities use) while seven last_verified values had drifted to datetime.date executed against the catalog by a dependency-free validator; duplicated Python enums removed
check_rulesets.py enforcement checked against an enum that accepts disabled; tag validation was one boolean branch/tag rulesets must be active; the tag-immutability rule set and the bypass-actor set are pinned
zizmor guidance four documented invocations, three wrong: regular where CI runs pedantic, an unpinned binary, and no GH_TOKEN in either CONTRIBUTING.md command all five identical, derived from the workflow inputs and from what ci.yml passes
secret-scan.yml container lane the default mode validated nothing — the guard returned before it looked and gitleaks_image was not in the step env — while printing "digest-pinned caller contract" requires an @sha256: digest; six executable cases
secret-scan.yml scope scanned every ref a fetch-depth: 0 checkout brings in, so an unmerged branch failed the scan on every other branch scoped to the checked-out ref; all-refs must be asked for
CHANGELOG.md ## [0.11.0] described a release that was never tagged; two headings predated their own tags reconciled; structure blocks in core, tag reconciliation is advisory
inventory counts 46/nine/twenty-seven/nine against a tree of 47/10/35/10, plus a duplicated sentence and README claiming tags are mutable volatile counts removed rather than corrected; README matches the adoption skill

Evidence

Every new rule is proven to reject, not just to pass:

  • discovery: a planted unregistered document fails; a foreign-tool document passes
  • schema: rejects an unquoted date, an unexpected property, a bad enum, an empty sources
  • rulesets: rejects a disabled branch ruleset, a removed update rule, an added bypass actor, a stale exemption
  • documented commands: rejects a reverted persona and a workflow pin bumped without the docs
  • release ledger: rejects a date contradicting its tag, bad ordering, a duplicate heading, a missing date, an untagged heading, a VERSION with no heading
  • secret-scan: rejects tag-only / latest / empty / short / uppercase-digest images and unknown scopes

Runtime: secret-scan.yml re-proved on run 31750480571 — both Linux X64 lanes green, proven_digest aa65f5d9.

The scope defect was found by running it: the first attempt on this branch went
red on a finding in a commit that is an ancestor of neither this branch nor
main. That failure is recorded in the ledger evidence rather than hidden.

Permissions and threat model

No permission changes. Two trust boundaries tighten:

  • secret-scan.yml no longer passes an unvalidated caller string to docker run
    in a workflow that reads every secret in the tree.
  • The scan result no longer depends on refs the change does not touch, which is
    what a required check is supposed to guarantee.

scan_scope is a behaviour change: a consumer relying on the implicit
all-branch sweep must now pass scan_scope: all-refs. Recorded in the changelog.

Out of scope, filed instead

…ng a registry

A closed allowlist can only check what someone remembered to list, so
.claude/CLAUDE.md — the only auto-loaded file here — carried a command that
aborts under the launcher while the gate stayed green.

Discovery now scans the tree for documents launching a real scripts/ subject,
requires registration or a written exemption, and rejects stale exemptions.
Foreign tools stay out of scope. Proven both ways: a planted unregistered
document fails, a foreign-tool document passes.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
validate_catalog.py only ever asserted the schema file existed, so the schema
drifted from the tree in both directions: it forbade runtime_requirements,
which two capabilities declare, and it declared last_verified a pattern-checked
string while seven entries parsed as datetime.date.

The schema now covers both runtime fields, the dates are quoted, and a
dependency-free _json_schema.py executes the schema against the catalog. The
duplicated Python enums and key lists are removed, so shape has one source.
Proven to reject an unquoted date, an unexpected property, a bad enum and an
empty sources list.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
enforcement was checked against an enum accepting 'disabled', so the
default-branch ruleset could be switched off with this gate still green. Tag
validation was a single existence boolean while the adoption skill promises
immutable tags.

Branch and tag rulesets must now be active; a weaker one records a reason here.
The tag rule set and the bypass-actor set are both pinned. Proven to reject a
disabled branch ruleset, a removed 'update' rule, an added bypass actor, and a
stale exemption.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
Four documented zizmor invocations, three wrong in three different ways:
AGENTS.md and the nddev-repo-flow skill named --persona regular while ci.yml
passes pedantic; CONTRIBUTING.md's comment contradicted its own command, ran an
unpinned zizmor off PATH, and omitted GH_TOKEN from both invocations - the
omission AGENTS.md documents as how three ref-version-mismatch findings reached
the default branch.

check_documented_commands.py now derives version, severity and persona from the
workflow inputs and from what ci.yml passes. Proven to catch a reverted persona
and a workflow pin bumped without the docs.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
…ner lane

The binary lane was exhaustively allowlisted while the container lane - the
default - validated nothing: the guard returned before it looked and
gitleaks_image was not even in the step env, so any caller string reached
docker run in a workflow that reads every secret in the tree. The step summary
claimed a digest-pinned caller contract that nothing checked.

The guard now requires an @sha256: digest and rejects tag-only, latest, empty,
short and uppercase-digest images, with six executable cases. The lone unclosed
file handle in the repository is closed.

Re-proof of the runtime record follows in this branch.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
release.yml only ever checked the forward direction, so a heading claiming
0.11.0 shipped on 2026-07-20 sat between 0.10.0 and 0.11.1 for a month
describing a release that was never tagged.

Structure - grammar, uniqueness, descending versions, non-increasing dates,
agreement with VERSION - blocks in core. Tag reconciliation is advisory,
because whether a tag exists is a property of the refs, not of the change.
0.8.1 and 0.6.0 are corrected to their real tag dates; 0.11.0 is recorded as
known debt.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
AGENTS.md claimed 46 reusables and nine self workflows against 47 and 10, said
the estate calls twenty-seven reusables when it calls 35 across three files,
named nine cross-platform-proven reusables against a ledger recording ten,
bounded the docs at docs/18 after docs/19 landed, and carried one sentence
twice.

The counts are removed rather than corrected - each was a copy of a fact the
tree already answers, so each was a fresh chance to drift. The README also said
tags are mutable while tag-semver.json blocks deletion, update and
non-fast-forward, contradicting the adoption skill.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
gitleaks detect walks every ref in the clone and fetch-depth: 0 fetches every
branch, so a finding on an unmerged branch failed this scan on every other
branch - a required check whose result depended on refs the change never
touched.

Found by running it: the evidence estate went red on a branch cut from a clean
main, and the finding traced to a commit that is an ancestor of neither.
scan_scope defaults to ref-history; all-refs stays reachable but must be asked
for, and an unknown value fails closed.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
Run 31750480571 executed both Linux X64 lanes green after the container digest
guard and the scan-scope input landed. proven_digest aa65f5d9.

The prior attempt on this branch is recorded in the evidence: it is what showed
that a fetch-depth-0 checkout lets an unrelated ref fail the scan.

Signed-off-by: Danil Silantyev <danilsilantyevwork@gmail.com>
@github-actions github-actions Bot added ci Continuous integration and removed ci Continuous integration labels Aug 13, 2026
@rldyourmnd
rldyourmnd merged commit 867d754 into main Aug 13, 2026
108 checks passed
@rldyourmnd
rldyourmnd deleted the fixtures/reprove-secret-scan-container-guard branch August 13, 2026 22:40
rldyourmnd added a commit that referenced this pull request Aug 14, 2026
Applies the PR #145 checkpoint (7cbd1e8) on top of the track-M fixes merged as
PR #146. Conflicts were unions: the Python execution surface carries both
track-M's subjects and this branch's.

This is the unverified WIP baseline; every defect it carries is fixed in the
commits that follow. One fix is folded in here rather than applied after:
`gitleaks` flags the sample cache identity on line 238 as `generic-api-key`, and
`secret-scan.yml` scans the history of the checked-out ref, so leaving the
literal in an ancestor commit would fail the scan on `main` for good. The value
is a Flutter action cache key in a negative-self-test sample, not a credential,
so nothing needs rotating -- but the line really did read like one, and the fix
is to stop the sample looking like a secret rather than to teach the scanner to
skip the file. The identities are built from parts, with the version read from
`sdk-runtime-spec.yml` instead of written out three more times.
rldyourmnd added a commit that referenced this pull request Aug 14, 2026
Applies the PR #145 checkpoint (7cbd1e8) on top of the track-M fixes merged as
PR #146. Conflicts were unions: the Python execution surface carries both
track-M's subjects and this branch's.

This is the unverified WIP baseline; every defect it carries is fixed in the
commits that follow. One fix is folded in here rather than applied after:
`gitleaks` flags the sample cache identity on line 238 as `generic-api-key`, and
`secret-scan.yml` scans the history of the checked-out ref, so leaving the
literal in an ancestor commit would fail the scan on `main` for good. The value
is a Flutter action cache key in a negative-self-test sample, not a credential,
so nothing needs rotating -- but the line really did read like one, and the fix
is to stop the sample looking like a secret rather than to teach the scanner to
skip the file. The identities are built from parts, with the version read from
`sdk-runtime-spec.yml` instead of written out three more times.
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