From e70e1d6c719838e3d80a81940ef08e128db72880 Mon Sep 17 00:00:00 2001 From: Bastien Chanot Date: Thu, 16 Jul 2026 16:56:32 +0200 Subject: [PATCH] =?UTF-8?q?fix(seo):=20I4=20=E2=80=94=20stop=20double-coun?= =?UTF-8?q?ting=20security=20headers;=20/harden=20owns=20them?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Headers were scored three ways: seo-analyzer priced them into the Technical axis at both depths (:619 FULL, :635 LOCAL), depth-matrix.md:29 said drop them, and /harden re-audits them 0-100 against three external validators. The dedup rule and the agent spec contradicted each other; the agent won by default, so the same finding moved two scores in two reports. Arbitrated (user): /harden keeps them, /seo drops them. That confirms the rule that already existed — seo-analyzer was the violator. Constraint: /harden REUSES seo-analyzer, so the capability cannot be deleted, only scoped. Reading is not scoring: - Technical axis definitions no longer name security headers. - STEP 4 still curls them — needed for X-Robots-Tag, canonical/redirect coherence, and the §14 observed-list — but they earn no points under /seo. - Dispatched from /harden: unchanged, headers ARE the job (verified: its scope spec untouched, 16 header references intact). Carve-out: X-Robots-Tag stays in /seo under indexability. It is an indexing directive wearing a header's clothes — `noindex` there deindexes as surely as a meta robots tag. That is what depth-matrix.md:29 means by "unless it directly affects indexability"; the security headers do not. Drop is not silence: mandatory §14 line on FULL naming what was observed live plus a "run /harden " pointer. A user who never runs /harden must not read a clean Technical score as clean headers — same principle as the mandatory COVERAGE line (I5). Verified: make test 35 GREEN / 0 RED. --- agents/seo-analyzer.md | 35 +++++++++++++++++++++++++++++++++-- skills/seo/SKILL.md | 9 +++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/agents/seo-analyzer.md b/agents/seo-analyzer.md index 4536e6e..190a865 100644 --- a/agents/seo-analyzer.md +++ b/agents/seo-analyzer.md @@ -244,6 +244,12 @@ anonymous PageSpeed lab data and STEP 4/STEP 11 emit the §11 user action ### HTTP headers & security +**Read them; score them only for `/harden` (I4).** This section stays — the +raw headers are needed for `X-Robots-Tag`, canonical/redirect coherence, and +the §14 observed-list. But under `/seo` the security headers themselves are +out of scope for scoring: see the Technical axis note in STEP 9. Under +`/harden` they are the entire job. Reading is not scoring. + ```bash DOMAIN="" @@ -671,7 +677,7 @@ FIX: AUTO () | USER () | Axis | Weight (local B2C) | Weight (SaaS/national/content) | Score /20 | |---|---|---|---| -| Technical (perf, CWV, security headers, indexability) | 20% | 30% | | +| Technical (perf, CWV, indexability) | 20% | 30% | | | On-page (content, meta, headings, images, video, a11y, i18n) | 20% | 30% | | | SEO Local (NAP, GMB, citations) | 25% | 5% | | | Off-page (unlinked brand mentions — backlinks/authority NOT auditable, §14) | 10% | 15% | | @@ -683,6 +689,31 @@ FIX: AUTO () | USER () real users, from STEP 4) when available; otherwise lab PageSpeed Lighthouse run. +**Security headers are NOT scored here (I4).** `/harden` owns them and +grades them out of 100 with three external validators — pricing them into +this axis too was double-counting the same finding in two reports +(`depth-matrix.md:29` already said drop; this spec contradicted it). +- Dispatched from `/harden` (its prompt says NARROW-SCOPE): headers ARE the + job — audit and score them per its brief, ignore this note. +- Dispatched from `/seo`: do not score CSP, HSTS, X-Frame-Options, + X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP/CORP, + cookie flags. STEP 4 still reads them — you need them for the one + carve-out below — but they earn and lose no points here. + +**Carve-out — `X-Robots-Tag` stays.** It is an indexing directive wearing a +header's clothes: `noindex` served there deindexes the page as surely as a +meta robots tag. Score it under indexability. That is what +`depth-matrix.md:29` means by "unless it directly affects indexability" — +it is the header that does, and the security headers above are not. + +**Drop ≠ silence.** A user who never runs `/harden` must not read a clean +Technical score as clean headers. Whenever depth=FULL, emit in §14: +`Security headers (CSP, HSTS, X-Frame-Options…) — not scored here: /harden +owns them (0-100 + Observatory/SecurityHeaders/SSL Labs). Run /harden +. Observed live this run: .` +Name what you saw. An omission has to stay legible — the same reason +COVERAGE is mandatory in STEP 9. + **Off-page axis note (I1).** Score ONLY the unlinked brand mentions gathered in STEP 6 (`web_search "" -site:`). Backlink profile and domain authority have NO data source here — no index, @@ -704,7 +735,7 @@ scores twice. Revisit the 10/15% only when the axis widens back. | Axis | Weight (local B2C) | Weight (SaaS/national/content) | Score /20 | |---|---|---|---| -| Technical (security headers, indexability, config) | 25% | 35% | | +| Technical (indexability, config) | 25% | 35% | | | On-page (content, meta, headings, images, video, a11y, i18n) | 35% | 45% | | | SEO Local (markup, NAP in JSON-LD, legal) | 20% | 5% | | | Legal compliance (pages, CMP, mentions) | 20% | 15% | | diff --git a/skills/seo/SKILL.md b/skills/seo/SKILL.md index 6a5078d..99e9049 100644 --- a/skills/seo/SKILL.md +++ b/skills/seo/SKILL.md @@ -348,6 +348,15 @@ audit GEO/AI signals (llms.txt, AI crawlers, QAPage/Speakable schemas, entity SEO, content shape for AI, AI visibility) — the geo-analyzer agent runs in parallel and owns those. +Do NOT score security headers either (CSP, HSTS, X-Frame-Options, +X-Content-Type-Options, Referrer-Policy, Permissions-Policy, COOP/CORP, +cookie flags) — `/harden` owns them and grades them 0-100 against three +external validators (`depth-matrix.md:29`). Read them, keep +`X-Robots-Tag` under indexability (it is an indexing directive, not a +security header), and declare the rest in §14 with a "run /harden" pointer +plus what you observed live. Dropping them from the score must not make +them silent. + FILE OWNERSHIP (authoritative, prevents parallel-edit conflicts): - YOU OWN (read+write): sitemap.xml, image/video sitemaps, .htaccess, meta tags (title, description, OG, Twitter, canonical, robots meta),