@yeyuan98/opencode-bioresearcher-plugin 1.5.0 → 1.5.2-alpha.2
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 +50 -50
- package/dist/agents/bioresearcher/prompt.d.ts +1 -1
- package/dist/agents/bioresearcher/prompt.js +235 -27
- package/dist/agents/bioresearcherDR/prompt.d.ts +1 -1
- package/dist/agents/bioresearcherDR/prompt.js +8 -8
- package/dist/agents/bioresearcherDR_worker/prompt.d.ts +3 -2
- package/dist/agents/bioresearcherDR_worker/prompt.js +37 -12
- package/dist/index.js +6 -6
- package/dist/shared/tool-restrictions.d.ts +2 -2
- package/dist/shared/tool-restrictions.js +4 -3
- package/dist/skills/bioresearcher-core/SKILL.md +58 -1
- package/dist/skills/bioresearcher-core/patterns/bioresearcher/analysis-methods.md +551 -0
- package/dist/skills/bioresearcher-core/patterns/bioresearcher/best-practices.md +647 -0
- package/dist/skills/bioresearcher-core/patterns/bioresearcher/python-standards.md +944 -0
- package/dist/skills/bioresearcher-core/patterns/bioresearcher/report-template.md +613 -0
- package/dist/skills/bioresearcher-core/patterns/bioresearcher/tool-selection.md +481 -0
- package/dist/skills/bioresearcher-core/patterns/citations.md +234 -0
- package/dist/skills/bioresearcher-core/patterns/rate-limiting.md +167 -0
- package/dist/skills/bioresearcher-tests/README.md +90 -0
- package/dist/skills/bioresearcher-tests/SKILL.md +255 -0
- package/dist/skills/bioresearcher-tests/pyproject.toml +6 -0
- package/dist/skills/bioresearcher-tests/resources/json_samples/in_markdown.md.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/json_samples/nested_object.json.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/json_samples/schema_draft7.json.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/json_samples/simple_array.json.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/json_samples/simple_object.json.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/obo_sample.obo.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/pubmed_sample.xml.gz +0 -0
- package/dist/skills/bioresearcher-tests/resources/table_sample.xlsx.gz +0 -0
- package/dist/skills/bioresearcher-tests/test_cases/json_tests.md +137 -0
- package/dist/skills/bioresearcher-tests/test_cases/misc_tests.md +141 -0
- package/dist/skills/bioresearcher-tests/test_cases/parser_tests.md +80 -0
- package/dist/skills/bioresearcher-tests/test_cases/skill_tests.md +59 -0
- package/dist/skills/bioresearcher-tests/test_cases/table_tests.md +194 -0
- package/dist/skills/bioresearcher-tests/test_runner.py +607 -0
- package/dist/skills/env-jsonc-setup/SKILL.md +206 -206
- package/dist/skills/long-table-summary/SKILL.md +117 -46
- package/dist/skills/long-table-summary/combine_outputs.py +55 -9
- package/dist/skills/long-table-summary/generate_prompts.py +9 -0
- package/dist/skills/pubmed-weekly/pubmed_weekly.py +130 -29
- package/dist/{db-tools → tools/db}/backends/mysql/translator.js +23 -23
- package/dist/{db-tools → tools/db}/tools.js +34 -34
- package/dist/{misc-tools → tools/misc}/json-validate.js +4 -5
- package/dist/tools/sandbox/bash-parser.d.ts +17 -0
- package/dist/tools/sandbox/bash-parser.js +166 -0
- package/dist/tools/sandbox/escape-scenarios.test.d.ts +7 -0
- package/dist/tools/sandbox/escape-scenarios.test.js +182 -0
- package/dist/tools/sandbox/expander.d.ts +30 -0
- package/dist/tools/sandbox/expander.js +57 -0
- package/dist/tools/sandbox/final-verification.test.d.ts +6 -0
- package/dist/tools/sandbox/final-verification.test.js +70 -0
- package/dist/tools/sandbox/hooks.d.ts +25 -0
- package/dist/tools/sandbox/hooks.js +217 -0
- package/dist/tools/sandbox/index.d.ts +19 -0
- package/dist/tools/sandbox/index.js +24 -0
- package/dist/tools/sandbox/manager.d.ts +60 -0
- package/dist/tools/sandbox/manager.js +113 -0
- package/dist/tools/sandbox/sandbox.integration.test.d.ts +7 -0
- package/dist/tools/sandbox/sandbox.integration.test.js +106 -0
- package/dist/tools/sandbox/sandbox.test.d.ts +6 -0
- package/dist/tools/sandbox/sandbox.test.js +160 -0
- package/dist/tools/sandbox/tool.d.ts +66 -0
- package/dist/tools/sandbox/tool.js +163 -0
- package/dist/tools/sandbox/types.d.ts +38 -0
- package/dist/tools/sandbox/types.js +6 -0
- package/dist/tools/sandbox/validator.d.ts +33 -0
- package/dist/tools/sandbox/validator.js +150 -0
- package/dist/{skill-tools → tools/skill}/registry.js +1 -2
- package/dist/{table-tools → tools/table}/utils.js +4 -4
- package/package.json +1 -1
- package/dist/db-tools/executor.d.ts +0 -13
- package/dist/db-tools/executor.js +0 -54
- package/dist/db-tools/pool.d.ts +0 -8
- package/dist/db-tools/pool.js +0 -49
- package/dist/db-tools/tools/index.d.ts +0 -27
- package/dist/db-tools/tools/index.js +0 -191
- package/dist/db-tools/types.d.ts +0 -94
- package/dist/db-tools/types.js +0 -40
- package/dist/misc-tools/json-tools.d.ts +0 -33
- package/dist/misc-tools/json-tools.js +0 -187
- package/dist/skill/frontmatter.d.ts +0 -2
- package/dist/skill/frontmatter.js +0 -65
- package/dist/skill/index.d.ts +0 -3
- package/dist/skill/index.js +0 -2
- package/dist/skill/registry.d.ts +0 -11
- package/dist/skill/registry.js +0 -64
- package/dist/skill/tool.d.ts +0 -9
- package/dist/skill/tool.js +0 -115
- package/dist/skill/types.d.ts +0 -22
- package/dist/skill/types.js +0 -7
- /package/dist/{db-tools → tools/db}/backends/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/index.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/backend.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/backend.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/connection.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/connection.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/index.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/translator.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mongodb/translator.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/backend.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/backend.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/connection.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/connection.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/index.js +0 -0
- /package/dist/{db-tools → tools/db}/backends/mysql/translator.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/base.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/base.js +0 -0
- /package/dist/{db-tools → tools/db}/core/config-loader.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/config-loader.js +0 -0
- /package/dist/{db-tools → tools/db}/core/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/index.js +0 -0
- /package/dist/{db-tools → tools/db}/core/jsonc-parser.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/jsonc-parser.js +0 -0
- /package/dist/{db-tools → tools/db}/core/validator.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/core/validator.js +0 -0
- /package/dist/{db-tools → tools/db}/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/index.js +0 -0
- /package/dist/{db-tools → tools/db}/interface/backend.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/interface/backend.js +0 -0
- /package/dist/{db-tools → tools/db}/interface/connection.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/interface/connection.js +0 -0
- /package/dist/{db-tools → tools/db}/interface/index.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/interface/index.js +0 -0
- /package/dist/{db-tools → tools/db}/interface/query.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/interface/query.js +0 -0
- /package/dist/{db-tools → tools/db}/interface/schema.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/interface/schema.js +0 -0
- /package/dist/{db-tools → tools/db}/tools.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/utils.d.ts +0 -0
- /package/dist/{db-tools → tools/db}/utils.js +0 -0
- /package/dist/{misc-tools → tools/misc}/calculator.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/calculator.js +0 -0
- /package/dist/{misc-tools → tools/misc}/index.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/index.js +0 -0
- /package/dist/{misc-tools → tools/misc}/json-extract.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/json-extract.js +0 -0
- /package/dist/{misc-tools → tools/misc}/json-infer.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/json-infer.js +0 -0
- /package/dist/{misc-tools → tools/misc}/json-validate.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/timer.d.ts +0 -0
- /package/dist/{misc-tools → tools/misc}/timer.js +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/index.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/index.js +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/obo.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/obo.js +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/types.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/types.js +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/utils.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/obo/utils.js +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/index.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/index.js +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/pubmed.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/pubmed.js +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/types.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/types.js +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/utils.d.ts +0 -0
- /package/dist/{parser-tools → tools/parser}/pubmed/utils.js +0 -0
- /package/dist/{skill-tools → tools/skill}/frontmatter.d.ts +0 -0
- /package/dist/{skill-tools → tools/skill}/frontmatter.js +0 -0
- /package/dist/{skill-tools → tools/skill}/index.d.ts +0 -0
- /package/dist/{skill-tools → tools/skill}/index.js +0 -0
- /package/dist/{skill-tools → tools/skill}/registry.d.ts +0 -0
- /package/dist/{skill-tools → tools/skill}/tool.d.ts +0 -0
- /package/dist/{skill-tools → tools/skill}/tool.js +0 -0
- /package/dist/{skill-tools → tools/skill}/types.d.ts +0 -0
- /package/dist/{skill-tools → tools/skill}/types.js +0 -0
- /package/dist/{table-tools → tools/table}/index.d.ts +0 -0
- /package/dist/{table-tools → tools/table}/index.js +0 -0
- /package/dist/{table-tools → tools/table}/tools.d.ts +0 -0
- /package/dist/{table-tools → tools/table}/tools.js +0 -0
- /package/dist/{table-tools → tools/table}/utils.d.ts +0 -0
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Real-World Escape Scenario Tests
|
|
3
|
+
*
|
|
4
|
+
* Tests the actual escape vectors discovered during security analysis
|
|
5
|
+
* to verify they are properly blocked by the sandbox.
|
|
6
|
+
*/
|
|
7
|
+
import { describe, test, expect, beforeEach, afterEach } from 'bun:test';
|
|
8
|
+
import { mkdtemp, rm, mkdir } from 'fs/promises';
|
|
9
|
+
import { tmpdir } from 'os';
|
|
10
|
+
import { join } from 'path';
|
|
11
|
+
import { sandboxManager } from './manager';
|
|
12
|
+
import { extractAllPathArgs } from './bash-parser';
|
|
13
|
+
import { hasCommandSubstitution } from './expander';
|
|
14
|
+
describe('Real-World Escape Scenarios', () => {
|
|
15
|
+
let sandboxDir;
|
|
16
|
+
let projectDir;
|
|
17
|
+
let sessionID;
|
|
18
|
+
beforeEach(async () => {
|
|
19
|
+
projectDir = await mkdtemp(join(tmpdir(), 'escape-test-'));
|
|
20
|
+
sandboxDir = join(projectDir, '.sandbox', 'escape-session');
|
|
21
|
+
await mkdir(sandboxDir, { recursive: true });
|
|
22
|
+
sessionID = 'escape-test-session';
|
|
23
|
+
sandboxManager.enable(sessionID, sandboxDir, projectDir, 'escape-test');
|
|
24
|
+
});
|
|
25
|
+
afterEach(async () => {
|
|
26
|
+
sandboxManager.disable(sessionID);
|
|
27
|
+
await rm(projectDir, { recursive: true, force: true });
|
|
28
|
+
});
|
|
29
|
+
describe('Scenario 1: ls .. Escape', () => {
|
|
30
|
+
test('should extract .. from ls .. command', () => {
|
|
31
|
+
const paths = extractAllPathArgs('ls ..');
|
|
32
|
+
expect(paths).toContain('..');
|
|
33
|
+
});
|
|
34
|
+
test('should reject .. path via manager', () => {
|
|
35
|
+
const result = sandboxManager.validatePath(sessionID, '..');
|
|
36
|
+
expect(result.allowed).toBe(false);
|
|
37
|
+
expect(result.error).toContain('PATH ESCAPE DETECTED');
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
describe('Scenario 2: Python Script Escape', () => {
|
|
41
|
+
test('should extract script path from python command', () => {
|
|
42
|
+
const paths = extractAllPathArgs('python script.py');
|
|
43
|
+
expect(paths).toContain('script.py');
|
|
44
|
+
});
|
|
45
|
+
test('should validate script path', () => {
|
|
46
|
+
const result = sandboxManager.validatePath(sessionID, 'script.py');
|
|
47
|
+
expect(result.allowed).toBe(true);
|
|
48
|
+
expect(result.transformedPath).toBe(join(sandboxDir, 'script.py'));
|
|
49
|
+
});
|
|
50
|
+
test('should reject script path outside sandbox', () => {
|
|
51
|
+
const result = sandboxManager.validatePath(sessionID, '../malicious.py');
|
|
52
|
+
expect(result.allowed).toBe(false);
|
|
53
|
+
expect(result.error).toContain('PATH ESCAPE DETECTED');
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('Scenario 3: Command Substitution', () => {
|
|
57
|
+
test('should detect $(pwd) command substitution', () => {
|
|
58
|
+
expect(hasCommandSubstitution('$(pwd)/../file.txt')).toBe(true);
|
|
59
|
+
});
|
|
60
|
+
test('should detect backtick command substitution', () => {
|
|
61
|
+
expect(hasCommandSubstitution('`pwd`/../file.txt')).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
test('should allow normal variable expansion', () => {
|
|
64
|
+
expect(hasCommandSubstitution('$HOME/file.txt')).toBe(false);
|
|
65
|
+
});
|
|
66
|
+
test('should extract paths with variables', () => {
|
|
67
|
+
const paths = extractAllPathArgs('cat $HOME/file.txt');
|
|
68
|
+
expect(paths).toContain('$HOME/file.txt');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('Scenario 4: Path Traversal Variations', () => {
|
|
72
|
+
test('should block ../../../etc/passwd', () => {
|
|
73
|
+
const result = sandboxManager.validatePath(sessionID, '../../../etc/passwd');
|
|
74
|
+
expect(result.allowed).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
test('should block .. with complex path', () => {
|
|
77
|
+
const result = sandboxManager.validatePath(sessionID, './data/../../outside.txt');
|
|
78
|
+
expect(result.allowed).toBe(false);
|
|
79
|
+
});
|
|
80
|
+
test('should block multiple .. at start', () => {
|
|
81
|
+
const result = sandboxManager.validatePath(sessionID, '../../../../file.txt');
|
|
82
|
+
expect(result.allowed).toBe(false);
|
|
83
|
+
});
|
|
84
|
+
test('should allow .. that stays in sandbox', () => {
|
|
85
|
+
// Create subdirectory structure
|
|
86
|
+
const result = sandboxManager.validatePath(sessionID, 'subdir/../file.txt');
|
|
87
|
+
expect(result.allowed).toBe(true);
|
|
88
|
+
expect(result.transformedPath).toBe(join(sandboxDir, 'file.txt'));
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
describe('Scenario 5: Absolute Path Injection', () => {
|
|
92
|
+
test('should block Unix absolute paths', () => {
|
|
93
|
+
const result = sandboxManager.validatePath(sessionID, '/etc/passwd');
|
|
94
|
+
expect(result.allowed).toBe(false);
|
|
95
|
+
expect(result.error).toContain('ABSOLUTE PATH REJECTED');
|
|
96
|
+
});
|
|
97
|
+
test('should block Windows absolute paths', () => {
|
|
98
|
+
const result = sandboxManager.validatePath(sessionID, 'C:\\Windows\\System32');
|
|
99
|
+
expect(result.allowed).toBe(false);
|
|
100
|
+
expect(result.error).toContain('ABSOLUTE PATH REJECTED');
|
|
101
|
+
});
|
|
102
|
+
test('should block UNC paths on Windows', () => {
|
|
103
|
+
const result = sandboxManager.validatePath(sessionID, '\\\\server\\share\\file.txt');
|
|
104
|
+
expect(result.allowed).toBe(false);
|
|
105
|
+
if (process.platform === 'win32') {
|
|
106
|
+
expect(result.error).toContain('UNC PATH REJECTED');
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
describe('Scenario 6: Multiple Paths in One Command', () => {
|
|
111
|
+
test('should extract all paths from cp command', () => {
|
|
112
|
+
const paths = extractAllPathArgs('cp source.txt dest.txt');
|
|
113
|
+
expect(paths).toContain('source.txt');
|
|
114
|
+
expect(paths).toContain('dest.txt');
|
|
115
|
+
});
|
|
116
|
+
test('should validate all paths and reject if any escape', () => {
|
|
117
|
+
const result = sandboxManager.validatePaths(sessionID, [
|
|
118
|
+
'./safe.txt',
|
|
119
|
+
'../escape.txt',
|
|
120
|
+
'./another-safe.txt'
|
|
121
|
+
]);
|
|
122
|
+
expect(result.allowed).toBe(false);
|
|
123
|
+
expect(result.firstError).toContain('PATH ESCAPE DETECTED');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe('Scenario 7: Redirection Targets', () => {
|
|
127
|
+
test('should extract redirection targets', () => {
|
|
128
|
+
const paths = extractAllPathArgs('cat file.txt > output.txt');
|
|
129
|
+
expect(paths).toContain('output.txt');
|
|
130
|
+
});
|
|
131
|
+
test('should extract input redirection', () => {
|
|
132
|
+
const paths = extractAllPathArgs('cat < input.txt');
|
|
133
|
+
expect(paths).toContain('input.txt');
|
|
134
|
+
});
|
|
135
|
+
test('should extract append redirection', () => {
|
|
136
|
+
const paths = extractAllPathArgs('echo test >> log.txt');
|
|
137
|
+
expect(paths).toContain('log.txt');
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe('Scenario 8: Complex Bash Commands', () => {
|
|
141
|
+
test('should extract paths from piped commands', () => {
|
|
142
|
+
const paths = extractAllPathArgs('cat file.txt | grep pattern');
|
|
143
|
+
expect(paths).toContain('file.txt');
|
|
144
|
+
});
|
|
145
|
+
test('should handle commands with multiple flags', () => {
|
|
146
|
+
const paths = extractAllPathArgs('ls -la -h ./data');
|
|
147
|
+
expect(paths).toContain('./data');
|
|
148
|
+
});
|
|
149
|
+
test('should extract paths with extensions', () => {
|
|
150
|
+
const paths = extractAllPathArgs('python script.py');
|
|
151
|
+
expect(paths).toContain('script.py');
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
describe('Fail-Closed Behavior', () => {
|
|
155
|
+
test('should reject all invalid paths', () => {
|
|
156
|
+
const invalidPaths = [
|
|
157
|
+
'../..',
|
|
158
|
+
'/etc/passwd',
|
|
159
|
+
'../../etc/passwd',
|
|
160
|
+
'./../..',
|
|
161
|
+
'./../../../file.txt'
|
|
162
|
+
];
|
|
163
|
+
for (const invalidPath of invalidPaths) {
|
|
164
|
+
const result = sandboxManager.validatePath(sessionID, invalidPath);
|
|
165
|
+
expect(result.allowed).toBe(false);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
test('should allow all valid paths', () => {
|
|
169
|
+
const validPaths = [
|
|
170
|
+
'./file.txt',
|
|
171
|
+
'data/file.txt',
|
|
172
|
+
'./a/b/c/file.txt',
|
|
173
|
+
'file.txt',
|
|
174
|
+
'.'
|
|
175
|
+
];
|
|
176
|
+
for (const validPath of validPaths) {
|
|
177
|
+
const result = sandboxManager.validatePath(sessionID, validPath);
|
|
178
|
+
expect(result.allowed).toBe(true);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Expansion for Sandbox Validation
|
|
3
|
+
*
|
|
4
|
+
* Expands shell variables and command substitutions in path arguments
|
|
5
|
+
* to validate the actual paths that will be accessed at runtime.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY: This function FAILS CLOSED - it throws errors rather than
|
|
8
|
+
* returning null to prevent bypassing validation.
|
|
9
|
+
*/
|
|
10
|
+
import type { PluginInput } from '@opencode-ai/plugin';
|
|
11
|
+
type BunShell = PluginInput['$'];
|
|
12
|
+
/**
|
|
13
|
+
* Expand and resolve a path argument that may contain variables.
|
|
14
|
+
* Returns the absolute path after shell expansion.
|
|
15
|
+
*
|
|
16
|
+
* SECURITY: Throws error on any failure to prevent validation bypass.
|
|
17
|
+
*
|
|
18
|
+
* @param pathArg - Path argument to expand (may contain variables, ~, etc.)
|
|
19
|
+
* @param cwd - Current working directory (should be sandbox path)
|
|
20
|
+
* @param shell - BunShell instance for command execution
|
|
21
|
+
* @returns Absolute path after expansion
|
|
22
|
+
* @throws Error if expansion fails or path doesn't exist
|
|
23
|
+
*/
|
|
24
|
+
export declare function expandPath(pathArg: string, cwd: string, shell: BunShell): Promise<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Check if a path argument contains command substitution.
|
|
27
|
+
* Command substitution can execute arbitrary code during expansion.
|
|
28
|
+
*/
|
|
29
|
+
export declare function hasCommandSubstitution(pathArg: string): boolean;
|
|
30
|
+
export {};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Path Expansion for Sandbox Validation
|
|
3
|
+
*
|
|
4
|
+
* Expands shell variables and command substitutions in path arguments
|
|
5
|
+
* to validate the actual paths that will be accessed at runtime.
|
|
6
|
+
*
|
|
7
|
+
* SECURITY: This function FAILS CLOSED - it throws errors rather than
|
|
8
|
+
* returning null to prevent bypassing validation.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Expand and resolve a path argument that may contain variables.
|
|
12
|
+
* Returns the absolute path after shell expansion.
|
|
13
|
+
*
|
|
14
|
+
* SECURITY: Throws error on any failure to prevent validation bypass.
|
|
15
|
+
*
|
|
16
|
+
* @param pathArg - Path argument to expand (may contain variables, ~, etc.)
|
|
17
|
+
* @param cwd - Current working directory (should be sandbox path)
|
|
18
|
+
* @param shell - BunShell instance for command execution
|
|
19
|
+
* @returns Absolute path after expansion
|
|
20
|
+
* @throws Error if expansion fails or path doesn't exist
|
|
21
|
+
*/
|
|
22
|
+
export async function expandPath(pathArg, cwd, shell) {
|
|
23
|
+
let result;
|
|
24
|
+
try {
|
|
25
|
+
// Execute realpath to expand variables, ~, and resolve path
|
|
26
|
+
// SECURITY: Do NOT use .nothrow() - let errors propagate
|
|
27
|
+
result = await shell `realpath ${pathArg}`
|
|
28
|
+
.cwd(cwd)
|
|
29
|
+
.quiet()
|
|
30
|
+
.text()
|
|
31
|
+
.then((x) => x.trim());
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
// Fail closed: throw descriptive error instead of returning null
|
|
35
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
36
|
+
throw new Error(`PATH EXPANSION FAILED: "${pathArg}"\n` +
|
|
37
|
+
`Working directory: "${cwd}"\n` +
|
|
38
|
+
`Error: ${errorMsg}\n` +
|
|
39
|
+
`This may indicate the path does not exist or contains invalid characters.`);
|
|
40
|
+
}
|
|
41
|
+
// Validate we got a non-empty result
|
|
42
|
+
if (!result || result.length === 0) {
|
|
43
|
+
throw new Error(`PATH EXPANSION RETURNED EMPTY: "${pathArg}"\n` +
|
|
44
|
+
`Working directory: "${cwd}"\n` +
|
|
45
|
+
`realpath returned empty result, which indicates a validation failure.`);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Check if a path argument contains command substitution.
|
|
51
|
+
* Command substitution can execute arbitrary code during expansion.
|
|
52
|
+
*/
|
|
53
|
+
export function hasCommandSubstitution(pathArg) {
|
|
54
|
+
// Check for command substitution: $(cmd) or `cmd`
|
|
55
|
+
// Also check for partial matches like $(cmd or `cmd (if space breaks the pattern)
|
|
56
|
+
return /\$\(/.test(pathArg) || /`/.test(pathArg);
|
|
57
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Final Verification Test for Sandbox Security Fixes
|
|
3
|
+
*
|
|
4
|
+
* This test verifies that all discovered escape vectors are properly blocked.
|
|
5
|
+
*/
|
|
6
|
+
import { test, expect, describe } from 'bun:test';
|
|
7
|
+
import { validateAndTransformPath, isPathWithinSandbox } from './validator';
|
|
8
|
+
import { extractAllPathArgs } from './bash-parser';
|
|
9
|
+
import { hasCommandSubstitution } from './expander';
|
|
10
|
+
import { tmpdir } from 'os';
|
|
11
|
+
import { join } from 'path';
|
|
12
|
+
describe('Final Verification - All Escape Vectors Blocked', () => {
|
|
13
|
+
const sandboxDir = join(tmpdir(), 'final-verify-sandbox');
|
|
14
|
+
test('Escape Vector 1: ls .. is blocked', () => {
|
|
15
|
+
// Extract path from command
|
|
16
|
+
const paths = extractAllPathArgs('ls ..');
|
|
17
|
+
expect(paths).toContain('..');
|
|
18
|
+
// Validate the path
|
|
19
|
+
const result = validateAndTransformPath('..', sandboxDir);
|
|
20
|
+
expect(result.allowed).toBe(false);
|
|
21
|
+
expect(result.error).toContain('PATH ESCAPE DETECTED');
|
|
22
|
+
});
|
|
23
|
+
test('Escape Vector 2: ../../etc/passwd is blocked', () => {
|
|
24
|
+
const result = validateAndTransformPath('../../etc/passwd', sandboxDir);
|
|
25
|
+
expect(result.allowed).toBe(false);
|
|
26
|
+
expect(result.error).toContain('PATH ESCAPE DETECTED');
|
|
27
|
+
});
|
|
28
|
+
test('Escape Vector 3: Command substitution is detected', () => {
|
|
29
|
+
expect(hasCommandSubstitution('$(pwd)/../file.txt')).toBe(true);
|
|
30
|
+
expect(hasCommandSubstitution('`pwd`/../file.txt')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
test('Escape Vector 4: Absolute paths are blocked', () => {
|
|
33
|
+
const result = validateAndTransformPath('/etc/passwd', sandboxDir);
|
|
34
|
+
expect(result.allowed).toBe(false);
|
|
35
|
+
expect(result.error).toContain('ABSOLUTE PATH REJECTED');
|
|
36
|
+
});
|
|
37
|
+
test('Valid paths are still allowed', () => {
|
|
38
|
+
const result = validateAndTransformPath('./data/file.txt', sandboxDir);
|
|
39
|
+
expect(result.allowed).toBe(true);
|
|
40
|
+
expect(result.transformedPath).toBe(join(sandboxDir, 'data', 'file.txt'));
|
|
41
|
+
});
|
|
42
|
+
test('Multiple paths are extracted from commands', () => {
|
|
43
|
+
const paths = extractAllPathArgs('cp source.txt dest.txt');
|
|
44
|
+
expect(paths).toContain('source.txt');
|
|
45
|
+
expect(paths).toContain('dest.txt');
|
|
46
|
+
expect(paths.length).toBe(2);
|
|
47
|
+
});
|
|
48
|
+
test('Path containment checks work correctly', () => {
|
|
49
|
+
expect(isPathWithinSandbox(sandboxDir, sandboxDir)).toBe(true);
|
|
50
|
+
expect(isPathWithinSandbox(join(sandboxDir, 'file.txt'), sandboxDir)).toBe(true);
|
|
51
|
+
expect(isPathWithinSandbox(join(sandboxDir, '..', 'outside'), sandboxDir)).toBe(false);
|
|
52
|
+
});
|
|
53
|
+
test('Complex escape attempts are all blocked', () => {
|
|
54
|
+
const escapeAttempts = [
|
|
55
|
+
'..',
|
|
56
|
+
'../',
|
|
57
|
+
'../..',
|
|
58
|
+
'../../..',
|
|
59
|
+
'../../../etc/passwd',
|
|
60
|
+
'./../..',
|
|
61
|
+
'./../../../file.txt',
|
|
62
|
+
'/etc/passwd',
|
|
63
|
+
'C:\\Windows\\System32',
|
|
64
|
+
];
|
|
65
|
+
for (const attempt of escapeAttempts) {
|
|
66
|
+
const result = validateAndTransformPath(attempt, sandboxDir);
|
|
67
|
+
expect(result.allowed).toBe(false);
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Plugin Hooks
|
|
3
|
+
*
|
|
4
|
+
* Implements plugin hooks for sandbox enforcement:
|
|
5
|
+
* - tool.execute.before: Path validation and transformation
|
|
6
|
+
* - shell.env: Environment variable injection
|
|
7
|
+
* - permission.ask: Auto-deny paths outside sandbox
|
|
8
|
+
*/
|
|
9
|
+
import type { Hooks, PluginInput } from '@opencode-ai/plugin';
|
|
10
|
+
import type { SandboxManagerImpl } from './manager';
|
|
11
|
+
type BunShell = PluginInput['$'];
|
|
12
|
+
/**
|
|
13
|
+
* Create sandbox enforcement hooks for the plugin system.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createSandboxHooks(manager: SandboxManagerImpl, shell: BunShell): Partial<Hooks>;
|
|
16
|
+
/**
|
|
17
|
+
* Get the tool path args mapping (for testing or extension).
|
|
18
|
+
*/
|
|
19
|
+
export declare function getToolPathArgs(): Record<string, string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Register additional tool path arguments.
|
|
22
|
+
* Useful for plugins that add new tools.
|
|
23
|
+
*/
|
|
24
|
+
export declare function registerToolPathArgs(toolName: string, pathArgs: string[]): void;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Plugin Hooks
|
|
3
|
+
*
|
|
4
|
+
* Implements plugin hooks for sandbox enforcement:
|
|
5
|
+
* - tool.execute.before: Path validation and transformation
|
|
6
|
+
* - shell.env: Environment variable injection
|
|
7
|
+
* - permission.ask: Auto-deny paths outside sandbox
|
|
8
|
+
*/
|
|
9
|
+
import { extractAllPathArgs } from './bash-parser';
|
|
10
|
+
import { expandPath, hasCommandSubstitution } from './expander';
|
|
11
|
+
import { isPathWithinSandbox } from './validator';
|
|
12
|
+
/**
|
|
13
|
+
* Maps tool names to their path-containing argument names.
|
|
14
|
+
* These arguments will be validated and transformed when sandbox is active.
|
|
15
|
+
*/
|
|
16
|
+
const TOOL_PATH_ARGS = {
|
|
17
|
+
// Built-in file tools
|
|
18
|
+
'read': ['filePath'],
|
|
19
|
+
'write': ['filePath'],
|
|
20
|
+
'edit': ['filePath'],
|
|
21
|
+
'glob': ['path'],
|
|
22
|
+
'grep': ['path'],
|
|
23
|
+
// Table tools
|
|
24
|
+
'tableGetSheetPreview': ['file_path'],
|
|
25
|
+
'tableListSheets': ['file_path'],
|
|
26
|
+
'tableGetHeaders': ['file_path'],
|
|
27
|
+
'tableGetCell': ['file_path'],
|
|
28
|
+
'tableFilterRows': ['file_path'],
|
|
29
|
+
'tableSearch': ['file_path'],
|
|
30
|
+
'tableGetRange': ['file_path'],
|
|
31
|
+
'tableSummarize': ['file_path'],
|
|
32
|
+
'tableGroupBy': ['file_path'],
|
|
33
|
+
'tablePivotSummary': ['file_path'],
|
|
34
|
+
'tableAppendRows': ['file_path'],
|
|
35
|
+
'tableUpdateCell': ['file_path'],
|
|
36
|
+
'tableCreateFile': ['file_path'],
|
|
37
|
+
// Parser tools
|
|
38
|
+
'parse_pubmed_articleSet': ['filePath', 'outputDir'],
|
|
39
|
+
'parse_obo_file': ['filePath', 'outputDir'],
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Get nested property from object using dot notation.
|
|
43
|
+
* Example: getNestedValue({a: {b: 1}}, 'a.b') => 1
|
|
44
|
+
*/
|
|
45
|
+
function getNestedValue(obj, path) {
|
|
46
|
+
return path.split('.').reduce((current, key) => {
|
|
47
|
+
if (current === null || current === undefined)
|
|
48
|
+
return undefined;
|
|
49
|
+
return current[key];
|
|
50
|
+
}, obj);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Set nested property in object using dot notation.
|
|
54
|
+
*/
|
|
55
|
+
function setNestedValue(obj, path, value) {
|
|
56
|
+
const keys = path.split('.');
|
|
57
|
+
const lastKey = keys.pop();
|
|
58
|
+
const target = keys.reduce((current, key) => {
|
|
59
|
+
if (current[key] === undefined) {
|
|
60
|
+
current[key] = {};
|
|
61
|
+
}
|
|
62
|
+
return current[key];
|
|
63
|
+
}, obj);
|
|
64
|
+
target[lastKey] = value;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Create sandbox enforcement hooks for the plugin system.
|
|
68
|
+
*/
|
|
69
|
+
export function createSandboxHooks(manager, shell) {
|
|
70
|
+
return {
|
|
71
|
+
/**
|
|
72
|
+
* Hook: tool.execute.before
|
|
73
|
+
*
|
|
74
|
+
* Validates and transforms paths before tool execution.
|
|
75
|
+
* For bash commands, expands variables and validates resolved paths.
|
|
76
|
+
*/
|
|
77
|
+
async "tool.execute.before"(input, output) {
|
|
78
|
+
const sandbox = manager.get(input.sessionID);
|
|
79
|
+
if (!sandbox) {
|
|
80
|
+
// No sandbox active - allow tool to proceed normally
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const toolName = input.tool;
|
|
84
|
+
// Special handling for bash commands
|
|
85
|
+
if (toolName === 'bash') {
|
|
86
|
+
const command = output.args.command;
|
|
87
|
+
// Extract ALL arguments from file operations (including variables)
|
|
88
|
+
const pathArgs = extractAllPathArgs(command);
|
|
89
|
+
// Expand and validate each argument
|
|
90
|
+
for (const arg of pathArgs) {
|
|
91
|
+
// Block command substitution in path arguments
|
|
92
|
+
if (hasCommandSubstitution(arg)) {
|
|
93
|
+
throw new Error(`COMMAND SUBSTITUTION NOT ALLOWED IN PATH ARGUMENTS\n` +
|
|
94
|
+
`Argument: ${arg}\n` +
|
|
95
|
+
`Command substitution can execute arbitrary code and is not allowed in file paths.`);
|
|
96
|
+
}
|
|
97
|
+
// Expand the argument (resolves variables, tilde, etc.)
|
|
98
|
+
// SECURITY: expandPath throws on failure - this is fail-closed behavior
|
|
99
|
+
let expanded;
|
|
100
|
+
try {
|
|
101
|
+
expanded = await expandPath(arg, sandbox.sandboxPath, shell);
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// Fail closed: any expansion error prevents command execution
|
|
105
|
+
throw new Error(`PATH EXPANSION FAILED - COMMAND BLOCKED\n` +
|
|
106
|
+
`Argument: ${arg}\n` +
|
|
107
|
+
`Sandbox boundary: ${sandbox.sandboxPath}\n` +
|
|
108
|
+
`Error: ${error instanceof Error ? error.message : String(error)}\n` +
|
|
109
|
+
`All paths must be valid and expandable within the sandbox.`);
|
|
110
|
+
}
|
|
111
|
+
// Validate the EXPANDED absolute path
|
|
112
|
+
if (!isPathWithinSandbox(expanded, sandbox.sandboxPath)) {
|
|
113
|
+
throw new Error(`PATH ESCAPES SANDBOX AFTER EXPANSION\n` +
|
|
114
|
+
`Argument: ${arg}\n` +
|
|
115
|
+
`Expands to: ${expanded}\n` +
|
|
116
|
+
`Sandbox boundary: ${sandbox.sandboxPath}\n` +
|
|
117
|
+
`All file access must remain within the sandbox folder.`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Set workdir to sandbox path
|
|
121
|
+
// This is shell-agnostic (works with bash, CMD.exe, PowerShell, zsh)
|
|
122
|
+
// The bash tool passes workdir to spawn() which sets cwd natively
|
|
123
|
+
output.args.workdir = sandbox.sandboxPath;
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Handle other tools with path arguments
|
|
127
|
+
const pathArgs = TOOL_PATH_ARGS[toolName];
|
|
128
|
+
if (!pathArgs) {
|
|
129
|
+
// Tool not in mapping - no path transformation needed
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
// Process each path argument
|
|
133
|
+
for (const argPath of pathArgs) {
|
|
134
|
+
const value = getNestedValue(output.args, argPath);
|
|
135
|
+
if (typeof value === 'string' && value.length > 0) {
|
|
136
|
+
const result = manager.validatePath(input.sessionID, value);
|
|
137
|
+
if (!result.allowed) {
|
|
138
|
+
throw new Error(result.error || 'Sandbox path validation failed');
|
|
139
|
+
}
|
|
140
|
+
// Transform the path to sandbox-resolved path
|
|
141
|
+
setNestedValue(output.args, argPath, result.transformedPath);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
/**
|
|
146
|
+
* Hook: shell.env
|
|
147
|
+
*
|
|
148
|
+
* Injects environment variables to indicate sandbox is active.
|
|
149
|
+
*/
|
|
150
|
+
async "shell.env"(input, output) {
|
|
151
|
+
// input.sessionID may be undefined in some contexts
|
|
152
|
+
const sessionID = input.sessionID;
|
|
153
|
+
if (!sessionID)
|
|
154
|
+
return;
|
|
155
|
+
const sandbox = manager.get(sessionID);
|
|
156
|
+
if (!sandbox)
|
|
157
|
+
return;
|
|
158
|
+
// Set environment variables for sandbox awareness
|
|
159
|
+
output.env = {
|
|
160
|
+
...output.env,
|
|
161
|
+
OPENCODE_SANDBOX: '1',
|
|
162
|
+
OPENCODE_SANDBOX_PATH: sandbox.sandboxPath,
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
/**
|
|
166
|
+
* Hook: permission.ask
|
|
167
|
+
*
|
|
168
|
+
* Auto-deny file permissions for paths outside sandbox.
|
|
169
|
+
* Auto-allow paths within sandbox.
|
|
170
|
+
*/
|
|
171
|
+
async "permission.ask"(input, output) {
|
|
172
|
+
const sessionID = input.sessionID;
|
|
173
|
+
if (!sessionID)
|
|
174
|
+
return;
|
|
175
|
+
const sandbox = manager.get(sessionID);
|
|
176
|
+
if (!sandbox)
|
|
177
|
+
return;
|
|
178
|
+
// File-related permissions that should be sandboxed
|
|
179
|
+
const filePermissions = ['read', 'edit', 'glob', 'grep', 'list', 'bash'];
|
|
180
|
+
if (!filePermissions.includes(input.type)) {
|
|
181
|
+
// Non-file permission - let normal flow handle it
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
// Get patterns to check (pattern can be string or string[])
|
|
185
|
+
const pattern = input.pattern;
|
|
186
|
+
if (!pattern)
|
|
187
|
+
return;
|
|
188
|
+
const patterns = Array.isArray(pattern) ? pattern : [pattern];
|
|
189
|
+
// Validate each pattern
|
|
190
|
+
for (const p of patterns) {
|
|
191
|
+
if (typeof p !== 'string')
|
|
192
|
+
continue;
|
|
193
|
+
const result = manager.validatePath(sessionID, p);
|
|
194
|
+
if (!result.allowed) {
|
|
195
|
+
// Path outside sandbox - deny
|
|
196
|
+
output.status = 'deny';
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
// All patterns within sandbox - allow
|
|
201
|
+
output.status = 'allow';
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Get the tool path args mapping (for testing or extension).
|
|
207
|
+
*/
|
|
208
|
+
export function getToolPathArgs() {
|
|
209
|
+
return { ...TOOL_PATH_ARGS };
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Register additional tool path arguments.
|
|
213
|
+
* Useful for plugins that add new tools.
|
|
214
|
+
*/
|
|
215
|
+
export function registerToolPathArgs(toolName, pathArgs) {
|
|
216
|
+
TOOL_PATH_ARGS[toolName] = pathArgs;
|
|
217
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Tool Module
|
|
3
|
+
*
|
|
4
|
+
* Provides invisible file sandboxing for safe file operations.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* 1. Agent calls sandbox_enable({ sessionName: "my-session" })
|
|
8
|
+
* 2. Sandbox folder is created at .sandbox/<session-name>_<timestamp>
|
|
9
|
+
* 3. All file operations use relative paths, which are transparently redirected
|
|
10
|
+
* 4. Absolute paths are rejected
|
|
11
|
+
* 5. Path traversal attempts (../) that escape sandbox are blocked
|
|
12
|
+
*/
|
|
13
|
+
export { sandboxTools, sandboxEnableTool, sandboxDisableTool, sandboxStatusTool } from './tool';
|
|
14
|
+
export { sandboxManager, SandboxManagerImpl } from './manager';
|
|
15
|
+
export { createSandboxHooks, getToolPathArgs, registerToolPathArgs } from './hooks';
|
|
16
|
+
export type { SandboxState, PathValidationResult, BashPathExtractionResult, ToolPathArgsMap } from './types';
|
|
17
|
+
export { isAbsolutePath, isPathWithinSandbox, validateAndTransformPath, looksLikePath } from './validator';
|
|
18
|
+
export { extractPathsFromBashCommand, extractAllPathArgs } from './bash-parser';
|
|
19
|
+
export { expandPath, hasCommandSubstitution } from './expander';
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Tool Module
|
|
3
|
+
*
|
|
4
|
+
* Provides invisible file sandboxing for safe file operations.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* 1. Agent calls sandbox_enable({ sessionName: "my-session" })
|
|
8
|
+
* 2. Sandbox folder is created at .sandbox/<session-name>_<timestamp>
|
|
9
|
+
* 3. All file operations use relative paths, which are transparently redirected
|
|
10
|
+
* 4. Absolute paths are rejected
|
|
11
|
+
* 5. Path traversal attempts (../) that escape sandbox are blocked
|
|
12
|
+
*/
|
|
13
|
+
// Tools
|
|
14
|
+
export { sandboxTools, sandboxEnableTool, sandboxDisableTool, sandboxStatusTool } from './tool';
|
|
15
|
+
// Manager
|
|
16
|
+
export { sandboxManager, SandboxManagerImpl } from './manager';
|
|
17
|
+
// Hooks
|
|
18
|
+
export { createSandboxHooks, getToolPathArgs, registerToolPathArgs } from './hooks';
|
|
19
|
+
// Validator utilities (for testing)
|
|
20
|
+
export { isAbsolutePath, isPathWithinSandbox, validateAndTransformPath, looksLikePath } from './validator';
|
|
21
|
+
// Bash parser utilities (for testing)
|
|
22
|
+
export { extractPathsFromBashCommand, extractAllPathArgs } from './bash-parser';
|
|
23
|
+
// Expander utilities (for testing)
|
|
24
|
+
export { expandPath, hasCommandSubstitution } from './expander';
|