---
title: "Documentation — PageWeave"
---

> For the full documentation index, see [PageWeave Documentation](https://pageweave.dev/docs) or fetch [llms.txt](https://pageweave.dev/llms.txt).

# Password Protection

PageWeave can password-protect the surfaces a website is served on. Two independent gates, same mechanics:

| Surface | What it protects | Password set via |
|---------|------------------|------------------|
| **Any environment** — including the default ("live") | Every request to the environment's hosts: the generated `*.env.pageweave.site` host and custom hostnames bound to it | `update_environment` `password` / `remove_password`; `create_environment` optional `password` |
| **Release permalinks** (`*.r.pageweave.site`) | All `*.r.*` share links of the website, sitewide | `update_website` `release_password` / `remove_release_password` |

The classic workflow is pre-launch privacy: protect the default environment (or a staging environment) while the site is being built, then remove the password to go live — the same pattern Shopify uses for new stores.

## How the gate works

Every request to a protected surface requires a credential:

- **Humans** get a localized password page. A correct password issues an encrypted, host-only cookie (30 days, `HttpOnly`, `Secure`, `SameSite=Lax`). The cookie binds a sub-second timestamp of the password's last change — rotating or removing the password invalidates every cookie already issued, immediately.
- **Automation** sends the password as a bearer header: `Authorization: Bearer <password>`. Header-only — no query-parameter tokens (they leak via logs and referrers).

Wrong credentials get `401` with the gate page. The gate POST is rate-limited to 10 attempts per minute per IP.

Each surface's password is independent: the environment password never unlocks release permalinks, and each environment has its own password.

## Caching

Gated surfaces are never publicly cacheable. Every response — the 401, the gate form, and **granted** requests — carries `Cache-Control: private, no-store`. Conditional-GET (`ETag`/`Last-Modified`) still works for automation, but hash-marked immutable URLs (snippets, stylesheets) drop their year-long public cache on gated surfaces: a cache must never outlive a password rotation.

## Storage security

Passwords are stored as bcrypt digests only. The plaintext is never stored — not in the database, not in workflow approval state, not in tool-call logs (tools pre-hash it into an opaque marker before anything is persisted). Agents should relay the password to the user in the same turn it is set; later calls cannot retrieve it.

There is no password reset by design: the owner can always remove or rotate the password via MCP (`remove_password` never asks for the old one).

## Caveats

- While the default environment is protected, the showcase screenshot pipeline and search crawlers only see the gate page — protecting an already-indexed live site will de-index it until the password is removed. Intended for pre-launch privacy.
- Release permalinks served before a release-links password was set were public while they served; set the password before sharing links for gated content.

## MCP usage

```json
{"name": "update_environment", "arguments": {"website": "acme", "environment": "staging", "password": "correct horse battery staple"}}
```

Responses carry the state as flags: `protected: true/false` on environments (`list_environments`, `get_website`, `create_environment`, `update_environment`) and `release_protected` on the website (`get_website`, `update_website`). Approval cards show protection turning on/off — never the password itself.
