Skip to content

feat(promote): bind HMAC v2 tokens to config hash and inventory - #25

Merged
gesh75 merged 3 commits into
mainfrom
feat/hmac-v2-inventory-binding
Sep 2, 2026
Merged

gesh75 merged 3 commits into
mainfrom
feat/hmac-v2-inventory-binding

Conversation

@gesh75

@gesh75 gesh75 commented Aug 30, 2026 •

Copy link
Copy Markdown
Owner

Why

Closes #24. External review (@ThorCommerce): Bind the token to the config hash and target inventory version. An approved token shouldn't survive a change to either one.

Phase 3 v1 tokens are bound to approver + bundle sha256 + expiry. That kills a token when the sealed bundle changes. It does not give G2/G3 a first-class config or inventory claim, and it does not refuse an approved token after the live target inventory has moved.

What landed on this branch

  • core/promote/tokens.py — v2 payload {a, b, c, inv, exp, n, v:2}.
    c = SHA-256 of canonical grounded configs.
    inv = SHA-256 of twin topology + engine + node_count + device list + optional twin.inventory_rev.
    mint_token_for_bundle() emits v2. Legacy mint_token(approver, bundle_sha256) still emits v1.
  • core/promote/gate.py — G2/G3 pass config + inventory digests into verify_approval. Optional live_inventory_sha256 overrides the bundle-derived inventory fingerprint.
  • core/promote/promote.py — live_inventory_sha256 threaded through; recorded on the promotion record when supplied.

v1 tokens keep verifying. Unknown token versions fail closed. Community/sim with no AEGIS_APPROVE_KEY stays asserted-unverified.

Honesty

This is not YubiKey PIV and not a live CMDB connector. The inventory fingerprint is derived from the twin record in the bundle unless the caller supplies a live digest at promote time.

Tests already run locally (not all pushed yet)

  • python -m aegis.tests.tokens_test — PASS 33 checks (v1 + v2)
  • python -m aegis.tests.promote_test 200 — PASS (P8 v1 + v2 + live inventory drift deny)
  • python -m aegis.tests.api_test — PASS 48 checks

Existing v1 CI suites on this branch should stay green because v1 mint/verify paths are unchanged and extra gate hashes are ignored for v1 tokens.

Follow-up commit still needed from the working tree: tests/tokens_test.py, tests/promote_test.py, tests/api_test.py, serve.py (bundle mint → v2, promote accepts inventory_sha256), CHANGELOG / DEVELOPER / PHASES / Pages.

Test plan

  • Local tokens 33 + promote 200 + api 48
  • CI green on this PR
  • Push remaining test/docs/serve files
  • Tag v0.2.1 after merge so the X reply can point at a release

Summary by Sourcery

Bind approval tokens to grounded configuration and target inventory state so promotions cannot reuse approvals after either changes.

New Features:

  • Add v2 HMAC approval tokens bound to grounded configuration and target inventory digests.
  • Allow bundle-based API token minting and live inventory overrides during promotion.

Bug Fixes:

  • Reject approvals when configuration or inventory drift invalidates a v2 token.
  • Fail closed for unknown token versions and tampered bundles.

Enhancements:

  • Preserve backward compatibility with v1 tokens and asserted-unverified behavior when HMAC is not configured.
  • Record supplied live inventory digests in promotion records.

Documentation:

  • Document v2 token claims, API behavior, and configuration/inventory binding.

Tests:

  • Expand token, promotion, and API coverage for v2 claims, drift denial, legacy compatibility, and unsupported versions.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sourcery-ai

sourcery-ai Bot commented Aug 30, 2026 •

Copy link
Copy Markdown

Reviewer's Guide

Adds backward-compatible HMAC v2 approval tokens bound to canonical grounded configs and target inventory, threads optional live inventory through promotion gates and records, and fails closed on config or inventory drift while retaining v1 and unverified-community behavior.

Sequence diagram for HMAC v2 promotion approval

sequenceDiagram
    participant Caller
    participant Promote as promote
    participant Gate as gate.evaluate
    participant Tokens as verify_approval
    participant HMAC as HMAC key

    Caller->>Promote: promote(bundle, approval_token, live_inventory_sha256)
    Promote->>Gate: evaluate(bundle, approver, approval_token, live_inventory_sha256)
    Gate->>Gate: config_digest(bundle)
    Gate->>Gate: inventory_digest(bundle) or live_inventory_sha256
    Gate->>Tokens: verify_approval(approver, token, bundle_sha256, config_sha256, inventory_sha256)
    Tokens->>HMAC: Validate HMAC-SHA256
    HMAC-->>Tokens: Valid signature
    Tokens-->>Gate: Approval accepted or drift denied
    Gate-->>Promote: GateDecision
    Promote-->>Caller: Promotion record with live_inventory_sha256
