You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implements the direction decided in policy-hot-reload.md (#490), after the two direction-independent fixes landed in #492.
Why
A hash pinned at startup says the policy is exactly this artifact, decided before the process began. That is what makes a bundle attestable — the hash goes into the TRACE claim and a verifier can confirm the gateway enforced what it said it did. It also means a pinned hash can never authorise a bundle that changed, which is why policy_reload_interval_seconds > 0 alongside CMCP_POLICY_HASH now aborts startup rather than pretending to work.
Runtime policy change needs an anchor that survives the bundle changing. A pinned signing key, not a pinned artifact hash. Trust moves from "this exact artifact" to "any artifact this authority approves", which is the thing that actually makes runtime change safe.
The operational motivation is real: for a confidential gateway, "restart to change policy" means an attestation cycle, so tightening a policy in response to an incident is expensive today.
Scope
Sign the manifest. The bundle manifest gains a signature over its own canonical contents. author_identity and commit_sha are already there and are currently unsigned assertions; signing makes them claims someone is accountable for.
Pin a key, not a hash. A new setting carrying the public key permitted to sign policy. CMCP_POLICY_HASH keeps its current meaning and stays correct for a deployment that wants one policy for the life of the process — the two are alternatives, and the startup refusal from fix(policy): refuse the inert reload config, and stop re-reading per call #492 stays.
Monotonic version. The manifest version must increase across reloads, and a bundle whose version does not is refused. Without this the feature is a downgrade attack: anyone who can write the bundle directory replays yesterday's more permissive policy and every signature still verifies.
Evidence. A TRACE claim records the bundle hash it evaluated under plus the signer identity and the bundle version, so a verifier can answer both "what policy ran" and "who authorised it" for a process whose policy changed mid-life. Without this, claims from one process carry different bundle hashes with nothing explaining why.
Sub-questions to settle before coding
These do not reopen the direction, but each changes the implementation:
Key rotation. Can the pinned key change without a restart? Restart-only is probably right, since rotation is rarer than policy change — but it should be a decision, not an omission.
Revocation. A compromised signing key needs to stop being trusted faster than a fleet restart, or the advantage over hash pinning shrinks. This is the hardest of the four.
Signature coverage. Over the bundle hash (smaller, reuses _canonical_bundle_hash) or over the full canonical bundle (self-contained)?
In-flight sessions. What happens to a session already admitted under the previous bundle. Listed as out of scope in the design doc, and now needs an answer, because a session that was allowed to start under a permissive policy continuing under a stricter one is a decision either way.
Acceptance
A signed bundle whose signature verifies against the pinned key, and whose version increased, is installed at runtime with no restart.
A validly signed bundle whose version did not increase is refused, with a test that would fail if the version check were dropped.
An unsigned or wrongly signed bundle is refused, and the old policy stays in force.
The TRACE claim carries signer and version alongside the bundle hash, and a verifier can reconstruct which policy was live for a given call.
Reload tests cover the signed path in the configuration production actually runs — not only the unpinned dev shape. That gap is what hid the original defect and it must not reopen.
Also
CMCP_CATALOG_HASH has the identical pin and load_catalog the identical shape, so the catalog has the same latent problem. Whatever lands here should be applied there deliberately rather than copied. Worth its own issue once this one has a design.
Implements the direction decided in policy-hot-reload.md (#490), after the two direction-independent fixes landed in #492.
Why
A hash pinned at startup says the policy is exactly this artifact, decided before the process began. That is what makes a bundle attestable — the hash goes into the TRACE claim and a verifier can confirm the gateway enforced what it said it did. It also means a pinned hash can never authorise a bundle that changed, which is why
policy_reload_interval_seconds > 0alongsideCMCP_POLICY_HASHnow aborts startup rather than pretending to work.Runtime policy change needs an anchor that survives the bundle changing. A pinned signing key, not a pinned artifact hash. Trust moves from "this exact artifact" to "any artifact this authority approves", which is the thing that actually makes runtime change safe.
The operational motivation is real: for a confidential gateway, "restart to change policy" means an attestation cycle, so tightening a policy in response to an incident is expensive today.
Scope
author_identityandcommit_shaare already there and are currently unsigned assertions; signing makes them claims someone is accountable for.CMCP_POLICY_HASHkeeps its current meaning and stays correct for a deployment that wants one policy for the life of the process — the two are alternatives, and the startup refusal from fix(policy): refuse the inert reload config, and stop re-reading per call #492 stays.versionmust increase across reloads, and a bundle whose version does not is refused. Without this the feature is a downgrade attack: anyone who can write the bundle directory replays yesterday's more permissive policy and every signature still verifies.Sub-questions to settle before coding
These do not reopen the direction, but each changes the implementation:
_canonical_bundle_hash) or over the full canonical bundle (self-contained)?Acceptance
Also
CMCP_CATALOG_HASHhas the identical pin andload_catalogthe identical shape, so the catalog has the same latent problem. Whatever lands here should be applied there deliberately rather than copied. Worth its own issue once this one has a design.