Skip to content

Add bl curl for protected hosted apps - #35

Open
damienrj wants to merge 2 commits into
block:mainfrom
damienrj:bl-curl
Open

damienrj wants to merge 2 commits into
block:mainfrom
damienrj:bl-curl

Conversation

@damienrj

@damienrj damienrj commented Oct 26, 2026 •

Copy link
Copy Markdown

Adds bl curl, a supported way for agents and scripts to call protected BuilderLab hosted apps. Today hosted apps only accept the browser-derived hosted-site session, so there's no supported way to curl them, and people copy browser cookies instead.

bl curl https://my-app.apps.block.builderlab.xyz/llms.txt
bl curl -X POST https://my-app.apps.block.builderlab.xyz/_compose/server/upload --data-binary @session.jsonl.gz

The first call for a tenant runs a type=cli_hosted browser login, which reuses the existing loopback, state and PKCE flow. It stores the resulting hosted-app session in the existing auth storage, separate from the normal CLI session, then runs the system curl with that session as the hosted-site cookie.

Security

  • Only https://<app>.apps.<org>.<root> URLs for the configured org are accepted. Anything else is refused before any login or request. A session is stored only if the server's cookie_domain is exactly that tenant's hosted-app domain.
  • The cookie and added headers are passed to curl on stdin with --config -, never on the command line. -q ignores ~/.curlrc, and --globoff keeps the URL to one request.
  • Only an allowlist of curl options is accepted. Options that could print or forward the cookie are refused (-v, --trace*, --libcurl, -L, -b, -c, -K, -k, --resolve, -u, extra URLs), as are request bodies read from stdin. Negations that would undo an enforced option or hide the request method (--no-globoff, --no-disable, --no-get, --no-head) are refused too.
  • POSTs to /_compose/server/* get Origin: https://<host> unless the user sets one.
  • A 401 with X-BB-Consent-Url, or a {"error":"consent_required"} body, prints the approval URL and exits 3.
  • A 401 with WWW-Authenticate: BBIdentity on a stored session drops it and logs in again. Only GET, HEAD and OPTIONS are resent automatically; other methods exit 3 and ask the user to rerun, so a mutation is never submitted twice.
  • bl auth logout revokes each stored hosted session on the server (best effort), then removes it locally.

Depends on

A matching BuilderLab backend change that adds cli_hosted sessions to hosted-app ingress, and an ingress change that forwards the consent header on denials. Until those ship, bl curl gets the existing 401.

Open questions

  • I assumed the hosted credential comes back in the existing session_credential field of the exchange response. The backend PR does it that way.
  • cookie_domain must equal apps.<org>.<root>, ignoring a leading dot. A broader domain is refused.
  • Hosted-app roots are a fixed list (builderlab.xyz, builderlabstaging.xyz). Other environments would need adding.

Run curl against hosted apps with a cli_hosted session passed as the hosted-site cookie on stdin. Hosts outside the configured tenant's hosted-app domain are refused, consent and rejected sessions are handled, and bl auth logout removes stored hosted sessions.
@damienrj
damienrj marked this pull request as ready for review October 26, 2026 22:28
@damienrj
damienrj requested a review from a team as a code owner October 26, 2026 22:28

@nathan-thillairajah nathan-thillairajah left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 left three inline comments on mutation retries, hosted-session logout, and URL globbing.

Comment thread src/bl/curl.rs
Comment thread src/bl/skills.rs Outdated
if let Some(org) = config.org.as_deref() {
for hosted_domain in super::curl::hosted_app_domains_for_org(org) {
let key = super::auth_storage::hosted_session_storage_key(config, &hosted_domain);
match storage.delete(&key) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🤖 can we revoke these hosted sessions before deleting the local copies? Local deletion leaves any retained credential valid for the rest of its eight-hour lifetime. The matching backend supports revocation through the hosted-cookie logout path, so this can follow the normal CLI session's best-effort revoke-then-delete behavior. A test should check that the old hosted credential is rejected after logout.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

🤖 Fixed in b2405b4. bl auth logout now sends each stored hosted session to POST /v1/auth/logout as the hosted cookie (using the stored cookie_name), then deletes it locally whatever the result, matching the CLI session's revoke-then-delete. The JSON output reports per-session server_revoked/removed. revoked_hosted_session_is_rejected_by_a_later_request uses a stateful test server that rejects the credential after logout; one caveat is that the follow-up request goes through the request code directly rather than the bl binary, since bl curl only allows https and the test server is plain http.

Comment thread src/bl/curl.rs Outdated
Resend a request after logging in again only for GET, HEAD and OPTIONS; other methods refresh the session and ask the user to rerun. Revoke stored hosted-app sessions with their hosted cookie on bl auth logout before deleting them. Refuse --no-globoff, --no-disable, --no-get and --no-head, and treat a consent_required JSON body as consent even without X-BB-Consent-Url.
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