mindforge-cc 11.2.0 → 11.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.mindforge/config.json +3 -2
- package/.mindforge/imported-agents.jsonl +154 -0
- package/CHANGELOG.md +80 -1
- package/MINDFORGE.md +5 -5
- package/README.md +1 -1
- package/bin/autonomous/mesh-self-healer.js +101 -28
- package/bin/browser/regression-writer.js +45 -3
- package/bin/browser/session-manager.js +21 -17
- package/bin/engine/logic-drift-detector.js +14 -6
- package/bin/engine/logic-validator.js +155 -25
- package/bin/engine/orbital-guardian.js +56 -10
- package/bin/engine/reason-source-aligner.js +19 -6
- package/bin/engine/remediation-engine.js +1 -1
- package/bin/engine/self-corrective-synthesizer.js +1 -1
- package/bin/engine/sre-manager.js +33 -6
- package/bin/governance/policy-engine.js +17 -4
- package/bin/governance/ztai-archiver.js +74 -9
- package/bin/governance/ztai-manager.js +3 -3
- package/bin/installer-core.js +126 -3
- package/bin/memory/eis-client.js +45 -4
- package/bin/memory/vector-hub.js +32 -0
- package/bin/review/finding-synthesizer.js +35 -6
- package/bin/security/trust-boundaries.js +96 -4
- package/bin/security/trust-gate-hook.js +13 -3
- package/bin/skill-registry.js +31 -20
- package/bin/spawn-agent.js +80 -1
- package/bin/sre/shadow-mirror.js +90 -40
- package/bin/utils/append-queue.js +12 -0
- package/bin/utils/file-io.js +4 -45
- package/bin/utils/version-check.js +21 -5
- package/bin/wizard/theme.js +4 -3
- package/package.json +3 -1
- package/subagents/.claude-plugin/marketplace.json +93 -0
- package/subagents/categories/01-core-development/.claude-plugin/plugin.json +24 -0
- package/subagents/categories/01-core-development/README.md +146 -0
- package/subagents/categories/01-core-development/api-designer-cc.md +237 -0
- package/subagents/categories/01-core-development/backend-developer.md +222 -0
- package/subagents/categories/01-core-development/design-bridge.md +129 -0
- package/subagents/categories/01-core-development/electron-pro.md +240 -0
- package/subagents/categories/01-core-development/frontend-developer.md +133 -0
- package/subagents/categories/01-core-development/fullstack-developer.md +235 -0
- package/subagents/categories/01-core-development/graphql-architect.md +238 -0
- package/subagents/categories/01-core-development/microservices-architect.md +239 -0
- package/subagents/categories/01-core-development/mobile-developer.md +283 -0
- package/subagents/categories/01-core-development/ui-designer.md +174 -0
- package/subagents/categories/01-core-development/websocket-engineer.md +150 -0
- package/subagents/categories/02-language-specialists/.claude-plugin/plugin.json +43 -0
- package/subagents/categories/02-language-specialists/README.md +245 -0
- package/subagents/categories/02-language-specialists/angular-architect.md +287 -0
- package/subagents/categories/02-language-specialists/cpp-pro.md +277 -0
- package/subagents/categories/02-language-specialists/csharp-developer.md +287 -0
- package/subagents/categories/02-language-specialists/django-developer.md +287 -0
- package/subagents/categories/02-language-specialists/dotnet-core-expert.md +287 -0
- package/subagents/categories/02-language-specialists/dotnet-framework-48-expert.md +306 -0
- package/subagents/categories/02-language-specialists/elixir-expert.md +311 -0
- package/subagents/categories/02-language-specialists/expo-react-native-expert.md +268 -0
- package/subagents/categories/02-language-specialists/fastapi-developer.md +287 -0
- package/subagents/categories/02-language-specialists/flutter-expert.md +287 -0
- package/subagents/categories/02-language-specialists/golang-pro.md +277 -0
- package/subagents/categories/02-language-specialists/java-architect.md +287 -0
- package/subagents/categories/02-language-specialists/javascript-pro.md +277 -0
- package/subagents/categories/02-language-specialists/kotlin-specialist.md +287 -0
- package/subagents/categories/02-language-specialists/laravel-specialist.md +287 -0
- package/subagents/categories/02-language-specialists/nextjs-developer.md +287 -0
- package/subagents/categories/02-language-specialists/node-specialist.md +124 -0
- package/subagents/categories/02-language-specialists/php-pro.md +287 -0
- package/subagents/categories/02-language-specialists/powershell-51-expert.md +59 -0
- package/subagents/categories/02-language-specialists/powershell-7-expert.md +57 -0
- package/subagents/categories/02-language-specialists/python-pro.md +277 -0
- package/subagents/categories/02-language-specialists/rails-expert.md +358 -0
- package/subagents/categories/02-language-specialists/react-specialist-cc.md +287 -0
- package/subagents/categories/02-language-specialists/rust-engineer.md +287 -0
- package/subagents/categories/02-language-specialists/spring-boot-engineer.md +287 -0
- package/subagents/categories/02-language-specialists/sql-pro.md +287 -0
- package/subagents/categories/02-language-specialists/swift-expert.md +287 -0
- package/subagents/categories/02-language-specialists/symfony-specialist.md +354 -0
- package/subagents/categories/02-language-specialists/typescript-pro.md +277 -0
- package/subagents/categories/02-language-specialists/vue-expert.md +287 -0
- package/subagents/categories/03-infrastructure/.claude-plugin/plugin.json +29 -0
- package/subagents/categories/03-infrastructure/README.md +170 -0
- package/subagents/categories/03-infrastructure/azure-infra-engineer.md +53 -0
- package/subagents/categories/03-infrastructure/cloud-architect-cc.md +277 -0
- package/subagents/categories/03-infrastructure/database-administrator.md +287 -0
- package/subagents/categories/03-infrastructure/deployment-engineer.md +287 -0
- package/subagents/categories/03-infrastructure/devops-engineer-cc.md +287 -0
- package/subagents/categories/03-infrastructure/devops-incident-responder.md +287 -0
- package/subagents/categories/03-infrastructure/docker-expert.md +278 -0
- package/subagents/categories/03-infrastructure/incident-responder.md +287 -0
- package/subagents/categories/03-infrastructure/kubernetes-specialist.md +287 -0
- package/subagents/categories/03-infrastructure/network-engineer.md +287 -0
- package/subagents/categories/03-infrastructure/platform-engineer-cc.md +287 -0
- package/subagents/categories/03-infrastructure/security-engineer.md +277 -0
- package/subagents/categories/03-infrastructure/sre-engineer.md +287 -0
- package/subagents/categories/03-infrastructure/terraform-engineer.md +287 -0
- package/subagents/categories/03-infrastructure/terragrunt-expert.md +307 -0
- package/subagents/categories/03-infrastructure/windows-infra-admin.md +52 -0
- package/subagents/categories/04-quality-security/.claude-plugin/plugin.json +30 -0
- package/subagents/categories/04-quality-security/README.md +175 -0
- package/subagents/categories/04-quality-security/accessibility-tester-cc.md +277 -0
- package/subagents/categories/04-quality-security/ad-security-reviewer.md +56 -0
- package/subagents/categories/04-quality-security/ai-writing-auditor.md +77 -0
- package/subagents/categories/04-quality-security/architect-reviewer.md +287 -0
- package/subagents/categories/04-quality-security/chaos-engineer-cc.md +277 -0
- package/subagents/categories/04-quality-security/code-reviewer.md +287 -0
- package/subagents/categories/04-quality-security/compliance-auditor-cc.md +277 -0
- package/subagents/categories/04-quality-security/debugger-cc.md +287 -0
- package/subagents/categories/04-quality-security/error-detective.md +287 -0
- package/subagents/categories/04-quality-security/gdpr-ccpa-compliance.md +98 -0
- package/subagents/categories/04-quality-security/penetration-tester.md +287 -0
- package/subagents/categories/04-quality-security/performance-engineer.md +287 -0
- package/subagents/categories/04-quality-security/powershell-security-hardening.md +54 -0
- package/subagents/categories/04-quality-security/qa-expert.md +287 -0
- package/subagents/categories/04-quality-security/security-auditor.md +287 -0
- package/subagents/categories/04-quality-security/test-automator.md +287 -0
- package/subagents/categories/04-quality-security/ui-ux-tester.md +234 -0
- package/subagents/categories/05-data-ai/.claude-plugin/plugin.json +26 -0
- package/subagents/categories/05-data-ai/README.md +153 -0
- package/subagents/categories/05-data-ai/ai-engineer.md +287 -0
- package/subagents/categories/05-data-ai/data-analyst.md +277 -0
- package/subagents/categories/05-data-ai/data-engineer-cc.md +287 -0
- package/subagents/categories/05-data-ai/data-scientist.md +287 -0
- package/subagents/categories/05-data-ai/database-optimizer.md +287 -0
- package/subagents/categories/05-data-ai/llm-architect.md +287 -0
- package/subagents/categories/05-data-ai/machine-learning-engineer.md +277 -0
- package/subagents/categories/05-data-ai/ml-engineer-cc.md +287 -0
- package/subagents/categories/05-data-ai/mlops-engineer.md +287 -0
- package/subagents/categories/05-data-ai/nlp-engineer.md +287 -0
- package/subagents/categories/05-data-ai/postgres-pro.md +287 -0
- package/subagents/categories/05-data-ai/prompt-engineer-cc.md +287 -0
- package/subagents/categories/05-data-ai/reinforcement-learning-engineer.md +277 -0
- package/subagents/categories/06-developer-experience/.claude-plugin/plugin.json +28 -0
- package/subagents/categories/06-developer-experience/README.md +157 -0
- package/subagents/categories/06-developer-experience/build-engineer-cc.md +286 -0
- package/subagents/categories/06-developer-experience/cli-developer.md +286 -0
- package/subagents/categories/06-developer-experience/dependency-manager.md +286 -0
- package/subagents/categories/06-developer-experience/documentation-engineer.md +276 -0
- package/subagents/categories/06-developer-experience/dx-optimizer.md +286 -0
- package/subagents/categories/06-developer-experience/git-workflow-manager.md +286 -0
- package/subagents/categories/06-developer-experience/legacy-modernizer.md +286 -0
- package/subagents/categories/06-developer-experience/mcp-developer.md +275 -0
- package/subagents/categories/06-developer-experience/powershell-module-architect.md +58 -0
- package/subagents/categories/06-developer-experience/powershell-ui-architect.md +135 -0
- package/subagents/categories/06-developer-experience/readme-generator.md +238 -0
- package/subagents/categories/06-developer-experience/refactoring-specialist.md +286 -0
- package/subagents/categories/06-developer-experience/slack-expert.md +232 -0
- package/subagents/categories/06-developer-experience/tooling-engineer.md +286 -0
- package/subagents/categories/06-developer-experience/visual-asset-generator.md +34 -0
- package/subagents/categories/07-specialized-domains/.claude-plugin/plugin.json +27 -0
- package/subagents/categories/07-specialized-domains/README.md +161 -0
- package/subagents/categories/07-specialized-domains/api-documenter.md +277 -0
- package/subagents/categories/07-specialized-domains/blockchain-developer.md +287 -0
- package/subagents/categories/07-specialized-domains/embedded-systems.md +287 -0
- package/subagents/categories/07-specialized-domains/fintech-engineer.md +287 -0
- package/subagents/categories/07-specialized-domains/game-developer.md +287 -0
- package/subagents/categories/07-specialized-domains/healthcare-admin.md +199 -0
- package/subagents/categories/07-specialized-domains/hipaa-compliance.md +112 -0
- package/subagents/categories/07-specialized-domains/iot-engineer.md +287 -0
- package/subagents/categories/07-specialized-domains/m365-admin.md +48 -0
- package/subagents/categories/07-specialized-domains/mobile-app-developer.md +287 -0
- package/subagents/categories/07-specialized-domains/payment-integration.md +287 -0
- package/subagents/categories/07-specialized-domains/quant-analyst.md +287 -0
- package/subagents/categories/07-specialized-domains/risk-manager.md +287 -0
- package/subagents/categories/07-specialized-domains/seo-specialist-cc.md +184 -0
- package/subagents/categories/08-business-product/.claude-plugin/plugin.json +29 -0
- package/subagents/categories/08-business-product/README.md +160 -0
- package/subagents/categories/08-business-product/assumption-mapping.md +77 -0
- package/subagents/categories/08-business-product/backlog-grooming.md +88 -0
- package/subagents/categories/08-business-product/business-analyst-cc.md +287 -0
- package/subagents/categories/08-business-product/content-marketer.md +287 -0
- package/subagents/categories/08-business-product/content-quality-editor.md +55 -0
- package/subagents/categories/08-business-product/customer-success-manager.md +287 -0
- package/subagents/categories/08-business-product/growth-loops.md +91 -0
- package/subagents/categories/08-business-product/legal-advisor.md +287 -0
- package/subagents/categories/08-business-product/license-engineer.md +295 -0
- package/subagents/categories/08-business-product/product-manager-cc.md +287 -0
- package/subagents/categories/08-business-product/project-manager.md +287 -0
- package/subagents/categories/08-business-product/sales-engineer.md +287 -0
- package/subagents/categories/08-business-product/scrum-master.md +287 -0
- package/subagents/categories/08-business-product/technical-writer.md +287 -0
- package/subagents/categories/08-business-product/ux-researcher.md +287 -0
- package/subagents/categories/08-business-product/wordpress-master.md +316 -0
- package/subagents/categories/09-meta-orchestration/.claude-plugin/plugin.json +24 -0
- package/subagents/categories/09-meta-orchestration/README.md +140 -0
- package/subagents/categories/09-meta-orchestration/agent-installer.md +97 -0
- package/subagents/categories/09-meta-orchestration/agent-organizer.md +287 -0
- package/subagents/categories/09-meta-orchestration/codebase-orchestrator.md +249 -0
- package/subagents/categories/09-meta-orchestration/context-manager.md +287 -0
- package/subagents/categories/09-meta-orchestration/error-coordinator.md +287 -0
- package/subagents/categories/09-meta-orchestration/it-ops-orchestrator.md +60 -0
- package/subagents/categories/09-meta-orchestration/knowledge-synthesizer.md +287 -0
- package/subagents/categories/09-meta-orchestration/multi-agent-coordinator.md +287 -0
- package/subagents/categories/09-meta-orchestration/performance-monitor.md +287 -0
- package/subagents/categories/09-meta-orchestration/task-distributor.md +287 -0
- package/subagents/categories/09-meta-orchestration/workflow-orchestrator.md +287 -0
- package/subagents/categories/10-research-analysis/.claude-plugin/plugin.json +24 -0
- package/subagents/categories/10-research-analysis/README.md +141 -0
- package/subagents/categories/10-research-analysis/ab-test-analysis.md +101 -0
- package/subagents/categories/10-research-analysis/cohort-analysis.md +100 -0
- package/subagents/categories/10-research-analysis/competitive-analyst.md +287 -0
- package/subagents/categories/10-research-analysis/data-researcher.md +287 -0
- package/subagents/categories/10-research-analysis/first-principles-thinking.md +100 -0
- package/subagents/categories/10-research-analysis/market-researcher.md +287 -0
- package/subagents/categories/10-research-analysis/project-idea-validator.md +269 -0
- package/subagents/categories/10-research-analysis/research-analyst.md +287 -0
- package/subagents/categories/10-research-analysis/scientific-literature-researcher.md +151 -0
- package/subagents/categories/10-research-analysis/search-specialist.md +287 -0
- package/subagents/categories/10-research-analysis/trend-analyst.md +287 -0
- package/subagents/tools/subagent-catalog/README.md +58 -0
- package/subagents/tools/subagent-catalog/config.sh +94 -0
- package/subagents/tools/subagent-catalog/fetch.md +82 -0
- package/subagents/tools/subagent-catalog/invalidate.md +47 -0
- package/subagents/tools/subagent-catalog/list.md +54 -0
- package/subagents/tools/subagent-catalog/search.md +58 -0
|
@@ -20,19 +20,35 @@ class ZTAIArchiver {
|
|
|
20
20
|
* @param {string} archiverDid - The DID of the archiver (e.g., Release Manager)
|
|
21
21
|
* @returns {Promise<Object>} - The signed manifest
|
|
22
22
|
*/
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
23
|
+
/**
|
|
24
|
+
* Computes the cumulative-hash-chain "Merkle root" for a block of entries.
|
|
25
|
+
*
|
|
26
|
+
* This is the SINGLE source of truth for the root algorithm. Both
|
|
27
|
+
* generateManifest() (write path) and verifyIntegrity() (read/verify path)
|
|
28
|
+
* MUST call this so the two can never drift — a drift would re-introduce the
|
|
29
|
+
* false-assurance defect (audit finding #15 / UC-22).
|
|
30
|
+
*
|
|
31
|
+
* @param {Array<Object>} entries - Ordered block of audit entries.
|
|
32
|
+
* @returns {string} - The cumulative SHA-256 hash chain (hex).
|
|
33
|
+
*/
|
|
34
|
+
_computeMerkleRoot(entries) {
|
|
35
|
+
const blockHashes = entries.map(e =>
|
|
28
36
|
crypto.createHash('sha256').update(JSON.stringify(e)).digest('hex')
|
|
29
37
|
);
|
|
30
|
-
|
|
31
|
-
// Simple cumulative hash chain as a Merkle Root equivalent
|
|
38
|
+
|
|
39
|
+
// Simple cumulative hash chain as a Merkle Root equivalent.
|
|
32
40
|
let cumulativeHash = '';
|
|
33
41
|
for (const h of blockHashes) {
|
|
34
42
|
cumulativeHash = crypto.createHash('sha256').update(cumulativeHash + h).digest('hex');
|
|
35
43
|
}
|
|
44
|
+
return cumulativeHash;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async generateManifest(entries, archiverDid) {
|
|
48
|
+
if (!entries || entries.length === 0) return null;
|
|
49
|
+
|
|
50
|
+
// 1. Calculate the Merkle-like root hash of the block.
|
|
51
|
+
const cumulativeHash = this._computeMerkleRoot(entries);
|
|
36
52
|
|
|
37
53
|
const manifestMetadata = {
|
|
38
54
|
blockStart: entries[0].timestamp,
|
|
@@ -94,8 +110,57 @@ class ZTAIArchiver {
|
|
|
94
110
|
throw new Error(`CRITICAL: Manifest signature invalid for ${manifestPath}`);
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
// 2. Recalculate and Verify Merkle Root
|
|
98
|
-
//
|
|
113
|
+
// 2. Recalculate and Verify Merkle Root against the LIVE AUDIT.jsonl.
|
|
114
|
+
//
|
|
115
|
+
// A valid signature only proves the manifest itself is authentic; it says
|
|
116
|
+
// NOTHING about whether the underlying audit log still matches. We must
|
|
117
|
+
// recompute the root from disk and compare — anything less is false
|
|
118
|
+
// assurance (audit finding #15 / UC-22). Fail-closed on every error path:
|
|
119
|
+
// a missing/unreadable log MUST NOT pass.
|
|
120
|
+
let auditData;
|
|
121
|
+
try {
|
|
122
|
+
auditData = await fs.readFile(this.auditPath, 'utf8');
|
|
123
|
+
} catch (err) {
|
|
124
|
+
throw new Error(`CRITICAL: Audit log unreadable at ${this.auditPath} — cannot verify integrity (${err.message})`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
let allEntries;
|
|
128
|
+
try {
|
|
129
|
+
allEntries = auditData
|
|
130
|
+
.split('\n')
|
|
131
|
+
.filter(l => l.trim() !== '')
|
|
132
|
+
.map(l => JSON.parse(l));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
throw new Error(`CRITICAL: Audit log corrupted / unparseable for ${manifestPath} (${err.message})`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Select the block this manifest covers: entries whose timestamp falls
|
|
138
|
+
// within [blockStart, blockEnd] inclusive.
|
|
139
|
+
const start = Date.parse(manifest.blockStart);
|
|
140
|
+
const end = Date.parse(manifest.blockEnd);
|
|
141
|
+
const blockEntries = allEntries.filter(e => {
|
|
142
|
+
const ts = Date.parse(e.timestamp);
|
|
143
|
+
return ts >= start && ts <= end;
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Sanity-check the selected count against the manifest. A mismatch means
|
|
147
|
+
// entries were added or deleted within the covered window.
|
|
148
|
+
if (blockEntries.length !== manifest.entryCount) {
|
|
149
|
+
throw new Error(
|
|
150
|
+
'CRITICAL: Audit log tampering detected — block entry count mismatch ' +
|
|
151
|
+
`(manifest=${manifest.entryCount}, found=${blockEntries.length}) for ${manifestPath}`
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Recompute the root with the SAME algorithm used at archive time.
|
|
156
|
+
const recomputedRoot = this._computeMerkleRoot(blockEntries);
|
|
157
|
+
if (recomputedRoot !== manifest.merkleRoot) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`CRITICAL: Audit log tampering detected — Merkle root mismatch for ${manifestPath} ` +
|
|
160
|
+
`(expected=${manifest.merkleRoot}, recomputed=${recomputedRoot}).`
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
99
164
|
console.log(`[ZTAI-ARCHIVER] Integrity Verified for block ending ${manifest.blockEnd}`);
|
|
100
165
|
return true;
|
|
101
166
|
}
|
|
@@ -65,7 +65,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
async generate(did) {
|
|
68
|
-
console.log(`[ZTAI-HSM] Provisioning
|
|
68
|
+
console.log(`[ZTAI-HSM-SIM] Provisioning simulated (in-process) identity enclave for ${did}...`);
|
|
69
69
|
const { publicKey, privateKey } = await generateKeyPair('ed25519');
|
|
70
70
|
const pubPEM = publicKey.export({ type: 'spki', format: 'pem' });
|
|
71
71
|
|
|
@@ -82,7 +82,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
82
82
|
const record = this.enclaveStore.get(did);
|
|
83
83
|
if (!record) throw new Error(`Enclave record not found for ${did}`);
|
|
84
84
|
|
|
85
|
-
console.log(`[ZTAI-HSM]
|
|
85
|
+
console.log(`[ZTAI-HSM-SIM] Signing via simulated in-process enclave (NOT a hardware HSM/TPM) [DID: ${did}]`);
|
|
86
86
|
|
|
87
87
|
// Simulate enclave "wrapping" or "sealing" logic
|
|
88
88
|
const signature = crypto.sign(null, Buffer.from(data), record.privateKey);
|
|
@@ -93,7 +93,7 @@ class SecureEnclaveProvider extends KeyProvider {
|
|
|
93
93
|
}
|
|
94
94
|
|
|
95
95
|
async rotate(did) {
|
|
96
|
-
console.log(`[ZTAI-HSM] Rotating enclave keys for ${did}...`);
|
|
96
|
+
console.log(`[ZTAI-HSM-SIM] Rotating simulated enclave keys for ${did}...`);
|
|
97
97
|
return this.generate(did);
|
|
98
98
|
}
|
|
99
99
|
|
package/bin/installer-core.js
CHANGED
|
@@ -28,6 +28,7 @@ const RUNTIMES = {
|
|
|
28
28
|
docsSubdir: 'docs',
|
|
29
29
|
memorySubdir: 'memory',
|
|
30
30
|
pluginsSubdir: 'plugins',
|
|
31
|
+
agentsSubdir: 'agents',
|
|
31
32
|
},
|
|
32
33
|
antigravity: {
|
|
33
34
|
displayName: 'Antigravity',
|
|
@@ -95,6 +96,25 @@ const RUNTIMES = {
|
|
|
95
96
|
},
|
|
96
97
|
};
|
|
97
98
|
|
|
99
|
+
/**
|
|
100
|
+
* Reads the target project's experimental.pqc_demo flag — the SINGLE gate that
|
|
101
|
+
* the engine (bin/governance/quantum-crypto.js) uses to enable the simulated
|
|
102
|
+
* PQAS minter. Defaults to false (engine default) when the config is absent or
|
|
103
|
+
* unreadable, so the installer never over-claims that PQAS is enabled.
|
|
104
|
+
* @param {string} cwd - Target project root being installed into.
|
|
105
|
+
* @returns {boolean} - true only when experimental.pqc_demo === true.
|
|
106
|
+
*/
|
|
107
|
+
function isPqcDemoEnabled(cwd) {
|
|
108
|
+
try {
|
|
109
|
+
const cfgPath = path.join(cwd, '.mindforge', 'config.json');
|
|
110
|
+
if (!fs.existsSync(cfgPath)) return false;
|
|
111
|
+
const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
|
|
112
|
+
return cfg && cfg.experimental && cfg.experimental.pqc_demo === true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
98
118
|
/**
|
|
99
119
|
* Generates runtime-specific entry file content.
|
|
100
120
|
* e.g. replacing "Claude" with "Gemini" in GEMINI.md
|
|
@@ -184,6 +204,50 @@ const fsu = {
|
|
|
184
204
|
},
|
|
185
205
|
};
|
|
186
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Flatten-copy the imported Claude-Code subagents into a runtime's native agents
|
|
209
|
+
* directory. The 154 source files live under subagents/categories/NN-name/*.md;
|
|
210
|
+
* Claude Code auto-discovers agents from the top level of .claude/agents/, so we
|
|
211
|
+
* flatten (basenames are already collision-free after the -cc renames) and skip
|
|
212
|
+
* the per-category README.md index files. Returns the count installed.
|
|
213
|
+
* @param {string} agentsDir - Destination agents directory (absolute).
|
|
214
|
+
* @param {object} options - { noOverwrite }.
|
|
215
|
+
* @returns {number} number of agent files copied.
|
|
216
|
+
*/
|
|
217
|
+
function installSubagents(agentsDir, options = {}) {
|
|
218
|
+
const { noOverwrite = false } = options;
|
|
219
|
+
const sourceDir = src('subagents', 'categories');
|
|
220
|
+
if (!fsu.exists(sourceDir)) return 0;
|
|
221
|
+
|
|
222
|
+
fsu.ensureDir(agentsDir);
|
|
223
|
+
let count = 0;
|
|
224
|
+
for (const file of fsu.listFilesRecursive(sourceDir, '.md')) {
|
|
225
|
+
if (path.basename(file) === 'README.md') continue;
|
|
226
|
+
const dst = path.join(agentsDir, path.basename(file));
|
|
227
|
+
if (noOverwrite && fsu.exists(dst)) continue;
|
|
228
|
+
fsu.copy(file, dst);
|
|
229
|
+
count++;
|
|
230
|
+
}
|
|
231
|
+
return count;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The set of imported-subagent basenames (e.g. 'api-designer-cc.md'). Used by
|
|
236
|
+
* uninstall to remove ONLY our agents from a runtime's agents/ dir, never the
|
|
237
|
+
* user's own hand-authored agents that may live alongside them.
|
|
238
|
+
* @returns {Set<string>}
|
|
239
|
+
*/
|
|
240
|
+
function importedSubagentBasenames() {
|
|
241
|
+
const sourceDir = src('subagents', 'categories');
|
|
242
|
+
const names = new Set();
|
|
243
|
+
if (!fsu.exists(sourceDir)) return names;
|
|
244
|
+
for (const file of fsu.listFilesRecursive(sourceDir, '.md')) {
|
|
245
|
+
const base = path.basename(file);
|
|
246
|
+
if (base !== 'README.md') names.add(base);
|
|
247
|
+
}
|
|
248
|
+
return names;
|
|
249
|
+
}
|
|
250
|
+
|
|
187
251
|
// ── Registry Management ────────────────────────────────────────────────────────
|
|
188
252
|
const RegistryManager = {
|
|
189
253
|
getRegistryPath: () => path.join(os.homedir(), '.mindforge', 'registry.json'),
|
|
@@ -396,6 +460,13 @@ async function install(runtime, scope, options = {}) {
|
|
|
396
460
|
}
|
|
397
461
|
}
|
|
398
462
|
});
|
|
463
|
+
|
|
464
|
+
if (cfg.agentsSubdir && fsu.exists(src('subagents', 'categories'))) {
|
|
465
|
+
const agentCount = fsu.listFilesRecursive(src('subagents', 'categories'), '.md')
|
|
466
|
+
.filter(f => path.basename(f) !== 'README.md').length;
|
|
467
|
+
const countStr = `${agentCount} subagents`.padEnd(12);
|
|
468
|
+
console.log(` ${countStr} → ${path.join(baseDir, cfg.agentsSubdir)}`);
|
|
469
|
+
}
|
|
399
470
|
return;
|
|
400
471
|
}
|
|
401
472
|
|
|
@@ -531,6 +602,21 @@ async function install(runtime, scope, options = {}) {
|
|
|
531
602
|
});
|
|
532
603
|
}
|
|
533
604
|
|
|
605
|
+
// ── 2.2 Install Subagents (native Claude-Code agents, both scopes) ──────────
|
|
606
|
+
// The 154 imported subagents are Claude-Code-native .md files; Claude Code
|
|
607
|
+
// auto-discovers them from <runtime>/agents/. Installed for BOTH scopes so a
|
|
608
|
+
// global install also exposes them. Mirrored to .claude/agents/ for non-claude
|
|
609
|
+
// local runtimes (same cross-IDE rationale as the command mirror above).
|
|
610
|
+
if (cfg.agentsSubdir && !selfInstall) {
|
|
611
|
+
const agentsDir = path.join(baseDir, cfg.agentsSubdir);
|
|
612
|
+
const count = installSubagents(agentsDir, { noOverwrite: !force });
|
|
613
|
+
if (count > 0) Theme.printResolved(`${c.bold(`${count} subagents`)} (native agents)`);
|
|
614
|
+
}
|
|
615
|
+
if (scope === 'local' && runtime !== 'claude' && !selfInstall) {
|
|
616
|
+
const mirrorDir = path.join(process.cwd(), '.claude', 'agents');
|
|
617
|
+
installSubagents(mirrorDir, { noOverwrite: !force });
|
|
618
|
+
}
|
|
619
|
+
|
|
534
620
|
// ── 3. Framework files (local scope only, non-self-install) ─────────────────
|
|
535
621
|
if (scope === 'local' && !selfInstall) {
|
|
536
622
|
// .mindforge/ — framework engine files
|
|
@@ -650,9 +736,19 @@ async function install(runtime, scope, options = {}) {
|
|
|
650
736
|
}
|
|
651
737
|
});
|
|
652
738
|
|
|
653
|
-
// ✨ SOVEREIGN INITIALIZATION:
|
|
739
|
+
// ✨ SOVEREIGN INITIALIZATION: report actual security posture honestly.
|
|
740
|
+
// The PQAS minter is gated SOLELY behind experimental.pqc_demo (see
|
|
741
|
+
// bin/governance/quantum-crypto.js: getProvider/_assertPqcDemoEnabled). When
|
|
742
|
+
// that flag is off (the default) PQAS is inert/simulated — claiming it is
|
|
743
|
+
// "enabled" would contradict the engine and mislead operators (UC-22).
|
|
654
744
|
Theme.printStatus(c.magenta('Sovereign Intelligence v8.2.0 activated'), 'done');
|
|
655
|
-
|
|
745
|
+
if (isPqcDemoEnabled(process.cwd())) {
|
|
746
|
+
Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): SIMULATED demo ENABLED '
|
|
747
|
+
+ '(experimental.pqc_demo=true — simulated lattice crypto, NOT production trust)'), 'info');
|
|
748
|
+
} else {
|
|
749
|
+
Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): available in simulated/experimental '
|
|
750
|
+
+ 'mode (inactive by default — set experimental.pqc_demo=true to enable the simulated demo)'), 'info');
|
|
751
|
+
}
|
|
656
752
|
Theme.printStatus(c.dim(' - Proactive Semantic Intent Harvesting active'), 'info');
|
|
657
753
|
|
|
658
754
|
// bin/ utilities (remaining non-engine scripts)
|
|
@@ -683,9 +779,16 @@ async function uninstall(runtime, scope, options = {}) {
|
|
|
683
779
|
const cmdsDir = norm(path.join(baseDir, cfg.commandsSubdir));
|
|
684
780
|
const entryFile = norm(path.join(baseDir, cfg.entryFile));
|
|
685
781
|
|
|
782
|
+
const agentsDir = cfg.agentsSubdir ? norm(path.join(baseDir, cfg.agentsSubdir)) : null;
|
|
783
|
+
const importedAgents = importedSubagentBasenames();
|
|
784
|
+
|
|
686
785
|
console.log(`\n Uninstalling MindForge (${runtime} / ${scope})...`);
|
|
687
786
|
if (dryRun) {
|
|
688
787
|
console.log(` Would remove: ${cmdsDir}`);
|
|
788
|
+
if (agentsDir && fsu.exists(agentsDir)) {
|
|
789
|
+
const present = fsu.listFiles(agentsDir).filter(f => importedAgents.has(f)).length;
|
|
790
|
+
if (present > 0) console.log(` Would remove: ${present} imported subagents from ${agentsDir}`);
|
|
791
|
+
}
|
|
689
792
|
if (fsu.exists(entryFile) && fsu.read(entryFile).includes('MindForge'))
|
|
690
793
|
console.log(` Would remove: ${entryFile}`);
|
|
691
794
|
return;
|
|
@@ -697,6 +800,18 @@ async function uninstall(runtime, scope, options = {}) {
|
|
|
697
800
|
console.log(` ✅ Removed: ${cmdsDir}`);
|
|
698
801
|
}
|
|
699
802
|
|
|
803
|
+
// Remove ONLY our imported subagents — leave the user's own agents/ files intact.
|
|
804
|
+
if (agentsDir && fsu.exists(agentsDir)) {
|
|
805
|
+
let removed = 0;
|
|
806
|
+
for (const f of fsu.listFiles(agentsDir)) {
|
|
807
|
+
if (importedAgents.has(f)) {
|
|
808
|
+
fs.unlinkSync(path.join(agentsDir, f));
|
|
809
|
+
removed++;
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
if (removed > 0) console.log(` ✅ Removed: ${removed} imported subagents from ${agentsDir}`);
|
|
813
|
+
}
|
|
814
|
+
|
|
700
815
|
// Remove entry file only if it's a MindForge-generated file
|
|
701
816
|
if (fsu.exists(entryFile) && fsu.read(entryFile).includes('MindForge')) {
|
|
702
817
|
fs.unlinkSync(entryFile);
|
|
@@ -715,6 +830,7 @@ function collectManifestStats() {
|
|
|
715
830
|
const stats = {
|
|
716
831
|
personas: 0,
|
|
717
832
|
skills: 0,
|
|
833
|
+
subagents: 0,
|
|
718
834
|
governance: 0,
|
|
719
835
|
integrations: 0,
|
|
720
836
|
actions: 0,
|
|
@@ -731,6 +847,13 @@ function collectManifestStats() {
|
|
|
731
847
|
stats.integrations = fsu.listFiles(path.join(forgeSrc, 'integrations')).filter(f => f.endsWith('.md')).length;
|
|
732
848
|
}
|
|
733
849
|
|
|
850
|
+
// Imported subagents (subagents/categories/**, excluding category READMEs)
|
|
851
|
+
const subagentsSrc = src('subagents', 'categories');
|
|
852
|
+
if (fsu.exists(subagentsSrc)) {
|
|
853
|
+
stats.subagents = fsu.listFilesRecursive(subagentsSrc, '.md')
|
|
854
|
+
.filter(f => path.basename(f) !== 'README.md').length;
|
|
855
|
+
}
|
|
856
|
+
|
|
734
857
|
// Docs & Templates count
|
|
735
858
|
const refSrc = src('docs', 'references');
|
|
736
859
|
const tmpSrc = src('docs', 'templates');
|
|
@@ -748,7 +871,7 @@ function collectManifestStats() {
|
|
|
748
871
|
}
|
|
749
872
|
} catch (e) {
|
|
750
873
|
// Fallback to default values if counting fails
|
|
751
|
-
return { personas: 117, skills: 20, governance: 4, integrations: 6, actions: 71, docs: 12, templates: 21 };
|
|
874
|
+
return { personas: 117, skills: 20, subagents: 154, governance: 4, integrations: 6, actions: 71, docs: 12, templates: 21 };
|
|
752
875
|
}
|
|
753
876
|
|
|
754
877
|
return stats;
|
package/bin/memory/eis-client.js
CHANGED
|
@@ -100,10 +100,48 @@ class EISClient {
|
|
|
100
100
|
return [];
|
|
101
101
|
}
|
|
102
102
|
|
|
103
|
-
|
|
103
|
+
/**
|
|
104
|
+
* Verifies the provenance of a remote knowledge entry by cryptographically
|
|
105
|
+
* checking its signature against the signer DID's registered public key.
|
|
106
|
+
*
|
|
107
|
+
* HONEST / FAIL-CLOSED CONTRACT (UC-22, finding #22): this method NEVER
|
|
108
|
+
* returns true for a signature it has not actually verified. It returns true
|
|
109
|
+
* ONLY when ZTAI.verifySignature confirms the signature against a public key
|
|
110
|
+
* that is resolvable in the local trust registry. Every other case fails
|
|
111
|
+
* closed → false:
|
|
112
|
+
* - no/empty signature,
|
|
113
|
+
* - no signer DID on the entry,
|
|
114
|
+
* - the DID is not resolvable here (e.g. a genuinely remote peer whose key
|
|
115
|
+
* is not in the local registry — there is no remote DID-resolution infra
|
|
116
|
+
* yet, see resolveRemoteNode),
|
|
117
|
+
* - tampered payload or signature (crypto.verify returns false / throws).
|
|
118
|
+
*
|
|
119
|
+
* @param {{did?: string, signedData?: string}} entry - Provenance-bearing entry.
|
|
120
|
+
* `did` is the signer's DID; `signedData` is the exact canonical bytes that
|
|
121
|
+
* were signed (defaults to a deterministic JSON of the entry if absent).
|
|
122
|
+
* @param {string} signature - Base64 signature to verify.
|
|
123
|
+
* @returns {boolean} true only if cryptographically verified; false otherwise.
|
|
124
|
+
*/
|
|
104
125
|
verifyRemoteProvenance(entry, signature) {
|
|
105
|
-
if (!signature) return false;
|
|
106
|
-
return
|
|
126
|
+
if (!signature || typeof signature !== 'string') return false;
|
|
127
|
+
if (!entry || typeof entry !== 'object') return false;
|
|
128
|
+
|
|
129
|
+
const did = entry.did;
|
|
130
|
+
if (!did || typeof did !== 'string') return false;
|
|
131
|
+
|
|
132
|
+
// Canonical signed bytes: prefer an explicit signedData field, else a
|
|
133
|
+
// deterministic JSON of the entry (excluding the signature envelope).
|
|
134
|
+
const signedData = typeof entry.signedData === 'string'
|
|
135
|
+
? entry.signedData
|
|
136
|
+
: JSON.stringify(entry);
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
// ZTAI.verifySignature throws for an unresolvable (unregistered) DID —
|
|
140
|
+
// treat that as fail-closed rather than asserting verified provenance.
|
|
141
|
+
return ZTAI.verifySignature(did, signedData, signature) === true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
107
145
|
}
|
|
108
146
|
|
|
109
147
|
// TODO: implement when remote nodes are available
|
|
@@ -113,7 +151,10 @@ class EISClient {
|
|
|
113
151
|
|
|
114
152
|
/**
|
|
115
153
|
* [HARDEN] Generates a cryptographically signed auth header using the agent's DID.
|
|
116
|
-
* This
|
|
154
|
+
* This attaches OUTBOUND provenance to locally-originated requests (it signs
|
|
155
|
+
* what this node sends). It does NOT verify the provenance of inbound remote
|
|
156
|
+
* entries — that is verifyRemoteProvenance's job, which fails closed unless a
|
|
157
|
+
* signature is cryptographically verified against a resolvable public key.
|
|
117
158
|
*/
|
|
118
159
|
async getAuthHeader(action, resource) {
|
|
119
160
|
const manager = new ZTAI();
|
package/bin/memory/vector-hub.js
CHANGED
|
@@ -56,6 +56,26 @@ class VectorHub {
|
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
/**
|
|
60
|
+
* Idempotently add a column to an existing table (lightweight migration).
|
|
61
|
+
* SQLite has no "ADD COLUMN IF NOT EXISTS", so we run the ALTER and swallow
|
|
62
|
+
* only the "duplicate column name" error — which simply means the column is
|
|
63
|
+
* already present (the table was created with it, or a prior run added it).
|
|
64
|
+
* Any other error is re-thrown so genuine schema problems surface loudly.
|
|
65
|
+
* @param {string} table
|
|
66
|
+
* @param {string} column
|
|
67
|
+
* @param {string} typeDecl - e.g. 'TEXT', 'INTEGER DEFAULT 0'
|
|
68
|
+
*/
|
|
69
|
+
_addColumnIfMissing(table, column, typeDecl) {
|
|
70
|
+
try {
|
|
71
|
+
this._db.run(`ALTER TABLE ${table} ADD COLUMN ${column} ${typeDecl}`);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
if (!/duplicate column name/i.test(err && err.message)) {
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
59
79
|
/**
|
|
60
80
|
* Initialize the WASM SQLite database and create tables + indexes.
|
|
61
81
|
*/
|
|
@@ -124,11 +144,23 @@ class VectorHub {
|
|
|
124
144
|
id TEXT PRIMARY KEY,
|
|
125
145
|
request_id TEXT NOT NULL,
|
|
126
146
|
status TEXT NOT NULL,
|
|
147
|
+
did TEXT,
|
|
148
|
+
signed_message TEXT,
|
|
127
149
|
attestation_payload TEXT,
|
|
128
150
|
timestamp TEXT NOT NULL
|
|
129
151
|
)
|
|
130
152
|
`);
|
|
131
153
|
|
|
154
|
+
// UC-22 (audit finding #2): orbital attestations must carry the signer DID
|
|
155
|
+
// and the EXACT canonical message that was signed so verify() can re-check
|
|
156
|
+
// the cryptographic signature instead of trusting status='APPROVED' alone.
|
|
157
|
+
// CREATE TABLE IF NOT EXISTS won't add columns to a database created before
|
|
158
|
+
// this fix, so back-fill them with guarded ALTER TABLE statements. SQLite
|
|
159
|
+
// throws "duplicate column name" when the column already exists — that case
|
|
160
|
+
// is the success path (already migrated), so it is swallowed.
|
|
161
|
+
this._addColumnIfMissing('attestations', 'did', 'TEXT');
|
|
162
|
+
this._addColumnIfMissing('attestations', 'signed_message', 'TEXT');
|
|
163
|
+
|
|
132
164
|
this._db.run(`
|
|
133
165
|
CREATE TABLE IF NOT EXISTS mesh_config (
|
|
134
166
|
key TEXT PRIMARY KEY,
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
|
|
7
7
|
const SEVERITY_ORDER = ['LOW', 'MEDIUM', 'HIGH', 'CRITICAL'];
|
|
8
8
|
|
|
9
|
+
// A severity spread of this many levels (or more) within a single location-group
|
|
10
|
+
// is treated as a contradiction (e.g. CRITICAL=3 vs LOW=0 → gap 3).
|
|
11
|
+
const CONTRADICTION_GAP_THRESHOLD = 2;
|
|
12
|
+
|
|
9
13
|
function synthesizeFindings(reviews) {
|
|
10
14
|
const allFindings = [];
|
|
11
15
|
const modelSpecific = {};
|
|
@@ -18,8 +22,9 @@ function synthesizeFindings(reviews) {
|
|
|
18
22
|
}
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
// Detect consensus
|
|
25
|
+
// Detect consensus and contradictions from the same location-groups.
|
|
22
26
|
const consensus = [];
|
|
27
|
+
const contradictions = [];
|
|
23
28
|
const processed = new Set();
|
|
24
29
|
|
|
25
30
|
for (let i = 0; i < allFindings.length; i++) {
|
|
@@ -31,7 +36,7 @@ function synthesizeFindings(reviews) {
|
|
|
31
36
|
for (let j = i + 1; j < allFindings.length; j++) {
|
|
32
37
|
if (processed.has(j)) continue;
|
|
33
38
|
const f2 = allFindings[j];
|
|
34
|
-
|
|
39
|
+
|
|
35
40
|
if (isSameFinding(f1, f2)) {
|
|
36
41
|
group.push(f2);
|
|
37
42
|
processed.add(j);
|
|
@@ -45,13 +50,12 @@ function synthesizeFindings(reviews) {
|
|
|
45
50
|
severity: getHighestSeverity(group.map(f => f.severity)),
|
|
46
51
|
models: group.map(f => f.model),
|
|
47
52
|
});
|
|
53
|
+
|
|
54
|
+
const contradiction = detectContradiction(f1.location, group);
|
|
55
|
+
if (contradiction) contradictions.push(contradiction);
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
|
|
51
|
-
// Detect contradictions (large severity gap on same finding)
|
|
52
|
-
const contradictions = [];
|
|
53
|
-
// (In a real implementation, we'd more deeply analyze conflicting logic)
|
|
54
|
-
|
|
55
59
|
return {
|
|
56
60
|
consensus,
|
|
57
61
|
model_specific: modelSpecific,
|
|
@@ -92,6 +96,31 @@ function normalizeLocation(loc) {
|
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
|
|
99
|
+
function severityRank(severity) {
|
|
100
|
+
const idx = SEVERITY_ORDER.indexOf(severity);
|
|
101
|
+
return idx < 0 ? 0 : idx;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// A location-group is contradictory when its reviews disagree on severity by
|
|
105
|
+
// CONTRADICTION_GAP_THRESHOLD levels or more (e.g. CRITICAL vs LOW). Reuses the
|
|
106
|
+
// already-computed location-group rather than re-deriving it.
|
|
107
|
+
function detectContradiction(location, group) {
|
|
108
|
+
const ranks = group.map(f => severityRank(f.severity));
|
|
109
|
+
const maxRank = Math.max(...ranks);
|
|
110
|
+
const minRank = Math.min(...ranks);
|
|
111
|
+
|
|
112
|
+
if (maxRank - minRank < CONTRADICTION_GAP_THRESHOLD) return null;
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
location,
|
|
116
|
+
severities: group.map(f => f.severity),
|
|
117
|
+
models: group.map(f => f.model),
|
|
118
|
+
description: `Severity disagreement at ${location}: ` +
|
|
119
|
+
`${SEVERITY_ORDER[minRank]} vs ${SEVERITY_ORDER[maxRank]} ` +
|
|
120
|
+
`(${maxRank - minRank}-level gap across ${group.length} reviews)`,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
95
124
|
function getHighestSeverity(severities) {
|
|
96
125
|
let highest = 0;
|
|
97
126
|
for (const s of severities) {
|
|
@@ -70,15 +70,41 @@ function tagUntrusted(content, meta) {
|
|
|
70
70
|
// character in a regex literal (eslint no-control-regex).
|
|
71
71
|
const NUL = String.fromCharCode(0);
|
|
72
72
|
|
|
73
|
+
// ${IFS} / $IFS are shell-internal field separators that expand to whitespace.
|
|
74
|
+
// Attackers use them to avoid literal spaces between a destructive token and
|
|
75
|
+
// its flags (rm${IFS}-rf${IFS}/). We normalize them back to a space so the
|
|
76
|
+
// existing rm/-rf patterns catch the de-obfuscated form (audit #8).
|
|
77
|
+
const IFS_TOKEN = /\$\{IFS\}|\$IFS/g;
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* De-obfuscates shell metacharacter tricks WITHOUT emulating a real shell.
|
|
81
|
+
* Strips quotes (' ") and backslash escapes, collapses ${IFS}/$IFS to a space,
|
|
82
|
+
* then collapses runs of whitespace. This turns r''m, r"m, r\m and
|
|
83
|
+
* rm${IFS}-rf${IFS}/ back into plain `rm -rf /` so the existing patterns fire.
|
|
84
|
+
* Intentionally conservative: it removes characters rather than interpreting
|
|
85
|
+
* them, which can only make a string MORE likely to match (fail-toward-block).
|
|
86
|
+
*/
|
|
87
|
+
function normalizeShell(input) {
|
|
88
|
+
return input
|
|
89
|
+
.split(NUL).join('') // shells ignore NUL; never let it split a token
|
|
90
|
+
.replace(IFS_TOKEN, ' ') // ${IFS}/$IFS -> space
|
|
91
|
+
.replace(/[\\'"]/g, '') // drop backslash escapes and quote chars
|
|
92
|
+
.replace(/\s+/g, ' '); // collapse whitespace runs
|
|
93
|
+
}
|
|
94
|
+
|
|
73
95
|
/**
|
|
74
96
|
* Detects high-impact / destructive commands via case-insensitive pattern matching.
|
|
75
97
|
* Returns true if the command matches known destructive patterns.
|
|
98
|
+
*
|
|
99
|
+
* The detector errs toward blocking by design: this feeds a PreToolUse gate
|
|
100
|
+
* where approval friction is strictly preferable to a destructive bypass.
|
|
76
101
|
*/
|
|
77
102
|
function isHighImpact(command) {
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
const sanitized = String(command)
|
|
103
|
+
// Normalize first so metacharacter-obfuscated commands (audit #8) are matched
|
|
104
|
+
// by the SAME pattern set as their plain-text equivalents.
|
|
105
|
+
const sanitized = normalizeShell(String(command));
|
|
81
106
|
const patterns = [
|
|
107
|
+
// ── Original allowlist (unchanged) ──────────────────────────────────────
|
|
82
108
|
/rm\s+(-\w*r\w*\s+-\w*f|(-\w*f\w*\s+-\w*r)|-\w*rf|-\w*fr)/i,
|
|
83
109
|
/git\s+push\s+.*--force/i,
|
|
84
110
|
/git\s+push\s+.*-f/i,
|
|
@@ -87,9 +113,75 @@ function isHighImpact(command) {
|
|
|
87
113
|
/delete\s+from/i,
|
|
88
114
|
/truncate\s+table/i,
|
|
89
115
|
/\bmkfs(\.\w+)?\s+\/dev\//i,
|
|
90
|
-
|
|
116
|
+
// #11: any dd write target, not just /dev/ (dd if=... of=important.db).
|
|
117
|
+
// Original /dev/-only check is a subset of this, so it stays covered.
|
|
118
|
+
/\bdd\b.*\bof=/i,
|
|
91
119
|
/\b(curl|wget)\b.*\|\s*(bash|sh|zsh)\b/i,
|
|
92
120
|
/^\s*find\s+.*-delete\b/i,
|
|
121
|
+
|
|
122
|
+
// ── #4 Command/process substitution RCE ─────────────────────────────────
|
|
123
|
+
// `eval` anywhere is high-impact (dynamic code execution).
|
|
124
|
+
/\beval\b/i,
|
|
125
|
+
// Command substitution $(...) or backtick combined with a network fetch.
|
|
126
|
+
/\$\(\s*(curl|wget)\b/i,
|
|
127
|
+
new RegExp(String.fromCharCode(96) + '\\s*(curl|wget)\\b', 'i'),
|
|
128
|
+
// Process substitution feeding an interpreter: bash <(...), sh <(...).
|
|
129
|
+
/\b(bash|sh|zsh)\b.*<\(/i,
|
|
130
|
+
// Curl/wget directly inside a process substitution.
|
|
131
|
+
/<\(\s*(curl|wget)\b/i,
|
|
132
|
+
|
|
133
|
+
// ── #5 Interpreter invocation of a script file ──────────────────────────
|
|
134
|
+
// source <file> and `. <file>` are unambiguous script execution.
|
|
135
|
+
/\bsource\s+\S+/i,
|
|
136
|
+
/(^|[;&|]|\s)\.\s+\S+\.\w+/i,
|
|
137
|
+
// bash/sh/zsh running a .sh script — clearly script execution. Kept broad
|
|
138
|
+
// (any .sh) because shelling out to an arbitrary shell script is itself a
|
|
139
|
+
// strong execution signal; this also covers untrusted paths like
|
|
140
|
+
// `bash /tmp/x.sh`.
|
|
141
|
+
/\b(bash|sh|zsh)\s+\S*\.sh\b/i,
|
|
142
|
+
// node/python/etc. running a script — narrowed (UC-22). Running a project
|
|
143
|
+
// file is THE default safe action in a Node/Python repo, so a blanket
|
|
144
|
+
// `node <file>.js` match is a terrible signal-to-noise ratio and blocked
|
|
145
|
+
// the project's OWN idiom (`node tests/run-all.js`). We now flag ONLY when
|
|
146
|
+
// the script path looks UNTRUSTED — an absolute path (/...), a writable
|
|
147
|
+
// temp dir (/tmp, /var/tmp, /dev/shm), or a home-relative path (~/...) —
|
|
148
|
+
// i.e. the write-then-execute attack chain (drop payload in a writable
|
|
149
|
+
// location, then run it). Project-relative paths (tests/run-all.js,
|
|
150
|
+
// bin/foo.js, scripts/build.py, index.js) are NOT matched. Piped/
|
|
151
|
+
// substituted execution (curl | bash, $(...), <(...), backticks) is
|
|
152
|
+
// already covered by the #4 patterns above.
|
|
153
|
+
/\b(node|python3?|ruby|perl)\s+(~\/|\/)\S*\.(js|mjs|cjs|ts|py|rb|pl)\b/i,
|
|
154
|
+
|
|
155
|
+
// ── #7 Redirect-overwrite of critical files / devices ───────────────────
|
|
156
|
+
// > or >> targeting an absolute sensitive path (/etc/, /dev/, /boot/, /sys/,
|
|
157
|
+
// /proc/, /var/, /usr/, /bin/, /sbin/, /lib/). Project-local redirects
|
|
158
|
+
// (> out.log) are NOT matched.
|
|
159
|
+
/>>?\s*\/(etc|dev|boot|sys|proc|var|usr|bin|sbin|lib|root|lib64)\//i,
|
|
160
|
+
|
|
161
|
+
// ── #8 handled by normalizeShell() above (de-obfuscation), no pattern here.
|
|
162
|
+
|
|
163
|
+
// ── #9 chmod dangerous modes ────────────────────────────────────────────
|
|
164
|
+
// Canonical dangerous octal modes only: 000 (full lockout) and the
|
|
165
|
+
// world-writable 666/777 family. Common safe modes (644/755/600/700/640)
|
|
166
|
+
// and symbolic modes (chmod +x build.sh) are intentionally NOT matched.
|
|
167
|
+
// ANY recursive chmod is also treated as high-impact regardless of mode.
|
|
168
|
+
/\bchmod\b.*\b(000|0?666|0?777)\b/i,
|
|
169
|
+
/\bchmod\s+-\w*[rR]/i,
|
|
170
|
+
|
|
171
|
+
// ── #10 chown recursive ─────────────────────────────────────────────────
|
|
172
|
+
/\bchown\s+-\w*[rR]/i,
|
|
173
|
+
|
|
174
|
+
// ── #12 mv of root / into /dev/null ─────────────────────────────────────
|
|
175
|
+
/\bmv\b.*\/dev\/null\b/i,
|
|
176
|
+
/\bmv\s+(-\w+\s+)?\/\s/i,
|
|
177
|
+
|
|
178
|
+
// ── #13 Process killers ─────────────────────────────────────────────────
|
|
179
|
+
/\bkill\s+-9\s+-1\b/i,
|
|
180
|
+
/\bkillall\b/i,
|
|
181
|
+
/\bpkill\b/i,
|
|
182
|
+
|
|
183
|
+
// ── #14 Power-state commands ────────────────────────────────────────────
|
|
184
|
+
/\b(shutdown|reboot|halt|poweroff)\b/i,
|
|
93
185
|
];
|
|
94
186
|
return patterns.some(pattern => pattern.test(sanitized));
|
|
95
187
|
}
|