feat: add local exec-policy CLI#64050
Conversation
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟡 Terminal escape/control sequence injection via unsanitized JSON output in exec-policy CLI
DescriptionThe While
then running Vulnerable code: if (opts.json) {
defaultRuntime.writeJson(payload, 0);
return;
}RecommendationHarden JSON output intended for terminal/log viewing by neutralizing control characters in all string fields before writing. Options:
function sanitizeForJsonTerminal(value: unknown): unknown {
if (typeof value === "string") {
// match sanitizeTerminalText behavior, but preserve JSON semantics
return value
.replace(/\r/g, "\\r")
.replace(/\n/g, "\\n")
.replace(/\t/g, "\\t")
.replace(/[\u0000-\u001F\u007F-\u009F]/g, (c) =>
`\\u{${c.codePointAt(0)!.toString(16).toUpperCase()}}`,
);
}
if (Array.isArray(value)) return value.map(sanitizeForJsonTerminal);
if (value && typeof value === "object") {
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, sanitizeForJsonTerminal(v)]));
}
return value;
}
// when emitting JSON to stdout
defaultRuntime.writeJson(sanitizeForJsonTerminal(payload), 0);
This prevents terminal/log escape sequence interpretation when untrusted strings are present. 2. 🟡 Non-atomic exec-policy sync can leave approvals in a more permissive state when config update fails
Description
This creates a non-atomic update window:
A local attacker/process running under the same user account (or a competing instance) can:
Depending on the previous state, this can result in an unintended relaxation of the effective exec policy. For example, if config previously requested RecommendationMake the update of config + approvals atomic (or at least fail-safe) so a config-write failure cannot leave approvals more permissive than before. Options:
Example (write config first): await replaceConfigFile({ baseHash: configSnapshot.hash, nextConfig });
saveExecApprovals(nextApprovals);If approvals must be written first, use a lock around both writes to prevent races. Analyzed PR: #64050 at commit Last updated on: 2026-04-10T06:10:29Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: fe0cd124ef
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Greptile SummaryAdds an Confidence Score: 5/5Safe to merge; all findings are minor style suggestions with no production impact. No P0 or P1 issues found. The three comments are P2: a duplicate import that can be merged, an unnecessary Promise.resolve() wrapper, and a double-exit design smell that only manifests in test scenarios (and no error-path tests exist to trigger it). Core logic — read → compute → mutate config → save approvals → re-read for display — is correct. src/cli/exec-policy-cli.ts (minor style nits only) Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/cli/exec-policy-cli.ts
Line: 2-3
Comment:
**Duplicate import from same module**
Both statements import from `../config/config.js` and can be merged into one.
```suggestion
import { mutateConfigFile, readConfigFileSnapshot } from "../config/config.js";
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cli/exec-policy-cli.ts
Line: 151-154
Comment:
**Unnecessary `Promise.resolve()` wrapper around synchronous call**
`readExecApprovalsSnapshot()` is synchronous (imported without `async`, mock confirms it). Wrapping it in `Promise.resolve()` adds no value and slightly obscures that the call is synchronous.
```suggestion
const [configSnapshot, approvalsSnapshot] = await Promise.all([
readConfigFileSnapshot(),
readExecApprovalsSnapshot(),
]);
```
How can I resolve this? If you propose a fix, please make it concise.
---
This is a comment left during a code review.
Path: src/cli/exec-policy-cli.ts
Line: 262-267
Comment:
**`exitWithError` inside `try/catch` causes double-exit in test scenarios**
`exitWithError` calls `defaultRuntime.exit(1)` then `throw`s so TypeScript sees it as `never`. In production `process.exit` terminates immediately and the catch block is never reached. In tests, however, the mocked `exit` throws `Error("__exit__:1")`, which propagates to the outer `catch (err)` block, causing a second `defaultRuntime.error("Error: __exit__:1")` and a second `defaultRuntime.exit(1)`. Any future error-path tests for this command would see unexpected extra calls to `error` and `exit`, making assertions harder to write.
The same pattern appears in the `set` command's `Object.keys(policy).length === 0` check (line 300). Consider either re-throwing a sentinel `ExitError` that the catch recognizes and skips re-logging, or moving the `exitWithError` invocations outside the `try` scope.
How can I resolve this? If you propose a fix, please make it concise.Reviews (1): Last reviewed commit: "feat: add local exec-policy CLI" | Re-trigger Greptile |
|
Follow-up pushed in Changes from review:
I did not expand this PR into a shared |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 003daa219f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: beaa25fdca
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 70d3bb9869
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
Following up on the latest Aisle refresh against I am not fixing the three newly reported TOCTOU/permission findings in this feature PR:
Reason: those are all deeper shared The inline codex threads are replied to directly now:
If we want the remaining Aisle items fixed, I’d handle them as a separate hardening PR scoped specifically to |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 411cfe96a6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 358aa27191
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
# Conflicts: # src/infra/exec-approvals.ts
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6cef726bba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
The `assertNoSymlinkPathComponents` function introduced in openclaw#64050 rejects any symlink encountered while walking from $HOME to the exec-approvals file. This breaks setups where `~/.openclaw` itself is a symlink (e.g. managed via GNU Stow for config backup). Use `fs.realpathSync` to resolve the trusted root through top-level symlinks before walking child segments. Symlinks *inside* the resolved root are still rejected. The "."-segment self-check on the root is removed since the root is now always the real path. Update test: the "symlinked parent" case now expects success instead of an error, and a new test verifies that symlinks inside the resolved home are still caught.
…s rejection OpenClaw v2026.4.14 introduced assertNoSymlinkPathComponents() in exec-approvals.ts (openclaw/openclaw#64050, commit 4bf94aa0d6, 2026-04-10) which rejects any symlink found along the exec approvals path. In dev mode, vendor/openclaw-runtime/current is a symlink to win-x64, causing the gateway to throw: "Refusing to traverse symlink in exec approvals path" Fix by calling fs.realpathSync() on the resolved runtime root so the gateway receives the real physical path instead of the symlink. Only affects dev mode — packaged builds use process.resourcesPath/cfmind (a real directory) and are not impacted. Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The exec-approvals symlink hardening from openclaw#72377 still rejected a symlinked ~/.openclaw immediate child of the trusted home directory, breaking exec-policy commands for users who manage OpenClaw config via GNU Stow, chezmoi, or other dotfile managers. openclaw#72377 only relaxed the restriction on the OPENCLAW_HOME root itself. Allow exactly one trusted symlink hop at the immediate child of the trusted home root, but only when the realpath target is owned by the current effective user AND is not group/other writable. Deeper symlinks inside the resolved .openclaw tree, additional symlink hops, and unsafe-permission targets remain rejected with the original error message. Updates the existing 'refuses to traverse symlinked approvals components below a symlinked home' test to construct an unsafe target (group-writable) so the deeper-symlink rejection intent of openclaw#72377 is still exercised, and adds a new test for the safe-symlink case from this issue. Fixes openclaw#72572 Refines openclaw#72377, openclaw#64663, openclaw#64050
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
* feat: add local exec-policy CLI * fix: harden exec-policy CLI output * fix: harden exec approvals writes * fix: tighten local exec-policy sync * docs: document exec-policy CLI * fix: harden exec-policy rollback and approvals path checks * fix: reject exec-policy sync when host remains node * fix: validate approvals path before mkdir * fix: guard exec-policy rollback against newer approvals writes * fix: restore exec approvals via hardened rollback path * fix: guard exec-policy config writes with base hash * docs: add exec-policy changelog entry * fix: clarify exec-policy show for node host * fix: strip stale exec-policy decisions
Summary
Describe the problem and fix in 2–5 bullets:
tools.exec.*and~/.openclaw/exec-approvals.jsonseparately, with mismatched CLI ergonomics (config setvs JSON-file replacement).openclaw exec-policyCLI withshow,preset, andsetcommands that synchronizes requested exec policy and host approvals together.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
Root Cause (if applicable)
For bug fixes or regressions, explain why this happened, not just what changed. Otherwise write
N/A. If the cause is unclear, writeUnknown.Regression Test Plan (if applicable)
For bug fixes or regressions, name the smallest reliable test coverage that should catch this. Otherwise write
N/A.src/cli/exec-policy-cli.test.ts,src/cli/program/register.subclis.test.tsexec-policy showreports the merged local policy,preset yoloupdates both config and approvals, andsetapplies explicit synced values.src/cli/program/register.subclis.test.tsUser-visible / Behavior Changes
List user-visible changes (including defaults/config).
If none, write
None.openclaw exec-policy showto display config path, approvals path, and effective merged exec policy.openclaw exec-policy preset yolo|cautious|deny-allto synchronize localtools.exec.*and host approvals defaults together.openclaw exec-policy set --host/--security/--ask/--ask-fallbackfor explicit local synchronization without editing JSON.Diagram (if applicable)
For UI changes or non-trivial logic flows, include a small ASCII diagram reviewers can scan quickly. Otherwise write
N/A.Security Impact (required)
Yes/No)Yes/No)Yes/No)Yes/No)Yes/No)Yes, explain risk + mitigation:yolo. Mitigation: scope is local-only, the command names/presets are explicit, andshowexposes the effective merged result immediately after mutation.Repro + Verification
Environment
tools.exec.*plus local~/.openclaw/exec-approvals.jsonSteps
openclaw exec-policy show --json.openclaw exec-policy preset yolo --json.openclaw exec-policy set --host node --security full --ask off --ask-fallback allowlist --json.Expected
showreports the local config path, approvals path, and effective merged policy.preset yoloupdates bothtools.exec.*and approvals defaults to permissive local values.setupdates both stores consistently using explicit flags.Actual
Evidence
Attach at least one:
Human Verification (required)
What you personally verified (not just CI), and how:
show,preset yolo, explicitset, and subcommand registration.Review Conversations
If a bot review conversation is addressed by this PR, resolve that conversation yourself. Do not leave bot review conversation cleanup for maintainers.
Compatibility / Migration
Yes/No)Yes/No)Yes/No)Risks and Mitigations
List only real risks for this PR. Add/remove entries as needed. If none, write
None.exec-policyalso updates remote gateway or node-host state.approvals/ effective-policy semantics over time.