package release // The production flag (2026-08-04 console bake controls spec §2–§3): a // root-signed per-app object deciding whether the supply-chain bake applies. // The properties tested here, in the order they'd hurt if lost: missing is // the default (nil, nil); a set flag round-trips; a scoped key's wrapper is // refused even with a valid root grant; a tampered payload is refused; the // key sits OUTSIDE channels/ so pointer listings never try to parse it. import ( "context" "crypto/ed25519" "crypto/rand" "encoding/base64" "encoding/json" "errors" "os" "strings" "testing" "time" ) func TestProductionFlagDefaultIsMissing(t *testing.T) { s := NewDirStore(t.TempDir()) _, pub := testKeys(t) f, err := GetProduction(context.Background(), s, pub, "bab", "kass") if err != nil || f != nil { t.Fatalf("GetProduction on empty store = %v, %v; want nil, nil (the default: not production)", f, err) } } func TestProductionFlagRoundTrips(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) if err := SetProduction(ctx, s, priv, "bab", "kass", true, "paul@keymail.dev"); err != nil { t.Fatalf("SetProduction: %v", err) } f, err := GetProduction(ctx, s, pub, "bab", "kass") if err != nil { t.Fatalf("GetProduction: %v", err) } if f == nil || !f.Production || f.Actor != "paul@keymail.dev" || f.SetAt.IsZero() { t.Fatalf("flag = %+v, want production on, actor recorded, set_at stamped", f) } // Flip it off over the existing object — the CAS path. if err := SetProduction(ctx, s, priv, "bab", "kass", false, "paul@keymail.dev"); err != nil { t.Fatalf("SetProduction off: %v", err) } if f, _ = GetProduction(ctx, s, pub, "bab", "kass"); f == nil || f.Production { t.Fatalf("flag = %+v after flipping off, want production false", f) } } // The flag key lives beside channels/, never inside it: ChannelsPrefix // listings are parsed as pointers by the console and CLI. func TestProductionKeyIsOutsideChannels(t *testing.T) { if k := ProductionKey("bab", "kass"); k != "apps/bab/kass/production.json" { t.Fatalf("ProductionKey = %q", k) } if strings.HasPrefix(ProductionKey("bab", "kass"), ChannelsPrefix("bab", "kass")) { t.Fatal("production flag key sits under channels/ — pointer listings would try to parse it") } } // A scoped key must not be able to mint the flag: a wrapper carrying ANY // grant is refused before signature checking, and a wrapper signed by a key // other than the root fails signature checking. Together: root only. func TestProductionFlagRefusesScopedAndForeignKeys(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) // Build a validly-shaped flag payload signed by a DIFFERENT key. otherPub, otherPriv, err := ed25519.GenerateKey(rand.Reader) if err != nil { t.Fatal(err) } _ = otherPub payload, _ := json.Marshal(ProductionFlag{Schema: 1, Account: "bab", App: "kass", Production: false, SetAt: timeNowTestHelper()}) b64 := base64.StdEncoding.EncodeToString(payload) wrapper, _ := json.Marshal(signedWrapper{ Payload: b64, Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(otherPriv, []byte(b64))), }) if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, ""); err != nil { t.Fatal(err) } if _, err := GetProduction(ctx, s, pub, "bab", "kass"); err == nil { t.Fatal("a flag signed by a non-root key verified") } // A wrapper WITH a grant is refused outright — even a grant the root // really signed. Mint a real scoped key and have it sign the flag. seed, grantB64, err := MintKeyGrant(priv, "builder", []string{RungEdge}, maxGrantTTL) if err != nil { t.Fatal(err) } scopedKey, err := ParseSigningKey(seed, "test") if err != nil { t.Fatal(err) } sg, err := parseGrantBlob(grantB64, "test") if err != nil { t.Fatal(err) } wrapper, _ = json.Marshal(signedWrapper{ Payload: b64, Sig: base64.StdEncoding.EncodeToString(ed25519.Sign(scopedKey, []byte(b64))), Grant: sg, }) if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, etagOfKey(t, s, "bab", "kass")); err != nil { t.Fatal(err) } _, err = GetProduction(ctx, s, pub, "bab", "kass") if err == nil || !strings.Contains(err.Error(), "root") { t.Fatalf("a grant-bearing flag wrapper verified (err=%v), want a root-only refusal", err) } } // etagOfKey reads the current etag of an app's flag so a test can CAS over it. func etagOfKey(t *testing.T, s Store, acct, app string) string { t.Helper() _, etag, err := s.Get(context.Background(), ProductionKey(acct, app)) if err != nil { t.Fatal(err) } return etag } func TestProductionFlagRefusesWrongApp(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) if err := SetProduction(ctx, s, priv, "bab", "kass", true, ""); err != nil { t.Fatal(err) } // Copy kass's flag to keymail's key — a cross-app replay must be refused // because the SIGNED payload names the app. raw, _, err := s.Get(ctx, ProductionKey("bab", "kass")) if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, ProductionKey("bab", "keymail"), raw, ""); err != nil { t.Fatal(err) } if _, err := GetProduction(ctx, s, pub, "bab", "keymail"); err == nil { t.Fatal("another app's flag verified under keymail's key") } } // The signature binds the exact base64 payload STRING, not the decoded JSON // meaning — so a payload that is decoded, edited, and re-encoded (even // leaving the original Sig in place) must fail verification. A verifier that // decoded first and checked a signature over re-marshalled bytes would let // this through if the re-marshalling happened to reproduce the tampered // bytes; this test pins the "no canonicalisation" wire rule directly against // the field an attacker would actually want to flip. func TestProductionFlagRefusesTamperedPayload(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) if err := SetProduction(ctx, s, priv, "bab", "kass", true, "paul@keymail.dev"); err != nil { t.Fatal(err) } raw, etag, err := s.Get(ctx, ProductionKey("bab", "kass")) if err != nil { t.Fatal(err) } var sw signedWrapper if err := json.Unmarshal(raw, &sw); err != nil { t.Fatal(err) } payload, err := base64.StdEncoding.DecodeString(sw.Payload) if err != nil { t.Fatal(err) } var f ProductionFlag if err := json.Unmarshal(payload, &f); err != nil { t.Fatal(err) } if !f.Production { t.Fatal("expected production=true before tampering") } f.Production = false // flip the bit an attacker would want to flip tampered, err := json.Marshal(&f) if err != nil { t.Fatal(err) } // Keep the ORIGINAL Sig — it covers the ORIGINAL payload string. A // re-encoded payload, even with identical decoded meaning, must fail // verification without a fresh signature. sw.Payload = base64.StdEncoding.EncodeToString(tampered) wrapper, err := json.Marshal(sw) if err != nil { t.Fatal(err) } if _, err := s.Put(ctx, ProductionKey("bab", "kass"), wrapper, etag); err != nil { t.Fatal(err) } if _, err := GetProduction(ctx, s, pub, "bab", "kass"); err == nil { t.Fatal("a tampered payload (re-encoded, original sig) verified") } } // A trashed app cannot be flagged production in EITHER direction: restore // first. This holds even though the flag would refuse to WRITE anything // destructive itself — the point is that a production flag appearing on a // trashed app contradicts the flag's own promise (production = never // deletable), so the door closes before the CAS write, not after. func TestSetProductionRefusesTrashedApp(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, _ := testKeys(t) if err := TrashApp(ctx, s, "bab", "derp", "paul@keymail.dev", time.Now()); err != nil { t.Fatal(err) } if err := SetProduction(ctx, s, priv, "bab", "derp", true, "paul@keymail.dev"); err == nil { t.Fatal("SetProduction(true) on a trashed app succeeded, want a refusal") } // The OFF direction is refused too — there is nothing legitimate to do to // a trashed app's flag; restore first. if err := SetProduction(ctx, s, priv, "bab", "derp", false, "paul@keymail.dev"); err == nil { t.Fatal("SetProduction(false) on a trashed app succeeded, want a refusal") } // No flag object should have been written. if _, _, err := s.Get(ctx, ProductionKey("bab", "derp")); err == nil { t.Fatal("a refused SetProduction on a trashed app wrote the flag object anyway") } } // An unreadable trash marker refuses too — the usual direction: a caller // about to declare production must never act as though a marker it cannot // verify simply isn't there. Uses a store that fails ONLY the tombstone // key (everything else, including the eventual production.json write, // would otherwise succeed) so this test actually pins the new trash-marker // check rather than any generic "the store is broken" refusal. func TestSetProductionRefusesUnreadableTrashMarker(t *testing.T) { priv, _ := testKeys(t) real := NewDirStore(t.TempDir()) s := &tombstoneReadFailStore{Store: real, key: AppTombstoneKey("bab", "derp"), err: errors.New("bucket on fire")} if err := SetProduction(context.Background(), s, priv, "bab", "derp", true, "paul@keymail.dev"); err == nil { t.Fatal("SetProduction with an unreadable trash marker succeeded, want a refusal") } // Confirm nothing else about the store is broken — the refusal really is // the trash-marker check, not a store that happens to fail everything. if _, _, err := real.Get(context.Background(), ProductionKey("bab", "derp")); !errors.Is(err, os.ErrNotExist) { t.Fatalf("production flag written despite an unreadable trash marker: err=%v", err) } } // tombstoneReadFailStore fails Get for exactly one key and delegates // everything else to the wrapped Store. type tombstoneReadFailStore struct { Store key string err error } func (t *tombstoneReadFailStore) Get(ctx context.Context, key string) ([]byte, string, error) { if key == t.key { return nil, "", t.err } return t.Store.Get(ctx, key) } // timeNowTestHelper is a stamped, truncated now — what signProductionFlag // itself writes — for tests that hand-build payloads. func timeNowTestHelper() time.Time { return time.Now().UTC().Truncate(time.Second) } // Task-4-review fix 5 (Minor, promoted), tightened by re-review: SetAt is // truncated to the second, so two flips within the same wall-clock second // would otherwise share a set_at — and the box-side replay floor (bake.go's // NewestSetAt) treats an equal set_at as nothing new, silently swallowing // the second flip on every box that already saw the first. A single // before/after pair is vacuous if the two calls happen to straddle a // second boundary (it would pass with or without the bump). Looping several // rapid flips and requiring EVERY consecutive pair to be strictly // increasing removes that escape: the bump guarantees strict monotonicity // regardless of whether any given pair lands in the same truncated second // or crosses one, so this holds deterministically with the fix in place. func TestSetProductionMonotonicSetAtOnRapidFlip(t *testing.T) { s := NewDirStore(t.TempDir()) ctx := context.Background() priv, pub := testKeys(t) var prev time.Time for i := 0; i < 3; i++ { if err := SetProduction(ctx, s, priv, "bab", "kass", i%2 == 0, "paul@keymail.dev"); err != nil { t.Fatalf("SetProduction flip %d: %v", i, err) } f, err := GetProduction(ctx, s, pub, "bab", "kass") if err != nil || f == nil { t.Fatalf("GetProduction after flip %d: %v, %v", i, f, err) } if i > 0 && !f.SetAt.After(prev) { t.Fatalf("flip %d's set_at %v not strictly after flip %d's %v (same-second collision not bumped)", i, f.SetAt, i-1, prev) } prev = f.SetAt } }