From ade2845e9be5ccf2e649f27bbf0baabbe096b8cc Mon Sep 17 00:00:00 2001 From: JSONbored <49853598+JSONbored@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:45:58 -0700 Subject: [PATCH] feat(orb): add an AMS-facing secret type to the generalized broker (#7674) Adds ORB_SECRET_TYPE_AMS_GITHUB_TOKEN to src/orb/broker.ts, ratified on #4941: hosted AMS reuses ORB's installation-based broker rather than a parallel identity system. Mechanically identical to ORB_SECRET_TYPE_GITHUB_TOKEN -- a GitHub App installation token's permissions come from the App and what the installer granted, not from anything the broker's caller specifies, so there is no real behavioral difference to build. brokerOrbToken's eligibility check now accepts either value, routing both through the exact same mint/cache/ install-eligibility flow; the distinct value exists purely so an enrollment row records which product's container it was issued for. Deliberately distinct from the self-host session-based GitHub auth packages/loopover-miner/lib/github-token-resolution.ts uses (a human's own OAuth token via /v1/auth/github/token, from a loopover-mcp login) -- that flow authenticates an interactive human tool as themselves; this one authorizes a headless hosted container as the installed App, the same reason ORB's own broker exists at all. The two are not duplicative: one models a human identity, the other a machine service identity. Scope is deliberately narrow, mirroring the #8064/#8066 split: this adds only the broker's capability to mint this type. It does not wire up a way for a real caller to request it at issuance time (POST /v1/internal/orb/enrollments and oauth.ts's self-enrollment landing page both still hardcode github_token) -- that's a follow-up once a real hosted-AMS consumer exists to call it. Tests mirror the existing github_token coverage (mint, cache, install- eligibility re-check) for the new type, plus a regression test confirming a genuinely unrecognized secret type is still rejected. --- src/orb/broker.ts | 26 +++++++++++++--- test/integration/orb-broker.test.ts | 48 +++++++++++++++++++++++++++++ 2 files changed, 69 insertions(+), 5 deletions(-) diff --git a/src/orb/broker.ts b/src/orb/broker.ts index ddec6a16bf..fa77468c4d 100644 --- a/src/orb/broker.ts +++ b/src/orb/broker.ts @@ -32,6 +32,17 @@ export const ORB_SECRET_TYPE_GITHUB_TOKEN = "github_token"; // brokerOrbToken's own secret_type branch below. export const ORB_SECRET_TYPE_TENANT_DB_CREDENTIAL = "tenant_db_credential"; +// A SECOND mint-style type (#7674, ratified on #4941: hosted AMS reuses ORB's installation-based broker rather +// than a parallel identity system), mechanically IDENTICAL to ORB_SECRET_TYPE_GITHUB_TOKEN -- a GitHub App +// installation token's permissions come from the App + what the installer granted, not from anything the +// broker's caller specifies, so there is no real behavioral difference to build here. The distinct value exists +// purely so an enrollment row records WHICH product's container it was issued for (audit/bookkeeping), not +// because AMS needs a different mint mechanism. Deliberately distinct from the self-host session-based GitHub +// auth `packages/loopover-miner/lib/github-token-resolution.ts` uses (a human's own OAuth token via +// `/v1/auth/github/token`) -- that flow exists for an interactive human tool acting as themselves; this one is +// for a headless hosted container acting as the installed App, the same reason ORB's own broker exists at all. +export const ORB_SECRET_TYPE_AMS_GITHUB_TOKEN = "ams_github_token"; + export function isOrbBrokerEnabled(env: Env): boolean { return /^(1|true|yes|on)$/i.test(String(env.ORB_BROKER_ENABLED ?? "").trim()); } @@ -126,9 +137,10 @@ type OrbEnrollmentRow = { }; /** The container's token-exchange: a valid enrollment secret → either a short-lived GitHub installation token - * (the original, mint-style flow) or a decrypted stored secret value (#8064's store-style flow), branching on - * the enrollment row's own secret_type. installation_id/eligibility only apply to the GitHub-token flow — a - * stored secret has no GitHub installation to re-check at all (see issueOrbStoredSecret's header comment). */ + * (the mint-style flow, shared identically by GITHUB_TOKEN and AMS_GITHUB_TOKEN, #7674) or a decrypted stored + * secret value (#8064's store-style flow), branching on the enrollment row's own secret_type. + * installation_id/eligibility only apply to the mint-style flow — a stored secret has no GitHub installation + * to re-check at all (see issueOrbStoredSecret's header comment). */ export async function brokerOrbToken(env: Env, secret: string, options: { forceRefresh?: boolean } = {}): Promise { // Warn when TOKEN_ENCRYPTION_SECRET is absent — without it, the broker cache is bypassed and every exchange hits // GitHub's token endpoint, dramatically increasing exposure to throttle-induced failures. @@ -146,8 +158,12 @@ export async function brokerOrbToken(env: Env, secret: string, options: { forceR if (!row || row.state !== "enrolled" || row.revoked_at !== null) return { error: "invalid_enrollment" }; if (row.secret_type === ORB_SECRET_TYPE_TENANT_DB_CREDENTIAL) return resolveStoredSecret(env, row); // Checked once the caller is already proven to hold a valid enrollment (same ordering rationale as the App- - // credential check below, #2710) — anything else here belongs to a mint strategy that doesn't exist yet. - if (row.secret_type !== ORB_SECRET_TYPE_GITHUB_TOKEN) return { error: "unsupported_secret_type" }; + // credential check below, #2710) — GITHUB_TOKEN and AMS_GITHUB_TOKEN both mint the SAME kind of GitHub App + // installation token through the identical flow below (#7674): the distinct value is bookkeeping only, not a + // different mint strategy. Anything else here belongs to a strategy that doesn't exist yet. + if (row.secret_type !== ORB_SECRET_TYPE_GITHUB_TOKEN && row.secret_type !== ORB_SECRET_TYPE_AMS_GITHUB_TOKEN) { + return { error: "unsupported_secret_type" }; + } const install = await env.DB .prepare("SELECT registered, suspended_at, removed_at FROM orb_github_installations WHERE installation_id = ?") .bind(row.installation_id) diff --git a/test/integration/orb-broker.test.ts b/test/integration/orb-broker.test.ts index 9878cc9160..dbc3a35cad 100644 --- a/test/integration/orb-broker.test.ts +++ b/test/integration/orb-broker.test.ts @@ -6,6 +6,7 @@ import { isOrbBrokerEnabled, issueOrbEnrollment, issueOrbStoredSecret, + ORB_SECRET_TYPE_AMS_GITHUB_TOKEN, ORB_SECRET_TYPE_GITHUB_TOKEN, ORB_SECRET_TYPE_TENANT_DB_CREDENTIAL, revokeOrbEnrollment, @@ -79,6 +80,14 @@ describe("issueOrbEnrollment", () => { const row = await db(e).prepare("SELECT secret_type FROM orb_enrollments WHERE installation_id=202").first<{ secret_type: string }>(); expect(row?.secret_type).toBe("ai_provider_key"); }); + + it("#7674: records ORB_SECRET_TYPE_AMS_GITHUB_TOKEN when issued for a hosted AMS container", async () => { + const e = await brokerEnv(); + await seedInstall(e, 203, { registered: 1 }); + await issueOrbEnrollment(e, 203, undefined, ORB_SECRET_TYPE_AMS_GITHUB_TOKEN); + const row = await db(e).prepare("SELECT secret_type, installation_id FROM orb_enrollments WHERE installation_id=203").first<{ secret_type: string; installation_id: number }>(); + expect(row).toMatchObject({ secret_type: ORB_SECRET_TYPE_AMS_GITHUB_TOKEN, installation_id: 203 }); + }); }); describe("issueOrbStoredSecret", () => { @@ -162,6 +171,45 @@ describe("brokerOrbToken", () => { expect(await brokerOrbToken(e, secret)).toEqual({ error: "unsupported_secret_type" }); }); + it("#7674: mints the SAME kind of GitHub installation token for an ams_github_token enrollment as for github_token", async () => { + const e = await brokerEnv(); + await seedInstall(e, 320, { registered: 1 }); + const { secret } = (await issueOrbEnrollment(e, 320, undefined, ORB_SECRET_TYPE_AMS_GITHUB_TOKEN)) as { secret: string }; + tokenFetch("ghs_ams_minted", "2026-06-25T08:00:00Z", { contents: "write" }); + + expect(await brokerOrbToken(e, secret)).toEqual({ token: "ghs_ams_minted", installationId: 320, expiresAt: "2026-06-25T08:00:00Z", permissions: { contents: "write" } }); + }); + + it("#7674: an ams_github_token enrollment shares the SAME cache as github_token — a second exchange doesn't re-mint", async () => { + vi.useFakeTimers(); + vi.setSystemTime(new Date("2026-06-25T07:00:00Z")); + const e = await brokerEnv({ TOKEN_ENCRYPTION_SECRET: "orb-ams-cache-test" }); + await seedInstall(e, 321, { registered: 1 }); + const { secret } = (await issueOrbEnrollment(e, 321, undefined, ORB_SECRET_TYPE_AMS_GITHUB_TOKEN)) as { secret: string }; + const fetchCalls = countingTokenFetch("2026-06-25T08:00:00Z"); + + expect(await brokerOrbToken(e, secret)).toMatchObject({ token: "ghs_minted_1" }); + expect(await brokerOrbToken(e, secret)).toMatchObject({ token: "ghs_minted_1" }); + expect(fetchCalls()).toBe(1); + }); + + it("#7674: an ams_github_token enrollment is re-checked for install eligibility just like github_token", async () => { + const e = await brokerEnv(); + await seedInstall(e, 322, { registered: 1 }); + const { secret } = (await issueOrbEnrollment(e, 322, undefined, ORB_SECRET_TYPE_AMS_GITHUB_TOKEN)) as { secret: string }; + await db(e).prepare("UPDATE orb_github_installations SET suspended_at=CURRENT_TIMESTAMP WHERE installation_id=322").run(); + + expect(await brokerOrbToken(e, secret)).toEqual({ error: "installation_not_eligible" }); + }); + + it("#7674: a genuinely unrecognized secret type is still rejected (the widened check isn't a blanket allow)", async () => { + const e = await brokerEnvMissingAppCreds("both"); + await seedInstall(e, 323, { registered: 1 }); + const { secret } = (await issueOrbEnrollment(e, 323, undefined, "some_future_type_not_yet_built")) as { secret: string }; + + expect(await brokerOrbToken(e, secret)).toEqual({ error: "unsupported_secret_type" }); + }); + it("caches a freshly minted token and serves repeated exchanges without reminting", async () => { vi.useFakeTimers(); vi.setSystemTime(new Date("2026-06-25T07:00:00Z"));