Skip to content

feat: add plan-to-policy cmd & fix SPIRE model discovery, attestation signing, and verification#11

Merged
manzil-infinity180 merged 7 commits into
mainfrom
spire-attestation-fixes
Mar 14, 2026
Merged

feat: add plan-to-policy cmd & fix SPIRE model discovery, attestation signing, and verification#11
manzil-infinity180 merged 7 commits into
mainfrom
spire-attestation-fixes

Conversation

@manzil-infinity180

Copy link
Copy Markdown
Contributor
  • plan-to-policy: New aflock plan-to-policy command that converts Claude plan markdown files into .aflock policies with deterministic steps and AI evaluators for
    spec-driven development

    • SPIRE model discovery fix: Added claude-opus-4-6 to TrustedModels and fixed fallback when sessions-index.json is missing — model was showing as unknown@0.0.0
    • Attestation signing & verification fix: SignCollection now wraps collections in in-toto Statement v1 (was sending raw collection), and Sign embeds the leaf certificate
      so verify can validate the chain to the SPIRE CA root

    Test plan

    • aflock plan-to-policy parses plan markdown and generates valid .aflock policy
    • SPIRE model discovery finds claude-opus-4-6 from session files without sessions-index.json
    • bash(attest=true, step='lint/test/build') creates properly signed in-toto attestations
    • sign_attestation works with SPIRE connected
    • aflock verify --policy .aflock returns success: true with all 3 steps verified
    • Signature chain validates: leaf cert (SVID) → SPIRE CA root in policy

  SignCollection now wraps the attestation collection in an in-toto
  Statement v1 before signing, matching what the verifier expects.
  Sign now embeds the PEM-encoded leaf certificate in the envelope
  so the verifier can validate the signature chain to the CA root.
  Verifier updated to parse leaf certs from the signature field.
@manzil-infinity180 manzil-infinity180 changed the title feat: add plan-to-policy cmd for spec-driven dev & fix SPIRE model discovery, attestation signing, and verification feat: add plan-to-policy cmd & fix SPIRE model discovery, attestation signing, and verification Mar 8, 2026
Signed-off-by: Rahul Vishwakarma <rahulvs2809@gmail.com>
colek42
colek42 previously approved these changes Mar 10, 2026
# Conflicts:
#	internal/hooks/handler_sublayout_test.go
#	internal/state/propagation_test.go
@github-actions

Copy link
Copy Markdown

Docs Preview

Status Deployed
Branch spire-attestation-fixes
Preview URL https://spire-attestation-fixes.aflock-d0m.pages.dev

@colek42

colek42 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

Code Review

CRITICAL: Embedded Certificate Trust Bypass (internal/verify/verifier.go)

The new code parses a certificate from the signature's Certificate field and adds it directly to verification candidates:

if sig.Certificate != "" {
    block, _ := pem.Decode([]byte(sig.Certificate))
    if block != nil {
        if leafCert, err := x509.ParseCertificate(block.Bytes); err == nil {
            candidates = append(candidates, leafCert)
        }
    }
}

The existing chain validation only runs when !cert.IsCA. An attacker can forge a self-signed cert with IsCA == true, embed it in the signature, sign with their own key, and pass verification — the chain check is skipped entirely for CA certs. Fix: validate the cert chain against rootPool before adding it to candidates, regardless of IsCA.

This change also has zero test coverage in this PR.

BUG: scanForPrompt consumes scanner lines without backtracking (internal/plan/parser.go)

When scanForPrompt reads ahead looking for a prompt and hits another heading (e.g. ### uat-inbox), that heading line is consumed but never returned to the main parse loop. The next UAT section is silently dropped. Example:

### uat-empty
Some text without a prompt
### uat-inbox
**AI Policy Prompt**: PASS if inbox shows emails

scanForPrompt for uat-empty consumes the ### uat-inbox line, so uat-inbox is never parsed.

Medium

  • Inconsistent verification paths: VerifyEnvelope (signer.go) ignores the embedded cert field, but verifyDSSESignatures (verifier.go) uses it. Two divergent signature verification code paths will cause confusion and bugs.
  • Policy file permissions: generator.go writes with 0644 but init uses 0600. Should be consistent.
  • Merge error swallowing (cmd/aflock/main.go): Three nested if err == nil blocks silently ignore corrupt policy files during --merge. User gets no indication the merge target was invalid.
  • Double output: Command writes the policy to a file AND dumps JSON to stdout. Noisy — consider only printing the JSON when no -o flag is given.

Minor

  • parseTableRowIntoplanparseTableRowIntoPlan (camelCase)
  • test/glob_test.go was missing os.Exit(m.Run()) — tests in that package were silently not running. Good fix, worth a call-out in the PR description.

Missing Test Coverage

  • Embedded certificate verification path (most security-critical change)
  • findModelAndSessionFromDir fallback
  • scanForPrompt line-consuming edge case

Signed-off-by: Rahul Vishwakarma <rahulvs2809@gmail.com>
@manzil-infinity180 manzil-infinity180 merged commit 431c0de into main Mar 14, 2026
7 checks passed
@manzil-infinity180 manzil-infinity180 deleted the spire-attestation-fixes branch March 14, 2026 05:29
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.

2 participants