Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 21 additions & 5 deletions src/orb/broker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand Down Expand Up @@ -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<BrokerResult> {
// 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.
Expand All @@ -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)
Expand Down
48 changes: 48 additions & 0 deletions test/integration/orb-broker.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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", () => {
Expand Down Expand Up @@ -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"));
Expand Down