Grants and visibility
How VibeHost decides who can see and act on an app. All gates AND-compose.
To do anything on an app, every active gate must pass. Hard Rule #8: gates AND-compose, never short-circuit. Adding a new gate is always ANDed in.
The four gates
┌─────────────┐ ┌──────────┐ ┌──────────────┐ ┌──────────────┐
│ Visibility │ ∧ │ Grants │ ∧ │ Password │ ∧ │ Share link │
│ public/ws/ │ │ team or │ │ (if set) │ │ (if used) │
│ private │ │ email │ │ │ │ │
└─────────────┘ └──────────┘ └──────────────┘ └──────────────┘
│ │ │ │
└──────────── all four AND ─────────┴──────────────────┘
│
▼
access grantedA viewer must satisfy all active gates. Disabling a gate (e.g. password clear) doesn't bypass the others — it just removes one term from the AND.
Visibility
Set per-app, decides who can hit the app at all:
| Visibility | Who can request |
|---|---|
public | Anyone on the internet (no auth required) |
workspace | Any workspace member |
private | Only users with an explicit grant |
vibehost app visibility public --app my-site
vibehost app visibility workspace --app my-site
vibehost app visibility private --app my-siteVisibility alone doesn't grant action permissions (deploy, settings) — those still need a grant.
Grants
Two grant tables: team-based and email-based. Both have the same role enum:
| Role | Can do |
|---|---|
viewer | View deployments, see logs |
deployer | Above + push new deploys, promote, rollback |
admin | Above + manage grants, settings, custom domains |
Grants are additive. If both your team and your email have grants, the effective role is the max (admin > deployer > viewer).
# Team grants (everyone in the team picks up the role)
vibehost app grants add-team web deployer --app my-site
vibehost app grants remove-team web --app my-site
# Email grants (one person, no team needed)
vibehost app grants add-email contractor@x.com viewer --app my-site
vibehost app grants remove-email contractor@x.com --app my-site
# List both axes
vibehost app grants ls --app my-site
# Grant yourself (for testing)
vibehost app grants self admin --app my-siteEmail grants work before the invitee signs up. The grant row sits in app_email_grants with acceptedUserId = null; on first login with a matching email, the system resolves the user.
Password gate
Optional second factor on top of visibility + grants:
vibehost app password set <password> --app my-site
vibehost app password clear --app my-site
vibehost app password status --app my-siteOnce set, anyone hitting the public URL sees a password prompt. After correct entry, a signed cookie remembers them for the session.
Use case: soft launches. "Public visibility, but only people I share the password with can see it."
Share links
Generate single-use cookie-issuing URLs:
vibehost app share-link create --app my-site
vibehost app share-link ls --app my-siteEach share link is a one-time URL like https://my-site.vibehost.space/_share/<token>. Visiting it sets a cookie tied to that share link; the cookie satisfies the share-link gate for that browser.
Use case: client previews. "Send this link to my client; revoke it next month."
How a request gets evaluated
When a viewer hits my-site.vibehost.space/index.html:
- Visibility check. Public → continue. Workspace → must be workspace member. Private → must have a grant.
- Grant check (for action endpoints). Read action → need viewer+. Deploy → need deployer+. Settings → need admin.
- Password check. If
passwordHash IS NOT NULLand no validvh_app_pw_<id>cookie → 401 password prompt. - Share-link check. If any share links are active and no valid
vh_share_<id>cookie → 401 password prompt (same UX as #3).
All four must pass. The dispatcher (apps/dispatcher/worker.js) runs these in order; the first failure short-circuits the response.
What this means in practice
| Setup | Who can view |
|---|---|
public, no password, no share links | Anyone |
public, password set | Anyone who knows the password |
workspace, no password | Workspace members only |
private, no extras | Users with explicit grants only |
private + password + share link | Grant-holders who also know the password OR clicked the share link |
The model is intentionally additive — every gate you turn on tightens, never loosens.