An application you deploy with b3os apply gets one address per addressed port: https://<label>.b3os.io. The commands on this page come with CLI 0.2.1, not published yet.

Addresses are being turned on per organization. Until yours has them, b3os apply prints preview URLs as before, and the address routes of the API answer 404 feature_not_enabled. When B3OS cannot read whether yours has them, they answer 503 flags_unavailable: try again.

What you get

  • b3os apply ends with the address, such as https://gpt-researcher.b3os.io. It never changes: not on a push, a rollback, a restart after a crash, or with time.
  • Your app is served at / on its own origin. No path prefix.
  • A push switches the address to the new generation once that generation passes its readiness probe. If the switch fails, the deploy is undone and the old generation keeps serving. When two deploys race, the newer one gets the address, and its access and cookies stand: an older deploy still running that would set them again stops as stale (app_host_route_conflict, reason newer_deploy), and so does every deploy that began before you changed them outside a deploy. Every change to an address that serves (a switch, a rotate, a block, b3os app down) reaches its visitors within 6 s; longer while B3OS cannot read its address table.
  • When nothing serves it, the address answers a page of its own: 503 app_deploying until the first deploy switches it (the page says <label> is not live yet and loads the app by itself when the deploy finishes), 503 app_not_running after b3os app down or while the service is down, and 410 app_blocked if B3 takes it offline. An unknown label is 404 app_not_found. An address that finds its service down answers 503 for the next 2 s without asking again, so the first good answer after it comes back can wait that long.

An address is shared by link unless you say otherwise.

  • b3os app link prints a link, and the console's Copy link copies one. A link works 7 days; --ttl sets another lifetime, up to 90 days. Share it like a password.
  • The first visit with a link sets a cookie, and that browser stays in for 30 days. Neither reaches your app: B3OS takes the link out of the URL (the browser is sent on to the same page without it) and out of the x-b3os-link header, and the cookie out of the request, before it goes on.
  • A visit without a valid link gets 401 app_link_required, the same for a missing and a wrong link.
  • A script or another non-browser client sends the link's token, the part after b3os_link=, in an x-b3os-link header.
  • A page on another origin that calls a link address from the browser must put ?b3os_link=<token> in the URL: the browser's preflight (OPTIONS) carries no header and no cookie, and without the link in the URL it gets 401. Calls from the app's own pages never preflight.
  • An image or script of a link address on another site's page arrives without the browser's cookies, so its URL needs ?b3os_link=<token> too. A public address needs none.
  • A link address never serves a sandbox whose port is also public: that port's preview URL would reach the app without a link. A public address turns link only on a sandbox whose port is private (422 app_host_port_public).
  • b3os app link --rotate, or Rotate link in the console: every link stops working within 6 s, and so does every browser a link let in; streams already open can run up to an hour.

access: public opens the address to anyone on the internet. Its B3OS calls spend from your project, so a public address needs grant.spend_cap_usd on its service, and the plan stops without one. In the beta, public addresses are open only to organizations B3 has allowed (apps.hosts.public_max, 0 by default; limits).

In b3os.yaml

Four keys on a service's port. A port has an address when it sets access, or public: true, which means access: link. A port cannot be 53, 1080, 3128, 7700 or 15001: B3OS's agent in the sandbox uses them. An application deployed before addresses keeps its file: its next apply gives each public: true port its address, and the port's preview URL ends with the old generation. When nothing else in the service changed, the plan says why it replaces the generation: port 8000 now has the address https://gpt-researcher.b3os.io; its preview URL ends with <old sandbox>; people need a link (b3os app link). Otherwise it shows the address line below and the service's own changes.

KeyValuesDefault
accesslink or publiclink
hostthe label to ask for, read when the port is first claimedthe application's name (names)
primarytrue on the port that gets the application's own label; needed when more than one port has an address and any port sets access, host, primary or cookies. With only public: true ports, the first in the file gets the label and the plan says which.false
cookiesisolated, shared, or { mode, script: [names] } (cookies)isolated
yaml
version: 1name: shopservices: - name: web command: node server.js ports: - port: 8000 access: link primary: true cookies: { script: [theme] } - name: api command: [python3, api.py] grant: { spend_cap_usd: 5 } ports: - port: 9000 access: public

