Repository navigation
[quality] sitemap.xml advertises 5 internal agent-skill docs and /search to search engines #382
Description
Finding
docs/ is served with routeBasePath: '/' (docusaurus.config.js:95), so every
docs/**/*.md becomes a public route. docs/skills/ holds five documents that
are not site content — they are authoring instructions for agents working on this
repository:
docs/skills/blog-management.md
docs/skills/doc-authoring.md
docs/skills/manifest.md
docs/skills/skill-improvement.md
docs/skills/ui-standards.md
docs/skills/manifest.md:1-3 says so in its own words:
Agent Skills Manifest
This manifest indexes specialized skills for agents working on this repository.
Load these skills as needed to ensure compliance with repository standards.
All five are built, published, and submitted to search engines. Verified at
00b44df, node v26.8.2, 2026-09-21:
$ SITE_URL=https://endusers.cncf.io BASE_URL=/ npm run build:production
[SUCCESS] Generated static files in "build".
$ grep -c '<loc>' build/sitemap.xml
43
$ grep -o '<loc>[^<]*</loc>' build/sitemap.xml | grep -E '/skills/|/search'
<loc>https://endusers.cncf.io/search</loc>
<loc>https://endusers.cncf.io/skills/blog-management</loc>
<loc>https://endusers.cncf.io/skills/doc-authoring</loc>
<loc>https://endusers.cncf.io/skills/manifest</loc>
<loc>https://endusers.cncf.io/skills/skill-improvement</loc>
<loc>https://endusers.cncf.io/skills/ui-standards</loc>
Six of the 43 advertised URLs should not be advertised.
What a searcher is offered
build/skills/ui-standards/index.html ships with the site's own branding and an
agent trigger phrase as its public SEO description:
<title>Frontend UI Standards | CNCF End User Community</title>
<meta name=description content="Use when adding or changing Docusaurus components,
global CSS, responsive layouts, accessibility, or theme-aware UI.">"Use when ..." is skill-dispatch prose written for a tool, and it is what Google
will print under a cncf result. None of the five carries any robots meta, and
no navbar item or sidebar entry links any of them (sidebars.js autogenerates
only architectures and community), so they are orphan pages whose sole route
to a reader is the sitemap this repository hands to crawlers.
/search is a second, separate leak
The search page does try to opt out, and the opt-out does not work. The chain is
three files deep and each link is verifiable in node_modules:
-
docusaurus-plugin-search-local/lib/client/theme/SearchPage/SearchPage.jsx:69-72
emits the tag with the wrong attribute:/* We should not index search pages See https://github.com/facebook/docusaurus/pull/3233 */ <meta property="robots" content="noindex, follow"/>
-
@docusaurus/core/lib/client/serverHelmetUtils.js:12-21decidesnoIndexwith
isNoIndexTag, which returnsfalseunlesstag.nameisrobots:if (!tag.name || !tag.content) { return false; } return tag.name.toLowerCase() === 'robots' && ...
-
@docusaurus/plugin-sitemap/lib/createSitemap.js:18-25filters a route only when
isNoIndexMetaRouteis true, which reads that samenoIndexflag.
property="robots" is not a name, so noIndex stays false, the route is never
filtered, and /search is submitted for indexing. Crawlers ignore
property="robots" for exactly the same reason, so there is no second line of
defence. An indexed internal search page is thin duplicate content over the site's
own pages — the case Docusaurus PR #3233, cited in the comment above, exists to
prevent.
Why existing gates miss it
docusaurus buildexits[SUCCESS]; nothing inspects the sitemap it just wrote.onBrokenLinks: 'throw'enforces that links resolve, never that a route should
be public.npm run test:unit(55/55 green) contains no test that reads sitemap config,
static/robots.txt, or the published route set.- Open PR test: cover the docusaurus.config.js and sidebars.js navigation contract (tests/site-config.test.mjs) #275 (
tests/site-config.test.mjs) asserts the navigation and branding
contract indocusaurus.config.js— navbar routes, sidebar wiring,onBrokenLinks,
branding assets. It does not read the sitemap options, and route indexability is
not among its 15 assertions. This issue is disjoint from it. - Open PR test: verify local /img static asset references resolve under static/ (tests/static-assets.test.mjs) #289 (
tests/static-assets.test.mjs) is scoped to/imgreferences and
does not model routes.
Recommendation
One PR closes both halves; both edits are mechanical and land in the same change set.
- Exclude the two route families from the sitemap. In
docusaurus.config.js,
inside theclassicpreset options (sibling ofdocs,blog,theme), add:
sitemap: {
// /search is thin duplicate content; the plugin's own opt-out is
// emitted as property="robots", which neither Docusaurus nor a
// crawler honours. /skills/* is agent tooling, not site content.
ignorePatterns: ['/search', '/skills/**'],
},- Stop publishing the skill docs as reader-facing pages. Add to the
frontmatter of each of the five files indocs/skills/:
unlisted: true `unlisted: true` keeps the page reachable by direct URL for contributors while
removing it from the sitemap, the local search index, and adding a real
`<meta name="robots" content="noindex, nofollow">`. Prefer it to `draft: true`
(which drops the pages from the production build entirely) only if someone
still links these from a PR; `docs/skills/manifest.md` is referenced from
`ROADMAP.md:52`, so a reachable URL is worth keeping.
Verify after the change:
SITE_URL=https://endusers.cncf.io BASE_URL=/ npm run build:production
grep -o '<loc>[^<]*</loc>' build/sitemap.xml | grep -E '/skills/|/search' # expect no output
grep -c '<loc>' build/sitemap.xml # expect 37Why there is no PR attached
Both deliverables are production changes — site configuration and content
frontmatter. The quality lane is restricted to test-only PRs, so this needs a human
or another lane to land. The exact replacement text above is given verbatim so
applying it is mechanical rather than a judgement call.
A test-only guard is not a substitute and is not being proposed alongside: any
assertion that /skills/** stays out of the sitemap is red against main until the
first box is ticked, and the route-derivation machinery such a test would need is
already claimed by open PR #275.
Evidence
- Revision
00b44df, node v26.8.2,@docusaurus/plugin-sitemap3.10.2, run locally 2026-09-21. - Build:
SITE_URL=https://endusers.cncf.io BASE_URL=/ npm run build:production->[SUCCESS]. - Sitemap: 43
<loc>entries, 6 of them/search+/skills/*(listed above). - Robots meta:
build/skills/ui-standards/index.htmlhas none;
build/search/index.htmlhas<meta property=robots content="noindex, follow"/>. - Unit coverage:
npm run test:unit -- --experimental-test-coverage-> 55/55 pass;
no test reads sitemap config or the published route set. - End-to-end coverage: unobtainable rather than absent — the repository declares no
playwright,cypressorpuppeteer, so there is no e2e suite to consult. See
[quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186 for the missing coverage-evidence pipeline.
Priority
- Impact: high — five internal tooling documents are advertised to search engines under CNCF branding on a pre-launch site, and an internal search page is submitted as thin duplicate content
- Effort: low — one config block plus five frontmatter lines, no new dependency
Filed by quality agent (hold-gated mode)
— hive: agent=quality backend=copilot model=claude-opus-5
Activity