mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-20 07:54:36 +00:00
fix(gateway): allow trusted-proxy control-ui auth to skip device pairing
Control UI connections authenticated via gateway.auth.mode=trusted-proxy were still forced through device pairing because pairing bypass only considered shared token/password auth (sharedAuthOk). In trusted-proxy deployments, this produced persistent "pairing required" failures despite valid trusted proxy headers. Treat authenticated trusted-proxy control-ui connections as pairing-bypass eligible and allow missing device identity in that mode. Fixes #25293 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
committed by
Peter Steinberger
parent
d84659f22f
commit
20523b918a
@@ -49,6 +49,7 @@ describe("ws connect policy", () => {
|
||||
role: "node",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
@@ -68,6 +69,7 @@ describe("ws connect policy", () => {
|
||||
role: "operator",
|
||||
isControlUi: true,
|
||||
controlUiAuthPolicy: controlUiStrict,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
@@ -82,6 +84,7 @@ describe("ws connect policy", () => {
|
||||
role: "operator",
|
||||
isControlUi: true,
|
||||
controlUiAuthPolicy: controlUiStrict,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
@@ -101,6 +104,7 @@ describe("ws connect policy", () => {
|
||||
role: "operator",
|
||||
isControlUi: true,
|
||||
controlUiAuthPolicy: controlUiNoInsecure,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
@@ -114,6 +118,7 @@ describe("ws connect policy", () => {
|
||||
role: "operator",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
@@ -127,6 +132,7 @@ describe("ws connect policy", () => {
|
||||
role: "operator",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: false,
|
||||
authOk: false,
|
||||
hasSharedAuth: true,
|
||||
@@ -140,15 +146,31 @@ describe("ws connect policy", () => {
|
||||
role: "node",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
sharedAuthOk: true,
|
||||
authOk: true,
|
||||
hasSharedAuth: true,
|
||||
isLocalClient: false,
|
||||
}).kind,
|
||||
).toBe("reject-device-required");
|
||||
|
||||
// Trusted-proxy authenticated Control UI should bypass device-identity gating.
|
||||
expect(
|
||||
evaluateMissingDeviceIdentity({
|
||||
hasDeviceIdentity: false,
|
||||
role: "operator",
|
||||
isControlUi: true,
|
||||
controlUiAuthPolicy: controlUiNoInsecure,
|
||||
trustedProxyAuthOk: true,
|
||||
sharedAuthOk: false,
|
||||
authOk: true,
|
||||
hasSharedAuth: false,
|
||||
isLocalClient: false,
|
||||
}).kind,
|
||||
).toBe("allow");
|
||||
});
|
||||
|
||||
test("pairing bypass requires control-ui bypass + shared auth", () => {
|
||||
test("pairing bypass requires control-ui bypass + shared auth (or trusted-proxy auth)", () => {
|
||||
const bypass = resolveControlUiAuthPolicy({
|
||||
isControlUi: true,
|
||||
controlUiConfig: { dangerouslyDisableDeviceAuth: true },
|
||||
@@ -159,8 +181,9 @@ describe("ws connect policy", () => {
|
||||
controlUiConfig: undefined,
|
||||
deviceRaw: null,
|
||||
});
|
||||
expect(shouldSkipControlUiPairing(bypass, true)).toBe(true);
|
||||
expect(shouldSkipControlUiPairing(bypass, false)).toBe(false);
|
||||
expect(shouldSkipControlUiPairing(strict, true)).toBe(false);
|
||||
expect(shouldSkipControlUiPairing(bypass, true, false)).toBe(true);
|
||||
expect(shouldSkipControlUiPairing(bypass, false, false)).toBe(false);
|
||||
expect(shouldSkipControlUiPairing(strict, true, false)).toBe(false);
|
||||
expect(shouldSkipControlUiPairing(strict, false, true)).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -35,7 +35,11 @@ export function resolveControlUiAuthPolicy(params: {
|
||||
export function shouldSkipControlUiPairing(
|
||||
policy: ControlUiAuthPolicy,
|
||||
sharedAuthOk: boolean,
|
||||
trustedProxyAuthOk = false,
|
||||
): boolean {
|
||||
if (trustedProxyAuthOk) {
|
||||
return true;
|
||||
}
|
||||
return policy.allowBypass && sharedAuthOk;
|
||||
}
|
||||
|
||||
@@ -50,6 +54,7 @@ export function evaluateMissingDeviceIdentity(params: {
|
||||
role: GatewayRole;
|
||||
isControlUi: boolean;
|
||||
controlUiAuthPolicy: ControlUiAuthPolicy;
|
||||
trustedProxyAuthOk?: boolean;
|
||||
sharedAuthOk: boolean;
|
||||
authOk: boolean;
|
||||
hasSharedAuth: boolean;
|
||||
@@ -58,6 +63,9 @@ export function evaluateMissingDeviceIdentity(params: {
|
||||
if (params.hasDeviceIdentity) {
|
||||
return { kind: "allow" };
|
||||
}
|
||||
if (params.isControlUi && params.trustedProxyAuthOk) {
|
||||
return { kind: "allow" };
|
||||
}
|
||||
if (params.isControlUi && !params.controlUiAuthPolicy.allowBypass) {
|
||||
// Allow localhost Control UI connections when allowInsecureAuth is configured.
|
||||
// Localhost has no network interception risk, and browser SubtleCrypto
|
||||
|
||||
@@ -427,11 +427,17 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
if (!device) {
|
||||
clearUnboundScopes();
|
||||
}
|
||||
const trustedProxyAuthOk =
|
||||
isControlUi &&
|
||||
resolvedAuth.mode === "trusted-proxy" &&
|
||||
authOk &&
|
||||
authMethod === "trusted-proxy";
|
||||
const decision = evaluateMissingDeviceIdentity({
|
||||
hasDeviceIdentity: Boolean(device),
|
||||
role,
|
||||
isControlUi,
|
||||
controlUiAuthPolicy,
|
||||
trustedProxyAuthOk,
|
||||
sharedAuthOk,
|
||||
authOk,
|
||||
hasSharedAuth,
|
||||
@@ -563,8 +569,13 @@ export function attachGatewayWsMessageHandler(params: {
|
||||
// In that case, don't force device pairing on first connect.
|
||||
const skipPairingForOperatorSharedAuth =
|
||||
role === "operator" && sharedAuthOk && !isControlUi && !isWebchat;
|
||||
const trustedProxyAuthOk =
|
||||
isControlUi &&
|
||||
resolvedAuth.mode === "trusted-proxy" &&
|
||||
authOk &&
|
||||
authMethod === "trusted-proxy";
|
||||
const skipPairing =
|
||||
shouldSkipControlUiPairing(controlUiAuthPolicy, sharedAuthOk) ||
|
||||
shouldSkipControlUiPairing(controlUiAuthPolicy, sharedAuthOk, trustedProxyAuthOk) ||
|
||||
skipPairingForOperatorSharedAuth;
|
||||
if (device && devicePublicKey && !skipPairing) {
|
||||
const formatAuditList = (items: string[] | undefined): string => {
|
||||
|
||||
Reference in New Issue
Block a user