site: security headers, entity schema, and the files AI crawlers look for - #5
Merged
Conversation
… for
From a 15-engine AEO audit, limited to what was verified against the live site
by hand. Several of the engines' findings were wrong and are not acted on here:
one reported no robots.txt, no sitemap and no JSON-LD when all three are
served; five called the sitemap lastmod "future-dated" when the date was that
day; and the "unrendered ${userInput} template literal" is a TypeScript sample
in a <code> block on the security page, showing the injection DiskPush
deliberately does not do.
What was real:
- No security headers at all. Only cache-control was set — no HSTS, CSP,
X-Frame-Options, nosniff, Referrer-Policy or Permissions-Policy. The CSP
keeps 'unsafe-inline' for script and style because Next inlines its
hydration payload and the app's styles; scripts are otherwise same-origin,
and object-src/frame-ancestors 'none' close the usual bypasses.
- Every sitemap URL carried one build timestamp. A lastmod that is identical
site-wide and changes on every deploy tells a crawler nothing, so it gets
discounted — worse than sending none. Dates now come from the files each
page is built from; doc pages use their own markdown's mtime, which is
exactly when that page last changed. 18 URLs, 18 distinct dates.
- No Organization. The schema described an application that nothing
published: the only trace of who makes DiskPush was a GitHub URL and the
domain on the security address. Organization and WebSite are now in the
graph, and SoftwareApplication names them as publisher and author.
- /llms.txt and /.well-known/security.txt 404'd. Both are generated rather
than written: a hand-kept copy of the docs index goes stale, and a stale
llms.txt is worse than none because models quote it confidently. The
security.txt Expires is computed a year out for the same reason — a
hard-coded date silently invalidates the file on a day nobody has diarised.
- /security and /docs/security were the same markdown at two URLs, with the
same title. The promoted page is now canonical for both.
robots.txt names the AI crawlers explicitly. The wildcard already allowed them,
so this grants nothing new; it states a position rather than leaving it to be
inferred, and makes narrowing it later an edit rather than an unrecorded
decision.
Left alone deliberately, because they are content decisions rather than bugs:
about/team page, general contact route, on-site changelog, comparison pages,
testimonials. Also outside the repo: _dmarc.diskpush.com is a CNAME to
uixie.porkbun.com, which serves no TXT record, so DMARC resolves to nothing.
Verified by building and serving the site: six headers present, llms.txt and
security.txt served, 18 distinct sitemap dates, /docs/security canonical to
/security, and Organization/WebSite in the graph.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5jnkZKX4AdPgBMzMosxE7
ralyodio
added a commit
that referenced
this pull request
Aug 30, 2026
The lastmod fix in #5 worked locally and did nothing in production. Deployed, all eighteen URLs still carried one identical date -- 2026-08-30T04:07:33, the build's own checkout time. mtime was the wrong source. A git clone stamps every file it writes with the moment it wrote it, so on a CI checkout every file in the tree shares one mtime and the per-page dates collapse straight back into the single build timestamp the change existed to remove. A working tree has real edit times, which is exactly why local verification passed and proved nothing. Reproduced by cloning the repository and comparing the two sources against the same thirteen files: distinct mtimes: 1 distinct git dates: 6 Six rather than thirteen because several docs were committed together, which is the honest answer: those pages did change at the same moment. So the date now comes from `git log -1 --format=%cI -- <path>`, which is a record of when the content changed rather than when the machine last touched the file. mtime stays as a fallback for a build with no git history, where it is no worse than the build clock it replaces, and the build clock remains the last resort. Verified against a build rather than a working tree: nine distinct dates across the eighteen URLs. Claude-Session: https://claude.ai/code/session_01Y5jnkZKX4AdPgBMzMosxE7 Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
From the 15-engine AEO audit. Limited to findings I verified against the live site by hand — several engines were wrong, and acting on them would have made things worse.
Findings I did not act on, and why
robots.txt200, sitemap with 18 URLs,SoftwareApplication+FAQPagein the head.lastmod" (5 engines)${userInput}template literal" (Slop Score)<code class="language-ts">on /security — a deliberate example of the injection DiskPush does not do. Fixing it would delete the point of the paragraph.What was actually broken
No security headers at all. Only
cache-controlwas set. Added HSTS, CSP,X-Content-Type-Options,X-Frame-Options,Referrer-Policy,Permissions-Policyvianext.config.mjs.The CSP keeps
'unsafe-inline'for script and style — Next inlines its hydration payload and the app's styles, so a stricter policy blanks the page. Scripts are otherwise same-origin only, which is the part that limits an injection, andobject-src 'none'+frame-ancestors 'none'close the classic bypasses.Every sitemap URL carried one build timestamp. Not that it was future-dated — that it was identical across all 18 URLs and changed on every deploy. A lastmod like that carries no information, so crawlers discount it, which is worse than sending none. Dates now come from the files each page is built from; doc pages use their own markdown's mtime, which is precisely when that page last changed.
No
Organization. The schema described an application that nothing published — the only trace of the maker was a GitHub URL and the domain on the security address.OrganizationandWebSiteare now in the@graph, andSoftwareApplicationnames them aspublisher/author, plusdownloadUrl,licenseandisAccessibleForFree./llms.txtand/.well-known/security.txt404'd. Both generated, not written: a hand-kept copy of the docs index goes stale, and a stalellms.txtis worse than none because models quote it confidently. Thesecurity.txtExpiresis computed a year out for the same reason — a hard-coded date silently invalidates the file on a day nobody has diarised./securityand/docs/securitywere the same markdown at two URLs, with the same<title>. The promoted page is now canonical for both, so they stop competing without either URL being dropped.robots.txt names the AI crawlers explicitly. The wildcard already allowed them, so this grants no new access — it states a position rather than leaving it inferred, and makes narrowing it later an edit rather than an unrecorded decision.
Verified by building and serving the site
//llms.txtand/.well-known/security.txtreturn the expected text/docs/security→rel="canonical" href="https://diskpush.com/security"Organization,WebSite,ContactPointin the JSON-LD graphTypecheck clean, full suite 238 passing.
Deliberately left out
Content decisions, not bugs — they need your input on what to say: about/team page, general contact route, on-site changelog, comparison pages, testimonials/social proof. Those were the other high-consensus findings.
One thing outside this repo: DMARC is broken, not just missing.
_dmarc.diskpush.comis a CNAME touixie.porkbun.com, which serves no TXT record — so it resolves to nothing. That needs a DNS change at Porkbun.🤖 Generated with Claude Code
https://claude.ai/code/session_01Y5jnkZKX4AdPgBMzMosxE7