-
Notifications
You must be signed in to change notification settings - Fork 0
Security Model
| Component | Trust |
|---|---|
| Coordinator | The source of truth. Holds the database and every secret. |
| Agents | Do the browsing. They receive work and credentials, and report results. |
| doc-extract | Processes bytes. Makes no outbound calls, never fetches a URL. |
| Targets | Untrusted. Every page an agent visits is hostile input. |
- Web UI — JWT access token held in memory only, never in localStorage or a JS-readable cookie, so XSS cannot lift it. A rotating httpOnly refresh cookie re-mints it on reload.
-
API keys — prefixed
wt_, scoped per resource and action, fail-closed against a route table. See scopes. - Agents — long-lived infrastructure JWTs, revocable individually.
- Pairing codes — single-use, 15-minute, rate-limited enrolment credentials.
Second factors: TOTP and WebAuthn passkeys. Enforce with REQUIRE_ADMIN_MFA=true
once enrolled.
Credentials, TOTP seeds and session cookies live in Fernet-encrypted columns
keyed by SECRET_ENCRYPTION_KEY. See Backup and restore
for why that key must be stored separately — and why replacing it is as
destructive as losing it.
The coordinator will not start when:
- any required secret is missing, blank, or under 32 characters — HS256 signs
happily with an empty key, so a half-filled
.envwould otherwise leave the signing secret publicly known -
CORS_ORIGINSis*in production - more than one web worker is configured
ALLOW_PRIVATE_TARGETS defaults to false. Monitored, crawled and webhook
targets that resolve to private or loopback addresses are refused. Only enable
it if you deliberately monitor an internal host, and understand that it also
lets an automation reach your internal network.
FORWARDED_ALLOW_IPS must name only your proxy. Set to *, any client can
spoof X-Forwarded-For and bypass every per-IP rate limit and ban.
Shipped in the compose file, not left to you: read-only root filesystem with
only /data writable, noexec,nosuid tmpfs for /tmp, cap_drop: ALL,
no-new-privileges, non-root user, and application code that is world-readable
but writable by nobody — so a file-write primitive cannot become persistent code
execution.
/openapi.json, /docs and /redoc are off in production. The installer at
/agent.sh is public but contains no secret — it takes a pairing code as an
argument and fetches settings at run time.
Nothing here phones home. There is no analytics or error-reporting client in the coordinator or the extractor, and no usage data leaves the machine.
The coordinator is AGPL-3.0-only. If you modify it and let anyone else interact with it over a network, you owe them the complete corresponding source of your version.
The app makes that offer for you: it serves a public GET /api/about and links
it from the login screen and Settings → General. If you deploy a patched
build, set
WRIT_SOURCE_URL=https://github.com/you/your-forkso that link points at your source. It is the one thing a fork must change to stay compliant.
Use GitHub's private vulnerability reporting on usewrit/writ. Please do not open a public issue for a security problem.
usewrit/writ · AGPL-3.0-only · Issues · Discussions · Report a vulnerability
Getting started
Using it
Integrations
Operations
Reference