Problem/Motivation

2.11.0 shipped signed portable policy bundles (#3616536): verify, activate, simulate, revoke, rollback, emergency deny, and policy_bundle_digest on every audit row. Simulation already refuses an operation when the attested bundle denies it, and a local deny cannot be widened by an upstream allow. That evaluation is not yet on the live access path. An activated bundle that denies an operation currently does not change what a governed request can do.

Proposed resolution

Call the existing simulate/deny evaluation from the live governance decision path so an attested bundle can only refuse more than the local profile, never less.

  • If no bundle is attested, behaviour is unchanged.
  • If a bundle is attested and denies the operation (or emergency deny is armed), the request is refused with a stable reason and the attested digest on the audit row.
  • A local deny still wins when the bundle would allow.
  • Missing signing key cannot invent new authority; existing local denials still apply.

Remaining tasks

  • Wire the evaluation into the existing access/decision seam (do not add a second policy engine).
  • Tests: no bundle = byte-identical allow/deny; bundle deny refuses; local deny still wins; emergency deny refuses; digest cited on the refusal row.
  • Document the live hook in README/CHANGELOG.

User interface changes

None required. Operators already activate/revoke bundles; this issue only makes those attestations enforceable.

API changes

No new public type. Existing McpPolicyBundleRegistry::simulate() becomes the live evaluation, not only a preview.

Data model changes

None.

Comments

jmcerda created an issue. See original summary.

  • jmcerda committed 79860d9c on feature/3617702-live-bundle-deny
    Issue #3617702: cap cached live allows at the attested document TTL.
    
    A...

  • jmcerda committed 909f22cd on feature/3617702-live-bundle-deny
    Issue #3617702: share unit-test state without a by-ref null parameter.
    
jmcerda’s picture

Merged to 1.x (0de7172).

The attested simulate() floor is now on the live access path: entity, create, config, and JSON:API filter access; the context schema document; GraphQL query and mutation gates; and governed raw SQL. A local deny cannot be widened. Emergency deny and revoke of the active digest refuse live access. An attested digest that will not verify fails closed. The client-facing reason is policy_bundle_denied; the digest stays on the audit row.

Working PR: https://github.com/Wilkes-Liberty/mcp_sentinel/pull/151

Not a tagged release. This issue stays Active until a module tag ships the live floor.

  • jmcerda committed 79860d9c on 1.x
    Issue #3617702: cap cached live allows at the attested document TTL.
    
    A...

  • jmcerda committed 909f22cd on 1.x
    Issue #3617702: share unit-test state without a by-ref null parameter.
    

  • jmcerda committed 5c1a963f on 1.x
    Issue #3617702: fail closed when an attested bundle will not verify.
    
    An...

  • jmcerda committed a513fd86 on 1.x
    Issue #3617702: honor attested policy-bundle denials on the live access...
jmcerda’s picture

Status: Active » Fixed

Released in 2.12.0.

The attested policy-bundle floor is now consulted on the live access path: McpAccessChecker (entity, create, config, JSON:API filter), the context schema document, GraphQL query and mutation gates, and the governed drush SQL command all call the same evaluation simulate already used.

Tightening only, by construction: a local deny cannot be widened by an upstream allow; an attested digest whose document will not verify (expired, revoked, tampered, or missing key) fails closed rather than allowing; emergency deny and revoke of the active digest refuse live access; and cached allows do not outlive the attested document's remaining TTL, so the floor cannot expire into an allow.

Client-facing reason is the stable code policy_bundle_denied, with the attested digest kept on the audit row rather than in the response.

https://www.drupal.org/project/mcp_sentinel/releases/2.12.0

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.