test(gateway): cover trusted backend attestation policy

This commit is contained in:
Rai Butera 2026-03-12 16:43:23 +00:00
parent 91de302a70
commit aa3eb2d444
3 changed files with 112 additions and 1 deletions

View File

@ -1,8 +1,10 @@
import { describe, expect, test } from "vitest";
import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../../protocol/client-info.js";
import {
evaluateMissingDeviceIdentity,
isTrustedProxyControlUiOperatorAuth,
resolveControlUiAuthPolicy,
shouldSkipBackendSelfPairing,
shouldSkipControlUiPairing,
} from "./connect-policy.js";
@ -300,4 +302,89 @@ describe("ws connect policy", () => {
).toBe(tc.expected);
}
});
test("backend self-pairing skip requires trusted local backend handshake conditions", () => {
const makeConnectParams = (clientId: string, mode: string) => ({
client: {
id: clientId,
mode,
version: "1.0.0",
},
});
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams(
GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
GATEWAY_CLIENT_MODES.BACKEND,
),
isLocalClient: true,
hasBrowserOriginHeader: false,
sharedAuthOk: true,
authMethod: "token",
}),
).toBe(true);
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams(
GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
GATEWAY_CLIENT_MODES.BACKEND,
),
isLocalClient: false,
hasBrowserOriginHeader: false,
sharedAuthOk: true,
authMethod: "token",
}),
).toBe(false);
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams(
GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
GATEWAY_CLIENT_MODES.BACKEND,
),
isLocalClient: true,
hasBrowserOriginHeader: true,
sharedAuthOk: true,
authMethod: "token",
}),
).toBe(false);
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams(
GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
GATEWAY_CLIENT_MODES.BACKEND,
),
isLocalClient: true,
hasBrowserOriginHeader: false,
sharedAuthOk: false,
authMethod: "token",
}),
).toBe(false);
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams(
GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
GATEWAY_CLIENT_MODES.BACKEND,
),
isLocalClient: true,
hasBrowserOriginHeader: false,
sharedAuthOk: true,
authMethod: "trusted-proxy",
}),
).toBe(false);
expect(
shouldSkipBackendSelfPairing({
connectParams: makeConnectParams("not-gateway-client", GATEWAY_CLIENT_MODES.BACKEND),
isLocalClient: true,
hasBrowserOriginHeader: false,
sharedAuthOk: true,
authMethod: "token",
}),
).toBe(false);
});
});

View File

@ -1,3 +1,5 @@
import { GATEWAY_CLIENT_IDS, GATEWAY_CLIENT_MODES } from "../../protocol/client-info.js";
import type { GatewayAuthResult } from "../../protocol/index.js";
import type { ConnectParams } from "../../protocol/index.js";
import type { GatewayRole } from "../../role-policy.js";
import { roleCanSkipDeviceIdentity } from "../../role-policy.js";
@ -75,6 +77,28 @@ export function isTrustedProxyControlUiOperatorAuth(params: {
);
}
export function shouldSkipBackendSelfPairing(params: {
connectParams: ConnectParams;
isLocalClient: boolean;
hasBrowserOriginHeader: boolean;
sharedAuthOk: boolean;
authMethod: GatewayAuthResult["method"];
}): boolean {
const isGatewayBackendClient =
params.connectParams.client.id === GATEWAY_CLIENT_IDS.GATEWAY_CLIENT &&
params.connectParams.client.mode === GATEWAY_CLIENT_MODES.BACKEND;
if (!isGatewayBackendClient) {
return false;
}
const usesSharedSecretAuth = params.authMethod === "token" || params.authMethod === "password";
return (
params.isLocalClient &&
!params.hasBrowserOriginHeader &&
params.sharedAuthOk &&
usesSharedSecretAuth
);
}
export type MissingDeviceIdentityDecision =
| { kind: "allow" }
| { kind: "reject-control-ui-insecure-auth" }

View File

@ -83,6 +83,7 @@ import {
evaluateMissingDeviceIdentity,
isTrustedProxyControlUiOperatorAuth,
resolveControlUiAuthPolicy,
shouldSkipBackendSelfPairing,
shouldSkipControlUiPairing,
} from "./connect-policy.js";
import {
@ -90,7 +91,6 @@ import {
resolveHandshakeBrowserSecurityContext,
resolveUnauthorizedHandshakeContext,
shouldAllowSilentLocalPairing,
shouldSkipBackendSelfPairing,
} from "./handshake-auth-helpers.js";
import { isUnauthorizedRoleError, UnauthorizedFloodGuard } from "./unauthorized-flood-guard.js";