thumbgate 1.27.17 → 1.27.18
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/.claude-plugin/marketplace.json +85 -0
- package/.claude-plugin/plugin.json +3 -4
- package/.well-known/llms.txt +12 -33
- package/.well-known/mcp/server-card.json +8 -8
- package/README.md +30 -221
- package/adapters/chatgpt/openapi.yaml +1695 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/gemini/function-declarations.json +0 -1
- package/adapters/mcp/server-stdio.js +11 -263
- package/adapters/opencode/opencode.json +1 -1
- package/bench/thumbgate-bench.json +2 -2
- package/bin/cli.js +122 -1421
- package/bin/postinstall.js +27 -11
- package/config/gate-templates.json +0 -216
- package/config/gates/claim-verification.json +0 -12
- package/config/gates/default.json +2 -31
- package/config/github-about.json +2 -2
- package/config/mcp-allowlists.json +13 -23
- package/config/merge-quality-checks.json +1 -0
- package/config/model-candidates.json +6 -121
- package/openapi/openapi.yaml +0 -12
- package/package.json +99 -229
- package/public/blog.html +4 -4
- package/public/codex-plugin.html +20 -72
- package/public/compare.html +8 -31
- package/public/dashboard.html +166 -930
- package/public/federal.html +2 -2
- package/public/guide.html +9 -33
- package/public/index.html +116 -495
- package/public/learn.html +18 -191
- package/public/lessons.html +10 -168
- package/public/numbers.html +7 -7
- package/public/pro.html +11 -34
- package/scripts/agent-memory-lifecycle.js +0 -211
- package/scripts/agent-readiness.js +3 -167
- package/scripts/agent-reward-model.js +1 -53
- package/scripts/auto-promote-gates.js +10 -82
- package/scripts/auto-wire-hooks.js +0 -14
- package/scripts/billing.js +1 -93
- package/scripts/build-metadata.js +10 -50
- package/scripts/cli-feedback.js +6 -18
- package/scripts/cli-schema.js +0 -90
- package/scripts/cli-telemetry.js +1 -6
- package/scripts/commercial-offer.js +2 -82
- package/scripts/context-manager.js +6 -74
- package/scripts/dashboard.js +2 -68
- package/scripts/durability/step.js +3 -3
- package/scripts/export-databricks-bundle.js +1 -5
- package/scripts/export-dpo-pairs.js +2 -7
- package/scripts/feedback-loop.js +1 -145
- package/scripts/feedback-quality.js +0 -87
- package/scripts/filesystem-search.js +10 -35
- package/scripts/gate-stats.js +0 -89
- package/scripts/gates-engine.js +85 -1105
- package/scripts/gemini-embedding-policy.js +1 -2
- package/scripts/hook-runtime.js +14 -20
- package/scripts/hook-thumbgate-cache-updater.js +2 -18
- package/scripts/hybrid-feedback-context.js +7 -142
- package/scripts/lesson-inference.js +3 -8
- package/scripts/lesson-search.js +1 -17
- package/scripts/license.js +1 -0
- package/scripts/llm-client.js +9 -390
- package/scripts/local-model-profile.js +8 -15
- package/scripts/memory-scope-readiness.js +0 -159
- package/scripts/meta-agent-loop.js +0 -36
- package/scripts/operational-integrity.js +5 -39
- package/scripts/oss-pr-opportunity-scout.js +5 -35
- package/scripts/plausible-server-events.js +6 -9
- package/scripts/pro-local-dashboard.js +4 -4
- package/scripts/proxy-pointer-rag-guardrails.js +1 -42
- package/scripts/rate-limiter.js +13 -64
- package/scripts/secret-scanner.js +5 -44
- package/scripts/security-scanner.js +10 -260
- package/scripts/self-distill-agent.js +1 -3
- package/scripts/seo-gsd.js +2 -916
- package/scripts/statusline-cache-path.js +2 -17
- package/scripts/statusline-local-stats.js +1 -9
- package/scripts/statusline-meta.js +2 -28
- package/scripts/statusline.sh +4 -20
- package/scripts/telemetry-analytics.js +0 -357
- package/scripts/thompson-sampling.js +10 -31
- package/scripts/thumbgate-bench.js +1 -16
- package/scripts/thumbgate-search.js +19 -85
- package/scripts/tool-registry.js +1 -169
- package/scripts/vector-store.js +0 -45
- package/scripts/workflow-sentinel.js +53 -286
- package/scripts/workspace-evolver.js +2 -62
- package/src/api/server.js +290 -2578
- package/.claude/commands/dashboard.md +0 -15
- package/.claude/commands/thumbgate-blocked.md +0 -27
- package/.claude/commands/thumbgate-dashboard.md +0 -15
- package/.claude/commands/thumbgate-doctor.md +0 -30
- package/.claude/commands/thumbgate-guard.md +0 -36
- package/.claude/commands/thumbgate-protect.md +0 -30
- package/.claude/commands/thumbgate-rules.md +0 -30
- package/.well-known/agentic-verify.txt +0 -1
- package/adapters/gcp/dfcx-webhook-gate.js +0 -295
- package/adapters/letta/README.md +0 -41
- package/adapters/letta/thumbgate-letta-adapter.js +0 -133
- package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
- package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
- package/bench/observability-eval-suite.json +0 -26
- package/bin/dashboard-cli.js +0 -7
- package/commands/dashboard.md +0 -15
- package/commands/thumbgate-dashboard.md +0 -15
- package/config/builtin-lessons.json +0 -23
- package/config/gate-classifier-routing.json +0 -98
- package/config/post-deploy-marketing-pages.json +0 -61
- package/public/about.html +0 -162
- package/public/agent-manager.html +0 -179
- package/public/agents-cost-savings.html +0 -153
- package/public/ai-malpractice-prevention.html +0 -818
- package/public/assets/brand/github-social-preview.png +0 -0
- package/public/assets/brand/thumbgate-icon-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
- package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
- package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
- package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
- package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
- package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
- package/public/assets/brand/thumbgate-mark-team.svg +0 -26
- package/public/assets/brand/thumbgate-mark.svg +0 -21
- package/public/assets/brand/thumbgate-wordmark.svg +0 -20
- package/public/assets/claude-thumbgate-statusbar.svg +0 -8
- package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
- package/public/assets/legal-intake-control-flow.svg +0 -66
- package/public/brand/thumbgate-mark.svg +0 -19
- package/public/brand/thumbgate-og.svg +0 -16
- package/public/chatgpt-app.html +0 -330
- package/public/codex-enterprise.html +0 -123
- package/public/js/buyer-intent.js +0 -672
- package/public/pricing.html +0 -399
- package/scripts/action-receipts.js +0 -324
- package/scripts/activation-quickstart.js +0 -187
- package/scripts/agent-operations-planner.js +0 -621
- package/scripts/ai-component-inventory.js +0 -367
- package/scripts/async-eval-observability.js +0 -236
- package/scripts/audit.js +0 -65
- package/scripts/aws-blocks-guardrails.js +0 -228
- package/scripts/classifier-routing.js +0 -130
- package/scripts/dashboard-chat.js +0 -332
- package/scripts/feedback-aggregate.js +0 -281
- package/scripts/feedback-sanitizer.js +0 -105
- package/scripts/hob-pack.js +0 -591
- package/scripts/hook-stop-anti-claim.js +0 -301
- package/scripts/install-shim.js +0 -87
- package/scripts/mcp-oauth.js +0 -293
- package/scripts/noop-detect.js +0 -285
- package/scripts/omlx-smoke.js +0 -192
- package/scripts/parallel-workflow-orchestrator.js +0 -293
- package/scripts/plan-gate.js +0 -243
- package/scripts/plausible-domain-config.js +0 -99
- package/scripts/qa-scenario-planner.js +0 -136
- package/scripts/repeat-metric.js +0 -137
- package/scripts/secret-fixture-tokens.js +0 -61
- package/scripts/secret-redaction.js +0 -166
- package/scripts/self-harness-optimizer.js +0 -141
- package/scripts/self-healing-check.js +0 -193
- package/scripts/silent-failure-cluster.js +0 -531
- package/scripts/statusline-cache-read.js +0 -57
- package/scripts/sync-telemetry-from-prod.js +0 -374
- package/scripts/tool-contract-validator.js +0 -76
- package/scripts/trajectory-scorer.js +0 -63
- package/scripts/verify-marketing-pages-deployed.js +0 -195
- package/scripts/visitor-journey.js +0 -172
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
-
const { loadOptionalModule } = require("./private-core-boundary");
|
|
5
|
-
|
|
6
|
-
|
|
7
4
|
/**
|
|
8
5
|
* Security Scanner — OWASP-aware static analysis for PreToolUse checks.
|
|
9
6
|
*
|
|
@@ -19,10 +16,6 @@ const { loadOptionalModule } = require("./private-core-boundary");
|
|
|
19
16
|
const fs = require('fs');
|
|
20
17
|
const path = require('path');
|
|
21
18
|
const { recordAuditEvent, auditToFeedback } = require('./audit-trail');
|
|
22
|
-
const { scanInstallCommand, detectSlopsquat } = loadOptionalModule('./slopsquat-guard', () => ({
|
|
23
|
-
scanInstallCommand: () => ({ detected: false, findings: [] }),
|
|
24
|
-
detectSlopsquat: () => null,
|
|
25
|
-
}));
|
|
26
19
|
|
|
27
20
|
// ---------------------------------------------------------------------------
|
|
28
21
|
// Vulnerability pattern definitions (OWASP Top 10 + supply chain)
|
|
@@ -37,7 +30,6 @@ const VULN_PATTERNS = [
|
|
|
37
30
|
label: 'Command injection via unsanitized input',
|
|
38
31
|
regex: /\bexec(?:Sync)?\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*(?:req\.|input|args|params|query|body|user))/g,
|
|
39
32
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
40
|
-
remediation: 'Avoid passing unsanitized input to child_process.exec/execSync. Use execFile or spawn with args passed as an array.',
|
|
41
33
|
},
|
|
42
34
|
{
|
|
43
35
|
id: 'shell-interpolation',
|
|
@@ -46,7 +38,6 @@ const VULN_PATTERNS = [
|
|
|
46
38
|
label: 'Shell command with string interpolation',
|
|
47
39
|
regex: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{[^}]*(?:req\.|input|args|params|query|body|user|process\.env)/g,
|
|
48
40
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
49
|
-
remediation: 'Use child_process.execFile or child_process.spawn with an array of arguments to avoid shell command interpolation.',
|
|
50
41
|
},
|
|
51
42
|
{
|
|
52
43
|
id: 'sql-injection',
|
|
@@ -55,7 +46,6 @@ const VULN_PATTERNS = [
|
|
|
55
46
|
label: 'Potential SQL injection via string concatenation',
|
|
56
47
|
regex: /(?:query|execute|run|all|get)\s*\(\s*(?:`[^`]*\$\{|['"][^'"]*['"]\s*\+\s*(?:req\.|input|args|params|query|body|user))/g,
|
|
57
48
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs', '.py'],
|
|
58
|
-
remediation: 'Use parameterized queries or prepared statements instead of dynamic SQL string concatenation.',
|
|
59
49
|
},
|
|
60
50
|
{
|
|
61
51
|
id: 'eval-usage',
|
|
@@ -64,7 +54,6 @@ const VULN_PATTERNS = [
|
|
|
64
54
|
label: 'Dynamic code execution (eval/Function constructor)',
|
|
65
55
|
regex: /\b(?:eval|new\s+Function)\s*\([^)]*(?:req\.|input|args|params|query|body|user)/g,
|
|
66
56
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
67
|
-
remediation: 'Use JSON.parse() or a safe parser library instead of eval() or dynamic Function constructors.',
|
|
68
57
|
},
|
|
69
58
|
|
|
70
59
|
// XSS
|
|
@@ -75,7 +64,6 @@ const VULN_PATTERNS = [
|
|
|
75
64
|
label: 'Potential XSS via innerHTML assignment',
|
|
76
65
|
regex: /\.innerHTML\s*=\s*(?!['"]<(?:div|span|p|br|hr)\s*\/?>['"])/g,
|
|
77
66
|
fileTypes: ['.js', '.ts', '.jsx', '.tsx', '.mjs'],
|
|
78
|
-
remediation: 'Use element.textContent or element.innerText instead of innerHTML to prevent cross-site scripting (XSS).',
|
|
79
67
|
},
|
|
80
68
|
{
|
|
81
69
|
id: 'xss-dangerously-set',
|
|
@@ -84,7 +72,6 @@ const VULN_PATTERNS = [
|
|
|
84
72
|
label: 'React dangerouslySetInnerHTML with dynamic content',
|
|
85
73
|
regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:\s*(?!['"])/g,
|
|
86
74
|
fileTypes: ['.jsx', '.tsx', '.js', '.ts'],
|
|
87
|
-
remediation: 'Ensure dynamic content passed to dangerouslySetInnerHTML is sanitized using DOMPurify or equivalent.',
|
|
88
75
|
},
|
|
89
76
|
|
|
90
77
|
// Path traversal
|
|
@@ -95,7 +82,6 @@ const VULN_PATTERNS = [
|
|
|
95
82
|
label: 'Path traversal via unsanitized user input',
|
|
96
83
|
regex: /path\.(?:join|resolve)\s*\([^)]*(?:req\.|input|args|params|query|body|user)/g,
|
|
97
84
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
98
|
-
remediation: 'Sanitize path input using path.basename() or validate the path against an explicit list of allowed directories.',
|
|
99
85
|
},
|
|
100
86
|
{
|
|
101
87
|
id: 'path-traversal-direct',
|
|
@@ -104,7 +90,6 @@ const VULN_PATTERNS = [
|
|
|
104
90
|
label: 'Direct file read with user-controlled path',
|
|
105
91
|
regex: /fs\.(?:readFile(?:Sync)?|createReadStream)\s*\(\s*(?:req\.|input|args|params|query|body|user)/g,
|
|
106
92
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
107
|
-
remediation: 'Validate input paths and restrict file system access to a sandboxed directory.',
|
|
108
93
|
},
|
|
109
94
|
|
|
110
95
|
// Prototype pollution
|
|
@@ -115,7 +100,6 @@ const VULN_PATTERNS = [
|
|
|
115
100
|
label: 'Potential prototype pollution via recursive merge',
|
|
116
101
|
regex: /(?:__proto__|constructor\s*\[\s*['"]prototype['"]\s*\]|Object\.assign\s*\(\s*\{\s*\})/g,
|
|
117
102
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
118
|
-
remediation: 'Avoid recursive object mergers without checking for __proto__ or constructor keys, or use Object.create(null).',
|
|
119
103
|
},
|
|
120
104
|
|
|
121
105
|
// Insecure crypto
|
|
@@ -126,7 +110,6 @@ const VULN_PATTERNS = [
|
|
|
126
110
|
label: 'Weak hash algorithm (MD5/SHA1) for security use',
|
|
127
111
|
regex: /createHash\s*\(\s*['"](?:md5|sha1)['"]\s*\)/gi,
|
|
128
112
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
129
|
-
remediation: 'Use SHA-256 or SHA-512 (e.g. crypto.createHash("sha256")) instead of MD5 or SHA-1 for security use cases.',
|
|
130
113
|
},
|
|
131
114
|
{
|
|
132
115
|
id: 'hardcoded-secret',
|
|
@@ -135,7 +118,6 @@ const VULN_PATTERNS = [
|
|
|
135
118
|
label: 'Hardcoded secret/password in source code',
|
|
136
119
|
regex: /(?:password|secret|apiKey|api_key|token)\s*[:=]\s*['"][A-Za-z0-9+/=_-]{12,}['"]/g,
|
|
137
120
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs', '.py', '.go', '.java'],
|
|
138
|
-
remediation: 'Move hardcoded secrets to environment variables or use a secure secrets manager.',
|
|
139
121
|
},
|
|
140
122
|
|
|
141
123
|
// SSRF
|
|
@@ -146,7 +128,6 @@ const VULN_PATTERNS = [
|
|
|
146
128
|
label: 'Potential SSRF via user-controlled URL',
|
|
147
129
|
regex: /(?:fetch|axios|got|request|https?\.(?:get|request))\s*\(\s*(?:`[^`]*\$\{|(?:req\.|input|args|params|query|body|user))/g,
|
|
148
130
|
fileTypes: ['.js', '.ts', '.mjs', '.cjs'],
|
|
149
|
-
remediation: 'Validate the destination host against a strict whitelist of allowed domains and block requests to internal IPs.',
|
|
150
131
|
},
|
|
151
132
|
|
|
152
133
|
// Insecure deserialization
|
|
@@ -157,16 +138,6 @@ const VULN_PATTERNS = [
|
|
|
157
138
|
label: 'Unsafe deserialization of untrusted data',
|
|
158
139
|
regex: /(?:unserialize|yaml\.load\s*\((?!.*Loader\s*=\s*yaml\.SafeLoader)|pickle\.loads?|Marshal\.load)/g,
|
|
159
140
|
fileTypes: ['.js', '.ts', '.py', '.rb'],
|
|
160
|
-
remediation: 'Use safe parsing libraries or options (such as yaml.safeLoad) instead of unsafe deserialization/eval-like loaders.',
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
id: 'badhost-url-confusion',
|
|
164
|
-
category: 'host-header',
|
|
165
|
-
severity: 'high',
|
|
166
|
-
label: 'Potential BadHost-style host or URL confusion in AI service',
|
|
167
|
-
regex: /\b(?:request\.url(?:\.path)?|url_for\s*\([^)]*_external\s*=\s*True|headers\s*\[\s*['"](?:host|x-forwarded-host)['"]\s*\])/gi,
|
|
168
|
-
fileTypes: ['.py'],
|
|
169
|
-
remediation: 'Verify Host and X-Forwarded-Host headers against an approved whitelist before using them for routing or URL generation.',
|
|
170
141
|
},
|
|
171
142
|
];
|
|
172
143
|
|
|
@@ -180,7 +151,6 @@ const SUPPLY_CHAIN_PATTERNS = [
|
|
|
180
151
|
category: 'supply-chain',
|
|
181
152
|
severity: 'high',
|
|
182
153
|
label: 'Potentially typosquatted package name',
|
|
183
|
-
remediation: 'Verify spelling and authenticity of package name. If typosquatted, run: npm uninstall <package>.',
|
|
184
154
|
// Common typosquat indicators: single-char substitutions of popular packages
|
|
185
155
|
knownSafe: new Set([
|
|
186
156
|
'express', 'lodash', 'axios', 'react', 'vue', 'angular', 'moment',
|
|
@@ -194,7 +164,6 @@ const SUPPLY_CHAIN_PATTERNS = [
|
|
|
194
164
|
category: 'supply-chain',
|
|
195
165
|
severity: 'critical',
|
|
196
166
|
label: 'Suspicious install script in package.json',
|
|
197
|
-
remediation: 'Remove the pre/postinstall script, or run package installation with --ignore-scripts.',
|
|
198
167
|
regex: /["'](?:pre|post)?install["']\s*:\s*["'](?:.*(?:curl|wget|nc\s|bash\s|sh\s|eval|exec|child_process))/g,
|
|
199
168
|
},
|
|
200
169
|
{
|
|
@@ -202,79 +171,10 @@ const SUPPLY_CHAIN_PATTERNS = [
|
|
|
202
171
|
category: 'supply-chain',
|
|
203
172
|
severity: 'medium',
|
|
204
173
|
label: 'Wildcard or latest version in dependency',
|
|
205
|
-
remediation: 'Specify a concrete version constraint (e.g., "^1.0.0") instead of a wildcard or latest.',
|
|
206
174
|
regex: /["'](?:dependencies|devDependencies|peerDependencies)["'][\s\S]{0,500}?["'][^"']+["']\s*:\s*["'](?:\*|latest|>=)/g,
|
|
207
175
|
},
|
|
208
176
|
];
|
|
209
177
|
|
|
210
|
-
/**
|
|
211
|
-
* Simple static analysis check (reachability/usage check) to see if a package is imported.
|
|
212
|
-
* @param {string} pkg - The package name to search for
|
|
213
|
-
* @param {string} rootDir - Root directory to walk
|
|
214
|
-
* @returns {boolean}
|
|
215
|
-
*/
|
|
216
|
-
function isPackageImported(pkg, rootDir = process.cwd()) {
|
|
217
|
-
const IGNORED_DIRS = new Set([
|
|
218
|
-
'node_modules', '.git', 'dist', 'coverage', '.planning', '.artifacts', '.gemini', '.antigravitycli'
|
|
219
|
-
]);
|
|
220
|
-
const FILE_EXTS = new Set(['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs', '.py']);
|
|
221
|
-
|
|
222
|
-
// Escape package name for regex
|
|
223
|
-
const escapedPkg = pkg.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
224
|
-
const importRegex = new RegExp(
|
|
225
|
-
`(?:require\\s*\\(\\s*['"]${escapedPkg}['"]\\s*\\)|from\\s*['"]${escapedPkg}['"]|import\\s*\\(\\s*['"]${escapedPkg}['"]\\s*\\)|import\\s+['"]${escapedPkg}['"])`,
|
|
226
|
-
'i'
|
|
227
|
-
);
|
|
228
|
-
|
|
229
|
-
let found = false;
|
|
230
|
-
let fileCount = 0;
|
|
231
|
-
const maxFiles = 200; // safety limit to keep it fast (<50ms)
|
|
232
|
-
|
|
233
|
-
function walk(dir) {
|
|
234
|
-
if (found || fileCount >= maxFiles) return;
|
|
235
|
-
let files;
|
|
236
|
-
try {
|
|
237
|
-
files = fs.readdirSync(dir);
|
|
238
|
-
} catch {
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
for (const file of files) {
|
|
243
|
-
if (found || fileCount >= maxFiles) return;
|
|
244
|
-
const fullPath = path.join(dir, file);
|
|
245
|
-
let stat;
|
|
246
|
-
try {
|
|
247
|
-
stat = fs.statSync(fullPath);
|
|
248
|
-
} catch {
|
|
249
|
-
continue;
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (stat.isDirectory()) {
|
|
253
|
-
if (!IGNORED_DIRS.has(file)) {
|
|
254
|
-
walk(fullPath);
|
|
255
|
-
}
|
|
256
|
-
} else if (stat.isFile()) {
|
|
257
|
-
const ext = path.extname(file).toLowerCase();
|
|
258
|
-
if (FILE_EXTS.has(ext)) {
|
|
259
|
-
fileCount++;
|
|
260
|
-
try {
|
|
261
|
-
const content = fs.readFileSync(fullPath, 'utf8');
|
|
262
|
-
if (importRegex.test(content)) {
|
|
263
|
-
found = true;
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
} catch {
|
|
267
|
-
// ignore read errors
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
walk(rootDir);
|
|
275
|
-
return found;
|
|
276
|
-
}
|
|
277
|
-
|
|
278
178
|
// ---------------------------------------------------------------------------
|
|
279
179
|
// Core scanning functions
|
|
280
180
|
// ---------------------------------------------------------------------------
|
|
@@ -312,7 +212,6 @@ function scanCode(content, filePath = '') {
|
|
|
312
212
|
line: lineNumber,
|
|
313
213
|
match: match[0].slice(0, 120),
|
|
314
214
|
path: filePath,
|
|
315
|
-
remediation: pattern.remediation,
|
|
316
215
|
});
|
|
317
216
|
// Only report first match per pattern per file to avoid noise
|
|
318
217
|
break;
|
|
@@ -325,22 +224,6 @@ function scanCode(content, filePath = '') {
|
|
|
325
224
|
};
|
|
326
225
|
}
|
|
327
226
|
|
|
328
|
-
/**
|
|
329
|
-
* Scan Python / AI-service code for BadHost-style URL and host-header confusion.
|
|
330
|
-
* This is deliberately narrow and evidence-oriented: it does not claim a CVE,
|
|
331
|
-
* it flags code that should prove canonical host handling before deployment.
|
|
332
|
-
* @param {string} content
|
|
333
|
-
* @param {string} filePath
|
|
334
|
-
* @returns {{ detected: boolean, findings: Array<Object> }}
|
|
335
|
-
*/
|
|
336
|
-
function scanBadHostExposure(content, filePath = '') {
|
|
337
|
-
const result = scanCode(content, filePath);
|
|
338
|
-
return {
|
|
339
|
-
detected: result.findings.some((finding) => finding.id === 'badhost-url-confusion'),
|
|
340
|
-
findings: result.findings.filter((finding) => finding.id === 'badhost-url-confusion'),
|
|
341
|
-
};
|
|
342
|
-
}
|
|
343
|
-
|
|
344
227
|
/**
|
|
345
228
|
* Scan dependency changes in package.json mutations.
|
|
346
229
|
* @param {string} oldContent - Previous package.json content (empty string if new file)
|
|
@@ -373,10 +256,6 @@ function scanDependencyChange(oldContent, newContent) {
|
|
|
373
256
|
|
|
374
257
|
for (const [pkg, version] of Object.entries(newDeps)) {
|
|
375
258
|
if (!oldDeps[pkg]) {
|
|
376
|
-
// Run usage check (reachability)
|
|
377
|
-
const reachable = isPackageImported(pkg);
|
|
378
|
-
const reachability = reachable ? 'imported' : 'unimported';
|
|
379
|
-
|
|
380
259
|
// New dependency added — check for red flags
|
|
381
260
|
if (version === '*' || version === 'latest' || version.startsWith('>=')) {
|
|
382
261
|
findings.push({
|
|
@@ -385,9 +264,6 @@ function scanDependencyChange(oldContent, newContent) {
|
|
|
385
264
|
severity: 'medium',
|
|
386
265
|
label: `Wildcard version for new dependency: ${pkg}@${version}`,
|
|
387
266
|
path: 'package.json',
|
|
388
|
-
remediation: `Specify a concrete version constraint (e.g., "^${version === '*' || version === 'latest' ? '1.0.0' : version}") instead of a wildcard.`,
|
|
389
|
-
reachable,
|
|
390
|
-
reachability,
|
|
391
267
|
});
|
|
392
268
|
}
|
|
393
269
|
|
|
@@ -399,24 +275,6 @@ function scanDependencyChange(oldContent, newContent) {
|
|
|
399
275
|
severity: 'high',
|
|
400
276
|
label: `Suspiciously short package name: "${pkg}"`,
|
|
401
277
|
path: 'package.json',
|
|
402
|
-
remediation: 'Double-check spelling and verify this package is not a typosquatting attempt.',
|
|
403
|
-
reachable,
|
|
404
|
-
reachability,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
// Tier 3: Slopsquat Guard — deterministic typosquat detection
|
|
409
|
-
const slopsquatFinding = detectSlopsquat(pkg, 'npm');
|
|
410
|
-
if (slopsquatFinding) {
|
|
411
|
-
findings.push({
|
|
412
|
-
id: slopsquatFinding.id,
|
|
413
|
-
category: 'supply-chain',
|
|
414
|
-
severity: slopsquatFinding.severity,
|
|
415
|
-
label: slopsquatFinding.label,
|
|
416
|
-
path: 'package.json',
|
|
417
|
-
remediation: `Verify spelling and authenticity of package "${pkg}". If typosquatted, run: npm uninstall ${pkg}.`,
|
|
418
|
-
reachable,
|
|
419
|
-
reachability,
|
|
420
278
|
});
|
|
421
279
|
}
|
|
422
280
|
}
|
|
@@ -434,7 +292,6 @@ function scanDependencyChange(oldContent, newContent) {
|
|
|
434
292
|
severity: 'critical',
|
|
435
293
|
label: `Suspicious install script: ${name} → ${cmd.slice(0, 80)}`,
|
|
436
294
|
path: 'package.json',
|
|
437
|
-
remediation: 'Remove the suspicious pre/postinstall script, or run package installation with --ignore-scripts.',
|
|
438
295
|
});
|
|
439
296
|
}
|
|
440
297
|
}
|
|
@@ -456,68 +313,27 @@ function scanDependencyChange(oldContent, newContent) {
|
|
|
456
313
|
* @param {Object} input - Hook input { tool_name, tool_input }
|
|
457
314
|
* @returns {Object|null} Gate result or null if clean
|
|
458
315
|
*/
|
|
459
|
-
|
|
460
|
-
/**
|
|
461
|
-
* Evaluate slopsquat guard for a Bash command.
|
|
462
|
-
* @param {string} toolName
|
|
463
|
-
* @param {Object} toolInput
|
|
464
|
-
* @returns {Object|null}
|
|
465
|
-
*/
|
|
466
|
-
function evaluateSlopsquatScan(toolName, toolInput) {
|
|
467
|
-
if (toolName !== "Bash") return null;
|
|
468
|
-
const command = toolInput.command || "";
|
|
469
|
-
if (!command) return null;
|
|
470
|
-
|
|
471
|
-
const { resolveMode, scanInstallCommand } = loadOptionalModule("./slopsquat-guard", () => ({
|
|
472
|
-
resolveMode: () => "block",
|
|
473
|
-
scanInstallCommand: () => ({ detected: false, findings: [] }),
|
|
474
|
-
}));
|
|
475
|
-
|
|
476
|
-
const mode = resolveMode();
|
|
477
|
-
if (mode === "off") return null;
|
|
478
|
-
|
|
479
|
-
const result = scanInstallCommand(command);
|
|
480
|
-
if (!result.detected) return null;
|
|
481
|
-
|
|
482
|
-
const hasCritical = result.findings.some(f => f.severity === "critical");
|
|
483
|
-
const decision = (mode === "block" && hasCritical) ? "deny" : "warn";
|
|
484
|
-
|
|
485
|
-
return {
|
|
486
|
-
decision,
|
|
487
|
-
gate: "slopsquat-guard",
|
|
488
|
-
message: "✗ THUMBGATE: " + result.findings[0].label,
|
|
489
|
-
severity: hasCritical ? "critical" : "high",
|
|
490
|
-
reasoning: result.findings.map(f => f.label),
|
|
491
|
-
};
|
|
492
|
-
}
|
|
493
|
-
|
|
494
316
|
function evaluateSecurityScan(input = {}) {
|
|
495
317
|
const toolName = input.tool_name || input.toolName || '';
|
|
496
318
|
const toolInput = input.tool_input || {};
|
|
497
319
|
|
|
498
|
-
// Only scan write-type operations
|
|
320
|
+
// Only scan write-type operations
|
|
499
321
|
const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (!WRITE_TOOLS.has(toolName) && !IS_BASH) {
|
|
322
|
+
if (!WRITE_TOOLS.has(toolName)) {
|
|
503
323
|
return null;
|
|
504
324
|
}
|
|
505
325
|
|
|
506
326
|
const filePath = toolInput.file_path || toolInput.path || '';
|
|
507
327
|
const content = toolInput.content || toolInput.new_string || '';
|
|
508
|
-
const command = toolInput.command || '';
|
|
509
328
|
|
|
510
|
-
if (!content
|
|
329
|
+
if (!content) return null;
|
|
511
330
|
|
|
512
|
-
// Tier 1: Code vulnerability scan
|
|
513
|
-
|
|
514
|
-
if (content) {
|
|
515
|
-
codeResult = scanCode(content, filePath);
|
|
516
|
-
}
|
|
331
|
+
// Tier 1: Code vulnerability scan
|
|
332
|
+
const codeResult = scanCode(content, filePath);
|
|
517
333
|
|
|
518
334
|
// Tier 2: Supply chain scan for package.json changes
|
|
519
335
|
let supplyChainResult = { detected: false, findings: [] };
|
|
520
|
-
if (filePath && path.basename(filePath) === 'package.json'
|
|
336
|
+
if (filePath && path.basename(filePath) === 'package.json') {
|
|
521
337
|
let oldContent = '';
|
|
522
338
|
try {
|
|
523
339
|
const absPath = path.isAbsolute(filePath) ? filePath : path.resolve(filePath);
|
|
@@ -528,14 +344,7 @@ function evaluateSecurityScan(input = {}) {
|
|
|
528
344
|
supplyChainResult = scanDependencyChange(oldContent, content);
|
|
529
345
|
}
|
|
530
346
|
|
|
531
|
-
|
|
532
|
-
let slopsquatResult = { detected: false, findings: [] };
|
|
533
|
-
if (IS_BASH && command) {
|
|
534
|
-
const slopsquatGate = evaluateSlopsquatScan(toolName, toolInput);
|
|
535
|
-
if (slopsquatGate) return slopsquatGate;
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const allFindings = [...codeResult.findings, ...supplyChainResult.findings, ...slopsquatResult.findings];
|
|
347
|
+
const allFindings = [...codeResult.findings, ...supplyChainResult.findings];
|
|
539
348
|
if (allFindings.length === 0) return null;
|
|
540
349
|
|
|
541
350
|
// Determine overall severity
|
|
@@ -550,18 +359,16 @@ function evaluateSecurityScan(input = {}) {
|
|
|
550
359
|
`[${f.severity.toUpperCase()}] ${f.label}${f.line ? ` (line ${f.line})` : ''}`
|
|
551
360
|
).join('; ');
|
|
552
361
|
|
|
553
|
-
const message = `Security scan detected ${allFindings.length} issue(s) in ${filePath ||
|
|
362
|
+
const message = `Security scan detected ${allFindings.length} issue(s) in ${filePath || 'code'}: ${summary}`;
|
|
554
363
|
|
|
555
364
|
const reasoning = [
|
|
556
|
-
|
|
557
|
-
? `Scanned Bash command for slopsquat/typosquat risk: "${command.slice(0, 100)}..."`
|
|
558
|
-
: `Scanned ${content.length} bytes of content being written to ${filePath || 'unknown file'}`,
|
|
365
|
+
`Scanned ${content.length} bytes of content being written to ${filePath || 'unknown file'}`,
|
|
559
366
|
...allFindings.map(f => `${f.category}/${f.id}: ${f.label}${f.match ? ` — matched: ${f.match.slice(0, 60)}` : ''}`),
|
|
560
367
|
];
|
|
561
368
|
|
|
562
369
|
recordAuditEvent({
|
|
563
370
|
toolName,
|
|
564
|
-
toolInput: { file_path: filePath, content_length: content.length
|
|
371
|
+
toolInput: { file_path: filePath, content_length: content.length },
|
|
565
372
|
decision,
|
|
566
373
|
gateId,
|
|
567
374
|
message,
|
|
@@ -627,72 +434,15 @@ function scanGitDiff(diffContent) {
|
|
|
627
434
|
};
|
|
628
435
|
}
|
|
629
436
|
|
|
630
|
-
function buildThreatDefensePlaybook(scanResult = {}, options = {}) {
|
|
631
|
-
const findings = Array.isArray(scanResult.findings)
|
|
632
|
-
? scanResult.findings
|
|
633
|
-
: (scanResult.securityScan && Array.isArray(scanResult.securityScan.findings) ? scanResult.securityScan.findings : []);
|
|
634
|
-
const critical = findings.filter((finding) => finding.severity === 'critical');
|
|
635
|
-
const high = findings.filter((finding) => finding.severity === 'high');
|
|
636
|
-
const categories = Array.from(new Set(findings.map((finding) => finding.category).filter(Boolean)));
|
|
637
|
-
const hasFindings = findings.length > 0;
|
|
638
|
-
const hasPatchEvidence = Boolean(options.patchEvidence || options.testEvidence || options.ciEvidence);
|
|
639
|
-
|
|
640
|
-
return {
|
|
641
|
-
name: 'thumbgate-ai-threat-defense-playbook',
|
|
642
|
-
status: critical.length > 0 ? 'block' : high.length > 0 ? 'remediate' : 'monitor',
|
|
643
|
-
phases: [
|
|
644
|
-
{
|
|
645
|
-
id: 'prepare',
|
|
646
|
-
action: 'harden-foundation',
|
|
647
|
-
evidence: ['gate templates enabled', 'protected files configured', 'rollback path documented'],
|
|
648
|
-
required: true,
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
id: 'scan-prioritize',
|
|
652
|
-
action: hasFindings ? 'prioritize detected security findings by severity and exploit surface' : 'keep posture scan active',
|
|
653
|
-
evidence: categories.length ? categories : ['clean scan'],
|
|
654
|
-
required: true,
|
|
655
|
-
},
|
|
656
|
-
{
|
|
657
|
-
id: 'remediate',
|
|
658
|
-
action: hasFindings ? 'patch, run focused tests, and re-scan before allowing risky agent actions' : 'no remediation required from current scan',
|
|
659
|
-
evidence: hasPatchEvidence ? ['patch evidence present'] : ['patch diff', 'focused test output', 'repeat scan'],
|
|
660
|
-
required: hasFindings,
|
|
661
|
-
},
|
|
662
|
-
{
|
|
663
|
-
id: 'monitor',
|
|
664
|
-
action: 'record audit event and keep continuous detection enabled for future tool calls',
|
|
665
|
-
evidence: ['audit trail event', 'gate stats', 'review checkpoint'],
|
|
666
|
-
required: true,
|
|
667
|
-
},
|
|
668
|
-
],
|
|
669
|
-
priority: {
|
|
670
|
-
critical: critical.length,
|
|
671
|
-
high: high.length,
|
|
672
|
-
total: findings.length,
|
|
673
|
-
categories,
|
|
674
|
-
},
|
|
675
|
-
gateDecision: critical.length > 0 ? 'deny' : high.length > 0 ? 'warn' : 'allow',
|
|
676
|
-
nextActions: critical.length > 0
|
|
677
|
-
? ['Block the action', 'Patch the critical finding', 'Run focused tests', 'Re-scan the diff before retry']
|
|
678
|
-
: high.length > 0
|
|
679
|
-
? ['Warn the operator', 'Create a remediation task', 'Run focused tests', 'Monitor for repeat findings']
|
|
680
|
-
: ['Keep continuous scan enabled', 'Review checkpoint metrics after the next session'],
|
|
681
|
-
};
|
|
682
|
-
}
|
|
683
|
-
|
|
684
437
|
// ---------------------------------------------------------------------------
|
|
685
438
|
// Exports
|
|
686
439
|
// ---------------------------------------------------------------------------
|
|
687
440
|
|
|
688
441
|
module.exports = {
|
|
689
|
-
evaluateSlopsquatScan,
|
|
690
442
|
VULN_PATTERNS,
|
|
691
443
|
SUPPLY_CHAIN_PATTERNS,
|
|
692
444
|
scanCode,
|
|
693
|
-
scanBadHostExposure,
|
|
694
445
|
scanDependencyChange,
|
|
695
446
|
evaluateSecurityScan,
|
|
696
447
|
scanGitDiff,
|
|
697
|
-
buildThreatDefensePlaybook,
|
|
698
448
|
};
|
|
@@ -26,7 +26,6 @@ const { resolveFeedbackDir } = require('./feedback-paths');
|
|
|
26
26
|
const { createLesson, inferStructuredLesson } = require('./lesson-inference');
|
|
27
27
|
const { buildStableId } = require('./conversation-context');
|
|
28
28
|
const { ensureParentDir, readJsonl } = require('./fs-utils');
|
|
29
|
-
const { redactSecretsDeep } = require('./secret-redaction');
|
|
30
29
|
|
|
31
30
|
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir() || '';
|
|
32
31
|
const SELF_DISTILL_RUNS_PATH = path.join(HOME, '.thumbgate', 'self-distill-runs.jsonl');
|
|
@@ -384,8 +383,7 @@ async function generateLlmLessons(conversationWindow, model) {
|
|
|
384
383
|
|
|
385
384
|
function writeRunManifest(manifest) {
|
|
386
385
|
ensureParentDir(SELF_DISTILL_RUNS_PATH);
|
|
387
|
-
|
|
388
|
-
fs.appendFileSync(SELF_DISTILL_RUNS_PATH, JSON.stringify(redactSecretsDeep(manifest)) + '\n');
|
|
386
|
+
fs.appendFileSync(SELF_DISTILL_RUNS_PATH, JSON.stringify(manifest) + '\n');
|
|
389
387
|
}
|
|
390
388
|
|
|
391
389
|
function readRunManifests() {
|