nextlevelbuilder / nextlevelbuilder/goclaw

[Security] Operator-accessible `exec` tool can leak `GOCLAW_*` secrets via `jq` `$ENV` despite `env_dump` protections

Open
#1,227 8 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

agent:github-maintain area:security maintain:triaged P1-high
Dominant language
Go
Stars
3.6k
Forks
1.1k
Avg merge
3d 5h
Merged PRs (30d)
24

Description

Advisory Details

Title: Operator-accessible exec tool can leak GOCLAW_* secrets via jq $ENV despite env_dump protections

Description:

Summary

An authenticated operator can use the public POST /v1/tools/invoke endpoint to invoke the built-in exec tool with jq -nr '$ENV.GOCLAW_GATEWAY_TOKEN' and retrieve sensitive GOCLAW_* process environment variables from the running gateway. This bypasses the intended env_dump protection, which blocks direct shell expansion forms like echo $GOCLAW_GATEWAY_TOKEN but does not detect jq's environment access primitive. Because the host exec path also preserves GoClaw's own secret-bearing environment variables in the child process, the secret is returned over HTTP in the normal tool response.

Details

The issue is reachable through the standard HTTP tool invocation API. ToolsInvokeHandler accepts authenticated POST /v1/tools/invoke requests from callers with at least operator role and forwards the supplied args to the tool registry without special handling for the command parameter beyond normal tool dispatch:

result := h.registry.ExecuteWithContext(ctx, req.Tool, args, "http", "api", "direct", "", nil)

At the affected stable upstream release v3.13.2, the env_dump deny group tries to block common environment disclosure patterns but only covers forms such as env, printenv, echo $GOCLAW_*, printf $GOCLAW_*, Python os.environ, and Node process.env. It does not include jq $ENV access:

regexp.MustCompile(`(?i)\becho\b.*\$\{?GOCLAW_(GATEWAY_TOKEN|ENCRYPTION_KEY|POSTGRES_DSN)`),
regexp.MustCompile(`(?i)\bprintf\b.*\$\{?GOCLAW_(GATEWAY_TOKEN|ENCRYPTION_KEY|POSTGRES_DSN)`),
regexp.MustCompile(`\bpython[23]?\b.*os\.(environ|getenv).*GOCLAW_`),
regexp.MustCompile(`\bnode\b.*-e.*process\.env\.GOCLAW_`),

When the command is allowed through, executeOnHost() spawns /bin/sh -c <command> and scrubs only a fixed credential list from the child environment. That static list does not include GoClaw's own process secrets such as GOCLAW_GATEWAY_TOKEN, GOCLAW_ENCRYPTION_KEY, or GOCLAW_POSTGRES_DSN, so those values remain visible to the child process:

var staticCredentialEnvKeys = []string{
    "GH_TOKEN",
    "GITHUB_TOKEN",
    "GH_ENTERPRISE_TOKEN",
    "GH_CONFIG_DIR",
    "ANTHROPIC_API_KEY",
    "OPENAI_API_KEY",
    ...
}
...
cmd.Env = scrubCredentialEnv(os.Environ(), dynKeys)

As a result, the following authenticated request succeeds against a real gateway process:

{"tool":"exec","args":{"command":"jq -nr '$ENV.GOCLAW_GATEWAY_TOKEN'"}}

In end-to-end verification, the response returned HTTP 200 and exposed the exact canary gateway token in result.output. A control request using echo $GOCLAW_GATEWAY_TOKEN returned HTTP 400 with command denied by safety policy, confirming this is a policy bypass rather than generic intended secret disclosure.

PoC
Prerequisites
  • A GoClaw deployment running an affected release with the public HTTP API enabled.
  • Authentication as a caller with at least operator role.
  • The built-in exec tool enabled on the normal host execution path.
  • A standard environment-based deployment where the gateway process is started with GOCLAW_GATEWAY_TOKEN or other GOCLAW_* secrets in its process environment.
  • jq installed on the host where GoClaw runs.
