nextlevelbuilder / nextlevelbuilder/goclaw
[Security] Operator-accessible `exec` tool can leak `GOCLAW_*` secrets via `jq` `$ENV` despite `env_dump` protections
Nobody has claimed this yet.
- 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
operatorrole. - The built-in
exectool enabled on the normal host execution path. - A standard environment-based deployment where the gateway process is started with
GOCLAW_GATEWAY_TOKENor otherGOCLAW_*secrets in its process environment. jqinstalled on the host where GoClaw runs.
Reproduction Steps
- Download the environment launcher from: exp_runner.py
- Download the minimal verification client from: verification_test.py
- Download the control script from: control-safe-policy.py
- Download the reference config used for reproduction from: config.json
- Start the local verification gateway:
python3 exp_runner.py start - Run the exploit verification:
python3 verification_test.py - Observe that the script submits
{"tool":"exec","args":{"command":"jq -nr '$ENV.GOCLAW_GATEWAY_TOKEN'"}}tohttp://127.0.0.1:18792/v1/tools/invokeand receives the canary token in the JSON response. - Run the control:
python3 control-safe-policy.py - Observe that the control using
echo $GOCLAW_GATEWAY_TOKENis 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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