mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-11 02:54:31 +00:00
Gateway: add safer password-file input for gateway run (#39067)
* CLI: add gateway password-file option * Docs: document safer gateway password input * Update src/cli/gateway-cli/run.ts Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> * Tests: clean up gateway password temp dirs * CLI: restore gateway password warning flow * Security: harden secret file reads --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
This commit is contained in:
54
src/acp/secret-file.test.ts
Normal file
54
src/acp/secret-file.test.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { mkdir, symlink, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { afterEach, describe, expect, it } from "vitest";
|
||||
import { createTrackedTempDirs } from "../test-utils/tracked-temp-dirs.js";
|
||||
import { MAX_SECRET_FILE_BYTES, readSecretFromFile } from "./secret-file.js";
|
||||
|
||||
const tempDirs = createTrackedTempDirs();
|
||||
const createTempDir = () => tempDirs.make("openclaw-secret-file-test-");
|
||||
|
||||
afterEach(async () => {
|
||||
await tempDirs.cleanup();
|
||||
});
|
||||
|
||||
describe("readSecretFromFile", () => {
|
||||
it("reads and trims a regular secret file", async () => {
|
||||
const dir = await createTempDir();
|
||||
const file = path.join(dir, "secret.txt");
|
||||
await writeFile(file, " top-secret \n", "utf8");
|
||||
|
||||
expect(readSecretFromFile(file, "Gateway password")).toBe("top-secret");
|
||||
});
|
||||
|
||||
it("rejects files larger than the secret-file limit", async () => {
|
||||
const dir = await createTempDir();
|
||||
const file = path.join(dir, "secret.txt");
|
||||
await writeFile(file, "x".repeat(MAX_SECRET_FILE_BYTES + 1), "utf8");
|
||||
|
||||
expect(() => readSecretFromFile(file, "Gateway password")).toThrow(
|
||||
`Gateway password file at ${file} exceeds ${MAX_SECRET_FILE_BYTES} bytes.`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects non-regular files", async () => {
|
||||
const dir = await createTempDir();
|
||||
const nestedDir = path.join(dir, "secret-dir");
|
||||
await mkdir(nestedDir);
|
||||
|
||||
expect(() => readSecretFromFile(nestedDir, "Gateway password")).toThrow(
|
||||
`Gateway password file at ${nestedDir} must be a regular file.`,
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects symlinks", async () => {
|
||||
const dir = await createTempDir();
|
||||
const target = path.join(dir, "target.txt");
|
||||
const link = path.join(dir, "secret-link.txt");
|
||||
await writeFile(target, "top-secret\n", "utf8");
|
||||
await symlink(target, link);
|
||||
|
||||
expect(() => readSecretFromFile(link, "Gateway password")).toThrow(
|
||||
`Gateway password file at ${link} must not be a symlink.`,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -1,11 +1,32 @@
|
||||
import fs from "node:fs";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
|
||||
export const MAX_SECRET_FILE_BYTES = 16 * 1024;
|
||||
|
||||
export function readSecretFromFile(filePath: string, label: string): string {
|
||||
const resolvedPath = resolveUserPath(filePath.trim());
|
||||
if (!resolvedPath) {
|
||||
throw new Error(`${label} file path is empty.`);
|
||||
}
|
||||
|
||||
let stat: fs.Stats;
|
||||
try {
|
||||
stat = fs.lstatSync(resolvedPath);
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to inspect ${label} file at ${resolvedPath}: ${String(err)}`, {
|
||||
cause: err,
|
||||
});
|
||||
}
|
||||
if (stat.isSymbolicLink()) {
|
||||
throw new Error(`${label} file at ${resolvedPath} must not be a symlink.`);
|
||||
}
|
||||
if (!stat.isFile()) {
|
||||
throw new Error(`${label} file at ${resolvedPath} must be a regular file.`);
|
||||
}
|
||||
if (stat.size > MAX_SECRET_FILE_BYTES) {
|
||||
throw new Error(`${label} file at ${resolvedPath} exceeds ${MAX_SECRET_FILE_BYTES} bytes.`);
|
||||
}
|
||||
|
||||
let raw = "";
|
||||
try {
|
||||
raw = fs.readFileSync(resolvedPath, "utf8");
|
||||
|
||||
Reference in New Issue
Block a user