Reproduction Steps
  1. Download the environment launcher from: exp_runner.py
  2. Download the minimal verification client from: verification_test.py
  3. Download the control script from: control-safe-policy.py
  4. Download the reference config used for reproduction from: config.json
  5. Start the local verification gateway:
    python3 exp_runner.py start
  6. Run the exploit verification:
    python3 verification_test.py
  7. Observe that the script submits {"tool":"exec","args":{"command":"jq -nr '$ENV.GOCLAW_GATEWAY_TOKEN'"}} to http://127.0.0.1:18792/v1/tools/invoke and receives the canary token in the JSON response.
  8. Run the control:
    python3 control-safe-policy.py
  9. Observe that the control using echo $GOCLAW_GATEWAY_TOKEN is rejected by the safety policy with HTTP 400.
Log of Evidence

The end-to-end verification produced the following evidence:

[Mode] End-to-End
[Verification Command] jq -nr '$ENV.GOCLAW_GATEWAY_TOKEN'
[HTTP Status] 200
[Output] 'goclaw-canary-token-ABCDEFGH123456789'
[Raw Response] {"result": {"forUser": "", "metadata": {}, "output": "goclaw-canary-token-ABCDEFGH123456789\n"}}

The control run produced:

[Mode] End-to-End
[Control Command] echo $GOCLAW_GATEWAY_TOKEN
[HTTP Status] 400
[Error Message] 'command denied by safety policy: matches pattern (?i)\becho\b.*\$\{?GOCLAW_(GATEWAY_TOKEN|ENCRYPTION_KEY|POSTGRES_DSN)'
Impact

This is a sensitive information disclosure and policy bypass vulnerability in the host command execution boundary. A caller who is only supposed to have operator-level tool access can retrieve the gateway's bearer token and potentially other GoClaw runtime secrets from the parent process environment. In practice, this can let an operator-level principal recover a stronger gateway credential, pivot into broader API access, and disclose other secrets preserved in the host environment. The impact is not limited to intended command execution; it specifically defeats the product's own environment secret protection model.

Affected products
  • Ecosystem: go
  • Package name: github.com/nextlevelbuilder/goclaw
  • Affected versions: <= 3.13.2
  • Patched versions:
Severity
  • Severity: Medium
  • Vector string: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:N/A:N
Weaknesses
  • CWE: CWE-200: Exposure of Sensitive Information to an Unauthorized Actor
Occurrences
Permalink Description
https://github.com/nextlevelbuilder/goclaw/blob/d85bf17171fd0faefbbd54df44bef573991aa7f8/internal/http/tools_invoke.go#L48-L56 The public HTTP tool invocation endpoint accepts authenticated callers with operator role, making the vulnerable exec path remotely reachable to non-admin authenticated users.
https://github.com/nextlevelbuilder/goclaw/blob/d85bf17171fd0faefbbd54df44bef573991aa7f8/internal/http/tools_invoke.go#L117-L128 User-controlled args, including the command string for the exec tool, are forwarded into ExecuteWithContext without a guard specific to jq environment access.
https://github.com/nextlevelbuilder/goclaw/blob/d85bf17171fd0faefbbd54df44bef573991aa7f8/internal/tools/shell_deny_groups.go#L212-L225 The env_dump deny rules block env, printenv, echo $GOCLAW_*, Python os.environ, and Node process.env, but there is no pattern covering jq $ENV access.
https://github.com/nextlevelbuilder/goclaw/blob/d85bf17171fd0faefbbd54df44bef573991aa7f8/internal/tools/env_scrub.go#L13-L32 The static environment scrub list excludes common third-party credentials but omits GoClaw's own sensitive variables such as GOCLAW_GATEWAY_TOKEN, GOCLAW_ENCRYPTION_KEY, and GOCLAW_POSTGRES_DSN.
https://github.com/nextlevelbuilder/goclaw/blob/d85bf17171fd0faefbbd54df44bef573991aa7f8/internal/tools/shell.go#L553-L574 The host execution path spawns /bin/sh -c <command> and applies scrubCredentialEnv(os.Environ(), dynKeys), so any unsanitized GOCLAW_* secrets remain inherited by the child process and can be read by jq.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the listed reproduction scripts, then inspect internal/tools/shell_deny_groups.go, env_scrub.go, shell.go, and internal/http/tools_invoke.go. Verify the jq $ENV request is blocked or cannot access GOCLAW_* secrets while the existing echo control remains denied, and rerun the end-to-end checks against the local gateway.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
api, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.