Files
openclaw/src/agents/execution-auth-binding.test.ts
Peter Steinberger 96f0983a85 fix(onboarding): skip setup for configured gateways and require inference first (#102883)
* fix(crestodian): keep onboarding RPCs restart-safe

* fix(profiles): isolate approval state migrations

* fix(crestodian): bypass configured gateway setup

* test(crestodian): type onboarding mocks

* fix(onboarding): require inference before Crestodian

* fix(onboarding): enforce verified inference handoff

* fix(macos): reset setup on gateway endpoint edits

* chore(i18n): refresh native source inventory

* fix(gateway): keep socket on request cancellation

* test(packaging): require workspace templates

* fix(onboarding): bind setup to verified inference

* fix(onboarding): align inference gate contracts

* fix(crestodian): classify concurrent policy rejection

* test(crestodian): expect registry restoration

* fix(onboarding): bind setup to configured gateways

* fix(codex): preserve startup phase deadlines

* test(crestodian): match fail-closed policy ordering

* test(onboarding): assert bound gateway handoff

* fix(codex): bind runtime resolution to spawn cwd

* test(crestodian): assert policy rejection order

* fix(cli): preserve gateway routing across restarts

* fix(macos): fail closed during gateway edits

* test(macos): cover gateway route generation races

* chore: keep release notes out of onboarding PR

* fix(ci): refresh onboarding generated checks

* style(swift): align gateway channel formatting

* fix(ci): refresh plugin SDK surface budgets

* fix(ci): resync native string inventory

* refactor(swift): split gateway channel support

* test(doctor): isolate plugin compatibility registry

* test(macos): isolate gateway onboarding fixtures

* test(macos): assert gateway lease health ordering

* fix(codex): reconcile computer-use startup changes
2026-07-11 10:25:14 -07:00

258 lines
8.1 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
fingerprintAuthProfileCredential,
fingerprintAuthProfileOwnerShape,
fingerprintAwsSdkRuntimeOwner,
fingerprintOpaqueRuntimeOwner,
fingerprintResolvedAuthProfileCredential,
fingerprintResolvedProviderAuth,
} from "./execution-auth-binding.js";
function jwt(claims: Record<string, unknown>): string {
return `header.${Buffer.from(JSON.stringify(claims)).toString("base64url")}.signature`;
}
describe("execution auth binding fingerprints", () => {
it("rejects an opaque CLI owner without an executable identity", () => {
expect(
fingerprintOpaqueRuntimeOwner({
kind: "cli-runtime",
runner: "cli",
provider: "claude-cli",
backendId: "claude-cli",
}),
).toBeUndefined();
});
it("requires and binds an opaque plugin harness implementation", () => {
const fingerprint = (runtimeArtifactFingerprint?: string) =>
fingerprintOpaqueRuntimeOwner({
kind: "plugin-harness",
runner: "embedded",
provider: "openai",
backendId: "codex",
...(runtimeArtifactFingerprint ? { runtimeArtifactFingerprint } : {}),
});
expect(fingerprint()).toBeUndefined();
expect(fingerprint("codex-runtime-v1")).not.toBe(fingerprint("codex-runtime-v2"));
});
it.each(["env", "file", "exec"] as const)(
"rejects an unresolved %s static-secret reference",
(source) => {
expect(
fingerprintAuthProfileCredential({
profileId: "openai:bound",
credential: {
type: "api_key",
provider: "openai",
keyRef: { source, provider: "default", id: source === "env" ? "OPENAI_KEY" : "key" },
},
}),
).toBeUndefined();
},
);
it("changes when a materialized static secret rotates", () => {
const fingerprint = (key: string) =>
fingerprintAuthProfileCredential({
profileId: "openai:bound",
credential: {
type: "api_key",
provider: "openai",
key,
keyRef: { source: "file", provider: "vault", id: "/openai/key" },
},
});
expect(fingerprint("first-key")).not.toBe(fingerprint("replacement-key"));
});
it("binds a SecretRef profile to the resolved selected value", () => {
const credential = {
type: "api_key" as const,
provider: "openai",
keyRef: { source: "env" as const, provider: "default", id: "OPENAI_KEY" },
};
const fingerprint = (apiKey: string, profileId = "openai:bound") =>
fingerprintResolvedAuthProfileCredential({
profileId: "openai:bound",
credential,
resolvedAuth: {
apiKey,
profileId,
source: "profile:openai:bound",
mode: "api-key",
},
});
expect(fingerprint("first-key")).not.toBe(fingerprint("replacement-key"));
expect(fingerprint("first-key", "openai:other")).toBeUndefined();
expect(
fingerprintResolvedAuthProfileCredential({
profileId: "openai:bound",
credential,
resolvedAuth: null,
}),
).toBeUndefined();
});
it("keeps identity-bearing OAuth stable across token refreshes", () => {
const fingerprint = (access: string, refresh: string) =>
fingerprintAuthProfileCredential({
profileId: "openai:oauth",
credential: {
type: "oauth",
provider: "openai",
access,
refresh,
expires: 1,
accountId: "account-1",
email: "User@Example.test",
},
});
expect(fingerprint("access-a", "refresh-a")).toBe(fingerprint("access-b", "refresh-b"));
});
it("invalidates identity-less OAuth when its opaque grant changes", () => {
const fingerprint = (access: string, refresh: string) =>
fingerprintAuthProfileCredential({
profileId: "anthropic:oauth",
credential: {
type: "oauth",
provider: "anthropic",
access,
refresh,
expires: 1,
},
});
expect(fingerprint("access-a", "refresh-a")).not.toBe(fingerprint("access-b", "refresh-b"));
});
it("uses stable id-token identity instead of rotating token material", () => {
const fingerprint = (access: string, subject: string) =>
fingerprintAuthProfileCredential({
profileId: "google:oauth",
credential: {
type: "oauth",
provider: "google",
access,
refresh: `refresh-${access}`,
expires: 1,
idToken: jwt({ sub: subject, email: "user@example.test" }),
},
});
expect(fingerprint("access-a", "subject-1")).toBe(fingerprint("access-b", "subject-1"));
expect(fingerprint("access-a", "subject-1")).not.toBe(fingerprint("access-a", "subject-2"));
});
it("keeps resolved credential fingerprints opaque and stable within one process", () => {
const fingerprint = (apiKey: string) =>
fingerprintResolvedProviderAuth({
apiKey,
profileId: "openai:bound",
source: "profile:openai:bound",
mode: "api-key",
});
const secret = "raw-secret-marker-with-non-hex";
const first = fingerprint(secret);
expect(first).toBe(fingerprint(secret));
expect(first).not.toBe(fingerprint("replacement-secret"));
expect(first).toMatch(/^[a-f0-9]{64}$/u);
expect(first).not.toContain(secret);
});
it("rejects auth modes without a concrete credential identity", () => {
expect(
fingerprintResolvedProviderAuth({ source: "aws-sdk:default-chain", mode: "aws-sdk" }),
).toBeUndefined();
});
it("keeps an opaque OAuth profile owner stable across runtime token refreshes", () => {
const fingerprint = (access: string) =>
fingerprintAuthProfileOwnerShape({
profileId: "anthropic:cli",
credential: {
type: "oauth",
provider: "anthropic",
access,
refresh: `refresh-${access}`,
expires: 1,
accountId: "account-1",
},
});
expect(fingerprint("access-a")).toBe(fingerprint("access-b"));
expect(
fingerprintAuthProfileOwnerShape({
profileId: "anthropic:missing",
credential: undefined,
}),
).toBeUndefined();
});
it("binds AWS SDK owners only to concrete bearer and static credentials", () => {
const auth = { source: "aws-sdk default chain", mode: "aws-sdk" as const };
const fingerprint = (env: NodeJS.ProcessEnv) =>
fingerprintAwsSdkRuntimeOwner({
provider: "amazon-bedrock",
backendId: "openclaw",
auth,
env,
});
expect(fingerprint({ AWS_BEARER_TOKEN_BEDROCK: "bearer-a" })).toBe(
fingerprint({ AWS_BEARER_TOKEN_BEDROCK: "bearer-a" }),
);
expect(fingerprint({ AWS_BEARER_TOKEN_BEDROCK: "bearer-a" })).not.toBe(
fingerprint({ AWS_BEARER_TOKEN_BEDROCK: "bearer-b" }),
);
expect(fingerprint({ AWS_ACCESS_KEY_ID: "AKIA1", AWS_SECRET_ACCESS_KEY: "secret-a" })).not.toBe(
fingerprint({ AWS_ACCESS_KEY_ID: "AKIA1", AWS_SECRET_ACCESS_KEY: "secret-b" }),
);
expect(fingerprint({ AWS_ACCESS_KEY_ID: "AKIA1", AWS_SECRET_ACCESS_KEY: "secret-a" })).not.toBe(
fingerprint({ AWS_ACCESS_KEY_ID: "AKIA2", AWS_SECRET_ACCESS_KEY: "secret-a" }),
);
expect(
fingerprint({
AWS_ACCESS_KEY_ID: "AKIA1",
AWS_SECRET_ACCESS_KEY: "secret-a",
AWS_SESSION_TOKEN: "session-a",
}),
).not.toBe(
fingerprint({
AWS_ACCESS_KEY_ID: "AKIA1",
AWS_SECRET_ACCESS_KEY: "secret-a",
AWS_SESSION_TOKEN: "session-b",
}),
);
});
it("fails closed when the AWS SDK principal cannot be established", () => {
const auth = { source: "aws-sdk default chain", mode: "aws-sdk" as const };
const fingerprint = (env: NodeJS.ProcessEnv) =>
fingerprintAwsSdkRuntimeOwner({
provider: "amazon-bedrock",
backendId: "openclaw",
auth,
env,
});
expect(fingerprint({ AWS_PROFILE: "work" })).toBeUndefined();
expect(fingerprint({})).toBeUndefined();
expect(
fingerprint({
AWS_PROFILE: "work",
AWS_ACCESS_KEY_ID: "AKIA1",
AWS_SECRET_ACCESS_KEY: "secret-a",
}),
).toBeUndefined();
});
});