aura-code 0.6.3 → 0.10.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 +47 -164
- package/dist/agent/affect.d.ts +13 -0
- package/dist/agent/affect.js +34 -0
- package/dist/agent/affect.js.map +1 -0
- package/dist/agent/compactor.d.ts +27 -4
- package/dist/agent/compactor.js +185 -54
- package/dist/agent/compactor.js.map +1 -1
- package/dist/agent/confess.d.ts +35 -0
- package/dist/agent/confess.js +296 -0
- package/dist/agent/confess.js.map +1 -0
- package/dist/agent/context.d.ts +0 -4
- package/dist/agent/context.js +0 -38
- package/dist/agent/context.js.map +1 -1
- package/dist/agent/domain-expertise.d.ts +22 -0
- package/dist/agent/domain-expertise.js +110 -0
- package/dist/agent/domain-expertise.js.map +1 -0
- package/dist/agent/executive-queue.d.ts +26 -0
- package/dist/agent/executive-queue.js +50 -0
- package/dist/agent/executive-queue.js.map +1 -0
- package/dist/agent/generational-flush.d.ts +28 -0
- package/dist/agent/generational-flush.js +48 -0
- package/dist/agent/generational-flush.js.map +1 -0
- package/dist/agent/loop-profile.d.ts +50 -0
- package/dist/agent/loop-profile.js +88 -0
- package/dist/agent/loop-profile.js.map +1 -0
- package/dist/agent/loop.d.ts +14 -5
- package/dist/agent/loop.js +167 -39
- package/dist/agent/loop.js.map +1 -1
- package/dist/agent/memory-consolidate.d.ts +14 -0
- package/dist/agent/memory-consolidate.js +141 -0
- package/dist/agent/memory-consolidate.js.map +1 -0
- package/dist/agent/mixture.d.ts +33 -0
- package/dist/agent/mixture.js +138 -0
- package/dist/agent/mixture.js.map +1 -0
- package/dist/agent/spawner.js +2 -2
- package/dist/agent/spawner.js.map +1 -1
- package/dist/agent/system-prompt.d.ts +1 -1
- package/dist/agent/system-prompt.js +50 -22
- package/dist/agent/system-prompt.js.map +1 -1
- package/dist/agent/unified-memory.d.ts +15 -0
- package/dist/agent/unified-memory.js +164 -0
- package/dist/agent/unified-memory.js.map +1 -0
- package/dist/checkpoints/engine.d.ts +44 -0
- package/dist/checkpoints/engine.js +324 -0
- package/dist/checkpoints/engine.js.map +1 -0
- package/dist/cli/command-palette.d.ts +26 -0
- package/dist/cli/command-palette.js +133 -0
- package/dist/cli/command-palette.js.map +1 -0
- package/dist/cli/context-health.d.ts +51 -0
- package/dist/cli/context-health.js +187 -0
- package/dist/cli/context-health.js.map +1 -0
- package/dist/cli/diamond.d.ts +67 -0
- package/dist/cli/diamond.js +222 -12
- package/dist/cli/diamond.js.map +1 -1
- package/dist/cli/dic.d.ts +2 -0
- package/dist/cli/dic.js +106 -0
- package/dist/cli/dic.js.map +1 -0
- package/dist/cli/diff-view.d.ts +18 -0
- package/dist/cli/diff-view.js +114 -0
- package/dist/cli/diff-view.js.map +1 -0
- package/dist/cli/display.d.ts +18 -4
- package/dist/cli/display.js +74 -151
- package/dist/cli/display.js.map +1 -1
- package/dist/cli/help-data.d.ts +7 -0
- package/dist/cli/help-data.js +80 -0
- package/dist/cli/help-data.js.map +1 -0
- package/dist/cli/index.js +1002 -852
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/markdown.d.ts +5 -0
- package/dist/cli/markdown.js +128 -0
- package/dist/cli/markdown.js.map +1 -0
- package/dist/cli/model-select.d.ts +39 -0
- package/dist/cli/model-select.js +65 -0
- package/dist/cli/model-select.js.map +1 -0
- package/dist/cli/tui.d.ts +34 -0
- package/dist/cli/tui.js +1176 -0
- package/dist/cli/tui.js.map +1 -0
- package/dist/config/defaults.d.ts +6 -17
- package/dist/config/defaults.js +25 -46
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/project-config.d.ts +2 -6
- package/dist/config/project-config.js +2 -2
- package/dist/config/project-config.js.map +1 -1
- package/dist/doctor/checks.d.ts +18 -0
- package/dist/doctor/checks.js +524 -0
- package/dist/doctor/checks.js.map +1 -0
- package/dist/doctor/index.d.ts +4 -0
- package/dist/doctor/index.js +229 -0
- package/dist/doctor/index.js.map +1 -0
- package/dist/doctor/repair.d.ts +19 -0
- package/dist/doctor/repair.js +126 -0
- package/dist/doctor/repair.js.map +1 -0
- package/dist/doctor/types.d.ts +38 -0
- package/dist/doctor/types.js +10 -0
- package/dist/doctor/types.js.map +1 -0
- package/dist/dream/dream.d.ts +79 -59
- package/dist/dream/dream.js +324 -144
- package/dist/dream/dream.js.map +1 -1
- package/dist/dream/episode.d.ts +17 -0
- package/dist/dream/episode.js +116 -0
- package/dist/dream/episode.js.map +1 -0
- package/dist/kanban/engine.d.ts +26 -0
- package/dist/kanban/engine.js +208 -0
- package/dist/kanban/engine.js.map +1 -0
- package/dist/kanban/types.d.ts +19 -0
- package/dist/kanban/types.js +9 -1
- package/dist/kanban/types.js.map +1 -1
- package/dist/machina/render-html.js +4 -2
- package/dist/machina/render-html.js.map +1 -1
- package/dist/machina/render-terminal.js +11 -4
- package/dist/machina/render-terminal.js.map +1 -1
- package/dist/machina/repair.d.ts +23 -0
- package/dist/machina/repair.js +98 -0
- package/dist/machina/repair.js.map +1 -0
- package/dist/machina/spec.js +12 -12
- package/dist/machina/spec.js.map +1 -1
- package/dist/machina/verify.d.ts +13 -1
- package/dist/machina/verify.js +12 -2
- package/dist/machina/verify.js.map +1 -1
- package/dist/mining/extract.d.ts +34 -0
- package/dist/mining/extract.js +229 -0
- package/dist/mining/extract.js.map +1 -0
- package/dist/mining/refine.d.ts +28 -0
- package/dist/mining/refine.js +256 -0
- package/dist/mining/refine.js.map +1 -0
- package/dist/perception/extractor.js +5 -3
- package/dist/perception/extractor.js.map +1 -1
- package/dist/perception/graph-store.d.ts +0 -11
- package/dist/perception/graph-store.js +0 -33
- package/dist/perception/graph-store.js.map +1 -1
- package/dist/perception/index.d.ts +1 -1
- package/dist/perception/index.js +1 -2
- package/dist/perception/index.js.map +1 -1
- package/dist/plugins/commands.d.ts +38 -0
- package/dist/plugins/commands.js +134 -0
- package/dist/plugins/commands.js.map +1 -0
- package/dist/plugins/frontmatter.d.ts +14 -0
- package/dist/plugins/frontmatter.js +78 -0
- package/dist/plugins/frontmatter.js.map +1 -0
- package/dist/plugins/hooks.d.ts +12 -0
- package/dist/plugins/hooks.js +148 -0
- package/dist/plugins/hooks.js.map +1 -0
- package/dist/plugins/loader.d.ts +7 -0
- package/dist/plugins/loader.js +255 -0
- package/dist/plugins/loader.js.map +1 -0
- package/dist/plugins/market.d.ts +23 -0
- package/dist/plugins/market.js +281 -0
- package/dist/plugins/market.js.map +1 -0
- package/dist/plugins/types.d.ts +90 -0
- package/dist/plugins/types.js +20 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/providers/anthropic-oauth-draft.d.ts +119 -0
- package/dist/providers/anthropic-oauth-draft.js +414 -0
- package/dist/providers/anthropic-oauth-draft.js.map +1 -0
- package/dist/providers/anthropic.d.ts +4 -1
- package/dist/providers/anthropic.js +35 -7
- package/dist/providers/anthropic.js.map +1 -1
- package/dist/providers/factory.d.ts +43 -19
- package/dist/providers/factory.js +183 -129
- package/dist/providers/factory.js.map +1 -1
- package/dist/providers/google.js +9 -22
- package/dist/providers/google.js.map +1 -1
- package/dist/providers/live-models.d.ts +23 -0
- package/dist/providers/live-models.js +145 -0
- package/dist/providers/live-models.js.map +1 -0
- package/dist/providers/openai-compatible.d.ts +1 -12
- package/dist/providers/openai-compatible.js +71 -108
- package/dist/providers/openai-compatible.js.map +1 -1
- package/dist/providers/resilient-factory.js +1 -1
- package/dist/providers/resilient-factory.js.map +1 -1
- package/dist/repl/queue.d.ts +22 -0
- package/dist/repl/queue.js +165 -0
- package/dist/repl/queue.js.map +1 -0
- package/dist/repl/side-channel.d.ts +23 -0
- package/dist/repl/side-channel.js +63 -0
- package/dist/repl/side-channel.js.map +1 -0
- package/dist/research/council.d.ts +2 -0
- package/dist/research/council.js +29 -4
- package/dist/research/council.js.map +1 -1
- package/dist/ruby/alternator.d.ts +11 -30
- package/dist/ruby/alternator.js +59 -38
- package/dist/ruby/alternator.js.map +1 -1
- package/dist/ruby/episode-capture.d.ts +0 -10
- package/dist/ruby/episode-capture.js +0 -33
- package/dist/ruby/episode-capture.js.map +1 -1
- package/dist/ruby/types.js +1 -1
- package/dist/ruby/types.js.map +1 -1
- package/dist/safety/path-jail.d.ts +17 -0
- package/dist/safety/path-jail.js +100 -0
- package/dist/safety/path-jail.js.map +1 -0
- package/dist/safety/permissions.d.ts +12 -21
- package/dist/safety/permissions.js +81 -216
- package/dist/safety/permissions.js.map +1 -1
- package/dist/safety/ssrf.d.ts +29 -0
- package/dist/safety/ssrf.js +185 -0
- package/dist/safety/ssrf.js.map +1 -0
- package/dist/server/index.js +64 -10
- package/dist/server/index.js.map +1 -1
- package/dist/setup/first-run.d.ts +0 -24
- package/dist/setup/first-run.js +10 -344
- package/dist/setup/first-run.js.map +1 -1
- package/dist/setup/key-store.d.ts +12 -0
- package/dist/setup/key-store.js +108 -0
- package/dist/setup/key-store.js.map +1 -0
- package/dist/setup/provider-registry.js +31 -18
- package/dist/setup/provider-registry.js.map +1 -1
- package/dist/setup/provider-test.d.ts +7 -0
- package/dist/setup/provider-test.js +73 -10
- package/dist/setup/provider-test.js.map +1 -1
- package/dist/setup/provider-wizard.d.ts +4 -1
- package/dist/setup/provider-wizard.js +92 -26
- package/dist/setup/provider-wizard.js.map +1 -1
- package/dist/setup/xiaomi.d.ts +1 -1
- package/dist/setup/xiaomi.js +3 -3
- package/dist/setup/xiaomi.js.map +1 -1
- package/dist/tools/browser.js +14 -10
- package/dist/tools/browser.js.map +1 -1
- package/dist/tools/clipboard.js +7 -1
- package/dist/tools/clipboard.js.map +1 -1
- package/dist/tools/dictate.d.ts +60 -0
- package/dist/tools/dictate.js +983 -0
- package/dist/tools/dictate.js.map +1 -0
- package/dist/tools/dictate_patched.js +1057 -0
- package/dist/tools/edit-file.js +10 -2
- package/dist/tools/edit-file.js.map +1 -1
- package/dist/tools/http-request.js +14 -4
- package/dist/tools/http-request.js.map +1 -1
- package/dist/tools/image-read.js +34 -2
- package/dist/tools/image-read.js.map +1 -1
- package/dist/tools/index.d.ts +16 -1
- package/dist/tools/index.js +59 -8
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/mcp.js +12 -0
- package/dist/tools/mcp.js.map +1 -1
- package/dist/tools/read-file.js +10 -14
- package/dist/tools/read-file.js.map +1 -1
- package/dist/tools/telegram-audio-policy.d.ts +30 -0
- package/dist/tools/telegram-audio-policy.js +49 -0
- package/dist/tools/telegram-audio-policy.js.map +1 -0
- package/dist/tools/telegram-bot.js +1136 -537
- package/dist/tools/telegram-bot.js.map +1 -1
- package/dist/tools/telegram-voice.d.ts +18 -5
- package/dist/tools/telegram-voice.js +54 -12
- package/dist/tools/telegram-voice.js.map +1 -1
- package/dist/tools/telegram.d.ts +3 -3
- package/dist/tools/telegram.js +5 -84
- package/dist/tools/telegram.js.map +1 -1
- package/dist/tools/tools.d.ts +2 -14
- package/dist/tools/tools.js +79 -144
- package/dist/tools/tools.js.map +1 -1
- package/dist/tools/web-fetch.js +39 -7
- package/dist/tools/web-fetch.js.map +1 -1
- package/dist/tools/web-search.d.ts +0 -20
- package/dist/tools/web-search.js +40 -137
- package/dist/tools/web-search.js.map +1 -1
- package/dist/util/errors.js +1 -15
- package/dist/util/errors.js.map +1 -1
- package/dist/util/json-repair.d.ts +11 -0
- package/dist/util/json-repair.js +31 -0
- package/dist/util/json-repair.js.map +1 -0
- package/dist/util/rate-limiter.js +2 -8
- package/dist/util/rate-limiter.js.map +1 -1
- package/dist/viz/index.js +793 -792
- package/dist/viz/index.js.map +1 -1
- package/package.json +12 -32
|
@@ -32,197 +32,19 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
exports.PermissionSystem =
|
|
36
|
+
exports.PermissionSystem = void 0;
|
|
37
|
+
exports.setSharedReadline = setSharedReadline;
|
|
38
|
+
exports.getSharedReadline = getSharedReadline;
|
|
39
|
+
exports.setConfirmHandler = setConfirmHandler;
|
|
40
40
|
exports.confirm = confirm;
|
|
41
|
-
const
|
|
42
|
-
const path = __importStar(require("path"));
|
|
43
|
-
const readline_1 = __importDefault(require("readline"));
|
|
41
|
+
const readline = __importStar(require("readline"));
|
|
44
42
|
const defaults_js_1 = require("../config/defaults.js");
|
|
45
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
46
|
-
// ShellCommandValidator — prevents recursive commands from traversing into
|
|
47
|
-
// unresponsive FUSE/network mounts where I/O can cause D-state hangs.
|
|
48
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
-
/** Extract search-path arguments from find/grep/rg command strings. */
|
|
50
|
-
function extractSearchPaths(base, args) {
|
|
51
|
-
if (base === 'find') {
|
|
52
|
-
// find [path] [expression] — first non-flag arg is the search path
|
|
53
|
-
for (const a of args) {
|
|
54
|
-
if (!a.startsWith('-') && a !== '!' && a !== '(' && a !== ')')
|
|
55
|
-
return [a];
|
|
56
|
-
}
|
|
57
|
-
return ['.'];
|
|
58
|
-
}
|
|
59
|
-
// grep / rg: look for paths after --, or the last non-flag arg that looks like a path
|
|
60
|
-
const paths = [];
|
|
61
|
-
let afterDoubleDash = false;
|
|
62
|
-
for (const a of args) {
|
|
63
|
-
if (a === '--') {
|
|
64
|
-
afterDoubleDash = true;
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
if (afterDoubleDash && !a.startsWith('-')) {
|
|
68
|
-
paths.push(a);
|
|
69
|
-
continue;
|
|
70
|
-
}
|
|
71
|
-
// Flags with values: skip the value part
|
|
72
|
-
if (a === '-e' || a === '-f' || a === '--regexp' || a === '--file')
|
|
73
|
-
continue;
|
|
74
|
-
if (a.startsWith('--max-count') || a.startsWith('--glob') || a.startsWith('--include') || a.startsWith('--exclude'))
|
|
75
|
-
continue;
|
|
76
|
-
if (a.startsWith('-') || a === '--')
|
|
77
|
-
continue;
|
|
78
|
-
// Only for -r/-R (recursive) commands — collect potential path args
|
|
79
|
-
paths.push(a);
|
|
80
|
-
}
|
|
81
|
-
return paths.length > 0 ? paths : [];
|
|
82
|
-
}
|
|
83
|
-
/** Detect whether a recursive search command targets a given path. */
|
|
84
|
-
function hasRecursiveFlag(args) {
|
|
85
|
-
return args.some(a => a === '-r' || a === '-R' || a === '--recursive' ||
|
|
86
|
-
(a.startsWith('-') && !a.startsWith('--') && (a.includes('r') || a.includes('R'))));
|
|
87
|
-
}
|
|
88
|
-
class ShellCommandValidator {
|
|
89
|
-
blockedPrefixes;
|
|
90
|
-
constructor() {
|
|
91
|
-
// Merge static blocklist with dynamically discovered FUSE mounts
|
|
92
|
-
this.blockedPrefixes = [...defaults_js_1.BLOCKED_TRAVERSAL_PATHS];
|
|
93
|
-
this.discoverFuseMounts();
|
|
94
|
-
}
|
|
95
|
-
discoverFuseMounts() {
|
|
96
|
-
try {
|
|
97
|
-
const mounts = fs.readFileSync('/proc/mounts', 'utf8');
|
|
98
|
-
for (const line of mounts.split('\n')) {
|
|
99
|
-
const parts = line.split(' ');
|
|
100
|
-
if (parts.length >= 3 && defaults_js_1.FUSE_MOUNT_PATTERN.test(parts[2])) {
|
|
101
|
-
const mountPoint = parts[1];
|
|
102
|
-
if (mountPoint && !mountPoint.endsWith('/')) {
|
|
103
|
-
this.blockedPrefixes.push(mountPoint + '/');
|
|
104
|
-
}
|
|
105
|
-
else if (mountPoint) {
|
|
106
|
-
this.blockedPrefixes.push(mountPoint);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
// /proc/mounts not available (non-Linux, permissions) — static list only
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
validateCommand(cmd, projectRoot, config) {
|
|
116
|
-
const tokens = tokenize(cmd);
|
|
117
|
-
if (tokens.length === 0)
|
|
118
|
-
return { allowed: true };
|
|
119
|
-
const base = path.basename(tokens[0]);
|
|
120
|
-
if (!['find', 'grep', 'rg'].includes(base))
|
|
121
|
-
return { allowed: true };
|
|
122
|
-
// rg is recursive by default; grep/find require explicit -r flag
|
|
123
|
-
const isRecursive = base === 'find' || base === 'rg' || hasRecursiveFlag(tokens.slice(1));
|
|
124
|
-
if (!isRecursive)
|
|
125
|
-
return { allowed: true };
|
|
126
|
-
const searchPaths = extractSearchPaths(base, tokens.slice(1));
|
|
127
|
-
const normalizedRoot = projectRoot.endsWith('/') ? projectRoot : projectRoot + '/';
|
|
128
|
-
const allowed = config?.allowedMountPaths ?? [];
|
|
129
|
-
for (const raw of searchPaths) {
|
|
130
|
-
// Expand ~ to HOME
|
|
131
|
-
const expanded = raw === '~' || raw.startsWith('~/')
|
|
132
|
-
? path.join(process.env.HOME ?? '/tmp', raw.slice(1))
|
|
133
|
-
: raw;
|
|
134
|
-
const resolved = path.resolve(projectRoot, expanded);
|
|
135
|
-
const normalizedResolved = resolved.endsWith('/') ? resolved : resolved + '/';
|
|
136
|
-
// 1) Check blocked mount prefixes first — allowedMountPaths override both
|
|
137
|
-
// the mount block AND the root boundary (user explicitly opted in).
|
|
138
|
-
let whitelisted = false;
|
|
139
|
-
for (const prefix of this.blockedPrefixes) {
|
|
140
|
-
if (normalizedResolved.startsWith(prefix)) {
|
|
141
|
-
const isExplicitlyAllowed = allowed.some(a => {
|
|
142
|
-
const normAllowed = a.endsWith('/') ? a : a + '/';
|
|
143
|
-
return normalizedResolved.startsWith(normAllowed);
|
|
144
|
-
});
|
|
145
|
-
if (isExplicitlyAllowed) {
|
|
146
|
-
whitelisted = true;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
allowed: false,
|
|
151
|
-
reason: `Blocked: '${base}' would traverse into '${raw}' which matches a ` +
|
|
152
|
-
`blocked mount prefix (${prefix}). This can cause hangs on unresponsive ` +
|
|
153
|
-
`FUSE/network filesystems. Add '${raw}' to allowedMountPaths in .aura.json ` +
|
|
154
|
-
`to override.`,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
// 2) Must be inside project root (skip if explicitly whitelisted above)
|
|
159
|
-
if (!whitelisted && !normalizedResolved.startsWith(normalizedRoot) && normalizedResolved !== normalizedRoot) {
|
|
160
|
-
return {
|
|
161
|
-
allowed: false,
|
|
162
|
-
reason: `Blocked: '${base}' search path '${raw}' resolves outside project root (${projectRoot}). ` +
|
|
163
|
-
`To search outside the project, run the command directly in your terminal.`,
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
return { allowed: true };
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
exports.ShellCommandValidator = ShellCommandValidator;
|
|
171
|
-
/**
|
|
172
|
-
* Minimal shell tokenizer that handles quotes and backslash escapes.
|
|
173
|
-
* Splits a command string into tokens without invoking a shell.
|
|
174
|
-
*/
|
|
175
|
-
function tokenize(cmd) {
|
|
176
|
-
const tokens = [];
|
|
177
|
-
let current = '';
|
|
178
|
-
let inSingle = false;
|
|
179
|
-
let inDouble = false;
|
|
180
|
-
let escape = false;
|
|
181
|
-
for (const ch of cmd) {
|
|
182
|
-
if (escape) {
|
|
183
|
-
current += ch;
|
|
184
|
-
escape = false;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
if (ch === '\\' && !inSingle) {
|
|
188
|
-
escape = true;
|
|
189
|
-
continue;
|
|
190
|
-
}
|
|
191
|
-
if (ch === "'" && !inDouble) {
|
|
192
|
-
inSingle = !inSingle;
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (ch === '"' && !inSingle) {
|
|
196
|
-
inDouble = !inDouble;
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
if (/\s/.test(ch) && !inSingle && !inDouble) {
|
|
200
|
-
if (current.length > 0) {
|
|
201
|
-
tokens.push(current);
|
|
202
|
-
current = '';
|
|
203
|
-
}
|
|
204
|
-
continue;
|
|
205
|
-
}
|
|
206
|
-
current += ch;
|
|
207
|
-
}
|
|
208
|
-
if (current.length > 0)
|
|
209
|
-
tokens.push(current);
|
|
210
|
-
return tokens;
|
|
211
|
-
}
|
|
212
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
213
|
-
// PermissionSystem
|
|
214
|
-
// ─────────────────────────────────────────────────────────────────────────────
|
|
215
43
|
class PermissionSystem {
|
|
216
44
|
level;
|
|
217
45
|
sessionApprovals = new Set();
|
|
218
|
-
|
|
219
|
-
projectRoot;
|
|
220
|
-
config;
|
|
221
|
-
constructor(level = 'normal', projectRoot, config) {
|
|
46
|
+
constructor(level = 'normal') {
|
|
222
47
|
this.level = level;
|
|
223
|
-
this.projectRoot = projectRoot ?? process.cwd();
|
|
224
|
-
this.config = config;
|
|
225
|
-
this.validator = new ShellCommandValidator();
|
|
226
48
|
}
|
|
227
49
|
check(toolName, input) {
|
|
228
50
|
// Read-only mode: only allow read operations
|
|
@@ -240,76 +62,119 @@ class PermissionSystem {
|
|
|
240
62
|
if (this.isDangerous(cmd)) {
|
|
241
63
|
return { allowed: false, reason: `Dangerous command blocked: ${cmd}` };
|
|
242
64
|
}
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
65
|
+
}
|
|
66
|
+
if (toolName === 'mcp' && String(input.action ?? '') === 'connect') {
|
|
67
|
+
const cmd = this.mcpConnectCommand(input);
|
|
68
|
+
if (this.isDangerous(cmd)) {
|
|
69
|
+
return { allowed: false, reason: `Dangerous command blocked: ${cmd}` };
|
|
70
|
+
}
|
|
246
71
|
}
|
|
247
72
|
return { allowed: true };
|
|
248
73
|
}
|
|
74
|
+
// mcp 'connect' spawns an arbitrary external server process — the same
|
|
75
|
+
// trust boundary as run_shell, so it gets the same dangerous-pattern
|
|
76
|
+
// screen plus an unconditional confirm (a server spawn is never on the
|
|
77
|
+
// safe list; once connected, its tools run without further prompts).
|
|
78
|
+
if (toolName === 'mcp' && String(input.action ?? '') === 'connect') {
|
|
79
|
+
const cmd = this.mcpConnectCommand(input);
|
|
80
|
+
if (this.isDangerous(cmd)) {
|
|
81
|
+
return { allowed: false, reason: `Dangerous command blocked: ${cmd}` };
|
|
82
|
+
}
|
|
83
|
+
return { allowed: true, needsConfirm: true };
|
|
84
|
+
}
|
|
249
85
|
// Normal mode: safe ops auto-approved, destructive need confirm
|
|
250
86
|
if (toolName === 'run_shell') {
|
|
251
87
|
const cmd = String(input.command ?? '');
|
|
252
88
|
if (this.isDangerous(cmd)) {
|
|
253
89
|
return { allowed: false, reason: `Dangerous command blocked: ${cmd}` };
|
|
254
90
|
}
|
|
255
|
-
const scopeCheck = this.validator.validateCommand(cmd, this.projectRoot, this.config);
|
|
256
|
-
if (!scopeCheck.allowed)
|
|
257
|
-
return scopeCheck;
|
|
258
91
|
if (!this.isSafe(cmd)) {
|
|
259
92
|
return { allowed: true, needsConfirm: true };
|
|
260
93
|
}
|
|
261
94
|
}
|
|
262
|
-
if (toolName === 'write_file'
|
|
95
|
+
if (toolName === 'write_file') {
|
|
263
96
|
const path = String(input.path ?? '');
|
|
264
|
-
const key =
|
|
97
|
+
const key = `write:${path}`;
|
|
265
98
|
if (this.sessionApprovals.has(key))
|
|
266
99
|
return { allowed: true };
|
|
267
|
-
return { allowed: true
|
|
100
|
+
return { allowed: true };
|
|
268
101
|
}
|
|
269
102
|
return { allowed: true };
|
|
270
103
|
}
|
|
271
104
|
approveForSession(key) {
|
|
272
105
|
this.sessionApprovals.add(key);
|
|
273
106
|
}
|
|
107
|
+
/** Current enforcement level. */
|
|
108
|
+
getLevel() {
|
|
109
|
+
return this.level;
|
|
110
|
+
}
|
|
274
111
|
/**
|
|
275
|
-
* Change the
|
|
276
|
-
*
|
|
277
|
-
*
|
|
278
|
-
*
|
|
112
|
+
* Change the level at runtime — used by the REPL `:approve` command to flip
|
|
113
|
+
* a session into auto-approve (no per-command confirm) and back. Dangerous
|
|
114
|
+
* commands are STILL blocked in 'auto'; this only removes the y/N prompt for
|
|
115
|
+
* ordinary destructive-but-safe operations.
|
|
279
116
|
*/
|
|
280
117
|
setLevel(level) {
|
|
281
118
|
this.level = level;
|
|
282
119
|
}
|
|
283
|
-
|
|
284
|
-
|
|
120
|
+
mcpConnectCommand(input) {
|
|
121
|
+
const args = Array.isArray(input.args_list) ? input.args_list.join(' ') : '';
|
|
122
|
+
return `${String(input.command ?? '')} ${args}`.trim();
|
|
285
123
|
}
|
|
286
124
|
isDangerous(cmd) {
|
|
287
125
|
return defaults_js_1.DANGEROUS_PATTERNS.some(p => p.test(cmd));
|
|
288
126
|
}
|
|
289
127
|
isSafe(cmd) {
|
|
290
|
-
const
|
|
291
|
-
|
|
128
|
+
const trimmed = cmd.trim();
|
|
129
|
+
// A command containing shell control operators can't be judged safe from
|
|
130
|
+
// its prefix alone — `cat x; python3 -c '…'` starts with a safe command
|
|
131
|
+
// but chains an interpreter. Force confirmation for anything with
|
|
132
|
+
// chaining (`;` `&` `|`), redirection (`>` `<`), or command substitution
|
|
133
|
+
// (`$(…)`, backticks). This is the structural fix for prefix-smuggling.
|
|
134
|
+
if (/[;&|<>`]/.test(trimmed) || trimmed.includes('$('))
|
|
135
|
+
return false;
|
|
136
|
+
const lower = trimmed.toLowerCase();
|
|
137
|
+
// Anchor to a whole-command match so `curlx …` doesn't match `curl` and
|
|
138
|
+
// `lscpu` doesn't match `ls`.
|
|
139
|
+
return defaults_js_1.SAFE_SHELL_COMMANDS.some(s => lower === s || lower.startsWith(s + ' '));
|
|
292
140
|
}
|
|
293
141
|
}
|
|
294
142
|
exports.PermissionSystem = PermissionSystem;
|
|
143
|
+
// Shared readline from the interactive REPL. confirm() must reuse it when
|
|
144
|
+
// set: a second interface on the same stdin echoes every keypress twice, and
|
|
145
|
+
// closing it pauses stdin without the REPL's interface knowing — its next
|
|
146
|
+
// prompt never resumes the stream, the event loop drains, and the process
|
|
147
|
+
// exits right after printing the prompt.
|
|
148
|
+
let sharedRl = null;
|
|
149
|
+
function setSharedReadline(rl) {
|
|
150
|
+
sharedRl = rl;
|
|
151
|
+
}
|
|
152
|
+
function getSharedReadline() {
|
|
153
|
+
return sharedRl;
|
|
154
|
+
}
|
|
155
|
+
// The TUI owns stdin in raw mode via its own 'data' handler — a plain
|
|
156
|
+
// readline.Interface (below) forces stdin out of raw mode the moment it
|
|
157
|
+
// attaches and never restores it, so a confirm() prompt during TUI mode
|
|
158
|
+
// used to fight the TUI's own input handling for every keystroke (garbled
|
|
159
|
+
// echo, dropped characters, leftover fragments on screen). setSharedReadline
|
|
160
|
+
// was meant to prevent a *second* readline.Interface, but the TUI doesn't
|
|
161
|
+
// use readline at all — it needs its own raw-mode-safe prompt. cli/index.ts
|
|
162
|
+
// registers one via setConfirmHandler() when TUI mode starts; confirm()
|
|
163
|
+
// prefers it over readline whenever it's set.
|
|
164
|
+
let confirmHandler = null;
|
|
165
|
+
function setConfirmHandler(fn) {
|
|
166
|
+
confirmHandler = fn;
|
|
167
|
+
}
|
|
295
168
|
/** Ask user to confirm in the terminal. Returns true if approved. */
|
|
296
169
|
async function confirm(message) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
const existingListeners = process.stdin.rawListeners('data');
|
|
301
|
-
for (const listener of existingListeners) {
|
|
302
|
-
process.stdin.removeListener('data', listener);
|
|
303
|
-
}
|
|
170
|
+
if (confirmHandler)
|
|
171
|
+
return confirmHandler(message);
|
|
172
|
+
const rl = sharedRl ?? readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
304
173
|
return new Promise(resolve => {
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
for (const listener of existingListeners) {
|
|
310
|
-
process.stdin.on('data', listener);
|
|
311
|
-
}
|
|
312
|
-
resolve(answer.trim().toLowerCase() === 'y' || answer.trim().toLowerCase() === 'yes');
|
|
174
|
+
rl.question(`\n⚠️ ${message} [y/N] `, answer => {
|
|
175
|
+
if (rl !== sharedRl)
|
|
176
|
+
rl.close();
|
|
177
|
+
resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
|
|
313
178
|
});
|
|
314
179
|
});
|
|
315
180
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/safety/permissions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"permissions.js","sourceRoot":"","sources":["../../src/safety/permissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSRF guard for the outbound HTTP tools (web_fetch, http_request).
|
|
3
|
+
*
|
|
4
|
+
* Without this, the agent can be steered — including by prompt injection in a
|
|
5
|
+
* fetched page — to reach loopback services, RFC-1918 hosts, or the cloud
|
|
6
|
+
* metadata endpoint (169.254.169.254) to steal IAM credentials, then exfil
|
|
7
|
+
* them back out. We reject private/loopback/link-local targets, restrict the
|
|
8
|
+
* protocol to http/https, and re-validate on every redirect hop.
|
|
9
|
+
*/
|
|
10
|
+
export declare class SsrfError extends Error {
|
|
11
|
+
constructor(message: string);
|
|
12
|
+
}
|
|
13
|
+
/** True if the literal IP is one we refuse to connect to. Unknown → blocked. */
|
|
14
|
+
export declare function isBlockedIp(ip: string): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Validate a URL for outbound use: http/https only, and its host must not
|
|
17
|
+
* resolve to a blocked address. Returns the parsed URL on success.
|
|
18
|
+
*/
|
|
19
|
+
export declare function assertUrlAllowed(rawUrl: string): Promise<URL>;
|
|
20
|
+
export interface SafeFetchOptions {
|
|
21
|
+
maxRedirects?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* fetch() wrapper that enforces {@link assertUrlAllowed} before the request
|
|
25
|
+
* and on every redirect hop. Redirects are followed manually (redirect:
|
|
26
|
+
* 'manual') so a public URL can't 302 to a private/metadata address without
|
|
27
|
+
* being re-checked.
|
|
28
|
+
*/
|
|
29
|
+
export declare function safeFetch(rawUrl: string, init: RequestInit, opts?: SafeFetchOptions): Promise<Response>;
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SsrfError = void 0;
|
|
37
|
+
exports.isBlockedIp = isBlockedIp;
|
|
38
|
+
exports.assertUrlAllowed = assertUrlAllowed;
|
|
39
|
+
exports.safeFetch = safeFetch;
|
|
40
|
+
const dns = __importStar(require("dns"));
|
|
41
|
+
const net = __importStar(require("net"));
|
|
42
|
+
/**
|
|
43
|
+
* SSRF guard for the outbound HTTP tools (web_fetch, http_request).
|
|
44
|
+
*
|
|
45
|
+
* Without this, the agent can be steered — including by prompt injection in a
|
|
46
|
+
* fetched page — to reach loopback services, RFC-1918 hosts, or the cloud
|
|
47
|
+
* metadata endpoint (169.254.169.254) to steal IAM credentials, then exfil
|
|
48
|
+
* them back out. We reject private/loopback/link-local targets, restrict the
|
|
49
|
+
* protocol to http/https, and re-validate on every redirect hop.
|
|
50
|
+
*/
|
|
51
|
+
class SsrfError extends Error {
|
|
52
|
+
constructor(message) {
|
|
53
|
+
super(message);
|
|
54
|
+
this.name = 'SsrfError';
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
exports.SsrfError = SsrfError;
|
|
58
|
+
const BLOCKED_HOSTNAMES = new Set([
|
|
59
|
+
'localhost',
|
|
60
|
+
'ip6-localhost',
|
|
61
|
+
'ip6-loopback',
|
|
62
|
+
]);
|
|
63
|
+
function isBlockedV4(ip) {
|
|
64
|
+
const parts = ip.split('.').map(Number);
|
|
65
|
+
if (parts.length !== 4 || parts.some((o) => !Number.isInteger(o) || o < 0 || o > 255)) {
|
|
66
|
+
return true; // malformed → block
|
|
67
|
+
}
|
|
68
|
+
const [a, b] = parts;
|
|
69
|
+
if (a === 0)
|
|
70
|
+
return true; // 0.0.0.0/8 "this host"
|
|
71
|
+
if (a === 10)
|
|
72
|
+
return true; // 10.0.0.0/8 private
|
|
73
|
+
if (a === 127)
|
|
74
|
+
return true; // 127.0.0.0/8 loopback
|
|
75
|
+
if (a === 169 && b === 254)
|
|
76
|
+
return true; // 169.254.0.0/16 link-local (incl. metadata)
|
|
77
|
+
if (a === 172 && b >= 16 && b <= 31)
|
|
78
|
+
return true; // 172.16.0.0/12 private
|
|
79
|
+
if (a === 192 && b === 168)
|
|
80
|
+
return true; // 192.168.0.0/16 private
|
|
81
|
+
if (a === 192 && b === 0 && parts[2] === 0)
|
|
82
|
+
return true; // 192.0.0.0/24 protocol assignments
|
|
83
|
+
if (a === 100 && b >= 64 && b <= 127)
|
|
84
|
+
return true; // 100.64.0.0/10 CGNAT
|
|
85
|
+
if (a === 198 && (b === 18 || b === 19))
|
|
86
|
+
return true; // 198.18.0.0/15 benchmarking
|
|
87
|
+
if (a >= 224)
|
|
88
|
+
return true; // 224.0.0.0/4 multicast + 240.0.0.0/4 reserved
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
function isBlockedV6(ip) {
|
|
92
|
+
let addr = ip.toLowerCase();
|
|
93
|
+
const zone = addr.indexOf('%');
|
|
94
|
+
if (zone >= 0)
|
|
95
|
+
addr = addr.slice(0, zone);
|
|
96
|
+
if (addr === '::1' || addr === '::')
|
|
97
|
+
return true; // loopback / unspecified
|
|
98
|
+
// IPv4-mapped (::ffff:a.b.c.d) — evaluate the embedded IPv4.
|
|
99
|
+
const mapped = addr.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
|
|
100
|
+
if (mapped)
|
|
101
|
+
return isBlockedV4(mapped[1]);
|
|
102
|
+
if (addr.startsWith('fe8') || addr.startsWith('fe9')
|
|
103
|
+
|| addr.startsWith('fea') || addr.startsWith('feb'))
|
|
104
|
+
return true; // fe80::/10 link-local
|
|
105
|
+
if (addr.startsWith('fc') || addr.startsWith('fd'))
|
|
106
|
+
return true; // fc00::/7 unique-local
|
|
107
|
+
if (addr.startsWith('ff'))
|
|
108
|
+
return true; // ff00::/8 multicast
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
/** True if the literal IP is one we refuse to connect to. Unknown → blocked. */
|
|
112
|
+
function isBlockedIp(ip) {
|
|
113
|
+
const kind = net.isIP(ip);
|
|
114
|
+
if (kind === 4)
|
|
115
|
+
return isBlockedV4(ip);
|
|
116
|
+
if (kind === 6)
|
|
117
|
+
return isBlockedV6(ip);
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Validate a URL for outbound use: http/https only, and its host must not
|
|
122
|
+
* resolve to a blocked address. Returns the parsed URL on success.
|
|
123
|
+
*/
|
|
124
|
+
async function assertUrlAllowed(rawUrl) {
|
|
125
|
+
let url;
|
|
126
|
+
try {
|
|
127
|
+
url = new URL(rawUrl);
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
throw new SsrfError(`Invalid URL: ${rawUrl}`);
|
|
131
|
+
}
|
|
132
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
133
|
+
throw new SsrfError(`Blocked protocol: ${url.protocol} (only http/https allowed)`);
|
|
134
|
+
}
|
|
135
|
+
const host = url.hostname.replace(/^\[|\]$/g, ''); // strip IPv6 brackets
|
|
136
|
+
if (net.isIP(host)) {
|
|
137
|
+
if (isBlockedIp(host))
|
|
138
|
+
throw new SsrfError(`Blocked address: ${host}`);
|
|
139
|
+
return url;
|
|
140
|
+
}
|
|
141
|
+
if (BLOCKED_HOSTNAMES.has(host.toLowerCase())) {
|
|
142
|
+
throw new SsrfError(`Blocked host: ${host}`);
|
|
143
|
+
}
|
|
144
|
+
let addrs;
|
|
145
|
+
try {
|
|
146
|
+
addrs = await dns.promises.lookup(host, { all: true });
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
throw new SsrfError(`Could not resolve host: ${host}`);
|
|
150
|
+
}
|
|
151
|
+
if (addrs.length === 0)
|
|
152
|
+
throw new SsrfError(`Could not resolve host: ${host}`);
|
|
153
|
+
for (const a of addrs) {
|
|
154
|
+
if (isBlockedIp(a.address)) {
|
|
155
|
+
throw new SsrfError(`Host ${host} resolves to blocked address ${a.address}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return url;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* fetch() wrapper that enforces {@link assertUrlAllowed} before the request
|
|
162
|
+
* and on every redirect hop. Redirects are followed manually (redirect:
|
|
163
|
+
* 'manual') so a public URL can't 302 to a private/metadata address without
|
|
164
|
+
* being re-checked.
|
|
165
|
+
*/
|
|
166
|
+
async function safeFetch(rawUrl, init, opts = {}) {
|
|
167
|
+
const maxRedirects = opts.maxRedirects ?? 5;
|
|
168
|
+
let currentUrl = rawUrl;
|
|
169
|
+
let redirects = 0;
|
|
170
|
+
while (true) {
|
|
171
|
+
await assertUrlAllowed(currentUrl);
|
|
172
|
+
const response = await fetch(currentUrl, { ...init, redirect: 'manual' });
|
|
173
|
+
const isRedirect = response.status >= 300 && response.status < 400;
|
|
174
|
+
const location = response.headers.get('location');
|
|
175
|
+
if (isRedirect && location) {
|
|
176
|
+
if (redirects >= maxRedirects)
|
|
177
|
+
throw new SsrfError('Too many redirects');
|
|
178
|
+
redirects++;
|
|
179
|
+
currentUrl = new URL(location, currentUrl).toString();
|
|
180
|
+
continue;
|
|
181
|
+
}
|
|
182
|
+
return response;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
//# sourceMappingURL=ssrf.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssrf.js","sourceRoot":"","sources":["../../src/safety/ssrf.ts"],"names":[],"mappings":"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"}
|