Repository navigation
feat(promote): bind HMAC tokens to config hash + inventory fingerprint #24
Description
Problem
Phase 3 HMAC tokens (aegis1.<payload>.<mac>) are bound to:
- approver
- evidence bundle
integrity.sha256 - expiry
- nonce
That is enough to kill a token when the sealed bundle changes. It is not enough to make the approval claims explicit, and it does not give G2/G3 a first-class way to refuse an approved token after the target inventory has moved.
External review (X / @ThorCommerce): "Bind the token to the config hash and target inventory version. An approved token shouldn't survive a change to either one."
That is the correct binding.
Why it matters
TOCTOU: validate against inventory N, mint a token for bundle B_N, inventory becomes N+1, promote B_N onto the new target. v1 still verifies because it only checks the bundle hash.
A config-only or inventory-only mismatch should also fail closed with a specific reason, not a generic bundle-hash miss.
Recommended fix
Add token v2 claims. Keep v1 verifiable until expiry.
{
"a": "<approver>",
"b": "<bundle sha256>",
"c": "<config sha256>",
"inv": "<inventory sha256>",
"exp": 0,
"n": "<nonce>",
"v": 2
}c= SHA-256 of canonical{device, vendor, config}rows fromchange.generated_configsinv= SHA-256 of canonical{topology, engine, node_count, devices[]}fromtwin+ device listverify_approval()on v2 requirescandinvto match the values supplied at verify timegate.evaluate()derivescfrom the bundle andinvfrom the bundle unless the caller passes liveinventory_sha256- live inventory mismatch → deny, even if the bundle hash still matches
- v1 tokens keep working (approver + bundle hash + expiry only)
- mint without
c/invstill emits v1 so existing callers do not break mint_token_for_bundle()andPOST /api/approve/mintwith abundleemit v2
Tests
- v1 mint/verify unchanged
- v2 mint from bundle verifies against the same bundle
- v2 + different config hash → deny (
token is not bound to this config hash) - v2 + different inventory hash → deny (
token is not bound to this inventory) - v2 + live inventory N+1 at the gate → deny
- v2 with missing
c/invat mint →TokenError - unknown
v→ deny - promote P8 still accepts a bound token (v1 or v2)
Honesty
Community/sim with no AEGIS_APPROVE_KEY stays asserted-unverified.
This is not YubiKey PIV and not a live CMDB inventory connector. The inventory fingerprint is derived from the twin record in the bundle unless the caller supplies a live digest at promote time.
Out of scope
- Hardware PIV signer
- SSH/NETCONF live connector
- Inventing a new
inventory_revfield on the evidence schema (use twin topology + device list; addtwin.inventory_revlater if a source of truth exists)
Activity
gesh75 commentedon Aug 30, 2026
Implementation started on #25 (
feat/hmac-v2-inventory-binding).Core already on the branch:
tokens.pyv2 claims (c,inv),gate.evaluate(live_inventory_sha256=...),promote()records the live digest.Local verification before the remaining test/docs/serve commit:
python -m aegis.tests.tokens_test— PASS (33)python -m aegis.tests.promote_test 200— PASSpython -m aegis.tests.api_test— PASS (48)After merge: tag
v0.2.1so the X reply can point at the cut.fix(promote): emit HMAC v2 from bundle mint and cover drift
feat(promote): bind HMAC v2 tokens to config hash and inventory (#25)