release: the grant gains its floor — no pointer predates the key that signed it
The review's one substantive finding: a scoped key could sign a pointer dated years before its own mint and it verified, because expiry only had a ceiling. The floor is clock-free and adoption-safe for the same reason the ceiling is — a genuine promotion always stamps promoted_at at or after minted_at — and it closes forged pre-grant history. Mutation- verified: removing the floor fails exactly its test. Also: rungs deduped at mint (noise in every listing forever), and the spec now states what previously lived only in a code comment — expiry is evadable by the key's own holder and defeats expiry only, never scope — plus the 90-day default the implementation had and the spec lacked.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
3 files changed,
+71
−0
docs/superpowers/specs/2026-08-03-scoped-signing-keys-design.md+8 −0internal/release/grant.go+20 −0internal/release/grant_test.go+43 −0
diff --git a/docs/superpowers/specs/2026-08-03-scoped-signing-keys-design.md b/docs/superpowers/specs/2026-08-03-scoped-signing-keys-design.md| index ace630d33..4b50a3dc9 100644 |
| --- a/docs/superpowers/specs/2026-08-03-scoped-signing-keys-design.md |
| +++ b/docs/superpowers/specs/2026-08-03-scoped-signing-keys-design.md |
| @@ -124,6 +124,14 @@ an expired grant fails at promote time and at verify time. (Revocation of |
| an actively-misused key is by rotating the ROOT key, which invalidates every |
| grant at once — documented as the incident response, not built.) |
| +Stated plainly, because it lives nowhere else: **expiry is evadable by the |
| +key's own holder** — promoted_at is signer-controlled, so a scoped key can |
| +backdate a pointer inside its own validity window. Backdating defeats expiry |
| +only, never scope: stable stays refused whatever the date says. Expiry is |
| +hygiene against forgotten keys, not revocation against hostile ones. There is |
| +also a floor: a pointer dated before the grant's minted_at is refused as |
| +forged history — no real promotion by a key can predate the key. |
| + |
| ## 6. Non-goals |
| - Per-app or per-account scoping (rung-only for v1; the grant schema's |
diff --git a/internal/release/grant.go b/internal/release/grant.go| index 0a3c0aa10..59df20a95 100644 |
| --- a/internal/release/grant.go |
| +++ b/internal/release/grant.go |
| @@ -97,6 +97,8 @@ func MintKeyGrant(root ed25519.PrivateKey, name string, rungs []string, ttl time |
| } |
| // Checked here as well as in validateGrant so the refusal names the flag |
| // the operator typed rather than the document they never saw. |
| + seen := map[string]bool{} |
| + deduped := rungs[:0:0] |
| for _, r := range rungs { |
| if r == RungStable { |
| return "", "", fmt.Errorf("mint key grant: %s is never delegable — moving stable requires the root key itself", RungStable) |
| @@ -104,7 +106,14 @@ func MintKeyGrant(root ed25519.PrivateKey, name string, rungs []string, ttl time |
| if RungIndex(r) < 0 { |
| return "", "", fmt.Errorf("mint key grant: %q is not a rung (%s)", r, strings.Join(delegableRungs(), ", ")) |
| } |
| + // Deduped at mint (2026-08-03 review, MINOR 6): a repeated rung is |
| + // harmless to verify but noise in every grant listing forever. |
| + if !seen[r] { |
| + seen[r] = true |
| + deduped = append(deduped, r) |
| + } |
| } |
| + rungs = deduped |
| pub, priv, err := ed25519.GenerateKey(rand.Reader) |
| if err != nil { |
| return "", "", err |
| @@ -263,6 +272,17 @@ func (g *Grant) allows(channel string, at time.Time) error { |
| return fmt.Errorf("key grant %q expired at %s, before %s", |
| g.Name, g.ExpiresAt.Format(time.RFC3339), at.UTC().Format(time.RFC3339)) |
| } |
| + // The lower bound (2026-08-03 review, IMPORTANT 1): a pointer dated |
| + // before the grant even existed is forged history — no real promotion by |
| + // this key can predate the key. Clock-free and adoption-safe for the same |
| + // reason the upper bound is: a genuine promotion always stamps |
| + // promoted_at at or after minted_at, so nothing legitimately adopted can |
| + // trip this, while a backdated pointer aimed under the grant's floor |
| + // fails closed. |
| + if at.Before(g.MintedAt) { |
| + return fmt.Errorf("key grant %q was minted %s; a pointer dated %s predates the key that signed it", |
| + g.Name, g.MintedAt.Format(time.RFC3339), at.UTC().Format(time.RFC3339)) |
| + } |
| return nil |
| } |
diff --git a/internal/release/grant_test.go b/internal/release/grant_test.go| index ec1c64f9f..ff762cbbe 100644 |
| --- a/internal/release/grant_test.go |
| +++ b/internal/release/grant_test.go |
| @@ -374,3 +374,46 @@ func TestGrantBlobParseErrorsNameTheSource(t *testing.T) { |
| } |
| } |
| } |
| + |
| +// A pointer dated before the grant existed is forged history: no real |
| +// promotion by a key can predate the key, so the floor refuses it outright |
| +// (2026-08-03 review, IMPORTANT 1 — before the floor, a scoped key could |
| +// sign a pointer dated years before its own mint and it verified). |
| +// |
| +// Mutation-mindedness: delete the at.Before(g.MintedAt) refusal in allows() |
| +// and the first half of this test goes green when it must not; the second |
| +// half pins that the exact mint instant — the earliest honest promotion — |
| +// stays allowed, so the floor cannot drift into refusing real history. |
| +func TestGrantRefusesAPointerPredatingTheGrant(t *testing.T) { |
| + _, scopedPub := testKeys(t) |
| + g := goodGrant(scopedPub) |
| + err := g.allows("edge", g.MintedAt.Add(-time.Second)) |
| + if err == nil { |
| + t.Fatal("grant honoured a pointer dated before the grant was minted") |
| + } |
| + if !strings.Contains(err.Error(), "predates") { |
| + t.Errorf("refusal %q does not say the pointer predates the key", err) |
| + } |
| + if err := g.allows("edge", g.MintedAt); err != nil { |
| + t.Errorf("grant refused its own mint instant: %v", err) |
| + } |
| +} |
| + |
| +// Duplicate rungs are collapsed at mint (2026-08-03 review, MINOR 6): |
| +// harmless to verify, noise in every listing of the grant forever. |
| +func TestMintDeduplicatesRungs(t *testing.T) { |
| + root, _ := testKeys(t) |
| + rootPub := root.Public().(ed25519.PublicKey) |
| + _, blob := mintGrant(t, root, "dupes", []string{"edge", "edge", "canary", "edge"}, time.Hour) |
| + sg, err := parseGrantBlob(blob, "test") |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + g, err := verifyGrant(rootPub, sg) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + if len(g.Rungs) != 2 { |
| + t.Fatalf("rungs = %v, want the two distinct ones", g.Rungs) |
| + } |
| +} |