openclaw-plugin-exec-grant 1.0.1 → 1.1.0
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/index.ts +34 -4
- package/openclaw.plugin.json +14 -7
- package/package.json +1 -1
- package/skills/exec-grant/SKILL.md +2 -2
- package/skills/exec-grant/references/security-model.md +6 -6
- package/skills/exec-grant/scripts/grant.sh +6 -47
- package/skills/exec-grant/scripts/request.sh +18 -12
- package/skills/exec-grant/scripts/revoke.sh +13 -5
package/index.ts
CHANGED
|
@@ -1,14 +1,20 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
|
|
4
|
+
const PLUGIN_ID = "openclaw-plugin-exec-grant";
|
|
4
5
|
const SCRIPTS_DIR = join(__dirname, "skills", "exec-grant", "scripts");
|
|
5
6
|
|
|
6
|
-
function runScript(
|
|
7
|
+
function runScript(
|
|
8
|
+
name: string,
|
|
9
|
+
args: string[] = [],
|
|
10
|
+
env?: Record<string, string>,
|
|
11
|
+
): string {
|
|
7
12
|
const scriptPath = join(SCRIPTS_DIR, name);
|
|
8
13
|
try {
|
|
9
14
|
return execFileSync("bash", [scriptPath, ...args], {
|
|
10
15
|
encoding: "utf-8",
|
|
11
16
|
timeout: 30_000,
|
|
17
|
+
env: { ...process.env, ...env },
|
|
12
18
|
}).trim();
|
|
13
19
|
} catch (err: any) {
|
|
14
20
|
const stderr = err.stderr?.toString().trim() || "";
|
|
@@ -18,6 +24,19 @@ function runScript(name: string, args: string[] = []): string {
|
|
|
18
24
|
}
|
|
19
25
|
|
|
20
26
|
export default function register(api: any) {
|
|
27
|
+
const config = api.getConfig?.() ?? {};
|
|
28
|
+
const adminTarget = config.adminTarget;
|
|
29
|
+
const channel = config.channel;
|
|
30
|
+
|
|
31
|
+
if (!adminTarget || !channel) {
|
|
32
|
+
api.logger?.warn(
|
|
33
|
+
`[exec-grant] Missing config. Set adminTarget and channel:\n` +
|
|
34
|
+
` openclaw config set plugins.entries.${PLUGIN_ID}.config.adminTarget "<target>"\n` +
|
|
35
|
+
` openclaw config set plugins.entries.${PLUGIN_ID}.config.channel "<channel>"\n` +
|
|
36
|
+
` Example channels: whatsapp, telegram, slack, discord, signal`,
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
21
40
|
api.registerCommand({
|
|
22
41
|
name: "grant",
|
|
23
42
|
description: "Activate time-boxed elevated shell access (admin only)",
|
|
@@ -28,7 +47,13 @@ export default function register(api: any) {
|
|
|
28
47
|
if (!minutes || !/^\d+$/.test(minutes)) {
|
|
29
48
|
return { text: "Usage: /grant <minutes> (1-120)" };
|
|
30
49
|
}
|
|
31
|
-
|
|
50
|
+
// Reply on the channel the admin used to send the command
|
|
51
|
+
const replyChannel = ctx.channel || channel;
|
|
52
|
+
const replyTarget = ctx.sender || adminTarget;
|
|
53
|
+
const result = runScript("grant.sh", [minutes], {
|
|
54
|
+
EXEC_GRANT_CHANNEL: replyChannel || "",
|
|
55
|
+
EXEC_GRANT_TARGET: replyTarget || "",
|
|
56
|
+
});
|
|
32
57
|
return { text: result };
|
|
33
58
|
},
|
|
34
59
|
});
|
|
@@ -38,8 +63,13 @@ export default function register(api: any) {
|
|
|
38
63
|
description: "Revoke elevated shell access immediately (admin only)",
|
|
39
64
|
acceptsArgs: false,
|
|
40
65
|
requireAuth: true,
|
|
41
|
-
handler() {
|
|
42
|
-
const
|
|
66
|
+
handler(ctx: any) {
|
|
67
|
+
const replyChannel = ctx.channel || channel;
|
|
68
|
+
const replyTarget = ctx.sender || adminTarget;
|
|
69
|
+
const result = runScript("revoke.sh", [], {
|
|
70
|
+
EXEC_GRANT_CHANNEL: replyChannel || "",
|
|
71
|
+
EXEC_GRANT_TARGET: replyTarget || "",
|
|
72
|
+
});
|
|
43
73
|
return { text: result };
|
|
44
74
|
},
|
|
45
75
|
});
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,23 +1,30 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "openclaw-plugin-exec-grant",
|
|
3
3
|
"name": "Exec Grant",
|
|
4
|
-
"description": "Time-boxed elevated shell access with
|
|
4
|
+
"description": "Time-boxed elevated shell access with admin approval via any messaging channel",
|
|
5
5
|
"version": "1.0.0",
|
|
6
6
|
"configSchema": {
|
|
7
7
|
"type": "object",
|
|
8
8
|
"properties": {
|
|
9
|
-
"
|
|
9
|
+
"adminTarget": {
|
|
10
10
|
"type": "string",
|
|
11
|
-
"description": "E.164 phone
|
|
11
|
+
"description": "Admin contact: E.164 phone for WhatsApp/Signal, chat ID for Telegram, channel/user for Slack/Discord"
|
|
12
|
+
},
|
|
13
|
+
"channel": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Messaging channel to use for approval requests (e.g. whatsapp, telegram, slack, discord, signal)"
|
|
12
16
|
}
|
|
13
17
|
},
|
|
14
|
-
"required": ["adminPhone"],
|
|
15
18
|
"additionalProperties": false
|
|
16
19
|
},
|
|
17
20
|
"uiHints": {
|
|
18
|
-
"
|
|
19
|
-
"label": "Admin
|
|
20
|
-
"placeholder": "+16505551234"
|
|
21
|
+
"adminTarget": {
|
|
22
|
+
"label": "Admin Target",
|
|
23
|
+
"placeholder": "+16505551234 or @admin or #approvals"
|
|
24
|
+
},
|
|
25
|
+
"channel": {
|
|
26
|
+
"label": "Channel",
|
|
27
|
+
"placeholder": "whatsapp"
|
|
21
28
|
}
|
|
22
29
|
},
|
|
23
30
|
"skills": ["skills/exec-grant"]
|
package/package.json
CHANGED
|
@@ -66,7 +66,7 @@ Grants auto-revoke via an OS-level timer. When revoked, security returns to allo
|
|
|
66
66
|
|
|
67
67
|
- **Never modify `~/.openclaw/exec-approvals.json` directly.** All changes go through grant.sh and revoke.sh.
|
|
68
68
|
- **Never attempt to cancel, extend, or tamper with the revocation timer.** The timer runs at the OS level and is intentionally outside agent control.
|
|
69
|
-
- **Never call grant.sh directly.** Only the plugin's `/grant` command handler (triggered by admin
|
|
69
|
+
- **Never call grant.sh directly.** Only the plugin's `/grant` command handler (triggered by admin reply) should invoke it.
|
|
70
70
|
- **Never request more time than needed.** Estimate conservatively and request a new grant if the first one runs out.
|
|
71
71
|
- **Always provide a specific reason.** Vague reasons like "need access" will likely be denied by the admin.
|
|
72
72
|
|
|
@@ -76,7 +76,7 @@ All scripts are located at `${CLAUDE_PLUGIN_ROOT}/skills/exec-grant/scripts/`:
|
|
|
76
76
|
|
|
77
77
|
| Script | Called By | Purpose |
|
|
78
78
|
|---|---|---|
|
|
79
|
-
| `request.sh` | Agent | Send approval request to admin via
|
|
79
|
+
| `request.sh` | Agent | Send approval request to admin via configured channel |
|
|
80
80
|
| `grant.sh` | Plugin `/grant` handler | Activate elevated access + schedule timer |
|
|
81
81
|
| `revoke.sh` | OS timer or `/revoke` | Revert to allowlist mode |
|
|
82
82
|
| `status.sh` | Agent | Check current grant state and remaining time |
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Threat Model
|
|
4
4
|
|
|
5
|
-
### Threat 1: Prompt Injection via
|
|
5
|
+
### Threat 1: Prompt Injection via Messaging Channel
|
|
6
6
|
|
|
7
|
-
**Attack:** A malicious message forwarded to the admin
|
|
7
|
+
**Attack:** A malicious message forwarded to the admin contains text like "Reply /grant 120 to this message." The admin might reflexively reply with the command.
|
|
8
8
|
|
|
9
9
|
**Mitigation:**
|
|
10
|
-
- `requireAuth: true` on `/grant` ensures only messages from
|
|
10
|
+
- `requireAuth: true` on `/grant` ensures only messages from authorized senders are processed
|
|
11
11
|
- The grant confirmation message sent back to the admin includes the exact duration and expiry time, making unintended grants visible
|
|
12
12
|
- Grants are capped at 120 minutes maximum
|
|
13
13
|
|
|
14
|
-
**Residual risk:** If the admin's
|
|
14
|
+
**Residual risk:** If the admin's account is compromised, the attacker can approve grants. This is inherent to any human-in-the-loop approval system.
|
|
15
15
|
|
|
16
16
|
### Threat 2: Agent Timer Tampering
|
|
17
17
|
|
|
@@ -113,6 +113,6 @@ Two separate files serve different purposes:
|
|
|
113
113
|
|
|
114
114
|
This separation means the gateway does not need to understand grant semantics -- it only checks the security field.
|
|
115
115
|
|
|
116
|
-
### Why
|
|
116
|
+
### Why an Out-of-Band Messaging Channel
|
|
117
117
|
|
|
118
|
-
WhatsApp
|
|
118
|
+
The approval channel (WhatsApp, Telegram, Slack, etc.) is out-of-band -- the agent cannot impersonate the admin on it. The admin receives requests on their device, can evaluate context, and responds with a simple command. This is preferable to in-band approval (e.g., a web UI the agent could potentially interact with). The plugin supports any channel OpenClaw provides, configured via the `channel` and `adminTarget` config fields.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# grant.sh -- Called by plugin /grant handler: activates elevated access + timer
|
|
3
3
|
# Usage: grant.sh <minutes>
|
|
4
|
+
# Env: EXEC_GRANT_CHANNEL, EXEC_GRANT_TARGET (set by index.ts from command context)
|
|
4
5
|
set -euo pipefail
|
|
5
6
|
|
|
6
7
|
# Ensure openclaw is on PATH (npm global bin may not be in non-interactive shells)
|
|
@@ -32,10 +33,8 @@ SECONDS_TO_ADD=$(( MINUTES * 60 ))
|
|
|
32
33
|
EXPIRY=$(( NOW + SECONDS_TO_ADD ))
|
|
33
34
|
|
|
34
35
|
if date -v+1S +%s >/dev/null 2>&1; then
|
|
35
|
-
# macOS (BSD date)
|
|
36
36
|
EXPIRY_HUMAN=$(date -r "$EXPIRY" '+%Y-%m-%d %H:%M:%S %Z')
|
|
37
37
|
else
|
|
38
|
-
# Linux (GNU date)
|
|
39
38
|
EXPIRY_HUMAN=$(date -d "@${EXPIRY}" '+%Y-%m-%d %H:%M:%S %Z')
|
|
40
39
|
fi
|
|
41
40
|
|
|
@@ -54,7 +53,6 @@ if [[ ! -f "$APPROVALS_FILE" ]]; then
|
|
|
54
53
|
ENDJSON
|
|
55
54
|
fi
|
|
56
55
|
|
|
57
|
-
# Targeted jq edit: only change .agents.main.security
|
|
58
56
|
TEMP_FILE=$(mktemp)
|
|
59
57
|
jq '.agents.main.security = "full"' "$APPROVALS_FILE" > "$TEMP_FILE" && mv "$TEMP_FILE" "$APPROVALS_FILE"
|
|
60
58
|
|
|
@@ -63,7 +61,6 @@ REVOKE_SCRIPT="${SCRIPT_DIR}/revoke.sh"
|
|
|
63
61
|
TIMER_ID=""
|
|
64
62
|
|
|
65
63
|
if command -v systemctl >/dev/null 2>&1 && systemctl --user status >/dev/null 2>&1; then
|
|
66
|
-
# Linux: systemd-run (tamper-resistant -- agent cannot cancel)
|
|
67
64
|
systemd-run --user \
|
|
68
65
|
--on-active="${MINUTES}m" \
|
|
69
66
|
--unit=exec-grant-revoke \
|
|
@@ -72,52 +69,13 @@ if command -v systemctl >/dev/null 2>&1 && systemctl --user status >/dev/null 2>
|
|
|
72
69
|
fi
|
|
73
70
|
|
|
74
71
|
if [[ -z "$TIMER_ID" ]] && [[ "$(uname)" == "Darwin" ]]; then
|
|
75
|
-
# macOS: launchd plist
|
|
76
|
-
PLIST_LABEL="com.openclaw.exec-grant-revoke"
|
|
77
|
-
PLIST_PATH="$HOME/Library/LaunchAgents/${PLIST_LABEL}.plist"
|
|
78
|
-
mkdir -p "$HOME/Library/LaunchAgents"
|
|
79
|
-
|
|
80
|
-
cat > "$PLIST_PATH" <<PLIST
|
|
81
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
|
82
|
-
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
83
|
-
<plist version="1.0">
|
|
84
|
-
<dict>
|
|
85
|
-
<key>Label</key>
|
|
86
|
-
<string>${PLIST_LABEL}</string>
|
|
87
|
-
<key>ProgramArguments</key>
|
|
88
|
-
<array>
|
|
89
|
-
<string>/bin/bash</string>
|
|
90
|
-
<string>${REVOKE_SCRIPT}</string>
|
|
91
|
-
</array>
|
|
92
|
-
<key>StartInterval</key>
|
|
93
|
-
<integer>0</integer>
|
|
94
|
-
<key>LaunchOnlyOnce</key>
|
|
95
|
-
<true/>
|
|
96
|
-
<key>RunAtLoad</key>
|
|
97
|
-
<false/>
|
|
98
|
-
<key>StandardOutPath</key>
|
|
99
|
-
<string>${HOME}/.openclaw/exec-grant-revoke.log</string>
|
|
100
|
-
<key>StandardErrorPath</key>
|
|
101
|
-
<string>${HOME}/.openclaw/exec-grant-revoke.log</string>
|
|
102
|
-
</dict>
|
|
103
|
-
</plist>
|
|
104
|
-
PLIST
|
|
105
|
-
|
|
106
|
-
# Use a delayed start: unload any existing, then schedule via at-style workaround
|
|
107
|
-
launchctl unload "$PLIST_PATH" 2>/dev/null || true
|
|
108
|
-
|
|
109
|
-
# launchd doesn't have on-active like systemd; use sleep-based subprocess
|
|
110
72
|
(sleep "$SECONDS_TO_ADD" && bash "$REVOKE_SCRIPT") &
|
|
111
73
|
BG_PID=$!
|
|
112
74
|
disown "$BG_PID" 2>/dev/null || true
|
|
113
75
|
TIMER_ID="bg:${BG_PID}"
|
|
114
|
-
|
|
115
|
-
# Clean up the plist since we're using bg approach
|
|
116
|
-
rm -f "$PLIST_PATH"
|
|
117
76
|
fi
|
|
118
77
|
|
|
119
78
|
if [[ -z "$TIMER_ID" ]]; then
|
|
120
|
-
# Fallback: background sleep + revoke
|
|
121
79
|
(sleep "$SECONDS_TO_ADD" && bash "$REVOKE_SCRIPT") &
|
|
122
80
|
BG_PID=$!
|
|
123
81
|
disown "$BG_PID" 2>/dev/null || true
|
|
@@ -139,12 +97,13 @@ ENDJSON
|
|
|
139
97
|
# --- Audit log ---
|
|
140
98
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] GRANT: ${MINUTES}m, expires ${EXPIRY_HUMAN}, timer=${TIMER_ID}" >> "$AUDIT_LOG"
|
|
141
99
|
|
|
142
|
-
# --- Notify admin ---
|
|
143
|
-
|
|
100
|
+
# --- Notify admin (uses channel context from the /grant command) ---
|
|
101
|
+
NOTIFY_CHANNEL="${EXEC_GRANT_CHANNEL:-}"
|
|
102
|
+
NOTIFY_TARGET="${EXEC_GRANT_TARGET:-}"
|
|
144
103
|
|
|
145
|
-
if [[ -n "$
|
|
104
|
+
if [[ -n "$NOTIFY_TARGET" ]] && [[ -n "$NOTIFY_CHANNEL" ]]; then
|
|
146
105
|
MSG="Grant active for *${MINUTES}m*. Expires at ${EXPIRY_HUMAN}. Reply \`/revoke\` to end early."
|
|
147
|
-
openclaw message send --target "$
|
|
106
|
+
openclaw message send --target "$NOTIFY_TARGET" --channel "$NOTIFY_CHANNEL" --message "$MSG" 2>/dev/null || true
|
|
148
107
|
fi
|
|
149
108
|
|
|
150
109
|
echo "Grant activated: ${MINUTES}m of full shell access. Expires at ${EXPIRY_HUMAN}."
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# request.sh -- Agent-facing: sends
|
|
2
|
+
# request.sh -- Agent-facing: sends approval request to admin
|
|
3
3
|
# Usage: request.sh <minutes> "<reason>"
|
|
4
4
|
set -euo pipefail
|
|
5
5
|
|
|
@@ -10,6 +10,7 @@ done
|
|
|
10
10
|
|
|
11
11
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
12
12
|
STATE_FILE="$HOME/.openclaw/exec-grant-state.json"
|
|
13
|
+
PLUGIN_ID="openclaw-plugin-exec-grant"
|
|
13
14
|
|
|
14
15
|
# --- Arg validation ---
|
|
15
16
|
if [[ $# -lt 2 ]]; then
|
|
@@ -43,7 +44,6 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
43
44
|
echo "Error: grant already active with ${REMAINING}m remaining. Use status.sh to check." >&2
|
|
44
45
|
exit 1
|
|
45
46
|
fi
|
|
46
|
-
# Expired but not cleaned up -- status.sh will self-heal
|
|
47
47
|
fi
|
|
48
48
|
|
|
49
49
|
if [[ "$STATUS" == "pending" ]]; then
|
|
@@ -51,22 +51,27 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
51
51
|
NOW=$(date +%s)
|
|
52
52
|
ELAPSED=$(( NOW - REQUESTED_AT ))
|
|
53
53
|
if [[ "$ELAPSED" -lt 300 ]]; then
|
|
54
|
-
echo "Error: approval request already pending (sent $
|
|
54
|
+
echo "Error: approval request already pending (sent ${ELAPSED}s ago). Wait for admin response." >&2
|
|
55
55
|
exit 1
|
|
56
56
|
fi
|
|
57
|
-
# Stale pending request (>5m) -- allow re-request
|
|
58
57
|
fi
|
|
59
58
|
fi
|
|
60
59
|
|
|
61
|
-
# ---
|
|
62
|
-
|
|
60
|
+
# --- Read plugin config ---
|
|
61
|
+
ADMIN_TARGET=$(openclaw config get "plugins.entries.${PLUGIN_ID}.config.adminTarget" 2>/dev/null | tail -1 | tr -d '[:space:]' || true)
|
|
62
|
+
CHANNEL=$(openclaw config get "plugins.entries.${PLUGIN_ID}.config.channel" 2>/dev/null | tail -1 | tr -d '[:space:]' || true)
|
|
63
63
|
|
|
64
|
-
if [[ -z "$
|
|
65
|
-
echo "Error: no admin
|
|
64
|
+
if [[ -z "$ADMIN_TARGET" ]]; then
|
|
65
|
+
echo "Error: no admin target configured. Set plugins.entries.${PLUGIN_ID}.config.adminTarget" >&2
|
|
66
66
|
exit 1
|
|
67
67
|
fi
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
if [[ -z "$CHANNEL" ]]; then
|
|
70
|
+
echo "Error: no channel configured. Set plugins.entries.${PLUGIN_ID}.config.channel" >&2
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# --- Send approval request ---
|
|
70
75
|
MSG="*Elevated Access Request*
|
|
71
76
|
|
|
72
77
|
Agent requests *${MINUTES}m* of full shell access.
|
|
@@ -75,7 +80,7 @@ Agent requests *${MINUTES}m* of full shell access.
|
|
|
75
80
|
|
|
76
81
|
Reply \`/grant ${MINUTES}\` to approve or ignore to deny."
|
|
77
82
|
|
|
78
|
-
openclaw message send --target "$
|
|
83
|
+
openclaw message send --target "$ADMIN_TARGET" --channel "$CHANNEL" --message "$MSG"
|
|
79
84
|
|
|
80
85
|
# --- Write pending state ---
|
|
81
86
|
mkdir -p "$(dirname "$STATE_FILE")"
|
|
@@ -87,8 +92,9 @@ cat > "$STATE_FILE" <<ENDJSON
|
|
|
87
92
|
"requested_minutes": ${MINUTES},
|
|
88
93
|
"reason": $(jq -n --arg v "$REASON" '$v'),
|
|
89
94
|
"requested_at": ${NOW},
|
|
90
|
-
"
|
|
95
|
+
"admin_target": $(jq -n --arg v "$ADMIN_TARGET" '$v'),
|
|
96
|
+
"channel": $(jq -n --arg v "$CHANNEL" '$v')
|
|
91
97
|
}
|
|
92
98
|
ENDJSON
|
|
93
99
|
|
|
94
|
-
echo "Approval request sent to admin. Waiting for /grant ${MINUTES} reply."
|
|
100
|
+
echo "Approval request sent to admin via ${CHANNEL}. Waiting for /grant ${MINUTES} reply."
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
2
|
# revoke.sh -- Called by timer or /revoke: reverts to allowlist mode
|
|
3
3
|
# Usage: revoke.sh
|
|
4
|
+
# Env: EXEC_GRANT_CHANNEL, EXEC_GRANT_TARGET (set by index.ts when called via /revoke)
|
|
4
5
|
set -euo pipefail
|
|
5
6
|
|
|
6
7
|
# Ensure openclaw is on PATH (npm global bin may not be in non-interactive shells)
|
|
@@ -8,6 +9,7 @@ for p in "$HOME/.npm-global/bin" "$HOME/.local/bin" "$HOME/node_modules/.bin"; d
|
|
|
8
9
|
[[ -d "$p" ]] && export PATH="$p:$PATH"
|
|
9
10
|
done
|
|
10
11
|
|
|
12
|
+
PLUGIN_ID="openclaw-plugin-exec-grant"
|
|
11
13
|
STATE_FILE="$HOME/.openclaw/exec-grant-state.json"
|
|
12
14
|
APPROVALS_FILE="$HOME/.openclaw/exec-approvals.json"
|
|
13
15
|
AUDIT_LOG="$HOME/.openclaw/exec-grant-audit.log"
|
|
@@ -29,8 +31,6 @@ if [[ -f "$STATE_FILE" ]]; then
|
|
|
29
31
|
systemctl --user stop "${UNIT}.timer" 2>/dev/null || true
|
|
30
32
|
systemctl --user stop "${UNIT}.service" 2>/dev/null || true
|
|
31
33
|
fi
|
|
32
|
-
# bg: timers cannot be reliably cancelled cross-process, but the revoke
|
|
33
|
-
# itself is idempotent so a second invocation is harmless
|
|
34
34
|
fi
|
|
35
35
|
|
|
36
36
|
# --- Update state file ---
|
|
@@ -46,10 +46,18 @@ ENDJSON
|
|
|
46
46
|
echo "[$(date -u '+%Y-%m-%dT%H:%M:%SZ')] REVOKE: security restored to allowlist" >> "$AUDIT_LOG"
|
|
47
47
|
|
|
48
48
|
# --- Notify admin ---
|
|
49
|
-
|
|
49
|
+
# When called from /revoke command, env vars are set by index.ts
|
|
50
|
+
# When called from timer, fall back to plugin config
|
|
51
|
+
NOTIFY_CHANNEL="${EXEC_GRANT_CHANNEL:-}"
|
|
52
|
+
NOTIFY_TARGET="${EXEC_GRANT_TARGET:-}"
|
|
53
|
+
|
|
54
|
+
if [[ -z "$NOTIFY_TARGET" ]] || [[ -z "$NOTIFY_CHANNEL" ]]; then
|
|
55
|
+
NOTIFY_TARGET=$(openclaw config get "plugins.entries.${PLUGIN_ID}.config.adminTarget" 2>/dev/null | tail -1 | tr -d '[:space:]' || true)
|
|
56
|
+
NOTIFY_CHANNEL=$(openclaw config get "plugins.entries.${PLUGIN_ID}.config.channel" 2>/dev/null | tail -1 | tr -d '[:space:]' || true)
|
|
57
|
+
fi
|
|
50
58
|
|
|
51
|
-
if [[ -n "$
|
|
52
|
-
openclaw message send --target "$
|
|
59
|
+
if [[ -n "$NOTIFY_TARGET" ]] && [[ -n "$NOTIFY_CHANNEL" ]]; then
|
|
60
|
+
openclaw message send --target "$NOTIFY_TARGET" --channel "$NOTIFY_CHANNEL" --message "Grant revoked. Security restored to allowlist mode." 2>/dev/null || true
|
|
53
61
|
fi
|
|
54
62
|
|
|
55
63
|
echo "Grant revoked. Security restored to allowlist mode."
|