Resolution
Fixed in agent-device 0.20.4. Automatically discovered project configuration is now restricted to project-safe automation defaults; endpoint, authentication, provider, tenant, lease, header, cloud, Metro, and local-path settings require an operator-controlled source. Daemon authentication is also kept out of serialized RPC flags and registered with centralized diagnostics redaction.
Affected versions: >= 0.8.0, <= 0.20.3. Upgrade to >= 0.20.4.
Credit: Reported responsibly by @glmgbj233. Thank you for the clear report and isolated reproduction.
Summary
agent-device automatically loads ./agent-device.json from the current working directory for normal CLI commands. In the current upstream main revision de1654127eb43c4f8676af6ea599c429503d68b5 (package version 0.20.3), that project file may set daemonBaseUrl and daemonAuthToken. A user or CI token supplied through AGENT_DEVICE_DAEMON_AUTH_TOKEN overrides project-file values and is sent to the project-selected URL.
When a user runs a client-backed command such as agent-device devices from an attacker-controlled repository, the CLI performs a health request and then an authenticated RPC request to the configured endpoint. The token is sent in the Authorization and x-agent-device-token headers and is also carried in the RPC request. This behavior was dynamically reproduced against the latest upstream build with a local, isolated HTTP listener.
The finding is a project-configuration endpoint-redirection and conditional bearer-credential disclosure issue.
Preconditions and Accurate Trigger
The repository must contain a valid agent-device.json with an attacker-selected daemonBaseUrl. The user must have the agent-device CLI installed and run a client-backed command from that repository, for example agent-device devices. For an external non-loopback URL, the effective configuration must also contain a non-empty token; the token may be written by the repository or supplied by the user's environment, user configuration, or CLI arguments.
The precise trigger is running the CLI command from the repository's working directory. Merely cloning or opening the directory does not start the request. No model prompt, agent instruction, MCP invocation, or additional workspace approval is needed after the command is executed.
If the repository supplies only an attacker-known token, the attacker does not gain a new secret from that token. The repository still redirects the command traffic and exposes request metadata. The credential-disclosure impact arises when an effective user or CI token is available; AGENT_DEVICE_DAEMON_AUTH_TOKEN has higher precedence than project-file defaults and can therefore replace an attacker-provided project token.
The target must be reachable for the request to arrive. To reach the RPC stage, the endpoint must return a compatible health response; the health request itself is attempted before that compatibility check and already carries the token.
Impact
The minimum demonstrated devices request exposes the bearer token, command name, session, current working directory, daemon state directory, request identifier, positionals, and effective flags. Other commands can include command-specific selectors, paths, URLs, device data, or input values in their RPC parameters. The client does not automatically upload the entire environment or arbitrary local files merely because this configuration is present.
The leaked token is security-relevant when it is a valid credential for a remote daemon or proxy. The remote daemon request router compares the received request token with its configured daemon token before dispatching the command. The project proxy compares its client token and forwards authorized requests with its upstream token. Authenticated auxiliary upload and artifact routes also enforce the daemon token. Consequently, a captured valid token may allow an attacker to impersonate the client against the corresponding reachable remote service, subject to that service's session, tenant, lease, and device authorization rules.
This does not imply unrestricted access to every local device. Users who only use the default local daemon and have no remote daemon token are not in the credential-disclosure precondition. A project-supplied attacker-known token mainly creates traffic redirection and metadata exposure rather than disclosure of a victim secret.
Proof of Concept
The reusable isolated runner is poc/callstack-agent-device-latest-recheck/capture-and-run.mjs. It creates separate temporary configured and control projects, clears ambient daemon URL/config selectors, sets only a synthetic poc-secret-token, and runs devices --json against an HTTP listener bound to loopback. The listener returns protocol version 2 and an empty device result; it records request method, URL, headers, and body only.
The configured project contains the equivalent of:
{
"daemonBaseUrl": "http://127.0.0.1:<port>/agent-device"
}
The token exists only in the child environment:
AGENT_DEVICE_DAEMON_AUTH_TOKEN=poc-secret-token
The latest upstream build returned success for devices --json and the listener recorded:
GET /agent-device/health
Authorization: Bearer poc-secret-token
x-agent-device-token: poc-secret-token
POST /agent-device/rpc
Authorization: Bearer poc-secret-token
x-agent-device-token: poc-secret-token
JSON-RPC method: agent_device.command
params.command: devices
params.flags.daemonBaseUrl: http://127.0.0.1:<port>/agent-device
params.flags.daemonAuthToken: poc-secret-token
The no-project-config control used a separate temporary state directory and the same synthetic token with the URL/config selectors cleared. It returned the local device inventory and the capture listener recorded zero requests.
The PoC was run against de165412 after a locked dependency install and successful build with Node v24.18.0 and pnpm 11.17.0. The focused upstream configuration test suite also passed: 27 tests passed. The experiment used loopback and a synthetic token; it did not read credentials, contact an external host, modify shell profiles, or use the real daemon state directory.
Source-to-Sink Evidence
All source references below refer to the confirmed upstream revision de1654127eb43c4f8676af6ea599c429503d68b5.
1. Repository-controlled configuration is automatically discovered
website/docs/docs/configuration.md:7-22 documents ./agent-device.json as the project-level configuration source and states that environment variables override project and user configuration. The same document lists daemonBaseUrl and daemonAuthToken as supported configuration fields at :26-54.
src/cli/resolve-cli-options.ts:16-39 parses the command using the process cwd and invokes resolveConfigBackedFlagDefaults() before finalizing flags. src/cli-schema/cli-config.ts:29-41 resolves the default project path as path.resolve(cwd, 'agent-device.json'), and :22-26 merges file defaults before environment defaults.
2. Endpoint and token fields remain enabled for project config
src/cli-schema/option-schema.ts:24-31 excludes parser/meta fields such as config, remoteConfig, help, and version, but does not exclude daemonBaseUrl or daemonAuthToken. :59-100 builds configuration-enabled option specs from the CLI flag definitions and derives their environment names.
3. Effective values reach remote daemon initialization
src/cli.ts:135-163 resolves remote context, constructs the client from effective flags, and dispatches the client-backed command. src/daemon/client/daemon-client-lifecycle.ts:96-105 reads the effective URL and token, normalizes the URL, and calls the remote trust validator. :143-168 performs a remote health check before dispatching the command.
4. The trust check is not workspace-bound
src/daemon/client/daemon-client-lifecycle.ts:670-708 accepts HTTP(S) URLs, treats loopback hosts as trusted for the token-presence check, and requires only a non-empty token for other hosts. This is not a workspace trust decision and is not bound to the repository, revision, config digest, endpoint approval, or token source. No first-use or content-change confirmation occurs on the default project-config path.
Saved remote connection state has separate profile hash validation in src/remote/remote-connection-state.ts:112-166, but that state path does not protect the independent default agent-device.json loader demonstrated here.
5. The URL and token reach authenticated HTTP sinks
src/daemon/client/daemon-client-transport.ts:105-153 builds the health URL from info.baseUrl and adds token headers. :362-453 builds the RPC URL from the same base URL, sends POST, and adds the same headers. src/daemon/http-contract.ts:8-19 defines the Authorization: Bearer and x-agent-device-token headers.
6. A valid captured token authorizes daemon/proxy operations
For direct daemon dispatch, src/daemon/request-router.ts:145-147 compares the incoming request token with the daemon token before request execution. src/daemon/server/http-server.ts:748-811 enforces the expected token on auxiliary HTTP routes such as uploads and artifact access. For the project proxy, src/remote/daemon-proxy.ts:344-369 compares the client token and rewrites authorized RPC requests with the upstream token; :314-330 attaches upstream authentication headers.
Existing Protections and Why They Do Not Close the Issue
- URL parsing restricts the value to HTTP(S), but does not establish that the URL is trusted for the current repository.
- Non-loopback validation checks only that a token is non-empty. It does not distinguish a repository token from a user or CI token.
- Loopback URLs bypass even the token-presence check, although an available token is still sent by the client.
- Remote daemon authentication protects the remote service from unauthenticated callers; it does not protect a user's token from being paired with an attacker-selected project endpoint.
- Saved remote connection config hashing protects the explicit connection-state workflow, not the default project config path.
- The local daemon's own boot token and code-signature checks do not apply when the client selects a remote
daemonBaseUrl.
Recommended Remediation
- Remove
daemonBaseUrl, daemonAuthToken, daemon transport/server mode, tenant/lease selectors, and other connection-bearing or credential-bearing fields from the default project configuration allowlist.
- If project-defined remote endpoints must remain supported, require explicit confirmation before the first health request. Display the final normalized endpoint, token source, transport, and command surface rather than only the config filename.
- Bind approval to the canonical workspace, revision or project-config digest, user, endpoint, and launcher surface. Require renewed confirmation after the effective endpoint, token-bearing fields, or referenced connection profile changes.
- Do not automatically pair an environment or user token with a repository-selected endpoint. Require an explicit user action that approves that exact endpoint/token-source combination.
- Revalidate the final effective command-bearing configuration immediately before
/health and /rpc; creation-time approval must not silently carry across configuration changes.
- Keep a local receipt recording the exact approved endpoint, policy, token source class, workspace identity, and approval time. The receipt must not act as global trust for the repository.
Resolution
Fixed in
agent-device0.20.4. Automatically discovered project configuration is now restricted to project-safe automation defaults; endpoint, authentication, provider, tenant, lease, header, cloud, Metro, and local-path settings require an operator-controlled source. Daemon authentication is also kept out of serialized RPC flags and registered with centralized diagnostics redaction.Affected versions:
>= 0.8.0, <= 0.20.3. Upgrade to>= 0.20.4.Credit: Reported responsibly by @glmgbj233. Thank you for the clear report and isolated reproduction.
Summary
agent-deviceautomatically loads./agent-device.jsonfrom the current working directory for normal CLI commands. In the current upstreammainrevisionde1654127eb43c4f8676af6ea599c429503d68b5(package version0.20.3), that project file may setdaemonBaseUrlanddaemonAuthToken. A user or CI token supplied throughAGENT_DEVICE_DAEMON_AUTH_TOKENoverrides project-file values and is sent to the project-selected URL.When a user runs a client-backed command such as
agent-device devicesfrom an attacker-controlled repository, the CLI performs a health request and then an authenticated RPC request to the configured endpoint. The token is sent in theAuthorizationandx-agent-device-tokenheaders and is also carried in the RPC request. This behavior was dynamically reproduced against the latest upstream build with a local, isolated HTTP listener.The finding is a project-configuration endpoint-redirection and conditional bearer-credential disclosure issue.
Preconditions and Accurate Trigger
The repository must contain a valid
agent-device.jsonwith an attacker-selecteddaemonBaseUrl. The user must have theagent-deviceCLI installed and run a client-backed command from that repository, for exampleagent-device devices. For an external non-loopback URL, the effective configuration must also contain a non-empty token; the token may be written by the repository or supplied by the user's environment, user configuration, or CLI arguments.The precise trigger is running the CLI command from the repository's working directory. Merely cloning or opening the directory does not start the request. No model prompt, agent instruction, MCP invocation, or additional workspace approval is needed after the command is executed.
If the repository supplies only an attacker-known token, the attacker does not gain a new secret from that token. The repository still redirects the command traffic and exposes request metadata. The credential-disclosure impact arises when an effective user or CI token is available;
AGENT_DEVICE_DAEMON_AUTH_TOKENhas higher precedence than project-file defaults and can therefore replace an attacker-provided project token.The target must be reachable for the request to arrive. To reach the RPC stage, the endpoint must return a compatible health response; the health request itself is attempted before that compatibility check and already carries the token.
Impact
The minimum demonstrated
devicesrequest exposes the bearer token, command name, session, current working directory, daemon state directory, request identifier, positionals, and effective flags. Other commands can include command-specific selectors, paths, URLs, device data, or input values in their RPC parameters. The client does not automatically upload the entire environment or arbitrary local files merely because this configuration is present.The leaked token is security-relevant when it is a valid credential for a remote daemon or proxy. The remote daemon request router compares the received request token with its configured daemon token before dispatching the command. The project proxy compares its client token and forwards authorized requests with its upstream token. Authenticated auxiliary upload and artifact routes also enforce the daemon token. Consequently, a captured valid token may allow an attacker to impersonate the client against the corresponding reachable remote service, subject to that service's session, tenant, lease, and device authorization rules.
This does not imply unrestricted access to every local device. Users who only use the default local daemon and have no remote daemon token are not in the credential-disclosure precondition. A project-supplied attacker-known token mainly creates traffic redirection and metadata exposure rather than disclosure of a victim secret.
Proof of Concept
The reusable isolated runner is
poc/callstack-agent-device-latest-recheck/capture-and-run.mjs. It creates separate temporary configured and control projects, clears ambient daemon URL/config selectors, sets only a syntheticpoc-secret-token, and runsdevices --jsonagainst an HTTP listener bound to loopback. The listener returns protocol version2and an empty device result; it records request method, URL, headers, and body only.The configured project contains the equivalent of:
{ "daemonBaseUrl": "http://127.0.0.1:<port>/agent-device" }The token exists only in the child environment:
The latest upstream build returned success for
devices --jsonand the listener recorded:The no-project-config control used a separate temporary state directory and the same synthetic token with the URL/config selectors cleared. It returned the local device inventory and the capture listener recorded zero requests.
The PoC was run against
de165412after a locked dependency install and successful build with Nodev24.18.0and pnpm11.17.0. The focused upstream configuration test suite also passed: 27 tests passed. The experiment used loopback and a synthetic token; it did not read credentials, contact an external host, modify shell profiles, or use the real daemon state directory.Source-to-Sink Evidence
All source references below refer to the confirmed upstream revision
de1654127eb43c4f8676af6ea599c429503d68b5.1. Repository-controlled configuration is automatically discovered
website/docs/docs/configuration.md:7-22documents./agent-device.jsonas the project-level configuration source and states that environment variables override project and user configuration. The same document listsdaemonBaseUrlanddaemonAuthTokenas supported configuration fields at:26-54.src/cli/resolve-cli-options.ts:16-39parses the command using the process cwd and invokesresolveConfigBackedFlagDefaults()before finalizing flags.src/cli-schema/cli-config.ts:29-41resolves the default project path aspath.resolve(cwd, 'agent-device.json'), and:22-26merges file defaults before environment defaults.2. Endpoint and token fields remain enabled for project config
src/cli-schema/option-schema.ts:24-31excludes parser/meta fields such asconfig,remoteConfig,help, andversion, but does not excludedaemonBaseUrlordaemonAuthToken.:59-100builds configuration-enabled option specs from the CLI flag definitions and derives their environment names.3. Effective values reach remote daemon initialization
src/cli.ts:135-163resolves remote context, constructs the client from effective flags, and dispatches the client-backed command.src/daemon/client/daemon-client-lifecycle.ts:96-105reads the effective URL and token, normalizes the URL, and calls the remote trust validator.:143-168performs a remote health check before dispatching the command.4. The trust check is not workspace-bound
src/daemon/client/daemon-client-lifecycle.ts:670-708accepts HTTP(S) URLs, treats loopback hosts as trusted for the token-presence check, and requires only a non-empty token for other hosts. This is not a workspace trust decision and is not bound to the repository, revision, config digest, endpoint approval, or token source. No first-use or content-change confirmation occurs on the default project-config path.Saved remote connection state has separate profile hash validation in
src/remote/remote-connection-state.ts:112-166, but that state path does not protect the independent defaultagent-device.jsonloader demonstrated here.5. The URL and token reach authenticated HTTP sinks
src/daemon/client/daemon-client-transport.ts:105-153builds the health URL frominfo.baseUrland adds token headers.:362-453builds the RPC URL from the same base URL, sendsPOST, and adds the same headers.src/daemon/http-contract.ts:8-19defines theAuthorization: Bearerandx-agent-device-tokenheaders.6. A valid captured token authorizes daemon/proxy operations
For direct daemon dispatch,
src/daemon/request-router.ts:145-147compares the incoming request token with the daemon token before request execution.src/daemon/server/http-server.ts:748-811enforces the expected token on auxiliary HTTP routes such as uploads and artifact access. For the project proxy,src/remote/daemon-proxy.ts:344-369compares the client token and rewrites authorized RPC requests with the upstream token;:314-330attaches upstream authentication headers.Existing Protections and Why They Do Not Close the Issue
daemonBaseUrl.Recommended Remediation
daemonBaseUrl,daemonAuthToken, daemon transport/server mode, tenant/lease selectors, and other connection-bearing or credential-bearing fields from the default project configuration allowlist./healthand/rpc; creation-time approval must not silently carry across configuration changes.