mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-02 12:51:57 +00:00
Config: sanitize terminal-rendered validation errors
This commit is contained in:
@@ -35,6 +35,18 @@ describe("config issue format", () => {
|
||||
).toEqual(["× <root>: first", "× channels.signal.dmPolicy: second"]);
|
||||
});
|
||||
|
||||
it("sanitizes control characters and ANSI sequences in formatted lines", () => {
|
||||
expect(
|
||||
formatConfigIssueLine(
|
||||
{
|
||||
path: "gateway.\nbind\x1b[31m",
|
||||
message: "bad\r\n\tvalue\x1b[0m\u0007",
|
||||
},
|
||||
"-",
|
||||
),
|
||||
).toBe("- gateway.\\nbind: bad\\r\\n\\tvalue");
|
||||
});
|
||||
|
||||
it("normalizes issue metadata for machine output", () => {
|
||||
expect(
|
||||
normalizeConfigIssue({
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
import type { ConfigValidationIssue } from "./types.js";
|
||||
|
||||
type ConfigIssueLineInput = {
|
||||
@@ -52,7 +53,9 @@ export function formatConfigIssueLine(
|
||||
opts?: ConfigIssueFormatOptions,
|
||||
): string {
|
||||
const prefix = marker ? `${marker} ` : "";
|
||||
return `${prefix}${resolveIssuePathForLine(issue.path, opts)}: ${issue.message}`;
|
||||
const path = sanitizeTerminalText(resolveIssuePathForLine(issue.path, opts));
|
||||
const message = sanitizeTerminalText(issue.message);
|
||||
return `${prefix}${path}: ${message}`;
|
||||
}
|
||||
|
||||
export function formatConfigIssueLines(
|
||||
|
||||
@@ -182,4 +182,30 @@ describe("schema validator", () => {
|
||||
expect(issue?.message).toContain("... (+");
|
||||
}
|
||||
});
|
||||
|
||||
it("sanitizes terminal text while preserving structured fields", () => {
|
||||
const maliciousProperty = "evil\nkey\t\x1b[31mred\x1b[0m";
|
||||
const res = validateJsonSchemaValue({
|
||||
cacheKey: "schema-validator.test.terminal-sanitize",
|
||||
schema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
required: [maliciousProperty],
|
||||
},
|
||||
value: {},
|
||||
});
|
||||
|
||||
expect(res.ok).toBe(false);
|
||||
if (!res.ok) {
|
||||
const issue = res.errors[0];
|
||||
expect(issue).toBeDefined();
|
||||
expect(issue?.path).toContain("\n");
|
||||
expect(issue?.message).toContain("\n");
|
||||
expect(issue?.text).toContain("\\n");
|
||||
expect(issue?.text).toContain("\\t");
|
||||
expect(issue?.text).not.toContain("\n");
|
||||
expect(issue?.text).not.toContain("\t");
|
||||
expect(issue?.text).not.toContain("\x1b");
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createRequire } from "node:module";
|
||||
import type { ErrorObject, ValidateFunction } from "ajv";
|
||||
import { appendAllowedValuesHint, summarizeAllowedValues } from "../config/allowed-values.js";
|
||||
import { sanitizeTerminalText } from "../terminal/safe-text.js";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
type AjvLike = {
|
||||
@@ -113,10 +114,12 @@ function formatAjvErrors(errors: ErrorObject[] | null | undefined): JsonSchemaVa
|
||||
const message = allowedValuesSummary
|
||||
? appendAllowedValuesHint(baseMessage, allowedValuesSummary)
|
||||
: baseMessage;
|
||||
const safePath = sanitizeTerminalText(path);
|
||||
const safeMessage = sanitizeTerminalText(message);
|
||||
return {
|
||||
path,
|
||||
message,
|
||||
text: `${path}: ${message}`,
|
||||
text: `${safePath}: ${safeMessage}`,
|
||||
...(allowedValuesSummary
|
||||
? {
|
||||
allowedValues: allowedValuesSummary.values,
|
||||
|
||||
20
src/terminal/safe-text.ts
Normal file
20
src/terminal/safe-text.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { stripAnsi } from "./ansi.js";
|
||||
|
||||
/**
|
||||
* Normalize untrusted text for single-line terminal/log rendering.
|
||||
*/
|
||||
export function sanitizeTerminalText(input: string): string {
|
||||
const normalized = stripAnsi(input)
|
||||
.replace(/\r/g, "\\r")
|
||||
.replace(/\n/g, "\\n")
|
||||
.replace(/\t/g, "\\t");
|
||||
let sanitized = "";
|
||||
for (const char of normalized) {
|
||||
const code = char.charCodeAt(0);
|
||||
const isControl = (code >= 0x00 && code <= 0x1f) || code === 0x7f;
|
||||
if (!isControl) {
|
||||
sanitized += char;
|
||||
}
|
||||
}
|
||||
return sanitized;
|
||||
}
|
||||
Reference in New Issue
Block a user