Skip to content

Cloud calls send the rth_at_ bearer to the stored base_url without a scheme check #56

Description

@khaliqgant

Raised by CodeRabbit during review of #55 (ai-hist coverage). Declined there as out of scope, because fixing it in one function would have made the CLI less consistent, not more. Filing so it isn't lost.

The issue

StoredAuth.base_url can hold an http:// URL, and three call sites send the service-local rth_at_ bearer to it with no scheme validation:

call site file
UreqIngestor::ingest (push) crates/ai-hist/src/cloud.rs:227
pair_check crates/ai-hist/src/cloud.rs:526
fleet_coverage crates/ai-hist/src/cloud.rs (added in #55)

validate_cloud_exchange_base_url (cloud.rs:415) exists but is wired at exactly one site — login_via_cloud (cloud.rs:409). That is deliberate and correct as far as it goes: it guards the Agent Relay Cloud bearer at exchange time. It says nothing about the service-local token afterward.

Why it wasn't fixed in #55

Enforcing HTTPS in fleet_coverage alone would have:

  • broken the documented http://localhost:8787 wrangler dev flow for coverage while push and pair kept working against the same URL, and
  • closed none of the actual exposure — push runs on a 300s timer, so it is the high-frequency path by a wide margin.

A one-command fix would have been visible without being useful.

What a real fix looks like

Validate once, centrally — ideally where StoredAuth is loaded or where the request is built, so all three sites are covered and no future call site can miss it:

  • require https:// by default;
  • allow loopback (127.0.0.1, ::1, localhost) so local wrangler dev keeps working;
  • gate any other plaintext host behind an explicit opt-in, mirroring the existing RELAYHISTORY_ALLOW_UNTRUSTED_CLOUD_BASE_URL precedent rather than inventing a second mechanism.

Worth doing as one deliberate change with tests per call site, not piecemeal.

Severity

Low in practice — the stored base_url is set by the user's own login, so this is a footgun rather than a remote attack. It matters most for a --base-url typo or a dev endpoint left configured on a machine that then runs the push service unattended.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions