feat(rules): signed v2 rules distribution (engine: producer tooling, --rules-source, trust root)#70
Merged
Merged
Conversation
…e, audit hardening Engine half of the signed rules-distribution (production-channel) feature: - cmd/rulesctl (separate, non-shipped binary) + internal/rulepub: keygen, bundle, sign, verify — share rulesign's CanonicalDigest / StatementSigningPayload so producer and verifier cannot drift. - internal/rulesign: export WriteCanonicalTar; ValidateBundlePath rejects non-portable / case-folding paths on both producer and consumer; embed the real signing public key in keyring.json + TestEmbeddedKeyring_IsPopulated guard. - internal/rulesource: genesis version floor (TOFU rollback guard, enforced online and offline); validate-before-advance-floor; installBundle self-heals a partial dest; fail-closed staleness on a corrupt cached expiry. - cmd/trustabl: --rules-source flag (--channel kept as deprecated alias); effectiveRules derives Config and RulesOrigin from one decision; signed-from- custom-repo is watermarked + gets a distinct ScanID. - Docs + tests throughout. Refs TR-319..325.
sairenchristianbuerano
approved these changes
Jun 16, 2026
Completeness/robustness from the maximal-fanout audit (crypto-keys, digest- portability, rollback-genesis, cache-concurrency). No change to default behavior. - rulesign: ParseKeyring rejects an inverted validity window; ValidateBundlePath rejects '~'-prefixed components; CanonicalDigest/WriteCanonicalTar reject the empty bundle and emit an explicit USTAR over-long-path error. New tests lock key ROTATION (overlapping windows) and REVOCATION (key removal) end to end. - rulesource: pruneBundles bounds the signed bundle cache (keeps every channel's active digest, clears orphaned .tmp-bundle-* dirs); RecordStatement refreshes the offline pointer on an equal-version re-sign while keeping the floor monotonic; 0o700 on the bundle/channel-state dirs. - rulesctl: keygen --not-after + RFC3339 timestamps (rotation tooling). - docs: key rotation/revocation runbook, bundle-path contract, genesis-floor unit, zero-tolerance clock-skew stance. Refs TR-319..325.
- rules pull --rules-source/--channel: pre-warm a signed channel's bundle cache (the documented offline story now works for signed rules, not just git). - mcp: --rules-source/--channel + propagate RulesOrigin/Stale/SchemaNewer into the returned ScanResult (MCP scans were dropping all rules provenance/stale signals). - scan: --require-signed / TRUSTABL_REQUIRE_SIGNED=1 fail-closed gate (refuse the unsigned git path); cutover guard test (a signed default must set a genesis floor). - tests: releaseSource.Pull no-fallback contract; statement freshness boundary. - docs: README --rules-source + schema-version example; ARCHITECTURE ScanResult provenance fields, bad-promote recovery + statement-renewal runbook. Refs TR-319..325.
…-trip - transport: make the byte/entry/unpacked ceilings test-overridable; add boundary tests (over-cap rejected, at-cap succeeds) for the decompression-bomb and unbounded-body defenses. - rulesctl: end-to-end keygen->bundle->sign->verify CLI test (O_EXCL seed, --not-after validation, channel-mismatch refusal) — the producer chain the publish workflow depends on. Refs TR-319..325.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The engine half of the signed v2 rules distribution (the production-channel feature). It makes the scanner able to resolve rules from a signature-verified release channel, and adds the producer tooling + the trust root, with all audit hardening applied.
Highlights:
cmd/rulesctl(a separate binary, NOT shipped to users) +internal/rulepub:keygen/bundle/sign/verify. Producer and verifier sharerulesign'sCanonicalDigest/StatementSigningPayload, so they cannot drift. No private-key/signing code links into the scanner binary.internal/rulesign: exportedWriteCanonicalTar;ValidateBundlePathrejects non-portable / case-folding paths on both producer and consumer (so the on-disk install always equals the verified digest); embedded the real signing public key inkeyring.json+ aTestEmbeddedKeyring_IsPopulatedguard so a build can never ship an empty trust root.internal/rulesource: build-embedded per-channel genesis version floor (trust-on-first-use rollback guard, enforced online and offline); validate-the-manifest-before-advancing-the-floor;installBundleself-heals a partial cache dir; fail-closed staleness on a corrupt cached expiry.cmd/trustabl:--rules-sourceflag (--channelkept as a deprecated alias);effectiveRulesderives therulesource.Configand theRulesOriginfrom one decision so provenance can never disagree with the resolved source; a signed channel served from a custom repo is watermarked and gets a distinctScanID.The default scan is unchanged — it still uses the unsigned git source. The flip to signed-production-by-default is a separate, later step (TR-325), gated on a first published statement.
Audit + verification
Two full-precision adversarial audits were run over the whole v2 surface; every confirmed finding was fixed (portability validator, install self-heal, fail-closed staleness, provenance labeling, the producer self-verify, CI script-injection — the last shipped separately in
trustabl-rules). Secret hygiene: only the public key is committed; no private-key material is in any tracked file.go build ./...andgofmtare clean; every v2 package passes. The branch was merged up to date withmain(24 commits) with no code conflicts.Companion changes (trustabl-rules)
.github/workflows/publish.yml.Once this merges,
maincarries the embedded key +cmd/rulesctl, which the publish workflow needs to build and self-verify. Refs TR-319..325.🤖 Generated with Claude Code