Summary
Pasting a comment URL copied from the Basecamp web app fails with "refusing untrusted host in URL". The web app serves both 3.basecamp.com and app.basecamp.com, and the API returns app.basecamp.com in every app_url regardless of which host was pasted. The CLI's trusted host allowlist only contains 3.basecamp.com and 3.basecampapi.com. The same URL works once the host is hand-edited to 3.basecamp.com.
Steps to reproduce
-
Open any card or todo in Basecamp, click a comment's timestamp to get its permalink, and copy the URL. It looks like:
https://app.basecamp.com/<account>/buckets/<project>/card_tables/cards/<card>#__recording_<comment>
-
Pass it to the CLI:
basecamp comments thread "https://app.basecamp.com/<account>/buckets/<project>/card_tables/cards/<card>#__recording_<comment>" --window 5
-
Change app.basecamp.com to 3.basecamp.com and run the same command.
Expected
Step 2 prints the comment thread. A URL copied straight from the app should be accepted the same way a 3.basecamp.com URL is.
Actual
Step 2 fails:
**Error:** refusing untrusted host in URL — expected a Basecamp URL
Step 3, with the host changed to 3.basecamp.com, prints the thread.
basecamp comments show fails the same way. basecamp show accepts the app.basecamp.com URL, so the two commands disagree about the same link.
The CLI's own output uses the rejected host. basecamp show <card-url> --json returns:
"app_url": "https://app.basecamp.com/3293071/buckets/5610905/card_tables/cards/10303126622"
So a script that reads app_url from one command and feeds it to comments thread fails.
Why this happens
hostutil.IsTrustedBasecampHost (internal/hostutil/hostutil.go) checks the URL host against a fixed allowlist:
var trustedBasecampHosts = map[string]bool{
"3.basecamp.com": true,
"3.basecampapi.com": true,
}
plus localhost and the host of the configured base URL. app.basecamp.com is none of those, so the check fails.
The check was added in #462 (v0.8.0) and now guards four commands: comments thread, comments show, chat update, and files replace. The other URL-taking commands go through urlarg.Parse, whose router is host-agnostic, which is why basecamp show accepts the URL.
Environment
- basecamp-cli v0.11.0 (Homebrew cask). The allowlist is unchanged on main at d91fc7b, which is after the v0.11.0 tag.
- Verified against production Basecamp on 2026-09-22
Summary
Pasting a comment URL copied from the Basecamp web app fails with "refusing untrusted host in URL". The web app serves both
3.basecamp.comandapp.basecamp.com, and the API returnsapp.basecamp.comin everyapp_urlregardless of which host was pasted. The CLI's trusted host allowlist only contains3.basecamp.comand3.basecampapi.com. The same URL works once the host is hand-edited to3.basecamp.com.Steps to reproduce
Open any card or todo in Basecamp, click a comment's timestamp to get its permalink, and copy the URL. It looks like:
Pass it to the CLI:
Change
app.basecamp.comto3.basecamp.comand run the same command.Expected
Step 2 prints the comment thread. A URL copied straight from the app should be accepted the same way a
3.basecamp.comURL is.Actual
Step 2 fails:
Step 3, with the host changed to
3.basecamp.com, prints the thread.basecamp comments showfails the same way.basecamp showaccepts theapp.basecamp.comURL, so the two commands disagree about the same link.The CLI's own output uses the rejected host.
basecamp show <card-url> --jsonreturns:So a script that reads
app_urlfrom one command and feeds it tocomments threadfails.Why this happens
hostutil.IsTrustedBasecampHost(internal/hostutil/hostutil.go) checks the URL host against a fixed allowlist:plus localhost and the host of the configured base URL.
app.basecamp.comis none of those, so the check fails.The check was added in #462 (v0.8.0) and now guards four commands:
comments thread,comments show,chat update, andfiles replace. The other URL-taking commands go throughurlarg.Parse, whose router is host-agnostic, which is whybasecamp showaccepts the URL.Environment