fix(dev): bound anthropic prompt log tails

This commit is contained in:
Vincent Koc
2026-06-07 10:22:50 +02:00
parent eae4d284e7
commit e64f2324b9
2 changed files with 60 additions and 3 deletions
+25 -3
View File
@@ -67,6 +67,7 @@ const CAPTURE_PROXY_MAX_BODY_BYTES = parseStrictIntegerOption({
min: 1,
raw: process.env.OPENCLAW_PROMPT_CAPTURE_MAX_BODY_BYTES,
});
const GATEWAY_LOG_TAIL_BYTES = 256 * 1024;
const SETUP_TOKEN_RAW = process.env.OPENCLAW_LIVE_SETUP_TOKEN?.trim() ?? "";
const SETUP_TOKEN_VALUE = process.env.OPENCLAW_LIVE_SETUP_TOKEN_VALUE?.trim() ?? "";
const SETUP_TOKEN_PROFILE = process.env.OPENCLAW_LIVE_SETUP_TOKEN_PROFILE?.trim() ?? "";
@@ -626,9 +627,29 @@ async function waitForGatewayReady(url: string, token: string): Promise<void> {
throw new Error(lastError);
}
async function readLogTail(logPath: string): Promise<string> {
const raw = await fs.readFile(logPath, "utf8").catch(() => "");
return redactForDevToolLog(raw.split(/\r?\n/).slice(-40).join("\n").trim());
async function readLogTail(logPath: string, maxBytes = GATEWAY_LOG_TAIL_BYTES): Promise<string> {
if (!Number.isSafeInteger(maxBytes) || maxBytes <= 0) {
throw new Error("maxBytes must be a positive integer");
}
const logFile = await fs.open(logPath, "r").catch(() => undefined);
if (!logFile) {
return "";
}
try {
const stat = await logFile.stat();
if (stat.size <= 0) {
return "";
}
const length = Math.min(stat.size, maxBytes);
const position = Math.max(0, stat.size - length);
const buffer = Buffer.allocUnsafe(length);
const { bytesRead } = await logFile.read(buffer, 0, length, position);
const raw = buffer.subarray(0, bytesRead).toString("utf8");
const lineAlignedRaw = position > 0 ? raw.replace(/^[^\n]*(?:\r?\n|$)/u, "") : raw;
return redactForDevToolLog(lineAlignedRaw.split(/\r?\n/).slice(-40).join("\n").trim());
} finally {
await logFile.close();
}
}
async function runGatewayPrompt(prompt: string): Promise<PromptResult> {
@@ -815,6 +836,7 @@ export const testing = {
cleanupPromptProbeTmpDir,
matchesExtraUsage400,
promptProbeTmpResult,
readLogTail,
readRequestBody,
resolveAnthropicUpstreamUrl,
stopGatewayPromptChild,
+35
View File
@@ -411,6 +411,41 @@ describe("script-specific dev tooling hardening", () => {
expect(destroy).toHaveBeenCalled();
});
it("reads only the bounded Anthropic prompt probe gateway log tail", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-prompt-probe-log-"));
tempDirs.push(tempRoot);
const logPath = path.join(tempRoot, "gateway.log");
const token = "sk-test1234567890abcdefghijklmnop"; // pragma: allowlist secret
await fs.writeFile(
logPath,
[
`DO_NOT_PRINT_OLD_GATEWAY_LOG OPENAI_API_KEY=${token}`,
"x".repeat(256),
`recent gateway tail Authorization: Bearer ${token}`,
].join("\n"),
"utf8",
);
const tail = await promptProbeTesting.readLogTail(logPath, 128);
expect(tail).toContain("recent gateway tail");
expect(tail).not.toContain("DO_NOT_PRINT_OLD_GATEWAY_LOG");
expect(tail).not.toContain(token);
});
it("drops partial Anthropic prompt probe log lines before redaction", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-prompt-probe-log-"));
tempDirs.push(tempRoot);
const logPath = path.join(tempRoot, "gateway.log");
const token = `sk-test${"a".repeat(80)}`; // pragma: allowlist secret
await fs.writeFile(logPath, `Authorization: Bearer ${token}\nrecent gateway tail`, "utf8");
const tail = await promptProbeTesting.readLogTail(logPath, "recent gateway tail".length + 24);
expect(tail).toBe("recent gateway tail");
expect(tail).not.toContain(token.slice(-16));
});
it("cleans Anthropic prompt probe temp dirs unless explicitly kept", async () => {
const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-prompt-probe-test-"));
const keepRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-prompt-probe-test-"));