web gets https://shop.b3os.io, shared by link, and api gets https://shop--api.b3os.io, public. The plan says each address before it is claimed, and once it is yours:

text
+ address https://shop.b3os.io (free now)= address https://shop.b3os.io

and under a public one, anyone on the internet can use this service; its B3OS calls spend from this project, up to $5. b3os apply --dry-run claims nothing: "free now" means another organization may still claim the label first. The plan stops on these, with these words:

The plan saysFix
port 8000: host, primary and cookies need an address (set access, or public: true)give the port access
2 ports have an address and none is primary: mark one with primary: truemark one port primary: true (not asked of a file whose addressed ports are all plain public: true)
more than one port is primarykeep one
port 9000: access public needs grant.spend_cap_usd on this serviceset the service's spend cap
port 9000: access public is not open to this organization yet (apps.hosts.public_max)ask B3, or use access: link
port 8000: host api: api is reservedask for another host (names)
port 8000: support-bot: has the word support, which no address may hold; set host on the portset host: the application's name holds a word no address may hold (names)
21 addresses are over your limit of 20 (apps.hosts.max): labels are kept for good (b3os app down frees none), and B3 raises the limit on requestask B3 to raise it, or deploy under an application that already has an address
no address is free for shop: set host on port 8000set host
port 8000: shop.b3os.io: B3OS has taken this address offline. Visitors get 410; contact supportask B3: while one of an application's addresses is blocked, no apply of it goes through

A change to access or cookies takes effect when the next apply switches the address, not before: visitors of the old generation keep the old rules until then. With no new generation it takes effect as that apply finishes. An address turns public only on a generation that has a spend cap. host and primary are read only when a port is first claimed: a label never moves to another port, and a port never changes its label.

Cookies

Cookies are isolated by default because, until b3os.io is on the Public Suffix List, browsers treat every app on b3os.io as one site. A page on another app could set a cookie for all of b3os.io, and your server would take it for its own. So B3OS stores each cookie your server sets as __Host-b3c.<name>, which only your own address can set, and gives your server back only those, under their own names. A cookie without HttpOnly is also set under its own name, so your page scripts read it as before.

A cookie your page script writes (document.cookie) never passed through B3OS, so it is dropped on its way to your server unless you list its name under cookies.script. b3os app status and the console name each cookie this address did not set and dropped in the last 7 days, with how many requests it was dropped on. A name there may be one another b3os.io app's page set for all of b3os.io, so list a name only if your own page script sets it.

What breaks under isolation:

  • A cookie set with a Path other than / loses its path: it is sent on every path.
  • Two cookies of one name on different paths become one.

A cookie your server sets SameSite=Strict is stored as __Host-b3s.<name>, and B3OS does not pass it on a request that comes from another app on b3os.io, also when that is a link the visitor follows (a browser would not send a Strict cookie across sites).

cookies: shared turns isolation off for a port: Set-Cookie passes with its Domain removed, and your server gets the cookies the browser sends, those other apps' pages set for all of b3os.io included. Under shared no cookie is stored under a second name, so a Strict cookie does reach your server from another app's link. In either mode Cloudflare's own cookies never pass, in either direction: CF_Authorization, __cf_bm and four more, and any name that starts with cf_, __cf or _cf. A cookie listed under script, and under shared every cookie, your server's own included, reaches your server only with a plain value: one with a space, comma, backslash, quote or control character inside it is dropped. Encode such a value (encodeURIComponent does).

What your app sees

  • Host and X-Forwarded-Host are the address (shop.b3os.io), X-Forwarded-Proto is https, and there is no X-Forwarded-Prefix.
  • X-Forwarded-For is the client's address.
  • B3OS_APP_URL in the service's environment is the address of its first addressed port, the primary first: https://shop.b3os.io for web, https://shop--api.b3os.io for api. Use it for an allowed-hosts list, a dev server's host check or an OAuth callback.

