monomind 1.11.14 → 1.13.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 +24 -10
- package/.claude/helpers/handlers/session-handler.cjs +11 -4
- package/.claude/helpers/handlers/session-restore-handler.cjs +35 -19
- package/.claude/helpers/handlers/task-handler.cjs +13 -5
- package/.claude/helpers/hook-handler.cjs +40 -0
- package/.claude/helpers/intelligence.cjs +130 -53
- package/.claude/helpers/loop-tracker.cjs +15 -3
- package/.claude/helpers/memory-palace.cjs +461 -0
- package/.claude/helpers/memory.cjs +138 -14
- package/.claude/helpers/metrics-db.mjs +87 -0
- package/.claude/helpers/router.cjs +300 -42
- package/.claude/helpers/session.cjs +89 -30
- package/.claude/helpers/statusline.cjs +148 -4
- package/.claude/helpers/toggle-statusline.cjs +73 -0
- package/.claude/helpers/token-tracker.cjs +934 -0
- package/.claude/helpers/utils/micro-agents.cjs +20 -4
- package/.claude/helpers/utils/monograph.cjs +39 -4
- package/.claude/helpers/utils/telemetry.cjs +3 -3
- 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 +192 -23
- 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 +476 -62
- 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 +348 -17
- 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 +8 -2
- 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
|
@@ -3,6 +3,34 @@
|
|
|
3
3
|
* Provides diff analysis and classification via MCP protocol
|
|
4
4
|
*/
|
|
5
5
|
import { analyzeDiff, assessFileRisk, assessOverallRisk, classifyDiff, suggestReviewers, getGitDiffNumstat, } from '../monovector/diff-classifier.js';
|
|
6
|
+
// ===== Shared validation helpers =====
|
|
7
|
+
const MAX_REF_LEN = 256; // git ref: branch/commit/tag names are bounded
|
|
8
|
+
const MAX_PATH_LEN = 4096; // OS path limit
|
|
9
|
+
const MAX_LIMIT = 100;
|
|
10
|
+
const VALID_FILE_STATUS = new Set(['added', 'modified', 'deleted', 'renamed']);
|
|
11
|
+
// Strip filesystem paths from error messages to avoid leaking internal layout
|
|
12
|
+
function sanitizeError(error) {
|
|
13
|
+
if (error instanceof Error) {
|
|
14
|
+
return error.message
|
|
15
|
+
.replace(/\/[^\s:]+(\/|(?=\s|:|$))/g, '<path>/')
|
|
16
|
+
.substring(0, 500);
|
|
17
|
+
}
|
|
18
|
+
return 'Internal error';
|
|
19
|
+
}
|
|
20
|
+
// Validate a git ref: non-empty string, bounded length, no shell metacharacters.
|
|
21
|
+
// execFileSync already prevents shell injection but we still cap the length and
|
|
22
|
+
// reject control chars / obvious injection patterns so error messages don't echo
|
|
23
|
+
// attacker-supplied content back.
|
|
24
|
+
const REF_SAFE_RE = /^[a-zA-Z0-9_./:@^~\-\.{}\[\]]+$/;
|
|
25
|
+
function validateRef(value) {
|
|
26
|
+
if (typeof value !== 'string' || value.length === 0)
|
|
27
|
+
return 'HEAD';
|
|
28
|
+
if (value.length > MAX_REF_LEN)
|
|
29
|
+
return null;
|
|
30
|
+
if (!REF_SAFE_RE.test(value))
|
|
31
|
+
return null;
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
6
34
|
/**
|
|
7
35
|
* Diff Analysis Tool
|
|
8
36
|
* Analyzes git diffs for change risk assessment and classification
|
|
@@ -38,7 +66,9 @@ export const analyzeDiffTool = {
|
|
|
38
66
|
},
|
|
39
67
|
},
|
|
40
68
|
handler: async (params) => {
|
|
41
|
-
const ref = params.ref
|
|
69
|
+
const ref = validateRef(params.ref);
|
|
70
|
+
if (ref === null)
|
|
71
|
+
return { error: true, message: 'Invalid ref: must be a safe git ref (max 256 chars, alphanumeric/._/:@^~-)' };
|
|
42
72
|
const includeFileRisks = params.includeFileRisks === true;
|
|
43
73
|
const includeReviewers = params.includeReviewers !== false;
|
|
44
74
|
const useMonoVector = params.useMonoVector !== false;
|
|
@@ -67,7 +97,7 @@ export const analyzeDiffTool = {
|
|
|
67
97
|
catch (error) {
|
|
68
98
|
return {
|
|
69
99
|
error: true,
|
|
70
|
-
message:
|
|
100
|
+
message: sanitizeError(error),
|
|
71
101
|
ref,
|
|
72
102
|
};
|
|
73
103
|
}
|
|
@@ -93,7 +123,9 @@ export const diffRiskTool = {
|
|
|
93
123
|
},
|
|
94
124
|
},
|
|
95
125
|
handler: async (params) => {
|
|
96
|
-
const ref = params.ref
|
|
126
|
+
const ref = validateRef(params.ref);
|
|
127
|
+
if (ref === null)
|
|
128
|
+
return { error: true, message: 'Invalid ref: must be a safe git ref (max 256 chars, alphanumeric/._/:@^~-)' };
|
|
97
129
|
try {
|
|
98
130
|
const files = getGitDiffNumstat(ref);
|
|
99
131
|
const fileRisks = files.map(assessFileRisk);
|
|
@@ -107,7 +139,7 @@ export const diffRiskTool = {
|
|
|
107
139
|
catch (error) {
|
|
108
140
|
return {
|
|
109
141
|
error: true,
|
|
110
|
-
message:
|
|
142
|
+
message: sanitizeError(error),
|
|
111
143
|
ref,
|
|
112
144
|
};
|
|
113
145
|
}
|
|
@@ -133,7 +165,9 @@ export const diffClassifyTool = {
|
|
|
133
165
|
},
|
|
134
166
|
},
|
|
135
167
|
handler: async (params) => {
|
|
136
|
-
const ref = params.ref
|
|
168
|
+
const ref = validateRef(params.ref);
|
|
169
|
+
if (ref === null)
|
|
170
|
+
return { error: true, message: 'Invalid ref: must be a safe git ref (max 256 chars, alphanumeric/._/:@^~-)' };
|
|
137
171
|
try {
|
|
138
172
|
const files = getGitDiffNumstat(ref);
|
|
139
173
|
const classification = classifyDiff(files);
|
|
@@ -146,7 +180,7 @@ export const diffClassifyTool = {
|
|
|
146
180
|
catch (error) {
|
|
147
181
|
return {
|
|
148
182
|
error: true,
|
|
149
|
-
message:
|
|
183
|
+
message: sanitizeError(error),
|
|
150
184
|
ref,
|
|
151
185
|
};
|
|
152
186
|
}
|
|
@@ -177,8 +211,13 @@ export const diffReviewersTool = {
|
|
|
177
211
|
},
|
|
178
212
|
},
|
|
179
213
|
handler: async (params) => {
|
|
180
|
-
const ref = params.ref
|
|
181
|
-
|
|
214
|
+
const ref = validateRef(params.ref);
|
|
215
|
+
if (ref === null)
|
|
216
|
+
return { error: true, message: 'Invalid ref: must be a safe git ref (max 256 chars, alphanumeric/._/:@^~-)' };
|
|
217
|
+
const rawLimit = params.limit;
|
|
218
|
+
const limit = (typeof rawLimit === 'number' && Number.isFinite(rawLimit) && rawLimit > 0)
|
|
219
|
+
? Math.min(Math.floor(rawLimit), MAX_LIMIT)
|
|
220
|
+
: 5;
|
|
182
221
|
try {
|
|
183
222
|
const files = getGitDiffNumstat(ref);
|
|
184
223
|
const fileRisks = files.map(assessFileRisk);
|
|
@@ -192,7 +231,7 @@ export const diffReviewersTool = {
|
|
|
192
231
|
catch (error) {
|
|
193
232
|
return {
|
|
194
233
|
error: true,
|
|
195
|
-
message:
|
|
234
|
+
message: sanitizeError(error),
|
|
196
235
|
ref,
|
|
197
236
|
};
|
|
198
237
|
}
|
|
@@ -234,11 +273,33 @@ export const fileRiskTool = {
|
|
|
234
273
|
},
|
|
235
274
|
handler: async (params) => {
|
|
236
275
|
try {
|
|
276
|
+
const rawPath = params.path;
|
|
277
|
+
if (typeof rawPath !== 'string' || rawPath.length === 0) {
|
|
278
|
+
return { error: true, message: 'path is required (non-empty string)' };
|
|
279
|
+
}
|
|
280
|
+
if (rawPath.length > MAX_PATH_LEN) {
|
|
281
|
+
return { error: true, message: `path too long (max ${MAX_PATH_LEN} chars)` };
|
|
282
|
+
}
|
|
283
|
+
// Only use the path for regex matching (assessFileRisk); still reject
|
|
284
|
+
// control characters to prevent log injection.
|
|
285
|
+
if (/[\x00-\x1F]/.test(rawPath)) {
|
|
286
|
+
return { error: true, message: 'path must not contain control characters' };
|
|
287
|
+
}
|
|
288
|
+
const rawStatus = params.status;
|
|
289
|
+
const status = (typeof rawStatus === 'string' && VALID_FILE_STATUS.has(rawStatus))
|
|
290
|
+
? rawStatus
|
|
291
|
+
: 'modified';
|
|
292
|
+
const rawAdd = params.additions;
|
|
293
|
+
const additions = (typeof rawAdd === 'number' && Number.isFinite(rawAdd) && rawAdd >= 0)
|
|
294
|
+
? Math.min(Math.floor(rawAdd), 1_000_000) : 0;
|
|
295
|
+
const rawDel = params.deletions;
|
|
296
|
+
const deletions = (typeof rawDel === 'number' && Number.isFinite(rawDel) && rawDel >= 0)
|
|
297
|
+
? Math.min(Math.floor(rawDel), 1_000_000) : 0;
|
|
237
298
|
const file = {
|
|
238
|
-
path:
|
|
239
|
-
status
|
|
240
|
-
additions
|
|
241
|
-
deletions
|
|
299
|
+
path: rawPath,
|
|
300
|
+
status,
|
|
301
|
+
additions,
|
|
302
|
+
deletions,
|
|
242
303
|
hunks: 1,
|
|
243
304
|
binary: false,
|
|
244
305
|
};
|
|
@@ -253,8 +314,8 @@ export const fileRiskTool = {
|
|
|
253
314
|
catch (error) {
|
|
254
315
|
return {
|
|
255
316
|
error: true,
|
|
256
|
-
message:
|
|
257
|
-
path: params.path,
|
|
317
|
+
message: sanitizeError(error),
|
|
318
|
+
path: typeof params.path === 'string' ? params.path.substring(0, 200) : '',
|
|
258
319
|
};
|
|
259
320
|
}
|
|
260
321
|
},
|
|
@@ -279,7 +340,9 @@ export const diffStatsTool = {
|
|
|
279
340
|
},
|
|
280
341
|
},
|
|
281
342
|
handler: async (params) => {
|
|
282
|
-
const ref = params.ref
|
|
343
|
+
const ref = validateRef(params.ref);
|
|
344
|
+
if (ref === null)
|
|
345
|
+
return { error: true, message: 'Invalid ref: must be a safe git ref (max 256 chars, alphanumeric/._/:@^~-)' };
|
|
283
346
|
try {
|
|
284
347
|
const files = getGitDiffNumstat(ref);
|
|
285
348
|
const totalAdditions = files.reduce((sum, f) => sum + f.additions, 0);
|
|
@@ -307,7 +370,7 @@ export const diffStatsTool = {
|
|
|
307
370
|
catch (error) {
|
|
308
371
|
return {
|
|
309
372
|
error: true,
|
|
310
|
-
message:
|
|
373
|
+
message: sanitizeError(error),
|
|
311
374
|
ref,
|
|
312
375
|
};
|
|
313
376
|
}
|
|
@@ -299,17 +299,36 @@ export const browserTools = [
|
|
|
299
299
|
},
|
|
300
300
|
},
|
|
301
301
|
handler: async (input) => {
|
|
302
|
-
const
|
|
302
|
+
const raw = input;
|
|
303
|
+
let safeSession;
|
|
304
|
+
try {
|
|
305
|
+
safeSession = validateSessionId(raw.session);
|
|
306
|
+
}
|
|
307
|
+
catch (e) {
|
|
308
|
+
return {
|
|
309
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
310
|
+
isError: true,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
303
313
|
const args = ['snapshot'];
|
|
304
|
-
if (interactive)
|
|
314
|
+
if (raw.interactive)
|
|
305
315
|
args.push('-i');
|
|
306
|
-
if (compact)
|
|
316
|
+
if (raw.compact)
|
|
307
317
|
args.push('-c');
|
|
308
|
-
if (depth)
|
|
309
|
-
args.push('-d', String(depth));
|
|
310
|
-
if (selector)
|
|
311
|
-
|
|
312
|
-
|
|
318
|
+
if (raw.depth)
|
|
319
|
+
args.push('-d', String(raw.depth));
|
|
320
|
+
if (raw.selector !== undefined) {
|
|
321
|
+
try {
|
|
322
|
+
args.push('-s', rejectFlagLike(raw.selector, 'selector'));
|
|
323
|
+
}
|
|
324
|
+
catch (e) {
|
|
325
|
+
return {
|
|
326
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
327
|
+
isError: true,
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
return execBrowserCommand(args, safeSession);
|
|
313
332
|
},
|
|
314
333
|
},
|
|
315
334
|
{
|
|
@@ -489,7 +508,15 @@ export const browserTools = [
|
|
|
489
508
|
},
|
|
490
509
|
handler: async (input) => {
|
|
491
510
|
const { target, session } = input;
|
|
492
|
-
|
|
511
|
+
try {
|
|
512
|
+
return execBrowserCommand(['check', rejectFlagLike(target, 'target')], session);
|
|
513
|
+
}
|
|
514
|
+
catch (e) {
|
|
515
|
+
return {
|
|
516
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
517
|
+
isError: true,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
493
520
|
},
|
|
494
521
|
},
|
|
495
522
|
{
|
|
@@ -507,7 +534,15 @@ export const browserTools = [
|
|
|
507
534
|
},
|
|
508
535
|
handler: async (input) => {
|
|
509
536
|
const { target, session } = input;
|
|
510
|
-
|
|
537
|
+
try {
|
|
538
|
+
return execBrowserCommand(['uncheck', rejectFlagLike(target, 'target')], session);
|
|
539
|
+
}
|
|
540
|
+
catch (e) {
|
|
541
|
+
return {
|
|
542
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
543
|
+
isError: true,
|
|
544
|
+
};
|
|
545
|
+
}
|
|
511
546
|
},
|
|
512
547
|
},
|
|
513
548
|
{
|
|
@@ -550,7 +585,15 @@ export const browserTools = [
|
|
|
550
585
|
},
|
|
551
586
|
handler: async (input) => {
|
|
552
587
|
const { target, session } = input;
|
|
553
|
-
|
|
588
|
+
try {
|
|
589
|
+
return execBrowserCommand(['get', 'text', rejectFlagLike(target, 'target')], session);
|
|
590
|
+
}
|
|
591
|
+
catch (e) {
|
|
592
|
+
return {
|
|
593
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
594
|
+
isError: true,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
554
597
|
},
|
|
555
598
|
},
|
|
556
599
|
{
|
|
@@ -568,7 +611,15 @@ export const browserTools = [
|
|
|
568
611
|
},
|
|
569
612
|
handler: async (input) => {
|
|
570
613
|
const { target, session } = input;
|
|
571
|
-
|
|
614
|
+
try {
|
|
615
|
+
return execBrowserCommand(['get', 'value', rejectFlagLike(target, 'target')], session);
|
|
616
|
+
}
|
|
617
|
+
catch (e) {
|
|
618
|
+
return {
|
|
619
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
620
|
+
isError: true,
|
|
621
|
+
};
|
|
622
|
+
}
|
|
572
623
|
},
|
|
573
624
|
},
|
|
574
625
|
{
|
|
@@ -622,17 +673,28 @@ export const browserTools = [
|
|
|
622
673
|
},
|
|
623
674
|
},
|
|
624
675
|
handler: async (input) => {
|
|
625
|
-
const
|
|
676
|
+
const raw = input;
|
|
626
677
|
const args = ['wait'];
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
678
|
+
try {
|
|
679
|
+
if (raw.selector !== undefined)
|
|
680
|
+
args.push(rejectFlagLike(raw.selector, 'selector'));
|
|
681
|
+
if (raw.text !== undefined)
|
|
682
|
+
args.push('--text', rejectFlagLike(raw.text, 'text'));
|
|
683
|
+
if (raw.url !== undefined)
|
|
684
|
+
args.push('--url', validateUrl(raw.url));
|
|
685
|
+
}
|
|
686
|
+
catch (e) {
|
|
687
|
+
return {
|
|
688
|
+
content: [{ type: 'text', text: JSON.stringify({ success: false, error: e.message }) }],
|
|
689
|
+
isError: true,
|
|
690
|
+
};
|
|
691
|
+
}
|
|
692
|
+
// Cap timeout to prevent event-loop blocking via a huge value
|
|
693
|
+
if (raw.timeout !== undefined && typeof raw.timeout === 'number') {
|
|
694
|
+
const clampedTimeout = Math.min(Math.max(Math.floor(raw.timeout), 0), 60_000);
|
|
695
|
+
args.push(String(clampedTimeout));
|
|
696
|
+
}
|
|
697
|
+
return execBrowserCommand(args, raw.session);
|
|
636
698
|
},
|
|
637
699
|
},
|
|
638
700
|
// ==========================================================================
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* @module @monomind/cli/mcp-tools/claims
|
|
8
8
|
*/
|
|
9
9
|
// File-based persistence
|
|
10
|
-
import { existsSync, readFileSync, writeFileSync, renameSync, mkdirSync } from 'fs';
|
|
10
|
+
import { existsSync, readFileSync, statSync, writeFileSync, renameSync, mkdirSync } from 'fs';
|
|
11
11
|
import { join, resolve } from 'path';
|
|
12
12
|
const CLAIMS_DIR = '.monomind/claims';
|
|
13
13
|
const CLAIMS_FILE = 'claims.json';
|
|
@@ -20,10 +20,11 @@ function ensureClaimsDir() {
|
|
|
20
20
|
mkdirSync(dir, { recursive: true });
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
const MAX_CLAIMS_STORE_BYTES = 10 * 1024 * 1024; // 10 MB
|
|
23
24
|
function loadClaims() {
|
|
24
25
|
try {
|
|
25
26
|
const path = getClaimsPath();
|
|
26
|
-
if (existsSync(path)) {
|
|
27
|
+
if (existsSync(path) && statSync(path).size <= MAX_CLAIMS_STORE_BYTES) {
|
|
27
28
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
28
29
|
}
|
|
29
30
|
}
|
|
@@ -80,7 +81,11 @@ export const claimsTools = [
|
|
|
80
81
|
handler: async (input) => {
|
|
81
82
|
const issueId = input.issueId;
|
|
82
83
|
const claimantStr = input.claimant;
|
|
83
|
-
|
|
84
|
+
// Cap context: stored verbatim in the claim JSON record on disk.
|
|
85
|
+
const MAX_CLAIM_CONTEXT_LEN = 4 * 1024;
|
|
86
|
+
const rawContext = input.context;
|
|
87
|
+
const context = typeof rawContext === 'string' && rawContext.length > MAX_CLAIM_CONTEXT_LEN
|
|
88
|
+
? rawContext.slice(0, MAX_CLAIM_CONTEXT_LEN) : rawContext;
|
|
84
89
|
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
85
90
|
if (!issueId || issueId.length > 256 || RESERVED_KEYS.has(issueId)) {
|
|
86
91
|
return { success: false, error: 'Invalid issueId' };
|
|
@@ -211,7 +216,13 @@ export const claimsTools = [
|
|
|
211
216
|
const issueId = input.issueId;
|
|
212
217
|
const fromStr = input.from;
|
|
213
218
|
const toStr = input.to;
|
|
214
|
-
|
|
219
|
+
// Cap handoff reason: stored as claim.handoffReason in the claims JSON store on disk.
|
|
220
|
+
// Without a cap, an arbitrarily long reason inflates every write of the store file.
|
|
221
|
+
const MAX_HANDOFF_REASON_LEN = 1024;
|
|
222
|
+
const rawHandoffReason = input.reason;
|
|
223
|
+
const reason = typeof rawHandoffReason === 'string' && rawHandoffReason.length > MAX_HANDOFF_REASON_LEN
|
|
224
|
+
? rawHandoffReason.slice(0, MAX_HANDOFF_REASON_LEN)
|
|
225
|
+
: rawHandoffReason;
|
|
215
226
|
const progress = input.progress || 0;
|
|
216
227
|
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
217
228
|
if (!issueId || typeof issueId !== 'string' || issueId.length > 256 || RESERVED_KEYS.has(issueId)) {
|
|
@@ -337,7 +348,11 @@ export const claimsTools = [
|
|
|
337
348
|
const issueId = input.issueId;
|
|
338
349
|
const status = input.status;
|
|
339
350
|
const claimantStr = input.claimant;
|
|
340
|
-
|
|
351
|
+
// Cap note: stored as claim.blockReason in the claims JSON store on disk.
|
|
352
|
+
const MAX_CLAIM_NOTE_LEN = 4 * 1024;
|
|
353
|
+
const rawNote = input.note;
|
|
354
|
+
const note = typeof rawNote === 'string' && rawNote.length > MAX_CLAIM_NOTE_LEN
|
|
355
|
+
? rawNote.slice(0, MAX_CLAIM_NOTE_LEN) : rawNote;
|
|
341
356
|
const progress = input.progress;
|
|
342
357
|
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
343
358
|
if (!issueId || typeof issueId !== 'string' || issueId.length > 256 || RESERVED_KEYS.has(issueId)) {
|
|
@@ -450,9 +465,22 @@ export const claimsTools = [
|
|
|
450
465
|
},
|
|
451
466
|
handler: async (input) => {
|
|
452
467
|
const issueId = input.issueId;
|
|
453
|
-
|
|
468
|
+
// Runtime-validate StealReason: JSON schema declares an enum, but callers
|
|
469
|
+
// that bypass schema validation (raw MCP calls) can pass arbitrary strings,
|
|
470
|
+
// which would be persisted verbatim in store.stealable[issueId].reason.
|
|
471
|
+
const VALID_STEAL_REASONS = new Set(['overloaded', 'stale', 'blocked-timeout', 'voluntary']);
|
|
472
|
+
const rawStealReason = input.reason;
|
|
473
|
+
if (!rawStealReason || !VALID_STEAL_REASONS.has(rawStealReason)) {
|
|
474
|
+
return { success: false, error: `Invalid reason "${rawStealReason}". Must be one of: overloaded, stale, blocked-timeout, voluntary` };
|
|
475
|
+
}
|
|
476
|
+
const reason = rawStealReason;
|
|
454
477
|
const preferredTypes = input.preferredTypes;
|
|
455
|
-
|
|
478
|
+
// Cap context: stored verbatim in the stealable record on disk.
|
|
479
|
+
const MAX_STEAL_CONTEXT_LEN = 4 * 1024;
|
|
480
|
+
const rawStealContext = input.context;
|
|
481
|
+
const context = typeof rawStealContext === 'string' && rawStealContext.length > MAX_STEAL_CONTEXT_LEN
|
|
482
|
+
? rawStealContext.slice(0, MAX_STEAL_CONTEXT_LEN)
|
|
483
|
+
: rawStealContext;
|
|
456
484
|
const RESERVED_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
457
485
|
if (!issueId || typeof issueId !== 'string' || issueId.length > 256 || RESERVED_KEYS.has(issueId)) {
|
|
458
486
|
return { success: false, error: 'Invalid issueId' };
|
|
@@ -136,11 +136,27 @@ export const configTools = [
|
|
|
136
136
|
},
|
|
137
137
|
handler: async (input) => {
|
|
138
138
|
const store = loadConfigStore();
|
|
139
|
-
|
|
140
|
-
|
|
139
|
+
// Cap key and scope to prevent DoS via O(n) key.split('.') and to block
|
|
140
|
+
// prototype-pollution when key/scope are used as object property names.
|
|
141
|
+
const MAX_CONFIG_KEY_LEN = 512;
|
|
142
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
143
|
+
const rawKey = input.key;
|
|
144
|
+
const key = typeof rawKey === 'string' && rawKey.length > MAX_CONFIG_KEY_LEN
|
|
145
|
+
? rawKey.slice(0, MAX_CONFIG_KEY_LEN) : rawKey;
|
|
146
|
+
const rawScope = input.scope || 'default';
|
|
147
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
148
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
149
|
+
if (DANGEROUS_KEYS.has(scope)) {
|
|
150
|
+
return { key, value: undefined, scope, exists: false, source: 'none' };
|
|
151
|
+
}
|
|
152
|
+
for (const seg of key.split('.')) {
|
|
153
|
+
if (DANGEROUS_KEYS.has(seg)) {
|
|
154
|
+
return { key, value: undefined, scope, exists: false, source: 'none' };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
141
157
|
let value;
|
|
142
158
|
// Check scope first, then default values
|
|
143
|
-
if (scope !== 'default' && store.scopes
|
|
159
|
+
if (scope !== 'default' && Object.hasOwn(store.scopes, scope)) {
|
|
144
160
|
value = store.scopes[scope][key];
|
|
145
161
|
}
|
|
146
162
|
if (value === undefined) {
|
|
@@ -173,9 +189,18 @@ export const configTools = [
|
|
|
173
189
|
},
|
|
174
190
|
handler: async (input) => {
|
|
175
191
|
const store = loadConfigStore();
|
|
176
|
-
|
|
192
|
+
// Cap key and scope: both become JSON object keys in the on-disk config
|
|
193
|
+
// store (store.values[key] and store.scopes[scope][key]). key is also
|
|
194
|
+
// split on '.' — O(n) — before the dangerous-key check.
|
|
195
|
+
const MAX_CONFIG_KEY_LEN = 512;
|
|
196
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
197
|
+
const rawKey = input.key;
|
|
198
|
+
const key = typeof rawKey === 'string' && rawKey.length > MAX_CONFIG_KEY_LEN
|
|
199
|
+
? rawKey.slice(0, MAX_CONFIG_KEY_LEN) : rawKey;
|
|
177
200
|
const value = input.value;
|
|
178
|
-
const
|
|
201
|
+
const rawScope = input.scope || 'default';
|
|
202
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
203
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
179
204
|
for (const seg of key.split('.')) {
|
|
180
205
|
if (DANGEROUS_KEYS.has(seg)) {
|
|
181
206
|
return { success: false, error: `Forbidden key segment: "${seg}"` };
|
|
@@ -219,9 +244,18 @@ export const configTools = [
|
|
|
219
244
|
},
|
|
220
245
|
handler: async (input) => {
|
|
221
246
|
const store = loadConfigStore();
|
|
222
|
-
const
|
|
223
|
-
const
|
|
247
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
248
|
+
const MAX_PREFIX_LEN = 256;
|
|
249
|
+
const rawScope = input.scope || 'default';
|
|
250
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
251
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
252
|
+
const rawPrefix = input.prefix;
|
|
253
|
+
const prefix = typeof rawPrefix === 'string' && rawPrefix.length > MAX_PREFIX_LEN
|
|
254
|
+
? rawPrefix.slice(0, MAX_PREFIX_LEN) : rawPrefix;
|
|
224
255
|
const includeDefaults = input.includeDefaults !== false;
|
|
256
|
+
if (DANGEROUS_KEYS.has(scope)) {
|
|
257
|
+
return { configs: [], total: 0, scope, updatedAt: store.updatedAt };
|
|
258
|
+
}
|
|
225
259
|
// Merge stored values with defaults
|
|
226
260
|
let configs = {};
|
|
227
261
|
if (includeDefaults) {
|
|
@@ -230,7 +264,7 @@ export const configTools = [
|
|
|
230
264
|
// Add stored values
|
|
231
265
|
Object.assign(configs, store.values);
|
|
232
266
|
// Add scope-specific values
|
|
233
|
-
if (scope !== 'default' && store.scopes
|
|
267
|
+
if (scope !== 'default' && Object.hasOwn(store.scopes, scope)) {
|
|
234
268
|
Object.assign(configs, store.scopes[scope]);
|
|
235
269
|
}
|
|
236
270
|
// Filter by prefix
|
|
@@ -265,18 +299,35 @@ export const configTools = [
|
|
|
265
299
|
},
|
|
266
300
|
handler: async (input) => {
|
|
267
301
|
const store = loadConfigStore();
|
|
268
|
-
|
|
269
|
-
const
|
|
302
|
+
// Cap scope and key to prevent DoS and prototype pollution.
|
|
303
|
+
const MAX_CONFIG_KEY_LEN = 512;
|
|
304
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
305
|
+
const rawScope = input.scope || 'default';
|
|
306
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
307
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
308
|
+
const rawKey = input.key;
|
|
309
|
+
const key = typeof rawKey === 'string' && rawKey.length > MAX_CONFIG_KEY_LEN
|
|
310
|
+
? rawKey.slice(0, MAX_CONFIG_KEY_LEN) : rawKey;
|
|
311
|
+
if (DANGEROUS_KEYS.has(scope)) {
|
|
312
|
+
return { success: false, error: `Forbidden scope: "${scope}"` };
|
|
313
|
+
}
|
|
314
|
+
if (key) {
|
|
315
|
+
for (const seg of key.split('.')) {
|
|
316
|
+
if (DANGEROUS_KEYS.has(seg)) {
|
|
317
|
+
return { success: false, error: `Forbidden key segment: "${seg}"` };
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
270
321
|
let resetKeys = [];
|
|
271
322
|
if (key) {
|
|
272
323
|
// Reset specific key
|
|
273
324
|
if (scope === 'default') {
|
|
274
|
-
if (
|
|
325
|
+
if (Object.hasOwn(store.values, key)) {
|
|
275
326
|
delete store.values[key];
|
|
276
327
|
resetKeys.push(key);
|
|
277
328
|
}
|
|
278
329
|
}
|
|
279
|
-
else if (store.scopes
|
|
330
|
+
else if (Object.hasOwn(store.scopes, scope) && Object.hasOwn(store.scopes[scope], key)) {
|
|
280
331
|
delete store.scopes[scope][key];
|
|
281
332
|
resetKeys.push(key);
|
|
282
333
|
}
|
|
@@ -287,7 +338,7 @@ export const configTools = [
|
|
|
287
338
|
resetKeys = Object.keys(store.values);
|
|
288
339
|
store.values = { ...DEFAULT_CONFIG };
|
|
289
340
|
}
|
|
290
|
-
else if (store.scopes
|
|
341
|
+
else if (Object.hasOwn(store.scopes, scope)) {
|
|
291
342
|
resetKeys = Object.keys(store.scopes[scope]);
|
|
292
343
|
delete store.scopes[scope];
|
|
293
344
|
}
|
|
@@ -315,14 +366,21 @@ export const configTools = [
|
|
|
315
366
|
},
|
|
316
367
|
handler: async (input) => {
|
|
317
368
|
const store = loadConfigStore();
|
|
318
|
-
|
|
369
|
+
// Cap scope to prevent DoS and prototype pollution.
|
|
370
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
371
|
+
const rawScope = input.scope || 'default';
|
|
372
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
373
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
319
374
|
const includeDefaults = input.includeDefaults !== false;
|
|
375
|
+
if (DANGEROUS_KEYS.has(scope)) {
|
|
376
|
+
return { config: {}, scope, version: store.version, exportedAt: new Date().toISOString(), count: 0 };
|
|
377
|
+
}
|
|
320
378
|
let exportData = {};
|
|
321
379
|
if (includeDefaults) {
|
|
322
380
|
exportData = { ...DEFAULT_CONFIG };
|
|
323
381
|
}
|
|
324
382
|
Object.assign(exportData, store.values);
|
|
325
|
-
if (scope !== 'default' && store.scopes
|
|
383
|
+
if (scope !== 'default' && Object.hasOwn(store.scopes, scope)) {
|
|
326
384
|
Object.assign(exportData, store.scopes[scope]);
|
|
327
385
|
}
|
|
328
386
|
return {
|
|
@@ -350,8 +408,15 @@ export const configTools = [
|
|
|
350
408
|
handler: async (input) => {
|
|
351
409
|
const store = loadConfigStore();
|
|
352
410
|
const config = filterDangerousKeys(input.config);
|
|
353
|
-
|
|
411
|
+
// Cap scope to prevent DoS and prototype pollution.
|
|
412
|
+
const MAX_CONFIG_SCOPE_LEN = 128;
|
|
413
|
+
const rawScope = input.scope || 'default';
|
|
414
|
+
const scope = typeof rawScope === 'string' && rawScope.length > MAX_CONFIG_SCOPE_LEN
|
|
415
|
+
? rawScope.slice(0, MAX_CONFIG_SCOPE_LEN) : rawScope;
|
|
354
416
|
const merge = input.merge !== false;
|
|
417
|
+
if (DANGEROUS_KEYS.has(scope)) {
|
|
418
|
+
return { success: false, error: `Forbidden scope: "${scope}"` };
|
|
419
|
+
}
|
|
355
420
|
const importedKeys = Object.keys(config);
|
|
356
421
|
if (scope === 'default') {
|
|
357
422
|
if (merge) {
|
|
@@ -362,7 +427,7 @@ export const configTools = [
|
|
|
362
427
|
}
|
|
363
428
|
}
|
|
364
429
|
else {
|
|
365
|
-
if (!store.scopes
|
|
430
|
+
if (!Object.hasOwn(store.scopes, scope) || !merge) {
|
|
366
431
|
store.scopes[scope] = {};
|
|
367
432
|
}
|
|
368
433
|
Object.assign(store.scopes[scope], config);
|