thumbgate 1.34.3 → 1.37.1
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/.agents/skills/cyberstrike-compare-not-clone/SKILL.md +36 -0
- package/.agents/skills/gitlab-sandbox-allowlist-not-trust/SKILL.md +77 -0
- package/.agents/skills/jit-harness-compare-not-clone/SKILL.md +34 -0
- package/.agents/skills/openui-catalog-compose-honesty/SKILL.md +64 -0
- package/.agents/skills/zvec-grep-compare-not-clone/SKILL.md +34 -0
- package/.claude-plugin/plugin.json +1 -1
- package/.well-known/llms.txt +1 -0
- package/.well-known/mcp/server-card.json +1 -1
- package/CONTRIBUTING.md +95 -0
- package/README.md +195 -632
- package/THIRD_PARTY_NOTICES.md +89 -0
- package/adapters/claude/.mcp.json +2 -2
- package/adapters/forge/forge.yaml +3 -3
- package/adapters/future-agi/.mcp.json +8 -0
- package/adapters/future-agi/FUTURE_AGI.md +23 -0
- package/adapters/future-agi/config.toml +3 -0
- package/adapters/future-agi/future-agi-bridge.js +9 -0
- package/adapters/future-agi/opencode.json +8 -0
- package/adapters/herdr/herdr-plugin.toml +18 -0
- package/adapters/mcp/server-stdio.js +238 -25
- package/adapters/opencode/opencode.json +1 -1
- package/adapters/workos/WORKOS.md +52 -0
- package/bin/cli.js +373 -5
- package/bin/futureagi-bridge +9 -0
- package/config/gate-templates.json +653 -4
- package/config/gates/actor-critic-audit.json +34 -0
- package/config/gates/default.json +21 -2
- package/config/gates/five-walls-governance.json +34 -0
- package/config/gates/future-agi-guardrails.json +34 -0
- package/config/gates/radware-threat-defense-2026.json +61 -0
- package/config/gates/simatree-data-governance.json +33 -0
- package/config/mcp-allowlists.json +4 -0
- package/config/merge-quality-checks.json +10 -1
- package/config/model-candidates.json +382 -24
- package/config/model-tiers.json +18 -0
- package/config/post-deploy-marketing-pages.json +10 -0
- package/config/progressive/01-wire-only.json +11 -0
- package/config/progressive/02-dashboard-empty-ok.json +10 -0
- package/config/progressive/03-one-lesson.json +10 -0
- package/config/progressive/04-warn-fires.json +11 -0
- package/config/progressive/05-strict-optional.json +11 -0
- package/config/progressive/README.md +15 -0
- package/config/schemas/broker-execution-receipt.schema.json +139 -0
- package/config/schemas/provider-execution-attestation-v1.schema.json +58 -0
- package/conformance/provider-attestation/vectors.json +320 -0
- package/docs/specs/provider-execution-attestation-v1.md +69 -0
- package/openapi/openapi.yaml +15 -0
- package/package.json +401 -147
- package/public/about.html +2 -2
- package/public/ai-malpractice-prevention.html +7 -7
- package/public/blog/a-10-dollar-vps-is-not-a-computer.html +143 -0
- package/public/blog/a-receipt-is-not-world-state.html +388 -0
- package/public/blog/git-at-agent-scale.html +374 -0
- package/public/blog/no-llm-in-the-gate.html +133 -0
- package/public/blog.html +80 -0
- package/public/case-studies.html +16 -1
- package/public/compare.html +28 -0
- package/public/diagnostic.html +216 -7
- package/public/docs/connectors.html +39 -0
- package/public/federal.html +2 -2
- package/public/founders.html +639 -0
- package/public/index.html +87 -9
- package/public/install.html +8 -8
- package/public/learn.html +39 -0
- package/public/numbers.html +2 -2
- package/public/peter.html +310 -0
- package/public/platform-partners.html +119 -0
- package/public/pricing.html +24 -3
- package/public/privacy.html +117 -0
- package/public/pro.html +17 -0
- package/public/support.html +62 -0
- package/public/terms.html +130 -0
- package/public/third-party-notices.html +95 -0
- package/public/yt.html +351 -0
- package/scripts/action-receipts.js +133 -3
- package/scripts/adaptive-governance-arena.js +349 -0
- package/scripts/admin-override.js +205 -0
- package/scripts/agent-action-inventory.js +869 -0
- package/scripts/agent-audit-trace.js +42 -2
- package/scripts/agent-egress-policy.js +1117 -0
- package/scripts/agent-memory-lifecycle.js +141 -2
- package/scripts/agent-operations-planner.js +441 -1
- package/scripts/agent-readiness.js +68 -0
- package/scripts/agent-security-central.js +647 -0
- package/scripts/allowlist-bridge-honesty.js +417 -0
- package/scripts/async-job-runner.js +102 -11
- package/scripts/audit-trail.js +212 -0
- package/scripts/auto-promote-gates.js +178 -27
- package/scripts/billing.js +1 -1
- package/scripts/broker-execution-receipts.js +719 -0
- package/scripts/budget-aware-gates-proof.js +423 -0
- package/scripts/claude-feedback-sync.js +29 -3
- package/scripts/claw-harness-production.js +237 -0
- package/scripts/cli-progress.js +111 -0
- package/scripts/cli-schema.js +163 -1
- package/scripts/codex-runbook-flywheel.js +318 -0
- package/scripts/context-footprint.js +186 -0
- package/scripts/contextfs.js +143 -61
- package/scripts/dashboard-limits.js +27 -0
- package/scripts/dashboard.js +279 -9
- package/scripts/deepseek-v4-runtime-guardrails.js +72 -6
- package/scripts/docker-sandbox-planner.js +18 -0
- package/scripts/double-blind-eval-protocol.js +252 -0
- package/scripts/edotenv-rl-gateway.js +259 -0
- package/scripts/ensure-production-search-corpus.js +162 -0
- package/scripts/eval-holdout.js +311 -0
- package/scripts/feedback-aggregate.js +21 -2
- package/scripts/feedback-loop.js +87 -5
- package/scripts/feedback-quality.js +9 -0
- package/scripts/file-ledger-lock.js +4 -1
- package/scripts/financial-control-plane.js +41 -1
- package/scripts/find-dormant-requires.js +118 -0
- package/scripts/fs-utils.js +84 -8
- package/scripts/gate-stats.js +2 -2
- package/scripts/gates-engine.js +859 -58
- package/scripts/generate-case-study-outreach.js +24 -15
- package/scripts/git-at-scale.js +628 -0
- package/scripts/governance-conflict-audit.js +1650 -0
- package/scripts/governance-difficulty-curriculum.js +328 -0
- package/scripts/graphrag-retrieval.js +275 -0
- package/scripts/gurobi-optimizer.js +324 -0
- package/scripts/gurobi_optimizer.py +485 -0
- package/scripts/harness-selector.js +82 -1
- package/scripts/hidden-entry-points.js +284 -0
- package/scripts/human-escalation.js +199 -1
- package/scripts/hybrid-feedback-context.js +152 -19
- package/scripts/intent-governed-execution.js +602 -0
- package/scripts/intervention-policy.js +123 -20
- package/scripts/jit-harness-compose.js +628 -0
- package/scripts/jsonl-watcher.js +10 -0
- package/scripts/lesson-embedding-index.js +95 -12
- package/scripts/lesson-retrieval.js +105 -19
- package/scripts/local-model-profile.js +19 -2
- package/scripts/mailer/resend-mailer.js +1 -1
- package/scripts/matryoshka-embedding.js +235 -0
- package/scripts/mcp-oauth.js +42 -4
- package/scripts/mcp-session-handles.js +1016 -0
- package/scripts/mcp-wiring-doctor.js +314 -0
- package/scripts/memory-firewall.js +115 -2
- package/scripts/memory-scope-readiness.js +299 -0
- package/scripts/memory-vs-rag-route.js +161 -0
- package/scripts/model-tier-router.js +148 -21
- package/scripts/nvidia-specdecode-al-doctor.js +536 -0
- package/scripts/openui-catalog-compose-honesty.js +593 -0
- package/scripts/operational-integrity.js +19 -1
- package/scripts/override-audit.js +213 -0
- package/scripts/package-manager-honesty-doctor.js +458 -0
- package/scripts/pr-manager.js +63 -1
- package/scripts/prove-herdr-adapter.js +52 -0
- package/scripts/prove-memory-pyramid-and-symbolic-canvas.js +95 -0
- package/scripts/prove-workos.js +73 -0
- package/scripts/provider-attestation-conformance.js +192 -0
- package/scripts/provider-receipt-contract.js +136 -0
- package/scripts/qwen38-max-cost-optimizer.js +401 -0
- package/scripts/radware-threat-defense.js +280 -0
- package/scripts/rag-embedding-identity.js +221 -0
- package/scripts/rag-precision-guardrails.js +112 -2
- package/scripts/remote-feedback-capture.js +159 -0
- package/scripts/research-agent-harness.js +256 -0
- package/scripts/rsi-safety-hillclimb.js +200 -0
- package/scripts/rule-sprawl.js +188 -0
- package/scripts/schedule-manager.js +147 -0
- package/scripts/self-heal.js +8 -0
- package/scripts/session-lease.js +415 -0
- package/scripts/simatree-data-governance.js +347 -0
- package/scripts/slo-alert-engine.js +172 -7
- package/scripts/solver-parity.js +539 -0
- package/scripts/stealth-memory-injection-gate.js +333 -0
- package/scripts/switchyard-router.js +366 -0
- package/scripts/telemetry-analytics.js +84 -27
- package/scripts/temporal-decay-weighting.js +138 -0
- package/scripts/test-all.js +165 -0
- package/scripts/token-savings.js +42 -0
- package/scripts/tool-kpi-tracker.js +108 -5
- package/scripts/tool-registry.js +193 -5
- package/scripts/universal-claim-evaluator.js +14 -2
- package/scripts/vector-store.js +279 -9
- package/scripts/workflow-notebook.js +391 -0
- package/scripts/workflow-sentinel.js +111 -12
- package/scripts/workos-production-guard.js +260 -0
- package/scripts/workspace-search-route.js +515 -0
- package/server.json +2 -2
- package/src/agent-identity-boundary.js +76 -0
- package/src/agent-retrieval-cache.js +155 -0
- package/src/alert-noise-ledger.js +502 -0
- package/src/api/server.js +802 -185
- package/src/git-fast-cache.js +220 -0
- package/src/git-wal-sync.js +156 -0
- package/src/hash-anchored-edit.js +82 -0
- package/src/hermes-platform-protocol.js +475 -0
- package/src/hermes-sync-plane.js +241 -0
- package/src/index.js +30 -1
- package/src/iso42001-compliance-guard.js +97 -0
- package/src/latency-budget.js +244 -0
- package/src/mcp-writeguard.js +316 -0
- package/src/miminions-adapter.js +106 -0
- package/src/pipeline-compass.js +104 -0
- package/src/ppl-alert-pipeline.js +284 -0
- package/src/rendezvous-router.js +90 -0
- package/src/security-questionnaire.js +195 -0
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* mcp-wiring-doctor.js
|
|
6
|
+
*
|
|
7
|
+
* Detects the structural RAG-loop break that unattended agents hit:
|
|
8
|
+
* AGENTS.md mandates recall/capture_feedback, but:
|
|
9
|
+
* 1. `.mcp.json` may not list the thumbgate MCP server
|
|
10
|
+
* 2. The lessons store may be missing (Mac-local only / empty container)
|
|
11
|
+
* 3. Remote hosted capture env may be unset, so cloud runs cannot write
|
|
12
|
+
*
|
|
13
|
+
* High-ROI: make the gap loud at session-start / doctor instead of silent.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { resolveMcpEntry } = require('./mcp-config');
|
|
19
|
+
const {
|
|
20
|
+
isRemoteCaptureConfigured,
|
|
21
|
+
resolveApiKey,
|
|
22
|
+
resolveBaseUrl,
|
|
23
|
+
} = require('./remote-feedback-capture');
|
|
24
|
+
|
|
25
|
+
const PKG_ROOT = path.resolve(__dirname, '..');
|
|
26
|
+
const PKG_VERSION = require('../package.json').version;
|
|
27
|
+
|
|
28
|
+
const LEGACY_MCP_KEYS = new Set(['mcp-memory-gateway', 'rlhf']);
|
|
29
|
+
|
|
30
|
+
function readJsonIfExists(filePath) {
|
|
31
|
+
try {
|
|
32
|
+
if (!fs.existsSync(filePath)) return null;
|
|
33
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
34
|
+
} catch {
|
|
35
|
+
return { __parseError: true };
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function listMcpServers(config) {
|
|
40
|
+
if (!config || typeof config !== 'object') return [];
|
|
41
|
+
const servers = config.mcpServers || config.servers || {};
|
|
42
|
+
return Object.keys(servers || {});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hasThumbgateServer(config) {
|
|
46
|
+
if (!config || config.__parseError) return false;
|
|
47
|
+
const servers = config.mcpServers || config.servers || {};
|
|
48
|
+
if (servers.thumbgate) return true;
|
|
49
|
+
// Accept explicit package/npx forms under alternate keys only if command mentions thumbgate
|
|
50
|
+
return Object.entries(servers).some(([key, entry]) => {
|
|
51
|
+
if (LEGACY_MCP_KEYS.has(key)) return false;
|
|
52
|
+
const blob = JSON.stringify(entry || {}).toLowerCase();
|
|
53
|
+
return blob.includes('thumbgate') || blob.includes('server-stdio.js');
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function resolveLessonsStore(projectRoot, env = process.env) {
|
|
58
|
+
const candidates = [
|
|
59
|
+
env.THUMBGATE_FEEDBACK_DIR,
|
|
60
|
+
path.join(projectRoot, '.thumbgate'),
|
|
61
|
+
path.join(projectRoot, '.claude', 'memory', 'feedback'),
|
|
62
|
+
env.HOME ? path.join(env.HOME, '.thumbgate') : null,
|
|
63
|
+
].filter(Boolean);
|
|
64
|
+
|
|
65
|
+
for (const dir of candidates) {
|
|
66
|
+
try {
|
|
67
|
+
if (fs.existsSync(dir) && fs.statSync(dir).isDirectory()) {
|
|
68
|
+
const writable = (() => {
|
|
69
|
+
try {
|
|
70
|
+
fs.accessSync(dir, fs.constants.W_OK);
|
|
71
|
+
return true;
|
|
72
|
+
} catch {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
})();
|
|
76
|
+
return { path: dir, present: true, writable };
|
|
77
|
+
}
|
|
78
|
+
} catch {
|
|
79
|
+
/* continue */
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const preferred = candidates[0] || path.join(projectRoot, '.thumbgate');
|
|
84
|
+
let parent = preferred;
|
|
85
|
+
while (!fs.existsSync(parent)) {
|
|
86
|
+
const next = path.dirname(parent);
|
|
87
|
+
if (next === parent) break;
|
|
88
|
+
parent = next;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
fs.accessSync(parent, fs.constants.W_OK);
|
|
92
|
+
return { path: preferred, present: false, writable: true, creatable: true };
|
|
93
|
+
} catch {
|
|
94
|
+
return { path: preferred, present: false, writable: false, creatable: false };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function remoteCaptureConfigured(env = process.env) {
|
|
99
|
+
const base = resolveBaseUrl(env);
|
|
100
|
+
const key = resolveApiKey(env);
|
|
101
|
+
return {
|
|
102
|
+
configured: isRemoteCaptureConfigured(env),
|
|
103
|
+
baseUrl: base || null,
|
|
104
|
+
hasKey: Boolean(key),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function isContainerLike(env = process.env) {
|
|
109
|
+
if (env.container || env.CONTAINER) return true;
|
|
110
|
+
if (fs.existsSync('/.dockerenv')) return true;
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* @returns {{
|
|
116
|
+
* overall: 'ok'|'warning'|'error',
|
|
117
|
+
* findings: string[],
|
|
118
|
+
* mcp: object,
|
|
119
|
+
* lessonsStore: object,
|
|
120
|
+
* remote: object,
|
|
121
|
+
* unattendedCaptureReady: boolean,
|
|
122
|
+
* }}
|
|
123
|
+
*/
|
|
124
|
+
function wiringReport(projectRoot = process.cwd(), env = process.env) {
|
|
125
|
+
const root = path.resolve(projectRoot);
|
|
126
|
+
const findings = [];
|
|
127
|
+
let overall = 'ok';
|
|
128
|
+
|
|
129
|
+
const mcpPath = path.join(root, '.mcp.json');
|
|
130
|
+
const claudeSettingsPath = path.join(root, '.claude', 'settings.json');
|
|
131
|
+
const cursorMcpPath = path.join(root, '.cursor', 'mcp.json');
|
|
132
|
+
|
|
133
|
+
const mcpConfig = readJsonIfExists(mcpPath);
|
|
134
|
+
const claudeSettings = readJsonIfExists(claudeSettingsPath);
|
|
135
|
+
const cursorMcp = readJsonIfExists(cursorMcpPath);
|
|
136
|
+
|
|
137
|
+
const mcpServers = listMcpServers(mcpConfig);
|
|
138
|
+
const hasProjectMcpFile = fs.existsSync(mcpPath);
|
|
139
|
+
const thumbgateInProjectMcp = hasThumbgateServer(mcpConfig);
|
|
140
|
+
const thumbgateInCursor = hasThumbgateServer(cursorMcp);
|
|
141
|
+
const thumbgateInClaudeSettings = hasThumbgateServer(claudeSettings);
|
|
142
|
+
|
|
143
|
+
if (!hasProjectMcpFile) {
|
|
144
|
+
findings.push('Missing project .mcp.json — bootstrap file required by agent-readiness; MCP tools are not project-wired.');
|
|
145
|
+
overall = 'error';
|
|
146
|
+
} else if (mcpConfig && mcpConfig.__parseError) {
|
|
147
|
+
findings.push('.mcp.json is not valid JSON.');
|
|
148
|
+
overall = 'error';
|
|
149
|
+
} else if (!thumbgateInProjectMcp) {
|
|
150
|
+
findings.push(
|
|
151
|
+
`.mcp.json has no thumbgate server (found: ${mcpServers.join(', ') || 'none'}). ` +
|
|
152
|
+
'capture_feedback / recall are uncallable for agents using this project MCP config. ' +
|
|
153
|
+
'Add mcpServers.thumbgate (see adapters/claude/.mcp.json) or run: node scripts/install-mcp.js --project'
|
|
154
|
+
);
|
|
155
|
+
overall = 'error';
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
for (const key of mcpServers) {
|
|
159
|
+
if (LEGACY_MCP_KEYS.has(key)) {
|
|
160
|
+
findings.push(`Detected legacy MCP key "${key}" in .mcp.json — migrate to "thumbgate".`);
|
|
161
|
+
if (overall === 'ok') overall = 'warning';
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!thumbgateInClaudeSettings && !thumbgateInCursor && !thumbgateInProjectMcp) {
|
|
166
|
+
findings.push('No thumbgate MCP entry in .mcp.json, .claude/settings.json, or .cursor/mcp.json.');
|
|
167
|
+
overall = 'error';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const lessonsStore = resolveLessonsStore(root, env);
|
|
171
|
+
const remote = remoteCaptureConfigured(env);
|
|
172
|
+
const container = isContainerLike(env);
|
|
173
|
+
|
|
174
|
+
if (!lessonsStore.present && !lessonsStore.writable) {
|
|
175
|
+
findings.push(
|
|
176
|
+
'No lessons store found (.thumbgate/ or THUMBGATE_FEEDBACK_DIR). ' +
|
|
177
|
+
'Local capture_feedback cannot persist. Unattended/cloud runs need THUMBGATE_FEEDBACK_DIR ' +
|
|
178
|
+
'or hosted capture via THUMBGATE_API_BASE_URL + THUMBGATE_API_KEY.'
|
|
179
|
+
);
|
|
180
|
+
if (overall === 'ok') overall = 'warning';
|
|
181
|
+
if (container) overall = 'error';
|
|
182
|
+
} else if (!lessonsStore.writable) {
|
|
183
|
+
findings.push(`Lessons store present but not writable: ${lessonsStore.path}`);
|
|
184
|
+
if (overall === 'ok') overall = 'warning';
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (container && !lessonsStore.writable && !remote.configured) {
|
|
188
|
+
findings.push(
|
|
189
|
+
'Container/unattended runtime: neither local lessons store nor hosted capture env is configured. ' +
|
|
190
|
+
'AGENTS.md RAG loop (recall → act → capture_feedback) is unenforceable in this environment.'
|
|
191
|
+
);
|
|
192
|
+
overall = 'error';
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (container && remote.configured) {
|
|
196
|
+
findings.push('Hosted capture env is set — unattended runs can POST feedback to the API when local store is missing.');
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const unattendedCaptureReady = Boolean(
|
|
200
|
+
lessonsStore.writable || remote.configured
|
|
201
|
+
);
|
|
202
|
+
|
|
203
|
+
if (!unattendedCaptureReady && overall === 'ok') {
|
|
204
|
+
overall = 'warning';
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return {
|
|
208
|
+
overall,
|
|
209
|
+
findings,
|
|
210
|
+
mcp: {
|
|
211
|
+
projectMcpPath: mcpPath,
|
|
212
|
+
projectMcpPresent: hasProjectMcpFile,
|
|
213
|
+
servers: mcpServers,
|
|
214
|
+
thumbgateInProjectMcp,
|
|
215
|
+
thumbgateInCursor,
|
|
216
|
+
thumbgateInClaudeSettings,
|
|
217
|
+
},
|
|
218
|
+
lessonsStore,
|
|
219
|
+
remote,
|
|
220
|
+
container,
|
|
221
|
+
unattendedCaptureReady,
|
|
222
|
+
recommendation: unattendedCaptureReady
|
|
223
|
+
? 'RAG capture path is available (local store and/or hosted API).'
|
|
224
|
+
: 'Wire mcpServers.thumbgate in .mcp.json and set THUMBGATE_FEEDBACK_DIR or THUMBGATE_API_BASE_URL+THUMBGATE_API_KEY for unattended agents.',
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function formatReport(report) {
|
|
229
|
+
const lines = [
|
|
230
|
+
`ThumbGate MCP wiring doctor: ${report.overall.toUpperCase()}`,
|
|
231
|
+
` Project .mcp.json: ${report.mcp.projectMcpPresent ? 'present' : 'MISSING'} (thumbgate: ${report.mcp.thumbgateInProjectMcp ? 'yes' : 'NO'})`,
|
|
232
|
+
` Lessons store: ${report.lessonsStore.present ? report.lessonsStore.path : 'NONE'} (writable: ${report.lessonsStore.writable})`,
|
|
233
|
+
` Hosted capture: ${report.remote.configured ? 'configured' : 'not configured'}`,
|
|
234
|
+
` Unattended capture ready: ${report.unattendedCaptureReady ? 'yes' : 'NO'}`,
|
|
235
|
+
` Container-like: ${report.container ? 'yes' : 'no'}`,
|
|
236
|
+
];
|
|
237
|
+
if (report.findings.length) {
|
|
238
|
+
lines.push(' Findings:');
|
|
239
|
+
for (const f of report.findings) lines.push(` - ${f}`);
|
|
240
|
+
}
|
|
241
|
+
lines.push(` ${report.recommendation}`);
|
|
242
|
+
return lines.join('\n');
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function applyFix(projectRoot = process.cwd()) {
|
|
246
|
+
const root = path.resolve(projectRoot);
|
|
247
|
+
const mcpPath = path.join(root, '.mcp.json');
|
|
248
|
+
const desired = {
|
|
249
|
+
mcpServers: {
|
|
250
|
+
thumbgate: resolveMcpEntry({
|
|
251
|
+
pkgRoot: PKG_ROOT,
|
|
252
|
+
pkgVersion: PKG_VERSION,
|
|
253
|
+
scope: 'project',
|
|
254
|
+
targetDir: root,
|
|
255
|
+
}),
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
|
|
259
|
+
let existing = {};
|
|
260
|
+
if (fs.existsSync(mcpPath)) {
|
|
261
|
+
try {
|
|
262
|
+
existing = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
|
|
263
|
+
} catch {
|
|
264
|
+
existing = {};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
if (!existing.mcpServers || typeof existing.mcpServers !== 'object') {
|
|
269
|
+
existing.mcpServers = {};
|
|
270
|
+
}
|
|
271
|
+
// Preserve other servers; force thumbgate entry.
|
|
272
|
+
existing.mcpServers.thumbgate = desired.mcpServers.thumbgate;
|
|
273
|
+
for (const legacy of LEGACY_MCP_KEYS) {
|
|
274
|
+
if (Object.prototype.hasOwnProperty.call(existing.mcpServers, legacy)) {
|
|
275
|
+
delete existing.mcpServers[legacy];
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
fs.writeFileSync(mcpPath, `${JSON.stringify(existing, null, 2)}\n`, 'utf8');
|
|
280
|
+
return { wrote: mcpPath, config: existing };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (path.resolve(process.argv[1] || '') === path.resolve(__filename)) {
|
|
284
|
+
const args = process.argv.slice(2);
|
|
285
|
+
const fix = args.includes('--fix');
|
|
286
|
+
const json = args.includes('--json');
|
|
287
|
+
const projectRoot = process.cwd();
|
|
288
|
+
|
|
289
|
+
if (fix) {
|
|
290
|
+
const result = applyFix(projectRoot);
|
|
291
|
+
if (json) {
|
|
292
|
+
console.log(JSON.stringify({ fixed: true, ...result }, null, 2));
|
|
293
|
+
} else {
|
|
294
|
+
console.log(`Wrote ${result.wrote}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const report = wiringReport(projectRoot);
|
|
299
|
+
if (json) {
|
|
300
|
+
console.log(JSON.stringify(report, null, 2));
|
|
301
|
+
} else {
|
|
302
|
+
console.log(formatReport(report));
|
|
303
|
+
}
|
|
304
|
+
process.exit(report.overall === 'error' ? 2 : report.overall === 'warning' ? 1 : 0);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
module.exports = {
|
|
308
|
+
wiringReport,
|
|
309
|
+
formatReport,
|
|
310
|
+
applyFix,
|
|
311
|
+
hasThumbgateServer,
|
|
312
|
+
resolveLessonsStore,
|
|
313
|
+
remoteCaptureConfigured,
|
|
314
|
+
};
|
|
@@ -8,6 +8,39 @@ const { scanText, buildSafeSummary, redactText } = require('./secret-scanner');
|
|
|
8
8
|
const SHIELDCORTEX_RUNNER_PATH = path.join(__dirname, 'shieldcortex-memory-firewall-runner.mjs');
|
|
9
9
|
const VALID_PROVIDERS = new Set(['auto', 'shieldcortex', 'local', 'off']);
|
|
10
10
|
const VALID_MODES = new Set(['strict', 'balanced', 'permissive']);
|
|
11
|
+
const UNTRUSTED_SOURCE_TYPES = new Set([
|
|
12
|
+
'browser',
|
|
13
|
+
'document',
|
|
14
|
+
'email',
|
|
15
|
+
'external',
|
|
16
|
+
'file',
|
|
17
|
+
'imap',
|
|
18
|
+
'third-party',
|
|
19
|
+
'tool-output',
|
|
20
|
+
'web',
|
|
21
|
+
'webhook',
|
|
22
|
+
]);
|
|
23
|
+
const MEMORY_WRITE_PATTERNS = [
|
|
24
|
+
/\b(?:add|commit|persist|record|remember|save|store|write)\b.{0,60}\b(?:memory|memories|preference|profile|workspace)\b/i,
|
|
25
|
+
/\bremember\b.{0,40}\b(?:forever|permanently|across (?:future|subsequent) sessions?|for (?:future|subsequent) sessions?)\b/i,
|
|
26
|
+
/\b(?:update|change|replace|set)\b.{0,50}\b(?:memory|preference|policy|rule|profile)\b/i,
|
|
27
|
+
/\b(?:from now on|going forward|in future sessions?)\b/i,
|
|
28
|
+
/\b(?:always|never)\b.{0,50}\b(?:use|allow|approve|block|prefer|trust|verify)\b/i,
|
|
29
|
+
];
|
|
30
|
+
const STEALTH_PATTERNS = [
|
|
31
|
+
/\b(?:do not|don't|never)\b.{0,40}\b(?:disclose|mention|notify|reveal|say|show|tell|warn)\b/i,
|
|
32
|
+
/\b(?:hide|silently|stealthily|without (?:alerting|mentioning|notifying|telling|warning))\b/i,
|
|
33
|
+
/\bno need to (?:mention|notify|tell|warn)\b/i,
|
|
34
|
+
];
|
|
35
|
+
const OVERRIDE_PATTERNS = [
|
|
36
|
+
/\bignore (?:all |any )?(?:earlier|previous|prior|system) instructions?\b/i,
|
|
37
|
+
/\b(?:developer|system) message\b/i,
|
|
38
|
+
/\b(?:administrator|admin|security team) (?:approved|requires|says)\b/i,
|
|
39
|
+
];
|
|
40
|
+
const DELAYED_INFLUENCE_PATTERNS = [
|
|
41
|
+
/\b(?:later|next time|subsequent|future)\b.{0,45}\b(?:action|behavior|decision|request|session|task)\b/i,
|
|
42
|
+
/\b(?:when|whenever)\b.{0,80}\b(?:approve|buy|deploy|install|login|merge|pay|publish|release|send|transfer)\b/i,
|
|
43
|
+
];
|
|
11
44
|
|
|
12
45
|
function resolveMemoryFirewallProvider(provider) {
|
|
13
46
|
const configured = String(
|
|
@@ -33,6 +66,9 @@ function canResolveShieldCortex() {
|
|
|
33
66
|
}
|
|
34
67
|
|
|
35
68
|
function buildIngressRecord(feedbackEvent = {}, memoryRecord = null) {
|
|
69
|
+
const memorySource = feedbackEvent.memorySource && typeof feedbackEvent.memorySource === 'object'
|
|
70
|
+
? feedbackEvent.memorySource
|
|
71
|
+
: {};
|
|
36
72
|
const feedbackPayload = {
|
|
37
73
|
signal: feedbackEvent.signal || null,
|
|
38
74
|
context: feedbackEvent.context || '',
|
|
@@ -79,24 +115,96 @@ function buildIngressRecord(feedbackEvent = {}, memoryRecord = null) {
|
|
|
79
115
|
project: 'thumbgate',
|
|
80
116
|
feedbackSignal: feedbackPayload.signal || null,
|
|
81
117
|
memoryCategory: memoryPayload ? memoryPayload.category : null,
|
|
118
|
+
sourceType: memorySource.type || feedbackEvent.sourceType || null,
|
|
119
|
+
sourceIdentifier: memorySource.identifier || feedbackEvent.sourceIdentifier || null,
|
|
120
|
+
sourceTrust: memorySource.trust || feedbackEvent.sourceTrust || null,
|
|
82
121
|
},
|
|
83
122
|
};
|
|
84
123
|
}
|
|
85
124
|
|
|
125
|
+
function normalizeSourceType(value) {
|
|
126
|
+
return String(value || '').trim().toLowerCase().replace(/[_\s]+/g, '-');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function assessStealthMemoryInjection(record, options = {}) {
|
|
130
|
+
const text = String(record && record.content || '');
|
|
131
|
+
const sourceType = normalizeSourceType(
|
|
132
|
+
options.sourceType || (record && record.metadata && record.metadata.sourceType)
|
|
133
|
+
);
|
|
134
|
+
const sourceTrust = String(
|
|
135
|
+
options.sourceTrust || (record && record.metadata && record.metadata.sourceTrust) || ''
|
|
136
|
+
).trim().toLowerCase();
|
|
137
|
+
const untrusted = sourceTrust === 'untrusted' || UNTRUSTED_SOURCE_TYPES.has(sourceType);
|
|
138
|
+
const indicators = [];
|
|
139
|
+
|
|
140
|
+
if (MEMORY_WRITE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('memory_write_instruction');
|
|
141
|
+
if (STEALTH_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('conversational_stealth');
|
|
142
|
+
if (OVERRIDE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('instruction_override');
|
|
143
|
+
if (DELAYED_INFLUENCE_PATTERNS.some((pattern) => pattern.test(text))) indicators.push('delayed_influence');
|
|
144
|
+
|
|
145
|
+
const highConfidence = indicators.includes('memory_write_instruction')
|
|
146
|
+
&& indicators.includes('conversational_stealth');
|
|
147
|
+
const mode = resolveMemoryFirewallMode(options.mode);
|
|
148
|
+
const blocked = untrusted && (
|
|
149
|
+
(mode === 'strict' && indicators.includes('memory_write_instruction'))
|
|
150
|
+
|| (mode === 'balanced' && highConfidence)
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
blocked,
|
|
155
|
+
highConfidence,
|
|
156
|
+
untrusted,
|
|
157
|
+
sourceType: sourceType || 'unknown',
|
|
158
|
+
sourceTrust: sourceTrust || 'unknown',
|
|
159
|
+
indicators,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function buildMemoryInjectionTelemetry(assessment) {
|
|
164
|
+
const indicators = Array.isArray(assessment && assessment.indicators)
|
|
165
|
+
? assessment.indicators
|
|
166
|
+
: [];
|
|
167
|
+
return {
|
|
168
|
+
'gen_ai.security.memory_injection.detected': indicators.length > 0,
|
|
169
|
+
'gen_ai.security.memory_injection.blocked': Boolean(assessment && assessment.blocked),
|
|
170
|
+
'gen_ai.security.memory_injection.high_confidence': Boolean(assessment && assessment.highConfidence),
|
|
171
|
+
'gen_ai.security.memory_injection.indicators': indicators,
|
|
172
|
+
'gen_ai.security.memory_source.type': assessment && assessment.sourceType || 'unknown',
|
|
173
|
+
'gen_ai.security.memory_source.trust': assessment && assessment.sourceTrust || 'unknown',
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
86
177
|
function buildLocalFirewallDecision(record, options = {}) {
|
|
178
|
+
const injectionAssessment = assessStealthMemoryInjection(record, options);
|
|
87
179
|
const scanResult = scanText(record.content, {
|
|
88
180
|
provider: options.secretProvider,
|
|
89
181
|
source: 'memory_ingress',
|
|
90
182
|
});
|
|
91
183
|
|
|
184
|
+
if (injectionAssessment.blocked) {
|
|
185
|
+
return {
|
|
186
|
+
allowed: false,
|
|
187
|
+
provider: 'local',
|
|
188
|
+
mode: options.mode,
|
|
189
|
+
reason: `Stealth memory injection blocked from untrusted ${injectionAssessment.sourceType} content.`,
|
|
190
|
+
threatIndicators: ['stealth_memory_injection', ...injectionAssessment.indicators],
|
|
191
|
+
findings: [],
|
|
192
|
+
provenance: injectionAssessment,
|
|
193
|
+
telemetry: buildMemoryInjectionTelemetry(injectionAssessment),
|
|
194
|
+
redactedPreview: redactText(record.content).slice(0, 400),
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
92
198
|
if (!scanResult.detected) {
|
|
93
199
|
return {
|
|
94
200
|
allowed: true,
|
|
95
201
|
provider: 'local',
|
|
96
202
|
mode: options.mode,
|
|
97
203
|
reason: 'Local memory-ingress scan passed.',
|
|
98
|
-
threatIndicators: [],
|
|
204
|
+
threatIndicators: injectionAssessment.untrusted ? injectionAssessment.indicators : [],
|
|
99
205
|
findings: [],
|
|
206
|
+
provenance: injectionAssessment,
|
|
207
|
+
telemetry: buildMemoryInjectionTelemetry(injectionAssessment),
|
|
100
208
|
redactedPreview: redactText(record.content).slice(0, 400),
|
|
101
209
|
};
|
|
102
210
|
}
|
|
@@ -164,8 +272,9 @@ function evaluateMemoryIngress({
|
|
|
164
272
|
memoryRecord = null,
|
|
165
273
|
provider,
|
|
166
274
|
mode,
|
|
167
|
-
sourceType
|
|
275
|
+
sourceType,
|
|
168
276
|
sourceIdentifier = 'feedback-loop',
|
|
277
|
+
sourceTrust,
|
|
169
278
|
secretProvider,
|
|
170
279
|
} = {}) {
|
|
171
280
|
const resolvedProvider = resolveMemoryFirewallProvider(provider);
|
|
@@ -199,6 +308,8 @@ function evaluateMemoryIngress({
|
|
|
199
308
|
const localDecision = buildLocalFirewallDecision(record, {
|
|
200
309
|
mode: resolvedMode,
|
|
201
310
|
secretProvider,
|
|
311
|
+
sourceType,
|
|
312
|
+
sourceTrust,
|
|
202
313
|
});
|
|
203
314
|
|
|
204
315
|
if (resolvedProvider === 'shieldcortex') {
|
|
@@ -216,6 +327,8 @@ function evaluateMemoryIngress({
|
|
|
216
327
|
module.exports = {
|
|
217
328
|
buildIngressRecord,
|
|
218
329
|
buildLocalFirewallDecision,
|
|
330
|
+
assessStealthMemoryInjection,
|
|
331
|
+
buildMemoryInjectionTelemetry,
|
|
219
332
|
evaluateMemoryIngress,
|
|
220
333
|
resolveMemoryFirewallMode,
|
|
221
334
|
resolveMemoryFirewallProvider,
|