Configuration names per target, where secrets live, and the browser and CMS security controls in source.

Reference Value
Subject Environment variables, secret handling, headers and CSP, indexing, privacy boundaries; for anyone touching configuration or integrations
Verified source main at 9dc18495 (linked f40e8704 and 9480caab files are unchanged there); branch rules and CodeQL setup read from the GitHub API 2026-11-07; branch rules re-read 2026-11-10 (ruleset 15785309, org ruleset 26919834); CSP, the analytics relay, build image reads and the CMS image supply chain re-verified on main at 0461e045 (#649, #651, #677, #693), checked 2026-11-10
Support state Storage and privacy contracts checked against main 9dc18495 and the live cookies policy November 5, 2026. Provider-side secret values are not asserted here

Variable names come from .env.example, apps/cms/.env.example, apps/web/src/env.ts and turbo.json. Values live only in ignored local files, 1Password, or the deployment target. Never commit a filled dotenv file.

Configuration by target

Variable Used by Target
PUBLIC_PLAUSIBLE_DOMAIN Web build, public static Vercel Production yesid.dev; empty turns analytics off
PUBLIC_DIRECTUS_URL Web build, public static; dev-only media fallback cms.yesid.dev in production; may be empty
OPENWEATHER_API_KEY /api/weather, server only Vercel Production only; absent means weather returns null
DIRECTUS_BUILD_TOKEN Exporter, read-only Build Bot: content and referenced image originals, read only from the pinned https://cms.yesid.dev origin Vercel Production only; listed in the turbo.json build env
VERCEL_ENV Exporter source policy, CSP preview allowances Set by Vercel
EXPORT_FALLBACKS_SOURCE, EXPORT_FALLBACKS_LIVE Exporter fixture by default; live for explicit local operator exports
DIRECTUS_URL, DIRECTUS_ADMIN_TOKEN, DIRECTUS_ADMIN_EMAIL, DIRECTUS_ADMIN_PASSWORD directus-sync and CMS scripts Declared CLI inputs only. Admin login is two-factor and is not stored in .env; confirmed writes use the owner's browser session
KEY, SECRET, PUBLIC_URL, LOG_LEVEL, ADMIN_EMAIL, ADMIN_PASSWORD (admin bootstrap), DB_*, STORAGE_LOCATIONS, STORAGE_S3_*, CACHE_*, SYNCHRONIZATION_STORE, RATE_LIMITER_*, WEBSOCKETS_*, EMAIL_*, CORS_*, MCP_*, STORAGE_ASSET_TRANSFORM, REVISIONS_RETENTION Directus runtime Railway service; schema in apps/cms/.env.example. The single instance uses memory stores; set no REDIS or REDIS_* variable (apps/cms/.env.example:92)
VERCEL_DEPLOY_HOOK_URL Publish site flow Railway service env, read by the Publish site request operation as {{$env.VERCEL_DEPLOY_HOOK_URL}} (directus/collections/operations.json:62)
DIRECTUS_PROD_ADMIN_TOKEN cms workflow jobs GitHub Actions secret used for the read-only production diff; declared write jobs require separate authority; confirmed writes use the owner's browser session
TURBO_TOKEN, TURBO_TEAM Optional Turbo remote cache in web.yml GitHub secret and variable
VERCEL_AUTOMATION_BYPASS_SECRET, PLAYWRIGHT_BASE_URL Manual E2E against a protected preview Local shell only

Vercel previews receive neither the build token nor live export. The exporter refuses fixture mode on Vercel Production and ignores a live request on any other Vercel target.

Browser controls

Control Where Contract
CSP csp-policy.mjs, SvelteKit hash mode default-src 'self'; scripts permit self, the theme boot hash from @yesid/ui/boot.json and static.cloudflareinsights.com; connect-src adds only Web3Forms and transit.yesid.dev, since Plausible events go to same-origin /relay/e; CMS images are same-origin files, so no CMS origin is allowed; frame-src allows only https://www.youtube-nocookie.com and img-src adds https://i.ytimg.com for video players; previews add Vercel toolbar origins
Static headers apps/web/vercel.json frame-ancestors 'none', X-Frame-Options: DENY, nosniff, strict referrer, restrictive Permissions-Policy, Cross-Origin-Opener-Policy: same-origin. Exact shared SECURITY_HEADERS equality is tested except HSTS; apex HSTS is provider-owned max-age only, with no includeSubDomains/preload
Site indexing hooks.server.ts, src/lib/server/indexing.ts, robots.txt route Any host other than yesid.dev and www.yesid.dev gets noindex, nofollow, noarchive and a disallow-all robots file. This is not access control
CMS indexing apps/cms/extensions/noindex Directus answers every request with X-Robots-Tag: noindex and serves a disallow-all robots.txt
Rich-text table cells sanitizeTableCell, Table.svelte CMS table cells are rendered with {@html} only after sanitize-html keeps an inline allowlist (b, strong, i, em, u, code, br, a with href/title) and http, https, mailto and tel links; tests cover scripts, event handlers and unsafe URLs. Project READMEs use their own sanitize-html pass in server/markdown.ts
Release gates check:csp, security-headers.test.ts, cache-headers.test.ts Emitted policy and headers are tested on every PR

Privacy boundaries

Surface Current boundary
Plausible Host-gated, cookieless pageviews and eight named events: contact_form_success, contact_form_error, booking_click, direct_contact_click, project_proof_click, profile_click, language_switch, 404. Events post to same-origin /relay/e, which forwards each one to Plausible with the visitor's IP address (from CF-Connecting-IP behind Cloudflare) and user agent, and returns 404 when PUBLIC_PLAUSIBLE_DOMAIN is empty
Other measurement Vercel Web Analytics and Speed Insights are production-host gated in analytics/client.ts. Cloudflare's injected beacon is provider-owned, outside the application startup function. These four tools are separate from the eight Plausible events
Payloads Bounded locale/link/placement/channel/network properties; no contact fields. Vercel URL reduction uses the shared sanitizer
Contact Browser POST to Web3Forms, 15-second abort, submit disabled before hydration. Public CMS key must be nonblank at schema/export. Per-tab draft storage is functional, not analytics storage
Contact acceptance Web3Forms blocks headless signatures. No automated real contact send; owner-browser delivery receipt is on connect Handoff. Failure copy offers email. A schema-valid key does not prove provider acceptance
Video Thumbnail from i.ytimg.com; iframe from youtube-nocookie.com only after activation
Weather Key is server-only; prerender ships without weather, hydration calls /api/weather
Locale handoff Guarded per-tab sessionStorage preserves transient locale reading/focus state; history snapshots are owned by SvelteKit
Section choice localStorage['sections-choice'] stores collapsed or expanded for this origin across pages and visits. Only bulk actions write it; it is not sent to a server and sets no cookie. The cookies policy lists the key; live policy text read November 5, 2026

CMS access model

Roles declared in apps/cms/directus/collections/roles.json: Administrator, Editor, AI Editor, Build Bot, Ops Bot. Policies include a read-only Build Bot content policy (with file reads limited to the seven delivery folders or unfiled files), a Human Editor policy, an AI Editor policy for the Directus MCP (MCP_ENABLED), an Ops Bot content policy and per-collection Public read rules. Status-bearing public collections require published rows. The declaration in Git does not prove live grants; the read-only permission audit compares them. Apply guards are on the CMS service page.

Owner account sign-in

Control Contract
Hardware key Yesito's OpenAI account sign-in is hardened with an enrolled YubiKey

Repository security

Control Contract
Secret scanning secret-scan.yml runs gitleaks 8.30.1 from a checksum-verified binary on PR ranges, merge queue entries, pushes to main, a weekly full-history scan and manual dispatch. Config: .gitleaks.toml, .gitleaksignore
Local agent tooling .gitignore excludes AGENTS.local.md, .mcp.json, .codex/ and .claude/, so local agent instructions and MCP client configuration stay out of the public repository
Code scanning CodeQL default setup for Actions and JavaScript/TypeScript, weekly and on PRs
Branch rules on main Ruleset 15785309: PR required, required checks gitleaks, ci, test (strict up-to-date off), CodeQL code scanning results (errors, and security alerts high or higher), merge queue (squash, ALLGREEN), no deletion, no force push, admin bypass always. Org ruleset 26919834 also protects the default branch, but only with no deletion, no force push and changes through a pull request; it adds no required checks, code scanning or merge queue for every yesid-dev repository
Supply chain Actions pinned by commit SHA and tested in workflow-supply-chain.test.ts; Directus and Node images pinned by digest; the sync extension tarball verified by SHA-512; the article editor extension installed with npm ci --ignore-scripts from its committed lockfile
Reporting Private GitHub vulnerability reporting or the address in SECURITY.md. Only the current production service is supported

Credential rotation is an owner-operated procedure on the Operations and recovery page.