#Trying to let my agent make API calls without full exec

1 messages · Page 1 of 1 (latest)

stiff pawn
#

Preferably to a specific url. Version 2026.4.24. I'm trying to understand the granularity with exec approvals and allow lists. Worrying about agent being able to overreach somehow.

fleet marshBOT
#

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:

  1. Read-only GET to public URLs: use web_fetch / group:web, and deny exec. web_fetch is HTTP(S) GET-only, blocks private/internal hosts, and re-checks redirects. (docs.openclaw.ai)

  2. 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, not curl.
  3. If you still use exec approvals, I’d keep it conservative:

    • tools.exec.security = "allowlist" or deny
    • tools.exec.ask = "always" or "on-miss"
    • askFallback = "deny"
#
  • autoAllowSkills = false
    • avoid allowlisting interpreters; if you must, enable tools.exec.strictInlineEval

Useful checks:

openclaw approvals get
openclaw exec-policy show

Docs: Exec approvals / CLI approvals are the main references. (github.com)

stiff pawn
#

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.

fleet marshBOT
#

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:

stiff pawn
#

the wrapper would need to be outside her workspace, right? otherwise she could rewrite it with write?

fleet marshBOT
#

Yes — exactly. Put the wrapper outside the agent’s writable workspace.

Good pattern:

  • wrapper: /usr/local/bin/my-api-call
  • owner/perms: root:root, 0755 or 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 restrict write/edit/apply_patch if 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

stiff pawn
#

what does the exact config json look like?

fleet marshBOT
#

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.

Docs: https://docs.openclaw.ai/tools/exec-approvals

stiff pawn
#

it's agents.[].allowList, not agents.[].tools.allowList?

fleet marshBOT
#

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:

stiff pawn
#

what is the --gateway option for?

fleet marshBOT
#

--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.

Docs: https://docs.openclaw.ai/cli/approvals

stiff pawn
#

can i string interpolate/replace the socket token in my exec-approvals via .env?

fleet marshBOT
#

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.

stiff pawn
#

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

fleet marshBOT
#

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.