On the way out, a Clear-Site-Data header loses "cookies" (it would clear every app on b3os.io), and B3OS adds Strict-Transport-Security and Origin-Agent-Cluster: ?1 when your app sends neither. B3OS also adds Content-Security-Policy: frame-ancestors 'self' with the application's own addresses to an HTML answer that has neither a frame-ancestors policy nor X-Frame-Options, so no other site and no other app on b3os.io can frame it. To be embedded, send your own frame-ancestors or X-Frame-Options: B3OS then leaves framing to you, and another app on b3os.io can frame yours with your visitors' cookies, since browsers count all of b3os.io as one site until it is on the Public Suffix List. A link address also gets X-Robots-Tag: noindex.

Limits

  • A request body is at most 64 MiB (413 payload_too_large).
  • A request or a stream lasts at most an hour.
  • A stream that sends nothing for 100 s is cut: send a heartbeat (an SSE comment, a WebSocket ping).
  • A response must start within the port's first_byte_s, 65 s unless it declares more (slow responses).
  • One client gets at most 600 requests a minute across b3os.io, counted at each Cloudflare location (an IPv6 client counts as its whole /64), then 429 rate_limited.
  • A sandbox serves at most 256 requests at once through its addresses, 64 of them from one client address (an IPv6 client by its /64). A request that finds no place waits up to 2 s for one, then gets 429 concurrency_limited with Retry-After: 1.
  • No gRPC.
  • When a push switches the address, the old generation stops seconds later: its open WebSocket and SSE connections close, and clients reconnect to the new one.

Names

  • A label is 1 to 63 lowercase letters, digits and hyphens, with no hyphen at either end. A longer one is cut and ends in 6 hex digits.
  • The default is the application's name. If that is taken or is a reserved word, <name>-<org slug>, then <name>-<6 hex digits of your organization's ID>. A name that holds a word no address may hold (below) gets no default address, since every default holds the name: set host on the port. One project gets these: the one the application's GitHub repository link names, else the first to deploy it. Its other projects (environments are projects in the beta) get <name>-<6 hex digits of the project's ID>.
  • -- is B3OS's. A further addressed port gets <label>--<service> (<label>--<service>-<port> when its service has several), which only the label's owner can get. A label of 2 characters gets none (its -- would stand at characters 3 and 4, which browsers read as an encoded international name), and neither does one of more than 55: set host on each further port. host cannot hold --, and an application name with -- gets - in its label.
  • First come: the first b3os apply to claim a label gets it, and the same letters without hyphens count as the same label (gptresearcher beside gpt-researcher), as does a digit for the letter it looks like, or i for l (acme-sh0p beside acme-shop, sh1p beside ship).
  • Reserved: words B3OS keeps (api, app, www, docs, status, demo, test, xn and more), anything that names B3OS (b3os, b3iq, or b3 between hyphens), and a word phishing uses (login, pay, support, admin, wallet and more), which no address may hold, B3's own included. A hyphen does not get around them, and neither do digits for letters: ad-min reads as admin, log-in and l0gin as login, supp0rt as support, b-3os and b30s as b3os. app_host_label_reserved says which.
  • An organization holds at most 20 addresses, stopped ones included (apps.hosts.max; limits).
  • A label stays yours. b3os app down stops it and keeps it, and no other organization ever gets it.

Not there yet

  • A link address inside another site's iframe: the frame's load carries no link, so it gets 401.
  • A page on one link address calling a second link address of the same application. Serve both from one port, or make the API public with its own auth.

Shared-domain limits

B3OS acts as the boundary between apps: a request another app's page sends carries no cookies (a plain page load still does), and the cookie rules above keep each app's server to its own. In Safari before 16.4 an app that sends Referrer-Policy: no-referrer gets no cookies on its own requests that are not page loads (images, scripts, fetch GETs), because B3OS cannot tell them from another app's; use same-origin or the default policy. The same holds for a client that is no browser but sends a browser's User-Agent with cookies: send the link in x-b3os-link, or an Origin of the app. Two gaps stay open until b3os.io is on the Public Suffix List and browsers have shipped it. A password manager may offer a password saved on one app on any other app's page. And a page script can set a cookie for all of b3os.io that other apps' page scripts can read; their servers do not get it while they stay isolated. Keep secrets out of cookies your scripts can read, and prefer a sign-in provider to a password form of your own.

Ask a question... ⌘I