monomind 1.11.14 → 1.12.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/.claude/agents/generated/channel-intelligence-director.md +87 -0
- package/.claude/agents/generated/chief-growth-officer.md +88 -0
- package/.claude/agents/generated/content-seo-strategist.md +90 -0
- package/.claude/agents/generated/developer-community-strategist.md +91 -0
- package/.claude/agents/generated/outreach-partnership-strategist.md +90 -0
- package/.claude/agents/generated/social-media-strategist.md +91 -0
- package/.claude/agents/generated/video-visual-strategist.md +90 -0
- package/.claude/commands/mastermind/master.md +1 -1
- package/.claude/helpers/auto-memory-hook.mjs +13 -4
- package/.claude/helpers/control-start.cjs +5 -0
- package/.claude/helpers/event-logger.cjs +114 -0
- package/.claude/helpers/handlers/adr-draft-handler.cjs +19 -5
- package/.claude/helpers/handlers/agent-start-handler.cjs +13 -4
- package/.claude/helpers/handlers/compact-handler.cjs +2 -0
- package/.claude/helpers/handlers/edit-handler.cjs +1 -1
- package/.claude/helpers/handlers/gates-handler.cjs +3 -0
- package/.claude/helpers/handlers/graph-status-handler.cjs +14 -8
- package/.claude/helpers/handlers/loops-status-handler.cjs +5 -2
- package/.claude/helpers/handlers/route-handler.cjs +13 -6
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +21 -11
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/intelligence.cjs +7 -2
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory.cjs +6 -1
- package/.claude/helpers/router.cjs +5 -2
- package/.claude/helpers/session.cjs +2 -0
- package/.claude/helpers/statusline.cjs +10 -2
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/scheduled_tasks.lock +1 -1
- package/.claude/settings.json +92 -1
- package/.claude/skills/mastermind/_protocol.md +25 -15
- package/.claude/skills/mastermind/architect.md +3 -3
- package/.claude/skills/mastermind/autodev.md +4 -2
- package/.claude/skills/mastermind/idea.md +10 -0
- package/.claude/skills/mastermind/ops.md +3 -3
- package/.claude/skills/mastermind/runorg.md +153 -86
- package/package.json +20 -3
- package/packages/@monomind/cli/dist/src/agents/registry-builder.js +2 -0
- package/packages/@monomind/cli/dist/src/autopilot-state.js +10 -5
- package/packages/@monomind/cli/dist/src/benchmarks/benchmark-runner.js +13 -0
- package/packages/@monomind/cli/dist/src/benchmarks/metric-evaluators.js +20 -9
- package/packages/@monomind/cli/dist/src/browser/actions.js +10 -3
- package/packages/@monomind/cli/dist/src/browser/browser.js +12 -2
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -3
- package/packages/@monomind/cli/dist/src/browser/har.js +27 -5
- package/packages/@monomind/cli/dist/src/commands/agent.js +11 -8
- package/packages/@monomind/cli/dist/src/commands/analyze.js +36 -21
- package/packages/@monomind/cli/dist/src/commands/autopilot.js +12 -4
- package/packages/@monomind/cli/dist/src/commands/benchmark.js +51 -8
- package/packages/@monomind/cli/dist/src/commands/browse.js +5 -2
- package/packages/@monomind/cli/dist/src/commands/claims.js +29 -11
- package/packages/@monomind/cli/dist/src/commands/cleanup.js +25 -5
- package/packages/@monomind/cli/dist/src/commands/config.js +15 -7
- package/packages/@monomind/cli/dist/src/commands/daemon.js +6 -0
- package/packages/@monomind/cli/dist/src/commands/deployment.js +34 -19
- package/packages/@monomind/cli/dist/src/commands/doctor.js +97 -20
- package/packages/@monomind/cli/dist/src/commands/guidance.js +15 -2
- package/packages/@monomind/cli/dist/src/commands/hive-mind.js +37 -14
- package/packages/@monomind/cli/dist/src/commands/hooks.js +42 -25
- package/packages/@monomind/cli/dist/src/commands/init.js +9 -4
- package/packages/@monomind/cli/dist/src/commands/issues.js +29 -26
- package/packages/@monomind/cli/dist/src/commands/mcp.js +11 -5
- package/packages/@monomind/cli/dist/src/commands/memory.js +10 -0
- package/packages/@monomind/cli/dist/src/commands/migrate.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/monograph.js +18 -5
- package/packages/@monomind/cli/dist/src/commands/monovector/backup.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/monovector/benchmark.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/monovector/import.js +15 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/migrate.js +4 -1
- package/packages/@monomind/cli/dist/src/commands/monovector/optimize.js +11 -0
- package/packages/@monomind/cli/dist/src/commands/monovector/setup.js +11 -1
- package/packages/@monomind/cli/dist/src/commands/neural.js +1 -1
- package/packages/@monomind/cli/dist/src/commands/performance.js +20 -7
- package/packages/@monomind/cli/dist/src/commands/platforms.js +90 -8
- package/packages/@monomind/cli/dist/src/commands/plugins.js +12 -5
- package/packages/@monomind/cli/dist/src/commands/process.js +33 -10
- package/packages/@monomind/cli/dist/src/commands/progress.js +5 -3
- package/packages/@monomind/cli/dist/src/commands/providers.js +5 -5
- package/packages/@monomind/cli/dist/src/commands/replay.js +8 -2
- package/packages/@monomind/cli/dist/src/commands/route.js +27 -7
- package/packages/@monomind/cli/dist/src/commands/security.js +4 -0
- package/packages/@monomind/cli/dist/src/commands/session.js +12 -1
- package/packages/@monomind/cli/dist/src/commands/start.js +11 -4
- package/packages/@monomind/cli/dist/src/commands/status.js +7 -4
- package/packages/@monomind/cli/dist/src/commands/swarm.js +27 -13
- package/packages/@monomind/cli/dist/src/commands/task.js +26 -11
- package/packages/@monomind/cli/dist/src/commands/tokens.js +7 -2
- package/packages/@monomind/cli/dist/src/commands/transfer-store.js +36 -22
- package/packages/@monomind/cli/dist/src/commands/update.js +15 -3
- package/packages/@monomind/cli/dist/src/commands/workflow.js +39 -6
- package/packages/@monomind/cli/dist/src/consensus/audit-writer.js +18 -7
- package/packages/@monomind/cli/dist/src/consensus/vote-signer.js +25 -8
- package/packages/@monomind/cli/dist/src/index.js +7 -3
- package/packages/@monomind/cli/dist/src/init/executor.js +14 -11
- package/packages/@monomind/cli/dist/src/init/shared-instructions-generator.js +20 -4
- package/packages/@monomind/cli/dist/src/init/statusline-generator.js +36 -15
- package/packages/@monomind/cli/dist/src/mcp-tools/a2a-tools.js +98 -13
- package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +16 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/analyze-tools.js +80 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/browser-tools.js +84 -22
- package/packages/@monomind/cli/dist/src/mcp-tools/claims-tools.js +35 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/config-tools.js +82 -17
- package/packages/@monomind/cli/dist/src/mcp-tools/coordination-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +49 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +45 -18
- package/packages/@monomind/cli/dist/src/mcp-tools/github-tools.js +75 -25
- package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +32 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +91 -20
- package/packages/@monomind/cli/dist/src/mcp-tools/hooks-tools.js +188 -29
- package/packages/@monomind/cli/dist/src/mcp-tools/memory-tools.js +25 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-compat.js +11 -2
- package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +148 -26
- package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +44 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/performance-tools.js +45 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/progress-tools.js +7 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/request-tracker.js +15 -1
- package/packages/@monomind/cli/dist/src/mcp-tools/security-tools.js +61 -9
- package/packages/@monomind/cli/dist/src/mcp-tools/session-tools.js +45 -14
- package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +15 -3
- package/packages/@monomind/cli/dist/src/mcp-tools/system-tools.js +14 -7
- package/packages/@monomind/cli/dist/src/mcp-tools/task-tools.js +52 -10
- package/packages/@monomind/cli/dist/src/mcp-tools/terminal-tools.js +40 -6
- package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +37 -4
- package/packages/@monomind/cli/dist/src/mcp-tools/workflow-tools.js +29 -6
- package/packages/@monomind/cli/dist/src/memory/ewc-consolidation.js +26 -10
- package/packages/@monomind/cli/dist/src/memory/intelligence.js +80 -19
- package/packages/@monomind/cli/dist/src/memory/memory-bridge.js +21 -2
- package/packages/@monomind/cli/dist/src/memory/memory-initializer.js +67 -3
- package/packages/@monomind/cli/dist/src/memory/sona-optimizer.js +14 -4
- package/packages/@monomind/cli/dist/src/monovector/command-outcomes.js +43 -7
- package/packages/@monomind/cli/dist/src/monovector/coverage-router.js +8 -4
- package/packages/@monomind/cli/dist/src/monovector/coverage-tools.js +6 -3
- package/packages/@monomind/cli/dist/src/monovector/diff-classifier.js +13 -0
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.d.ts +2 -1
- package/packages/@monomind/cli/dist/src/monovector/route-outcomes.js +46 -4
- package/packages/@monomind/cli/dist/src/plugins/manager.js +8 -3
- package/packages/@monomind/cli/dist/src/plugins/store/discovery.js +46 -2
- package/packages/@monomind/cli/dist/src/plugins/store/search.js +5 -4
- package/packages/@monomind/cli/dist/src/production/circuit-breaker.js +17 -3
- package/packages/@monomind/cli/dist/src/production/error-handler.js +3 -0
- package/packages/@monomind/cli/dist/src/production/monitoring.js +20 -3
- package/packages/@monomind/cli/dist/src/production/rate-limiter.js +13 -4
- package/packages/@monomind/cli/dist/src/production/retry.js +17 -9
- package/packages/@monomind/cli/dist/src/routing/embed-worker.js +6 -2
- package/packages/@monomind/cli/dist/src/routing/embedder.js +0 -0
- package/packages/@monomind/cli/dist/src/routing/llm-caller.js +13 -2
- package/packages/@monomind/cli/dist/src/routing/route-layer-factory.js +18 -3
- package/packages/@monomind/cli/dist/src/services/claim-service.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/services/claim-service.js +8 -0
- package/packages/@monomind/cli/dist/src/services/config-file-manager.js +14 -2
- package/packages/@monomind/cli/dist/src/services/headless-worker-executor.js +18 -2
- package/packages/@monomind/cli/dist/src/services/worker-daemon.js +53 -12
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.d.ts +0 -3
- package/packages/@monomind/cli/dist/src/transfer/anonymization/index.js +16 -1
- package/packages/@monomind/cli/dist/src/transfer/export.js +8 -0
- package/packages/@monomind/cli/dist/src/transfer/ipfs/upload.js +33 -3
- package/packages/@monomind/cli/dist/src/transfer/serialization/cfp.js +9 -3
- package/packages/@monomind/cli/dist/src/transfer/storage/gcs.js +37 -3
- package/packages/@monomind/cli/dist/src/transfer/store/discovery.js +45 -3
- package/packages/@monomind/cli/dist/src/transfer/store/download.js +5 -0
- package/packages/@monomind/cli/dist/src/transfer/store/publish.js +13 -1
- package/packages/@monomind/cli/dist/src/transfer/store/registry.d.ts +8 -0
- package/packages/@monomind/cli/dist/src/transfer/store/registry.js +30 -5
- package/packages/@monomind/cli/dist/src/transfer/store/search.js +20 -5
- package/packages/@monomind/cli/dist/src/update/checker.js +59 -7
- package/packages/@monomind/cli/dist/src/update/executor.js +50 -3
- package/packages/@monomind/cli/dist/src/update/index.js +18 -1
- package/packages/@monomind/cli/dist/src/update/rate-limiter.d.ts +6 -0
- package/packages/@monomind/cli/dist/src/update/rate-limiter.js +79 -7
- package/packages/@monomind/cli/dist/src/update/validator.js +52 -1
- package/packages/@monomind/cli/package.json +2 -3
|
@@ -206,12 +206,26 @@ export function serializeRegistry(registry) {
|
|
|
206
206
|
}
|
|
207
207
|
/**
|
|
208
208
|
* Deserialize registry from JSON
|
|
209
|
+
*
|
|
210
|
+
* Caps the input string length before parsing to prevent OOM on a malicious or
|
|
211
|
+
* oversized registry fetched from IPFS / Pinata. The in-flight body is already
|
|
212
|
+
* capped by readBodyWithLimit (50 MB), but deserializeRegistry is also called
|
|
213
|
+
* with locally-cached data, so we add a 10 MB guard here too.
|
|
214
|
+
*
|
|
215
|
+
* We also reject non-semver and suspiciously long version strings to prevent
|
|
216
|
+
* version fields being used as a side-channel for large-payload injection.
|
|
209
217
|
*/
|
|
210
218
|
export function deserializeRegistry(json) {
|
|
219
|
+
const MAX_JSON_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
220
|
+
if (typeof json !== 'string' || json.length > MAX_JSON_BYTES) {
|
|
221
|
+
throw new Error(`Registry JSON too large: ${typeof json === 'string' ? json.length : typeof json} bytes (max ${MAX_JSON_BYTES})`);
|
|
222
|
+
}
|
|
211
223
|
const registry = JSON.parse(json);
|
|
212
|
-
// Validate version
|
|
213
|
-
if (!registry.version
|
|
214
|
-
|
|
224
|
+
// Validate version — must be a non-empty semver-like string (e.g. "1.0.0").
|
|
225
|
+
if (!registry.version || typeof registry.version !== 'string'
|
|
226
|
+
|| registry.version.length > 32
|
|
227
|
+
|| !/^\d+\.\d+\.\d+/.test(registry.version)) {
|
|
228
|
+
throw new Error('Invalid registry: missing or malformed version');
|
|
215
229
|
}
|
|
216
230
|
return registry;
|
|
217
231
|
}
|
|
@@ -266,9 +280,16 @@ export async function verifyRegistrySignature(registry) {
|
|
|
266
280
|
*/
|
|
267
281
|
export function mergeRegistries(local, remote) {
|
|
268
282
|
const merged = createRegistry(local.ipnsName);
|
|
283
|
+
// Cap combined input sizes to prevent OOM when merging a large remote registry.
|
|
284
|
+
// A remote registry fetched from IPFS could contain hundreds of thousands of
|
|
285
|
+
// entries; without a hard cap, patternMap grows unboundedly in memory.
|
|
286
|
+
const MAX_MERGE_PATTERNS = 10_000;
|
|
287
|
+
const MAX_MERGE_AUTHORS = 1_000;
|
|
269
288
|
// Combine patterns, preferring newer versions
|
|
270
289
|
const patternMap = new Map();
|
|
271
290
|
for (const pattern of [...local.patterns, ...remote.patterns]) {
|
|
291
|
+
if (patternMap.size >= MAX_MERGE_PATTERNS && !patternMap.has(pattern.name))
|
|
292
|
+
continue;
|
|
272
293
|
const existing = patternMap.get(pattern.name);
|
|
273
294
|
if (!existing || new Date(pattern.lastUpdated) > new Date(existing.lastUpdated)) {
|
|
274
295
|
patternMap.set(pattern.name, pattern);
|
|
@@ -278,6 +299,8 @@ export function mergeRegistries(local, remote) {
|
|
|
278
299
|
// Combine authors
|
|
279
300
|
const authorMap = new Map();
|
|
280
301
|
for (const author of [...local.authors, ...remote.authors]) {
|
|
302
|
+
if (authorMap.size >= MAX_MERGE_AUTHORS && !authorMap.has(author.id))
|
|
303
|
+
continue;
|
|
281
304
|
const existing = authorMap.get(author.id);
|
|
282
305
|
if (!existing || author.patterns > existing.patterns) {
|
|
283
306
|
authorMap.set(author.id, author);
|
|
@@ -294,8 +317,10 @@ export function mergeRegistries(local, remote) {
|
|
|
294
317
|
* Generate pattern ID from name
|
|
295
318
|
*/
|
|
296
319
|
export function generatePatternId(name) {
|
|
297
|
-
|
|
298
|
-
const
|
|
320
|
+
// Cap name before normalizing to prevent OOM from an extremely long input string
|
|
321
|
+
const safeName = typeof name === 'string' ? name.slice(0, 128) : '';
|
|
322
|
+
const normalized = safeName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
323
|
+
const hash = crypto.createHash('sha256').update(safeName + Date.now()).digest('hex').slice(0, 8);
|
|
299
324
|
return `${normalized}-${hash}`;
|
|
300
325
|
}
|
|
301
326
|
//# sourceMappingURL=registry.js.map
|
|
@@ -2,6 +2,16 @@
|
|
|
2
2
|
* Pattern Search Service
|
|
3
3
|
* Search and filter patterns from decentralized registry
|
|
4
4
|
*/
|
|
5
|
+
/** Maximum query string length to prevent O(n*m) regex/includes DoS */
|
|
6
|
+
const MAX_QUERY_LEN = 256;
|
|
7
|
+
/** Hard cap on page size to prevent huge slice allocations */
|
|
8
|
+
const MAX_PAGE_LIMIT = 200;
|
|
9
|
+
/** Hard cap on offset to prevent absurdly large slice calculations */
|
|
10
|
+
const MAX_OFFSET = 10_000;
|
|
11
|
+
/** Hard cap on suggestion partial string length */
|
|
12
|
+
const MAX_PARTIAL_LEN = 128;
|
|
13
|
+
/** Hard cap on similar-pattern limit */
|
|
14
|
+
const MAX_SIMILAR_LIMIT = 50;
|
|
5
15
|
/**
|
|
6
16
|
* Search patterns in registry
|
|
7
17
|
*/
|
|
@@ -9,7 +19,7 @@ export function searchPatterns(registry, options = {}) {
|
|
|
9
19
|
let patterns = [...registry.patterns];
|
|
10
20
|
// Text search
|
|
11
21
|
if (options.query) {
|
|
12
|
-
const query = options.query.toLowerCase();
|
|
22
|
+
const query = String(options.query).slice(0, MAX_QUERY_LEN).toLowerCase();
|
|
13
23
|
patterns = patterns.filter(p => p.name.toLowerCase().includes(query) ||
|
|
14
24
|
p.displayName.toLowerCase().includes(query) ||
|
|
15
25
|
p.description.toLowerCase().includes(query) ||
|
|
@@ -77,10 +87,12 @@ export function searchPatterns(registry, options = {}) {
|
|
|
77
87
|
}
|
|
78
88
|
return sortOrder === 'desc' ? -comparison : comparison;
|
|
79
89
|
});
|
|
80
|
-
// Pagination
|
|
90
|
+
// Pagination — cap limit and offset to prevent huge slice allocations
|
|
81
91
|
const total = patterns.length;
|
|
82
|
-
const
|
|
83
|
-
const
|
|
92
|
+
const rawLimit = typeof options.limit === 'number' && Number.isFinite(options.limit) ? options.limit : 20;
|
|
93
|
+
const limit = Math.min(Math.max(1, Math.floor(rawLimit)), MAX_PAGE_LIMIT);
|
|
94
|
+
const rawOffset = typeof options.offset === 'number' && Number.isFinite(options.offset) ? options.offset : 0;
|
|
95
|
+
const offset = Math.min(Math.max(0, Math.floor(rawOffset)), MAX_OFFSET);
|
|
84
96
|
const page = Math.floor(offset / limit) + 1;
|
|
85
97
|
patterns = patterns.slice(offset, offset + limit);
|
|
86
98
|
return {
|
|
@@ -144,6 +156,7 @@ export function getPatternsByCategory(registry, categoryId) {
|
|
|
144
156
|
* Get similar patterns (by tags and category)
|
|
145
157
|
*/
|
|
146
158
|
export function getSimilarPatterns(registry, pattern, limit = 5) {
|
|
159
|
+
limit = Math.min(Math.max(1, Math.floor(limit)), MAX_SIMILAR_LIMIT);
|
|
147
160
|
const scores = new Map();
|
|
148
161
|
for (const p of registry.patterns) {
|
|
149
162
|
if (p.id === pattern.id)
|
|
@@ -203,7 +216,9 @@ export function getTagCloud(registry) {
|
|
|
203
216
|
*/
|
|
204
217
|
export function getSearchSuggestions(registry, partial, limit = 10) {
|
|
205
218
|
const suggestions = new Set();
|
|
206
|
-
|
|
219
|
+
// Cap partial length to prevent O(n*m) DoS via enormous query strings
|
|
220
|
+
const query = String(partial).slice(0, MAX_PARTIAL_LEN).toLowerCase();
|
|
221
|
+
limit = Math.min(Math.max(1, Math.floor(limit)), MAX_SIMILAR_LIMIT);
|
|
207
222
|
// Add matching pattern names
|
|
208
223
|
for (const pattern of registry.patterns) {
|
|
209
224
|
if (pattern.name.toLowerCase().includes(query)) {
|
|
@@ -4,7 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { createRequire } from 'module';
|
|
6
6
|
import { execFileSync } from 'child_process';
|
|
7
|
-
|
|
7
|
+
// Inline semver shim — avoids external dependency
|
|
8
|
+
const semver = {
|
|
9
|
+
valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
|
|
10
|
+
eq: (a, b) => a === b,
|
|
11
|
+
major: (v) => parseInt((v || '0').split('.')[0], 10),
|
|
12
|
+
minor: (v) => parseInt((v || '0').split('.')[1] || '0', 10),
|
|
13
|
+
patch: (v) => parseInt(((v || '0').split('.')[2] || '0').replace(/[^0-9].*/, ''), 10),
|
|
14
|
+
gt: (a, b) => {
|
|
15
|
+
const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
16
|
+
const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
17
|
+
return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
8
20
|
import { reserveCheck, recordCheck, getCachedVersions } from './rate-limiter.js';
|
|
9
21
|
const require = createRequire(import.meta.url);
|
|
10
22
|
const DEFAULT_CONFIG = {
|
|
@@ -45,6 +57,13 @@ const NPM_NAME_RE = /^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/i;
|
|
|
45
57
|
function isValidNpmName(name) {
|
|
46
58
|
return NPM_NAME_RE.test(name) && !name.includes('..') && name.length <= 214;
|
|
47
59
|
}
|
|
60
|
+
// Cap registry response at 5 MB. The full npm registry payload for a package
|
|
61
|
+
// can include the entire `versions` object (dozens of version entries with
|
|
62
|
+
// dist/scripts/dependencies for each). A spoofed or compromised registry
|
|
63
|
+
// endpoint could stream an arbitrarily large body; AbortSignal.timeout(5000)
|
|
64
|
+
// only enforces a wall-clock deadline and does NOT cap bytes. Without this
|
|
65
|
+
// cap, fetchPackageInfo will buffer an unbounded body into memory (OOM).
|
|
66
|
+
const MAX_REGISTRY_RESPONSE_BYTES = 5 * 1024 * 1024; // 5 MB
|
|
48
67
|
async function fetchPackageInfo(packageName) {
|
|
49
68
|
if (!isValidNpmName(packageName))
|
|
50
69
|
return null;
|
|
@@ -56,7 +75,42 @@ async function fetchPackageInfo(packageName) {
|
|
|
56
75
|
if (!response.ok) {
|
|
57
76
|
return null;
|
|
58
77
|
}
|
|
59
|
-
|
|
78
|
+
// Reject immediately if Content-Length header exceeds cap
|
|
79
|
+
const contentLength = response.headers.get('content-length');
|
|
80
|
+
if (contentLength) {
|
|
81
|
+
const declared = parseInt(contentLength, 10);
|
|
82
|
+
if (Number.isFinite(declared) && declared > MAX_REGISTRY_RESPONSE_BYTES) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
// Stream body and enforce byte cap — prevents OOM if the server streams
|
|
87
|
+
// a large body that evades the Content-Length check (missing/wrong header).
|
|
88
|
+
if (!response.body)
|
|
89
|
+
return null;
|
|
90
|
+
const reader = response.body.getReader();
|
|
91
|
+
const chunks = [];
|
|
92
|
+
let totalBytes = 0;
|
|
93
|
+
while (true) {
|
|
94
|
+
const { done, value } = await reader.read();
|
|
95
|
+
if (done)
|
|
96
|
+
break;
|
|
97
|
+
if (value) {
|
|
98
|
+
totalBytes += value.byteLength;
|
|
99
|
+
if (totalBytes > MAX_REGISTRY_RESPONSE_BYTES) {
|
|
100
|
+
await reader.cancel();
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
chunks.push(value);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
const buf = new Uint8Array(totalBytes);
|
|
107
|
+
let offset = 0;
|
|
108
|
+
for (const chunk of chunks) {
|
|
109
|
+
buf.set(chunk, offset);
|
|
110
|
+
offset += chunk.byteLength;
|
|
111
|
+
}
|
|
112
|
+
const text = new TextDecoder('utf-8').decode(buf);
|
|
113
|
+
return JSON.parse(text);
|
|
60
114
|
}
|
|
61
115
|
catch {
|
|
62
116
|
return null;
|
|
@@ -66,7 +120,8 @@ function getUpdateType(current, latest) {
|
|
|
66
120
|
if (!semver.valid(current) || !semver.valid(latest)) {
|
|
67
121
|
return 'none';
|
|
68
122
|
}
|
|
69
|
-
|
|
123
|
+
// Not an upgrade (equal or downgrade)
|
|
124
|
+
if (!semver.gt(latest, current)) {
|
|
70
125
|
return 'none';
|
|
71
126
|
}
|
|
72
127
|
if (semver.major(latest) > semver.major(current)) {
|
|
@@ -75,10 +130,7 @@ function getUpdateType(current, latest) {
|
|
|
75
130
|
if (semver.minor(latest) > semver.minor(current)) {
|
|
76
131
|
return 'minor';
|
|
77
132
|
}
|
|
78
|
-
|
|
79
|
-
return 'patch';
|
|
80
|
-
}
|
|
81
|
-
return 'none';
|
|
133
|
+
return 'patch';
|
|
82
134
|
}
|
|
83
135
|
function shouldAutoUpdate(updateType, priority, config) {
|
|
84
136
|
if (updateType === 'none')
|
|
@@ -6,11 +6,32 @@ import { execFile } from 'child_process';
|
|
|
6
6
|
import * as fs from 'fs';
|
|
7
7
|
import * as path from 'path';
|
|
8
8
|
import * as os from 'os';
|
|
9
|
-
import
|
|
9
|
+
import { validateUpdate } from './validator.js';
|
|
10
|
+
// Inline semver shim — avoids external dependency (semver is not in package.json)
|
|
11
|
+
const semver = {
|
|
12
|
+
valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Validate a npm package name.
|
|
16
|
+
* Allows scoped (@scope/name) and unscoped names; rejects path-traversal,
|
|
17
|
+
* shell metacharacters, and names that are too long to be legitimate.
|
|
18
|
+
* See https://docs.npmjs.com/cli/v9/configuring-npm/package-json#name
|
|
19
|
+
*/
|
|
20
|
+
function isValidPackageName(name) {
|
|
21
|
+
if (typeof name !== 'string' || name.length === 0 || name.length > 214)
|
|
22
|
+
return false;
|
|
23
|
+
// Scoped: @scope/name — both parts: lowercase alnum + hyphens + underscores + dots
|
|
24
|
+
if (name.startsWith('@')) {
|
|
25
|
+
return /^@[a-z0-9][a-z0-9_.-]*\/[a-z0-9][a-z0-9_.-]*$/.test(name);
|
|
26
|
+
}
|
|
27
|
+
// Unscoped: must not start with . or _ (legacy rule)
|
|
28
|
+
return /^[a-z0-9][a-z0-9_.-]*$/.test(name);
|
|
29
|
+
}
|
|
30
|
+
/** Max bytes we will read from the on-disk update history file. */
|
|
31
|
+
const MAX_HISTORY_FILE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
10
32
|
function execFileAsync(cmd, args) {
|
|
11
33
|
return new Promise((resolve, reject) => execFile(cmd, args, (err) => (err ? reject(err) : resolve())));
|
|
12
34
|
}
|
|
13
|
-
import { validateUpdate } from './validator.js';
|
|
14
35
|
const HISTORY_FILE = path.join(os.homedir(), '.monomind', 'update-history.json');
|
|
15
36
|
const MAX_HISTORY_ENTRIES = 100;
|
|
16
37
|
function ensureDir() {
|
|
@@ -22,8 +43,29 @@ function ensureDir() {
|
|
|
22
43
|
export function loadHistory() {
|
|
23
44
|
try {
|
|
24
45
|
if (fs.existsSync(HISTORY_FILE)) {
|
|
46
|
+
// Guard against a bloated or attacker-crafted history file causing OOM.
|
|
47
|
+
const stat = fs.statSync(HISTORY_FILE);
|
|
48
|
+
if (stat.size > MAX_HISTORY_FILE_BYTES) {
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
25
51
|
const content = fs.readFileSync(HISTORY_FILE, 'utf-8');
|
|
26
|
-
|
|
52
|
+
const raw = JSON.parse(content);
|
|
53
|
+
if (!Array.isArray(raw))
|
|
54
|
+
return [];
|
|
55
|
+
// Sanitize each entry: reject any entry whose package name or version
|
|
56
|
+
// fails validation so that a tampered history file cannot inject
|
|
57
|
+
// arbitrary arguments into a subsequent npm install via rollbackUpdate().
|
|
58
|
+
return raw.filter((e) => {
|
|
59
|
+
if (typeof e !== 'object' || e === null)
|
|
60
|
+
return false;
|
|
61
|
+
if (typeof e.package !== 'string' || !isValidPackageName(e.package))
|
|
62
|
+
return false;
|
|
63
|
+
if (typeof e.fromVersion !== 'string' || !semver.valid(e.fromVersion))
|
|
64
|
+
return false;
|
|
65
|
+
if (typeof e.toVersion !== 'string' || !semver.valid(e.toVersion))
|
|
66
|
+
return false;
|
|
67
|
+
return true;
|
|
68
|
+
});
|
|
27
69
|
}
|
|
28
70
|
}
|
|
29
71
|
catch {
|
|
@@ -68,6 +110,11 @@ export async function executeUpdate(update, installedPackages, dryRun = false) {
|
|
|
68
110
|
// Execute npm install — use execFile to avoid shell injection
|
|
69
111
|
const pkg = update.package;
|
|
70
112
|
const version = update.latestVersion;
|
|
113
|
+
// Validate both package name and version before constructing the npm arg
|
|
114
|
+
// to prevent argument injection via a crafted UpdateCheckResult.
|
|
115
|
+
if (!isValidPackageName(pkg)) {
|
|
116
|
+
throw new Error(`Invalid package name: ${pkg}`);
|
|
117
|
+
}
|
|
71
118
|
if (!semver.valid(version)) {
|
|
72
119
|
throw new Error(`Invalid version: ${version}`);
|
|
73
120
|
}
|
|
@@ -16,7 +16,24 @@ export { executeUpdate, executeMultipleUpdates, rollbackUpdate, getUpdateHistory
|
|
|
16
16
|
import { checkForUpdates, DEFAULT_CONFIG, getInstalledVersion } from './checker.js';
|
|
17
17
|
import { executeMultipleUpdates } from './executor.js';
|
|
18
18
|
import { getCachedVersions } from './rate-limiter.js';
|
|
19
|
-
|
|
19
|
+
// Inline semver shim — avoids external dependency (semver is not listed in package.json)
|
|
20
|
+
const semver = {
|
|
21
|
+
valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
|
|
22
|
+
gt: (a, b) => {
|
|
23
|
+
const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
24
|
+
const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
25
|
+
return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
|
|
26
|
+
},
|
|
27
|
+
lte: (a, b) => {
|
|
28
|
+
const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
29
|
+
const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
30
|
+
if (aMaj !== bMaj)
|
|
31
|
+
return aMaj < bMaj;
|
|
32
|
+
if (aMin !== bMin)
|
|
33
|
+
return aMin < bMin;
|
|
34
|
+
return aPat <= bPat;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
20
37
|
/**
|
|
21
38
|
* Synchronous — reads cached state from last check.
|
|
22
39
|
* Returns a short inline string for the CLI version tagline, e.g.
|
|
@@ -20,6 +20,12 @@ export declare function shouldCheckForUpdates(intervalHours?: number): {
|
|
|
20
20
|
* only after a successful reserveCheck, so that limit enforcement and
|
|
21
21
|
* increment happen in the same synchronous turn (no await gap between
|
|
22
22
|
* them), preventing two concurrent callers both seeing "allowed".
|
|
23
|
+
*
|
|
24
|
+
* IMPORTANT: performs a single loadState() → check → increment → saveState()
|
|
25
|
+
* cycle to eliminate the TOCTOU window that existed when this function
|
|
26
|
+
* delegated to shouldCheckForUpdates() (which called loadState() itself)
|
|
27
|
+
* and then called loadState() a second time to increment. Two callers
|
|
28
|
+
* sharing that gap could both see allowed=true and both increment.
|
|
23
29
|
*/
|
|
24
30
|
export declare function reserveCheck(intervalHours?: number): {
|
|
25
31
|
allowed: boolean;
|
|
@@ -8,6 +8,12 @@ import * as os from 'os';
|
|
|
8
8
|
const STATE_FILE = path.join(os.homedir(), '.monomind', 'update-state.json');
|
|
9
9
|
const DEFAULT_INTERVAL_HOURS = 24;
|
|
10
10
|
const MAX_CHECKS_PER_DAY = 10;
|
|
11
|
+
// Hard cap on how many package version entries we persist. Prevents an
|
|
12
|
+
// attacker who can write to the state file from inflating it unboundedly,
|
|
13
|
+
// and protects recordCheck() from DoS via a huge incoming packageVersions map.
|
|
14
|
+
const MAX_PACKAGE_VERSIONS = 100;
|
|
15
|
+
// Hard cap on the state file size we are willing to read into memory.
|
|
16
|
+
const MAX_STATE_FILE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
11
17
|
function ensureDir() {
|
|
12
18
|
const dir = path.dirname(STATE_FILE);
|
|
13
19
|
if (!fs.existsSync(dir)) {
|
|
@@ -25,8 +31,36 @@ function getDefaultState() {
|
|
|
25
31
|
export function loadState() {
|
|
26
32
|
try {
|
|
27
33
|
if (fs.existsSync(STATE_FILE)) {
|
|
34
|
+
// Guard against oversized state files (DoS / OOM) before reading
|
|
35
|
+
const stat = fs.statSync(STATE_FILE);
|
|
36
|
+
if (stat.size > MAX_STATE_FILE_BYTES) {
|
|
37
|
+
// State file is unreasonably large — discard and start fresh
|
|
38
|
+
try {
|
|
39
|
+
fs.unlinkSync(STATE_FILE);
|
|
40
|
+
}
|
|
41
|
+
catch { /* ignore */ }
|
|
42
|
+
return getDefaultState();
|
|
43
|
+
}
|
|
28
44
|
const content = fs.readFileSync(STATE_FILE, 'utf-8');
|
|
29
|
-
|
|
45
|
+
// Block prototype pollution via JSON.parse reviver
|
|
46
|
+
const state = JSON.parse(content, (key, value) => {
|
|
47
|
+
if (key === '__proto__' || key === 'constructor' || key === 'prototype')
|
|
48
|
+
return undefined;
|
|
49
|
+
return value;
|
|
50
|
+
});
|
|
51
|
+
// Validate that packageVersions is a plain object (not an array/primitive)
|
|
52
|
+
if (!state.packageVersions || typeof state.packageVersions !== 'object' || Array.isArray(state.packageVersions)) {
|
|
53
|
+
state.packageVersions = {};
|
|
54
|
+
}
|
|
55
|
+
// Cap the number of package version entries to prevent bloat
|
|
56
|
+
const versionKeys = Object.keys(state.packageVersions);
|
|
57
|
+
if (versionKeys.length > MAX_PACKAGE_VERSIONS) {
|
|
58
|
+
const capped = {};
|
|
59
|
+
for (const k of versionKeys.slice(0, MAX_PACKAGE_VERSIONS)) {
|
|
60
|
+
capped[k] = state.packageVersions[k];
|
|
61
|
+
}
|
|
62
|
+
state.packageVersions = capped;
|
|
63
|
+
}
|
|
30
64
|
// Reset counter if new day
|
|
31
65
|
const today = new Date().toISOString().split('T')[0];
|
|
32
66
|
if (state.date !== today) {
|
|
@@ -86,14 +120,41 @@ export function shouldCheckForUpdates(intervalHours = DEFAULT_INTERVAL_HOURS) {
|
|
|
86
120
|
* only after a successful reserveCheck, so that limit enforcement and
|
|
87
121
|
* increment happen in the same synchronous turn (no await gap between
|
|
88
122
|
* them), preventing two concurrent callers both seeing "allowed".
|
|
123
|
+
*
|
|
124
|
+
* IMPORTANT: performs a single loadState() → check → increment → saveState()
|
|
125
|
+
* cycle to eliminate the TOCTOU window that existed when this function
|
|
126
|
+
* delegated to shouldCheckForUpdates() (which called loadState() itself)
|
|
127
|
+
* and then called loadState() a second time to increment. Two callers
|
|
128
|
+
* sharing that gap could both see allowed=true and both increment.
|
|
89
129
|
*/
|
|
90
130
|
export function reserveCheck(intervalHours = DEFAULT_INTERVAL_HOURS) {
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
return
|
|
94
|
-
|
|
95
|
-
|
|
131
|
+
// Fast-path: environment gates that don't need file I/O
|
|
132
|
+
if (process.env.CI === 'true' || process.env.CONTINUOUS_INTEGRATION === 'true') {
|
|
133
|
+
return { allowed: false, reason: 'CI environment detected' };
|
|
134
|
+
}
|
|
135
|
+
if (process.env.MONOMIND_AUTO_UPDATE === 'false') {
|
|
136
|
+
return { allowed: false, reason: 'Auto-update disabled via environment' };
|
|
137
|
+
}
|
|
138
|
+
// Single load — check and increment in one synchronous cycle
|
|
96
139
|
const state = loadState();
|
|
140
|
+
if (process.env.MONOMIND_FORCE_UPDATE !== 'true') {
|
|
141
|
+
// Daily limit
|
|
142
|
+
if (state.checksToday >= MAX_CHECKS_PER_DAY) {
|
|
143
|
+
return { allowed: false, reason: `Daily check limit (${MAX_CHECKS_PER_DAY}) reached` };
|
|
144
|
+
}
|
|
145
|
+
// Time interval
|
|
146
|
+
if (state.lastCheck) {
|
|
147
|
+
const hoursSinceLastCheck = (Date.now() - new Date(state.lastCheck).getTime()) / (1000 * 60 * 60);
|
|
148
|
+
if (hoursSinceLastCheck < intervalHours) {
|
|
149
|
+
const nextCheck = Math.ceil(intervalHours - hoursSinceLastCheck);
|
|
150
|
+
return {
|
|
151
|
+
allowed: false,
|
|
152
|
+
reason: `Last check was ${Math.floor(hoursSinceLastCheck)}h ago (next check in ~${nextCheck}h)`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Reserve the slot: increment and persist before any async work begins
|
|
97
158
|
state.checksToday += 1;
|
|
98
159
|
state.lastCheck = new Date().toISOString();
|
|
99
160
|
saveState(state);
|
|
@@ -102,7 +163,18 @@ export function reserveCheck(intervalHours = DEFAULT_INTERVAL_HOURS) {
|
|
|
102
163
|
export function recordCheck(packageVersions) {
|
|
103
164
|
// Update only package versions; count/timestamp already incremented by reserveCheck
|
|
104
165
|
const state = loadState();
|
|
105
|
-
|
|
166
|
+
// Merge only string-valued keys to block prototype pollution and type confusion.
|
|
167
|
+
// Also enforce the total cap so a large incoming map cannot bloat the state file.
|
|
168
|
+
const FORBIDDEN = new Set(['__proto__', 'constructor', 'prototype']);
|
|
169
|
+
for (const [k, v] of Object.entries(packageVersions)) {
|
|
170
|
+
if (FORBIDDEN.has(k))
|
|
171
|
+
continue;
|
|
172
|
+
if (typeof k !== 'string' || typeof v !== 'string')
|
|
173
|
+
continue;
|
|
174
|
+
if (Object.keys(state.packageVersions).length >= MAX_PACKAGE_VERSIONS)
|
|
175
|
+
break;
|
|
176
|
+
state.packageVersions[k] = v;
|
|
177
|
+
}
|
|
106
178
|
saveState(state);
|
|
107
179
|
}
|
|
108
180
|
export function getCachedVersions() {
|
|
@@ -2,9 +2,42 @@
|
|
|
2
2
|
* Package validator for update compatibility
|
|
3
3
|
* Ensures updates don't break the ecosystem
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
// Inline semver shim — avoids external dependency (semver is not listed in package.json)
|
|
6
|
+
const semver = {
|
|
7
|
+
valid: (v) => /^\d+\.\d+\.\d+/.test(v || '') ? v : null,
|
|
8
|
+
major: (v) => parseInt((v || '0').split('.')[0], 10),
|
|
9
|
+
gt: (a, b) => {
|
|
10
|
+
const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
11
|
+
const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
12
|
+
return aMaj !== bMaj ? aMaj > bMaj : aMin !== bMin ? aMin > bMin : aPat > bPat;
|
|
13
|
+
},
|
|
14
|
+
lt: (a, b) => {
|
|
15
|
+
const [aMaj, aMin, aPat] = (a || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
16
|
+
const [bMaj, bMin, bPat] = (b || '0').split('.').map(n => parseInt(n, 10) || 0);
|
|
17
|
+
return aMaj !== bMaj ? aMaj < bMaj : aMin !== bMin ? aMin < bMin : aPat < bPat;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
// Maximum number of updates accepted in a single validateBulkUpdate call.
|
|
21
|
+
// Without this cap a caller can DoS the validator by passing thousands of
|
|
22
|
+
// update entries — each entry triggers validateUpdate which iterates over
|
|
23
|
+
// COMPATIBILITY_MATRIX and BREAKING_CHANGES.
|
|
24
|
+
const MAX_BULK_UPDATES = 50;
|
|
25
|
+
// Version strings must look like semver (major.minor.patch with optional pre-release)
|
|
26
|
+
// before we use them in string interpolation or comparisons.
|
|
27
|
+
const SEMVER_RE = /^\d+\.\d+\.\d+(-[\w.]+)?(\+[\w.]+)?$/;
|
|
28
|
+
// Package names: scoped (@scope/name) or plain, no shell-special chars.
|
|
29
|
+
const PKG_NAME_RE = /^(@[a-zA-Z0-9][a-zA-Z0-9_.-]*\/)?[a-zA-Z0-9][a-zA-Z0-9_.-]*$/;
|
|
30
|
+
function isSafeVersion(v) {
|
|
31
|
+
return typeof v === 'string' && v.length <= 64 && SEMVER_RE.test(v);
|
|
32
|
+
}
|
|
33
|
+
function isSafePackageName(p) {
|
|
34
|
+
return typeof p === 'string' && p.length <= 200 && PKG_NAME_RE.test(p);
|
|
35
|
+
}
|
|
6
36
|
// Known compatibility matrix between monomind packages
|
|
7
37
|
const COMPATIBILITY_MATRIX = {
|
|
38
|
+
'@monomind/cli': {
|
|
39
|
+
'@monomind/security': { minVersion: '3.0.0-alpha.1' },
|
|
40
|
+
},
|
|
8
41
|
'@monoes/monomindcli': {
|
|
9
42
|
'monofence-ai': { minVersion: '1.0.0' },
|
|
10
43
|
},
|
|
@@ -35,6 +68,18 @@ export function validateUpdate(packageName, fromVersion, toVersion, installedPac
|
|
|
35
68
|
warnings: [],
|
|
36
69
|
requiredPeerUpdates: [],
|
|
37
70
|
};
|
|
71
|
+
// Guard inputs: reject untrusted or malformed strings before they flow into
|
|
72
|
+
// error messages or semver comparisons (which assume well-formed input).
|
|
73
|
+
if (!isSafePackageName(packageName)) {
|
|
74
|
+
result.valid = false;
|
|
75
|
+
result.incompatibilities.push('Invalid package name');
|
|
76
|
+
return result;
|
|
77
|
+
}
|
|
78
|
+
if (!isSafeVersion(fromVersion) || !isSafeVersion(toVersion)) {
|
|
79
|
+
result.valid = false;
|
|
80
|
+
result.incompatibilities.push('Invalid version string(s)');
|
|
81
|
+
return result;
|
|
82
|
+
}
|
|
38
83
|
// Check if this is a major version bump
|
|
39
84
|
if (semver.valid(fromVersion) && semver.valid(toVersion)) {
|
|
40
85
|
const fromMajor = semver.major(fromVersion);
|
|
@@ -93,6 +138,12 @@ export function validateBulkUpdate(updates, currentPackages) {
|
|
|
93
138
|
warnings: [],
|
|
94
139
|
requiredPeerUpdates: [],
|
|
95
140
|
};
|
|
141
|
+
// Cap the number of updates to prevent DoS via large arrays
|
|
142
|
+
if (!Array.isArray(updates) || updates.length > MAX_BULK_UPDATES) {
|
|
143
|
+
combinedResult.valid = false;
|
|
144
|
+
combinedResult.incompatibilities.push(`Too many updates: max ${MAX_BULK_UPDATES} allowed per call`);
|
|
145
|
+
return combinedResult;
|
|
146
|
+
}
|
|
96
147
|
// Create a simulated state after all updates
|
|
97
148
|
const simulatedPackages = { ...currentPackages };
|
|
98
149
|
for (const update of updates) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@monoes/monomindcli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Monomind CLI - Enterprise AI agent orchestration with 60+ specialized agents, swarm coordination, MCP server, self-learning hooks, and vector memory for Claude Code",
|
|
6
6
|
"main": "dist/src/index.js",
|
|
@@ -87,8 +87,7 @@
|
|
|
87
87
|
"dependencies": {
|
|
88
88
|
"ws": "^8.18.0",
|
|
89
89
|
"@noble/ed25519": "^2.1.0",
|
|
90
|
-
"@monoes/monograph": "^1.2.0"
|
|
91
|
-
"semver": "^7.6.0"
|
|
90
|
+
"@monoes/monograph": "^1.2.0"
|
|
92
91
|
},
|
|
93
92
|
"optionalDependencies": {
|
|
94
93
|
"sql.js": "^1.14.1",
|