nodebench-mcp 2.31.2 → 2.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -6
- package/dist/engine/server.js +14 -4
- package/dist/engine/server.js.map +1 -1
- package/dist/index.js +1581 -670
- package/dist/index.js.map +1 -1
- package/dist/security/SecurityError.d.ts +18 -0
- package/dist/security/SecurityError.js +22 -0
- package/dist/security/SecurityError.js.map +1 -0
- package/dist/security/__tests__/security.test.d.ts +8 -0
- package/dist/security/__tests__/security.test.js +295 -0
- package/dist/security/__tests__/security.test.js.map +1 -0
- package/dist/security/auditLog.d.ts +36 -0
- package/dist/security/auditLog.js +178 -0
- package/dist/security/auditLog.js.map +1 -0
- package/dist/security/commandSandbox.d.ts +33 -0
- package/dist/security/commandSandbox.js +159 -0
- package/dist/security/commandSandbox.js.map +1 -0
- package/dist/security/config.d.ts +23 -0
- package/dist/security/config.js +43 -0
- package/dist/security/config.js.map +1 -0
- package/dist/security/credentialRedactor.d.ts +22 -0
- package/dist/security/credentialRedactor.js +118 -0
- package/dist/security/credentialRedactor.js.map +1 -0
- package/dist/security/index.d.ts +20 -0
- package/dist/security/index.js +21 -0
- package/dist/security/index.js.map +1 -0
- package/dist/security/pathSandbox.d.ts +23 -0
- package/dist/security/pathSandbox.js +160 -0
- package/dist/security/pathSandbox.js.map +1 -0
- package/dist/security/urlValidator.d.ts +23 -0
- package/dist/security/urlValidator.js +125 -0
- package/dist/security/urlValidator.js.map +1 -0
- package/dist/tools/agentBootstrapTools.js +22 -29
- package/dist/tools/agentBootstrapTools.js.map +1 -1
- package/dist/tools/contextSandboxTools.js +7 -9
- package/dist/tools/contextSandboxTools.js.map +1 -1
- package/dist/tools/deepSimTools.d.ts +2 -0
- package/dist/tools/deepSimTools.js +404 -0
- package/dist/tools/deepSimTools.js.map +1 -0
- package/dist/tools/dimensionTools.d.ts +2 -0
- package/dist/tools/dimensionTools.js +246 -0
- package/dist/tools/dimensionTools.js.map +1 -0
- package/dist/tools/executionTraceTools.d.ts +2 -0
- package/dist/tools/executionTraceTools.js +446 -0
- package/dist/tools/executionTraceTools.js.map +1 -0
- package/dist/tools/founderTools.d.ts +13 -0
- package/dist/tools/founderTools.js +595 -0
- package/dist/tools/founderTools.js.map +1 -0
- package/dist/tools/gitWorkflowTools.js +14 -10
- package/dist/tools/gitWorkflowTools.js.map +1 -1
- package/dist/tools/githubTools.js +19 -2
- package/dist/tools/githubTools.js.map +1 -1
- package/dist/tools/index.d.ts +87 -0
- package/dist/tools/index.js +102 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/localFileTools.js +24 -12
- package/dist/tools/localFileTools.js.map +1 -1
- package/dist/tools/memoryDecay.d.ts +70 -0
- package/dist/tools/memoryDecay.js +247 -0
- package/dist/tools/memoryDecay.js.map +1 -0
- package/dist/tools/missionHarnessTools.d.ts +32 -0
- package/dist/tools/missionHarnessTools.js +972 -0
- package/dist/tools/missionHarnessTools.js.map +1 -0
- package/dist/tools/observabilityTools.d.ts +15 -0
- package/dist/tools/observabilityTools.js +787 -0
- package/dist/tools/observabilityTools.js.map +1 -0
- package/dist/tools/openclawTools.js +151 -36
- package/dist/tools/openclawTools.js.map +1 -1
- package/dist/tools/progressiveDiscoveryTools.js +5 -4
- package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
- package/dist/tools/qualityGateTools.js +118 -2
- package/dist/tools/qualityGateTools.js.map +1 -1
- package/dist/tools/rssTools.js +3 -0
- package/dist/tools/rssTools.js.map +1 -1
- package/dist/tools/scraplingTools.js +15 -0
- package/dist/tools/scraplingTools.js.map +1 -1
- package/dist/tools/seoTools.js +66 -1
- package/dist/tools/seoTools.js.map +1 -1
- package/dist/tools/sessionMemoryTools.js +50 -11
- package/dist/tools/sessionMemoryTools.js.map +1 -1
- package/dist/tools/temporalIntelligenceTools.d.ts +12 -0
- package/dist/tools/temporalIntelligenceTools.js +1068 -0
- package/dist/tools/temporalIntelligenceTools.js.map +1 -0
- package/dist/tools/toolRegistry.d.ts +19 -0
- package/dist/tools/toolRegistry.js +857 -31
- package/dist/tools/toolRegistry.js.map +1 -1
- package/dist/tools/webTools.js +14 -1
- package/dist/tools/webTools.js.map +1 -1
- package/dist/tools/webmcpTools.js +13 -2
- package/dist/tools/webmcpTools.js.map +1 -1
- package/dist/toolsetRegistry.js +13 -0
- package/dist/toolsetRegistry.js.map +1 -1
- package/dist/types.d.ts +10 -0
- package/package.json +124 -124
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* commandSandbox.ts — Command execution with allow-list enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Replaces raw execSync calls with validated, audited execution.
|
|
5
|
+
* Uses allow-list (not deny-list) for command prefixes.
|
|
6
|
+
*/
|
|
7
|
+
import { execSync } from "node:child_process";
|
|
8
|
+
import { SecurityError } from "./SecurityError.js";
|
|
9
|
+
import { getSecurityConfig } from "./config.js";
|
|
10
|
+
// Allow-list of safe command prefixes
|
|
11
|
+
const ALLOWED_PREFIXES = [
|
|
12
|
+
// Version control
|
|
13
|
+
"git ",
|
|
14
|
+
// Node.js ecosystem
|
|
15
|
+
"node ",
|
|
16
|
+
"npm ",
|
|
17
|
+
"npx ",
|
|
18
|
+
"pnpm ",
|
|
19
|
+
"yarn ",
|
|
20
|
+
"bun ",
|
|
21
|
+
// TypeScript/JavaScript
|
|
22
|
+
"tsc ",
|
|
23
|
+
"tsx ",
|
|
24
|
+
"vitest ",
|
|
25
|
+
"jest ",
|
|
26
|
+
// Python
|
|
27
|
+
"python ",
|
|
28
|
+
"python3 ",
|
|
29
|
+
"pip ",
|
|
30
|
+
"pytest ",
|
|
31
|
+
// Build tools
|
|
32
|
+
"cargo ",
|
|
33
|
+
"go ",
|
|
34
|
+
"make ",
|
|
35
|
+
"cmake ",
|
|
36
|
+
"gcc ",
|
|
37
|
+
"g++ ",
|
|
38
|
+
"clang ",
|
|
39
|
+
// Safe read-only commands
|
|
40
|
+
"ls",
|
|
41
|
+
"dir",
|
|
42
|
+
"cat ",
|
|
43
|
+
"head ",
|
|
44
|
+
"tail ",
|
|
45
|
+
"wc ",
|
|
46
|
+
"grep ",
|
|
47
|
+
"rg ",
|
|
48
|
+
"find ",
|
|
49
|
+
"which ",
|
|
50
|
+
"where ",
|
|
51
|
+
"type ",
|
|
52
|
+
// Info commands
|
|
53
|
+
"echo ",
|
|
54
|
+
"date",
|
|
55
|
+
"pwd",
|
|
56
|
+
"hostname",
|
|
57
|
+
"uname ",
|
|
58
|
+
"whoami",
|
|
59
|
+
"env",
|
|
60
|
+
"printenv",
|
|
61
|
+
// Package managers
|
|
62
|
+
"apt ",
|
|
63
|
+
"brew ",
|
|
64
|
+
"choco ",
|
|
65
|
+
"winget ",
|
|
66
|
+
// Containers
|
|
67
|
+
"docker ",
|
|
68
|
+
"docker-compose ",
|
|
69
|
+
"podman ",
|
|
70
|
+
// Network (SSRF handled separately by urlValidator)
|
|
71
|
+
"curl ",
|
|
72
|
+
"wget ",
|
|
73
|
+
"ping ",
|
|
74
|
+
"nslookup ",
|
|
75
|
+
"dig ",
|
|
76
|
+
];
|
|
77
|
+
// Shell metacharacters that enable command chaining/injection
|
|
78
|
+
const DANGEROUS_METACHAR_RE = /[;`$]|\$\(|&&|\|\||>>|<<|>\s*\/|<\s*\//;
|
|
79
|
+
const PIPE_RE = /\|(?!\|)/; // Single pipe (not ||)
|
|
80
|
+
function isAllowedCommand(command, extraPrefixes) {
|
|
81
|
+
const trimmed = command.trim().toLowerCase();
|
|
82
|
+
const allPrefixes = [...ALLOWED_PREFIXES, ...extraPrefixes];
|
|
83
|
+
for (const prefix of allPrefixes) {
|
|
84
|
+
if (trimmed === prefix.trim() || trimmed.startsWith(prefix)) {
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Execute a command with security validation.
|
|
92
|
+
*
|
|
93
|
+
* @throws SecurityError if command is not on allow-list or contains injection
|
|
94
|
+
*/
|
|
95
|
+
export function safeExec(command, opts) {
|
|
96
|
+
const config = getSecurityConfig();
|
|
97
|
+
const trimmedCommand = command.trim();
|
|
98
|
+
if (!trimmedCommand) {
|
|
99
|
+
return { stdout: "", stderr: "Empty command", exitCode: 1, timedOut: false, durationMs: 0 };
|
|
100
|
+
}
|
|
101
|
+
// In permissive mode, skip validation
|
|
102
|
+
if (config.mode !== "permissive") {
|
|
103
|
+
// Check allow-list
|
|
104
|
+
const extraPrefixes = [
|
|
105
|
+
...config.extraExecAllowList,
|
|
106
|
+
...(opts?.additionalPrefixes ?? []),
|
|
107
|
+
];
|
|
108
|
+
if (!isAllowedCommand(trimmedCommand, extraPrefixes)) {
|
|
109
|
+
if (config.mode === "audit_only") {
|
|
110
|
+
// Log but proceed
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
throw new SecurityError("EXEC_BLOCKED", `Command "${trimmedCommand.substring(0, 60)}..." is not on the allow-list. ` +
|
|
114
|
+
`Allowed prefixes: ${ALLOWED_PREFIXES.slice(0, 10).join(", ")}...`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Check for shell metacharacters (injection prevention)
|
|
118
|
+
if (DANGEROUS_METACHAR_RE.test(trimmedCommand)) {
|
|
119
|
+
throw new SecurityError("EXEC_METACHAR", `Command contains dangerous shell metacharacters: ${trimmedCommand.substring(0, 60)}`);
|
|
120
|
+
}
|
|
121
|
+
// Check pipes separately (allowed if opts.allowPipes)
|
|
122
|
+
if (!opts?.allowPipes && PIPE_RE.test(trimmedCommand)) {
|
|
123
|
+
throw new SecurityError("EXEC_METACHAR", `Command contains pipe operator. Use allowPipes option if intended: ${trimmedCommand.substring(0, 60)}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
const timeout = Math.min(opts?.timeout ?? 30_000, config.maxExecTimeoutMs);
|
|
127
|
+
const maxBuffer = opts?.maxBuffer ?? 10 * 1024 * 1024;
|
|
128
|
+
const cwd = opts?.cwd ?? process.cwd();
|
|
129
|
+
const start = Date.now();
|
|
130
|
+
try {
|
|
131
|
+
const stdout = execSync(trimmedCommand, {
|
|
132
|
+
cwd,
|
|
133
|
+
timeout,
|
|
134
|
+
encoding: "utf-8",
|
|
135
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
136
|
+
maxBuffer,
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
stdout: (stdout ?? "").slice(0, 50_000),
|
|
140
|
+
stderr: "",
|
|
141
|
+
exitCode: 0,
|
|
142
|
+
timedOut: false,
|
|
143
|
+
durationMs: Date.now() - start,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (err) {
|
|
147
|
+
const timedOut = err.killed === true || err.signal === "SIGTERM";
|
|
148
|
+
return {
|
|
149
|
+
stdout: (err.stdout ?? "").slice(0, 50_000),
|
|
150
|
+
stderr: (err.stderr ?? "").slice(0, 10_000),
|
|
151
|
+
exitCode: err.status ?? 1,
|
|
152
|
+
timedOut,
|
|
153
|
+
durationMs: Date.now() - start,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/** Export for testing */
|
|
158
|
+
export const _ALLOWED_PREFIXES = ALLOWED_PREFIXES;
|
|
159
|
+
//# sourceMappingURL=commandSandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commandSandbox.js","sourceRoot":"","sources":["../../src/security/commandSandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.ts — Centralized security configuration.
|
|
3
|
+
*
|
|
4
|
+
* Reads from env vars at startup, cached for the process lifetime.
|
|
5
|
+
* Test helpers follow the embeddingProvider.ts pattern.
|
|
6
|
+
*/
|
|
7
|
+
export type SecurityMode = "strict" | "permissive" | "audit_only";
|
|
8
|
+
export interface SecurityConfig {
|
|
9
|
+
/** strict = block + log, permissive = allow all, audit_only = log but don't block */
|
|
10
|
+
mode: SecurityMode;
|
|
11
|
+
/** Filesystem roots tools are allowed to access (default: cwd) */
|
|
12
|
+
allowedRoots: string[];
|
|
13
|
+
/** Max command execution timeout in ms (hard cap) */
|
|
14
|
+
maxExecTimeoutMs: number;
|
|
15
|
+
/** Whether audit logging is enabled */
|
|
16
|
+
auditEnabled: boolean;
|
|
17
|
+
/** Additional command prefixes allowed beyond the built-in list */
|
|
18
|
+
extraExecAllowList: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function getSecurityConfig(): SecurityConfig;
|
|
21
|
+
export declare function setSecurityConfig(partial: Partial<SecurityConfig>): void;
|
|
22
|
+
/** Test helper — reset to defaults */
|
|
23
|
+
export declare function _resetSecurityConfigForTesting(): void;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* config.ts — Centralized security configuration.
|
|
3
|
+
*
|
|
4
|
+
* Reads from env vars at startup, cached for the process lifetime.
|
|
5
|
+
* Test helpers follow the embeddingProvider.ts pattern.
|
|
6
|
+
*/
|
|
7
|
+
import * as path from "node:path";
|
|
8
|
+
const DEFAULT_CONFIG = {
|
|
9
|
+
mode: "strict",
|
|
10
|
+
allowedRoots: [process.cwd()],
|
|
11
|
+
maxExecTimeoutMs: 60_000,
|
|
12
|
+
auditEnabled: true,
|
|
13
|
+
extraExecAllowList: [],
|
|
14
|
+
};
|
|
15
|
+
let _config = null;
|
|
16
|
+
export function getSecurityConfig() {
|
|
17
|
+
if (_config)
|
|
18
|
+
return _config;
|
|
19
|
+
const mode = process.env.NODEBENCH_SECURITY_MODE ?? "strict";
|
|
20
|
+
const rootsEnv = process.env.NODEBENCH_ALLOWED_ROOTS;
|
|
21
|
+
const allowedRoots = rootsEnv
|
|
22
|
+
? rootsEnv.split(",").map((r) => path.resolve(r.trim()))
|
|
23
|
+
: [process.cwd()];
|
|
24
|
+
const timeoutEnv = process.env.NODEBENCH_EXEC_TIMEOUT_MS;
|
|
25
|
+
const maxExecTimeoutMs = timeoutEnv
|
|
26
|
+
? Math.min(parseInt(timeoutEnv, 10) || 60_000, 60_000)
|
|
27
|
+
: 60_000;
|
|
28
|
+
const auditEnabled = process.env.NODEBENCH_AUDIT_ENABLED !== "false";
|
|
29
|
+
const extraEnv = process.env.NODEBENCH_EXEC_ALLOWLIST;
|
|
30
|
+
const extraExecAllowList = extraEnv
|
|
31
|
+
? extraEnv.split(",").map((s) => s.trim())
|
|
32
|
+
: [];
|
|
33
|
+
_config = { mode, allowedRoots, maxExecTimeoutMs, auditEnabled, extraExecAllowList };
|
|
34
|
+
return _config;
|
|
35
|
+
}
|
|
36
|
+
export function setSecurityConfig(partial) {
|
|
37
|
+
_config = { ...getSecurityConfig(), ...partial };
|
|
38
|
+
}
|
|
39
|
+
/** Test helper — reset to defaults */
|
|
40
|
+
export function _resetSecurityConfigForTesting() {
|
|
41
|
+
_config = null;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/security/config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAiBlC,MAAM,cAAc,GAAmB;IACrC,IAAI,EAAE,QAAQ;IACd,YAAY,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC;IAC7B,gBAAgB,EAAE,MAAM;IACxB,YAAY,EAAE,IAAI;IAClB,kBAAkB,EAAE,EAAE;CACvB,CAAC;AAEF,IAAI,OAAO,GAA0B,IAAI,CAAC;AAE1C,MAAM,UAAU,iBAAiB;IAC/B,IAAI,OAAO;QAAE,OAAO,OAAO,CAAC;IAE5B,MAAM,IAAI,GAAI,OAAO,CAAC,GAAG,CAAC,uBAAwC,IAAI,QAAQ,CAAC;IAC/E,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC;IACrD,MAAM,YAAY,GAAG,QAAQ;QAC3B,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QACxD,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAEpB,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;IACzD,MAAM,gBAAgB,GAAG,UAAU;QACjC,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,EAAE,MAAM,CAAC;QACtD,CAAC,CAAC,MAAM,CAAC;IAEX,MAAM,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,uBAAuB,KAAK,OAAO,CAAC;IAErE,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC;IACtD,MAAM,kBAAkB,GAAG,QAAQ;QACjC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAC1C,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO,GAAG,EAAE,IAAI,EAAE,YAAY,EAAE,gBAAgB,EAAE,YAAY,EAAE,kBAAkB,EAAE,CAAC;IACrF,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,OAAgC;IAChE,OAAO,GAAG,EAAE,GAAG,iBAAiB,EAAE,EAAE,GAAG,OAAO,EAAE,CAAC;AACnD,CAAC;AAED,sCAAsC;AACtC,MAAM,UAAU,8BAA8B;IAC5C,OAAO,GAAG,IAAI,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentialRedactor.ts — Redacts secrets from tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Applied at the response serialization layer in index.ts so every tool
|
|
5
|
+
* output is automatically sanitized without individual tool changes.
|
|
6
|
+
*/
|
|
7
|
+
export interface RedactOpts {
|
|
8
|
+
/** Skip specific patterns (by label) */
|
|
9
|
+
skipPatterns?: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Redact secrets from a string output.
|
|
13
|
+
*
|
|
14
|
+
* @returns The sanitized string with secrets replaced by [REDACTED:label]
|
|
15
|
+
*/
|
|
16
|
+
export declare function redactSecrets(output: string, opts?: RedactOpts): string;
|
|
17
|
+
/**
|
|
18
|
+
* Redact secrets from a structured object (recursively processes string values).
|
|
19
|
+
*/
|
|
20
|
+
export declare function redactObject(obj: unknown): unknown;
|
|
21
|
+
/** Test helper — reset env secrets cache */
|
|
22
|
+
export declare function _resetEnvSecretsForTesting(): void;
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* credentialRedactor.ts — Redacts secrets from tool outputs.
|
|
3
|
+
*
|
|
4
|
+
* Applied at the response serialization layer in index.ts so every tool
|
|
5
|
+
* output is automatically sanitized without individual tool changes.
|
|
6
|
+
*/
|
|
7
|
+
const SECRET_PATTERNS = [
|
|
8
|
+
// OpenAI
|
|
9
|
+
{ re: /sk-[a-zA-Z0-9]{20,}/g, label: "OPENAI_KEY" },
|
|
10
|
+
{ re: /sk-proj-[a-zA-Z0-9_-]{40,}/g, label: "OPENAI_PROJECT_KEY" },
|
|
11
|
+
// Anthropic
|
|
12
|
+
{ re: /sk-ant-[a-zA-Z0-9_-]{20,}/g, label: "ANTHROPIC_KEY" },
|
|
13
|
+
// GitHub
|
|
14
|
+
{ re: /ghp_[a-zA-Z0-9]{36}/g, label: "GITHUB_PAT" },
|
|
15
|
+
{ re: /gho_[a-zA-Z0-9]{36}/g, label: "GITHUB_OAUTH" },
|
|
16
|
+
{ re: /github_pat_[a-zA-Z0-9_]{20,}/g, label: "GITHUB_FINE_PAT" },
|
|
17
|
+
// npm
|
|
18
|
+
{ re: /npm_[a-zA-Z0-9]{36}/g, label: "NPM_TOKEN" },
|
|
19
|
+
// Google
|
|
20
|
+
{ re: /AIza[a-zA-Z0-9_-]{35}/g, label: "GOOGLE_API_KEY" },
|
|
21
|
+
// AWS
|
|
22
|
+
{ re: /AKIA[A-Z0-9]{16}/g, label: "AWS_ACCESS_KEY" },
|
|
23
|
+
{ re: /(?:aws_secret_access_key\s*=\s*)[a-zA-Z0-9/+=]{40}/gi, label: "AWS_SECRET_KEY" },
|
|
24
|
+
// Slack
|
|
25
|
+
{ re: /xoxb-[a-zA-Z0-9-]+/g, label: "SLACK_BOT_TOKEN" },
|
|
26
|
+
{ re: /xoxp-[a-zA-Z0-9-]+/g, label: "SLACK_USER_TOKEN" },
|
|
27
|
+
// Private keys
|
|
28
|
+
{ re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g, label: "PRIVATE_KEY" },
|
|
29
|
+
// Generic secrets in key=value format
|
|
30
|
+
{
|
|
31
|
+
re: /(?:password|passwd|pwd|secret|token|api[_-]?key|auth[_-]?token)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
|
|
32
|
+
label: "CREDENTIAL",
|
|
33
|
+
},
|
|
34
|
+
// Bearer tokens
|
|
35
|
+
{ re: /Bearer\s+[a-zA-Z0-9._-]{20,}/g, label: "BEARER_TOKEN" },
|
|
36
|
+
// Figma
|
|
37
|
+
{ re: /figd_[a-zA-Z0-9_-]{20,}/g, label: "FIGMA_TOKEN" },
|
|
38
|
+
// Stripe
|
|
39
|
+
{ re: /sk_(?:live|test)_[a-zA-Z0-9]{24,}/g, label: "STRIPE_KEY" },
|
|
40
|
+
// Twilio
|
|
41
|
+
{ re: /SK[a-f0-9]{32}/g, label: "TWILIO_KEY" },
|
|
42
|
+
];
|
|
43
|
+
// Dynamic env-var based redaction: build a set of known secret values from process.env
|
|
44
|
+
const SECRET_ENV_KEYS = [
|
|
45
|
+
"KEY",
|
|
46
|
+
"SECRET",
|
|
47
|
+
"TOKEN",
|
|
48
|
+
"PASSWORD",
|
|
49
|
+
"PASS",
|
|
50
|
+
"CREDENTIAL",
|
|
51
|
+
"AUTH",
|
|
52
|
+
"API_KEY",
|
|
53
|
+
];
|
|
54
|
+
let _envSecrets = null;
|
|
55
|
+
function getEnvSecrets() {
|
|
56
|
+
if (_envSecrets)
|
|
57
|
+
return _envSecrets;
|
|
58
|
+
_envSecrets = new Set();
|
|
59
|
+
for (const [key, value] of Object.entries(process.env)) {
|
|
60
|
+
if (!value || value.length < 8)
|
|
61
|
+
continue;
|
|
62
|
+
const upperKey = key.toUpperCase();
|
|
63
|
+
if (SECRET_ENV_KEYS.some((s) => upperKey.includes(s))) {
|
|
64
|
+
_envSecrets.add(value);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return _envSecrets;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Redact secrets from a string output.
|
|
71
|
+
*
|
|
72
|
+
* @returns The sanitized string with secrets replaced by [REDACTED:label]
|
|
73
|
+
*/
|
|
74
|
+
export function redactSecrets(output, opts) {
|
|
75
|
+
if (!output || typeof output !== "string")
|
|
76
|
+
return output;
|
|
77
|
+
const skip = new Set(opts?.skipPatterns ?? []);
|
|
78
|
+
let result = output;
|
|
79
|
+
// Pattern-based redaction
|
|
80
|
+
for (const { re, label } of SECRET_PATTERNS) {
|
|
81
|
+
if (skip.has(label))
|
|
82
|
+
continue;
|
|
83
|
+
// Reset regex lastIndex (they're global)
|
|
84
|
+
re.lastIndex = 0;
|
|
85
|
+
result = result.replace(re, `[REDACTED:${label}]`);
|
|
86
|
+
}
|
|
87
|
+
// Dynamic env-var redaction
|
|
88
|
+
const envSecrets = getEnvSecrets();
|
|
89
|
+
for (const secret of envSecrets) {
|
|
90
|
+
if (result.includes(secret)) {
|
|
91
|
+
// Use a safe replacement that doesn't leak length info
|
|
92
|
+
result = result.replaceAll(secret, "[REDACTED:ENV_VALUE]");
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Redact secrets from a structured object (recursively processes string values).
|
|
99
|
+
*/
|
|
100
|
+
export function redactObject(obj) {
|
|
101
|
+
if (typeof obj === "string")
|
|
102
|
+
return redactSecrets(obj);
|
|
103
|
+
if (Array.isArray(obj))
|
|
104
|
+
return obj.map(redactObject);
|
|
105
|
+
if (obj && typeof obj === "object") {
|
|
106
|
+
const result = {};
|
|
107
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
108
|
+
result[key] = redactObject(value);
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
return obj;
|
|
113
|
+
}
|
|
114
|
+
/** Test helper — reset env secrets cache */
|
|
115
|
+
export function _resetEnvSecretsForTesting() {
|
|
116
|
+
_envSecrets = null;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=credentialRedactor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentialRedactor.js","sourceRoot":"","sources":["../../src/security/credentialRedactor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security/ — NodeBench MCP Security Module
|
|
3
|
+
*
|
|
4
|
+
* Single import for all security primitives:
|
|
5
|
+
* import { safePath, safeExec, safeUrl, redactSecrets, auditLog } from '../security/index.js';
|
|
6
|
+
*
|
|
7
|
+
* Addresses Reddit concern: "how many out of these 260 tools steal passwords?"
|
|
8
|
+
* Answer: Zero, with this module enforcing boundaries at 4 layers:
|
|
9
|
+
* 1. Path sandboxing — blocks reads outside project + sensitive files
|
|
10
|
+
* 2. Command sandboxing — allow-list replaces bypassable deny-list
|
|
11
|
+
* 3. URL validation — SSRF protection blocks private IPs/metadata endpoints
|
|
12
|
+
* 4. Credential redaction — strips secrets from all tool outputs
|
|
13
|
+
*/
|
|
14
|
+
export { SecurityError, type SecurityErrorCode } from "./SecurityError.js";
|
|
15
|
+
export { type SecurityConfig, type SecurityMode, getSecurityConfig, setSecurityConfig, _resetSecurityConfigForTesting, } from "./config.js";
|
|
16
|
+
export { safePath, type SafePathOpts } from "./pathSandbox.js";
|
|
17
|
+
export { safeExec, type SafeExecOpts, type ExecResult, _ALLOWED_PREFIXES } from "./commandSandbox.js";
|
|
18
|
+
export { safeUrl, safeUrlWithDnsCheck, type UrlValidationOpts } from "./urlValidator.js";
|
|
19
|
+
export { redactSecrets, redactObject, _resetEnvSecretsForTesting } from "./credentialRedactor.js";
|
|
20
|
+
export { auditLog, getAuditLog, flushAuditLog, type AuditEntry, _resetAuditForTesting, } from "./auditLog.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* security/ — NodeBench MCP Security Module
|
|
3
|
+
*
|
|
4
|
+
* Single import for all security primitives:
|
|
5
|
+
* import { safePath, safeExec, safeUrl, redactSecrets, auditLog } from '../security/index.js';
|
|
6
|
+
*
|
|
7
|
+
* Addresses Reddit concern: "how many out of these 260 tools steal passwords?"
|
|
8
|
+
* Answer: Zero, with this module enforcing boundaries at 4 layers:
|
|
9
|
+
* 1. Path sandboxing — blocks reads outside project + sensitive files
|
|
10
|
+
* 2. Command sandboxing — allow-list replaces bypassable deny-list
|
|
11
|
+
* 3. URL validation — SSRF protection blocks private IPs/metadata endpoints
|
|
12
|
+
* 4. Credential redaction — strips secrets from all tool outputs
|
|
13
|
+
*/
|
|
14
|
+
export { SecurityError } from "./SecurityError.js";
|
|
15
|
+
export { getSecurityConfig, setSecurityConfig, _resetSecurityConfigForTesting, } from "./config.js";
|
|
16
|
+
export { safePath } from "./pathSandbox.js";
|
|
17
|
+
export { safeExec, _ALLOWED_PREFIXES } from "./commandSandbox.js";
|
|
18
|
+
export { safeUrl, safeUrlWithDnsCheck } from "./urlValidator.js";
|
|
19
|
+
export { redactSecrets, redactObject, _resetEnvSecretsForTesting } from "./credentialRedactor.js";
|
|
20
|
+
export { auditLog, getAuditLog, flushAuditLog, _resetAuditForTesting, } from "./auditLog.js";
|
|
21
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/security/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,oBAAoB,CAAC;AAC3E,OAAO,EAGL,iBAAiB,EACjB,iBAAiB,EACjB,8BAA8B,GAC/B,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,QAAQ,EAAqB,MAAM,kBAAkB,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAsC,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACtG,OAAO,EAAE,OAAO,EAAE,mBAAmB,EAA0B,MAAM,mBAAmB,CAAC;AACzF,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,0BAA0B,EAAE,MAAM,yBAAyB,CAAC;AAClG,OAAO,EACL,QAAQ,EACR,WAAW,EACX,aAAa,EAEb,qBAAqB,GACtB,MAAM,eAAe,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pathSandbox.ts — Filesystem boundary enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Every tool that reads/writes files should call safePath() before fs access.
|
|
5
|
+
* Blocks path traversal, symlink escape, and access to sensitive directories.
|
|
6
|
+
*/
|
|
7
|
+
export interface SafePathOpts {
|
|
8
|
+
/** Override default allowed roots from config */
|
|
9
|
+
allowedRoots?: string[];
|
|
10
|
+
/** Allow access to home directory (still blocks sensitive subdirs) */
|
|
11
|
+
allowHome?: boolean;
|
|
12
|
+
/** Allow access to temp directory */
|
|
13
|
+
allowTemp?: boolean;
|
|
14
|
+
/** Skip boundary check entirely (use for known-safe internal paths) */
|
|
15
|
+
skipBoundaryCheck?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Validate and resolve a file path against security boundaries.
|
|
19
|
+
*
|
|
20
|
+
* @throws SecurityError if the path violates boundaries
|
|
21
|
+
* @returns The resolved, validated absolute path
|
|
22
|
+
*/
|
|
23
|
+
export declare function safePath(inputPath: string, opts?: SafePathOpts): string;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pathSandbox.ts — Filesystem boundary enforcement.
|
|
3
|
+
*
|
|
4
|
+
* Every tool that reads/writes files should call safePath() before fs access.
|
|
5
|
+
* Blocks path traversal, symlink escape, and access to sensitive directories.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from "node:fs";
|
|
8
|
+
import * as os from "node:os";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
import { SecurityError } from "./SecurityError.js";
|
|
11
|
+
import { getSecurityConfig } from "./config.js";
|
|
12
|
+
// Sensitive paths that are ALWAYS blocked, even if within allowed roots
|
|
13
|
+
const SENSITIVE_DIRS = [
|
|
14
|
+
".ssh",
|
|
15
|
+
".gnupg",
|
|
16
|
+
".gpg",
|
|
17
|
+
".aws",
|
|
18
|
+
".azure",
|
|
19
|
+
".config/gcloud",
|
|
20
|
+
".kube",
|
|
21
|
+
".docker",
|
|
22
|
+
".ethereum",
|
|
23
|
+
".solana",
|
|
24
|
+
".phantom",
|
|
25
|
+
".bitcoin",
|
|
26
|
+
".metamask",
|
|
27
|
+
".config/gh",
|
|
28
|
+
".git-credentials",
|
|
29
|
+
];
|
|
30
|
+
const SENSITIVE_FILE_PATTERNS = [
|
|
31
|
+
/\.env(\.\w+)?$/i,
|
|
32
|
+
/\.pem$/i,
|
|
33
|
+
/\.key$/i,
|
|
34
|
+
/\.p12$/i,
|
|
35
|
+
/\.pfx$/i,
|
|
36
|
+
/\.jks$/i,
|
|
37
|
+
/id_rsa$/i,
|
|
38
|
+
/id_ed25519$/i,
|
|
39
|
+
/id_ecdsa$/i,
|
|
40
|
+
/\.npmrc$/i,
|
|
41
|
+
/\.netrc$/i,
|
|
42
|
+
/credentials\.json$/i,
|
|
43
|
+
/service[_-]?account.*\.json$/i,
|
|
44
|
+
/wallet.*\.json$/i,
|
|
45
|
+
/keystore.*\.json$/i,
|
|
46
|
+
/seed(phrase)?.*\.(txt|json|md)$/i,
|
|
47
|
+
/mnemonic.*\.(txt|json|md)$/i,
|
|
48
|
+
/private[_-]?key/i,
|
|
49
|
+
];
|
|
50
|
+
function expandTilde(p) {
|
|
51
|
+
if (!p)
|
|
52
|
+
return p;
|
|
53
|
+
if (p === "~")
|
|
54
|
+
return os.homedir();
|
|
55
|
+
if (p.startsWith("~/") || p.startsWith("~\\"))
|
|
56
|
+
return path.join(os.homedir(), p.slice(2));
|
|
57
|
+
return p;
|
|
58
|
+
}
|
|
59
|
+
function isSensitivePath(resolved) {
|
|
60
|
+
const home = os.homedir();
|
|
61
|
+
const relative = path.relative(home, resolved);
|
|
62
|
+
// Check sensitive directories
|
|
63
|
+
for (const dir of SENSITIVE_DIRS) {
|
|
64
|
+
const sensitiveFull = path.join(home, dir);
|
|
65
|
+
if (resolved === sensitiveFull ||
|
|
66
|
+
resolved.startsWith(sensitiveFull + path.sep)) {
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// Check sensitive file patterns
|
|
71
|
+
const basename = path.basename(resolved);
|
|
72
|
+
for (const pattern of SENSITIVE_FILE_PATTERNS) {
|
|
73
|
+
if (pattern.test(basename))
|
|
74
|
+
return true;
|
|
75
|
+
}
|
|
76
|
+
// Block /etc/shadow, /etc/passwd on Unix
|
|
77
|
+
if (process.platform !== "win32") {
|
|
78
|
+
if (resolved === "/etc/shadow" || resolved === "/etc/passwd")
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
function isWithinRoots(resolved, roots) {
|
|
84
|
+
for (const root of roots) {
|
|
85
|
+
const normalizedRoot = path.resolve(root);
|
|
86
|
+
if (resolved === normalizedRoot ||
|
|
87
|
+
resolved.startsWith(normalizedRoot + path.sep)) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Validate and resolve a file path against security boundaries.
|
|
95
|
+
*
|
|
96
|
+
* @throws SecurityError if the path violates boundaries
|
|
97
|
+
* @returns The resolved, validated absolute path
|
|
98
|
+
*/
|
|
99
|
+
export function safePath(inputPath, opts) {
|
|
100
|
+
const config = getSecurityConfig();
|
|
101
|
+
// In permissive mode, just resolve and return
|
|
102
|
+
if (config.mode === "permissive") {
|
|
103
|
+
const expanded = expandTilde(String(inputPath ?? "").trim());
|
|
104
|
+
if (!expanded)
|
|
105
|
+
throw new Error("path is required");
|
|
106
|
+
return path.isAbsolute(expanded)
|
|
107
|
+
? expanded
|
|
108
|
+
: path.resolve(process.cwd(), expanded);
|
|
109
|
+
}
|
|
110
|
+
const raw = String(inputPath ?? "").trim();
|
|
111
|
+
if (!raw)
|
|
112
|
+
throw new Error("path is required");
|
|
113
|
+
const expanded = expandTilde(raw);
|
|
114
|
+
const resolved = path.isAbsolute(expanded)
|
|
115
|
+
? path.resolve(expanded)
|
|
116
|
+
: path.resolve(process.cwd(), expanded);
|
|
117
|
+
// Always check sensitive paths (even in audit_only mode this is a hard block)
|
|
118
|
+
if (isSensitivePath(resolved)) {
|
|
119
|
+
throw new SecurityError("PATH_SENSITIVE", `Access denied: ${path.basename(resolved)} is a sensitive file/directory`);
|
|
120
|
+
}
|
|
121
|
+
// Skip boundary check if explicitly requested (for internal paths)
|
|
122
|
+
if (opts?.skipBoundaryCheck)
|
|
123
|
+
return resolved;
|
|
124
|
+
// Build allowed roots
|
|
125
|
+
const roots = [...(opts?.allowedRoots ?? config.allowedRoots)];
|
|
126
|
+
if (opts?.allowHome)
|
|
127
|
+
roots.push(os.homedir());
|
|
128
|
+
if (opts?.allowTemp)
|
|
129
|
+
roots.push(os.tmpdir());
|
|
130
|
+
// Boundary check
|
|
131
|
+
if (!isWithinRoots(resolved, roots)) {
|
|
132
|
+
if (config.mode === "audit_only") {
|
|
133
|
+
// Log but don't block
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
throw new SecurityError("PATH_TRAVERSAL", `Path "${raw}" resolves outside allowed boundaries`);
|
|
137
|
+
}
|
|
138
|
+
// Symlink check — resolve the real path and re-verify
|
|
139
|
+
try {
|
|
140
|
+
if (fs.existsSync(resolved)) {
|
|
141
|
+
const stat = fs.lstatSync(resolved);
|
|
142
|
+
if (stat.isSymbolicLink()) {
|
|
143
|
+
const realPath = fs.realpathSync(resolved);
|
|
144
|
+
if (isSensitivePath(realPath)) {
|
|
145
|
+
throw new SecurityError("PATH_SYMLINK", `Symlink "${raw}" resolves to sensitive path`);
|
|
146
|
+
}
|
|
147
|
+
if (!isWithinRoots(realPath, roots) && config.mode !== "audit_only") {
|
|
148
|
+
throw new SecurityError("PATH_SYMLINK", `Symlink "${raw}" resolves outside allowed boundaries`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
catch (e) {
|
|
154
|
+
if (e instanceof SecurityError)
|
|
155
|
+
throw e;
|
|
156
|
+
// File doesn't exist yet (write case) — boundary check above is sufficient
|
|
157
|
+
}
|
|
158
|
+
return resolved;
|
|
159
|
+
}
|
|
160
|
+
//# sourceMappingURL=pathSandbox.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pathSandbox.js","sourceRoot":"","sources":["../../src/security/pathSandbox.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* urlValidator.ts — SSRF protection for HTTP tools.
|
|
3
|
+
*
|
|
4
|
+
* Blocks private IPs, internal hostnames, and dangerous schemes.
|
|
5
|
+
*/
|
|
6
|
+
export interface UrlValidationOpts {
|
|
7
|
+
/** Allow requests to private/internal IPs (for known internal services) */
|
|
8
|
+
allowPrivate?: boolean;
|
|
9
|
+
/** Additional blocked hostnames */
|
|
10
|
+
blockedHostnames?: string[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Validate a URL for SSRF safety.
|
|
14
|
+
*
|
|
15
|
+
* @throws SecurityError if URL targets private infrastructure
|
|
16
|
+
* @returns The validated URL string
|
|
17
|
+
*/
|
|
18
|
+
export declare function safeUrl(url: string, opts?: UrlValidationOpts): string;
|
|
19
|
+
/**
|
|
20
|
+
* Async version that also checks DNS resolution for rebinding attacks.
|
|
21
|
+
* Use this for user-facing tools where the hostname isn't known-safe.
|
|
22
|
+
*/
|
|
23
|
+
export declare function safeUrlWithDnsCheck(url: string, opts?: UrlValidationOpts): Promise<string>;
|