thumbgate 1.27.18 → 1.27.20

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 (170) hide show
  1. package/.claude/commands/dashboard.md +15 -0
  2. package/.claude/commands/thumbgate-blocked.md +27 -0
  3. package/.claude/commands/thumbgate-dashboard.md +15 -0
  4. package/.claude/commands/thumbgate-doctor.md +30 -0
  5. package/.claude/commands/thumbgate-guard.md +36 -0
  6. package/.claude/commands/thumbgate-protect.md +30 -0
  7. package/.claude/commands/thumbgate-rules.md +30 -0
  8. package/.claude-plugin/plugin.json +4 -3
  9. package/.well-known/agentic-verify.txt +1 -0
  10. package/.well-known/llms.txt +33 -12
  11. package/.well-known/mcp/server-card.json +8 -8
  12. package/README.md +246 -30
  13. package/adapters/claude/.mcp.json +2 -2
  14. package/adapters/codex/config.toml +2 -2
  15. package/adapters/gcp/dfcx-webhook-gate.js +295 -0
  16. package/adapters/gemini/function-declarations.json +1 -0
  17. package/adapters/letta/README.md +41 -0
  18. package/adapters/letta/thumbgate-letta-adapter.js +133 -0
  19. package/adapters/mcp/server-stdio.js +263 -11
  20. package/adapters/opencode/opencode.json +1 -1
  21. package/adapters/policy-engine/ethicore-guardian-client.js +68 -0
  22. package/adapters/policy-engine/thumbgate-policy-engine-adapter.js +260 -0
  23. package/bench/observability-eval-suite.json +26 -0
  24. package/bench/thumbgate-bench.json +2 -2
  25. package/bin/cli.js +1456 -122
  26. package/bin/dashboard-cli.js +7 -0
  27. package/bin/postinstall.js +11 -27
  28. package/commands/dashboard.md +15 -0
  29. package/commands/thumbgate-dashboard.md +15 -0
  30. package/config/gate-classifier-routing.json +98 -0
  31. package/config/gate-templates.json +216 -0
  32. package/config/gates/claim-verification.json +12 -0
  33. package/config/gates/default.json +31 -2
  34. package/config/github-about.json +2 -2
  35. package/config/mcp-allowlists.json +23 -13
  36. package/config/merge-quality-checks.json +0 -1
  37. package/config/model-candidates.json +121 -6
  38. package/config/post-deploy-marketing-pages.json +80 -0
  39. package/config/tessl-tiles.json +1 -3
  40. package/openapi/openapi.yaml +12 -0
  41. package/package.json +225 -100
  42. package/public/about.html +162 -0
  43. package/public/agent-manager.html +179 -0
  44. package/public/agents-cost-savings.html +153 -0
  45. package/public/ai-malpractice-prevention.html +818 -0
  46. package/public/assets/brand/github-social-preview.png +0 -0
  47. package/public/assets/brand/thumbgate-icon-512.png +0 -0
  48. package/public/assets/brand/thumbgate-icon-pro-512.png +0 -0
  49. package/public/assets/brand/thumbgate-icon-team-512.png +0 -0
  50. package/public/assets/brand/thumbgate-logo-1200x360.png +0 -0
  51. package/public/assets/brand/thumbgate-logo-transparent.svg +28 -0
  52. package/public/assets/brand/thumbgate-mark-inline-v3.svg +18 -0
  53. package/public/assets/brand/thumbgate-mark-pro.svg +23 -0
  54. package/public/assets/brand/thumbgate-mark-team.svg +26 -0
  55. package/public/assets/brand/thumbgate-mark.svg +21 -0
  56. package/public/assets/brand/thumbgate-wordmark.svg +20 -0
  57. package/public/assets/claude-thumbgate-statusbar.svg +8 -0
  58. package/public/assets/codex-thumbgate-statusbar-test.svg +9 -0
  59. package/public/assets/legal-intake-control-flow.svg +66 -0
  60. package/public/blog.html +4 -4
  61. package/public/brand/thumbgate-mark.svg +19 -0
  62. package/public/brand/thumbgate-og.svg +16 -0
  63. package/public/chatgpt-app.html +330 -0
  64. package/public/codex-enterprise.html +123 -0
  65. package/public/codex-plugin.html +72 -20
  66. package/public/compare.html +31 -8
  67. package/public/dashboard.html +930 -166
  68. package/public/diagnostic.html +345 -0
  69. package/public/federal.html +2 -2
  70. package/public/guide.html +33 -13
  71. package/public/index.html +469 -111
  72. package/public/install.html +193 -0
  73. package/public/js/buyer-intent.js +672 -0
  74. package/public/learn.html +183 -18
  75. package/public/lessons.html +168 -10
  76. package/public/numbers.html +7 -7
  77. package/public/pricing.html +399 -0
  78. package/public/pro.html +34 -11
  79. package/scripts/action-receipts.js +324 -0
  80. package/scripts/activation-quickstart.js +187 -0
  81. package/scripts/agent-memory-lifecycle.js +211 -0
  82. package/scripts/agent-operations-planner.js +621 -0
  83. package/scripts/agent-readiness.js +20 -3
  84. package/scripts/agent-reward-model.js +53 -1
  85. package/scripts/ai-component-inventory.js +367 -0
  86. package/scripts/async-eval-observability.js +236 -0
  87. package/scripts/audit.js +65 -0
  88. package/scripts/auto-promote-gates.js +82 -10
  89. package/scripts/auto-wire-hooks.js +14 -0
  90. package/scripts/aws-blocks-guardrails.js +272 -0
  91. package/scripts/billing.js +93 -1
  92. package/scripts/bot-detection.js +61 -3
  93. package/scripts/build-metadata.js +50 -10
  94. package/scripts/classifier-routing.js +130 -0
  95. package/scripts/cli-feedback.js +4 -2
  96. package/scripts/cli-schema.js +97 -0
  97. package/scripts/cli-telemetry.js +6 -1
  98. package/scripts/commercial-offer.js +82 -2
  99. package/scripts/context-manager.js +74 -6
  100. package/scripts/dashboard-chat.js +332 -0
  101. package/scripts/dashboard.js +68 -2
  102. package/scripts/export-databricks-bundle.js +5 -1
  103. package/scripts/export-dpo-pairs.js +7 -2
  104. package/scripts/feedback-aggregate.js +281 -0
  105. package/scripts/feedback-loop.js +123 -1
  106. package/scripts/feedback-quality.js +87 -0
  107. package/scripts/feedback-sanitizer.js +105 -0
  108. package/scripts/filesystem-search.js +35 -10
  109. package/scripts/gate-stats.js +89 -0
  110. package/scripts/gates-engine.js +1176 -85
  111. package/scripts/gemini-embedding-policy.js +2 -1
  112. package/scripts/hook-runtime.js +20 -14
  113. package/scripts/hook-stop-anti-claim.js +301 -0
  114. package/scripts/hook-thumbgate-cache-updater.js +18 -2
  115. package/scripts/hybrid-feedback-context.js +142 -7
  116. package/scripts/install-shim.js +87 -0
  117. package/scripts/lesson-inference.js +8 -3
  118. package/scripts/lesson-search.js +17 -1
  119. package/scripts/license.js +10 -10
  120. package/scripts/llm-client.js +169 -4
  121. package/scripts/local-model-profile.js +15 -8
  122. package/scripts/mcp-config.js +7 -1
  123. package/scripts/mcp-oauth.js +293 -0
  124. package/scripts/memory-scope-readiness.js +159 -0
  125. package/scripts/meta-agent-loop.js +36 -0
  126. package/scripts/noop-detect.js +285 -0
  127. package/scripts/operational-integrity.js +39 -5
  128. package/scripts/oss-pr-opportunity-scout.js +35 -5
  129. package/scripts/parallel-workflow-orchestrator.js +293 -0
  130. package/scripts/plan-gate.js +243 -0
  131. package/scripts/plausible-domain-config.js +99 -0
  132. package/scripts/plausible-server-events.js +9 -6
  133. package/scripts/pro-local-dashboard.js +4 -4
  134. package/scripts/proxy-pointer-rag-guardrails.js +42 -1
  135. package/scripts/published-cli.js +0 -8
  136. package/scripts/qa-scenario-planner.js +136 -0
  137. package/scripts/rate-limiter.js +64 -13
  138. package/scripts/repeat-metric.js +137 -0
  139. package/scripts/secret-fixture-tokens.js +61 -0
  140. package/scripts/secret-redaction.js +166 -0
  141. package/scripts/secret-scanner.js +44 -5
  142. package/scripts/security-scanner.js +260 -10
  143. package/scripts/self-distill-agent.js +3 -1
  144. package/scripts/self-harness-optimizer.js +141 -0
  145. package/scripts/self-healing-check.js +193 -0
  146. package/scripts/self-protection.js +90 -0
  147. package/scripts/seo-gsd.js +916 -7
  148. package/scripts/silent-failure-cluster.js +531 -0
  149. package/scripts/statusline-cache-path.js +17 -2
  150. package/scripts/statusline-cache-read.js +57 -0
  151. package/scripts/statusline-local-stats.js +9 -1
  152. package/scripts/statusline-meta.js +28 -2
  153. package/scripts/statusline.sh +20 -4
  154. package/scripts/sync-telemetry-from-prod.js +374 -0
  155. package/scripts/telemetry-analytics.js +357 -0
  156. package/scripts/thompson-sampling.js +31 -10
  157. package/scripts/thumbgate-bench.js +16 -1
  158. package/scripts/thumbgate-search.js +85 -19
  159. package/scripts/tool-contract-validator.js +76 -0
  160. package/scripts/tool-registry.js +169 -1
  161. package/scripts/trajectory-scorer.js +63 -0
  162. package/scripts/vector-store.js +45 -0
  163. package/scripts/verify-marketing-pages-deployed.js +212 -0
  164. package/scripts/visitor-journey.js +172 -0
  165. package/scripts/workflow-sentinel.js +286 -53
  166. package/scripts/workspace-evolver.js +62 -2
  167. package/src/api/server.js +2683 -319
  168. package/.claude-plugin/marketplace.json +0 -85
  169. package/adapters/chatgpt/openapi.yaml +0 -1695
  170. package/scripts/bot-detector.js +0 -50
