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.
Files changed (165) hide show
  1. package/.claude-plugin/marketplace.json +85 -0
  2. package/.claude-plugin/plugin.json +3 -4
  3. package/.well-known/llms.txt +12 -33
  4. package/.well-known/mcp/server-card.json +8 -8
  5. package/README.md +30 -221
  6. package/adapters/chatgpt/openapi.yaml +1695 -0
  7. package/adapters/claude/.mcp.json +2 -2
  8. package/adapters/gemini/function-declarations.json +0 -1
  9. package/adapters/mcp/server-stdio.js +11 -263
  10. package/adapters/opencode/opencode.json +1 -1
  11. package/bench/thumbgate-bench.json +2 -2
  12. package/bin/cli.js +122 -1421
  13. package/bin/postinstall.js +27 -11
  14. package/config/gate-templates.json +0 -216
  15. package/config/gates/claim-verification.json +0 -12
  16. package/config/gates/default.json +2 -31
  17. package/config/github-about.json +2 -2
  18. package/config/mcp-allowlists.json +13 -23
  19. package/config/merge-quality-checks.json +1 -0
  20. package/config/model-candidates.json +6 -121
  21. package/openapi/openapi.yaml +0 -12
  22. package/package.json +99 -229
  23. package/public/blog.html +4 -4
  24. package/public/codex-plugin.html +20 -72
  25. package/public/compare.html +8 -31
  26. package/public/dashboard.html +166 -930
  27. package/public/federal.html +2 -2
  28. package/public/guide.html +9 -33
  29. package/public/index.html +116 -495
  30. package/public/learn.html +18 -191
  31. package/public/lessons.html +10 -168
  32. package/public/numbers.html +7 -7
  33. package/public/pro.html +11 -34
  34. package/scripts/agent-memory-lifecycle.js +0 -211
  35. package/scripts/agent-readiness.js +3 -167
  36. package/scripts/agent-reward-model.js +1 -53
  37. package/scripts/auto-promote-gates.js +10 -82
  38. package/scripts/auto-wire-hooks.js +0 -14
  39. package/scripts/billing.js +1 -93
  40. package/scripts/build-metadata.js +10 -50
  41. package/scripts/cli-feedback.js +6 -18
  42. package/scripts/cli-schema.js +0 -90
  43. package/scripts/cli-telemetry.js +1 -6
  44. package/scripts/commercial-offer.js +2 -82
  45. package/scripts/context-manager.js +6 -74
  46. package/scripts/dashboard.js +2 -68
  47. package/scripts/durability/step.js +3 -3
  48. package/scripts/export-databricks-bundle.js +1 -5
  49. package/scripts/export-dpo-pairs.js +2 -7
  50. package/scripts/feedback-loop.js +1 -145
  51. package/scripts/feedback-quality.js +0 -87
  52. package/scripts/filesystem-search.js +10 -35
  53. package/scripts/gate-stats.js +0 -89
  54. package/scripts/gates-engine.js +85 -1105
  55. package/scripts/gemini-embedding-policy.js +1 -2
  56. package/scripts/hook-runtime.js +14 -20
  57. package/scripts/hook-thumbgate-cache-updater.js +2 -18
  58. package/scripts/hybrid-feedback-context.js +7 -142
  59. package/scripts/lesson-inference.js +3 -8
  60. package/scripts/lesson-search.js +1 -17
  61. package/scripts/license.js +1 -0
  62. package/scripts/llm-client.js +9 -390
  63. package/scripts/local-model-profile.js +8 -15
  64. package/scripts/memory-scope-readiness.js +0 -159
  65. package/scripts/meta-agent-loop.js +0 -36
  66. package/scripts/operational-integrity.js +5 -39
  67. package/scripts/oss-pr-opportunity-scout.js +5 -35
  68. package/scripts/plausible-server-events.js +6 -9
  69. package/scripts/pro-local-dashboard.js +4 -4
  70. package/scripts/proxy-pointer-rag-guardrails.js +1 -42
  71. package/scripts/rate-limiter.js +13 -64
  72. package/scripts/secret-scanner.js +5 -44
  73. package/scripts/security-scanner.js +10 -260
  74. package/scripts/self-distill-agent.js +1 -3
  75. package/scripts/seo-gsd.js +2 -916
  76. package/scripts/statusline-cache-path.js +2 -17
  77. package/scripts/statusline-local-stats.js +1 -9
  78. package/scripts/statusline-meta.js +2 -28
  79. package/scripts/statusline.sh +4 -20
  80. package/scripts/telemetry-analytics.js +0 -357
  81. package/scripts/thompson-sampling.js +10 -31
  82. package/scripts/thumbgate-bench.js +1 -16
  83. package/scripts/thumbgate-search.js +19 -85
  84. package/scripts/tool-registry.js +1 -169
  85. package/scripts/vector-store.js +0 -45
  86. package/scripts/workflow-sentinel.js +53 -286
  87. package/scripts/workspace-evolver.js +2 -62
  88. package/src/api/server.js +290 -2578
  89. package/.claude/commands/dashboard.md +0 -15
  90. package/.claude/commands/thumbgate-blocked.md +0 -27
  91. package/.claude/commands/thumbgate-dashboard.md +0 -15
  92. package/.claude/commands/thumbgate-doctor.md +0 -30
  93. package/.claude/commands/thumbgate-guard.md +0 -36
  94. package/.claude/commands/thumbgate-protect.md +0 -30
  95. package/.claude/commands/thumbgate-rules.md +0 -30
  96. package/.well-known/agentic-verify.txt +0 -1
  97. package/adapters/gcp/dfcx-webhook-gate.js +0 -295
  98. package/adapters/letta/README.md +0 -41
  99. package/adapters/letta/thumbgate-letta-adapter.js +0 -133
  100. package/adapters/policy-engine/ethicore-guardian-client.js +0 -68
  101. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +0 -260
  102. package/bench/observability-eval-suite.json +0 -26
  103. package/bin/dashboard-cli.js +0 -7
  104. package/commands/dashboard.md +0 -15
  105. package/commands/thumbgate-dashboard.md +0 -15
  106. package/config/builtin-lessons.json +0 -23
  107. package/config/gate-classifier-routing.json +0 -98
  108. package/config/post-deploy-marketing-pages.json +0 -61
  109. package/public/about.html +0 -162
  110. package/public/agent-manager.html +0 -179
  111. package/public/agents-cost-savings.html +0 -153
  112. package/public/ai-malpractice-prevention.html +0 -818
  113. package/public/assets/brand/github-social-preview.png +0 -0
  114. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  115. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  116. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  117. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  118. package/public/assets/brand/thumbgate-logo-transparent.svg +0 -22
  119. package/public/assets/brand/thumbgate-mark-inline-v3.svg +0 -19
  120. package/public/assets/brand/thumbgate-mark-pro.svg +0 -23
  121. package/public/assets/brand/thumbgate-mark-team.svg +0 -26
  122. package/public/assets/brand/thumbgate-mark.svg +0 -21
  123. package/public/assets/brand/thumbgate-wordmark.svg +0 -20
  124. package/public/assets/claude-thumbgate-statusbar.svg +0 -8
  125. package/public/assets/codex-thumbgate-statusbar-test.svg +0 -9
  126. package/public/assets/legal-intake-control-flow.svg +0 -66
  127. package/public/brand/thumbgate-mark.svg +0 -19
  128. package/public/brand/thumbgate-og.svg +0 -16
  129. package/public/chatgpt-app.html +0 -330
  130. package/public/codex-enterprise.html +0 -123
  131. package/public/js/buyer-intent.js +0 -672
  132. package/public/pricing.html +0 -399
  133. package/scripts/action-receipts.js +0 -324
  134. package/scripts/activation-quickstart.js +0 -187
  135. package/scripts/agent-operations-planner.js +0 -621
  136. package/scripts/ai-component-inventory.js +0 -367
  137. package/scripts/async-eval-observability.js +0 -236
  138. package/scripts/audit.js +0 -65
  139. package/scripts/aws-blocks-guardrails.js +0 -228
  140. package/scripts/classifier-routing.js +0 -130
  141. package/scripts/dashboard-chat.js +0 -332
  142. package/scripts/feedback-aggregate.js +0 -281
  143. package/scripts/feedback-sanitizer.js +0 -105
  144. package/scripts/hob-pack.js +0 -591
  145. package/scripts/hook-stop-anti-claim.js +0 -301
  146. package/scripts/install-shim.js +0 -87
  147. package/scripts/mcp-oauth.js +0 -293
  148. package/scripts/noop-detect.js +0 -285
  149. package/scripts/omlx-smoke.js +0 -192
  150. package/scripts/parallel-workflow-orchestrator.js +0 -293
  151. package/scripts/plan-gate.js +0 -243
  152. package/scripts/plausible-domain-config.js +0 -99
  153. package/scripts/qa-scenario-planner.js +0 -136
  154. package/scripts/repeat-metric.js +0 -137
  155. package/scripts/secret-fixture-tokens.js +0 -61
  156. package/scripts/secret-redaction.js +0 -166
  157. package/scripts/self-harness-optimizer.js +0 -141
  158. package/scripts/self-healing-check.js +0 -193
  159. package/scripts/silent-failure-cluster.js +0 -531
  160. package/scripts/statusline-cache-read.js +0 -57
  161. package/scripts/sync-telemetry-from-prod.js +0 -374
  162. package/scripts/tool-contract-validator.js +0 -76
  163. package/scripts/trajectory-scorer.js +0 -63
  164. package/scripts/verify-marketing-pages-deployed.js +0 -195
  165. 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 and Bash commands
320
+ // Only scan write-type operations
499
321
  const WRITE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
500
- const IS_BASH = toolName === 'Bash';
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 && !command) return null;
329
+ if (!content) return null;
511
330
 
512
- // Tier 1: Code vulnerability scan (for Edits)
513
- let codeResult = { detected: false, findings: [] };
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' && content) {
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
- // Tier 3: Slopsquat Guard for Bash commands
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 || (IS_BASH ? 'command' : 'code')}: ${summary}`;
362
+ const message = `Security scan detected ${allFindings.length} issue(s) in ${filePath || 'code'}: ${summary}`;
554
363
 
555
364
  const reasoning = [
556
- IS_BASH
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, command: IS_BASH ? command : undefined },
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
- // Redact secrets — the manifest embeds lesson triggers/actions distilled from conversation text.
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() {