Tighten opaque plugin content boundaries

This commit is contained in:
npc0-hue
2026-09-03 18:24:39 +08:00
parent 80cddbf19d
commit 14cbc63e61
31 changed files with 452 additions and 558 deletions
-71
View File
@@ -171,76 +171,6 @@ function unsafeGameClientBridgeCommandTypeReason(value: string): string | undefi
return undefined;
}
function unsafeGameClientBridgePayloadKey(value: string): boolean {
const tokens = identifierTokens(value);
const compact = tokens.join("");
return ["sql", "rawsql", "sqltext", "sqlstatement", "dsn", "hostpath", "socket", "credential", "accesstoken"].includes(compact);
}
function unsafeBridgeSchemaFieldReason(fieldName: string): string | undefined {
const tokens = identifierTokens(fieldName);
const compact = tokens.join("");
const generalReason = unsafeFieldReason(fieldName);
if (generalReason) {
return generalReason;
}
if ((tokens.includes("sql") || tokens.includes("query")) && tokens.includes("template") && (tokens.includes("key") || tokens.includes("ref"))) {
return undefined;
}
if (["sql", "rawsql", "sqltext", "sqlquery", "sqlstatement", "rawquery", "statement"].includes(compact)) {
return "arbitrary SQL field is not allowed";
}
if (["shell", "shellcommand", "shellscript", "script", "scriptbody", "terminalcommand", "commandline", "powershell"].includes(compact)) {
return "arbitrary shell or script field is not allowed";
}
if (["hostpath", "rawpath", "absolutepath", "filesystempath"].includes(compact)) {
return "raw host path field is not allowed";
}
if (["runcapability", "executorcapability", "runendpoint", "runsocket", "directrun"].includes(compact)) {
return "unsafe executor capability or direct Run field is not allowed";
}
if (tokens.some((token) => ["socket", "password", "credential", "secret", "token", "dsn"].includes(token))) {
return "direct socket or raw credential field is not allowed";
}
return undefined;
}
function unsafeBridgeSchemaStringReasons(value: string): string[] {
const reasons = [...unsafeStringReasons(value)];
const trimmed = value.trim();
const fieldReason = unsafeBridgeSchemaFieldReason(trimmed);
if (fieldReason) {
reasons.push(fieldReason);
}
if (/\bselect\b[\s\S]{0,240}\bfrom\b/i.test(trimmed) || /\b(?:insert\s+into|update\s+[a-z0-9_.]+\s+set|delete\s+from|drop\s+table|alter\s+table|create\s+table|attach\s+database|pragma\s+[a-z0-9_]+)/i.test(trimmed)) {
reasons.push("arbitrary SQL content is not allowed");
}
if (/^\s*(?:sh|bash|zsh|powershell|pwsh)\s+-[a-z]*c\b/i.test(trimmed) || /^\s*cmd(?:\.exe)?\s+\/c\b/i.test(trimmed)) {
reasons.push("arbitrary shell content is not allowed");
}
if (/^(?:run|executor|shell|script|terminal)\.(?:socket|endpoint|exec|execute|command)$/i.test(trimmed)) {
reasons.push("unsafe executor capability is not allowed");
}
return [...new Set(reasons)];
}
function scanUnsafeBridgeSchema(value: unknown, location: string): string[] {
if (typeof value === "string") {
return unsafeBridgeSchemaStringReasons(value).map((reason) => `${location}: ${reason}`);
}
if (Array.isArray(value)) {
return value.flatMap((item, index) => scanUnsafeBridgeSchema(item, `${location}[${index}]`));
}
if (typeof value === "object" && value !== null) {
return Object.entries(value).flatMap(([key, child]) => {
const keyReason = unsafeBridgeSchemaFieldReason(key);
const keyErrors = keyReason ? [`${location}.${key}: ${keyReason}`] : [];
return [...keyErrors, ...scanUnsafeBridgeSchema(child, `${location}.${key}`)];
});
}
return [];
}
function validateBoundedBridgeSchema(value: unknown, location: string): string[] {
if (typeof value !== "object" || value === null || Array.isArray(value)) {
return [`${location}: bridge schema root must be an object schema`];
@@ -790,7 +720,6 @@ function validateGameClientBridgeSchemaFiles(manifest: unknown, manifestDir: str
const message = error instanceof Error ? error.message : "invalid JSON Schema";
errors.push(`${declaration.location}: bridge schema is invalid: ${message}`);
}
errors.push(...scanUnsafeBridgeSchema(schema, `${declaration.location}.schema`));
errors.push(...validateBoundedBridgeSchema(schema, `${declaration.location}.schema`));
}
return errors;