mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:50:43 +00:00
Gateway: reject published placeholder tokens
This commit is contained in:
committed by
Peter Steinberger
parent
960bc52e3c
commit
106b770c40
@@ -1,6 +1,7 @@
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS } from "../gateway/known-weak-gateway-secrets.js";
|
||||
|
||||
const INSTALL_DOCS_DIR = path.join(process.cwd(), "docs", "install");
|
||||
const CLOUD_INSTALL_DOCS = ["gcp.md", "hetzner.md"] as const;
|
||||
@@ -10,7 +11,9 @@ describe("cloud install docs", () => {
|
||||
for (const docName of CLOUD_INSTALL_DOCS) {
|
||||
const markdown = await fs.readFile(path.join(INSTALL_DOCS_DIR, docName), "utf8");
|
||||
|
||||
expect(markdown).not.toContain("OPENCLAW_GATEWAY_TOKEN=change-me-now");
|
||||
for (const token of KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS) {
|
||||
expect(markdown).not.toContain(`OPENCLAW_GATEWAY_TOKEN=${token}`);
|
||||
}
|
||||
expect(markdown).toMatch(/OPENCLAW_GATEWAY_TOKEN=[ \t]*\r?\n/);
|
||||
expect(markdown).toContain("openssl rand -hex 32");
|
||||
}
|
||||
|
||||
6
src/gateway/known-weak-gateway-secrets.ts
Normal file
6
src/gateway/known-weak-gateway-secrets.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export const KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS = [
|
||||
"change-me-to-a-long-random-token",
|
||||
"change-me-now",
|
||||
] as const;
|
||||
|
||||
export const KNOWN_WEAK_GATEWAY_PASSWORD_PLACEHOLDERS = ["change-me-to-a-strong-password"] as const;
|
||||
@@ -1,6 +1,7 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { expectGeneratedTokenPersistedToGatewayAuth } from "../test-utils/auth-token-assertions.js";
|
||||
import { KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS } from "./known-weak-gateway-secrets.js";
|
||||
import {
|
||||
assertGatewayAuthNotKnownWeak,
|
||||
assertHooksTokenSeparateFromGatewayAuth,
|
||||
@@ -404,34 +405,40 @@ describe("ensureGatewayStartupAuth", () => {
|
||||
).rejects.toThrow(/hooks\.token must not match gateway auth token/i);
|
||||
});
|
||||
|
||||
it("rejects the .env.example placeholder token supplied via environment", async () => {
|
||||
await expect(
|
||||
ensureGatewayStartupAuth({
|
||||
cfg: {},
|
||||
env: {
|
||||
OPENCLAW_GATEWAY_TOKEN: "change-me-to-a-long-random-token",
|
||||
} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).rejects.toThrow(/example placeholder/i);
|
||||
expect(mocks.replaceConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
|
||||
"rejects the published placeholder token %s supplied via environment",
|
||||
async (token) => {
|
||||
await expect(
|
||||
ensureGatewayStartupAuth({
|
||||
cfg: {},
|
||||
env: {
|
||||
OPENCLAW_GATEWAY_TOKEN: token,
|
||||
} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).rejects.toThrow(/example placeholder/i);
|
||||
expect(mocks.replaceConfigFile).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects the .env.example placeholder token supplied via config", async () => {
|
||||
await expect(
|
||||
ensureGatewayStartupAuth({
|
||||
cfg: {
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "token",
|
||||
token: "change-me-to-a-long-random-token",
|
||||
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
|
||||
"rejects the published placeholder token %s supplied via config",
|
||||
async (token) => {
|
||||
await expect(
|
||||
ensureGatewayStartupAuth({
|
||||
cfg: {
|
||||
gateway: {
|
||||
auth: {
|
||||
mode: "token",
|
||||
token,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).rejects.toThrow(/example placeholder/i);
|
||||
expect(mocks.replaceConfigFile).not.toHaveBeenCalled();
|
||||
});
|
||||
env: {} as NodeJS.ProcessEnv,
|
||||
}),
|
||||
).rejects.toThrow(/example placeholder/i);
|
||||
expect(mocks.replaceConfigFile).not.toHaveBeenCalled();
|
||||
},
|
||||
);
|
||||
|
||||
it("rejects the .env.example placeholder password supplied via config", async () => {
|
||||
await expect(
|
||||
@@ -472,16 +479,19 @@ describe("assertGatewayAuthNotKnownWeak", () => {
|
||||
mocks.replaceConfigFile.mockClear();
|
||||
});
|
||||
|
||||
it("throws on the known-weak token sentinel", () => {
|
||||
expect(() =>
|
||||
assertGatewayAuthNotKnownWeak({
|
||||
mode: "token",
|
||||
modeSource: "config",
|
||||
token: "change-me-to-a-long-random-token",
|
||||
allowTailscale: false,
|
||||
}),
|
||||
).toThrow(/example placeholder/i);
|
||||
});
|
||||
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
|
||||
"throws on the known-weak token sentinel %s",
|
||||
(token) => {
|
||||
expect(() =>
|
||||
assertGatewayAuthNotKnownWeak({
|
||||
mode: "token",
|
||||
modeSource: "config",
|
||||
token,
|
||||
allowTailscale: false,
|
||||
}),
|
||||
).toThrow(/example placeholder/i);
|
||||
},
|
||||
);
|
||||
|
||||
it("throws on the known-weak password sentinel", () => {
|
||||
expect(() =>
|
||||
@@ -494,16 +504,19 @@ describe("assertGatewayAuthNotKnownWeak", () => {
|
||||
).toThrow(/example placeholder/i);
|
||||
});
|
||||
|
||||
it("ignores whitespace-padded placeholder tokens (trimmed match)", () => {
|
||||
expect(() =>
|
||||
assertGatewayAuthNotKnownWeak({
|
||||
mode: "token",
|
||||
modeSource: "config",
|
||||
token: " change-me-to-a-long-random-token ",
|
||||
allowTailscale: false,
|
||||
}),
|
||||
).toThrow(/example placeholder/i);
|
||||
});
|
||||
it.each(KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS)(
|
||||
"rejects whitespace-padded placeholder token %s after trimming",
|
||||
(token) => {
|
||||
expect(() =>
|
||||
assertGatewayAuthNotKnownWeak({
|
||||
mode: "token",
|
||||
modeSource: "config",
|
||||
token: ` ${token} `,
|
||||
allowTailscale: false,
|
||||
}),
|
||||
).toThrow(/example placeholder/i);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not throw on an empty token (falls through to generation path)", () => {
|
||||
expect(() =>
|
||||
|
||||
@@ -18,6 +18,10 @@ import {
|
||||
hasGatewayTokenEnvCandidate,
|
||||
trimToUndefined,
|
||||
} from "./credentials.js";
|
||||
import {
|
||||
KNOWN_WEAK_GATEWAY_PASSWORD_PLACEHOLDERS,
|
||||
KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS,
|
||||
} from "./known-weak-gateway-secrets.js";
|
||||
|
||||
/**
|
||||
* Placeholder credentials that have ever shipped in `.env.example` or been
|
||||
@@ -31,13 +35,13 @@ import {
|
||||
* the example file alone does not protect users who follow an older doc
|
||||
* snippet or copy a tutorial command line.
|
||||
*/
|
||||
const KNOWN_WEAK_GATEWAY_TOKENS: ReadonlySet<string> = new Set([
|
||||
"change-me-to-a-long-random-token",
|
||||
]);
|
||||
const KNOWN_WEAK_GATEWAY_TOKENS: ReadonlySet<string> = new Set(
|
||||
KNOWN_WEAK_GATEWAY_TOKEN_PLACEHOLDERS,
|
||||
);
|
||||
|
||||
const KNOWN_WEAK_GATEWAY_PASSWORDS: ReadonlySet<string> = new Set([
|
||||
"change-me-to-a-strong-password", // pragma: allowlist secret
|
||||
]);
|
||||
const KNOWN_WEAK_GATEWAY_PASSWORDS: ReadonlySet<string> = new Set(
|
||||
KNOWN_WEAK_GATEWAY_PASSWORD_PLACEHOLDERS,
|
||||
);
|
||||
|
||||
export function mergeGatewayAuthConfig(
|
||||
base?: GatewayAuthConfig,
|
||||
@@ -269,8 +273,8 @@ export function assertGatewayAuthNotKnownWeak(auth: ResolvedGatewayAuth): void {
|
||||
const token = auth.token?.trim() ?? "";
|
||||
if (token && KNOWN_WEAK_GATEWAY_TOKENS.has(token)) {
|
||||
throw new Error(
|
||||
"Invalid config: gateway auth token is set to the example placeholder " +
|
||||
"from .env.example. Generate a real secret (e.g. `openssl rand -hex 32`) " +
|
||||
"Invalid config: gateway auth token is set to a published example placeholder " +
|
||||
"from docs or .env.example. Generate a real secret (e.g. `openssl rand -hex 32`) " +
|
||||
"and set OPENCLAW_GATEWAY_TOKEN or gateway.auth.token before starting " +
|
||||
"the gateway.",
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user