Loading

Flow diagram for v2 token binding and drift denial

flowchart TD
    A[mint_token_for_bundle] --> B[digests_from_bundle]
    B --> C[Bundle SHA-256]
    B --> D[config_digest]
    B --> E[inventory_digest]
    C --> F[HMAC v2 token]
    D --> F
    E --> F
    F --> G[verify_approval]
    G --> H{Bundle, config, and inventory match?}
    H -->|yes| I[Approval accepted]
    H -->|no| J[Promotion denied]
    K[Optional live_inventory_sha256] --> G
Loading

File-Level Changes

Change Details Files
Introduce HMAC token v2 claims for grounded configuration and target inventory, while preserving v1 compatibility.
  • Canonicalize and hash generated configs, including device and vendor identity.
  • Canonicalize and hash twin topology, engine, node count, device list, and optional inventory revision.
  • Add v2 minting APIs and enforce claim presence, format, and matching during verification.
  • Reject unsupported versions and fail closed when v2 verification inputs are missing or changed.
core/promote/tokens.py
Make promotion gates validate live or bundle-derived configuration and inventory bindings.
  • Compute config and inventory digests during G2/G3 evaluation.
  • Allow a supplied live inventory digest to override the bundle-derived fingerprint.
  • Pass the live inventory value through promotion and record it in the promotion result.
core/promote/gate.py
core/promote/promote.py
Retain legacy approval behavior and document the new token and inventory-binding semantics.
  • Keep the legacy mint_token path producing v1 tokens when no additional claims are supplied.
  • Preserve asserted-unverified behavior when HMAC configuration is absent.
  • Document that inventory binding is bundle-derived unless a live digest is supplied.
core/promote/tokens.py
core/promote/gate.py

Assessment against linked issues

Issue Objective Addressed Explanation
#24 Introduce v2 HMAC approval tokens containing config and inventory fingerprints, while preserving v1 token minting and verification compatibility. ✅
#24 Require v2 token verification to match the bundle, canonical generated-config hash, and inventory fingerprint, with explicit fail-closed reasons for config or inventory mismatches and unknown token versions. ✅
#24 Use bundle-derived digests by default at the promotion gate, support live inventory overrides that deny promotion after inventory drift, and make bundle-based approval minting emit v2 tokens through the documented API path. ❌ The core gate and promote functions derive and verify config/inventory digests and accept a live inventory override. However, the diff does not include the required serve/API changes for POST /api/approve/mint with a bundle to emit v2 tokens or for the API promotion path to accept inventory_sha256; the PR body explicitly lists those changes as still pending.

Possibly linked issues


Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved. Cursor Bugbot was not present after the first checks poll, so that signal was skipped; remaining policies and review state do not require human review. No reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="core/promote/tokens.py" line_range="192-193" />
<code_context>
+
+    cfg = (config_sha256 or "").strip().lower() or None
+    inv = (inventory_sha256 or "").strip().lower() or None
+    if version is None:
+        version = 2 if (cfg or inv) else 1
+    if version not in (1, 2):
+        raise TokenError("token version must be 1 or 2")
</code_context>
<issue_to_address>
**issue (broader_impact):** The existing `/api/approve/mint` caller still invokes `mint_token()` without config or inventory claims, so it continues issuing v1 tokens. G2/G3 deliberately accept those v1 tokens without checking config or inventory, leaving the public approval flow unable to enforce the feature's stated binding.

**Triggers:** When approvals are minted through the existing server API rather than by directly calling `mint_token_for_bundle()`.

**Suggested fix:** Update the API mint flow to accept the bundle (or its config and inventory digests) and call `mint_token_for_bundle()`/`mint_token(..., version=2)`; update promotion input handling to pass the live inventory digest.
</issue_to_address>

Sourcery assessment

Needs a human reviewer. 1 finding to address first, and if the new HMAC claims are validated incorrectly, an approval token could authorize promotion of configurations or inventory different from what the approver reviewed, allowing changes to reach the live connector. Reverting would stop future misuse but would not undo any production changes already promoted.

Blocking findings: core/promote/tokens.py:193


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread core/promote/tokens.py
Close the #24 API gap: POST /api/approve/mint with a bundle now mints v2,
promote accepts a live inventory_sha256, and v2 verification fails closed
on missing claims. Hash-only mint stays v1.

Co-authored-by: georgy75 <georgy75@gmail.com>
@gesh75
gesh75 merged commit 2516c75 into main Sep 2, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(promote): bind HMAC tokens to config hash + inventory fingerprint

2 participants