mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 20:20:43 +00:00
fix(gateway): allow no-auth backend self-pairing
Signed-off-by: sallyom <somalley@redhat.com>
This commit is contained in:
@@ -210,6 +210,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Compute plugin callback authorization dynamically [AI]. (#78866) Thanks @pgondhi987.
|
||||
- fix(active-memory): require admin scope for global toggles [AI]. (#78863) Thanks @pgondhi987.
|
||||
- Honor owner enforcement for native commands [AI]. (#78864) Thanks @pgondhi987.
|
||||
- Gateway/auth: allow `gateway.auth.mode: "none"` loopback backend RPC clients to skip device identity only for local non-browser backend connections, restoring subagent spawns and gateway tools without opening remote or browser-origin bypasses. Fixes #75780. Thanks @yozakura-ava.
|
||||
- Tavily: resolve dedicated `tavily_search` and `tavily_extract` tool credentials from the active runtime config snapshot, so `exec` SecretRef-backed API keys do not reach the tools unresolved. (#78610) Thanks @VACInc.
|
||||
- Gateway/sessions: clear cached skills snapshots during `/new` and `sessions.reset` so long-lived channel sessions rebuild the visible skill list after skills change. (#78873) Thanks @Evizero.
|
||||
- fix(auto-reply): gate inline skill tool dispatch [AI]. (#78517) Thanks @pgondhi987.
|
||||
@@ -1204,7 +1205,6 @@ 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.
|
||||
|
||||
@@ -333,6 +333,52 @@ describe("gateway auth compatibility baseline", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("allows auth-none local backend connects without device identity", async () => {
|
||||
const ws = await openWs(port);
|
||||
try {
|
||||
const res = await connectReq(ws, {
|
||||
skipDefaultAuth: true,
|
||||
client: { ...BACKEND_GATEWAY_CLIENT },
|
||||
scopes: ["operator.admin"],
|
||||
device: null,
|
||||
});
|
||||
expect(res.ok, JSON.stringify(res)).toBe(true);
|
||||
|
||||
const helloOk = res.payload as
|
||||
| {
|
||||
auth?: {
|
||||
scopes?: unknown;
|
||||
};
|
||||
}
|
||||
| undefined;
|
||||
expect(helloOk?.auth?.scopes).toEqual(["operator.admin"]);
|
||||
|
||||
const adminRes = await rpcReq(ws, "set-heartbeats", { enabled: false });
|
||||
expect(adminRes.ok).toBe(true);
|
||||
} finally {
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("rejects auth-none browser-origin backend connects without device identity", async () => {
|
||||
const ws = await openWs(port, { origin: originForPort(port) });
|
||||
try {
|
||||
const res = await connectReq(ws, {
|
||||
skipDefaultAuth: true,
|
||||
client: { ...BACKEND_GATEWAY_CLIENT },
|
||||
scopes: ["operator.admin"],
|
||||
device: null,
|
||||
});
|
||||
expect(res.ok).toBe(false);
|
||||
expect(res.error?.message ?? "").toContain("device identity required");
|
||||
expect((res.error?.details as { code?: string } | undefined)?.code).toBe(
|
||||
ConnectErrorDetailCodes.DEVICE_IDENTITY_REQUIRED,
|
||||
);
|
||||
} finally {
|
||||
ws.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("keeps auth-none control ui first-connect token absence unchanged", async () => {
|
||||
const ws = await openWs(port, { origin: originForPort(port) });
|
||||
try {
|
||||
|
||||
@@ -82,6 +82,7 @@ describe("resolveConnectAuthDecision", () => {
|
||||
const state = await resolveConnectAuthState({
|
||||
resolvedAuth: {
|
||||
mode: "none",
|
||||
allowTailscale: false,
|
||||
} satisfies ResolvedGatewayAuth,
|
||||
connectAuth: {},
|
||||
hasDeviceIdentity: false,
|
||||
|
||||
@@ -128,6 +128,36 @@ describe("ws connect policy", () => {
|
||||
}).kind,
|
||||
).toBe("allow");
|
||||
|
||||
expect(
|
||||
evaluateMissingDeviceIdentity({
|
||||
hasDeviceIdentity: false,
|
||||
role: "operator",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
localBackendSelfPairingOk: true,
|
||||
sharedAuthOk: false,
|
||||
authOk: true,
|
||||
hasSharedAuth: false,
|
||||
isLocalClient: true,
|
||||
}).kind,
|
||||
).toBe("allow");
|
||||
|
||||
expect(
|
||||
evaluateMissingDeviceIdentity({
|
||||
hasDeviceIdentity: false,
|
||||
role: "node",
|
||||
isControlUi: false,
|
||||
controlUiAuthPolicy: policy,
|
||||
trustedProxyAuthOk: false,
|
||||
localBackendSelfPairingOk: true,
|
||||
sharedAuthOk: false,
|
||||
authOk: true,
|
||||
hasSharedAuth: false,
|
||||
isLocalClient: true,
|
||||
}).kind,
|
||||
).toBe("reject-device-required");
|
||||
|
||||
expect(
|
||||
evaluateMissingDeviceIdentity({
|
||||
hasDeviceIdentity: false,
|
||||
|
||||
@@ -111,6 +111,7 @@ export function evaluateMissingDeviceIdentity(params: {
|
||||
isControlUi: boolean;
|
||||
controlUiAuthPolicy: ControlUiAuthPolicy;
|
||||
trustedProxyAuthOk?: boolean;
|
||||
localBackendSelfPairingOk?: boolean;
|
||||
sharedAuthOk: boolean;
|
||||
authOk: boolean;
|
||||
hasSharedAuth: boolean;
|
||||
@@ -130,6 +131,9 @@ export function evaluateMissingDeviceIdentity(params: {
|
||||
// registrations (see #45405 review).
|
||||
return { kind: "allow" };
|
||||
}
|
||||
if (params.localBackendSelfPairingOk && params.role === "operator") {
|
||||
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
|
||||
|
||||
@@ -673,6 +673,7 @@ export function attachGatewayWsMessageHandler(params: GatewayWsMessageHandlerPar
|
||||
isControlUi,
|
||||
controlUiAuthPolicy,
|
||||
trustedProxyAuthOk,
|
||||
localBackendSelfPairingOk: skipLocalBackendSelfPairing,
|
||||
sharedAuthOk,
|
||||
authOk,
|
||||
hasSharedAuth,
|
||||
|
||||
Reference in New Issue
Block a user