key_id) differs.
1. Top-level structure
Canonical-hash message
The signature is computed over the UTF-8 bytes of the dotted message:canonical_json recursively sorts object keys and emits compact JSON (no whitespace, no insignificant separators). The resulting digest is the SHA-256 hash in lowercase hex.
The signature wraps key_id and signed_at in the message, so an attacker that obtained the per-row recipe payload but did not produce the signature cannot replay the envelope under a different signed_at.
2. The two envelope variants
The promoter writes two parallel envelopes in the same canonical recipe set. They differ only in the key they use:
The two signing chains are independent. Poisoning of the primary tier degrades gracefully to the secondary tier; the gateway’s tiered read pipeline (see Managed Rules) verifies each tier with its own JWKS binding and fails over on signature failure.
3. Per-recipe row shape
Each entry inrecipes is the full internal detection-recipe row — including proprietary detection content (parsed_content, yaml_content) and internal routing/telemetry fields (technique_category, technique_ids, hit_count, similarity_hash, has_tier1/2/3, effective_since, soak_ends_at) not documented here. This page documents only the fields relevant to composition and severity semantics:
Internal detection-routing fields (the detection-routing fields
door, threat_type, variant_class, evasion_technique, mitre_atlas, and the detection content itself) are out of scope for this spec — the row carries them on the wire, but the platform writes/reads them internally; this page documents what a gateway or tooling integrator needs, not the full internal shape.
Terminology note: severity_p vs “tier”
Concept-level docs (for example Managed Rules and AEGIS) use the words tier 1, tier 2, tier 3. The database canonical is the column severity_p with values p0 / p1 / p2. The two vocabularies refer to the same dimension; spec pages use the database canonical so wire-format consumers can map directly. The bridge is fixed:
Per-row promotion signature
Each recipe row also carries a per-row Ed25519 signature in itspromotion_signature field (sourced from the internal detection_recipes.promotion_signature column, and — unlike the internal-only fields above — present in the envelope’s recipes[] entries). The signature is computed over the canonical-hash inputs recipe_id, version, composition_scope, surface, severity_p, scope, created_by, created_at (note: recipe_id here names the hash-input field for this specific computation — the envelope row itself carries the same value under id). created_by/created_at are inputs to this signature but are not themselves present in the envelope row. The envelope signature wraps the whole recipe set and is the signature the gateway verifies at read time. The gateway does not currently re-check the per-row promotion_signature; it is carried for provenance.
4. Signature verification — internal-resilience, not customer-side
The verification model and what it does and does not provide:- What the envelope signature provides. Defense in depth between the primary and secondary read tiers. The gateway verifies on every read; a poisoned primary-tier envelope (whose signature does not verify against the primary-tier verification key set) falls through to the secondary tier (a different signing chain). A poisoned secondary-tier envelope falls through to the last-known-good in-process cache.
- What it does not provide. Customer-side cryptographic attestation. The verification key sets for both tiers are held server-side as secrets in the gateway’s runtime environment, not customer-reachable URLs.
- The customer-facing transparency surface. The append-only audit chain on
recipe_review_actionsis the recipe-level provenance trail, verifiable via the admin API surface. /v1/.well-known/jwks.jsonpublishes a different set of keys. That endpoint publishes AAP attestation keys (theaap_signing_keystable). Recipe-set signing keys are held in Mnemom’s operator credential vault under the operator’s rotation policy and are not part of the public JWKS.
5. Three independent signing chains
The full rule plane uses three Ed25519 keys, each rotated under Class A annual rotation (90-day JWKS overlap):
Poisoning of the primary tier degrades to the secondary tier, which uses a different signing chain. The promotion signing key signs individual rows for provenance; the gateway’s read-time check is the envelope signature.
6. Failover behavior
The gateway’s tiered read pipeline at request time:- In-process memory cache — fresh entries (< 60 s) served from in-process state.
- Primary storage tier — no expiration (overwritten on every promotion); signed with the primary-tier signing key.
- Secondary storage tier — per-promotion refresh; signed with the secondary-tier signing key (independent chain).
- Last-known-good in-process cache — staleness up to 24 hours.
- No verified rule set — the gateway continues with an empty Managed Rules index and its built-in detection only, and raises an operator alert. Requests are not rejected.
7. Failover alerting
The gateway emits internal operator alerts on the failover path, at two severities. These are operator-facing diagnostics that customers do not consume directly.8. Example — a signed envelope at GA
The Day-1 envelope carries the five GA-seeded synthetic Managed Rules. Signatures, identifiers, and signed-at are placeholder-shaped here; production envelopes carry the real cryptographic values.9. Validation notes
- Versioning.
key_idincludes the rotation cohort. The gateway verifies against its configured JWKS, never a hardcoded key. - Signature failure is loud. The gateway emits
P0_kv_sig_failorP0_r2_sig_failon signature failure and falls through to the next tier. It does not silently accept an envelope whose signature did not verify. - Replay defense. The signed message includes
key_idandsigned_at— an envelope captured at time T cannot be replayed under a differentsigned_atbecause the signature would not verify.
See also
- Managed Rules — concept page; pipeline + reviewer modes
- AEGIS — the protection-layer framing
- Card Composition — composition_scope semantics