micro-models-agent 0.52.0 → 0.52.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +358 -358
- package/dist/main.js +244 -122
- package/dist/modules/browser/bridge-server.mjs +202 -202
- package/dist/skills/builtin/git.md +36 -36
- package/dist/skills/builtin/typescript.md +35 -35
- package/package.json +1 -1
- package/dist/cli/commands.js +0 -447
- package/dist/cli/completer.js +0 -167
- package/dist/cli/index.js +0 -2
- package/dist/cli/main.js +0 -153
- package/dist/cli/plugin-commands.js +0 -36
- package/dist/cli/repl-commands.js +0 -761
- package/dist/cli/repl.js +0 -702
- package/dist/cli/run-result.js +0 -33
- package/dist/cli/security-commands.js +0 -164
- package/dist/cli/setup.js +0 -237
- package/dist/config/config.js +0 -276
- package/dist/config/defaults.js +0 -141
- package/dist/config/domains.js +0 -179
- package/dist/config/experts.js +0 -15
- package/dist/config/index.js +0 -4
- package/dist/config/security.js +0 -213
- package/dist/config/types.js +0 -1
- package/dist/core/agent-moe.js +0 -102
- package/dist/core/agent.js +0 -1018
- package/dist/core/bootstrap.js +0 -481
- package/dist/core/crash-handler.js +0 -51
- package/dist/core/environment.js +0 -199
- package/dist/core/index.js +0 -2
- package/dist/core/prompt-builder.js +0 -76
- package/dist/core/session-logger.js +0 -251
- package/dist/core/types.js +0 -1
- package/dist/core/version.js +0 -26
- package/dist/core/workspace.js +0 -76
- package/dist/i18n/en.json +0 -679
- package/dist/i18n/index.js +0 -46
- package/dist/i18n/ru.json +0 -679
- package/dist/index.js +0 -22
- package/dist/llm/image-utils.js +0 -143
- package/dist/llm/index.js +0 -4
- package/dist/llm/model-loader.js +0 -78
- package/dist/llm/openai-compat.js +0 -497
- package/dist/llm/orchestrator.js +0 -200
- package/dist/llm/provider.js +0 -10
- package/dist/llm/response.js +0 -39
- package/dist/llm/token-counter.js +0 -39
- package/dist/llm/types.js +0 -1
- package/dist/logger/app-logger.js +0 -189
- package/dist/logger/file-log.js +0 -151
- package/dist/logger/index.js +0 -1
- package/dist/migration/backup.js +0 -45
- package/dist/migration/detect.js +0 -50
- package/dist/migration/index.js +0 -2
- package/dist/modules/artifacts/store.js +0 -61
- package/dist/modules/browser/actions.js +0 -76
- package/dist/modules/browser/bridge-client.js +0 -199
- package/dist/modules/browser/bridge-path.js +0 -10
- package/dist/modules/browser/cookie-store.js +0 -24
- package/dist/modules/browser/driver.js +0 -136
- package/dist/modules/browser/index.js +0 -7
- package/dist/modules/browser/module.js +0 -29
- package/dist/modules/browser/session.js +0 -342
- package/dist/modules/browser/snapshot.js +0 -148
- package/dist/modules/browser/types.js +0 -12
- package/dist/modules/certification/cli.js +0 -213
- package/dist/modules/certification/fact-checker.js +0 -82
- package/dist/modules/certification/loader.js +0 -106
- package/dist/modules/certification/manifest.js +0 -58
- package/dist/modules/certification/runner.js +0 -245
- package/dist/modules/certification/scenarios.js +0 -407
- package/dist/modules/certification/types.js +0 -1
- package/dist/modules/context/chunk-query.js +0 -100
- package/dist/modules/context/fact-extractor.js +0 -168
- package/dist/modules/context/history.js +0 -15
- package/dist/modules/context/index.js +0 -1
- package/dist/modules/context/manager.js +0 -440
- package/dist/modules/execution/audit-runners.js +0 -206
- package/dist/modules/execution/auditor.js +0 -218
- package/dist/modules/execution/execution-plugin.js +0 -431
- package/dist/modules/execution/index.js +0 -8
- package/dist/modules/execution/module.js +0 -625
- package/dist/modules/execution/moe-executor.js +0 -304
- package/dist/modules/execution/plan-coverage.js +0 -68
- package/dist/modules/execution/plan-persister.js +0 -46
- package/dist/modules/execution/plan-store.js +0 -196
- package/dist/modules/execution/plan-tool.js +0 -677
- package/dist/modules/execution/plan-validator.js +0 -153
- package/dist/modules/execution/planner.js +0 -94
- package/dist/modules/execution/stuck-detector.js +0 -746
- package/dist/modules/execution/tracker.js +0 -69
- package/dist/modules/execution/types.js +0 -1
- package/dist/modules/execution/verifier.js +0 -235
- package/dist/modules/execution/windows-commands.js +0 -41
- package/dist/modules/hallucination/confidence.js +0 -66
- package/dist/modules/hallucination/consistency.js +0 -26
- package/dist/modules/hallucination/detector.js +0 -47
- package/dist/modules/hallucination/factual.js +0 -169
- package/dist/modules/hallucination/index.js +0 -5
- package/dist/modules/hallucination/js-identifiers.js +0 -262
- package/dist/modules/hallucination/llm-judge.js +0 -101
- package/dist/modules/index.js +0 -5
- package/dist/modules/indexer/cache.js +0 -40
- package/dist/modules/indexer/index.js +0 -3
- package/dist/modules/indexer/module.js +0 -246
- package/dist/modules/indexer/project-profile.js +0 -183
- package/dist/modules/indexer/walker.js +0 -101
- package/dist/modules/lsp/check-tool.js +0 -58
- package/dist/modules/lsp/client.js +0 -389
- package/dist/modules/lsp/command.js +0 -60
- package/dist/modules/lsp/config.js +0 -135
- package/dist/modules/lsp/index.js +0 -3
- package/dist/modules/lsp/module.js +0 -260
- package/dist/modules/lsp/probe.js +0 -86
- package/dist/modules/lsp/project-root.js +0 -32
- package/dist/modules/lsp/startup-check.js +0 -144
- package/dist/modules/lsp/types.js +0 -1
- package/dist/modules/mcp/client.js +0 -399
- package/dist/modules/mcp/index.js +0 -3
- package/dist/modules/mcp/module.js +0 -142
- package/dist/modules/mcp/registry.js +0 -15
- package/dist/modules/memory/index.js +0 -1
- package/dist/modules/memory/module.js +0 -96
- package/dist/modules/memory/search.js +0 -42
- package/dist/modules/memory/store.js +0 -69
- package/dist/modules/pipelines/engine.js +0 -60
- package/dist/modules/pipelines/index.js +0 -3
- package/dist/modules/pipelines/parser.js +0 -56
- package/dist/modules/pipelines/template.js +0 -14
- package/dist/modules/plugins/builtin/lint-on-write.js +0 -334
- package/dist/modules/plugins/builtin/notify.js +0 -9
- package/dist/modules/plugins/index.js +0 -1
- package/dist/modules/plugins/loader.js +0 -70
- package/dist/modules/plugins/manager.js +0 -261
- package/dist/modules/plugins/types.js +0 -1
- package/dist/modules/pricing/index.js +0 -61
- package/dist/modules/pricing/prices.js +0 -129
- package/dist/modules/processes/detect.js +0 -34
- package/dist/modules/processes/index.js +0 -2
- package/dist/modules/processes/registry.js +0 -327
- package/dist/modules/processes/runner.js +0 -23
- package/dist/modules/providers/create.js +0 -22
- package/dist/modules/providers/fallback.js +0 -79
- package/dist/modules/providers/health.js +0 -46
- package/dist/modules/providers/index.js +0 -5
- package/dist/modules/providers/manager.js +0 -161
- package/dist/modules/providers/presets.js +0 -128
- package/dist/modules/providers/registry.js +0 -22
- package/dist/modules/providers/types.js +0 -1
- package/dist/modules/registry.js +0 -48
- package/dist/modules/security/audit-log.js +0 -136
- package/dist/modules/security/audit-notifier.js +0 -292
- package/dist/modules/security/command-validator.js +0 -219
- package/dist/modules/security/content-scanner.js +0 -53
- package/dist/modules/security/data-sanitizer.js +0 -89
- package/dist/modules/security/encryption.js +0 -242
- package/dist/modules/security/index.js +0 -14
- package/dist/modules/security/network-validator.js +0 -88
- package/dist/modules/security/path-validator.js +0 -203
- package/dist/modules/security/rate-limiter.js +0 -119
- package/dist/modules/security/security-policies.js +0 -531
- package/dist/modules/security/session-encryption.js +0 -210
- package/dist/modules/security/session-isolation.js +0 -95
- package/dist/modules/session/index.js +0 -3
- package/dist/modules/session/manager.js +0 -172
- package/dist/modules/session/module.js +0 -24
- package/dist/modules/session/store.js +0 -222
- package/dist/modules/session/types.js +0 -1
- package/dist/modules/skills/index.js +0 -2
- package/dist/modules/skills/loader.js +0 -72
- package/dist/modules/skills/matcher.js +0 -27
- package/dist/modules/skills/module.js +0 -129
- package/dist/modules/types.js +0 -1
- package/dist/modules/updater/checker.js +0 -96
- package/dist/modules/updater/index.js +0 -2
- package/dist/modules/updater/module.js +0 -116
- package/dist/modules/user-profile/compressor.js +0 -16
- package/dist/modules/user-profile/index.js +0 -1
- package/dist/modules/user-profile/profile.js +0 -68
- package/dist/tools/approve.js +0 -33
- package/dist/tools/attach-image.js +0 -101
- package/dist/tools/bash.js +0 -519
- package/dist/tools/browser.js +0 -115
- package/dist/tools/chunk-query.js +0 -100
- package/dist/tools/create-dir.js +0 -56
- package/dist/tools/delete-file.js +0 -63
- package/dist/tools/download-file.js +0 -117
- package/dist/tools/edit-file.js +0 -80
- package/dist/tools/enable-tools.js +0 -59
- package/dist/tools/executor.js +0 -154
- package/dist/tools/file-info.js +0 -47
- package/dist/tools/filter-tools.js +0 -17
- package/dist/tools/glob-tool.js +0 -27
- package/dist/tools/grep-tool.js +0 -125
- package/dist/tools/hidden-tools-block.js +0 -37
- package/dist/tools/index.js +0 -78
- package/dist/tools/list-dir.js +0 -49
- package/dist/tools/load-skill.js +0 -43
- package/dist/tools/mcp-call.js +0 -69
- package/dist/tools/move-file.js +0 -86
- package/dist/tools/path-utils.js +0 -101
- package/dist/tools/pipeline-run.js +0 -145
- package/dist/tools/preview.js +0 -2
- package/dist/tools/process-kill.js +0 -40
- package/dist/tools/process-list.js +0 -37
- package/dist/tools/process-log.js +0 -54
- package/dist/tools/question.js +0 -141
- package/dist/tools/read-file.js +0 -179
- package/dist/tools/recall.js +0 -118
- package/dist/tools/registry.js +0 -47
- package/dist/tools/remember.js +0 -68
- package/dist/tools/scope-check.js +0 -32
- package/dist/tools/search-history.js +0 -85
- package/dist/tools/subagent.js +0 -196
- package/dist/tools/types.js +0 -1
- package/dist/tools/user-input.js +0 -123
- package/dist/tools/web-browse.js +0 -87
- package/dist/tools/web-fetch.js +0 -119
- package/dist/tools/web-search.js +0 -105
- package/dist/tools/write-file.js +0 -82
- package/dist/ui/box.js +0 -77
- package/dist/ui/colors.js +0 -4
- package/dist/ui/diff.js +0 -178
- package/dist/ui/index.js +0 -6
- package/dist/ui/line-editor.js +0 -822
- package/dist/ui/line-math.js +0 -73
- package/dist/ui/md-formatter.js +0 -212
- package/dist/ui/output.js +0 -13
- package/dist/ui/plan-view.js +0 -103
- package/dist/ui/renderer.js +0 -259
- package/dist/ui/spinner.js +0 -70
- package/dist/ui/table.js +0 -144
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
import { DEFAULT_SECURITY_CONFIG } from "../../config/security";
|
|
2
|
-
// Fallback in case DEFAULT_SECURITY_CONFIG is not available
|
|
3
|
-
const FALLBACK_BASH_CONFIG = {
|
|
4
|
-
enabled: false,
|
|
5
|
-
blacklist: [
|
|
6
|
-
"rm",
|
|
7
|
-
"dd",
|
|
8
|
-
"chmod",
|
|
9
|
-
"wget",
|
|
10
|
-
"curl",
|
|
11
|
-
"scp",
|
|
12
|
-
"ssh",
|
|
13
|
-
"nc",
|
|
14
|
-
"netcat",
|
|
15
|
-
"powershell",
|
|
16
|
-
"pwsh",
|
|
17
|
-
"cmd",
|
|
18
|
-
],
|
|
19
|
-
whitelist: [],
|
|
20
|
-
blockDangerousFlags: false,
|
|
21
|
-
dangerousFlags: ["--force", "-rf", "--no-preserve-root"],
|
|
22
|
-
dangerousOperators: [">", ">>", "2>", "2>>", "|", "&&", "||", ";", "`"],
|
|
23
|
-
logCommands: true,
|
|
24
|
-
};
|
|
25
|
-
export const DEFAULT_BASH_CONFIG = DEFAULT_SECURITY_CONFIG?.bash || FALLBACK_BASH_CONFIG;
|
|
26
|
-
/**
|
|
27
|
-
* Extract the actual base command from a shell command string.
|
|
28
|
-
* Handles path prefixes, env assignments, and sudo.
|
|
29
|
-
*
|
|
30
|
-
* Examples:
|
|
31
|
-
* "/usr/bin/rm -rf /" → "rm"
|
|
32
|
-
* "sudo /usr/bin/rm file" → "rm"
|
|
33
|
-
* "NODE_ENV=prod node app.js" → "node"
|
|
34
|
-
* "env PATH=/x rm -rf /" → "rm"
|
|
35
|
-
* "cmd=rm; $cmd -rf /" → "cmd" (variable indirection — not expanded)
|
|
36
|
-
* "powershell Remove-Item x" → "Remove-Item"
|
|
37
|
-
* "cmd /c del /s /q" → "del"
|
|
38
|
-
*/
|
|
39
|
-
function extractBaseCommand(trimmed) {
|
|
40
|
-
const tokens = trimmed.split(/\s+/);
|
|
41
|
-
let i = 0;
|
|
42
|
-
// Skip env assignments (KEY=val, KEY="val", etc.) and prefixed commands
|
|
43
|
-
while (i < tokens.length) {
|
|
44
|
-
const tok = tokens[i];
|
|
45
|
-
if (/^[a-zA-Z_][a-zA-Z0-9_]*=/.test(tok)) {
|
|
46
|
-
i++;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
if (tok === "sudo" || tok === "env" || tok === "command" || tok === "exec" || tok === "nohup") {
|
|
50
|
-
i++;
|
|
51
|
-
continue;
|
|
52
|
-
}
|
|
53
|
-
break;
|
|
54
|
-
}
|
|
55
|
-
if (i >= tokens.length)
|
|
56
|
-
return tokens[0] ?? "";
|
|
57
|
-
// Extract basename from path (e.g. /usr/bin/rm → rm)
|
|
58
|
-
const raw = tokens[i];
|
|
59
|
-
const parts = raw.split(/[\\/]/);
|
|
60
|
-
const basename = parts[parts.length - 1] || raw;
|
|
61
|
-
// Strip .exe/.cmd/.bat extensions for blacklist matching (e.g. powershell.exe → powershell)
|
|
62
|
-
return basename.replace(/\.(exe|cmd|bat)$/i, "") || basename;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Check if a shell operator appears as a standalone token in the command.
|
|
66
|
-
* Uses regex with lookbehind/lookahead so that:
|
|
67
|
-
* "&" does NOT match "&&" (AND operator)
|
|
68
|
-
* "|" does NOT match "||" (OR operator)
|
|
69
|
-
* ">" does NOT match ">>" (append)
|
|
70
|
-
* "2>" does NOT match "2>>" (append stderr)
|
|
71
|
-
*/
|
|
72
|
-
function containsOperator(command, op) {
|
|
73
|
-
const escaped = op.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
74
|
-
// For single-char operators that are prefixes of multi-char ones,
|
|
75
|
-
// ensure they don't match when part of a longer sequence.
|
|
76
|
-
// Note: "&" is no longer in dangerousOperators (&& is legitimate),
|
|
77
|
-
// but keep the special case for defense-in-depth if manually added.
|
|
78
|
-
if (op === "&")
|
|
79
|
-
return /(?<!&)&(?!&)/.test(command);
|
|
80
|
-
if (op === "|")
|
|
81
|
-
return /(?<!\|)\|(?!\|)/.test(command);
|
|
82
|
-
if (op === ">")
|
|
83
|
-
return /(?<!>)>(?!>)/.test(command);
|
|
84
|
-
// For everything else (including multi-char like ">>", "2>", "2>>", ";", "`"),
|
|
85
|
-
// match literally.
|
|
86
|
-
return new RegExp(escaped).test(command);
|
|
87
|
-
}
|
|
88
|
-
/**
|
|
89
|
-
* Check if a command is allowed based on security configuration
|
|
90
|
-
*/
|
|
91
|
-
export function isCommandAllowed(command, securityConfig) {
|
|
92
|
-
// If security is disabled, allow everything
|
|
93
|
-
if (!securityConfig?.enabled) {
|
|
94
|
-
return { allowed: true, sanitizedCommand: command };
|
|
95
|
-
}
|
|
96
|
-
// Ensure config has all required fields
|
|
97
|
-
let config = securityConfig;
|
|
98
|
-
if (!config.blacklist || !Array.isArray(config.blacklist)) {
|
|
99
|
-
config = {
|
|
100
|
-
...FALLBACK_BASH_CONFIG,
|
|
101
|
-
enabled: config.enabled ?? FALLBACK_BASH_CONFIG.enabled,
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
const trimmedCommand = command.trim();
|
|
105
|
-
if (!trimmedCommand) {
|
|
106
|
-
return { allowed: false, reason: "Empty command" };
|
|
107
|
-
}
|
|
108
|
-
// Shell wrappers are always blocked — they bypass all command validation.
|
|
109
|
-
// The agent should use the bash tool directly, not shell interpreters.
|
|
110
|
-
const ALWAYS_BLOCKED = ["powershell", "pwsh", "cmd"];
|
|
111
|
-
const baseForShellCheck = extractBaseCommand(trimmedCommand);
|
|
112
|
-
if (ALWAYS_BLOCKED.includes(baseForShellCheck)) {
|
|
113
|
-
return {
|
|
114
|
-
allowed: false,
|
|
115
|
-
reason: `Shell wrapper "${baseForShellCheck}" is blocked — use the bash tool directly`,
|
|
116
|
-
};
|
|
117
|
-
}
|
|
118
|
-
// Interpreters with -c/-e execute arbitrary code and bypass the blacklist.
|
|
119
|
-
const INTERPRETER_FLAGS = /(?:^|\s)(?:bash|sh|zsh|dash|python|python3|node|bun|perl|ruby)\s+(?:-c|-e|--command|--eval)\b/;
|
|
120
|
-
if (INTERPRETER_FLAGS.test(trimmedCommand)) {
|
|
121
|
-
return { allowed: false, reason: "Interpreter -c/-e invocation is blocked" };
|
|
122
|
-
}
|
|
123
|
-
// Multi-line commands can hide blacklisted commands after a newline.
|
|
124
|
-
if (/\r?\n/.test(trimmedCommand)) {
|
|
125
|
-
return { allowed: false, reason: "Multi-line commands are blocked" };
|
|
126
|
-
}
|
|
127
|
-
// Chained commands bypass the base-command check (e.g. "echo hi; rm -rf /").
|
|
128
|
-
if (/[;&|]/.test(trimmedCommand) &&
|
|
129
|
-
/(^|[;&|\s])(rm|del|rd|dd|chmod|chown|wget|curl|nc|netcat|shutdown|reboot|mkfs|fdisk)\b/.test(trimmedCommand)) {
|
|
130
|
-
return { allowed: false, reason: "Command chaining to a dangerous command is blocked" };
|
|
131
|
-
}
|
|
132
|
-
// Check dangerous operators FIRST — before parsing the base command.
|
|
133
|
-
// e.g. "curl http://x | bash" must be caught even though curl isn't blacklisted.
|
|
134
|
-
if (config.blockDangerousFlags) {
|
|
135
|
-
for (const op of config.dangerousOperators || []) {
|
|
136
|
-
if (containsOperator(trimmedCommand, op)) {
|
|
137
|
-
return {
|
|
138
|
-
allowed: false,
|
|
139
|
-
reason: `Operator "${op}" is not allowed`,
|
|
140
|
-
};
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
// Extract actual base command (handles paths, env vars, sudo)
|
|
145
|
-
const baseCommand = extractBaseCommand(trimmedCommand);
|
|
146
|
-
// Check whitelist first (if non-empty, only whitelisted commands are allowed)
|
|
147
|
-
if (config.whitelist.length > 0) {
|
|
148
|
-
if (!config.whitelist.includes(baseCommand)) {
|
|
149
|
-
return {
|
|
150
|
-
allowed: false,
|
|
151
|
-
reason: `Command "${baseCommand}" is not in the whitelist`,
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
// Check blacklist (against the extracted basename, not the raw token)
|
|
156
|
-
if (config.blacklist.includes(baseCommand)) {
|
|
157
|
-
return {
|
|
158
|
-
allowed: false,
|
|
159
|
-
reason: `Command "${baseCommand}" is blacklisted`,
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
// Also check the raw first token in case it's a simple name
|
|
163
|
-
const rawFirst = trimmedCommand.split(/\s+/)[0].replace(/\.(exe|cmd|bat)$/i, "");
|
|
164
|
-
if (rawFirst !== baseCommand && config.blacklist.includes(rawFirst)) {
|
|
165
|
-
return {
|
|
166
|
-
allowed: false,
|
|
167
|
-
reason: `Command "${rawFirst}" is blacklisted`,
|
|
168
|
-
};
|
|
169
|
-
}
|
|
170
|
-
// Check for dangerous flags
|
|
171
|
-
if (config.blockDangerousFlags) {
|
|
172
|
-
for (const flag of config.dangerousFlags || []) {
|
|
173
|
-
// Check for flag as whole word or with equals
|
|
174
|
-
const flagPattern = new RegExp(`(?:^|\\s)${flag}(?:\\s|$|=)`);
|
|
175
|
-
if (flagPattern.test(trimmedCommand)) {
|
|
176
|
-
return {
|
|
177
|
-
allowed: false,
|
|
178
|
-
reason: `Dangerous flag "${flag}" is not allowed`,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
}
|
|
183
|
-
// Command is allowed
|
|
184
|
-
return { allowed: true, sanitizedCommand: command };
|
|
185
|
-
}
|
|
186
|
-
/**
|
|
187
|
-
* Sanitize command for logging (remove sensitive information)
|
|
188
|
-
* Uses the same patterns as sanitizeLogMessage for consistency
|
|
189
|
-
*/
|
|
190
|
-
export function sanitizeCommandForLog(command) {
|
|
191
|
-
// Import patterns from data-sanitizer to maintain consistency
|
|
192
|
-
const patterns = [
|
|
193
|
-
/(api[_-]?key|apikey|api_key)\s*[=:\s]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi,
|
|
194
|
-
/(token|access[_-]?token|auth[_-]?token)\s*[=:\s]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi,
|
|
195
|
-
/(password|passwd|pwd|pass)\s*[=:\s]\s*["']?([^\s"'&]+)["']?/gi,
|
|
196
|
-
/(secret|private[_-]?key|secret[_-]?key)\s*[=:\s]\s*["']?([^\s"'&]+)["']?/gi,
|
|
197
|
-
/\bsk-[a-zA-Z0-9_-]{20,}\b/gi,
|
|
198
|
-
/\bctx7sk-[a-zA-Z0-9_-]{10,}\b/gi,
|
|
199
|
-
/\bghp_[a-zA-Z0-9]{30,}\b/gi,
|
|
200
|
-
/\bgithub_pat_[a-zA-Z0-9]{20,}\b/gi,
|
|
201
|
-
/\bAKIA[0-9A-Z]{15,}\b/gi,
|
|
202
|
-
/Bearer\s+[a-zA-Z0-9_-]{20,}/gi,
|
|
203
|
-
/\b([a-zA-Z0-9_-]{30,})\b/g,
|
|
204
|
-
];
|
|
205
|
-
let sanitized = command;
|
|
206
|
-
for (const pattern of patterns) {
|
|
207
|
-
if (pattern.toString().includes("($1")) {
|
|
208
|
-
// For patterns with capture groups, use the group in replacement
|
|
209
|
-
sanitized = sanitized.replace(pattern, (match, p1) => {
|
|
210
|
-
return `${p1}=[REDACTED]`;
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
else {
|
|
214
|
-
// For simple patterns, just replace with [REDACTED]
|
|
215
|
-
sanitized = sanitized.replace(pattern, "[REDACTED]");
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
return sanitized;
|
|
219
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Default dangerous patterns to scan for in file content.
|
|
3
|
-
* NOTE: only genuinely dangerous constructs are listed. Legitimate runtime
|
|
4
|
-
* APIs (process.exit, setTimeout/setInterval, template literals `${}`,
|
|
5
|
-
* backtick strings) are NOT scanned — they are valid JS/TS and blocking
|
|
6
|
-
* them breaks ordinary CLI code (false positives).
|
|
7
|
-
*/
|
|
8
|
-
const DEFAULT_DANGEROUS_PATTERNS = [
|
|
9
|
-
/eval\(/gi,
|
|
10
|
-
/new Function\(/gi,
|
|
11
|
-
/child_process\.exec\(/gi,
|
|
12
|
-
/child_process\.spawn\(/gi,
|
|
13
|
-
/require\('child_process'\)/gi,
|
|
14
|
-
/require\('http'\).createServer/gi,
|
|
15
|
-
/require\('net'\).createServer/gi,
|
|
16
|
-
/require\('fs'\).writeFileSync/gi,
|
|
17
|
-
/require\('fs'\).unlinkSync/gi,
|
|
18
|
-
/while\(true\)/gi,
|
|
19
|
-
/for\(;;\)/gi,
|
|
20
|
-
/rm -rf/gi,
|
|
21
|
-
/dd if=/gi,
|
|
22
|
-
/chmod 777/gi,
|
|
23
|
-
/wget.*\|.*sh/gi,
|
|
24
|
-
/curl.*\|.*sh/gi,
|
|
25
|
-
];
|
|
26
|
-
/**
|
|
27
|
-
* Scan file content for dangerous patterns
|
|
28
|
-
*/
|
|
29
|
-
export function scanContent(content, filePath, config) {
|
|
30
|
-
// If scanning is disabled, allow everything
|
|
31
|
-
if (!config?.enabled) {
|
|
32
|
-
return { allowed: true };
|
|
33
|
-
}
|
|
34
|
-
// Use default patterns if none provided
|
|
35
|
-
const patterns = config.dangerousPatterns?.length
|
|
36
|
-
? config.dangerousPatterns
|
|
37
|
-
: DEFAULT_DANGEROUS_PATTERNS;
|
|
38
|
-
const matches = [];
|
|
39
|
-
for (const pattern of patterns) {
|
|
40
|
-
const match = content.match(pattern);
|
|
41
|
-
if (match) {
|
|
42
|
-
matches.push(...match);
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
if (matches.length > 0) {
|
|
46
|
-
return {
|
|
47
|
-
allowed: false,
|
|
48
|
-
reason: `Content contains dangerous patterns in ${filePath}: ${matches.slice(0, 3).join(", ")}`,
|
|
49
|
-
matches,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return { allowed: true };
|
|
53
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Patterns for sensitive data that should be redacted from logs
|
|
3
|
-
*/
|
|
4
|
-
const SENSITIVE_PATTERNS = [
|
|
5
|
-
// API keys and tokens in key=value format
|
|
6
|
-
{
|
|
7
|
-
pattern: /(api[_-]?key|apikey|api_key)\s*[=:\s]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi,
|
|
8
|
-
replacement: "$1=[REDACTED]",
|
|
9
|
-
},
|
|
10
|
-
{
|
|
11
|
-
pattern: /(token|access[_-]?token|auth[_-]?token)\s*[=:\s]\s*["']?([a-zA-Z0-9_-]{10,})["']?/gi,
|
|
12
|
-
replacement: "$1=[REDACTED]",
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
pattern: /(password|passwd|pwd|pass)\s*[=:\s]\s*["']?([^\s"'&]+)["']?/gi,
|
|
16
|
-
replacement: "$1=[REDACTED]",
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
pattern: /(secret|private[_-]?key|secret[_-]?key)\s*[=:\s]\s*["']?([^\s"'&]+)["']?/gi,
|
|
20
|
-
replacement: "$1=[REDACTED]",
|
|
21
|
-
},
|
|
22
|
-
// Standalone API keys (not in key=value format)
|
|
23
|
-
{
|
|
24
|
-
pattern: /\bsk-[a-zA-Z0-9_-]{20,}\b/gi,
|
|
25
|
-
replacement: "sk-[REDACTED]",
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
pattern: /\bctx7sk-[a-zA-Z0-9_-]{10,}\b/gi,
|
|
29
|
-
replacement: "ctx7sk-[REDACTED]",
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
pattern: /\bghp_[a-zA-Z0-9]{30,}\b/gi,
|
|
33
|
-
replacement: "ghp_[REDACTED]",
|
|
34
|
-
},
|
|
35
|
-
{
|
|
36
|
-
pattern: /\bgithub_pat_[a-zA-Z0-9]{20,}\b/gi,
|
|
37
|
-
replacement: "github_pat_[REDACTED]",
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
pattern: /\bAKIA[0-9A-Z]{15,}\b/gi,
|
|
41
|
-
replacement: "AKIA[REDACTED]",
|
|
42
|
-
},
|
|
43
|
-
// Bearer tokens in headers
|
|
44
|
-
{
|
|
45
|
-
pattern: /Bearer\s+[a-zA-Z0-9_-]{20,}/gi,
|
|
46
|
-
replacement: "Bearer [REDACTED]",
|
|
47
|
-
},
|
|
48
|
-
// Generic high-entropy strings that look like secrets (30+ chars, mixed case and numbers)
|
|
49
|
-
{
|
|
50
|
-
pattern: /\b([a-zA-Z0-9_-]{30,})\b/g,
|
|
51
|
-
replacement: "[REDACTED]",
|
|
52
|
-
},
|
|
53
|
-
];
|
|
54
|
-
/**
|
|
55
|
-
* Sanitize a log message by removing sensitive information
|
|
56
|
-
*/
|
|
57
|
-
export function sanitizeLogMessage(message) {
|
|
58
|
-
let sanitized = message;
|
|
59
|
-
for (const { pattern, replacement } of SENSITIVE_PATTERNS) {
|
|
60
|
-
sanitized = sanitized.replace(pattern, replacement);
|
|
61
|
-
}
|
|
62
|
-
return sanitized;
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Sanitize a command string by removing sensitive information
|
|
66
|
-
*/
|
|
67
|
-
export function sanitizeCommand(command) {
|
|
68
|
-
return sanitizeLogMessage(command);
|
|
69
|
-
}
|
|
70
|
-
/**
|
|
71
|
-
* Sanitize a URL by removing sensitive query parameters
|
|
72
|
-
*/
|
|
73
|
-
export function sanitizeUrl(url) {
|
|
74
|
-
try {
|
|
75
|
-
const urlObj = new URL(url);
|
|
76
|
-
const sensitiveKeys = ["api_key", "token", "password", "secret", "access_token", "auth", "key"];
|
|
77
|
-
const sanitizedParams = new URLSearchParams(urlObj.search);
|
|
78
|
-
for (const key of sensitiveKeys) {
|
|
79
|
-
if (sanitizedParams.has(key)) {
|
|
80
|
-
sanitizedParams.set(key, "[REDACTED]");
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
urlObj.search = sanitizedParams.toString();
|
|
84
|
-
return urlObj.toString();
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
return sanitizeLogMessage(url);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
@@ -1,242 +0,0 @@
|
|
|
1
|
-
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, timingSafeEqual } from "node:crypto";
|
|
2
|
-
import { homedir } from "os";
|
|
3
|
-
import { join } from "path";
|
|
4
|
-
import { readFileSync, writeFileSync, existsSync, copyFileSync } from "fs";
|
|
5
|
-
const DEFAULT_CONFIG = {
|
|
6
|
-
keyPath: join(homedir(), ".mma", ".encryption-key"),
|
|
7
|
-
algorithm: "aes-256-gcm",
|
|
8
|
-
};
|
|
9
|
-
const IV_LENGTH = 16;
|
|
10
|
-
const SALT_LENGTH = 16;
|
|
11
|
-
const KEY_LENGTH = 32;
|
|
12
|
-
const AUTH_TAG_LENGTH = 16;
|
|
13
|
-
/**
|
|
14
|
-
* Generate a new encryption key
|
|
15
|
-
*/
|
|
16
|
-
export function generateEncryptionKey() {
|
|
17
|
-
return randomBytes(KEY_LENGTH);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Get or create encryption key from file
|
|
21
|
-
*/
|
|
22
|
-
export function getOrCreateEncryptionKey(config) {
|
|
23
|
-
const { keyPath } = { ...DEFAULT_CONFIG, ...config };
|
|
24
|
-
// Ensure directory exists
|
|
25
|
-
const dir = join(keyPath, "..");
|
|
26
|
-
if (!existsSync(dir)) {
|
|
27
|
-
// Note: We can't use mkdirSync here because we're in a pure function
|
|
28
|
-
// This will be handled by the caller
|
|
29
|
-
}
|
|
30
|
-
if (!existsSync(keyPath)) {
|
|
31
|
-
const key = generateEncryptionKey();
|
|
32
|
-
try {
|
|
33
|
-
writeFileSync(keyPath, key.toString("base64"), { mode: 0o600 });
|
|
34
|
-
return key;
|
|
35
|
-
}
|
|
36
|
-
catch (e) {
|
|
37
|
-
// If we can't persist the key, encrypted data would be unrecoverable
|
|
38
|
-
// on the next run — fail loudly instead of silently degrading.
|
|
39
|
-
throw new Error(`Cannot write encryption key to ${keyPath}: ${e instanceof Error ? e.message : String(e)}`);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const keyBase64 = readFileSync(keyPath, "utf8");
|
|
44
|
-
return Buffer.from(keyBase64, "base64");
|
|
45
|
-
}
|
|
46
|
-
catch {
|
|
47
|
-
// Backup the corrupted key file before throwing
|
|
48
|
-
try {
|
|
49
|
-
const backupPath = `${keyPath}.corrupted.${Date.now()}`;
|
|
50
|
-
copyFileSync(keyPath, backupPath);
|
|
51
|
-
}
|
|
52
|
-
catch {
|
|
53
|
-
/* backup failed, continue */
|
|
54
|
-
}
|
|
55
|
-
throw new Error(`Encryption key file is corrupted or unreadable: ${keyPath}. ` +
|
|
56
|
-
`All previously encrypted data will be unrecoverable. ` +
|
|
57
|
-
`Delete the key file to generate a new one.`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Encrypt a string using AES-256-GCM
|
|
62
|
-
* Note: Bun's crypto module doesn't support getAuthTag/setAuthTag directly,
|
|
63
|
-
* so we use a simpler approach with AES-256-CBC + HMAC for authentication
|
|
64
|
-
*/
|
|
65
|
-
export function encryptString(plaintext, key) {
|
|
66
|
-
const iv = randomBytes(IV_LENGTH);
|
|
67
|
-
const salt = randomBytes(SALT_LENGTH);
|
|
68
|
-
// Derive encryption key and HMAC key from the main key
|
|
69
|
-
const encKey = scryptSync(key, salt, KEY_LENGTH);
|
|
70
|
-
const hmacSalt = Buffer.concat([salt, Buffer.from([1, 2, 3, 4])]);
|
|
71
|
-
const hmacKey = scryptSync(key, hmacSalt, KEY_LENGTH);
|
|
72
|
-
// Encrypt with AES-256-CBC
|
|
73
|
-
const cipher = createCipheriv("aes-256-cbc", encKey, iv);
|
|
74
|
-
let encrypted = cipher.update(plaintext, "utf8", "base64");
|
|
75
|
-
encrypted += cipher.final("base64");
|
|
76
|
-
// Create HMAC for authentication
|
|
77
|
-
const hmac = createHmac("sha256", hmacKey);
|
|
78
|
-
hmac.update(iv);
|
|
79
|
-
hmac.update(Buffer.from(encrypted, "base64"));
|
|
80
|
-
const authTag = hmac.digest();
|
|
81
|
-
// Format: salt:iv:authTag:encrypted
|
|
82
|
-
return `${salt.toString("base64")}:${iv.toString("base64")}:${authTag.toString("base64")}:${encrypted}`;
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Decrypt a string using AES-256-CBC + HMAC
|
|
86
|
-
*/
|
|
87
|
-
export function decryptString(encryptedText, key) {
|
|
88
|
-
const parts = encryptedText.split(":");
|
|
89
|
-
if (parts.length !== 4) {
|
|
90
|
-
throw new Error("Invalid encrypted format");
|
|
91
|
-
}
|
|
92
|
-
const salt = Buffer.from(parts[0], "base64");
|
|
93
|
-
const iv = Buffer.from(parts[1], "base64");
|
|
94
|
-
const authTag = Buffer.from(parts[2], "base64");
|
|
95
|
-
const encrypted = parts[3];
|
|
96
|
-
// Derive encryption key and HMAC key from the main key
|
|
97
|
-
const encKey = scryptSync(key, salt, KEY_LENGTH);
|
|
98
|
-
const hmacSalt = Buffer.concat([salt, Buffer.from([1, 2, 3, 4])]);
|
|
99
|
-
const hmacKey = scryptSync(key, hmacSalt, KEY_LENGTH);
|
|
100
|
-
// Verify HMAC
|
|
101
|
-
const hmac = createHmac("sha256", hmacKey);
|
|
102
|
-
hmac.update(iv);
|
|
103
|
-
hmac.update(Buffer.from(encrypted, "base64"));
|
|
104
|
-
const computedTag = hmac.digest();
|
|
105
|
-
if (computedTag.length !== authTag.length || !timingSafeEqual(computedTag, authTag)) {
|
|
106
|
-
throw new Error("Authentication failed: invalid tag");
|
|
107
|
-
}
|
|
108
|
-
// Decrypt with AES-256-CBC
|
|
109
|
-
const decipher = createDecipheriv("aes-256-cbc", encKey, iv);
|
|
110
|
-
let decrypted = decipher.update(encrypted, "base64", "utf8");
|
|
111
|
-
decrypted += decipher.final("utf8");
|
|
112
|
-
return decrypted;
|
|
113
|
-
}
|
|
114
|
-
import { createHmac } from "node:crypto";
|
|
115
|
-
/**
|
|
116
|
-
* Check if a string looks like it's encrypted
|
|
117
|
-
*/
|
|
118
|
-
export function isEncryptedString(value) {
|
|
119
|
-
// Encrypted strings have the format: salt:iv:authTag:encrypted
|
|
120
|
-
// Each part is base64 encoded and separated by colons
|
|
121
|
-
const parts = value.split(":");
|
|
122
|
-
if (parts.length !== 4)
|
|
123
|
-
return false;
|
|
124
|
-
// Check if all parts are valid base64
|
|
125
|
-
try {
|
|
126
|
-
Buffer.from(parts[0], "base64");
|
|
127
|
-
Buffer.from(parts[1], "base64");
|
|
128
|
-
Buffer.from(parts[2], "base64");
|
|
129
|
-
Buffer.from(parts[3], "base64");
|
|
130
|
-
return true;
|
|
131
|
-
}
|
|
132
|
-
catch {
|
|
133
|
-
return false;
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
/**
|
|
137
|
-
* Sensitive field names that should be encrypted
|
|
138
|
-
*/
|
|
139
|
-
export const SENSITIVE_FIELD_NAMES = [
|
|
140
|
-
"api_key",
|
|
141
|
-
"apikey",
|
|
142
|
-
"apiKey",
|
|
143
|
-
"token",
|
|
144
|
-
"access_token",
|
|
145
|
-
"accessToken",
|
|
146
|
-
"password",
|
|
147
|
-
"passwd",
|
|
148
|
-
"pwd",
|
|
149
|
-
"secret",
|
|
150
|
-
"private_key",
|
|
151
|
-
"privateKey",
|
|
152
|
-
"auth",
|
|
153
|
-
"authorization",
|
|
154
|
-
"key",
|
|
155
|
-
];
|
|
156
|
-
/**
|
|
157
|
-
* Check if a field name indicates sensitive data
|
|
158
|
-
*/
|
|
159
|
-
export function isSensitiveFieldName(fieldName) {
|
|
160
|
-
const lowerFieldName = fieldName.toLowerCase();
|
|
161
|
-
return SENSITIVE_FIELD_NAMES.some((name) => lowerFieldName.includes(name));
|
|
162
|
-
}
|
|
163
|
-
/**
|
|
164
|
-
* Encrypt sensitive fields in an object
|
|
165
|
-
*/
|
|
166
|
-
export function encryptSensitiveFields(obj, key) {
|
|
167
|
-
const result = {};
|
|
168
|
-
for (const [fieldName, value] of Object.entries(obj)) {
|
|
169
|
-
if (isSensitiveFieldName(fieldName) && typeof value === "string" && !isEncryptedString(value)) {
|
|
170
|
-
result[fieldName] = encryptString(value, key);
|
|
171
|
-
}
|
|
172
|
-
else if (typeof value === "object" && value !== null) {
|
|
173
|
-
if (Array.isArray(value)) {
|
|
174
|
-
result[fieldName] = value.map((item) => typeof item === "object" && item !== null && !(item instanceof RegExp)
|
|
175
|
-
? encryptSensitiveFields(item, key)
|
|
176
|
-
: item);
|
|
177
|
-
}
|
|
178
|
-
else if (value instanceof RegExp) {
|
|
179
|
-
result[fieldName] = value;
|
|
180
|
-
}
|
|
181
|
-
else {
|
|
182
|
-
result[fieldName] = encryptSensitiveFields(value, key);
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
result[fieldName] = value;
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
/**
|
|
192
|
-
* Decrypt sensitive fields in an object
|
|
193
|
-
*/
|
|
194
|
-
export function decryptSensitiveFields(obj, key) {
|
|
195
|
-
const result = {};
|
|
196
|
-
for (const [fieldName, value] of Object.entries(obj)) {
|
|
197
|
-
if (isSensitiveFieldName(fieldName) && typeof value === "string" && isEncryptedString(value)) {
|
|
198
|
-
try {
|
|
199
|
-
result[fieldName] = decryptString(value, key);
|
|
200
|
-
}
|
|
201
|
-
catch {
|
|
202
|
-
// If decryption fails, keep the encrypted value
|
|
203
|
-
result[fieldName] = value;
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
else if (typeof value === "object" && value !== null) {
|
|
207
|
-
if (Array.isArray(value)) {
|
|
208
|
-
result[fieldName] = value.map((item) => typeof item === "object" && item !== null && !(item instanceof RegExp)
|
|
209
|
-
? decryptSensitiveFields(item, key)
|
|
210
|
-
: item);
|
|
211
|
-
}
|
|
212
|
-
else if (value instanceof RegExp) {
|
|
213
|
-
result[fieldName] = value;
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
result[fieldName] = decryptSensitiveFields(value, key);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
else {
|
|
220
|
-
result[fieldName] = value;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
return result;
|
|
224
|
-
}
|
|
225
|
-
/**
|
|
226
|
-
* Create a wrapper for encrypting/decrypting config values
|
|
227
|
-
*/
|
|
228
|
-
export class ConfigEncryptor {
|
|
229
|
-
key;
|
|
230
|
-
constructor(config) {
|
|
231
|
-
this.key = getOrCreateEncryptionKey(config);
|
|
232
|
-
}
|
|
233
|
-
encrypt(config) {
|
|
234
|
-
return encryptSensitiveFields(config, this.key);
|
|
235
|
-
}
|
|
236
|
-
decrypt(config) {
|
|
237
|
-
return decryptSensitiveFields(config, this.key);
|
|
238
|
-
}
|
|
239
|
-
getKey() {
|
|
240
|
-
return this.key;
|
|
241
|
-
}
|
|
242
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export * from "./command-validator";
|
|
2
|
-
export * from "./path-validator";
|
|
3
|
-
export * from "./network-validator";
|
|
4
|
-
export * from "./content-scanner";
|
|
5
|
-
export * from "./data-sanitizer";
|
|
6
|
-
export * from "./audit-log";
|
|
7
|
-
export * from "./rate-limiter";
|
|
8
|
-
export * from "./encryption";
|
|
9
|
-
export * from "./session-isolation";
|
|
10
|
-
export * from "./session-encryption";
|
|
11
|
-
export * from "./audit-notifier";
|
|
12
|
-
export * from "./security-policies";
|
|
13
|
-
// Re-export sanitizeUrl from network-validator to avoid conflicts
|
|
14
|
-
export { sanitizeUrl } from "./network-validator";
|