mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 12:56:01 -06:00
fix(security): escape field names in transcript regex extraction
extractJsonStringFieldPrefix and extractJsonNullableStringFieldPrefix
interpolate the `field` parameter into `new RegExp(...)` without
escaping. All current callers pass hardcoded strings ("id",
"parentId", "type", "role"), but the function signature accepts
any string. A future caller passing a field containing regex
metacharacters (e.g. "foo.bar") would match unintended patterns.
Wrap the interpolation with escapeRegExp() from src/shared/regexp.ts
so metacharacters are treated literally.
Signed-off-by: Sebastien Tardif <sebtardif@ncf.ca>
This commit is contained in:
committed by
Peter Steinberger
parent
78a1e7dfe6
commit
fe8d99d421
@@ -1982,6 +1982,39 @@ describe("oversized transcript line guards", () => {
|
||||
expectUsageFields(usage, { modelProvider: "test-provider" });
|
||||
});
|
||||
|
||||
test("oversized line metadata extraction preserves id and parentId", async () => {
|
||||
const sessionId = "test-oversized-metadata-extract";
|
||||
const transcriptPath = path.join(tmpDir, `${sessionId}.jsonl`);
|
||||
const oversizedContent = "w".repeat(300 * 1024);
|
||||
const lines = [
|
||||
JSON.stringify({ type: "session", version: 3, id: sessionId }),
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
id: "root-msg",
|
||||
parentId: null,
|
||||
message: { role: "user", content: "root" },
|
||||
}),
|
||||
JSON.stringify({
|
||||
type: "message",
|
||||
id: "oversized-child",
|
||||
parentId: "root-msg",
|
||||
message: { role: "assistant", content: oversizedContent },
|
||||
}),
|
||||
];
|
||||
fs.writeFileSync(transcriptPath, `${lines.join("\n")}\n`, "utf-8");
|
||||
|
||||
const out = await readRecentSessionMessagesAsync(sessionId, storePath, undefined, {
|
||||
maxMessages: 10,
|
||||
});
|
||||
|
||||
const serialized = JSON.stringify(out);
|
||||
// The oversized line's id and parentId must be extracted correctly
|
||||
// from the prefix via regex-based field extraction.
|
||||
expect(serialized).toContain("oversized-child");
|
||||
expect(serialized).toContain("root-msg");
|
||||
expect(serialized).not.toContain(oversizedContent);
|
||||
});
|
||||
|
||||
test("readSessionTitleFieldsFromTranscriptAsync delegates to bounded sync reader", async () => {
|
||||
const sessionId = "test-async-title-bounded";
|
||||
writeTranscript(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { deriveSessionTotalTokens, hasNonzeroUsage, normalizeUsage } from "../ag
|
||||
import { jsonUtf8Bytes } from "../infra/json-utf8-bytes.js";
|
||||
import { hasInterSessionUserProvenance } from "../sessions/input-provenance.js";
|
||||
import { extractAssistantVisibleText } from "../shared/chat-message-content.js";
|
||||
import { escapeRegExp } from "../shared/regexp.js";
|
||||
import { normalizeLowercaseStringOrEmpty } from "../shared/string-coerce.js";
|
||||
import { estimateStringChars, estimateTokensFromChars } from "../utils/cjk-chars.js";
|
||||
import { stripInlineDirectiveTagsForDisplay } from "../utils/directive-tags.js";
|
||||
@@ -273,7 +274,7 @@ function isOversizedTranscriptLine(line: string): boolean {
|
||||
}
|
||||
|
||||
function extractJsonStringFieldPrefix(prefix: string, field: string): string | undefined {
|
||||
const match = new RegExp(`"${field}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`).exec(prefix);
|
||||
const match = new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*"((?:\\\\.|[^"\\\\])*)"`).exec(prefix);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -289,7 +290,7 @@ function extractJsonNullableStringFieldPrefix(
|
||||
prefix: string,
|
||||
field: string,
|
||||
): string | null | undefined {
|
||||
if (new RegExp(`"${field}"\\s*:\\s*null`).test(prefix)) {
|
||||
if (new RegExp(`"${escapeRegExp(field)}"\\s*:\\s*null`).test(prefix)) {
|
||||
return null;
|
||||
}
|
||||
return extractJsonStringFieldPrefix(prefix, field);
|
||||
|
||||
Reference in New Issue
Block a user