@iinm/plain-agent 1.8.9 → 1.8.10

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,11 +322,17 @@ 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
325
+ ├── memory/ # Task-specific memory files (auto-approvable, writable in sandbox)
326
+ ├── tmp/ # Agent scratch space (auto-approvable, writable in sandbox)
327
+ ├── claude-code-plugins/ # Cached Claude Code plugins (auto-approvable, writable in sandbox)
326
328
  ├── prompts/ # Project-specific prompts
327
- └── agents/ # Project-specific agent roles
329
+ ├── agents/ # Project-specific agent roles
330
+ ├── sandbox/ # Sandbox runner scripts (run.sh, Dockerfile)
331
+ └── setup.sh # Initial setup script
328
332
  ```
329
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
+
330
336
  ### Example
331
337
 
332
338
  <details>
@@ -75,8 +75,23 @@ Generate `.plain-agent/sandbox/run.sh`. Use the following Node.js example as the
75
75
 
76
76
  set -eu -o pipefail
77
77
 
78
+ # Mount .plain-agent/ as read-only over the writable project root, then
79
+ # re-overlay the agent's scratch directories as writable. This prevents
80
+ # in-sandbox modification of host-executed scripts (sandbox/run.sh,
81
+ # setup.sh) and agent config (config.json, prompts/, agents/, ...).
82
+ working_dir=$(pwd)
83
+ metadata_dir="$working_dir/.plain-agent"
84
+ mkdir -p \
85
+ "$metadata_dir/memory" \
86
+ "$metadata_dir/tmp" \
87
+ "$metadata_dir/claude-code-plugins"
88
+
78
89
  options=(
79
90
  --allow-write
91
+ --mount-readonly "$metadata_dir:$metadata_dir"
92
+ --mount-writable "$metadata_dir/memory:$metadata_dir/memory"
93
+ --mount-writable "$metadata_dir/tmp:$metadata_dir/tmp"
94
+ --mount-writable "$metadata_dir/claude-code-plugins:$metadata_dir/claude-code-plugins"
80
95
  --volume plain-sandbox--global--home-npm:/home/sandbox/.npm
81
96
  --volume node_modules
82
97
  )
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@iinm/plain-agent",
3
- "version": "1.8.9",
3
+ "version": "1.8.10",
4
4
  "description": "A lightweight CLI-based coding agent",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -3,6 +3,7 @@ import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import {
5
5
  AGENT_MEMORY_DIR,
6
+ AGENT_PROJECT_METADATA_DIR,
6
7
  AGENT_TMP_DIR,
7
8
  CLAUDE_CODE_PLUGIN_DIR,
8
9
  } from "./env.mjs";
@@ -64,9 +65,15 @@ export function isSafeToolInputItem(arg) {
64
65
  return false;
65
66
  }
66
67
 
67
- // Allow safe path even if git-ignored.
68
- if (isSafePath(realPath)) {
69
- return true;
68
+ // Inside the agent metadata directory, only the agent's known scratch
69
+ // directories (memory, tmp, claude-code-plugins) are auto-approvable
70
+ // (and that exception applies even when those subdirectories are
71
+ // git-ignored). Other entries (sandbox/, setup.sh, config.json,
72
+ // prompts/, agents/, ...) are executed on the host or change agent
73
+ // behavior across sessions, so tool uses targeting them must require
74
+ // explicit approval even if the file is git-managed.
75
+ if (isInsideAgentMetadataDir(realPath)) {
76
+ return isSafePath(realPath);
70
77
  }
71
78
 
72
79
  // Deny git ignored files (which may contain sensitive information or should not be accessed)
@@ -155,6 +162,18 @@ function isSafePath(targetPath) {
155
162
  return false;
156
163
  }
157
164
 
165
+ /**
166
+ * @param {string} targetPath
167
+ * @returns {boolean}
168
+ */
169
+ function isInsideAgentMetadataDir(targetPath) {
170
+ const metadataAbsPath = path.resolve(AGENT_PROJECT_METADATA_DIR);
171
+ return (
172
+ targetPath === metadataAbsPath ||
173
+ targetPath.startsWith(`${metadataAbsPath}${path.sep}`)
174
+ );
175
+ }
176
+
158
177
  /**
159
178
  * @param {string} absPath
160
179
  * @returns {boolean}