lint: replace raw socket guard with codeql

This commit is contained in:
jesse-merhi
2026-05-07 16:02:54 +10:00
committed by Jesse Merhi
parent 9cc5e49e65
commit dd0a9bf869
10 changed files with 224 additions and 572 deletions

View File

@@ -0,0 +1,27 @@
name: openclaw-codeql-raw-socket-boundary-critical-quality
disable-default-queries: true
queries:
- uses: ./.github/codeql/openclaw-boundary/queries/raw-socket-callsite-classification.ql
paths:
- src
- extensions
paths-ignore:
- "**/node_modules"
- "**/coverage"
- "**/*.generated.ts"
- "**/*.bundle.js"
- "**/*-runtime.js"
- "**/*.test.ts"
- "**/*.test.tsx"
- "**/*.e2e.test.ts"
- "**/*.e2e.test.tsx"
- "**/*test-support*"
- "**/*test-helper*"
- "**/*mock*"
- "**/*fixture*"
- "**/*bench*"
- "extensions/diffs/assets/**"

View File

@@ -0,0 +1,30 @@
---
lockVersion: 1.0.0
dependencies:
codeql/concepts:
version: 0.0.22
codeql/controlflow:
version: 2.0.32
codeql/dataflow:
version: 2.1.4
codeql/javascript-all:
version: 2.6.28
codeql/mad:
version: 1.0.48
codeql/regex:
version: 1.0.48
codeql/ssa:
version: 2.0.24
codeql/threat-models:
version: 1.0.48
codeql/tutorial:
version: 1.0.48
codeql/typetracking:
version: 2.0.32
codeql/util:
version: 2.0.35
codeql/xml:
version: 1.0.48
codeql/yaml:
version: 1.0.48
compiled: false

View File

@@ -0,0 +1,6 @@
name: openclaw/codeql-boundary-queries
version: 0.0.0
library: false
dependencies:
codeql/javascript-all: 2.6.28
extractor: javascript

View File

@@ -0,0 +1,92 @@
/**
* @name Raw socket client callsite classification
* @description Raw net/tls/http2 client egress must be classified before landing.
* @kind problem
* @problem.severity error
* @precision high
* @id js/openclaw/raw-socket-callsite-classification
* @tags maintainability
* security
* external/cwe/cwe-441
*/
import javascript
predicate rawModule(string moduleName) {
moduleName = ["net", "node:net", "tls", "node:tls", "http2", "node:http2"]
}
predicate netModule(string moduleName) { moduleName = ["net", "node:net"] }
predicate rawConnectMember(string memberName) { memberName = ["connect", "createConnection"] }
predicate relevantSourceFile(File file) {
exists(string path |
path = file.getRelativePath() and
path.regexpMatch("^(src|extensions)/.*\\.ts$") and
not path.regexpMatch(".*\\.(test|spec|test-utils|test-harness|e2e-harness)\\.ts$") and
not path.regexpMatch(".*/test-support/.*") and
not path.regexpMatch("^extensions/diffs/assets/.*")
)
}
Expr rawSocketClientCall() {
exists(API::CallNode call, string moduleName, string memberName |
rawModule(moduleName) and
rawConnectMember(memberName) and
call = API::moduleImport(moduleName).getMember(memberName).getACall() and
result = call.asExpr()
)
or
exists(string moduleName |
netModule(moduleName) and
result =
DataFlow::moduleMember(moduleName, "Socket")
.getAnInstantiation()
.getAMethodCall("connect")
.asExpr()
)
}
predicate allowedOwnerScope(Expr call, string path, string functionName) {
exists(Function owner |
call.getFile().getRelativePath() = path and
owner.getFile() = call.getFile() and
owner.getName() = functionName and
call.getParent*() = owner.getBody()
)
}
predicate allowedRawSocketClientCall(Expr call) {
allowedOwnerScope(call, "src/cli/gateway-cli/run-loop.ts", "waitForGatewayPortReady")
or
allowedOwnerScope(call, "src/infra/ssh-tunnel.ts", "canConnectLocal")
or
allowedOwnerScope(call, "src/infra/gateway-lock.ts", "checkPortFree")
or
allowedOwnerScope(call, "src/infra/jsonl-socket.ts", "requestJsonlSocket")
or
allowedOwnerScope(call, "src/infra/net/http-connect-tunnel.ts", "connectToProxy")
or
allowedOwnerScope(call, "src/infra/net/http-connect-tunnel.ts", "startTargetTls")
or
allowedOwnerScope(call, "src/infra/push-apns-http2.ts", "openProxiedApnsHttp2Session")
or
allowedOwnerScope(call, "src/infra/push-apns-http2.ts", "connectApnsHttp2Session")
or
allowedOwnerScope(call, "src/proxy-capture/proxy-server.ts", "startDebugProxyServer")
or
allowedOwnerScope(call, "extensions/irc/src/client.ts", "connectIrcClient")
or
allowedOwnerScope(call, "extensions/qa-lab/src/lab-server-capture.ts", "probeTcpReachability")
or
allowedOwnerScope(call, "extensions/qa-lab/src/lab-server-ui.ts", "proxyUpgradeRequest")
}
from Expr call
where
rawSocketClientCall() = call and
relevantSourceFile(call.getFile()) and
not allowedRawSocketClientCall(call)
select call,
"Classify raw net/tls/http2 client egress as managed/proxied, local-only, diagnostic guarded, or documented unsupported before adding this callsite."

