Summary
check_edge_access validates its public_ip input but not its pubkey input, then passes both as argv elements to the doublezero CLI. A pubkey that begins with a dash is read by the CLI as a flag rather than as a value for --user-payer.
This arrived in #770 (commit 472d815), which moved the access-pass lookup off ClickHouse and onto an exec of doublezero access-pass get --user-payer <pubkey> --client-ip <ip> --json. It is deliberately out of scope for that PR.
The handler trims pubkey and checks only that it is non-empty. The IP alongside it goes through net.ParseIP, a To4() check, and a re-serialize. The pubkey then reaches lookupEdgeAccessPass, which hands it to exec.CommandContext as an argv element.
Bounding the severity: exec.CommandContext takes an argument slice and does not involve a shell, so this is argument injection, not command injection, and there is no path to arbitrary command execution. What an MCP caller can do is steer the flags of the CLI invocation, for example by sending -h or --json as the pubkey, and get a confusing tool error or unintended CLI output in place of an access-pass lookup.
Fix
Validate the pubkey as a base58-encoded 32-byte value before the lookup call, rejecting it in the same shape as the existing empty-input and invalid-IP errors.
References
- Handler input checks:
|
pubkey := strings.TrimSpace(input.Pubkey) |
|
ip := strings.TrimSpace(input.PublicIP) |
|
if pubkey == "" || ip == "" { |
|
return nil, CheckEdgeAccessOutput{}, errors.New("both pubkey and public_ip are required") |
|
} |
|
parsed := net.ParseIP(ip) |
|
if parsed == nil || parsed.To4() == nil { |
|
return nil, CheckEdgeAccessOutput{}, errors.New("public_ip must be a valid IPv4 address") |
|
} |
|
ip = parsed.To4().String() |
|
|
|
pass, err := a.lookupEdgeAccessPass(ctx, pubkey, ip) |
- CLI invocation:
|
out, err := run(ctx, "access-pass", "get", "--user-payer", userPayer, "--client-ip", publicIP, "--json") |
Summary
check_edge_accessvalidates itspublic_ipinput but not itspubkeyinput, then passes both as argv elements to thedoublezeroCLI. A pubkey that begins with a dash is read by the CLI as a flag rather than as a value for--user-payer.This arrived in #770 (commit 472d815), which moved the access-pass lookup off ClickHouse and onto an exec of
doublezero access-pass get --user-payer <pubkey> --client-ip <ip> --json. It is deliberately out of scope for that PR.The handler trims
pubkeyand checks only that it is non-empty. The IP alongside it goes throughnet.ParseIP, aTo4()check, and a re-serialize. The pubkey then reacheslookupEdgeAccessPass, which hands it toexec.CommandContextas an argv element.Bounding the severity:
exec.CommandContexttakes an argument slice and does not involve a shell, so this is argument injection, not command injection, and there is no path to arbitrary command execution. What an MCP caller can do is steer the flags of the CLI invocation, for example by sending-hor--jsonas the pubkey, and get a confusing tool error or unintended CLI output in place of an access-pass lookup.Fix
Validate the pubkey as a base58-encoded 32-byte value before the lookup call, rejecting it in the same shape as the existing empty-input and invalid-IP errors.
References
lake/api/handlers/mcp_onboarding.go
Lines 306 to 317 in ef656fc
lake/api/handlers/mcp_onboarding.go
Line 352 in ef656fc