Skip to content

fix: validate DEVIN_API_URL and disallow redirects on authenticated requests - #11

Merged
TheOnlyFusionCube merged 1 commit into
TheOnlyFusionCube:mainfrom
hessabi:fix/api-url-validation
Oct 23, 2026
Merged

TheOnlyFusionCube merged 1 commit into
TheOnlyFusionCube:mainfrom
hessabi:fix/api-url-validation

Conversation

@hessabi

@hessabi hessabi commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

Proposal 7 in #4.

What was wrong

  • cloud.mjs sent bearer-authenticated create/get/message calls to DEVIN_API_URL exactly as given. A malicious or mistyped value could send the API key to any host, or to a URL carrying credentials or a path.
  • Redirects on those requests were followed.

What changed

  • New resolveApiOrigin(env):
    • Unset: uses https://api.devin.ai.
    • The only other accepted value is exactly that origin, with an optional trailing slash; host casing is normalized.
    • Rejected before any fetch: userinfo, paths, queries, fragments, other ports, other hosts, plain http, whitespace or control characters, and look-alike or IDN tricks. Error messages never echo the raw URL.
  • Loopback is test-only and opt-in. It's accepted only with DEVIN_COMPANION_ALLOW_LOOPBACK_API=1, for http(s) on 127.0.0.1, [::1] or localhost, on any port.
  • getCloudConfig no longer throws. It returns apiUrl: null and apiUrlError. resolveApiBase and apiRequest both refuse to build or send a request when that error is set.
  • apiRequest uses redirect: "error" and treats any 3xx as an error.
  • /devin:setup reports a rejected URL instead of crashing, and /devin:handoff fails before it creates a job.
  • Docs updated: README, AGENTS.md, and the handoff command.

How tested

  • New tests/cloud-url.test.mjs, 16 tests, all against a mocked globalThis.fetch:
    • 58 malicious URL variants, each making zero fetch calls and sending no Authorization header, for create, get and message, with both personal and service keys.
    • The default and exact-origin values succeed.
    • Loopback is rejected without the opt-in and accepted with it.
    • A 302 makes no second request.
    • setup and handoff behave correctly through the CLI.
  • node --test: 44 pass, 0 fail.

DEVIN_API_URL was used verbatim as the destination for bearer-authenticated
create/get/message calls, so a malicious or mistyped value could send the
API key to an arbitrary host, and redirects were followed.

Add resolveApiOrigin: unset or exactly https://api.devin.ai only. Userinfo,
paths, queries, fragments, other ports, and other hosts are rejected before
any fetch. Loopback http(s) is allowed only with the explicit test opt-in
DEVIN_COMPANION_ALLOW_LOOPBACK_API=1. apiRequest now uses redirect "error"
and treats 3xx as an error. setup reports a rejected URL instead of
crashing, and handoff fails before creating a job.

@TheOnlyFusionCube TheOnlyFusionCube left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified test suite (44 passed across 58 origin/redirect assertions). Clean implementation of Proposal 7.

@TheOnlyFusionCube
TheOnlyFusionCube merged commit 8b5948d into TheOnlyFusionCube:main Oct 23, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants