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.
Files changed (94) hide show
  1. package/README.md +14 -6
  2. package/dist/engine/server.js +14 -4
  3. package/dist/engine/server.js.map +1 -1
  4. package/dist/index.js +1581 -670
  5. package/dist/index.js.map +1 -1
  6. package/dist/security/SecurityError.d.ts +18 -0
  7. package/dist/security/SecurityError.js +22 -0
  8. package/dist/security/SecurityError.js.map +1 -0
  9. package/dist/security/__tests__/security.test.d.ts +8 -0
  10. package/dist/security/__tests__/security.test.js +295 -0
  11. package/dist/security/__tests__/security.test.js.map +1 -0
  12. package/dist/security/auditLog.d.ts +36 -0
  13. package/dist/security/auditLog.js +178 -0
  14. package/dist/security/auditLog.js.map +1 -0
  15. package/dist/security/commandSandbox.d.ts +33 -0
  16. package/dist/security/commandSandbox.js +159 -0
  17. package/dist/security/commandSandbox.js.map +1 -0
  18. package/dist/security/config.d.ts +23 -0
  19. package/dist/security/config.js +43 -0
  20. package/dist/security/config.js.map +1 -0
  21. package/dist/security/credentialRedactor.d.ts +22 -0
  22. package/dist/security/credentialRedactor.js +118 -0
  23. package/dist/security/credentialRedactor.js.map +1 -0
  24. package/dist/security/index.d.ts +20 -0
  25. package/dist/security/index.js +21 -0
  26. package/dist/security/index.js.map +1 -0
  27. package/dist/security/pathSandbox.d.ts +23 -0
  28. package/dist/security/pathSandbox.js +160 -0
  29. package/dist/security/pathSandbox.js.map +1 -0
  30. package/dist/security/urlValidator.d.ts +23 -0
  31. package/dist/security/urlValidator.js +125 -0
  32. package/dist/security/urlValidator.js.map +1 -0
  33. package/dist/tools/agentBootstrapTools.js +22 -29
  34. package/dist/tools/agentBootstrapTools.js.map +1 -1
  35. package/dist/tools/contextSandboxTools.js +7 -9
  36. package/dist/tools/contextSandboxTools.js.map +1 -1
  37. package/dist/tools/deepSimTools.d.ts +2 -0
  38. package/dist/tools/deepSimTools.js +404 -0
  39. package/dist/tools/deepSimTools.js.map +1 -0
  40. package/dist/tools/dimensionTools.d.ts +2 -0
  41. package/dist/tools/dimensionTools.js +246 -0
  42. package/dist/tools/dimensionTools.js.map +1 -0
  43. package/dist/tools/executionTraceTools.d.ts +2 -0
  44. package/dist/tools/executionTraceTools.js +446 -0
  45. package/dist/tools/executionTraceTools.js.map +1 -0
  46. package/dist/tools/founderTools.d.ts +13 -0
  47. package/dist/tools/founderTools.js +595 -0
  48. package/dist/tools/founderTools.js.map +1 -0
  49. package/dist/tools/gitWorkflowTools.js +14 -10
  50. package/dist/tools/gitWorkflowTools.js.map +1 -1
  51. package/dist/tools/githubTools.js +19 -2
  52. package/dist/tools/githubTools.js.map +1 -1
  53. package/dist/tools/index.d.ts +87 -0
  54. package/dist/tools/index.js +102 -0
  55. package/dist/tools/index.js.map +1 -0
  56. package/dist/tools/localFileTools.js +24 -12
  57. package/dist/tools/localFileTools.js.map +1 -1
  58. package/dist/tools/memoryDecay.d.ts +70 -0
  59. package/dist/tools/memoryDecay.js +247 -0
  60. package/dist/tools/memoryDecay.js.map +1 -0
  61. package/dist/tools/missionHarnessTools.d.ts +32 -0
  62. package/dist/tools/missionHarnessTools.js +972 -0
  63. package/dist/tools/missionHarnessTools.js.map +1 -0
  64. package/dist/tools/observabilityTools.d.ts +15 -0
  65. package/dist/tools/observabilityTools.js +787 -0
  66. package/dist/tools/observabilityTools.js.map +1 -0
  67. package/dist/tools/openclawTools.js +151 -36
  68. package/dist/tools/openclawTools.js.map +1 -1
  69. package/dist/tools/progressiveDiscoveryTools.js +5 -4
  70. package/dist/tools/progressiveDiscoveryTools.js.map +1 -1
  71. package/dist/tools/qualityGateTools.js +118 -2
  72. package/dist/tools/qualityGateTools.js.map +1 -1
  73. package/dist/tools/rssTools.js +3 -0
  74. package/dist/tools/rssTools.js.map +1 -1
  75. package/dist/tools/scraplingTools.js +15 -0
  76. package/dist/tools/scraplingTools.js.map +1 -1
  77. package/dist/tools/seoTools.js +66 -1
  78. package/dist/tools/seoTools.js.map +1 -1
  79. package/dist/tools/sessionMemoryTools.js +50 -11
  80. package/dist/tools/sessionMemoryTools.js.map +1 -1
  81. package/dist/tools/temporalIntelligenceTools.d.ts +12 -0
  82. package/dist/tools/temporalIntelligenceTools.js +1068 -0
  83. package/dist/tools/temporalIntelligenceTools.js.map +1 -0
  84. package/dist/tools/toolRegistry.d.ts +19 -0
  85. package/dist/tools/toolRegistry.js +857 -31
  86. package/dist/tools/toolRegistry.js.map +1 -1
  87. package/dist/tools/webTools.js +14 -1
  88. package/dist/tools/webTools.js.map +1 -1
  89. package/dist/tools/webmcpTools.js +13 -2
  90. package/dist/tools/webmcpTools.js.map +1 -1
  91. package/dist/toolsetRegistry.js +13 -0
  92. package/dist/toolsetRegistry.js.map +1 -1
  93. package/dist/types.d.ts +10 -0
  94. 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>;