Threat model

What could go wrong on this site, what is at risk, and how each threat is handled, with private documentation as the main concern.

Snapshot of the system as it stands today. Edit in place; never record what changed.

Current attack surface

The site is static pages built from content/, one folder per package or project at /<slug>/…, served with their Markdown, llms.txt / llms-full.txt, share images and static.json, plus Algolia search (../architecture/system.mdx). Every page is prerendered (Cache Components, ADR-0016); none reads cookies or headers. There is no sign-in, no database and no user input beyond the search box. The home page's Private projects group is public static markup listing three invented projects from src/components/home/private-projects.ts (ADR-0017 decision 4); it holds no private data.

SurfacePresentDetail
Authentication / sessionsNo—
Route handlersYesMarkdown, llms.txt, llms-full.txt, share images, static.json, robots.txt, sitemap.xml, the manifest, the Apple icon, the Archify diagrams under /diagrams/ — all public, all static (../api/content-routes.mdx, ../api/crawler-routes.mdx)
User input of any kindYesThe search query, sent from the browser straight to Algolia; the Accept header, read by src/proxy.ts; the request's path, compared by src/proxy.ts with a fixed list and never echoed
Database or persistenceNoContent is files; the search index is a build output
Files / uploadsNo uploadsArchify diagrams in diagrams/: generated script, committed by the owner, served with the site shell's added script and style, run in a sandboxed frame and served with a CSP sandbox header. The frame sends the page one message, its height, which the page takes only from that frame's window and only as a number from 120 to 4000 (ADR-0011, ADR-0022, ../api/content-routes.mdx)
Secrets / environment variablesYesThree Algolia variables, listed in secrets.mdx
Third-party integrationsYesAlgolia (search). GitHub serves the avatar in each profile chip to the reader's browser, without a referrer (ADR-0020). The page actions' Open in ChatGPT, Claude, Cursor and Scira links, on docs pages and the home page, send a public page's URL to that service when the reader clicks one. The fonts are fetched by next/font at build time and served from the site's own origin
Logging / audit trailNoThe sync script prints a record count

Each ADR that adds a surface updates this table in the same change.

Assets to protect

#AssetHarm if exposed or corrupted
A1Private project documentationArchitecture, internal APIs, hostnames or client names published and crawled; cannot be recalled
A2ALGOLIA_WRITE_KEYAnyone can replace or wipe the search index, or plant pages in it
A3Integrity of published package docsA wrong install command or a tampered example is copied into other people's projects
A4Unfixed security findingsPublished in these maintainer pages before the fix, they are an attack plan; they go to the owner, not into these pages (ADR-0002)
A5The owner's identity and reputationPages or search results that appear to be the owner's but are not

Adversaries to design for

AdversaryGoalTypical path
Anyone on the internetRead private documentationThe site, llms-full.txt, static.json, or the search index with the public search-only key
Crawlers and LLM scrapersCollect everything publishedllms.txt / llms-full.txt, the Markdown routes — intended for public content, indiscriminate for private
Someone holding a leaked write keyDeface or poison searchDirect writes to the Algolia index
A malicious contribution or dependencyTamper with content or the buildA changed example, a compromised package in the build

Trust boundaries

Crossed today, each in ../dfd/system.mdx:

BoundaryCarries
Build ↔ AlgoliaEvery page's search record, with the write key
Browser ↔ AlgoliaSearch queries and results, with the search-only key
Browser ↔ GitHubAvatar requests for profile chips: the reader's IP address and user agent, no page address
Site ↔ publicEvery page on every surface

Anticipated, each documented by the ADR that introduces it:

BoundaryWill carry
Repository ↔ hostThe build and its environment variables
Site ↔ authentication providerReader identity, if private docs are served from this site
Site ↔ analyticsPage views, if analytics is adopted

Accepted risks

RiskWhy acceptedGuard
GitHub sees which IP addresses load which avatars, so it can tell that someone read a page naming that user (A5 for readers' privacy).The owner chose live avatars over a committed copy or an image proxy (ADR-0020).referrerpolicy="no-referrer"; avatars load lazily, only when the chip is near the viewport.
The avatar is whatever the GitHub account sets, shown on the site.It is that person's own profile picture, shown at 20px as decoration.The chip's name and link come from the page, not from GitHub.
A future Archify release adds a network call or an external resource to the generated diagrams; the sandbox does not block outgoing requests.Diagrams are made with Archify by the owner's standing choice; Archify 3.2.0 makes none.Each upgrade needs the owner's yes, and its HTML is scanned before it is published (checklist, Archify).
The private projects list (src/components/home/private-projects.ts) is the one place in code where a private project's name is typed. Someone may later replace an invented name with a real client's, publishing it on the home page and to crawlers (A1).The owner chose to show invented private projects on the live site (ADR-0017).The file's comment says only invented names belong there until the private-documentation ADR; a real name there breaks invariant 1. The list is never sent to search, llms.txt or static.json.
Visitors take the invented private projects as real, and their links return the 404 (A5).The owner's decision, ADR-0017.Replacing the list is a one-file change.

Incidents

None.

On this page

7 min read