@@ -0,0 +1,166 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * secret-redaction.js — the single, canonical secret-redaction helper.
5
+ *
6
+ * WHY THIS EXISTS
7
+ * On 2026-06-10 a live Stripe `sk_live_` key was found in plaintext inside a captured
8
+ * `.thumbgate/conversation-window.jsonl`. ThumbGate must never become a secret-leak vector:
9
+ * secrets must be redacted (a) before any conversation/feedback/memory record lands on disk,
10
+ * and (b) before any training/analytics dataset is exported or shared.
11
+ *
12
+ * This module is the ONE place that owns redaction-at-rest. Capture writers and dataset
13
+ * exporters all call into it instead of each rolling their own. It is intentionally separate
14
+ * from `secret-scanner.js`: that module is the PreToolUse *scan/block* layer and deliberately
15
+ * tolerates `sk_test_`/`test_token` inside commands. For data persisted to disk we redact
16
+ * test-mode secrets too — a `sk_test_` key is still a credential we should not store.
17
+ *
18
+ * NOT REDACTED ON PURPOSE
19
+ * - Stripe publishable keys (`pk_live_`, `pk_test_`) are public by design. No pattern matches
20
+ * them, and the generic `key=value` pattern keys on `api_key|secret|token|...`, never on a
21
+ * bare `publishable_key`, so they are preserved verbatim.
22
+ *
23
+ * Properties:
24
+ * - Idempotent: re-redacting already-redacted text is a no-op (the `[REDACTED:id]` marker
25
+ * contains characters outside every value charset).
26
+ * - Non-mutating: `redactSecretsDeep` returns a redacted copy; inputs are untouched.
27
+ */
28
+
29
+ const REDACTED = (id) => `[REDACTED:${id}]`;
30
+
31
+ // Identifier names that denote a credential, used by the generic `key = value` pattern. Kept as
32
+ // two small matchers (instead of one mega-alternation) so the assignment regex stays simple.
33
+ const SECRET_KEY_NAME = /(?:api[_-]?key|secret|token|password|passwd|credential|private[_-]?key|access[_-]?token|client[_-]?secret)/i;
34
+ // Public identifiers whose values are not secrets (e.g. Stripe publishable `pk_*` keys). Excluded
35
+ // so `publishable_key = pk_live_…` is preserved verbatim.
36
+ const PUBLIC_KEY_NAME = /publishable|public/i;
37
+
38
+ /**
39
+ * Ordered list of redaction patterns. Order matters: the most specific provider patterns run
40
+ * before the broad `key=value` fallback so a known secret keeps its precise label. Every regex
41
+ * is `g` (and case-insensitive where appropriate) so all occurrences are replaced.
42
+ *
43
+ * `replace` may be a string or a function `(match, ...groups) => string`.
44
+ */
45
+ const SECRET_REDACTION_PATTERNS = [
46
+ // PEM / OpenSSH private key blocks — redact the whole block.
47
+ {
48
+ id: 'private_key_block',
49
+ label: 'Private key block',
50
+ regex: /-----BEGIN (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----[\s\S]+?-----END (?:RSA |EC |OPENSSH |DSA |PGP )?PRIVATE KEY-----/g,
51
+ },
52
+
53
+ // Stripe secret-bearing keys. Publishable keys (pk_*) are intentionally absent.
54
+ { id: 'stripe_live_secret', label: 'Stripe live secret key', regex: /\bsk_live_[A-Za-z0-9]{8,}/g },
55
+ { id: 'stripe_test_secret', label: 'Stripe test secret key', regex: /\bsk_test_[A-Za-z0-9]{8,}/g },
56
+ { id: 'stripe_restricted_live', label: 'Stripe restricted live key', regex: /\brk_live_[A-Za-z0-9]{8,}/g },
57
+ { id: 'stripe_restricted_test', label: 'Stripe restricted test key', regex: /\brk_test_[A-Za-z0-9]{8,}/g },
58
+ { id: 'stripe_webhook_secret', label: 'Stripe webhook signing secret', regex: /\bwhsec_[A-Za-z0-9]{8,}/g },
59
+
60
+ // Anthropic / OpenAI. Run before the legacy `sk-` pattern so the precise label wins.
61
+ { id: 'anthropic_api_key', label: 'Anthropic API key', regex: /\bsk-ant-[A-Za-z0-9_-]{16,}/g },
62
+ { id: 'openai_project_key', label: 'OpenAI project key', regex: /\bsk-proj-[A-Za-z0-9_-]{16,}/g },
63
+ { id: 'openai_api_key', label: 'OpenAI API key', regex: /\bsk-[A-Za-z0-9]{20,}/g },
64
+
65
+ // GitHub tokens.
66
+ { id: 'github_pat', label: 'GitHub personal access token', regex: /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{16,}/g },
67
+ { id: 'github_fine_grained_pat', label: 'GitHub fine-grained token', regex: /\bgithub_pat_\w{20,}/g },
68
+
69
+ // Slack tokens.
70
+ { id: 'slack_token', label: 'Slack token', regex: /\bxox[abprs]-[A-Za-z0-9-]{10,}/g },
71
+
72
+ // Google API key.
73
+ { id: 'google_api_key', label: 'Google API key', regex: /\bAIza[0-9A-Za-z_-]{35}/g },
74
+
75
+ // AWS access key id (long-lived AKIA + temporary ASIA).
76
+ { id: 'aws_access_key', label: 'AWS access key id', regex: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b/g },
77
+
78
+ // JSON Web Tokens.
79
+ {
80
+ id: 'jwt',
81
+ label: 'JSON Web Token',
82
+ regex: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9._-]{8,}\.[A-Za-z0-9._-]{8,}/g,
83
+ },
84
+
85
+ // `Authorization: Bearer <token>` style. Keep the scheme word, redact the credential.
86
+ {
87
+ id: 'bearer_token',
88
+ label: 'Bearer token',
89
+ regex: /\b(Bearer|Token)\s+[a-z0-9._~+/=-]{16,}/gi,
90
+ replace: (_m, scheme) => `${scheme} ${REDACTED('bearer_token')}`,
91
+ },
92
+
93
+ // Generic `<name> = <value>` / `<name>: "<value>"` assignment with a 16+ char whitespace-free
94
+ // value. A simple matcher finds every assignment; the replace fn redacts only when the name is a
95
+ // credential (SECRET_KEY_NAME) and not a public identifier (PUBLIC_KEY_NAME), so `publishable_key`
96
+ // is preserved. Runs last so provider-specific labels are preferred.
97
+ {
98
+ id: 'secret_assignment',
99
+ label: 'Secret assignment',
100
+ regex: /\b([\w.-]{2,40})(\s*[:=]\s*["']?)([A-Za-z0-9._+=~/-]{16,})(["']?)/g,
101
+ replace: (match, key, pre, _value, post) =>
102
+ (SECRET_KEY_NAME.test(key) && !PUBLIC_KEY_NAME.test(key))
103
+ ? `${key}${pre}${REDACTED('secret_assignment')}${post}`
104
+ : match,
105
+ },
106
+ ];
107
+
108
+ /**
109
+ * Redact secrets from a single string. Returns the input unchanged when it is not a non-empty
110
+ * string. Each pattern's lastIndex is reset before use so the module-level (stateful, `g`-flag)
111
+ * regexes are safe to reuse across calls.
112
+ *
113
+ * @param {string} text
114
+ * @returns {string}
115
+ */
116
+ function redactSecrets(text) {
117
+ if (typeof text !== 'string' || text.length === 0) return text;
118
+ let out = text;
119
+ for (const pattern of SECRET_REDACTION_PATTERNS) {
120
+ pattern.regex.lastIndex = 0;
121
+ const replacement = pattern.replace || REDACTED(pattern.id);
122
+ out = out.replace(pattern.regex, replacement);
123
+ }
124
+ return out;
125
+ }
126
+
127
+ /**
128
+ * Returns true if `text` contains at least one redactable secret.
129
+ * @param {string} text
130
+ * @returns {boolean}
131
+ */
132
+ function containsSecret(text) {
133
+ if (typeof text !== 'string' || text.length === 0) return false;
134
+ return SECRET_REDACTION_PATTERNS.some((pattern) => {
135
+ pattern.regex.lastIndex = 0;
136
+ return pattern.regex.test(text);
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Recursively redact every string contained in `value`. Objects and arrays are deep-copied so
142
+ * the input is never mutated; non-string primitives pass through untouched. Object keys are left
143
+ * as-is (only values are redacted).
144
+ *
145
+ * @param {*} value
146
+ * @returns {*} redacted copy
147
+ */
148
+ function redactSecretsDeep(value) {
149
+ if (typeof value === 'string') return redactSecrets(value);
150
+ if (Array.isArray(value)) return value.map((item) => redactSecretsDeep(item));
151
+ if (value && typeof value === 'object') {
152
+ const out = {};
153
+ for (const [key, val] of Object.entries(value)) {
154
+ out[key] = redactSecretsDeep(val);
155
+ }
156
+ return out;
157
+ }
158
+ return value;
159
+ }
160
+
161
+ module.exports = {
162
+ SECRET_REDACTION_PATTERNS,
163
+ redactSecrets,
164
+ redactSecretsDeep,
165
+ containsSecret,
166
+ };
@@ -55,6 +55,11 @@ const BASH_SECRET_READ_PREFIXES = [
55
55
  ];
56
56
 
57
57
  const EDIT_LIKE_TOOLS = new Set(['Edit', 'Write', 'MultiEdit']);
58
+ const SAFE_SECRET_STORAGE_DIRS = [
59
+ '.resume_secrets',
60
+ '.thumbgate/secrets',
61
+ '.config/thumbgate',
62
+ ];
58
63
 
59
64
  function redactText(text) {
60
65
  if (!text) return '';
@@ -172,6 +177,13 @@ function heuristicScanText(text, source = 'text') {
172
177
  pattern.regex.lastIndex = 0;
173
178
  let match = pattern.regex.exec(input);
174
179
  while (match) {
180
+ // Safe test key bypass
181
+ const matchedString = match[0].toLowerCase();
182
+ if (pattern.id === 'generic_assignment' && (matchedString.includes('sk_test_') || matchedString.includes('test_token'))) {
183
+ match = pattern.regex.exec(input);
184
+ continue;
185
+ }
186
+
175
187
  findings.push({
176
188
  id: pattern.id,
177
189
  label: pattern.label,
@@ -295,6 +307,26 @@ function resolvePathToken(token, cwd) {
295
307
  return path.join(cwd || process.cwd(), normalized);
296
308
  }
297
309
 
310
+ function normalizePathForPolicy(filePath) {
311
+ return path.resolve(String(filePath || '').replace(/^~(?=\/|$)/, os.homedir()));
312
+ }
313
+
314
+ function isSafeSecretStoragePath(filePath) {
315
+ if (!filePath) return false;
316
+ const normalized = normalizePathForPolicy(filePath);
317
+ const home = normalizePathForPolicy(os.homedir());
318
+ return SAFE_SECRET_STORAGE_DIRS.some((dir) => {
319
+ const allowedRoot = path.join(home, dir);
320
+ return normalized === allowedRoot || normalized.startsWith(`${allowedRoot}${path.sep}`);
321
+ });
322
+ }
323
+
324
+ function isSafeSecretStorageWrite(toolName, toolInput = {}, cwd = process.cwd()) {
325
+ if (!EDIT_LIKE_TOOLS.has(toolName)) return false;
326
+ const paths = getToolInputPaths(toolInput, cwd);
327
+ return paths.length > 0 && paths.every((filePath) => isSafeSecretStoragePath(filePath));
328
+ }
329
+
298
330
  function scanBashCommand(command, options = {}) {
299
331
  const cwd = options.cwd || process.cwd();
300
332
  const findings = [];
@@ -347,6 +379,7 @@ function scanHookInput(input = {}, options = {}) {
347
379
  let provider = resolveProvider(options.provider);
348
380
  let commandHash = null;
349
381
  let fileHashes = [];
382
+ const safeSecretStorageWrite = isSafeSecretStorageWrite(toolName, toolInput, cwd);
350
383
 
351
384
  const contentFields = [
352
385
  toolInput.content,
@@ -376,11 +409,13 @@ function scanHookInput(input = {}, options = {}) {
376
409
  }
377
410
  }
378
411
 
379
- for (const content of contentFields) {
380
- const result = scanText(content, { provider, source: 'tool_input' });
381
- if (result.detected) {
382
- provider = result.provider;
383
- findings.push(...result.findings);
412
+ if (!safeSecretStorageWrite) {
413
+ for (const content of contentFields) {
414
+ const result = scanText(content, { provider, source: 'tool_input' });
415
+ if (result.detected) {
416
+ provider = result.provider;
417
+ findings.push(...result.findings);
418
+ }
384
419
  }
385
420
  }
386
421
 
@@ -402,6 +437,8 @@ function buildSafeSummary(findings, prefix) {
402
437
  module.exports = {
403
438
  SECRET_PATTERNS,
404
439
  SECRET_FILE_PATTERNS,
440
+ SAFE_SECRET_STORAGE_DIRS,
441
+ EDIT_LIKE_TOOLS,
405
442
  redactText,
406
443
  resolveProvider,
407
444
  scanText,
@@ -409,6 +446,8 @@ module.exports = {
409
446
  scanBashCommand,
410
447
  scanHookInput,
411
448
  classifySecretPath,
449
+ isSafeSecretStoragePath,
450
+ isSafeSecretStorageWrite,
412
451
  buildSafeSummary,
413
452
  tokenizeCommand,
414
453
  };