test: tighten browser cdpUrl redaction coverage

This commit is contained in:
Mason Huang
2026-04-18 00:22:15 +08:00
parent c2d5e03f0c
commit 3d20dcc10b
2 changed files with 77 additions and 50 deletions

View File

@@ -1163,59 +1163,38 @@ describe("redactConfigSnapshot", () => {
expect(channels.slack.accounts[1].botToken).toBe(REDACTED_SENTINEL);
});
it("redacts credentials embedded in browser.cdpUrl (query token and userinfo)", () => {
it("redacts browser cdpUrl secrets while preserving bare endpoints", () => {
const hints = buildConfigSchema().uiHints;
const raw = `{
browser: {
cdpUrl: "https://user:pass@chrome.browserless.io?token=supersecret123",
},
}`;
const snapshot = makeSnapshot(
{
browser: {
cdpUrl: "https://user:pass@chrome.browserless.io?token=supersecret123",
},
},
raw,
);
const result = redactConfigSnapshot(snapshot, hints);
const cfg = result.config as typeof snapshot.config;
expect(cfg.browser.cdpUrl).toBe(REDACTED_SENTINEL);
expect(result.raw).toContain(REDACTED_SENTINEL);
expect(result.raw).not.toContain("user:pass@");
expect(result.raw).not.toContain("supersecret123");
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
expect(restored.browser.cdpUrl).toBe(
"https://user:pass@chrome.browserless.io?token=supersecret123",
);
});
it("redacts credentials embedded in browser.profiles.*.cdpUrl", () => {
const hints = buildConfigSchema().uiHints;
const raw = `{
browser: {
profiles: {
staging: {
remote: {
cdpUrl: "https://chrome.staging.example.com?token=staging-secret",
},
prod: {
cdpUrl: "https://alice:secret@chrome.prod.example.com",
},
local: {
cdpUrl: "ws://localhost:9222",
},
},
},
}`;
const snapshot = makeSnapshot(
{
browser: {
cdpUrl: "https://user:pass@chrome.browserless.io?token=supersecret123",
profiles: {
staging: {
remote: {
cdpUrl: "https://chrome.staging.example.com?token=staging-secret",
},
prod: {
cdpUrl: "https://alice:secret@chrome.prod.example.com",
},
local: {
cdpUrl: "ws://localhost:9222",
},
},
},
},
@@ -1224,34 +1203,26 @@ describe("redactConfigSnapshot", () => {
const result = redactConfigSnapshot(snapshot, hints);
const cfg = result.config as typeof snapshot.config;
expect(cfg.browser.profiles.staging.cdpUrl).toBe(REDACTED_SENTINEL);
expect(cfg.browser.cdpUrl).toBe(REDACTED_SENTINEL);
expect(cfg.browser.profiles.remote.cdpUrl).toBe(REDACTED_SENTINEL);
expect(cfg.browser.profiles.prod.cdpUrl).toBe(REDACTED_SENTINEL);
expect(cfg.browser.profiles.local.cdpUrl).toBe("ws://localhost:9222");
expect(result.raw).toContain(REDACTED_SENTINEL);
expect(result.raw).not.toContain("user:pass@");
expect(result.raw).not.toContain("supersecret123");
expect(result.raw).not.toContain("staging-secret");
expect(result.raw).not.toContain("alice:secret@");
const restored = restoreRedactedValues(result.config, snapshot.config, hints);
expect(restored.browser.profiles.staging.cdpUrl).toBe(
expect(restored.browser.cdpUrl).toBe(
"https://user:pass@chrome.browserless.io?token=supersecret123",
);
expect(restored.browser.profiles.remote.cdpUrl).toBe(
"https://chrome.staging.example.com?token=staging-secret",
);
expect(restored.browser.profiles.prod.cdpUrl).toBe(
"https://alice:secret@chrome.prod.example.com",
);
});
it("does not redact bare cdpUrl addresses without credentials", () => {
const hints = buildConfigSchema().uiHints;
const snapshot = makeSnapshot({
browser: {
cdpUrl: "http://10.0.0.42:9222",
profiles: {
local: { cdpUrl: "ws://localhost:9222" },
},
},
});
const result = redactConfigSnapshot(snapshot, hints);
const cfg = result.config as typeof snapshot.config;
expect(cfg.browser.cdpUrl).toBe("http://10.0.0.42:9222");
expect(cfg.browser.profiles.local.cdpUrl).toBe("ws://localhost:9222");
expect(restored.browser.profiles.local.cdpUrl).toBe("ws://localhost:9222");
});
});

View File

@@ -157,6 +157,62 @@ describe("gateway config methods", () => {
expect(res.payload?.config).toBeTruthy();
});
it("redacts browser cdpUrl credentials from config.get responses", async () => {
const { createConfigIO, resetConfigRuntimeState } = await import("../config/config.js");
const configPath = createConfigIO().configPath;
await fs.mkdir(path.dirname(configPath), { recursive: true });
try {
await fs.writeFile(
configPath,
`${JSON.stringify(
{
browser: {
cdpUrl: "https://user:pass@chrome.browserless.io?token=supersecret123",
profiles: {
remote: {
cdpUrl: "https://alice:secret@chrome.remote.example.com?token=profile-secret",
},
local: {
cdpUrl: "ws://127.0.0.1:9222",
},
},
},
},
null,
2,
)}\n`,
"utf-8",
);
resetConfigRuntimeState();
const after = await rpcReq<{
raw?: string | null;
config?: {
browser?: {
cdpUrl?: string;
profiles?: Record<string, { cdpUrl?: string }>;
};
};
}>(requireWs(), "config.get", {});
expect(after.ok).toBe(true);
expect(after.payload?.config?.browser?.cdpUrl).toBe("__OPENCLAW_REDACTED__");
expect(after.payload?.config?.browser?.profiles?.remote?.cdpUrl).toBe(
"__OPENCLAW_REDACTED__",
);
expect(after.payload?.config?.browser?.profiles?.local?.cdpUrl).toBe("ws://127.0.0.1:9222");
if (typeof after.payload?.raw === "string") {
expect(after.payload.raw).toContain("__OPENCLAW_REDACTED__");
expect(after.payload.raw).not.toContain("supersecret123");
expect(after.payload.raw).not.toContain("user:pass@");
expect(after.payload.raw).not.toContain("profile-secret");
expect(after.payload.raw).not.toContain("alice:secret@");
}
} finally {
await fs.rm(configPath, { force: true });
resetConfigRuntimeState();
}
});
it("does not reject config.set for unresolved auth-profile refs outside submitted config", async () => {
const missingEnvVar = `OPENCLAW_MISSING_AUTH_PROFILE_REF_${Date.now()}`;
await writeUnresolvedAuthProfileTokenRef(missingEnvVar);