Repository navigation
Expand file tree
/
Copy path_headers
More file actions
173 lines (150 loc) · 9.03 KB
/
Copy path_headers
File metadata and controls
173 lines (150 loc) · 9.03 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# Netlify response headers for whysheet.press.
#
# Kept in the repository rather than only in the dashboard, so the deploy setup
# is reviewable in a diff like everything else here.
/*
# Documents revalidate every time. The pages are small, and a reader who comes
# back to a sheet should get the sheet as it is now — these get corrected.
Cache-Control: public, max-age=0, must-revalidate
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
# Netlify's "Force HTTPS" setting sends HSTS on its own, but only a one-year
# max-age with no includeSubDomains. This overrides it with the two-year policy
# RFC 6797 and OWASP ask for. No `preload`: the preload list's own operator now
# advises against it, browsers auto-upgrade HTTP navigations anyway, and getting
# off the list takes months.
Strict-Transport-Security: max-age=63072000; includeSubDomains
# Three IANA-registered relations, on every response rather than only on the
# front page, because an agent can arrive at any URL and a header reaches one
# that never parses HTML. Everything here is CC0 and the licence relation says
# so without anybody having to find the footer.
Link: </llms.txt>; rel="describedby"; type="text/markdown", </sitemap.xml>; rel="sitemap"; type="application/xml", <https://creativecommons.org/publicdomain/zero/1.0/>; rel="license", </.well-known/api-catalog>; rel="api-catalog"; type="application/linkset+json", </.well-known/agent-skills/index.json>; rel="agent-skills"; type="application/json"
# Every one of these is a capability this site does not use and will not.
# An empty allowlist turns it off outright — so a page somebody is reading
# about their own child's school is not asking where they are, and says so in
# the response rather than asking to be taken on trust.
Permissions-Policy: accelerometer=(), autoplay=(), camera=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), usb=(), xr-spatial-tracking=()
X-Frame-Options: DENY
# No inline script, no inline style, no external origin of any kind. The whole
# site is local HTML, local CSS, local ES modules, local fonts and local PDFs.
#
# `style-src 'self'` with no 'unsafe-inline' is why the broadside blocks are
# taken apart at build time: each one arrives from WordPress as a self-contained
# <style> plus an inline <script>, and both are re-emitted as files rather than
# loosening this. One block also carried a single inline `style=` attribute,
# which is hoisted into a rule — see tools/lib/broadside.mjs. The easy version
# of that decision is an exception here that then covers everything forever.
#
# `'inline-speculation-rules'` permits the <script type="speculationrules">
# block in <head> and NOTHING else — it is not a door held open for other
# inline script the way 'unsafe-inline' would be. Speculation rules are
# governed by script-src; the JSON-LD block is not, because a data block is
# never executed.
#
# `connect-src 'self'` is load-bearing and not decoration: the packet builder
# fetches the sheets' own PDFs to join them. Without it the page loads, the
# button works, and every fetch fails.
#
# `object-src 'none'` and `base-uri 'none'`: nothing embeds a plugin, and
# nothing should be able to repoint every relative URL on the page.
Content-Security-Policy: default-src 'none'; script-src 'self' 'inline-speculation-rules'; style-src 'self'; font-src 'self'; img-src 'self' data:; connect-src 'self'; form-action 'none'; frame-ancestors 'none'; base-uri 'none'; object-src 'none'; manifest-src 'self'
/assets/*
# Fingerprinted by content, not by name, so they still revalidate — but these
# change far less often than the pages do.
Cache-Control: public, max-age=3600, must-revalidate
/fonts/*
# The one thing here that is genuinely immutable: a released version of an
# open font. A year is safe and saves every reader a round trip.
Cache-Control: public, max-age=31536000, immutable
/pdf/*
# A PDF changes when its sheet is revised, which is the whole reason somebody
# should not be handed a stale one at an appointment. Revalidate.
Cache-Control: public, max-age=0, must-revalidate
Content-Disposition: inline
/prompts/*
# A prompt is regenerated whenever its sheet changes, and a stale one would
# carry quotations the sheet no longer makes. Revalidate like the pages.
Cache-Control: public, max-age=0, must-revalidate
# Served as plain text so a browser shows it rather than downloading it, and
# UTF-8 explicitly: these carry em dashes and curly quotes inside quotations
# that must reach an assistant exactly as the source wrote them.
Content-Type: text/plain; charset=utf-8
/og/*
# The social cards. Generated from each page's own title and moment, so a card
# changes only when that page does — but a crawler that cached a stale one is
# the whole failure this revalidation avoids, and these are fetched once per
# link ever shared, not once per reader.
Cache-Control: public, max-age=3600, must-revalidate
Content-Type: image/png
/broadsides/*/print.html
# NOT A PAGE — it is the standalone document Chrome prints to make a
# broadside's PDF: the block's own markup and its own stylesheet, with no site
# chrome, no landmarks and no navigation, because what the printer receives
# has to be exactly that. Nothing links to it and no sitemap lists it, but
# robots.txt allows everything, so without this it carries an implicit
# "index, follow" and a search engine that finds it indexes a bare fragment as
# if it were a page of the site. Thin content gets an explicit policy.
X-Robots-Tag: noindex, nofollow
/sheets/*.md
# The Markdown source of each sheet. text/markdown explicitly, and charset
# explicitly: without it a browser downloads the file instead of showing it,
# and an agent may misclassify it. These are generated from the same file as
# the page in the same build, so they revalidate on the same terms.
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/feed.xml
# RSS 2.0. The registered type, explicitly: Netlify serves .xml as
# application/xml, which some readers accept and some refuse to subscribe to,
# and a refusal looks to the reader like the feed does not exist.
Content-Type: application/rss+xml; charset=utf-8
# An hour, unlike the pages. A feed is polled on a timer by every subscriber
# forever, and a changelog that gains an entry every few weeks does not need
# a revalidation round trip per reader per poll. must-revalidate afterwards,
# so nobody is served a stale one for longer than that.
Cache-Control: public, max-age=3600, must-revalidate
# Browser-based readers fetch the feed from their own origin. Without this
# they fail with a CORS error the reader reports as "invalid feed".
Access-Control-Allow-Origin: *
/CHANGELOG.md
# The source the changelog page is generated from, declared on that page as a
# rel="alternate". Same reasoning as /sheets/*.md: without an explicit type a
# browser downloads it instead of showing it.
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=0, must-revalidate
/.well-known/security.txt
# RFC 9116 requires text/plain. The Expires field inside is checked by
# tools/check-site.mjs, which fails the build 30 days before it lapses.
Content-Type: text/plain; charset=utf-8
Cache-Control: public, max-age=3600, must-revalidate
/site.webmanifest
# The registered type. Serving it as application/json works in Chrome and is
# ignored by some others, which is a silent install failure.
Content-Type: application/manifest+json; charset=utf-8
Cache-Control: public, max-age=3600, must-revalidate
/*.png
# Icons change only when favicon.svg does, and they are fingerprinted against
# it by tools/build-icons.mjs rather than by filename — so they revalidate.
Cache-Control: public, max-age=3600, must-revalidate
/favicon.ico
Content-Type: image/x-icon
Cache-Control: public, max-age=3600, must-revalidate
/.well-known/api-catalog
# RFC 9727 requires the Linkset media type. Netlify would otherwise serve a
# file with no extension as application/octet-stream, and a client that
# type-checks strictly would skip it — which is the whole failure this file
# exists to avoid.
Content-Type: application/linkset+json; charset=utf-8
Cache-Control: public, max-age=3600, must-revalidate
Access-Control-Allow-Origin: *
/.well-known/agent-skills/index.json
Content-Type: application/json; charset=utf-8
Cache-Control: public, max-age=3600, must-revalidate
# CORS open so a browser-based agent can fetch it, which the discovery RFC
# asks for explicitly.
Access-Control-Allow-Origin: *
/.well-known/agent-skills/*/SKILL.md
# text/markdown, not text/html: an agent fetching this directly needs to know
# what it got.
Content-Type: text/markdown; charset=utf-8
Cache-Control: public, max-age=3600, must-revalidate
Access-Control-Allow-Origin: *