mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 05:30:43 +00:00
lint: replace proxy mutation guard with opengrep
This commit is contained in:
@@ -1433,7 +1433,6 @@
|
||||
"lint:swift": "swiftlint lint --config config/swiftlint.yml && (cd apps/ios && swiftlint lint --config .swiftlint.yml)",
|
||||
"lint:tmp:channel-agnostic-boundaries": "node scripts/check-channel-agnostic-boundaries.mjs",
|
||||
"lint:tmp:dynamic-import-warts": "node scripts/check-dynamic-import-warts.mjs",
|
||||
"lint:tmp:managed-proxy-runtime-mutation": "node scripts/check-managed-proxy-runtime-mutation.mjs",
|
||||
"lint:tmp:no-random-messaging": "node scripts/check-no-random-messaging-tmp.mjs",
|
||||
"lint:tmp:no-raw-channel-fetch": "node scripts/check-no-raw-channel-fetch.mjs",
|
||||
"lint:tmp:no-raw-http2-imports": "node scripts/check-no-raw-http2-imports.mjs",
|
||||
|
||||
@@ -1,504 +0,0 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import ts from "typescript";
|
||||
import { runCallsiteGuard } from "./lib/callsite-guard.mjs";
|
||||
import { runAsScript, toLine, unwrapExpression } from "./lib/ts-guard-utils.mjs";
|
||||
|
||||
const sourceRoots = ["src", "extensions"];
|
||||
|
||||
const allowedManagedProxyRuntimeMutationScopes = new Set([
|
||||
// Canonical managed proxy lifecycle owns process proxy env/global-agent mutation.
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#applyProxyEnv",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#restoreProxyEnv",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#restoreGlobalAgentRuntime",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#restoreNodeHttpStack",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#bootstrapNodeHttpStack",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#writeGlobalAgentNoProxy",
|
||||
"src/infra/net/proxy/proxy-lifecycle.ts#disableGlobalAgentProxyForIpv6GatewayLoopback",
|
||||
|
||||
// Browser CDP loopback helper leases NO_PROXY only for localhost/loopback CDP URLs.
|
||||
"extensions/browser/src/browser/cdp-proxy-bypass.ts#NoProxyLeaseManager.acquire",
|
||||
"extensions/browser/src/browser/cdp-proxy-bypass.ts#NoProxyLeaseManager.release",
|
||||
]);
|
||||
|
||||
const forbiddenEnvKeys = new Set([
|
||||
"HTTP_PROXY",
|
||||
"HTTPS_PROXY",
|
||||
"http_proxy",
|
||||
"https_proxy",
|
||||
"NO_PROXY",
|
||||
"no_proxy",
|
||||
"GLOBAL_AGENT_HTTP_PROXY",
|
||||
"GLOBAL_AGENT_HTTPS_PROXY",
|
||||
"GLOBAL_AGENT_NO_PROXY",
|
||||
"GLOBAL_AGENT_FORCE_GLOBAL_AGENT",
|
||||
"OPENCLAW_PROXY_ACTIVE",
|
||||
"OPENCLAW_PROXY_LOOPBACK_MODE",
|
||||
]);
|
||||
|
||||
const forbiddenGlobalAgentKeys = new Set(["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"]);
|
||||
|
||||
function stringLiteralText(node) {
|
||||
return ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node) ? node.text : null;
|
||||
}
|
||||
|
||||
function propertyNameText(name) {
|
||||
if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name)) {
|
||||
return name.text;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function qualifiedScopeName(name, classScopes) {
|
||||
const classScope = classScopes.at(-1);
|
||||
return classScope ? `${classScope}.${name}` : name;
|
||||
}
|
||||
|
||||
function functionExpressionScopeName(node, classScopes) {
|
||||
const parent = node.parent;
|
||||
if (ts.isVariableDeclaration(parent) && ts.isIdentifier(parent.name)) {
|
||||
return parent.name.text;
|
||||
}
|
||||
if (ts.isPropertyAssignment(parent)) {
|
||||
const name = propertyNameText(parent.name);
|
||||
return name ? qualifiedScopeName(name, classScopes) : null;
|
||||
}
|
||||
if (ts.isPropertyDeclaration(parent) && parent.name) {
|
||||
const name = propertyNameText(parent.name);
|
||||
return name ? qualifiedScopeName(name, classScopes) : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function scopeNameForNode(node, classScopes) {
|
||||
if (ts.isFunctionDeclaration(node) && node.name) {
|
||||
return node.name.text;
|
||||
}
|
||||
if (ts.isFunctionExpression(node) || ts.isArrowFunction(node)) {
|
||||
return functionExpressionScopeName(node, classScopes);
|
||||
}
|
||||
if (
|
||||
ts.isMethodDeclaration(node) ||
|
||||
ts.isGetAccessorDeclaration(node) ||
|
||||
ts.isSetAccessorDeclaration(node)
|
||||
) {
|
||||
const name = propertyNameText(node.name);
|
||||
return name ? qualifiedScopeName(name, classScopes) : null;
|
||||
}
|
||||
if (ts.isConstructorDeclaration(node)) {
|
||||
return qualifiedScopeName("constructor", classScopes);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function isGlobalIdentifier(node, context = { globalAliases: new Set() }) {
|
||||
const unwrapped = unwrapExpression(node);
|
||||
return (
|
||||
ts.isIdentifier(unwrapped) &&
|
||||
(unwrapped.text === "global" ||
|
||||
unwrapped.text === "globalThis" ||
|
||||
context.globalAliases.has(unwrapped.text))
|
||||
);
|
||||
}
|
||||
|
||||
function processEnvExpression(expression, context = { envAliases: new Set() }) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (ts.isIdentifier(unwrapped) && context.envAliases.has(unwrapped.text)) {
|
||||
return unwrapped;
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(unwrapped) && unwrapped.name.text === "env") {
|
||||
const base = unwrapExpression(unwrapped.expression);
|
||||
return ts.isIdentifier(base) && base.text === "process" ? unwrapped : null;
|
||||
}
|
||||
if (ts.isElementAccessExpression(unwrapped)) {
|
||||
const key = stringLiteralText(unwrapExpression(unwrapped.argumentExpression));
|
||||
if (key !== "env") {
|
||||
return null;
|
||||
}
|
||||
const base = unwrapExpression(unwrapped.expression);
|
||||
return ts.isIdentifier(base) && base.text === "process" ? unwrapped : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectStringConstants(sourceFile) {
|
||||
const constants = new Map();
|
||||
const visit = (node) => {
|
||||
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
||||
const literal = stringLiteralText(unwrapExpression(node.initializer));
|
||||
if (literal) {
|
||||
constants.set(node.name.text, literal);
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
return constants;
|
||||
}
|
||||
|
||||
function collectStringArrays(sourceFile) {
|
||||
const arrays = new Map();
|
||||
for (let pass = 0; pass < 4; pass += 1) {
|
||||
const visit = (node) => {
|
||||
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
||||
const initializer = unwrapExpression(node.initializer);
|
||||
if (ts.isArrayLiteralExpression(initializer)) {
|
||||
const values = [];
|
||||
let complete = true;
|
||||
for (const element of initializer.elements) {
|
||||
const unwrapped = unwrapExpression(element);
|
||||
if (
|
||||
ts.isSpreadElement(unwrapped) &&
|
||||
ts.isIdentifier(unwrapExpression(unwrapped.expression))
|
||||
) {
|
||||
const nested = arrays.get(unwrapExpression(unwrapped.expression).text);
|
||||
if (nested) {
|
||||
values.push(...nested);
|
||||
} else {
|
||||
complete = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const literal = stringLiteralText(unwrapped);
|
||||
if (literal) {
|
||||
values.push(literal);
|
||||
} else {
|
||||
complete = false;
|
||||
}
|
||||
}
|
||||
if (complete) {
|
||||
const previous = arrays.get(node.name.text);
|
||||
const sameValues = previous && previous.join("\0") === values.join("\0");
|
||||
if (!sameValues) {
|
||||
arrays.set(node.name.text, values);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
}
|
||||
return arrays;
|
||||
}
|
||||
|
||||
function collectForbiddenKeyArrays(sourceFile) {
|
||||
const stringArrays = collectStringArrays(sourceFile);
|
||||
const arrays = new Set();
|
||||
for (const [name, values] of stringArrays) {
|
||||
if (values.some((key) => forbiddenEnvKeys.has(key))) {
|
||||
arrays.add(name);
|
||||
}
|
||||
}
|
||||
return arrays;
|
||||
}
|
||||
|
||||
function collectForbiddenKeyVariables(sourceFile, forbiddenKeyArrays) {
|
||||
const variables = new Set();
|
||||
const visit = (node) => {
|
||||
if (ts.isForOfStatement(node)) {
|
||||
const expression = unwrapExpression(node.expression);
|
||||
if (ts.isIdentifier(expression) && forbiddenKeyArrays.has(expression.text)) {
|
||||
const initializer = node.initializer;
|
||||
if (ts.isVariableDeclarationList(initializer)) {
|
||||
for (const declaration of initializer.declarations) {
|
||||
if (ts.isIdentifier(declaration.name)) {
|
||||
variables.add(declaration.name.text);
|
||||
}
|
||||
}
|
||||
} else if (ts.isIdentifier(initializer)) {
|
||||
variables.add(initializer.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
return variables;
|
||||
}
|
||||
|
||||
function envKeyExpressionIsForbidden(argumentExpression, context) {
|
||||
const keyExpression = unwrapExpression(argumentExpression);
|
||||
const literalKey = stringLiteralText(keyExpression);
|
||||
if (literalKey) {
|
||||
return forbiddenEnvKeys.has(literalKey);
|
||||
}
|
||||
if (!ts.isIdentifier(keyExpression)) {
|
||||
return false;
|
||||
}
|
||||
if (context.forbiddenKeyVariables.has(keyExpression.text)) {
|
||||
return true;
|
||||
}
|
||||
const constant = context.stringConstants.get(keyExpression.text);
|
||||
return constant ? forbiddenEnvKeys.has(constant) : false;
|
||||
}
|
||||
|
||||
function envMutationTarget(expression, context) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (
|
||||
ts.isPropertyAccessExpression(unwrapped) &&
|
||||
processEnvExpression(unwrapped.expression, context)
|
||||
) {
|
||||
return forbiddenEnvKeys.has(unwrapped.name.text) ? unwrapped : null;
|
||||
}
|
||||
if (
|
||||
ts.isElementAccessExpression(unwrapped) &&
|
||||
processEnvExpression(unwrapped.expression, context)
|
||||
) {
|
||||
return envKeyExpressionIsForbidden(unwrapped.argumentExpression, context) ? unwrapped : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function globalAgentExpression(
|
||||
expression,
|
||||
context = { globalAgentAliases: new Set(), globalAliases: new Set() },
|
||||
) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (ts.isIdentifier(unwrapped) && context.globalAgentAliases.has(unwrapped.text)) {
|
||||
return unwrapped;
|
||||
}
|
||||
if (ts.isPropertyAccessExpression(unwrapped)) {
|
||||
const receiver = unwrapExpression(unwrapped.expression);
|
||||
if (unwrapped.name.text === "GLOBAL_AGENT" && isGlobalIdentifier(receiver, context)) {
|
||||
return unwrapped;
|
||||
}
|
||||
}
|
||||
if (ts.isElementAccessExpression(unwrapped)) {
|
||||
const receiver = unwrapExpression(unwrapped.expression);
|
||||
const key = stringLiteralText(unwrapExpression(unwrapped.argumentExpression));
|
||||
if (key === "GLOBAL_AGENT" && isGlobalIdentifier(receiver, context)) {
|
||||
return unwrapped;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function collectEnvAliases(sourceFile) {
|
||||
const aliases = new Set();
|
||||
const emptyContext = { envAliases: new Set() };
|
||||
const visit = (node) => {
|
||||
if (ts.isVariableDeclaration(node) && node.initializer) {
|
||||
if (ts.isIdentifier(node.name) && processEnvExpression(node.initializer, emptyContext)) {
|
||||
aliases.add(node.name.text);
|
||||
}
|
||||
if (ts.isObjectBindingPattern(node.name)) {
|
||||
const initializer = unwrapExpression(node.initializer);
|
||||
if (ts.isIdentifier(initializer) && initializer.text === "process") {
|
||||
for (const element of node.name.elements) {
|
||||
if (!ts.isIdentifier(element.name)) {
|
||||
continue;
|
||||
}
|
||||
const importedName =
|
||||
element.propertyName && ts.isIdentifier(element.propertyName)
|
||||
? element.propertyName.text
|
||||
: element.name.text;
|
||||
if (importedName === "env") {
|
||||
aliases.add(element.name.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function collectGlobalAliases(sourceFile) {
|
||||
const aliases = new Set();
|
||||
const visit = (node) => {
|
||||
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
||||
if (isGlobalIdentifier(node.initializer, { globalAliases: new Set() })) {
|
||||
aliases.add(node.name.text);
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function collectGlobalAgentAliases(sourceFile, globalAliases = collectGlobalAliases(sourceFile)) {
|
||||
const aliases = new Set();
|
||||
const emptyContext = { globalAgentAliases: new Set(), globalAliases };
|
||||
const visit = (node) => {
|
||||
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
|
||||
if (globalAgentExpression(node.initializer, emptyContext)) {
|
||||
aliases.add(node.name.text);
|
||||
}
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
};
|
||||
visit(sourceFile);
|
||||
return aliases;
|
||||
}
|
||||
|
||||
function globalAgentMutationTarget(expression, context) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (globalAgentExpression(unwrapped, context)) {
|
||||
return unwrapped;
|
||||
}
|
||||
if (
|
||||
ts.isPropertyAccessExpression(unwrapped) &&
|
||||
globalAgentExpression(unwrapped.expression, context)
|
||||
) {
|
||||
return forbiddenGlobalAgentKeys.has(unwrapped.name.text) ? unwrapped : null;
|
||||
}
|
||||
if (
|
||||
ts.isElementAccessExpression(unwrapped) &&
|
||||
globalAgentExpression(unwrapped.expression, context)
|
||||
) {
|
||||
const key = stringLiteralText(unwrapExpression(unwrapped.argumentExpression));
|
||||
return key && forbiddenGlobalAgentKeys.has(key) ? unwrapped : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function mutationTarget(expression, context) {
|
||||
return envMutationTarget(expression, context) ?? globalAgentMutationTarget(expression, context);
|
||||
}
|
||||
|
||||
function deleteTarget(expression, context) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
return ts.isDeleteExpression(unwrapped) ? mutationTarget(unwrapped.expression, context) : null;
|
||||
}
|
||||
|
||||
function assignmentTarget(expression, context) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (ts.isBinaryExpression(unwrapped) && ts.isAssignmentOperator(unwrapped.operatorToken.kind)) {
|
||||
return mutationTarget(unwrapped.left, context);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function mutatingCallTarget(expression, context) {
|
||||
const unwrapped = unwrapExpression(expression);
|
||||
if (!ts.isCallExpression(unwrapped)) {
|
||||
return null;
|
||||
}
|
||||
const callee = unwrapExpression(unwrapped.expression);
|
||||
if (!ts.isPropertyAccessExpression(callee)) {
|
||||
return null;
|
||||
}
|
||||
const method = callee.name.text;
|
||||
if (method !== "defineProperty" && method !== "assign") {
|
||||
return null;
|
||||
}
|
||||
const receiver = unwrapExpression(callee.expression);
|
||||
if (!ts.isIdentifier(receiver) || receiver.text !== "Object") {
|
||||
return null;
|
||||
}
|
||||
const first = unwrapped.arguments[0] ? unwrapExpression(unwrapped.arguments[0]) : null;
|
||||
if (!first) {
|
||||
return null;
|
||||
}
|
||||
if (method === "assign") {
|
||||
return globalAgentExpression(first, context) ?? processEnvExpression(first, context);
|
||||
}
|
||||
const rawKeyArg = unwrapped.arguments[1] ? unwrapExpression(unwrapped.arguments[1]) : null;
|
||||
const literalKeyArg = rawKeyArg ? stringLiteralText(rawKeyArg) : null;
|
||||
const keyArg =
|
||||
literalKeyArg ??
|
||||
(rawKeyArg && ts.isIdentifier(rawKeyArg) ? context.stringConstants.get(rawKeyArg.text) : null);
|
||||
if (keyArg && processEnvExpression(first, context)) {
|
||||
return forbiddenEnvKeys.has(keyArg) ? first : null;
|
||||
}
|
||||
if (keyArg && globalAgentExpression(first, context)) {
|
||||
return forbiddenGlobalAgentKeys.has(keyArg) ? first : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findManagedProxyRuntimeMutations(content, fileName = "source.ts") {
|
||||
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
|
||||
const globalAliases = collectGlobalAliases(sourceFile);
|
||||
const context = {
|
||||
forbiddenKeyVariables: collectForbiddenKeyVariables(
|
||||
sourceFile,
|
||||
collectForbiddenKeyArrays(sourceFile),
|
||||
),
|
||||
globalAgentAliases: collectGlobalAgentAliases(sourceFile, globalAliases),
|
||||
globalAliases,
|
||||
envAliases: collectEnvAliases(sourceFile),
|
||||
stringConstants: collectStringConstants(sourceFile),
|
||||
};
|
||||
const mutations = [];
|
||||
const classScopes = [];
|
||||
const scopeStack = [];
|
||||
const visit = (node) => {
|
||||
let pushedClass = false;
|
||||
let pushedScope = false;
|
||||
if (ts.isClassDeclaration(node) && node.name) {
|
||||
classScopes.push(node.name.text);
|
||||
pushedClass = true;
|
||||
}
|
||||
const scopeName = scopeNameForNode(node, classScopes);
|
||||
if (scopeName) {
|
||||
scopeStack.push(scopeName);
|
||||
pushedScope = true;
|
||||
}
|
||||
|
||||
const match =
|
||||
assignmentTarget(node, context) ??
|
||||
deleteTarget(node, context) ??
|
||||
mutatingCallTarget(node, context);
|
||||
if (match) {
|
||||
mutations.push({
|
||||
line: toLine(sourceFile, match),
|
||||
scope: scopeStack.at(-1) ?? null,
|
||||
});
|
||||
}
|
||||
ts.forEachChild(node, visit);
|
||||
|
||||
if (pushedScope) {
|
||||
scopeStack.pop();
|
||||
}
|
||||
if (pushedClass) {
|
||||
classScopes.pop();
|
||||
}
|
||||
};
|
||||
visit(sourceFile);
|
||||
return mutations;
|
||||
}
|
||||
|
||||
export function findManagedProxyRuntimeMutationLines(content, fileName = "source.ts") {
|
||||
return findManagedProxyRuntimeMutations(content, fileName).map((mutation) => mutation.line);
|
||||
}
|
||||
|
||||
export function isAllowedManagedProxyRuntimeMutation(violation) {
|
||||
if (!violation.scope) {
|
||||
return false;
|
||||
}
|
||||
return allowedManagedProxyRuntimeMutationScopes.has(
|
||||
`${violation.relativePath}#${violation.scope}`,
|
||||
);
|
||||
}
|
||||
|
||||
function formatManagedProxyRuntimeMutationCallsite(violation) {
|
||||
const scope = violation.scope ? ` (${violation.scope})` : "";
|
||||
return `${violation.relativePath}:${violation.line}${scope}`;
|
||||
}
|
||||
|
||||
export async function main() {
|
||||
await runCallsiteGuard({
|
||||
importMetaUrl: import.meta.url,
|
||||
sourceRoots,
|
||||
extraTestSuffixes: [
|
||||
".browser.test.ts",
|
||||
".node.test.ts",
|
||||
".live.test.ts",
|
||||
".e2e.test.ts",
|
||||
".integration.test.ts",
|
||||
],
|
||||
findCallViolations: findManagedProxyRuntimeMutations,
|
||||
allowViolation: isAllowedManagedProxyRuntimeMutation,
|
||||
formatViolation: formatManagedProxyRuntimeMutationCallsite,
|
||||
header: "Found unmanaged managed-proxy runtime mutation:",
|
||||
footer:
|
||||
"Only proxy lifecycle code may mutate GLOBAL_AGENT or proxy-related process.env runtime state.",
|
||||
});
|
||||
}
|
||||
|
||||
runAsScript(import.meta.url, main);
|
||||
@@ -6,19 +6,6 @@ import {
|
||||
resolveSourceRoots,
|
||||
} from "./ts-guard-utils.mjs";
|
||||
|
||||
function normalizeViolation(rawViolation, relPath) {
|
||||
if (typeof rawViolation === "number") {
|
||||
return {
|
||||
line: rawViolation,
|
||||
callsite: `${relPath}:${rawViolation}`,
|
||||
};
|
||||
}
|
||||
return {
|
||||
...rawViolation,
|
||||
callsite: rawViolation.callsite ?? `${relPath}:${rawViolation.line}`,
|
||||
};
|
||||
}
|
||||
|
||||
export async function runCallsiteGuard(params) {
|
||||
const repoRoot = resolveRepoRoot(params.importMetaUrl);
|
||||
const sourceRoots = resolveSourceRoots(repoRoot, params.sourceRoots);
|
||||
@@ -33,30 +20,12 @@ export async function runCallsiteGuard(params) {
|
||||
continue;
|
||||
}
|
||||
const content = await fs.readFile(filePath, "utf8");
|
||||
const rawViolations = params.findCallViolations
|
||||
? params.findCallViolations(content, filePath)
|
||||
: params.findCallLines(content, filePath);
|
||||
for (const rawViolation of rawViolations) {
|
||||
const violation = normalizeViolation(rawViolation, relPath);
|
||||
if (
|
||||
params.allowViolation?.({
|
||||
...violation,
|
||||
relativePath: relPath,
|
||||
filePath,
|
||||
}) ??
|
||||
params.allowCallsite?.(violation.callsite, violation)
|
||||
) {
|
||||
for (const line of params.findCallLines(content, filePath)) {
|
||||
const callsite = `${relPath}:${line}`;
|
||||
if (params.allowCallsite?.(callsite)) {
|
||||
continue;
|
||||
}
|
||||
violations.push(
|
||||
params.formatViolation
|
||||
? params.formatViolation({
|
||||
...violation,
|
||||
relativePath: relPath,
|
||||
filePath,
|
||||
})
|
||||
: violation.callsite,
|
||||
);
|
||||
violations.push(callsite);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -10,11 +10,6 @@ export const BOUNDARY_CHECKS = [
|
||||
["lint:tmp:tsgo-core-boundary", "pnpm", ["run", "lint:tmp:tsgo-core-boundary"]],
|
||||
["lint:tmp:no-raw-channel-fetch", "pnpm", ["run", "lint:tmp:no-raw-channel-fetch"]],
|
||||
["lint:tmp:no-raw-http2-imports", "pnpm", ["run", "lint:tmp:no-raw-http2-imports"]],
|
||||
[
|
||||
"lint:tmp:managed-proxy-runtime-mutation",
|
||||
"pnpm",
|
||||
["run", "lint:tmp:managed-proxy-runtime-mutation"],
|
||||
],
|
||||
["lint:tmp:raw-socket-classification", "pnpm", ["run", "lint:tmp:raw-socket-classification"]],
|
||||
["lint:agent:ingress-owner", "pnpm", ["run", "lint:agent:ingress-owner"]],
|
||||
[
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
# DO NOT EDIT BY HAND. Re-run the compile script after editing source rules.
|
||||
#
|
||||
# Source rules dir: security/opengrep/rules/openclaw-policy
|
||||
# Generated at : 2026-04-30T09:09:41.198Z
|
||||
# Rule count : 148
|
||||
# Generated at : 2026-05-07T04:40:02.803Z
|
||||
# Rule count : 154
|
||||
rules:
|
||||
- id: ghsa-25gx-x37c-7pph.openclaw-novnc-x11vnc-missing-auth
|
||||
message: x11vnc starts without VNC authentication; avoid -nopw and require password auth when exposing noVNC observer access.
|
||||
@@ -5010,3 +5010,513 @@ rules:
|
||||
- "**/*.test.mjs"
|
||||
patterns:
|
||||
- pattern: http2.connect(...)
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-process-env-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate proxy-related process.env runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-process-env-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: process.env.$KEY = ...
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env.$KEY
|
||||
- pattern: delete process.env[$KEY]
|
||||
- pattern: Object.defineProperty(process.env, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(process.env, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-process-env-alias-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate proxy-related process.env aliases.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-process-env-alias-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $ENV = process.env;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $ENV.$KEY = ...
|
||||
- pattern: $ENV[$KEY] = ...
|
||||
- pattern: delete $ENV.$KEY
|
||||
- pattern: delete $ENV[$KEY]
|
||||
- pattern: Object.defineProperty($ENV, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($ENV, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const { env } = process;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: env.$KEY = ...
|
||||
- pattern: env[$KEY] = ...
|
||||
- pattern: delete env.$KEY
|
||||
- pattern: delete env[$KEY]
|
||||
- pattern: Object.defineProperty(env, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(env, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-process-env-dynamic-key-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate process.env through proxy-related dynamic keys.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-process-env-dynamic-key-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env[$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $SOURCE_KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
const $KEYS = [..., ...$SOURCE_KEYS, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env[$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $ENV = process.env;
|
||||
...
|
||||
const $KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: $ENV[$KEY] = ...
|
||||
- pattern: delete $ENV[$KEY]
|
||||
- metavariable-regex:
|
||||
metavariable: $FORBIDDEN
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-global-agent-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate GLOBAL_AGENT proxy runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-global-agent-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: global.GLOBAL_AGENT = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT = ...
|
||||
- pattern: global["GLOBAL_AGENT"] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"] = ...
|
||||
- pattern: global.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: global.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: global["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete global.GLOBAL_AGENT
|
||||
- pattern: delete globalThis.GLOBAL_AGENT
|
||||
- pattern: delete global["GLOBAL_AGENT"]
|
||||
- pattern: delete globalThis["GLOBAL_AGENT"]
|
||||
- pattern: delete global.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete global.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete globalThis.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete globalThis.GLOBAL_AGENT[$KEY]
|
||||
- pattern: Object.defineProperty(global.GLOBAL_AGENT, $KEY, ...)
|
||||
- pattern: Object.defineProperty(globalThis.GLOBAL_AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(global.GLOBAL_AGENT, { $KEY: ... })
|
||||
- pattern: |
|
||||
Object.assign(globalThis.GLOBAL_AGENT, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)["']?$
|
||||
- pattern-not-inside: |
|
||||
function restoreGlobalAgentRuntime(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function bootstrapNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function writeGlobalAgentNoProxy(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function disableGlobalAgentProxyForIpv6GatewayLoopback(...) {
|
||||
...
|
||||
}
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-global-agent-object-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may replace or delete GLOBAL_AGENT runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-global-agent-object-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: global.GLOBAL_AGENT = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT = ...
|
||||
- pattern: global["GLOBAL_AGENT"] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete global.GLOBAL_AGENT
|
||||
- pattern: delete globalThis.GLOBAL_AGENT
|
||||
- pattern: delete global["GLOBAL_AGENT"]
|
||||
- pattern: delete globalThis["GLOBAL_AGENT"]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global as $TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- id: openclaw-policy-managed-proxy-runtime-mutation.managed-proxy-global-agent-alias-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate GLOBAL_AGENT aliases.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- CWE-441
|
||||
category: security
|
||||
confidence: HIGH
|
||||
detector-bucket: precise
|
||||
source-rule-id: managed-proxy-global-agent-alias-mutation
|
||||
source-file: security/opengrep/rules/openclaw-policy/managed-proxy-runtime-mutation.yml
|
||||
paths:
|
||||
include:
|
||||
- src/**/*.ts
|
||||
- src/**/*.mts
|
||||
- src/**/*.js
|
||||
- src/**/*.mjs
|
||||
- extensions/**/*.ts
|
||||
- extensions/**/*.mts
|
||||
- extensions/**/*.js
|
||||
- extensions/**/*.mjs
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = global.GLOBAL_AGENT;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = global["GLOBAL_AGENT"];
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = (global as $TYPE)["GLOBAL_AGENT"] as $AGENT_TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"][$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global as $TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"][$KEY]
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)["']?$
|
||||
- pattern-not-inside: |
|
||||
function restoreGlobalAgentRuntime(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function bootstrapNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function writeGlobalAgentNoProxy(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function disableGlobalAgentProxyForIpv6GatewayLoopback(...) {
|
||||
...
|
||||
}
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
rules:
|
||||
- id: managed-proxy-process-env-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate proxy-related process.env runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: process.env.$KEY = ...
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env.$KEY
|
||||
- pattern: delete process.env[$KEY]
|
||||
- pattern: Object.defineProperty(process.env, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(process.env, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
- id: managed-proxy-process-env-alias-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate proxy-related process.env aliases.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $ENV = process.env;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $ENV.$KEY = ...
|
||||
- pattern: $ENV[$KEY] = ...
|
||||
- pattern: delete $ENV.$KEY
|
||||
- pattern: delete $ENV[$KEY]
|
||||
- pattern: Object.defineProperty($ENV, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($ENV, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const { env } = process;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: env.$KEY = ...
|
||||
- pattern: env[$KEY] = ...
|
||||
- pattern: delete env.$KEY
|
||||
- pattern: delete env[$KEY]
|
||||
- pattern: Object.defineProperty(env, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(env, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
- id: managed-proxy-process-env-dynamic-key-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate process.env through proxy-related dynamic keys.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env[$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $SOURCE_KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
const $KEYS = [..., ...$SOURCE_KEYS, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: process.env[$KEY] = ...
|
||||
- pattern: delete process.env[$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $ENV = process.env;
|
||||
...
|
||||
const $KEYS = [..., $FORBIDDEN, ...];
|
||||
...
|
||||
for (const $KEY of $KEYS) {
|
||||
...
|
||||
}
|
||||
- pattern-either:
|
||||
- pattern: $ENV[$KEY] = ...
|
||||
- pattern: delete $ENV[$KEY]
|
||||
- metavariable-regex:
|
||||
metavariable: $FORBIDDEN
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|http_proxy|https_proxy|NO_PROXY|no_proxy|GLOBAL_AGENT_HTTP_PROXY|GLOBAL_AGENT_HTTPS_PROXY|GLOBAL_AGENT_NO_PROXY|GLOBAL_AGENT_FORCE_GLOBAL_AGENT|OPENCLAW_PROXY_ACTIVE|OPENCLAW_PROXY_LOOPBACK_MODE)["']?$
|
||||
- pattern-not-inside: |
|
||||
function applyProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreProxyEnv(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
acquire(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
class NoProxyLeaseManager {
|
||||
...
|
||||
release(...) {
|
||||
...
|
||||
}
|
||||
...
|
||||
}
|
||||
|
||||
- id: managed-proxy-global-agent-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate GLOBAL_AGENT proxy runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: global.GLOBAL_AGENT = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT = ...
|
||||
- pattern: global["GLOBAL_AGENT"] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"] = ...
|
||||
- pattern: global.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: global.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: global["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete global.GLOBAL_AGENT
|
||||
- pattern: delete globalThis.GLOBAL_AGENT
|
||||
- pattern: delete global["GLOBAL_AGENT"]
|
||||
- pattern: delete globalThis["GLOBAL_AGENT"]
|
||||
- pattern: delete global.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete global.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete globalThis.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete globalThis.GLOBAL_AGENT[$KEY]
|
||||
- pattern: Object.defineProperty(global.GLOBAL_AGENT, $KEY, ...)
|
||||
- pattern: Object.defineProperty(globalThis.GLOBAL_AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign(global.GLOBAL_AGENT, { $KEY: ... })
|
||||
- pattern: |
|
||||
Object.assign(globalThis.GLOBAL_AGENT, { $KEY: ... })
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)["']?$
|
||||
- pattern-not-inside: |
|
||||
function restoreGlobalAgentRuntime(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function bootstrapNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function writeGlobalAgentNoProxy(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function disableGlobalAgentProxyForIpv6GatewayLoopback(...) {
|
||||
...
|
||||
}
|
||||
|
||||
- id: managed-proxy-global-agent-object-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may replace or delete GLOBAL_AGENT runtime state.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- pattern: global.GLOBAL_AGENT = ...
|
||||
- pattern: globalThis.GLOBAL_AGENT = ...
|
||||
- pattern: global["GLOBAL_AGENT"] = ...
|
||||
- pattern: globalThis["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete global.GLOBAL_AGENT
|
||||
- pattern: delete globalThis.GLOBAL_AGENT
|
||||
- pattern: delete global["GLOBAL_AGENT"]
|
||||
- pattern: delete globalThis["GLOBAL_AGENT"]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global as $TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
|
||||
- id: managed-proxy-global-agent-alias-mutation
|
||||
languages:
|
||||
- typescript
|
||||
- javascript
|
||||
severity: ERROR
|
||||
message: Only managed proxy owner scopes may mutate GLOBAL_AGENT aliases.
|
||||
metadata:
|
||||
advisory-id: OPENCLAW-POLICY-MANAGED-PROXY-RUNTIME-MUTATION
|
||||
advisory-url: https://github.com/openclaw/openclaw/pull/77126
|
||||
cwe:
|
||||
- "CWE-441"
|
||||
category: security
|
||||
confidence: HIGH
|
||||
paths:
|
||||
include:
|
||||
- "src/**/*.ts"
|
||||
- "src/**/*.mts"
|
||||
- "src/**/*.js"
|
||||
- "src/**/*.mjs"
|
||||
- "extensions/**/*.ts"
|
||||
- "extensions/**/*.mts"
|
||||
- "extensions/**/*.js"
|
||||
- "extensions/**/*.mjs"
|
||||
exclude:
|
||||
- "**/*.test.*"
|
||||
- "**/*.spec.*"
|
||||
- "**/*.min.js"
|
||||
- "**/vendor/**"
|
||||
patterns:
|
||||
- pattern-either:
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = global.GLOBAL_AGENT;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = global["GLOBAL_AGENT"];
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $AGENT = (global as $TYPE)["GLOBAL_AGENT"] as $AGENT_TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $AGENT.$KEY = ...
|
||||
- pattern: $AGENT[$KEY] = ...
|
||||
- pattern: delete $AGENT.$KEY
|
||||
- pattern: delete $AGENT[$KEY]
|
||||
- pattern: Object.defineProperty($AGENT, $KEY, ...)
|
||||
- pattern: |
|
||||
Object.assign($AGENT, { $KEY: ... })
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"][$KEY]
|
||||
- patterns:
|
||||
- pattern-inside: |
|
||||
const $GLOBAL = global as $TYPE;
|
||||
...
|
||||
- pattern-either:
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"] = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT.$KEY = ...
|
||||
- pattern: $GLOBAL.GLOBAL_AGENT[$KEY] = ...
|
||||
- pattern: $GLOBAL["GLOBAL_AGENT"][$KEY] = ...
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"]
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT.$KEY
|
||||
- pattern: delete $GLOBAL.GLOBAL_AGENT[$KEY]
|
||||
- pattern: delete $GLOBAL["GLOBAL_AGENT"][$KEY]
|
||||
- metavariable-regex:
|
||||
metavariable: $KEY
|
||||
regex: ^["']?(HTTP_PROXY|HTTPS_PROXY|NO_PROXY)["']?$
|
||||
- pattern-not-inside: |
|
||||
function restoreGlobalAgentRuntime(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function restoreNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function bootstrapNodeHttpStack(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function writeGlobalAgentNoProxy(...) {
|
||||
...
|
||||
}
|
||||
- pattern-not-inside: |
|
||||
function disableGlobalAgentProxyForIpv6GatewayLoopback(...) {
|
||||
...
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import {
|
||||
findManagedProxyRuntimeMutationLines,
|
||||
findManagedProxyRuntimeMutations,
|
||||
isAllowedManagedProxyRuntimeMutation,
|
||||
} from "../../scripts/check-managed-proxy-runtime-mutation.mjs";
|
||||
|
||||
describe("check-managed-proxy-runtime-mutation", () => {
|
||||
it("finds assignments and deletes for proxy env vars", () => {
|
||||
const source = `
|
||||
process.env.HTTP_PROXY = "http://proxy";
|
||||
process.env["HTTPS_PROXY"] = "http://proxy";
|
||||
delete process.env.NO_PROXY;
|
||||
delete process.env["GLOBAL_AGENT_NO_PROXY"];
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it("finds global object alias GLOBAL_AGENT mutations", () => {
|
||||
const source = `
|
||||
const globalRecord = global;
|
||||
const agent = globalRecord.GLOBAL_AGENT;
|
||||
globalRecord.GLOBAL_AGENT = {};
|
||||
globalRecord["GLOBAL_AGENT"] = {};
|
||||
delete globalRecord.GLOBAL_AGENT;
|
||||
delete globalRecord["GLOBAL_AGENT"];
|
||||
agent.HTTP_PROXY = "http://proxy";
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5, 6, 7, 8]);
|
||||
});
|
||||
|
||||
it("finds GLOBAL_AGENT mutations", () => {
|
||||
const source = `
|
||||
global.GLOBAL_AGENT = {};
|
||||
global.GLOBAL_AGENT.NO_PROXY = "localhost";
|
||||
global["GLOBAL_AGENT"].HTTP_PROXY = "http://proxy";
|
||||
delete global.GLOBAL_AGENT.HTTPS_PROXY;
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it("finds Object.assign and Object.defineProperty mutations", () => {
|
||||
const source = `
|
||||
Object.assign(global.GLOBAL_AGENT, { NO_PROXY: "localhost" });
|
||||
Object.assign(process.env, { NO_PROXY: "localhost" });
|
||||
Object.defineProperty(process.env, "HTTP_PROXY", { value: "http://proxy" });
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
it("finds missing managed-proxy env key mutations", () => {
|
||||
const source = `
|
||||
process.env.GLOBAL_AGENT_FORCE_GLOBAL_AGENT = "true";
|
||||
process.env.OPENCLAW_PROXY_LOOPBACK_MODE = "gateway-only";
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3]);
|
||||
});
|
||||
|
||||
it("finds defineProperty mutations with constant proxy keys", () => {
|
||||
const source = `
|
||||
const proxyKey = "HTTP_PROXY";
|
||||
const agentKey = "NO_PROXY";
|
||||
Object.defineProperty(process.env, proxyKey, { value: "http://proxy" });
|
||||
Object.defineProperty(global.GLOBAL_AGENT, agentKey, { value: "localhost" });
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5]);
|
||||
});
|
||||
|
||||
it("finds destructured process.env alias mutations", () => {
|
||||
const source = `
|
||||
const { env } = process;
|
||||
env.HTTP_PROXY = "http://proxy";
|
||||
env["NO_PROXY"] = "localhost";
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([3, 4]);
|
||||
});
|
||||
|
||||
it("finds process.env alias and constant key mutations", () => {
|
||||
const source = `
|
||||
const env = process.env;
|
||||
const proxyKey = "HTTP_PROXY";
|
||||
env.HTTPS_PROXY = "http://proxy";
|
||||
env[proxyKey] = "http://proxy";
|
||||
delete env.NO_PROXY;
|
||||
Object.assign(env, { GLOBAL_AGENT_HTTP_PROXY: "http://proxy" });
|
||||
Object.defineProperty(env, "OPENCLAW_PROXY_ACTIVE", { value: "1" });
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 5, 6, 7, 8]);
|
||||
});
|
||||
|
||||
it("finds dynamic process.env key mutations from forbidden key arrays", () => {
|
||||
const source = `
|
||||
const proxyKeys = ["HTTP_PROXY", "HTTPS_PROXY", "NO_PROXY"];
|
||||
for (const key of proxyKeys) {
|
||||
process.env[key] = "http://proxy";
|
||||
}
|
||||
for (const key of proxyKeys) {
|
||||
delete process.env[key];
|
||||
}
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([4, 7]);
|
||||
});
|
||||
|
||||
it("finds dynamic process.env key mutations from spread-built forbidden key arrays", () => {
|
||||
const source = `
|
||||
const lower = ["http_proxy", "https_proxy"];
|
||||
const upper = ["HTTP_PROXY", "HTTPS_PROXY"];
|
||||
const all = [...lower, ...upper, "OPENCLAW_PROXY_LOOPBACK_MODE"];
|
||||
for (const key of all) {
|
||||
delete process.env[key];
|
||||
}
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([6]);
|
||||
});
|
||||
|
||||
it("ignores dynamic process.env key mutations from unrelated key arrays", () => {
|
||||
const source = `
|
||||
const normalKeys = ["PATH", "HOME"];
|
||||
for (const key of normalKeys) {
|
||||
process.env[key] = "value";
|
||||
}
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]);
|
||||
});
|
||||
|
||||
it("finds GLOBAL_AGENT alias mutations", () => {
|
||||
const source = `
|
||||
const agent = global.GLOBAL_AGENT;
|
||||
agent.HTTP_PROXY = "http://proxy";
|
||||
agent["NO_PROXY"] = "localhost";
|
||||
delete agent.HTTPS_PROXY;
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([3, 4, 5]);
|
||||
});
|
||||
|
||||
it("finds globalThis.GLOBAL_AGENT mutations alongside global.GLOBAL_AGENT", () => {
|
||||
const source = `
|
||||
globalThis.GLOBAL_AGENT = {};
|
||||
globalThis.GLOBAL_AGENT.NO_PROXY = "localhost";
|
||||
globalThis["GLOBAL_AGENT"].HTTP_PROXY = "http://proxy";
|
||||
delete globalThis.GLOBAL_AGENT.HTTPS_PROXY;
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4, 5]);
|
||||
});
|
||||
|
||||
it('finds process["env"] mixed access mutations', () => {
|
||||
const source = `
|
||||
process["env"].HTTP_PROXY = "http://proxy";
|
||||
process["env"]["HTTPS_PROXY"] = "http://proxy";
|
||||
delete process["env"].NO_PROXY;
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([2, 3, 4]);
|
||||
});
|
||||
|
||||
it("does not flag Object.assign on a non-process .env namespace", () => {
|
||||
const source = `
|
||||
Object.assign(config.env, { NO_PROXY: "localhost" });
|
||||
Object.defineProperty(config.env, "HTTP_PROXY", { value: "http://proxy" });
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]);
|
||||
});
|
||||
|
||||
it("ignores reads, unrelated env vars, comments, and strings", () => {
|
||||
const source = `
|
||||
const current = process.env.HTTP_PROXY;
|
||||
process.env.PATH = "/usr/bin";
|
||||
const text = "process.env.NO_PROXY = '*'";
|
||||
// global.GLOBAL_AGENT.NO_PROXY = '*';
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutationLines(source)).toEqual([]);
|
||||
});
|
||||
|
||||
it("reports the enclosing owner scope for each mutation", () => {
|
||||
const source = `
|
||||
function applyProxyEnv() {
|
||||
process.env.HTTP_PROXY = "http://proxy";
|
||||
}
|
||||
|
||||
class NoProxyLeaseManager {
|
||||
release() {
|
||||
delete process.env.NO_PROXY;
|
||||
}
|
||||
}
|
||||
|
||||
const updateProxy = () => {
|
||||
global.GLOBAL_AGENT.NO_PROXY = "localhost";
|
||||
};
|
||||
`;
|
||||
|
||||
expect(findManagedProxyRuntimeMutations(source)).toEqual([
|
||||
{ line: 3, scope: "applyProxyEnv" },
|
||||
{ line: 8, scope: "NoProxyLeaseManager.release" },
|
||||
{ line: 13, scope: "updateProxy" },
|
||||
]);
|
||||
});
|
||||
|
||||
it("allows approved owner scopes without exact line allowlists", () => {
|
||||
expect(
|
||||
isAllowedManagedProxyRuntimeMutation({
|
||||
relativePath: "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
scope: "applyProxyEnv",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isAllowedManagedProxyRuntimeMutation({
|
||||
relativePath: "extensions/browser/src/browser/cdp-proxy-bypass.ts",
|
||||
scope: "NoProxyLeaseManager.release",
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
isAllowedManagedProxyRuntimeMutation({
|
||||
relativePath: "src/infra/net/proxy/proxy-lifecycle.ts",
|
||||
scope: "startProxy",
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@@ -30,14 +30,6 @@ describe("run-additional-boundary-checks", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("runs managed proxy runtime mutation guard in CI", () => {
|
||||
expect(BOUNDARY_CHECKS).toContainEqual({
|
||||
label: "lint:tmp:managed-proxy-runtime-mutation",
|
||||
command: "pnpm",
|
||||
args: ["run", "lint:tmp:managed-proxy-runtime-mutation"],
|
||||
});
|
||||
});
|
||||
|
||||
it("runs raw socket classification guard in CI", () => {
|
||||
expect(BOUNDARY_CHECKS).toContainEqual({
|
||||
label: "lint:tmp:raw-socket-classification",
|
||||
|
||||
Reference in New Issue
Block a user