View File

@@ -21,15 +21,20 @@ on:
- plugin-sdk-package-contract
- plugin-sdk-reply-runtime
- provider-runtime-boundary
- raw-socket-boundary
- session-diagnostics-boundary
pull_request:
types: [opened, synchronize, reopened, ready_for_review]
paths:
- ".github/codeql/**"
- ".github/workflows/codeql-critical-quality.yml"
- "extensions/*.ts"
- "extensions/**/*.ts"
- "packages/plugin-package-contract/**"
- "packages/plugin-sdk/**"
- "packages/memory-host-sdk/**"
- "src/*.ts"
- "src/**/*.ts"
- "src/config/**"
- "extensions/bluebubbles/src/**"
- "extensions/discord/src/**"
@@ -159,6 +164,7 @@ jobs:
plugin_sdk_package: ${{ steps.detect.outputs.plugin_sdk_package }}
plugin_sdk_reply: ${{ steps.detect.outputs.plugin_sdk_reply }}
provider: ${{ steps.detect.outputs.provider }}
raw_socket: ${{ steps.detect.outputs.raw_socket }}
session_diagnostics: ${{ steps.detect.outputs.session_diagnostics }}
steps:
- name: Detect PR shard paths
@@ -182,6 +188,7 @@ jobs:
plugin_sdk_package=false
plugin_sdk_reply=false
provider=false
raw_socket=false
session_diagnostics=false
if [[ "${EVENT_NAME}" != "pull_request" ]]; then
@@ -196,6 +203,7 @@ jobs:
plugin_sdk_package=true
plugin_sdk_reply=true
provider=true
raw_socket=true
session_diagnostics=true
else
while IFS= read -r file; do
@@ -212,8 +220,12 @@ jobs:
plugin_sdk_package=true
plugin_sdk_reply=true
provider=true
raw_socket=true
session_diagnostics=true
;;
src/*.ts|src/**/*.ts|extensions/*.ts|extensions/**/*.ts)
raw_socket=true
;;
src/acp/control-plane/*|src/agents/cli-runner/*|src/agents/command/*|src/agents/pi-embedded-runner/*|src/agents/tools/*|src/agents/*completion*.ts|src/agents/*transport*.ts|src/agents/model-*.ts|src/agents/openclaw-tools*.ts|src/agents/provider-*.ts|src/agents/session*.ts|src/agents/tool-call*.ts|src/auto-reply/reply/agent-runner*.ts|src/auto-reply/reply/commands*.ts|src/auto-reply/reply/directive-handling*.ts|src/auto-reply/reply/dispatch-*.ts|src/auto-reply/reply/get-reply-run*.ts|src/auto-reply/reply/provider-dispatcher*.ts|src/auto-reply/reply/queue*.ts|src/auto-reply/reply/reply-run-registry*.ts|src/auto-reply/reply/session*.ts)
agent=true
;;
@@ -296,6 +308,7 @@ jobs:
echo "plugin_sdk_package=${plugin_sdk_package}"
echo "plugin_sdk_reply=${plugin_sdk_reply}"
echo "provider=${provider}"
echo "raw_socket=${raw_socket}"
echo "session_diagnostics=${session_diagnostics}"
} >> "${GITHUB_OUTPUT}"
@@ -391,6 +404,62 @@ jobs:
with:
category: "/codeql-critical-quality/channel-runtime-boundary"
raw-socket-boundary:
name: Critical Quality (raw-socket-boundary)
needs: quality-shards
if: ${{ needs.quality-shards.outputs.raw_socket == 'true' && (github.event_name != 'pull_request' || !github.event.pull_request.draft) && (github.event_name == 'pull_request' || github.event_name != 'workflow_dispatch' || inputs.profile == 'all' || inputs.profile == 'raw-socket-boundary') }}
runs-on: blacksmith-4vcpu-ubuntu-2404
timeout-minutes: 25
steps:
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
submodules: false
- name: Initialize CodeQL
uses: github/codeql-action/init@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
with:
languages: javascript-typescript
config-file: ./.github/codeql/codeql-raw-socket-boundary-critical-quality.yml
- name: Analyze
id: analyze
uses: github/codeql-action/analyze@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4
with:
output: sarif-results
category: "/codeql-critical-quality/raw-socket-boundary"
- name: Fail on raw socket findings
env:
SARIF_OUTPUT: sarif-results
run: |
set -euo pipefail
shopt -s nullglob
files=("$SARIF_OUTPUT"/*.sarif)
if [ "${#files[@]}" -eq 0 ]; then
echo "No SARIF files found in $SARIF_OUTPUT" >&2
exit 1
fi
findings="$(jq -s '[.[].runs[]?.results[]?] | length' "${files[@]}")"
if [ "$findings" = "0" ]; then
exit 0
fi
echo "Found ${findings} unclassified raw socket client callsite(s):" >&2
jq -r '
.runs[]?.results[]?
| .locations[0].physicalLocation as $location
| "- "
+ ($location.artifactLocation.uri // "unknown")
+ ":"
+ (($location.region.startLine // 0) | tostring)
+ " "
+ (.message.text // .ruleId)
' "${files[@]}" >&2
exit 1
agent-runtime-boundary:
name: Critical Quality (agent-runtime-boundary)
needs: quality-shards

View File

@@ -1436,7 +1436,6 @@
"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",
"lint:tmp:raw-socket-classification": "node scripts/check-raw-socket-callsite-classification.mjs",
"lint:tmp:tsgo-core-boundary": "node scripts/check-tsgo-core-boundary.mjs",
"lint:ui:no-raw-window-open": "node scripts/check-no-raw-window-open.mjs",
"lint:web-fetch-provider-boundaries": "node scripts/check-web-fetch-provider-boundaries.mjs",

View File

@@ -1,370 +0,0 @@
#!/usr/bin/env node
import ts from "typescript";
import { bundledPluginCallsite } from "./lib/bundled-plugin-paths.mjs";
import { runCallsiteGuard } from "./lib/callsite-guard.mjs";
import {
collectCallExpressionLines,
runAsScript,
unwrapExpression,
} from "./lib/ts-guard-utils.mjs";
const sourceRoots = ["src", "extensions"];
// Managed-proxy raw-socket classification allowlist.
// Each entry is intentionally a concrete callsite so new raw socket egress fails until reviewed.
const allowedRawSocketCallsites = new Set([
// Local Gateway run loop readiness probe.
"src/cli/gateway-cli/run-loop.ts:46",
// Local loopback readiness probe for SSH tunnels.
"src/infra/ssh-tunnel.ts:80",
// Local gateway lock probe.
"src/infra/gateway-lock.ts:147",
// Local Unix-domain socket IPC client.
"src/infra/jsonl-socket.ts:35",
// Managed HTTP CONNECT tunnel helper used by APNs and proxy validation.
"src/infra/net/http-connect-tunnel.ts:117",
"src/infra/net/http-connect-tunnel.ts:123",
"src/infra/net/http-connect-tunnel.ts:268",
// APNs HTTP/2 wrapper: direct only when managed proxy is inactive; tunneled when active.
"src/infra/push-apns-http2.ts:74",
"src/infra/push-apns-http2.ts:85",
// Debug proxy CONNECT internals. PR #77010 guards this path while managed proxy mode is active.
"src/proxy-capture/proxy-server.ts:266",
// QA-lab tunnel/capture helpers used for local lab diagnostics.
bundledPluginCallsite("qa-lab", "src/lab-server-capture.ts", 99),
bundledPluginCallsite("qa-lab", "src/lab-server-ui.ts", 207),
bundledPluginCallsite("qa-lab", "src/lab-server-ui.ts", 212),
// IRC is a raw TCP/TLS channel and is documented as outside managed HTTP proxy coverage.
bundledPluginCallsite("irc", "src/client.ts", 124),
bundledPluginCallsite("irc", "src/client.ts", 129),
]);
function stringLiteralText(node) {
if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) {
return node.text;
}
return undefined;
}
const rawModuleSpecifiers = new Map([
["node:net", "net"],
["net", "net"],
["node:tls", "tls"],
["tls", "tls"],
["node:http2", "http2"],
["http2", "http2"],
]);
function unwrapInitializer(expression) {
const unwrapped = unwrapExpression(expression);
if (ts.isAwaitExpression(unwrapped)) {
return unwrapExpression(unwrapped.expression);
}
return unwrapped;
}
function rawMemberAliasKind(initializer, aliases) {
const unwrapped = unwrapExpression(initializer);
let receiverExpression;
let member;
if (ts.isPropertyAccessExpression(unwrapped)) {
receiverExpression = unwrapped.expression;
member = unwrapped.name.text;
} else if (ts.isElementAccessExpression(unwrapped)) {
receiverExpression = unwrapped.expression;
member = stringLiteralText(unwrapExpression(unwrapped.argumentExpression));
} else {
return undefined;
}
if (!member) {
return undefined;
}
const receiverKind = aliasKind(receiverExpression, aliases);
if (
(receiverKind === "net" || receiverKind === "tls") &&
(member === "connect" || member === "createConnection")
) {
return `${receiverKind}.${member}`;
}
if (receiverKind === "net" && member === "Socket") {
return "net.Socket";
}
if (receiverKind === "http2" && member === "connect") {
return "http2.connect";
}
return undefined;
}
function bindRawModuleDestructureAliases(bindingName, moduleKind, aliases) {
if (!ts.isObjectBindingPattern(bindingName)) {
return;
}
for (const element of bindingName.elements) {
if (!ts.isIdentifier(element.name)) {
continue;
}
const importedName =
element.propertyName && ts.isIdentifier(element.propertyName)
? element.propertyName.text
: element.name.text;
if (importedName === "default") {
aliases.set(element.name.text, moduleKind);
continue;
}
if (
importedName === "connect" ||
importedName === "createConnection" ||
importedName === "Socket"
) {
aliases.set(element.name.text, `${moduleKind}.${importedName}`);
}
}
}
function collectSocketInstanceAliases(sourceFile, rawAliases) {
const socketAliases = new Set();
const visit = (node) => {
if (ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.initializer) {
if (isSocketConstructorExpression(node.initializer, rawAliases)) {
socketAliases.add(node.name.text);
}
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
return socketAliases;
}
function collectRawModuleAliases(sourceFile) {
const aliases = new Map();
const visit = (node) => {
if (ts.isImportDeclaration(node) && ts.isStringLiteral(node.moduleSpecifier)) {
const moduleKind = rawModuleSpecifiers.get(node.moduleSpecifier.text);
const clause = node.importClause;
if (moduleKind && clause) {
if (clause.name) {
aliases.set(clause.name.text, moduleKind);
}
if (clause.namedBindings && ts.isNamespaceImport(clause.namedBindings)) {
aliases.set(clause.namedBindings.name.text, moduleKind);
}
if (clause.namedBindings && ts.isNamedImports(clause.namedBindings)) {
for (const specifier of clause.namedBindings.elements) {
const importedName = (specifier.propertyName ?? specifier.name).text;
if (importedName === "default") {
aliases.set(specifier.name.text, moduleKind);
continue;
}
if (
importedName === "connect" ||
importedName === "createConnection" ||
importedName === "Socket"
) {
aliases.set(specifier.name.text, `${moduleKind}.${importedName}`);
}
}
}
}
}
if (ts.isVariableDeclaration(node) && node.initializer) {
const initializer = unwrapInitializer(node.initializer);
if (ts.isIdentifier(node.name)) {
const moduleKind = aliasKind(initializer, aliases);
if (moduleKind === "net" || moduleKind === "tls" || moduleKind === "http2") {
aliases.set(node.name.text, moduleKind);
}
const memberKind = rawMemberAliasKind(initializer, aliases);
if (memberKind) {
aliases.set(node.name.text, memberKind);
}
}
if (
ts.isCallExpression(initializer) &&
ts.isIdentifier(unwrapExpression(initializer.expression)) &&
unwrapExpression(initializer.expression).text === "require" &&
initializer.arguments.length === 1 &&
ts.isStringLiteral(initializer.arguments[0])
) {
const moduleKind = rawModuleSpecifiers.get(initializer.arguments[0].text);
if (moduleKind) {
if (ts.isIdentifier(node.name)) {
aliases.set(node.name.text, moduleKind);
} else {
bindRawModuleDestructureAliases(node.name, moduleKind, aliases);
}
}
}
if (ts.isObjectBindingPattern(node.name) && ts.isIdentifier(initializer)) {
const moduleKind = aliases.get(initializer.text);
if (moduleKind === "net" || moduleKind === "tls" || moduleKind === "http2") {
bindRawModuleDestructureAliases(node.name, moduleKind, aliases);
}
}
if (
ts.isCallExpression(initializer) &&
initializer.expression.kind === ts.SyntaxKind.ImportKeyword &&
initializer.arguments.length === 1 &&
ts.isStringLiteral(initializer.arguments[0])
) {
const moduleKind = rawModuleSpecifiers.get(initializer.arguments[0].text);
if (moduleKind) {
if (ts.isIdentifier(node.name)) {
aliases.set(node.name.text, moduleKind);
} else {
bindRawModuleDestructureAliases(node.name, moduleKind, aliases);
}
}
}
}
ts.forEachChild(node, visit);
};
visit(sourceFile);
return aliases;
}
function rawModuleKindFromExpression(expression) {
const unwrappedExpression = unwrapExpression(expression);
const unwrapped = ts.isAwaitExpression(unwrappedExpression)
? unwrapExpression(unwrappedExpression.expression)
: unwrappedExpression;
if (
ts.isCallExpression(unwrapped) &&
ts.isIdentifier(unwrapped.expression) &&
unwrapped.expression.text === "require" &&
unwrapped.arguments.length === 1
) {
const moduleName = stringLiteralText(unwrapExpression(unwrapped.arguments[0]));
return moduleName ? rawModuleSpecifiers.get(moduleName) : undefined;
}
if (
ts.isCallExpression(unwrapped) &&
unwrapped.expression.kind === ts.SyntaxKind.ImportKeyword &&
unwrapped.arguments.length === 1
) {
const moduleName = stringLiteralText(unwrapExpression(unwrapped.arguments[0]));
return moduleName ? rawModuleSpecifiers.get(moduleName) : undefined;
}
return undefined;
}
function aliasKind(expression, aliases) {
const receiver = unwrapExpression(expression);
if (ts.isIdentifier(receiver)) {
return aliases.get(receiver.text);
}
if (ts.isPropertyAccessExpression(receiver) && receiver.name.text === "default") {
return rawModuleKindFromExpression(receiver.expression);
}
return rawModuleKindFromExpression(receiver);
}
function isRawModuleAlias(expression, aliases, expectedKinds) {
const kind = aliasKind(expression, aliases);
return expectedKinds.has(kind);
}
function isSocketConstructorExpression(expression, aliases) {
const unwrapped = unwrapExpression(expression);
if (!ts.isNewExpression(unwrapped)) {
return false;
}
const callee = unwrapExpression(unwrapped.expression);
if (aliasKind(callee, aliases) === "net.Socket") {
return true;
}
if (!ts.isPropertyAccessExpression(callee) || callee.name.text !== "Socket") {
return false;
}
return isRawModuleAlias(callee.expression, aliases, new Set(["net"]));
}
function rawSocketCallee(expression, aliases, socketAliases = new Set()) {
const callee = unwrapExpression(expression);
if (ts.isIdentifier(callee)) {
const kind = aliasKind(callee, aliases);
return kind === "net.connect" ||
kind === "tls.connect" ||
kind === "http2.connect" ||
kind === "net.createConnection" ||
kind === "tls.createConnection"
? callee
: null;
}
let receiverExpression;
let member;
if (ts.isPropertyAccessExpression(callee)) {
receiverExpression = callee.expression;
member = callee.name.text;
} else if (ts.isElementAccessExpression(callee)) {
receiverExpression = callee.expression;
member = stringLiteralText(unwrapExpression(callee.argumentExpression));
} else {
return null;
}
if (!member) {
return null;
}
if (
member === "connect" &&
isRawModuleAlias(receiverExpression, aliases, new Set(["net", "tls", "http2"]))
) {
return callee;
}
if (
member === "createConnection" &&
isRawModuleAlias(receiverExpression, aliases, new Set(["net", "tls"]))
) {
return callee;
}
if (member === "connect" && isSocketConstructorExpression(receiverExpression, aliases)) {
return callee;
}
if (
member === "connect" &&
ts.isIdentifier(unwrapExpression(receiverExpression)) &&
socketAliases.has(unwrapExpression(receiverExpression).text)
) {
return callee;
}
return null;
}
export function findRawSocketClientCallLines(content, fileName = "source.ts") {
const sourceFile = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
const aliases = collectRawModuleAliases(sourceFile);
const socketAliases = collectSocketInstanceAliases(sourceFile, aliases);
return collectCallExpressionLines(ts, sourceFile, (node) =>
rawSocketCallee(node.expression, aliases, socketAliases),
);
}
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",
],
findCallLines: findRawSocketClientCallLines,
skipRelativePath: (relPath) => relPath.includes("/test-support/"),
allowCallsite: (callsite) => allowedRawSocketCallsites.has(callsite),
header: "Found unclassified raw socket client calls:",
footer:
"Classify raw net/tls/http2 egress as managed/proxied, local-only, diagnostic guarded, or documented unsupported before adding callsites.",
});
}
runAsScript(import.meta.url, main);

View File

@@ -10,7 +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:raw-socket-classification", "pnpm", ["run", "lint:tmp:raw-socket-classification"]],
["lint:agent:ingress-owner", "pnpm", ["run", "lint:agent:ingress-owner"]],
[
"lint:plugins:no-register-http-handler",

View File

@@ -1,192 +0,0 @@
import { describe, expect, it } from "vitest";
import { findRawSocketClientCallLines } from "../../scripts/check-raw-socket-callsite-classification.mjs";
describe("check-raw-socket-callsite-classification", () => {
it("finds raw net, tls, and http2 client calls", () => {
const source = `
import net from "node:net";
import * as tls from "node:tls";
import http2 from "node:http2";
net.connect({ host: "example.com", port: 6667 });
tls.connect({ host: "example.com", port: 6697 });
http2.connect("https://api.example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
it("ignores comments, strings, and unrelated connect methods", () => {
const source = `
// net.connect({ host: "example.com" });
const text = "tls.connect({ host: 'example.com' })";
client.connect(transport);
websocket.connect();
`;
expect(findRawSocketClientCallLines(source)).toEqual([]);
});
it("handles aliased imports, requires, and dynamic literal imports", () => {
const source = `
import * as rawNet from "node:net";
const rawTls = require("node:tls");
const rawHttp2 = await import("node:http2");
rawNet.connect({ host: "127.0.0.1", port: 1 });
rawTls.connect({ host: "127.0.0.1", port: 1 });
rawHttp2.connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
it("finds element-access raw socket calls", () => {
const source = `
import net from "node:net";
import tls from "node:tls";
import http2 from "node:http2";
net["connect"]({ host: "127.0.0.1", port: 1 });
tls["createConnection"]({ host: "127.0.0.1", port: 1 });
http2["connect"]("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
it("finds destructured dynamic-import default raw module aliases", () => {
const source = `
const { default: rawNet } = await import("node:net");
const { default: rawTls } = await import("node:tls");
const { default: rawHttp2 } = await import("node:http2");
rawNet.connect({ host: "127.0.0.1", port: 1 });
rawTls.connect({ host: "127.0.0.1", port: 1 });
rawHttp2.connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
it("finds direct raw module receiver calls", () => {
const source = `
require("node:net").connect({ host: "127.0.0.1", port: 1 });
require("node:tls").createConnection({ host: "127.0.0.1", port: 1 });
(await import("node:http2")).connect("https://example.com");
(await import("node:net")).default.connect({ host: "127.0.0.1", port: 1 });
(await import("node:tls")).default.createConnection({ host: "127.0.0.1", port: 1 });
(await import("node:http2")).default.connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([2, 3, 4, 5, 6, 7]);
});
it("finds named default raw module imports", () => {
const source = `
import { default as rawNet } from "node:net";
import { default as rawTls } from "node:tls";
import { default as rawHttp2 } from "node:http2";
rawNet.connect({ host: "127.0.0.1", port: 1 });
rawTls.connect({ host: "127.0.0.1", port: 1 });
rawHttp2.connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
it("finds raw socket module object aliases", () => {
const source = `
import net from "node:net";
import tls from "node:tls";
import http2 from "node:http2";
const rawNet = net;
const rawTls = tls;
const rawHttp2 = http2;
rawNet.connect({ host: "127.0.0.1", port: 1 });
rawTls.connect({ host: "127.0.0.1", port: 1 });
rawHttp2.connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([8, 9, 10]);
});
it("finds aliases to raw socket module members", () => {
const source = `
import net from "node:net";
import tls from "node:tls";
import http2 from "node:http2";
const netConnect = net.connect;
const tlsConnect = tls.connect;
const h2Connect = http2.connect;
const Socket = net.Socket;
const { createConnection } = net;
netConnect({ host: "127.0.0.1", port: 1 });
tlsConnect({ host: "127.0.0.1", port: 1 });
h2Connect("https://example.com");
createConnection({ host: "127.0.0.1", port: 1 });
new Socket().connect("/tmp/socket");
`;
expect(findRawSocketClientCallLines(source)).toEqual([10, 11, 12, 13, 14]);
});
it("finds destructured require and dynamic import raw socket bindings", () => {
const source = `
const { connect, createConnection, Socket } = require("node:net");
const { connect: tlsConnect } = await import("node:tls");
connect({ host: "127.0.0.1", port: 1 });
createConnection({ host: "127.0.0.1", port: 1 });
tlsConnect({ host: "127.0.0.1", port: 1 });
new Socket().connect("/tmp/socket");
`;
expect(findRawSocketClientCallLines(source)).toEqual([4, 5, 6, 7]);
});
it("finds stored Socket instance connect calls", () => {
const source = `
import net from "node:net";
const client = new net.Socket();
client.connect("/tmp/socket");
`;
expect(findRawSocketClientCallLines(source)).toEqual([4]);
});
it("finds named raw socket imports", () => {
const source = `
import { connect as netConnect, createConnection, Socket } from "node:net";
import { connect as tlsConnect } from "node:tls";
import { connect as http2Connect } from "node:http2";
netConnect({ host: "127.0.0.1", port: 1 });
createConnection({ host: "127.0.0.1", port: 1 });
tlsConnect({ host: "127.0.0.1", port: 1 });
http2Connect("https://example.com");
new Socket().connect("/tmp/socket");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7, 8, 9]);
});
it("finds createConnection and constructed Socket.connect client calls", () => {
const source = `
import net from "node:net";
import tls from "node:tls";
net.createConnection({ host: "127.0.0.1", port: 1 });
tls.createConnection({ host: "127.0.0.1", port: 1 });
new net.Socket().connect("/tmp/socket");
`;
expect(findRawSocketClientCallLines(source)).toEqual([4, 5, 6]);
});
it("handles parenthesized and asserted module identifiers", () => {
const source = `
import net from "node:net";
import tls from "node:tls";
import http2 from "node:http2";
(net as typeof import("node:net")).connect({ host: "127.0.0.1", port: 1 });
(tls as typeof import("node:tls")).connect({ host: "127.0.0.1", port: 1 });
(http2 as typeof import("node:http2")).connect("https://example.com");
`;
expect(findRawSocketClientCallLines(source)).toEqual([5, 6, 7]);
});
});

View File

@@ -30,14 +30,6 @@ describe("run-additional-boundary-checks", () => {
});
});
it("runs raw socket classification guard in CI", () => {
expect(BOUNDARY_CHECKS).toContainEqual({
label: "lint:tmp:raw-socket-classification",
command: "pnpm",
args: ["run", "lint:tmp:raw-socket-classification"],
});
});
it("normalizes concurrency input", () => {
expect(resolveConcurrency("6")).toBe(6);
expect(resolveConcurrency("0")).toBe(4);