feat(cli): add trust windows for time-bounded exec approval#46956
feat(cli): add trust windows for time-bounded exec approval#46956keylimesoda wants to merge 11 commits intoopenclaw:mainfrom
Conversation
- Remove dead expiredNotified/grantNotified fields from TrustWindow - Add gateway exec error audit entry in .catch path - Add 6 new tests: agent-session guard, trust.status shape, untrust cleanup, invalid agentId, summary boundaries - Improve CLI help text and error messages - Add MAX_COMMAND_CHARS rationale comment - Regenerate protocol schema Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Security fixes: - Add structured gateway logs for trust grant/revoke events (sec2-1) - Use resolved approvals in host context instead of hardcoded override (sec2-3) - Clear stale audit files at grant time (sr2-2) - Re-check trust active before post-flight audit append (sr2-3) CLI and readability: - Import ABSOLUTE_MAX_TRUST_MINUTES constant in CLI (sr2-1) - Add requireNonAgentSession guard to trust-status (sr2-4) - Only chmod audit file on creation (sr2-5) - Improve trust confirmation prompt with security context (pm2-6) - Add JSDoc and inline comments for constants and auth paths Tests (+12 new): - Grant rejects zero/negative/non-integer minutes (qa2-1) - Revoke rejects invalid agentId (qa2-7) - Duplicate grant returns INVALID_REQUEST (qa2-2) - Expired window returns null in trust.status (qa2-3) - Empty command returns null from audit (qa2-5) - Malformed JSONL lines skipped on load (qa2-6) - Cleanup no-op when file absent (qa2-9) - Duration formatting hours branch (qa2-12) - Force and keep-audit flag propagation (qa2-10, qa2-11) - Fix mock to include logGateway.info Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Round 3 fix: cover the OPENCLAW_AGENT_ID branch of requireNonAgentSession in addition to the existing OPENCLAW_SESSION_KEY test (qa3-1). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These notification-tracking fields were added to TrustWindow during early development but are never read. Their removal was missed in the Round 1 fix commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Greptile SummaryThis PR introduces time-bounded exec approval trust windows — a well-motivated feature that fills the usability gap between per-command prompts and fully-disabled approvals. The implementation is additive, keeping the existing approval pipeline intact and bolting the override onto the end of Key issues found:
Confidence Score: 3/5
Prompt To Fix All With AIThis is a comment left during a code review.
Path: src/agents/bash-tools.exec.ts
Line: 570-578
Comment:
**Race window silently drops audit entries on revoke**
The second `isTrustWindowActive(getTrustWindow(agentId))` call (lines 572–573) introduces a race: if the operator runs `untrust` in the moment between command completion and the audit write, the entry is silently dropped. `revokeTrustWindow` calls `summarizeTrustAudit` synchronously, so commands completing concurrently with a revocation will never appear in the final summary.
`trustWindowActiveAtStart` is the correct and sufficient predicate — the command started inside the window and should be recorded regardless of what happened afterward. The same pattern recurs in the `.catch` path below (lines 608–611) and in `bash-tools.exec-host-node.ts` (lines 368 and 388).
```suggestion
if (host === "gateway" && trustWindowActiveAtStart) {
tryAppendTrustAuditEntry({
agentId,
command: params.command,
exitCode: outcome.exitCode,
durationMs: outcome.durationMs,
logLabel: "gateway",
});
}
```
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/agents/bash-tools.exec.ts
Line: 607-614
Comment:
**Same race window in the error path**
Mirrors the issue on the success path above — the second `isTrustWindowActive` check can silently suppress the error-path audit entry when the trust window is concurrently revoked. Drop the re-check here too.
```suggestion
if (host === "gateway" && trustWindowActiveAtStart) {
tryAppendTrustAuditEntry({
agentId,
command: params.command,
exitCode: null,
logLabel: "gateway error",
});
}
```
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/infra/exec-approvals.ts
Line: 624-630
Comment:
**Trust window silently promotes `security: "deny"` to `"full"`**
`normalizeSecurity("full", resolvedAgent.security)` returns `"full"` unconditionally because `"full"` is a valid literal — the fallback is never reached. This means an active trust window overrides an agent's `security: "deny"` config to `"full"`, bypassing what may be intended as an absolute execution block (e.g., for sandboxed or read-only agents).
If `security: "deny"` is ever used as a hard ceiling rather than a soft default, this is a privilege escalation path that isn't called out in the PR description or enforced at grant time. Consider either:
1. Rejecting `grantTrustWindow` when the resolved agent security is `"deny"`, or
2. Explicitly documenting that trust windows are intentionally stronger than deny policies, so operators know a deny policy alone won't prevent trust grants.
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/infra/trust-audit.ts
Line: 192-202
Comment:
**Preview threshold and limit are inconsistent**
When `commands.length <= 10`, all commands are shown. When `> 10`, only `previewLimit = 5` are shown with "and N more". This creates a cliff: 10 commands displays all 10, but 11 commands displays only 5. The threshold and limit should match to avoid the jarring jump.
```suggestion
const previewLimit = 10;
if (commands.length <= previewLimit) {
for (const command of commands) {
lines.push(`- ${command}`);
}
} else {
for (const command of commands.slice(0, previewLimit)) {
lines.push(`- ${command}`);
}
lines.push(`- …and ${commands.length - previewLimit} more`);
}
```
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/infra/exec-approvals.ts
Line: 171
Comment:
**Expired trust windows accumulate in cache indefinitely**
`trustWindowCache` is a module-level `Map` that is never pruned of expired entries. Only explicit `revokeTrustWindow` or `initTrustWindowCache` calls remove entries. In a long-running gateway that processes many transient agent IDs, stale expired entries will accumulate until the process restarts.
Consider adding lazy eviction: in `getTrustWindow`, after a `Map.get`, check whether the returned window has already expired and call `trustWindowCache.delete(key)` before returning `undefined`. This keeps the cache bounded without requiring a background timer.
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 933a09b |
| }); | ||
| }) | ||
| .catch((err) => { | ||
| if ( | ||
| host === "gateway" && | ||
| trustWindowActiveAtStart && | ||
| isTrustWindowActive(getTrustWindow(agentId)) | ||
| ) { | ||
| tryAppendTrustAuditEntry({ | ||
| agentId, | ||
| command: params.command, | ||
| exitCode: null, | ||
| logLabel: "gateway error", |
There was a problem hiding this comment.
durationMs missing from error-path audit entry
In the success path (a few lines above), the audit entry includes durationMs: outcome.durationMs. In the .catch() error path, durationMs is omitted entirely. The startedAt timestamp is captured just before the async gateway call, so the elapsed duration is available even on error — the same Date.now() - startedAt pattern used in bash-tools.exec-host-node.ts (error path, line 327) could apply here.
Omitting it means error entries in the JSONL audit will always have durationMs: null, reducing the usefulness of the audit summary for debugging failed commands. Consider capturing the start time in scope and passing it, consistent with the node-host path.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/bash-tools.exec.ts
Line: 604-616
Comment:
**`durationMs` missing from error-path audit entry**
In the success path (a few lines above), the audit entry includes `durationMs: outcome.durationMs`. In the `.catch()` error path, `durationMs` is omitted entirely. The `startedAt` timestamp is captured just before the async gateway call, so the elapsed duration is available even on error — the same `Date.now() - startedAt` pattern used in `bash-tools.exec-host-node.ts` (error path, line 327) could apply here.
Omitting it means error entries in the JSONL audit will always have `durationMs: null`, reducing the usefulness of the audit summary for debugging failed commands. Consider capturing the start time in scope and passing it, consistent with the node-host path.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 250c92ac3f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
The Swift ExecApprovalsTrustParams had a grantedBy field and ExecApprovalsUntrustParams had a revokedBy field, but both were removed from the TypeScript schema (additionalProperties: false). Any macOS client setting these would get a validation error. Regenerated via pnpm protocol:gen:swift. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Addressed the Swift model mismatch: regenerated both GatewayModels.swift files via pnpm protocol:gen:swift. The grantedBy field in ExecApprovalsTrustParams and evokedBy in ExecApprovalsUntrustParams are now removed, matching the TypeScript schema (AdditionalProperties: false). The two style notes (normalize helpers vs direct assignment, and durationMs in error-path audit) are non-blocking per the review. |
|
@greptile review |
| if ( | ||
| host === "gateway" && | ||
| trustWindowActiveAtStart && | ||
| isTrustWindowActive(getTrustWindow(agentId)) | ||
| ) { | ||
| tryAppendTrustAuditEntry({ | ||
| agentId, | ||
| command: params.command, | ||
| exitCode: outcome.exitCode, |
There was a problem hiding this comment.
Race window silently drops audit entries on revoke
The second isTrustWindowActive(getTrustWindow(agentId)) call (lines 572–573) introduces a race: if the operator runs untrust in the moment between command completion and the audit write, the entry is silently dropped. revokeTrustWindow calls summarizeTrustAudit synchronously, so commands completing concurrently with a revocation will never appear in the final summary.
trustWindowActiveAtStart is the correct and sufficient predicate — the command started inside the window and should be recorded regardless of what happened afterward. The same pattern recurs in the .catch path below (lines 608–611) and in bash-tools.exec-host-node.ts (lines 368 and 388).
| if ( | |
| host === "gateway" && | |
| trustWindowActiveAtStart && | |
| isTrustWindowActive(getTrustWindow(agentId)) | |
| ) { | |
| tryAppendTrustAuditEntry({ | |
| agentId, | |
| command: params.command, | |
| exitCode: outcome.exitCode, | |
| if (host === "gateway" && trustWindowActiveAtStart) { | |
| tryAppendTrustAuditEntry({ | |
| agentId, | |
| command: params.command, | |
| exitCode: outcome.exitCode, | |
| durationMs: outcome.durationMs, | |
| logLabel: "gateway", | |
| }); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/bash-tools.exec.ts
Line: 570-578
Comment:
**Race window silently drops audit entries on revoke**
The second `isTrustWindowActive(getTrustWindow(agentId))` call (lines 572–573) introduces a race: if the operator runs `untrust` in the moment between command completion and the audit write, the entry is silently dropped. `revokeTrustWindow` calls `summarizeTrustAudit` synchronously, so commands completing concurrently with a revocation will never appear in the final summary.
`trustWindowActiveAtStart` is the correct and sufficient predicate — the command started inside the window and should be recorded regardless of what happened afterward. The same pattern recurs in the `.catch` path below (lines 608–611) and in `bash-tools.exec-host-node.ts` (lines 368 and 388).
```suggestion
if (host === "gateway" && trustWindowActiveAtStart) {
tryAppendTrustAuditEntry({
agentId,
command: params.command,
exitCode: outcome.exitCode,
durationMs: outcome.durationMs,
logLabel: "gateway",
});
}
```
How can I resolve this? If you propose a fix, please make it concise.| if ( | ||
| host === "gateway" && | ||
| trustWindowActiveAtStart && | ||
| isTrustWindowActive(getTrustWindow(agentId)) | ||
| ) { | ||
| tryAppendTrustAuditEntry({ | ||
| agentId, | ||
| command: params.command, |
There was a problem hiding this comment.
Same race window in the error path
Mirrors the issue on the success path above — the second isTrustWindowActive check can silently suppress the error-path audit entry when the trust window is concurrently revoked. Drop the re-check here too.
| if ( | |
| host === "gateway" && | |
| trustWindowActiveAtStart && | |
| isTrustWindowActive(getTrustWindow(agentId)) | |
| ) { | |
| tryAppendTrustAuditEntry({ | |
| agentId, | |
| command: params.command, | |
| if (host === "gateway" && trustWindowActiveAtStart) { | |
| tryAppendTrustAuditEntry({ | |
| agentId, | |
| command: params.command, | |
| exitCode: null, | |
| logLabel: "gateway error", | |
| }); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agents/bash-tools.exec.ts
Line: 607-614
Comment:
**Same race window in the error path**
Mirrors the issue on the success path above — the second `isTrustWindowActive` check can silently suppress the error-path audit entry when the trust window is concurrently revoked. Drop the re-check here too.
```suggestion
if (host === "gateway" && trustWindowActiveAtStart) {
tryAppendTrustAuditEntry({
agentId,
command: params.command,
exitCode: null,
logLabel: "gateway error",
});
}
```
How can I resolve this? If you propose a fix, please make it concise.| const finalAgent: Required<ExecApprovalsDefaults> = trustWindowActive | ||
| ? { | ||
| ...resolvedAgent, | ||
| security: normalizeSecurity(trustWindow.security, resolvedAgent.security), | ||
| ask: normalizeAsk(trustWindow.ask, resolvedAgent.ask), | ||
| } | ||
| : resolvedAgent; |
There was a problem hiding this comment.
Trust window silently promotes security: "deny" to "full"
normalizeSecurity("full", resolvedAgent.security) returns "full" unconditionally because "full" is a valid literal — the fallback is never reached. This means an active trust window overrides an agent's security: "deny" config to "full", bypassing what may be intended as an absolute execution block (e.g., for sandboxed or read-only agents).
If security: "deny" is ever used as a hard ceiling rather than a soft default, this is a privilege escalation path that isn't called out in the PR description or enforced at grant time. Consider either:
- Rejecting
grantTrustWindowwhen the resolved agent security is"deny", or - Explicitly documenting that trust windows are intentionally stronger than deny policies, so operators know a deny policy alone won't prevent trust grants.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/exec-approvals.ts
Line: 624-630
Comment:
**Trust window silently promotes `security: "deny"` to `"full"`**
`normalizeSecurity("full", resolvedAgent.security)` returns `"full"` unconditionally because `"full"` is a valid literal — the fallback is never reached. This means an active trust window overrides an agent's `security: "deny"` config to `"full"`, bypassing what may be intended as an absolute execution block (e.g., for sandboxed or read-only agents).
If `security: "deny"` is ever used as a hard ceiling rather than a soft default, this is a privilege escalation path that isn't called out in the PR description or enforced at grant time. Consider either:
1. Rejecting `grantTrustWindow` when the resolved agent security is `"deny"`, or
2. Explicitly documenting that trust windows are intentionally stronger than deny policies, so operators know a deny policy alone won't prevent trust grants.
How can I resolve this? If you propose a fix, please make it concise.| const previewLimit = 5; | ||
| if (commands.length <= 10) { | ||
| for (const command of commands) { | ||
| lines.push(`- ${command}`); | ||
| } | ||
| } else { | ||
| for (const command of commands.slice(0, previewLimit)) { | ||
| lines.push(`- ${command}`); | ||
| } | ||
| lines.push(`- …and ${commands.length - previewLimit} more`); | ||
| } |
There was a problem hiding this comment.
Preview threshold and limit are inconsistent
When commands.length <= 10, all commands are shown. When > 10, only previewLimit = 5 are shown with "and N more". This creates a cliff: 10 commands displays all 10, but 11 commands displays only 5. The threshold and limit should match to avoid the jarring jump.
| const previewLimit = 5; | |
| if (commands.length <= 10) { | |
| for (const command of commands) { | |
| lines.push(`- ${command}`); | |
| } | |
| } else { | |
| for (const command of commands.slice(0, previewLimit)) { | |
| lines.push(`- ${command}`); | |
| } | |
| lines.push(`- …and ${commands.length - previewLimit} more`); | |
| } | |
| const previewLimit = 10; | |
| if (commands.length <= previewLimit) { | |
| for (const command of commands) { | |
| lines.push(`- ${command}`); | |
| } | |
| } else { | |
| for (const command of commands.slice(0, previewLimit)) { | |
| lines.push(`- ${command}`); | |
| } | |
| lines.push(`- …and ${commands.length - previewLimit} more`); | |
| } |
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/trust-audit.ts
Line: 192-202
Comment:
**Preview threshold and limit are inconsistent**
When `commands.length <= 10`, all commands are shown. When `> 10`, only `previewLimit = 5` are shown with "and N more". This creates a cliff: 10 commands displays all 10, but 11 commands displays only 5. The threshold and limit should match to avoid the jarring jump.
```suggestion
const previewLimit = 10;
if (commands.length <= previewLimit) {
for (const command of commands) {
lines.push(`- ${command}`);
}
} else {
for (const command of commands.slice(0, previewLimit)) {
lines.push(`- ${command}`);
}
lines.push(`- …and ${commands.length - previewLimit} more`);
}
```
How can I resolve this? If you propose a fix, please make it concise.| export const DEFAULT_MAX_TRUST_MINUTES = 60; // 1h — conservative default | ||
| export const ABSOLUTE_MAX_TRUST_MINUTES = 480; // 8h — absolute max with --force | ||
|
|
||
| const trustWindowCache = new Map<string, TrustWindow>(); |
There was a problem hiding this comment.
Expired trust windows accumulate in cache indefinitely
trustWindowCache is a module-level Map that is never pruned of expired entries. Only explicit revokeTrustWindow or initTrustWindowCache calls remove entries. In a long-running gateway that processes many transient agent IDs, stale expired entries will accumulate until the process restarts.
Consider adding lazy eviction: in getTrustWindow, after a Map.get, check whether the returned window has already expired and call trustWindowCache.delete(key) before returning undefined. This keeps the cache bounded without requiring a background timer.
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/infra/exec-approvals.ts
Line: 171
Comment:
**Expired trust windows accumulate in cache indefinitely**
`trustWindowCache` is a module-level `Map` that is never pruned of expired entries. Only explicit `revokeTrustWindow` or `initTrustWindowCache` calls remove entries. In a long-running gateway that processes many transient agent IDs, stale expired entries will accumulate until the process restarts.
Consider adding lazy eviction: in `getTrustWindow`, after a `Map.get`, check whether the returned window has already expired and call `trustWindowCache.delete(key)` before returning `undefined`. This keeps the cache bounded without requiring a background timer.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 933a09b5ce
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| // so allow that authenticated path for trusted CLI usage. | ||
| const sharedAuthMethod = client?.authMethod; | ||
| const usesSharedAuth = sharedAuthMethod === "token" || sharedAuthMethod === "password"; | ||
| return usesSharedAuth && isLoopbackAddress(client?.clientIp); |
There was a problem hiding this comment.
Permit trusted localhost auth path for trust operations
isTrustGrantCallerAllowed gates shared-auth callers on isLoopbackAddress(client?.clientIp), but the WebSocket handshake intentionally clears clientIp for local/loopback requests (src/gateway/server/ws-connection/message-handler.ts sets reportedClientIp to undefined for local clients), so valid CLI sessions using localhost token/password auth (the no-device-identity path this feature explicitly allows) are rejected as "non-CLI". This makes exec.approvals.trust, ...untrust, and ...trust.status unusable in that supported auth mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 505e878960
ℹ️ About Codex in GitHub
Codex has been enabled to automatically 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 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| }); | ||
|
|
||
| function requireNonAgentSession(action: string): void { | ||
| if (process.env.OPENCLAW_SESSION_KEY || process.env.OPENCLAW_AGENT_ID) { |
There was a problem hiding this comment.
Enforce agent-session blocking on gateway, not env vars
This check is ineffective because OPENCLAW_SESSION_KEY/OPENCLAW_AGENT_ID are not populated by the codebase outside this file/tests (repo-wide search), so agent-spawned CLI processes can pass it and still call exec.approvals.trust (for example from exec with pty=true) to open a trust window. Since this is a security boundary, the non-agent restriction needs to be enforced server-side in the trust RPC handlers using authenticated session metadata rather than client environment variables.
Useful? React with 👍 / 👎.
|
Codex review: found issues before merge. Summary Reproducibility: yes. for review purposes. The blocking issues are source-reproducible from current main plus the provided PR diff by inspecting the trust RPC gate, local WebSocket client IP handling, hard-deny policy behavior, and audit append predicates. Next step before merge Security Review findings
Review detailsBest possible solution: Land a consolidated trust-window design that enforces grant authority at the Gateway, preserves or deliberately redefines deny-policy semantics, records trusted-command audit entries from start-time trust state, and updates CLI, protocol, Swift clients, docs, and regression tests together. Do we have a high-confidence way to reproduce the issue? Yes for review purposes. The blocking issues are source-reproducible from current main plus the provided PR diff by inspecting the trust RPC gate, local WebSocket client IP handling, hard-deny policy behavior, and audit append predicates. Is this the best way to solve the issue? No. The feature direction is useful, but this PR is not the best mergeable solution until the Gateway owns the authorization boundary and the deny/audit semantics are settled explicitly. Full review comments:
Overall correctness: patch is incorrect Security concerns:
What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against dfadf03e1f50. |
AI Disclosure
Built with Claude and GPT-5.3 Codex. Ran AI review passes as a sanity check before submitting. Fully tested — 64 new test cases covering the main flows and auth edge cases.
Summary
trust,untrust,trust-status) that let an operator open a time-limited window where commands run without prompts. In-memory trust state, one enforcement point inresolveExecApprovalsFromFile, JSONL audit trail of commands that ran during the window, caller auth checks.resolveExecApprovalsFromFile. No changes to auth, tokens, network calls, or wire protocol. Agents can't initiate trust windows; only interactive CLI sessions can. This PR doesn't expose this feature through common usage points like TUI, Discord, Whatsapp, etc. It does lay a foundation for those features to be added next.Change Type (select all)
Scope (select all touched areas)
Linked Issue/PR
User-visible / Behavior Changes
openclaw approvals trust --minutes N— opens a trust window (default cap: 60m, up to 480m with--force)openclaw approvals untrust— revokes the window and prints what commands ranopenclaw approvals trust-status— shows whether a window is active and how much time is leftsecurity=full,ask=off).~/.openclaw/trust-audit-<agentId>.jsonlduring the window (mode 0o600).untrustdeletes it by default;--keep-auditpreserves it.Security Impact (required)
Risk + mitigation:
This is a privilege escalation feature, so it needs to be locked down:
requireNonAgentSessionchecks bothOPENCLAW_SESSION_KEYandOPENCLAW_AGENT_ID.--forceto go higher, and 480m is a hard ceiling.Repro + Verification
Environment
~/.openclaw/exec-approvals.json(unchanged format)Steps
openclaw gateway startopenclaw approvals trust --minutes 10 --yesopenclaw approvals trust-statusopenclaw approvals untrust --yesExpected
activewith remaining minutesActual
Evidence
64 tests across 8 files (1,100+ lines of test code). Coverage includes:
--force,--keep-audit,--minutesflag propagation, TTY checksHappy-path coverage is solid. Edge cases around real device-identity auth are tested via mocked
GatewayClientobjects, not live auth flows.Human Verification (required)
--forcewith durations above 60m, idempotent untrust when no window exists, revoke after natural expiry.Review Conversations
Compatibility / Migration
Failure Recovery (if this breaks)
~/.openclaw/can be safely deleted.resolveExecApprovalsFromFilemisbehaves, commands could bypass approval prompts when they shouldn't. Watch fortrustWindowActive: truein resolved approvals when no window was granted.Risks and Mitigations
Risk: User grants a long trust window and walks away — agent has elevated permissions for up to 8 hours.
--forcerequired for longer. 480m hard max. Audit trail + gateway logs record everything.Risk: Audit trail is best-effort — I/O errors don't fail exec, so there could be gaps.