mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 00:40:44 +00:00
fix(gateway): scoped no-auth local backend bypass (#75781)
When gateway.auth.mode is 'none', the local backend self-pairing skip was gated on sharedAuthOk, which stays false for no-auth mode. The missing-device handler still rejected with 1008: device identity required. Fix: shouldSkipLocalBackendSelfPairing now bypasses sharedAuthOk entirely when authMethod is 'none' and the connection is local (direct_local or shared_secret_loopback_local) without browser origin. Remote and browser-originated connections still require proper device auth. ClawSweeper P1: Make the none-auth backend bypass reachable ClawSweeper P2: Test the reachable none-auth connect state Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
committed by
Sally O'Malley
parent
fd08fd0b1f
commit
f29efde73a
@@ -1204,6 +1204,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Agents/replies: defer implicit image model discovery and keep OAuth auth-store adoption on persisted profiles during reply startup, cutting OCM MarCodex warm prep to sub-second in live checks. Thanks @shakkernerd.
|
||||
- Plugins/tools: enforce `contracts.tools` as the manifest ownership contract for plugin tool registration, rejecting undeclared runtime tool names and adding bundled plugin drift coverage. Thanks @shakkernerd.
|
||||
- Agents/Codex: stop prompting message-tool-only source turns to finish with `NO_REPLY`, so quiet turns are represented by not calling the visible message tool instead of conflicting final-text instructions. Thanks @pashpashpash.
|
||||
|
||||
- Gateway/config: report failed backup restores as failed in logs and config observe audit records instead of marking them valid. (#70515) Thanks @davidangularme.
|
||||
- Compaction: use the active session model fallback chain for implicit summarization failures without persisting fallback model selection, so Azure content-filter 400s can recover. Fixes #64960. (#74470) Thanks @jalehman and @OpenCodeEngineer.
|
||||
- Gateway/config: allow `gateway config.patch` to update documented subagent thinking defaults. Fixes #75764. (#75802) Thanks @kAIborg24.
|
||||
|
||||
@@ -78,6 +78,31 @@ describe("resolveConnectAuthState", () => {
|
||||
});
|
||||
|
||||
describe("resolveConnectAuthDecision", () => {
|
||||
it("sets sharedAuthOk false when auth mode is none (no shared secret provided)", async () => {
|
||||
const state = await resolveConnectAuthState({
|
||||
resolvedAuth: {
|
||||
mode: "none",
|
||||
} satisfies ResolvedGatewayAuth,
|
||||
connectAuth: {},
|
||||
hasDeviceIdentity: false,
|
||||
req: {
|
||||
headers: {},
|
||||
socket: { remoteAddress: "127.0.0.1" },
|
||||
} as never,
|
||||
trustedProxies: [],
|
||||
allowRealIpFallback: false,
|
||||
rateLimiter: createLimiter(),
|
||||
clientIp: "127.0.0.1",
|
||||
});
|
||||
|
||||
expect(state.authOk).toBe(true);
|
||||
expect(state.authMethod).toBe("none");
|
||||
// auth:none does NOT set sharedAuthOk globally — it's not a shared secret.
|
||||
// Only shouldSkipLocalBackendSelfPairing treats auth:none as shared-auth-scoped
|
||||
// for local backend connections specifically.
|
||||
expect(state.sharedAuthOk).toBe(false);
|
||||
});
|
||||
|
||||
it("resets the shared-secret limiter after device-token auth succeeds", async () => {
|
||||
const rateLimiter = createLimiter();
|
||||
await resolveConnectAuthDecision({
|
||||
|
||||
@@ -451,6 +451,64 @@ describe("handshake auth helpers", () => {
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("skips backend self-pairing when auth mode is none (scoped, sharedAuthOk-independent)", () => {
|
||||
const connectParams = {
|
||||
client: {
|
||||
id: GATEWAY_CLIENT_IDS.GATEWAY_CLIENT,
|
||||
mode: GATEWAY_CLIENT_MODES.BACKEND,
|
||||
},
|
||||
} as ConnectParams;
|
||||
// auth:none on local backend skips regardless of sharedAuthOk
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "direct_local",
|
||||
hasBrowserOriginHeader: false,
|
||||
sharedAuthOk: true,
|
||||
authMethod: "none",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "shared_secret_loopback_local",
|
||||
hasBrowserOriginHeader: false,
|
||||
sharedAuthOk: true,
|
||||
authMethod: "none",
|
||||
}),
|
||||
).toBe(true);
|
||||
// sharedAuthOk=false is fine for auth:none on local backend
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "direct_local",
|
||||
hasBrowserOriginHeader: false,
|
||||
sharedAuthOk: false,
|
||||
authMethod: "none",
|
||||
}),
|
||||
).toBe(true);
|
||||
// Remote connections with auth:none should NOT skip
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "remote",
|
||||
hasBrowserOriginHeader: false,
|
||||
sharedAuthOk: true,
|
||||
authMethod: "none",
|
||||
}),
|
||||
).toBe(false);
|
||||
// Browser origin with auth:none should NOT skip
|
||||
expect(
|
||||
shouldSkipLocalBackendSelfPairing({
|
||||
connectParams,
|
||||
locality: "direct_local",
|
||||
hasBrowserOriginHeader: true,
|
||||
sharedAuthOk: false,
|
||||
authMethod: "none",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("classifies non-CLI loopback + shared-secret clients as shared_secret_loopback_local", () => {
|
||||
const connectParams = {
|
||||
client: {
|
||||
|
||||
@@ -262,13 +262,19 @@ export function shouldSkipLocalBackendSelfPairing(params: {
|
||||
if (!isBackendClient) {
|
||||
return false;
|
||||
}
|
||||
const isLocal =
|
||||
params.locality === "direct_local" || params.locality === "shared_secret_loopback_local";
|
||||
if (!isLocal || params.hasBrowserOriginHeader) {
|
||||
return false;
|
||||
}
|
||||
// No-auth local backend: scoped bypass — not shared secret, but local-only
|
||||
// device-less operation is safe when auth.mode is explicitly "none".
|
||||
if (params.authMethod === "none") {
|
||||
return true;
|
||||
}
|
||||
const usesSharedSecretAuth = params.authMethod === "token" || params.authMethod === "password";
|
||||
const usesDeviceTokenAuth = params.authMethod === "device-token";
|
||||
return (
|
||||
(params.locality === "direct_local" || params.locality === "shared_secret_loopback_local") &&
|
||||
!params.hasBrowserOriginHeader &&
|
||||
((params.sharedAuthOk && usesSharedSecretAuth) || usesDeviceTokenAuth)
|
||||
);
|
||||
return (params.sharedAuthOk && usesSharedSecretAuth) || usesDeviceTokenAuth;
|
||||
}
|
||||
|
||||
function resolveSignatureToken(connectParams: ConnectParams): string | null {
|
||||
|
||||
Reference in New Issue
Block a user