mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
c70aee247e
* refactor(scripts): migrate JavaScript tools to TypeScript * fix(ci): keep changed-scope preflight zero-install * fix(ci): preserve zero-install script owners * fix(ci): complete script migration follow-through * fix(release): keep stable closeout zero-install * fix(scripts): preserve standalone execution boundaries * fix(scripts): repair standalone loader boundaries * fix(scripts): normalize gateway observation ids * fix(scripts): keep Docker packager standalone * test(scripts): preserve rebase cleanup helpers * test(sessions): use tracked temp directory
69 lines
2.4 KiB
TypeScript
69 lines
2.4 KiB
TypeScript
#!/usr/bin/env node
|
|
|
|
// Ensures webhook handlers authenticate before reading request bodies.
|
|
import path from "node:path";
|
|
import ts from "typescript";
|
|
import { bundledPluginCallsite, bundledPluginFile } from "./lib/bundled-plugin-paths.mjs";
|
|
import { runCallsiteGuard } from "./lib/callsite-guard.mts";
|
|
import { runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mts";
|
|
|
|
const sourceRoots = ["extensions"];
|
|
const enforcedFiles = new Set([
|
|
bundledPluginFile("feishu", "src/monitor.transport.ts"),
|
|
bundledPluginFile("googlechat", "src/monitor.ts"),
|
|
bundledPluginFile("zalo", "src/monitor.webhook.ts"),
|
|
]);
|
|
const blockedCallees = new Set(["readJsonBodyWithLimit", "readRequestBodyWithLimit"]);
|
|
const allowedCallsites = new Set([
|
|
// Feishu signs the exact wire body, so this handler must read raw bytes before parsing JSON.
|
|
bundledPluginCallsite("feishu", "src/monitor.transport.ts", 199),
|
|
]);
|
|
|
|
function getCalleeName(expression: ts.Expression): string | null {
|
|
const callee = unwrapExpression(expression);
|
|
if (ts.isIdentifier(callee)) {
|
|
return callee.text;
|
|
}
|
|
if (ts.isPropertyAccessExpression(callee)) {
|
|
return callee.name.text;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Finds request body reads that occur before webhook auth validation.
|
|
*/
|
|
function findBlockedWebhookBodyReadLines(content: string, fileName = "source.ts"): number[] {
|
|
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
|
const lines: number[] = [];
|
|
const visit = (node: ts.Node): void => {
|
|
if (ts.isCallExpression(node)) {
|
|
const calleeName = getCalleeName(node.expression);
|
|
if (calleeName && blockedCallees.has(calleeName)) {
|
|
lines.push(toLine(sourceFile, node.expression));
|
|
}
|
|
}
|
|
ts.forEachChild(node, visit);
|
|
};
|
|
visit(sourceFile);
|
|
return lines;
|
|
}
|
|
|
|
/**
|
|
* Runs the webhook auth/body-order guard.
|
|
*/
|
|
async function main() {
|
|
await runCallsiteGuard({
|
|
importMetaUrl: import.meta.url,
|
|
sourceRoots,
|
|
findCallLines: findBlockedWebhookBodyReadLines,
|
|
skipRelativePath: (relPath) => !enforcedFiles.has(relPath.replaceAll(path.sep, "/")),
|
|
allowCallsite: (callsite) => allowedCallsites.has(callsite),
|
|
header: "Found forbidden low-level body reads in auth-sensitive webhook handlers:",
|
|
footer:
|
|
"Use plugin-sdk webhook guards (`readJsonWebhookBodyOrReject` / `readWebhookBodyOrReject`) with explicit pre-auth/post-auth profiles.",
|
|
});
|
|
}
|
|
|
|
runAsScript(import.meta.url, main);
|