#Trying to let my agent make API calls without full exec
1 messages · Page 1 of 1 (latest)
Short version: don’t use exec allowlists to mean “only this URL.” Exec allowlists are mainly binary/path-level, not HTTP-method/URL/body-level. If you allowlist curl, python, node, etc., you’ve effectively trusted that tool broadly. The docs call out that allowlist patterns are resolved binary paths / command-name globs, and full is powerful. (github.com)
Safer patterns:
-
Read-only GET to public URLs: use
web_fetch/group:web, and denyexec.web_fetchis HTTP(S) GET-only, blocks private/internal hosts, and re-checks redirects. (docs.openclaw.ai) -
Specific API call / auth / POST: make a tiny wrapper or plugin/tool that hardcodes/validates:
- allowed host/path
- method
- allowed params/body shape
- token handling
Then allowlist that wrapper/tool, notcurl.
-
If you still use exec approvals, I’d keep it conservative:
tools.exec.security = "allowlist"ordenytools.exec.ask = "always"or"on-miss"askFallback = "deny"
autoAllowSkills = false- avoid allowlisting interpreters; if you must, enable
tools.exec.strictInlineEval
- avoid allowlisting interpreters; if you must, enable
Useful checks:
openclaw approvals get
openclaw exec-policy show
Docs: Exec approvals / CLI approvals are the main references. (github.com)
I tried option 1 but the call needs to string replace an api key from .env since I don't want the agent having the plaintext key.
Let me verify the current docs/source rather than guessing config knobs here — this is exactly the kind of security boundary where details matter.
Yep — if the agent needs to do the substitution itself, it effectively has the secret capability even if it never prints the key.
The safer pattern is:
- keep the key in
.env/ SecretRef / 1Password / Vault outside the agent workspace - expose a tiny wrapper/tool that:
- hardcodes the allowed host/path
- accepts only safe params
- reads the key internally
- never returns/logs the key
- allowlist that wrapper, not
curl,node,python, etc.
Example shape:
#!/usr/bin/env bash
set -euo pipefail
source /secure/path/api.env
# no agent-controlled URL here
curl --fail --silent --show-error \
-H "Authorization: Bearer $MY_API_KEY" \
-H "Content-Type: application/json" \
--data "{\"query\": \"$1\"}" \
"https://api.example.com/v1/specific-endpoint"
Then exec policy can allowlist only /usr/local/bin/my-safe-api-call.
Even better: make it an OpenClaw plugin/MCP tool so the model sees a typed tool like call_my_api({ query }), not shell. If you use config SecretRefs, check the supported credential surfaces first — arbitrary plugin config paths are not automatically secret-safe unless that surface supports SecretRef.
Docs:
- https://docs.openclaw.ai/tools/exec-approvals
- https://docs.openclaw.ai/tools/exec
- https://docs.openclaw.ai/gateway/secrets
- https://docs.openclaw.ai/reference/secretref-credential-surface
- https://docs.openclaw.ai/plugins/building-plugins#registering-agent-tools
Key rule of thumb: don’t give the agent a secret; give it a narrow action that happens to use a secret internally.
the wrapper would need to be outside her workspace, right? otherwise she could rewrite it with write?
Yes — exactly. Put the wrapper outside the agent’s writable workspace.
Good pattern:
- wrapper:
/usr/local/bin/my-api-call - owner/perms:
root:root,0755or tighter - secret file: outside workspace too, e.g.
/etc/openclaw/my-api.env, not writable by the agent/workspace - exec allowlist: only that wrapper path
- tool policy: don’t allow broad
exec; ideally deny or restrictwrite/edit/apply_patchif this agent doesn’t need file mutation
If the wrapper lives in the workspace and the agent has write/edit/apply_patch, she can replace it and then the exec allowlist becomes meaningless.
Also watch same-UID issues: if the gateway process user can modify the wrapper via shell, then broad exec can still tamper with it. So the real boundary is OS permissions + narrow exec allowlist + no broad shell/interpreter allowlist.
Docs refs:
https://docs.openclaw.ai/tools/exec-approvals
https://docs.openclaw.ai/gateway/sandbox-vs-tool-policy-vs-elevated
what does the exact config json look like?
Yep — for the “only this wrapper, no broad shell” setup, it’s two layers.
~/.openclaw/openclaw.json — tool visibility + requested exec policy:
{
tools: {
profile: "minimal",
allow: ["exec", "session_status"],
exec: {
host: "gateway",
security: "allowlist",
ask: "off",
strictInlineEval: true
}
}
}
Gateway host approvals — actual host-side allowlist:
openclaw approvals set --gateway --stdin <<'EOF'
{
version: 1,
defaults: {
security: "allowlist",
ask: "off",
askFallback: "deny",
autoAllowSkills: false
},
agents: {
"*": {
allowlist: [
{ pattern: "/usr/local/bin/my-safe-api-call" }
]
}
}
}
EOF
Then restart after the config change:
openclaw gateway restart
openclaw approvals get --gateway
For a node instead of gateway, change:
exec: {
host: "node",
node: "your-node-id-or-name",
security: "allowlist",
ask: "off",
strictInlineEval: true
}
and set approvals with:
openclaw approvals set --node your-node-id-or-name --stdin <<'EOF'
{
version: 1,
defaults: {
security: "allowlist",
ask: "off",
askFallback: "deny",
autoAllowSkills: false
},
agents: {
"*": {
allowlist: [
{ pattern: "/usr/local/bin/my-safe-api-call" }
]
}
}
}
EOF
Important: the exec allowlist is still binary/path-level, not URL/argument-level. So /usr/local/bin/my-safe-api-call must itself hardcode/validate the URL and allowed args. Don’t allowlist curl, bash, node, or python for this use case.
it's agents.[].allowList, not agents.[].tools.allowList?
Good catch — there are two different allowlists:
1. Tool allowlist in openclaw.json:
{
tools: {
allow: ["exec", "session_status"]
}
// or per-agent:
// agents: { list: [{ id: "main", tools: { allow: ["exec"] } }] }
}
2. Exec binary allowlist in ~/.openclaw/exec-approvals.json:
{
version: 1,
agents: {
"main": {
allowlist: [
{ pattern: "/usr/local/bin/my-safe-api-call" }
]
}
}
}
So for exec approvals it’s agents.<agentId>.allowlist — lowercase allowlist, and agents is an object keyed by agent id, not agents[].
Easiest way to avoid typo risk:
openclaw approvals allowlist add --gateway --agent main "/usr/local/bin/my-safe-api-call"
what is the --gateway option for?
--gateway means: edit/read the exec approvals on the Gateway machine, not necessarily the machine where you’re running the CLI.
Exec approvals are enforced on the execution host:
- no flag → local
~/.openclaw/exec-approvals.json --gateway→ the Gateway host’s approvals file--node <id>→ that node’s approvals file
So if your agent runs exec with:
tools: { exec: { host: "gateway" } }
then use:
openclaw approvals allowlist add --gateway --agent main "/usr/local/bin/my-safe-api-call"
If OpenClaw/Gateway is on the same machine you’re typing on, local vs --gateway may feel the same. It matters when your CLI is on a laptop but the Gateway is on a VPS/Mac/server.
can i string interpolate/replace the socket token in my exec-approvals via .env?
No — not as a built-in exec-approvals.json feature.
~/.openclaw/exec-approvals.json is a host-local approvals file, not normal openclaw.json config, and the socket token is stored literally there. If the socket.token is missing, OpenClaw generates one and writes it back.
So don’t do this expecting interpolation:
{
socket: {
token: "${EXEC_APPROVALS_TOKEN}"
}
}
That would just be the literal string, not resolved from .env.
For normal use, I’d leave the socket block alone and let OpenClaw manage it. It’s local IPC plumbing, not the API secret your agent uses. If you need to rotate it, delete the socket.token field or regenerate/reset the approvals file, then restart the relevant gateway/node/app.
what are good restricted defaults in exec-approvals if i have my main agent full elevated and my secondary agent just allow-listing that script we mentioned
I’d make the default fail closed, then explicitly open only the trusted agents.
~/.openclaw/exec-approvals.json:
{
version: 1,
defaults: {
security: "deny",
ask: "off",
askFallback: "deny",
autoAllowSkills: false
},
agents: {
"main": {
// trusted / break-glass agent
security: "full",
ask: "off",
askFallback: "deny",
autoAllowSkills: false
},
"secondary": {
// narrow API-caller agent
security: "allowlist",
ask: "off",
askFallback: "deny",
autoAllowSkills: false,
allowlist: [
{ pattern: "/usr/local/bin/my-safe-api-call" }
]
}
}
}
Then align openclaw.json for the secondary agent so it only has the tools it needs:
{
agents: {
list: [
{
id: "secondary",
tools: {
profile: "minimal",
allow: ["exec", "session_status"],
exec: {
host: "gateway",
security: "allowlist",
ask: "off",
strictInlineEval: true
}
}
}
]
}
}
If the script runs on a node, use host: "node" + node: "your-node" and apply the approvals with --node.
Key idea: don’t put the script in agents["*"].allowlist if only the secondary should have it. Keep defaults deny, and make each trusted agent explicit.