Gateway: cover trusted-proxy scope regression (#49372)

* Gateway: cover trusted-proxy scope regression

* Changelog: note trusted-proxy regression coverage

* Gateway: format trusted-proxy regression test
This commit is contained in:
Vincent Koc 2026-03-17 19:59:01 -07:00 committed by GitHub
parent 25e6cd38b6
commit 870f260772
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 19 additions and 0 deletions

View File

@ -131,6 +131,7 @@ Docs: https://docs.openclaw.ai
- Mattermost/DM send: retry transient direct-channel creation failures for DM deliveries, with configurable backoff and per-request timeout. (#42398) Thanks @JonathanJing.
- Telegram/network: unify API and media fetches under the same sticky IPv4 and pinned-IP fallback chain, and re-validate pinned override addresses against SSRF policy. (#49148) Thanks @obviyus.
- Agents/prompt composition: append bootstrap truncation warnings to the current-turn prompt and add regression coverage for stable system-prompt cache invariants. (#49237) Thanks @scoootscooob.
- Gateway/auth: add regression coverage that keeps device-less trusted-proxy Control UI sessions off privileged pairing approval RPCs. Thanks @vincentkoc.
### Breaking

View File

@ -112,6 +112,12 @@ export function registerControlUiAndPairingSuite(): void {
expect(talk.error?.message).toBe("missing scope: operator.read");
};
const expectDevicePairApproveDenied = async (ws: WebSocket, requestId: string) => {
const approve = await rpcReq(ws, "device.pair.approve", { requestId });
expect(approve.ok).toBe(false);
expect(approve.error?.message).toBe("missing scope: operator.admin");
};
const connectControlUiWithoutDeviceAndExpectOk = async (params: {
ws: WebSocket;
token?: string;
@ -244,6 +250,17 @@ export function registerControlUiAndPairingSuite(): void {
test("clears self-declared scopes for trusted-proxy control ui without device identity", async () => {
await configureTrustedProxyControlUiAuth();
const { publicKeyRawBase64UrlFromPem } = await import("../infra/device-identity.js");
const { requestDevicePairing } = await import("../infra/device-pairing.js");
const { identity } = await createOperatorIdentityFixture("openclaw-control-ui-trusted-proxy-");
const pendingRequest = await requestDevicePairing({
deviceId: identity.deviceId,
publicKey: publicKeyRawBase64UrlFromPem(identity.publicKeyPem),
role: "operator",
scopes: ["operator.admin"],
clientId: CONTROL_UI_CLIENT.id,
clientMode: CONTROL_UI_CLIENT.mode,
});
await withGatewayServer(async ({ port }) => {
const ws = await openWs(port, TRUSTED_PROXY_CONTROL_UI_HEADERS);
try {
@ -259,6 +276,7 @@ export function registerControlUiAndPairingSuite(): void {
await expectStatusMissingScopeButHealthOk(ws);
await expectAdminRpcDenied(ws);
await expectTalkSecretsDenied(ws);
await expectDevicePairApproveDenied(ws, pendingRequest.request.requestId);
} finally {
ws.close();
}