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.
| 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.
| 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 |
| 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 |
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.
| Control | Contract |
|---|---|
| Hardware key | Yesito's OpenAI account sign-in is hardened with an enrolled YubiKey |
| 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.