Skip to content

Commit 538605f

Browse files
authored
[codex] Extract filesystem safety primitives (#77918)
* refactor: extract filesystem safety primitives * refactor: use fs-safe for file access helpers * refactor: reuse fs-safe for media reads * refactor: use fs-safe for image reads * refactor: reuse fs-safe in qqbot media opener * refactor: reuse fs-safe for local media checks * refactor: consume cleaner fs-safe api * refactor: align fs-safe json option names * fix: preserve fs-safe migration contracts * refactor: use fs-safe primitive subpaths * refactor: use grouped fs-safe subpaths * refactor: align fs-safe api usage * refactor: adapt private state store api * chore: refresh proof gate * refactor: follow fs-safe json api split * refactor: follow reduced fs-safe surface * build: default fs-safe python helper off * fix: preserve fs-safe plugin sdk aliases * refactor: consolidate fs-safe usage * refactor: unify fs-safe store usage * refactor: trim fs-safe temp workspace usage * refactor: hide low-level fs-safe primitives * build: use published fs-safe package * fix: preserve outbound recovery durability after rebase * chore: refresh pr checks
1 parent 61481eb commit 538605f

356 files changed

Lines changed: 4896 additions & 11891 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ Docs: https://docs.openclaw.ai
7171
- Gateway/performance: defer non-readiness sidecars until after the ready signal, avoid hot-path channel plugin barrel imports, and fast-path trusted bundled plugin metadata during Gateway startup.
7272
- Gateway/performance: avoid importing `jiti` on native-loadable plugin startup paths, so compiled bundled plugin surfaces do not pay source-transform loader cost unless fallback loading is actually needed.
7373
- Plugins/loader: preserve real compiled plugin module evaluation errors on the native fast path instead of treating every thrown `.js` module as a source-transform fallback miss. Thanks @vincentkoc.
74+
- Plugin SDK/fs-safe: expose reusable atomic replacement, sibling-temp writes, and cross-device move fallback helpers through `plugin-sdk/security-runtime`, and move OpenClaw's duplicated safe filesystem write paths onto the shared `@openclaw/fs-safe` package.
75+
- Plugin SDK/fs-safe: rename the public temp workspace helpers to `tempWorkspace`, `withTempWorkspace`, `tempWorkspaceSync`, and `withTempWorkspaceSync`, matching the cleaner `@openclaw/fs-safe` API before the package is published.
7476
- Providers/OpenRouter: add opt-in response caching params that send OpenRouter's `X-OpenRouter-Cache`, `X-OpenRouter-Cache-TTL`, and cache-clear headers only on verified OpenRouter routes. Thanks @vincentkoc.
7577
- Providers/OpenRouter: expand app-attribution categories so OpenClaw advertises coding, programming, writing, chat, and personal-agent usage on verified OpenRouter routes. Thanks @vincentkoc.
7678
- Agents/performance: pass the resolved workspace through BTW, compaction, embedded-run model generation, and PDF model setup so explicit agent-dir model refreshes can reuse the current workspace-scoped plugin metadata snapshot instead of falling back to cold plugin metadata scans. (#77519, #77532)
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
fe061b6f35adb2b152d8f48244a94d4934b335143cc5f5aebb8cc96e5ba8b287 plugin-sdk-api-baseline.json
2-
495248d5981456192aaf7da2ed23d5951eaa6d9e59d70c716ab91c3da3620e73 plugin-sdk-api-baseline.jsonl
1+
1a06492fe05d1c9dc3194677f52d57ec90468b93023b70d0852ef01d87c7eae3 plugin-sdk-api-baseline.json
2+
c950a1923c0dc7d31120a3010e24217bcf22fd9cacbe102d3ae19b0120c0f648 plugin-sdk-api-baseline.jsonl

docs/.i18n/glossary.zh-CN.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@
5959
"source": "Gateway RPC reference",
6060
"target": "Gateway RPC 参考"
6161
},
62+
{
63+
"source": "Secure file operations",
64+
"target": "安全文件操作"
65+
},
6266
{
6367
"source": "Sessions",
6468
"target": "会话"
@@ -758,5 +762,9 @@
758762
{
759763
"source": "/cli/config",
760764
"target": "/cli/config"
765+
},
766+
{
767+
"source": "fs-safe Cleanup Plan",
768+
"target": "fs-safe Cleanup Plan"
761769
}
762770
]

docs/docs.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,7 @@
15011501
"group": "Security and sandboxing",
15021502
"pages": [
15031503
"gateway/security/index",
1504+
"gateway/security/secure-file-operations",
15041505
"gateway/security/audit-checks",
15051506
"gateway/operator-scopes",
15061507
"gateway/sandboxing",

docs/gateway/security/index.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ OpenClaw assumes the host and config boundary are trusted:
6565
- Session identifiers (`sessionKey`, session IDs, labels) are routing selectors, not authorization tokens.
6666
- If several people can message one tool-enabled agent, each of them can steer that same permission set. Per-user session/memory isolation helps privacy, but does not convert a shared agent into per-user host authorization.
6767

68+
### Secure file operations
69+
70+
OpenClaw uses `@openclaw/fs-safe` for root-bounded file access, atomic writes, archive extraction, temp workspaces, and secret-file helpers. OpenClaw defaults fs-safe's optional POSIX Python helper to **off**; set `OPENCLAW_FS_SAFE_PYTHON_MODE=auto` or `require` only when you want the extra fd-relative mutation hardening and can support a Python runtime.
71+
72+
Details: [Secure file operations](/gateway/security/secure-file-operations).
73+
6874
### Shared Slack workspace: real risk
6975

7076
If "everyone in Slack can message the bot," the core risk is delegated tool authority:
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
---
2+
summary: "How OpenClaw handles local file access safely, and why the optional fs-safe Python helper is off by default"
3+
read_when:
4+
- Changing file access, archive extraction, workspace storage, or plugin filesystem helpers
5+
title: "Secure file operations"
6+
---
7+
8+
OpenClaw uses [`@openclaw/fs-safe`](https://github.com/openclaw/fs-safe) for security-sensitive local file operations: root-bounded reads/writes, atomic replacement, archive extraction, temp workspaces, JSON state, and secret-file handling.
9+
10+
The goal is a consistent **library guardrail** for trusted OpenClaw code that receives untrusted path names. It is not a sandbox. Host filesystem permissions, OS users, containers, and the agent/tool policy still define the real blast radius.
11+
12+
## Default: no Python helper
13+
14+
OpenClaw defaults the fs-safe POSIX Python helper to **off**.
15+
16+
Why:
17+
18+
- the gateway should not spawn a persistent Python sidecar unless an operator opted into it;
19+
- many installs do not need the extra parent-directory mutation hardening;
20+
- disabling Python keeps package/runtime behavior more predictable across desktop, Docker, CI, and bundled app environments.
21+
22+
OpenClaw only changes the default. If you explicitly set a mode, fs-safe honors it:
23+
24+
```bash
25+
# Default OpenClaw behavior: Node-only fs-safe fallbacks.
26+
OPENCLAW_FS_SAFE_PYTHON_MODE=off
27+
28+
# Opt into the helper when available, falling back if unavailable.
29+
OPENCLAW_FS_SAFE_PYTHON_MODE=auto
30+
31+
# Fail closed if the helper cannot start.
32+
OPENCLAW_FS_SAFE_PYTHON_MODE=require
33+
34+
# Optional explicit interpreter.
35+
OPENCLAW_FS_SAFE_PYTHON=/usr/bin/python3
36+
```
37+
38+
The generic fs-safe names also work: `FS_SAFE_PYTHON_MODE` and `FS_SAFE_PYTHON`.
39+
40+
## What stays protected without Python
41+
42+
With the helper off, OpenClaw still uses fs-safe's Node paths for:
43+
44+
- rejecting relative-path escapes such as `..`, absolute paths, and path separators where only names are allowed;
45+
- resolving operations through a trusted root handle instead of ad-hoc `path.resolve(...).startsWith(...)` checks;
46+
- refusing symlink and hardlink patterns on APIs that require that policy;
47+
- opening files with identity checks where the API returns or consumes file contents;
48+
- atomic sibling-temp writes for state/config files;
49+
- byte limits for reads and archive extraction;
50+
- private modes for secrets and state files where the API requires them.
51+
52+
These protections cover the normal OpenClaw threat model: trusted gateway code handling untrusted model/plugin/channel path input inside a single trusted operator boundary.
53+
54+
## What Python adds
55+
56+
On POSIX, fs-safe's optional helper keeps one persistent Python process and uses fd-relative filesystem operations for parent-directory mutations such as rename, remove, mkdir, stat/list, and some write paths.
57+
58+
That narrows same-UID race windows where another process can swap a parent directory between validation and mutation. It is defense in depth for hosts where untrusted local processes can modify the same directories OpenClaw is operating in.
59+
60+
If your deployment has that risk and Python is guaranteed to exist, use:
61+
62+
```bash
63+
OPENCLAW_FS_SAFE_PYTHON_MODE=require
64+
```
65+
66+
Use `require` rather than `auto` when the helper is part of your security posture; `auto` intentionally falls back to Node-only behavior if the helper is unavailable.
67+
68+
## Plugin and core guidance
69+
70+
- Plugin-facing file access should go through `openclaw/plugin-sdk/*` helpers, not raw `fs`, when a path comes from a message, model output, config, or plugin input.
71+
- Core code should use the local fs-safe wrappers under `src/infra/*` so OpenClaw's process policy is applied consistently.
72+
- Archive extraction should use the fs-safe archive helpers with explicit size, entry-count, link, and destination limits.
73+
- Secrets should use OpenClaw secret helpers or fs-safe secret/private-state helpers; do not hand-roll mode checks around `fs.writeFile`.
74+
- If you need hostile local-user isolation, do not rely on fs-safe alone. Run separate gateways under separate OS users/hosts or use sandboxing.
75+
76+
Related: [Security](/gateway/security), [Sandboxing](/gateway/sandboxing), [Exec approvals](/tools/exec-approvals), [Secrets](/gateway/secrets).

docs/plugins/sdk-migration.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -425,7 +425,7 @@ releases.
425425
| `plugin-sdk/approval-native-runtime` | Approval target helpers | Native approval target/account binding helpers |
426426
| `plugin-sdk/approval-reply-runtime` | Approval reply helpers | Exec/plugin approval reply payload helpers |
427427
| `plugin-sdk/channel-runtime-context` | Channel runtime-context helpers | Generic channel runtime-context register/get/watch helpers |
428-
| `plugin-sdk/security-runtime` | Security helpers | Shared trust, DM gating, external-content, and secret-collection helpers |
428+
| `plugin-sdk/security-runtime` | Security helpers | Shared trust, DM gating, root-bounded file/path helpers, external-content, and secret-collection helpers |
429429
| `plugin-sdk/ssrf-policy` | SSRF policy helpers | Host allowlist and private-network policy helpers |
430430
| `plugin-sdk/ssrf-runtime` | SSRF runtime helpers | Pinned-dispatcher, guarded fetch, SSRF policy helpers |
431431
| `plugin-sdk/system-event-runtime` | System event helpers | `enqueueSystemEvent`, `peekSystemEventEntries` |

docs/plugins/sdk-subpaths.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
161161
| `plugin-sdk/allow-from` | `formatAllowFromLowercase` |
162162
| `plugin-sdk/channel-secret-runtime` | Narrow secret-contract collection helpers for channel/plugin secret surfaces |
163163
| `plugin-sdk/secret-ref-runtime` | Narrow `coerceSecretRef` and SecretRef typing helpers for secret-contract/config parsing |
164-
| `plugin-sdk/security-runtime` | Shared trust, DM gating, external-content, sensitive text redaction, constant-time secret comparison, and secret-collection helpers |
164+
| `plugin-sdk/security-runtime` | Shared trust, DM gating, root-bounded file/path helpers including create-only writes, sync/async atomic file replacement, sibling temp writes, cross-device move fallback, private file-store helpers, symlink-parent guards, external-content, sensitive text redaction, constant-time secret comparison, and secret-collection helpers |
165165
| `plugin-sdk/ssrf-policy` | Host allowlist and private-network SSRF policy helpers |
166166
| `plugin-sdk/ssrf-dispatcher` | Narrow pinned-dispatcher helpers without the broad infra runtime surface |
167167
| `plugin-sdk/ssrf-runtime` | Pinned-dispatcher, SSRF-guarded fetch, SSRF error, and SSRF policy helpers |
@@ -210,7 +210,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
210210
| `plugin-sdk/param-readers` | Common tool/CLI param readers |
211211
| `plugin-sdk/tool-payload` | Extract normalized payloads from tool result objects |
212212
| `plugin-sdk/tool-send` | Extract canonical send target fields from tool args |
213-
| `plugin-sdk/temp-path` | Shared temp-download path helpers |
213+
| `plugin-sdk/temp-path` | Shared temp-download path helpers and private secure temp workspaces |
214214
| `plugin-sdk/logging-core` | Subsystem logger and redaction helpers |
215215
| `plugin-sdk/markdown-table-runtime` | Markdown table mode and conversion helpers |
216216
| `plugin-sdk/model-session-runtime` | Model/session override helpers such as `applyModelOverrideToSessionEntry` and `resolveAgentMaxConcurrent` |

0 commit comments

Comments
 (0)