Compare commits

...

1 Commits

Author SHA1 Message Date
Patrick Erichsen
c4c364cd27 fix: mark approval gateway calls as runtime clients 2026-05-17 21:24:42 -07:00
2 changed files with 45 additions and 0 deletions

View File

@@ -14,6 +14,7 @@ import {
pickPrimaryTailnetIPv4Mock as pickPrimaryTailnetIPv4,
resolveGatewayPortMock as resolveGatewayPort,
} from "./gateway-connection.test-mocks.js";
import { APPROVALS_SCOPE } from "./operator-scopes.js";
const deviceIdentityState = vi.hoisted(() => ({
value: {
@@ -52,6 +53,7 @@ let lastClientOptions: {
clientDisplayName?: string;
mode?: string;
scopes?: string[];
approvalRuntimeToken?: string;
deviceIdentity?: unknown;
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
onClose?: (code: number, reason: string) => void;
@@ -88,6 +90,7 @@ vi.mock("./client.js", () => ({
clientDisplayName?: string;
mode?: string;
scopes?: string[];
approvalRuntimeToken?: string;
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
onClose?: (code: number, reason: string) => void;
}) {
@@ -153,6 +156,7 @@ class StubGatewayClient {
clientDisplayName?: string;
mode?: string;
scopes?: string[];
approvalRuntimeToken?: string;
onHelloOk?: (hello: { features?: { methods?: string[] } }) => void | Promise<void>;
onClose?: (code: number, reason: string) => void;
}) {
@@ -389,6 +393,21 @@ describe("callGateway url resolution", () => {
expect(lastClientOptions?.deviceIdentity).toBeNull();
});
it("marks local backend approval-scope gateway calls as approval runtime clients", async () => {
setLocalLoopbackGatewayConfig();
await callGateway({
method: "exec.approval.waitDecision",
params: { id: "approval-1" },
token: "explicit-token",
});
expect(lastClientOptions?.clientName).toBe(GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT);
expect(lastClientOptions?.mode).toBe(GATEWAY_CLIENT_MODES.BACKEND);
expect(lastClientOptions?.scopes).toContain(APPROVALS_SCOPE);
expect(lastClientOptions?.approvalRuntimeToken).toEqual(expect.any(String));
});
it("keeps device identity enabled for explicit CLI loopback shared-token auth", async () => {
setLocalLoopbackGatewayConfig();

View File

@@ -37,11 +37,13 @@ import {
import { canSkipGatewayConfigLoad } from "./explicit-connection-policy.js";
import { resolvePreauthHandshakeTimeoutMs } from "./handshake-timeouts.js";
import {
APPROVALS_SCOPE,
CLI_DEFAULT_OPERATOR_SCOPES,
isGatewayMethodClassified,
resolveLeastPrivilegeOperatorScopesForMethod,
type OperatorScope,
} from "./method-scopes.js";
import { getOperatorApprovalRuntimeToken } from "./operator-approval-runtime-token.js";
import { MIN_CLIENT_PROTOCOL_VERSION, PROTOCOL_VERSION } from "./protocol/index.js";
export type { GatewayConnectionDetails };
@@ -325,6 +327,21 @@ function shouldOmitDeviceIdentityForGatewayCall(params: {
);
}
function shouldAttachApprovalRuntimeToken(params: {
opts: CallGatewayBaseOptions;
scopes: OperatorScope[];
allowApprovalRuntimeToken: boolean;
}): boolean {
const mode = params.opts.mode ?? GATEWAY_CLIENT_MODES.CLI;
const clientName = params.opts.clientName ?? GATEWAY_CLIENT_NAMES.CLI;
return (
params.allowApprovalRuntimeToken &&
mode === GATEWAY_CLIENT_MODES.BACKEND &&
clientName === GATEWAY_CLIENT_NAMES.GATEWAY_CLIENT &&
params.scopes.includes(APPROVALS_SCOPE)
);
}
function resolveDeviceIdentityForGatewayCall(params: {
opts: CallGatewayBaseOptions;
url: string;
@@ -654,6 +671,7 @@ async function executeGatewayRequestWithScopes<T>(params: {
timeoutMs: number;
safeTimerTimeoutMs: number;
connectionDetails: GatewayConnectionDetails;
allowApprovalRuntimeToken: boolean;
}): Promise<T> {
const {
opts,
@@ -700,6 +718,13 @@ async function executeGatewayRequestWithScopes<T>(params: {
mode: opts.mode ?? GATEWAY_CLIENT_MODES.CLI,
role: "operator",
scopes,
...(shouldAttachApprovalRuntimeToken({
opts,
scopes,
allowApprovalRuntimeToken: params.allowApprovalRuntimeToken,
})
? { approvalRuntimeToken: getOperatorApprovalRuntimeToken() }
: {}),
deviceIdentity:
opts.deviceIdentity === undefined
? resolveDeviceIdentityForGatewayCall({ opts, url, token, password })
@@ -814,6 +839,7 @@ async function callGatewayWithScopes<T = Record<string, unknown>>(
timeoutMs,
safeTimerTimeoutMs,
connectionDetails,
allowApprovalRuntimeToken: !context.urlOverride,
});
}