@iinm/plain-agent 1.8.10 → 1.8.11
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
CHANGED
|
@@ -322,17 +322,15 @@ Files are loaded in the following order. Settings in later files override earlie
|
|
|
322
322
|
└── .plain-agent/
|
|
323
323
|
├── (3) config.json # Project-specific configuration
|
|
324
324
|
├── (4) config.local.json # Project-specific local configuration (including secrets)
|
|
325
|
-
├── memory/ # Task-specific memory files (auto-approvable
|
|
326
|
-
├── tmp/ # Agent scratch space (auto-approvable
|
|
327
|
-
├── claude-code-plugins/ # Cached Claude Code plugins
|
|
325
|
+
├── memory/ # Task-specific memory files (auto-approvable)
|
|
326
|
+
├── tmp/ # Agent scratch space (auto-approvable)
|
|
327
|
+
├── claude-code-plugins/ # Cached Claude Code plugins
|
|
328
328
|
├── prompts/ # Project-specific prompts
|
|
329
329
|
├── agents/ # Project-specific agent roles
|
|
330
|
-
├── sandbox/ # Sandbox runner scripts (run.sh, Dockerfile)
|
|
330
|
+
├── sandbox/ # Sandbox runner scripts (run.sh, Dockerfile); always require approval
|
|
331
331
|
└── setup.sh # Initial setup script
|
|
332
332
|
```
|
|
333
333
|
|
|
334
|
-
Within `.plain-agent/`, only `memory/`, `tmp/`, and `claude-code-plugins/` are auto-approvable as tool input; everything else is executed on the host or changes agent behavior, so writes/reads require explicit approval. The sandbox runner mounts `.plain-agent/` read-only and re-overlays those three scratch directories as writable.
|
|
335
|
-
|
|
336
334
|
### Example
|
|
337
335
|
|
|
338
336
|
<details>
|
|
@@ -76,22 +76,18 @@ Generate `.plain-agent/sandbox/run.sh`. Use the following Node.js example as the
|
|
|
76
76
|
set -eu -o pipefail
|
|
77
77
|
|
|
78
78
|
# Mount .plain-agent/ as read-only over the writable project root, then
|
|
79
|
-
# re-overlay
|
|
80
|
-
# in-sandbox modification of host-executed scripts (sandbox/run.sh,
|
|
81
|
-
# setup.sh) and agent config (config.json, prompts/, agents/, ...).
|
|
79
|
+
# re-overlay only memory/ and tmp/ as writable scratch space.
|
|
82
80
|
working_dir=$(pwd)
|
|
83
81
|
metadata_dir="$working_dir/.plain-agent"
|
|
84
82
|
mkdir -p \
|
|
85
83
|
"$metadata_dir/memory" \
|
|
86
|
-
"$metadata_dir/tmp"
|
|
87
|
-
"$metadata_dir/claude-code-plugins"
|
|
84
|
+
"$metadata_dir/tmp"
|
|
88
85
|
|
|
89
86
|
options=(
|
|
90
87
|
--allow-write
|
|
91
88
|
--mount-readonly "$metadata_dir:$metadata_dir"
|
|
92
89
|
--mount-writable "$metadata_dir/memory:$metadata_dir/memory"
|
|
93
90
|
--mount-writable "$metadata_dir/tmp:$metadata_dir/tmp"
|
|
94
|
-
--mount-writable "$metadata_dir/claude-code-plugins:$metadata_dir/claude-code-plugins"
|
|
95
91
|
--volume plain-sandbox--global--home-npm:/home/sandbox/.npm
|
|
96
92
|
--volume node_modules
|
|
97
93
|
)
|
package/package.json
CHANGED
|
@@ -9,6 +9,17 @@ import {
|
|
|
9
9
|
} from "./env.mjs";
|
|
10
10
|
import { noThrowSync } from "./utils/noThrow.mjs";
|
|
11
11
|
|
|
12
|
+
// Paths that must never be auto-approvable as tool input, even when
|
|
13
|
+
// git-managed. Sandbox scripts run on the host and the project config files
|
|
14
|
+
// drive auto-approval policy itself, so silent in-sandbox modification of
|
|
15
|
+
// either could lead to host code execution or self-granted privilege
|
|
16
|
+
// escalation.
|
|
17
|
+
const UNSAFE_PROJECT_PATHS = [
|
|
18
|
+
path.join(AGENT_PROJECT_METADATA_DIR, "sandbox"),
|
|
19
|
+
path.join(AGENT_PROJECT_METADATA_DIR, "config.json"),
|
|
20
|
+
path.join(AGENT_PROJECT_METADATA_DIR, "config.local.json"),
|
|
21
|
+
];
|
|
22
|
+
|
|
12
23
|
/**
|
|
13
24
|
* @param {unknown} input
|
|
14
25
|
* @returns {boolean}
|
|
@@ -65,15 +76,14 @@ export function isSafeToolInputItem(arg) {
|
|
|
65
76
|
return false;
|
|
66
77
|
}
|
|
67
78
|
|
|
68
|
-
//
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
return isSafePath(realPath);
|
|
79
|
+
// Always require approval for these, even if git-managed.
|
|
80
|
+
if (isUnsafeProjectPath(realPath)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Always allow these even if git-ignored.
|
|
85
|
+
if (isSafePath(realPath)) {
|
|
86
|
+
return true;
|
|
77
87
|
}
|
|
78
88
|
|
|
79
89
|
// Deny git ignored files (which may contain sensitive information or should not be accessed)
|
|
@@ -166,12 +176,18 @@ function isSafePath(targetPath) {
|
|
|
166
176
|
* @param {string} targetPath
|
|
167
177
|
* @returns {boolean}
|
|
168
178
|
*/
|
|
169
|
-
function
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
179
|
+
function isUnsafeProjectPath(targetPath) {
|
|
180
|
+
for (const unsafePath of UNSAFE_PROJECT_PATHS) {
|
|
181
|
+
const unsafeAbsPath = path.resolve(unsafePath);
|
|
182
|
+
if (
|
|
183
|
+
targetPath === unsafeAbsPath ||
|
|
184
|
+
targetPath.startsWith(`${unsafeAbsPath}${path.sep}`)
|
|
185
|
+
) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return false;
|
|
175
191
|
}
|
|
176
192
|
|
|
177
193
|
/**
|