Files
tutti/tools/scripts/check-http-client-funnel.mjs
agent-radar 2ed3415fb7
Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
源码快照 @ 2026-09-01 (source-only, 剔除非源码素材)
2026-09-01 20:17:27 +08:00

65 lines
2.2 KiB
JavaScript

// Guards the outbound-HTTP funnel: Go code must build clients through
// packages/agent/daemon/httpx (proxy-aware) instead of bare http.Client
// literals, which silently bypass the user's proxy configuration. forbidigo
// already rejects http.DefaultClient and http.Get/Post/...; this script covers
// the composite-literal case forbidigo cannot express.
//
// Exemptions: the httpx package itself, _test.go files, and lines carrying a
// `proxy-funnel-exempt: <reason>` comment.
import { readdirSync, readFileSync } from "node:fs";
import { dirname, join, relative } from "node:path";
import { fileURLToPath } from "node:url";
const workspaceRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
const scanRoots = [
"services/tuttid",
"packages/agent",
"packages/connector/daemon"
];
const clientLiteral = /\bhttp\.Client\{/;
const exemptComment = "proxy-funnel-exempt:";
const exemptPathFragments = ["packages/agent/daemon/httpx/"];
function* goFiles(directory) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.name === "node_modules" || entry.name.startsWith(".")) {
continue;
}
const path = join(directory, entry.name);
if (entry.isDirectory()) {
yield* goFiles(path);
} else if (entry.name.endsWith(".go") && !entry.name.endsWith("_test.go")) {
yield path;
}
}
}
const violations = [];
for (const root of scanRoots) {
for (const path of goFiles(join(workspaceRoot, root))) {
const relativePath = relative(workspaceRoot, path);
if (
exemptPathFragments.some((fragment) => relativePath.includes(fragment))
) {
continue;
}
const lines = readFileSync(path, "utf8").split("\n");
lines.forEach((line, index) => {
if (clientLiteral.test(line) && !line.includes(exemptComment)) {
violations.push(`${relativePath}:${index + 1}: ${line.trim()}`);
}
});
}
}
if (violations.length > 0) {
console.error(
"Bare http.Client literals bypass the proxy funnel. Use packages/agent/daemon/httpx" +
" (NewClient/NewTransport/Default), or append `// proxy-funnel-exempt: <reason>`:\n"
);
for (const violation of violations) {
console.error(` ${violation}`);
}
process.exit(1);
}