Skip to content

comments thread and comments show refuse app.basecamp.com URLs #782

Description

@mrhead

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

  1. 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>
    
  2. Pass it to the CLI:

    basecamp comments thread "https://app.basecamp.com/<account>/buckets/<project>/card_tables/cards/<card>#__recording_<comment>" --window 5
    
  3. 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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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