feat: add single-use delegated Core authorization (ENG-CORE-DELEGATION-001A) - #124
Merged
Merged
Conversation
…ion-001a-read-broker # Conflicts: # adapters/mcp-server/pyproject.toml # tests/test_ci_contract.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary and threat model
Adds a separate, opaque delegated credential authority for one short-lived, single-use Core read request. The design prevents an external broker from inheriting provisioning authority, stores no plaintext bearer secret, consumes authority before route execution, terminally revokes unused tokens on any observed authority loss, and returns generic authentication failures.
Work-start and repository state
3e4f8933df10dedb4729a4aaccc0676d833a17c23e4f8933df10dedb4729a4aaccc0676d833a17c2f06a15b(includes currentorigin/mainat96fd2f3dda3e4629e28c2efdba69a4566ee5f11d)a744e761e35cf243b02b0f7aa128948a5fc78585Files and migration 029
28 files changed across delegated auth/API/CLI/config/model code, migration and deterministic downgrade, CI leak scanning, focused tests, operator/security/integration documentation, and an MCP v1 compatibility constraint exposed by the fresh CI resolver.
Migration 029 adds
service_delegation_grants,service_delegation_tokens,service_delegation_idempotency, and append-onlyservice_delegation_events; issue/revoke SECURITY DEFINER functions; subject-integrity, append-only, revoked-grant-history, and terminal authority-invalidation triggers; binding integrity keys and required indexes. Objects remain owner-owned. The provisioner has no delegation-table SELECT or DML and receives EXECUTE only on the two public delegation functions; trigger helpers are not executable by application/provisioner roles.Clean 001→029 and persisted 028→029 upgrade/downgrade proofs pass. Existing clients, credentials, bindings, permissions, events, and the provisioner password remain unchanged. No broker, grant, token, event, or
delegation.issuepermission is created silently.Permission and broker model
The canonical service vocabulary adds
delegation.issue. The test broker has only that permission. The separate binding owner retains the five provisioning permissions and has no delegation permission. An owner-created active broker→binding-owner grant is required, has a bounded maximum TTL, and cannot be reactivated after revocation.Credential lifecycle
The delegated grammar is the Bearer-compatible
engd_<22-character-base62-key-id>_<43-character-URL-safe-secret>. PostgreSQL stores only the redacted representation: key ID plus SHA-256 secret digest. Scope is exactlyread, audience isengram-core, TTL is 30–300 seconds, and successful authentication atomically transitions active→used before returning the existing human principal.Malformed delegated material never reaches legacy API-key fallback. Delegated principals are not cached. Service routes retain strict service-credential parsing. Generation, parsing, documentation, leak scanning, fixtures, and tests use only the underscore form; no compatibility path exists for the unmerged asterisk form. A generation test proves every emitted credential is valid RFC 6750 Bearer token material.
Idempotency and ambiguous-response recovery
Canonical compact sorted request state is recomputed inside PostgreSQL. Same-key replay and exact external-reference reconciliation return original metadata without plaintext or expiry extension. Changed key/request or external-reference/request pairs produce bounded conflicts without partial state. Response-loss recovery is: replay, revoke the prior external reference, then issue under a new external reference; the old token remains unusable and the replacement authenticates once.
Revocation and terminal invalidation matrix
Unused authority is denied after explicit revoke, expiry, use, grant revoke, issuer disable, binding-owner disable, issuer credential revoke/expiry, permission removal, or subject/binding-integrity change. Authority mutations atomically revoke every active affected token and append bounded denial evidence. A use attempt that first observes time-based or otherwise invalid authority atomically marks the token
authority_invalidatedbefore returning401.Disable/re-enable, permission remove/restore, credential revoke/restore, and subject invalid/restore cycles never reactivate an affected token. A credential that expires and is restored before its token's first use still terminally invalidates that token. Status-only used/revoked transitions bypass current-subject validation, while insertion and identity-column changes retain relationship validation. Explicit revocation while the principal is invalid succeeds idempotently as
already_revoked.Revoke returns truthful
revoked,already_revoked,already_used, andnot_founddispositions. A read authenticated before a later revocation commit may finish; authentication beginning afterward cannot succeed.Scope denial matrix
/whoami: allowed.PostgreSQL race, events, and rollback results
Concurrent use/use yields exactly one success. Concurrent equivalent issue yields one token; same-external reconciliation and changed-request conflict serialize correctly. Use/revoke yields only use-first/already-used or revoke-first/denied. A use blocked on the token row observes a committed revoke.
Isolated count proofs record exactly one event for grant create/revoke and each issued, replayed, reconciled, used, revoked, authority-invalidated, and denied operation; the conflict sequence records one bounded event per intentional conflict. Event fields contain digests rather than raw external identities.
Owner-installed test failpoints after token insert, idempotency insert, event insert, credential timestamp update, and during revoke prove no partial token, idempotency, event, credential timestamp, or status mutation survives rollback. Locked issuance rejects credential and grant revocation occurring after preliminary service authentication.
Privilege and secret proofs
The provisioner is LOGIN, NOSUPERUSER, NOBYPASSRLS, NOCREATEDB, NOCREATEROLE, NOREPLICATION, NOINHERIT; it has no memberships, schema CREATE, or public object ownership. Delegation-table privilege checks and delegation-function EXECUTE checks pass.
Tracked/untracked repository files, all PostgreSQL text/JSON fields, generated diffs, and container logs are scanned for credential-shaped material. The scanner recognizes the Bearer-compatible delegated grammar and works from Git metadata locally and from the exact copied source snapshot in git-less CI runtime images.
Verification
git diff --check, and leak scans remain covered by CI.4748e6a2a99dcb81b84693f1a4b993d3239f3e13.609c68d6be7094f5b3d0f33a098c57a700adfc4a.f06a15bafb30609d40668b6494ddf1e0841fcb56(3,006 root passed; SDK 55, MCP 36, hooks 186; leak scantracked_files=0 database_fields=0).Deferred work
Portal broker/BFF delivery, memory UI, browser proxying, review step-up authority, and additional-agent delegation remain explicitly out of scope. Portal was not modified.