Repository navigation
Add least-privilege cross-repository dispatch capability contract #735
Description
Problem
Consumer required workflows exchange a GitHub Actions OIDC assertion at /exchange_github_app_token, but the returned installation token is not proven to authorize a continuation dispatch into the central ContextualWisdomLab/.github repository.
This is a canonical Noema capability-boundary defect, not a consumer-workflow defect. The consumer must not compensate with a PAT, inherited repository secret, mutable source read, or broader GitHub token.
Exact RED evidence
A retained protected run exercises the existing exchange followed by central dispatch:
- consumer:
ContextualWisdomLab/late-life-anxiety-reanalysis - source ref:
34032cff52e8522db6ea0aad9f68ae0217e86ee3 - retained log:
local/supervisor-audit-20260912/current-ci/103571810868.log - evidence blob:
cb91b0c0bfe1ff9001cab9e1675d97b00cfcef32 - exchange call: line 141
- central
POST repos/ContextualWisdomLab/.github/dispatches: line 158 - terminal result: line 173,
Resource not accessible by integration (HTTP 403)
The protected .github doctoring note docs/doctoring/codeql-pr-private-consumer-read-permissions.md:21 also records that this central-dispatch use of a consumer-origin exchanged token was unexercised and might be out of scope. Proposed consumer PR ContextualWisdomLab/.github#2540 repeats that authority pattern and is Draft pending this owner contract.
Required contract
Implement and document a versioned API/client/schema that issues or delegates a short-lived, least-privilege capability for an explicit allowed central target repository and action. The contract must:
- validate GitHub OIDC issuer, audience, organization, source repository, ref, reusable-workflow identity, and current exact revision;
- bind source repository/PR/head and the explicit target repository/action;
- permit only the allowlisted central
repository_dispatchevent(s), without repository contents or administration write authority; - reject arbitrary target repositories, event types, mutable heads, stale heads, forks, replay, malformed claims, and whitespace/non-string credential fields;
- return no long-lived token and never log the assertion or capability;
- expose provenance, expiry, and exact-revision evidence sufficient for the consumer to fail closed;
- distinguish exchange/authorization failure from provider capacity and user cancellation.
Do not infer request fields or retrofit an undocumented query/body shape onto the existing endpoint. Publish the selected contract as a versioned owner API/client/schema.
Test-first acceptance
- RED integration fixture reproduces the retained 403 from a consumer OIDC identity targeting
ContextualWisdomLab/.github. - Unit/contract tests cover claim binding, target/action allowlists, replay/expiry, malformed JSON/typed credentials, and authorization denial.
- Hosted integration proves the exact consumer workflow identity can create only the intended central event.
- Owner CI is GREEN for API/schema, security, SBOM, and provenance on one exact SHA.
- Release an immutable Noema version containing the contract and evidence.
- Only then bump/pin
ContextualWisdomLab/.githubto that released contract and revalidate #2540 plus affected consumer PR heads.
Boundary
No PAT fallback, inherited central secret, cross-repository source read, or broad GitHub App installation token is acceptable. Until an immutable owner release exists, consumers must remain Draft/feature-gated and fail closed.
Permission-model correction — broker, not delegated GitHub token
GitHub's official Create a repository dispatch event contract requires Contents: write for GitHub App installation tokens and other fine-grained tokens: https://docs.github.com/en/rest/repos/repos#create-a-repository-dispatch-event. GitHub does not expose a narrower repository_dispatch-only token permission. Therefore a consumer-visible GitHub token cannot simultaneously create the event and satisfy this issue's “no repository contents write authority” invariant.
Selected owner shape: Noema validates the exact OIDC/source/PR/head/target/action/payload contract, performs the single allowlisted dispatch server-side with a target-repository installation token that never leaves Noema, and returns a short-lived signed receipt bound to the request and resulting dispatch attempt. The consumer receives no GitHub credential. Rejected alternatives are a broad installation token returned to the consumer, PAT/central-secret inheritance, and an undocumented extension of the legacy OpenCode endpoint.
Additional RED/GREEN acceptance:
- prove the response contains no GitHub token or reusable credential;
- prove arbitrary REST paths, target repositories, event types, and payload keys cannot be selected by the caller;
- prove the broker installation token is scoped to the explicit target repository and is used only inside the dispatch adapter;
- prove the signed receipt binds source repository, PR, head SHA, workflow SHA, target repository, event type, payload digest, outcome, expiry, and replay nonce;
- prove GitHub denial is returned as authorization failure without misclassifying it as provider capacity or user cancellation.
Activity
seonghobae commentedon Sep 30, 2026
Protected-main boundary confirmation:
POST /exchangeAPI with an explicittarget_repositorybody field (README.md,openapi.json,docs/api-spec.md).src/index.tsverifies OIDC and exchanges a repository-scoped installation token.repository_dispatchcapability, and the existing.githubcandidate does not use this released Noema contract; it calls the legacy external/exchange_github_app_tokenendpoint without a target/action body..githubcan pin.Therefore the owner repair should extend/version Noema's own
/exchangecontract (or a separately versioned dispatch capability) rather than inventing parameters on the legacy OpenCode endpoint. It must bind the explicitContextualWisdomLab/.githubtarget and allowlisted continuation event/action while avoiding a reusable broad installation token. Publish an immutable release and consumer example/schema; then.github#2540can replace its legacy endpoint call and pin the owner contract.seonghobae commentedon Sep 30, 2026
Proposed v1 owner contract — design checkpoint (no implementation claim)
Fresh protected-source review on 2026-09-30 confirms:
main@1204a075ac15b924e2f907804ba99c337d1a3ac7;POST /exchange, which returns a reusable installation token;Create a repository dispatch event, so returning a token to the consumer cannot meet this issue's least-privilege invariant.Selected endpoint and responsibility
Add a separate versioned endpoint:
POST /v1/continuation-dispatchesDo not widen or silently reinterpret
POST /exchange. The endpoint is a synchronous broker: Noema validates the caller and live PR state, performs exactly one internally mapped centralrepository_dispatch, and returns a signed evidence receipt. It never returns a GitHub credential.Closed request schema
Reject duplicate JSON keys, unknown keys, non-string typed lookalikes, whitespace-bearing identifiers, sparse/oversized payloads, and multiple JSON values before credential use.
Required fields:
contract_version: exact published v1 identifier;dispatch_action: enumnoema_review_continuationorstrix_scan_continuation;central_repository: constContextualWisdomLab/.github;source_repository: exactowner/name, required to equal the verified OIDC repository claim;pull_request_number: positive integer;expected_head_sha: lowercase 40-hex;expected_base_sha: lowercase 40-hex;expected_base_ref: canonical nonempty Git ref name;transport_retry_attempt: integer 1 or 2.The caller does not supply an arbitrary event type, REST path,
client_payload, installation id, permissions object, or target URL. Noema maps the two action enums internally:noema_review_continuation→event_type=noema-review;strix_scan_continuation→event_type=strix-scan.The emitted
client_payloadis constructed only from the validated fields above; no additional caller-selected keys are copied.Trust and live-state sequence
job_workflow_sha, time claims, and OIDCjtireplay boundary.source_repository; reject forks and mutable/ambiguous workflow identity.ContextualWisdomLab/.github. Its Contents-write installation token stays inside Noema, is scoped to that single repository, and is passed only to the fixed repository-dispatch adapter.This permission split avoids upgrading the existing consumer-installed credential-exchange App to Contents write across consumer repositories.
Exactly-once/idempotency boundary
Compute a canonical request digest from contract version, verified caller/workflow SHA, source repository, PR number, head/base/ref, action, central repository, and retry attempt. Atomically reserve that digest before dispatch:
Signed receipt (evidence, not authority)
Return no
tokenor reusable credential. The response contains:accepted,denied,indeterminate) and bounded upstream status;The receipt is audit evidence only; it does not authorize merge, approval, release, deployment, or a later dispatch.
Error taxonomy
Use dedicated error codes so transport authorization is never misreported as model/provider capacity or user cancellation:
ERR_DISPATCH_REQUEST_INVALID;ERR_DISPATCH_IDENTITY_DENIED;ERR_DISPATCH_LIVE_STATE_STALE;ERR_DISPATCH_REPLAY_CONFLICT;ERR_GITHUB_DISPATCH_AUTHORIZATION;ERR_GITHUB_DISPATCH_UPSTREAM.No secret, bearer, App JWT, installation token, or raw OIDC assertion may appear in the response, receipt, trace, or retained log.
RED → GREEN acceptance and release order
Start with executable RED contracts for: retained consumer→central 403; arbitrary target/event/path/payload-key attempts; wrong workflow SHA; source-claim mismatch; moved head/base/ref; closed/draft/fork PR; duplicate/unknown JSON members; exact replay and conflicting replay; GitHub 403/404/422/5xx/timeout; response/log secret absence; and exact receipt verification. Then implement, update OpenAPI/PRD/TRD/ADR/UML/threat model/runbook/CHANGELOG/gap baseline, obtain exact-head owner CI/review/security/SBOM/provenance evidence, and publish an immutable release.
Only after that release exists should
.github#2540replace the legacy/exchange_github_app_token+ localgh apisequence with this endpoint and pin the released contract. The current consumer Draft remains correctly blocked.seonghobae commentedon Sep 30, 2026
Canonical owner work is now staged in Draft PR #736.
1f2465aac8b2ef16400b329eb050e17910d2a9012db47f3e7cebfe1ebe8c3645564b350a722a2bd51204a075ac15b924e2f907804ba99c337d1a3ac7The PR contains only the Proposed broker design and a task-by-task RED→GREEN plan. It intentionally makes no implementation, release, deployment, or downstream-repair claim.
.github#2540remains Draft/blocked until the owner endpoint is implemented, verified at exact head, released immutably, and pinned by the consumer.seonghobae commentedon Sep 30, 2026
CodeQL owner-contract gap added to the existing acceptance — 2026-10-01
Fresh downstream evidence shows that #736 is a valid least-privilege broker for the Noema/Strix continuations, but it does not yet carry the CodeQL failure that originally established this issue's RED.
ContextualWisdomLab/.github#2040@38a1692bd4419d4be3fb800abe70dd44644ac006.36784069940/110121731055.repository_dispatchreturned HTTP 403.5c6d2f82821ad3039038d1102b225d018d472b1fadmits onlynoema_review_continuationandstrix_scan_continuation, mapped tonoema-reviewandstrix-scan.codeql-scan-v2and preserves additional evidence (matrix,required_run_id,required_jobs,producer_source_sha) that the current v1 request schema cannot represent.Keep #736's current two-action delta intact; do not widen the consumer token or copy the owner source. Extend the Noema owner stack with RED contracts for the CodeQL action, exact closed payload construction/digest/idempotency/receipt coverage, and the same no-token-egress guarantee. Then obtain exact-head CI/security/review, publish an immutable versioned release, and let
.github#2040adopt that released contract before rerunning its exact head. #2040 has been returned to Draft; no predecessor or sibling dispatch success is counted.seonghobae commentedon Sep 30, 2026
Fresh exact-head RED evidence from canonical consumer ContextualWisdomLab/.github#2040:
38a1692bd4419d4be3fb800abe70dd44644ac00637b10243cec3d160ecc9c1be75c71428b160a70336784069940110121731055POST repos/ContextualWisdomLab/.github/dispatchesreturnedResource not accessible by integration (HTTP 403)The coordinator had already validated live PR state/head/base/merge identity, bound both required shard job IDs, obtained GitHub OIDC, and received a nonempty installation token from
/exchange_github_app_token. Seven sibling exact-head workflows are terminal GREEN; only the owner capability boundary prevents CodeQL continuation. This independently reproduces the issue on the central repository itself, not only on a distinct consumer repository.No consumer workaround was introduced. ContextualWisdomLab/.github#2040 remains Ready/Proposed and fail-closed; it has no independent exact-head approval and cannot qualify for ordinary merge or circular-admission bypass. The required owner sequence remains RED fixture → broker API/schema/security CI GREEN → immutable Noema release → pinned consumer bump and fresh exact-head hosted verification.
seonghobae commentedon Sep 30, 2026
Canonical owner repair snapshot:
noema#736exact head1bd5fce7e8df5a3ecbda109071d9b2bbd7a5917b, tree5228844956962bacad76cc2c572cb324acca96c0, base1204a075ac15b924e2f907804ba99c337d1a3ac7; open, Ready-for-review, mergeable.anyOf, with both real envelopes executed by contract tests).36790157474, reviewer-ci36790157475, and Security Scan36790157585: SUCCESS.36790157437: still running; not counted as GREEN.Therefore the owner source is repaired but merge/release authority is not complete.
.github#2540remains correctly Draft and must not replace its legacy exchange/dispatch sequence or pin this contract until ordinary merge plus immutable Noema release publishes the receipt schema and key-id→SPKI artifact/digest.seonghobae commentedon Sep 30, 2026
Current exact-head evidence for the continuation admission cycle:
81568910dccff168681b9b923e2c7667861b1063(treecb2e60ceb4632ae381f245c24070ad000b1946bb), open/Ready/mergeable;2a4488c6210921b9fa1af254868c0dfb764d5be9→ GREEN350a36656343caf7730d107625e695575ef7e238repairs only the proven post-commit/pre-request token-expiry path; exact receipt + original reservation capability are required for release, while request-started ambiguity remains terminal;ContextualWisdomLab/.github#2540remains Draft at48dbe4e7a080a0235cb0a244820c87232dada1f2, mechanically mergeable, and explicitly blocked on an immutable Noema release plus consumer pin.The running image/SBOM/provenance lane is not counted GREEN. No merge, bypass, release, consumer source copy, force update, destructive rebase, or manual rerun is authorized.
seonghobae commentedon Oct 1, 2026
Fresh consumer recurrence — 2026-10-02
The owner boundary remains live on
ContextualWisdomLab/mcp-shared-gateway#28@ba0f67d2f02e3ae635f988fb95119c3108709aee.36901701673, job110502265658, classified all threeorchestrator/freecandidates as HTTP 429 and emitted bounded continuation eligibility.110502605570waited the emitted 170 seconds, revalidated the same open PR/head/base, then failedPOST repos/ContextualWisdomLab/.github/dispatcheswithResource not accessible by integration (HTTP 403).36901701684, jobs110502262379and110502636142.This is a current consumer proof of the existing RED contract, not a new completion claim.
.github#2540must continue to avoid returning a broad GitHub installation token to the consumer. The Noema broker/release path in #736 remains the canonical owner repair: ordinary prerequisite integration, qualifying review, immutable release with receipt-verification material, then a released-contract pin and unchanged-head hosted continuation proof in.github#2540.seonghobae commentedon Oct 2, 2026
New exact-head consumer recurrence and offline boundary execution; not dispatch recovery or merge approval.
ContextualWisdomLab/semantic-data-portal#107 remains at
742e7a7e1d4d0096a4fe46954d51c4b012f07dd3, basee48aa13c4af7a4875d4b53e6a60b50405c265a2f. Required Noema run36970586032model job110723568569ended in gateway HTTP429 after 2615.5 seconds; the single caller request does not count the gateway's provider routes. Continuation job110737322328then waited the emitted95 seconds, verified unchanged head/base, and failed the central repository-dispatch POST withResource not accessible by integration (HTTP403). This workflow-token denial is distinct from an observer API quota. Strix job110723570305was canceled at six hours and supplies no authoritative completed verdict.I executed the unchanged published continuation shell from protected central
37b10243cec3d160ecc9c1be75c71428b160a703and existing consumer-adapter #2540d8bc25753dcec4cb83b816835568c0e11a27f4e5. Their dispatch-shell bytes are identical. Sixteen synthetic CLI-boundary cases pass with warnings fatal and zero skips/errors: valid matching identity dispatches exactly once; central403 propagates failure without claiming scheduled success; changed head/base, fork and closed PR never dispatch; invalid retry and excessive delay perform no API call. No real token/exchange/provider/network dispatch was used.This confirms preservation and causal scope, not that exchanged credentials grant central authority. Reuse Noema#735/#736's existing broker/release responsibility and
.github#2540consumer. Fresh owner read: #736 is still unmerged atdbe22c03651f878690691221595fcaaae2ebfadf, prerequisite#737 atbf2cd03dd817e0db8ec67aa9eb91458aaca36c14; immutable release count0 and no qualifying current-head approval observed. Required next transition remains ordinary prerequisite/broker integration plus immutable receipt-verification release, then released-contract adoption and actual exact-head continuation proof. No PAT fallback, inherited-secret change, broad token, unpublished source pin, manual redispatch, duplicate writer, Ready churn or protection bypass was performed. Semantic-data-portal#82/#107 remain open and #108 remains unstarted.