From 2489cca8ecee9518e6c124fa20433642b90ceba9 Mon Sep 17 00:00:00 2001 From: araharah-executor Date: Sun, 25 Oct 2026 05:18:47 +0800 Subject: [PATCH] =?UTF-8?q?fix(07-06):=20the=20/help=20doc=20pointer=20is?= =?UTF-8?q?=20a=20documented=20static-text=20class=20=E2=80=94=20privacy?= =?UTF-8?q?=20audit=20green=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - source-side runtime-hosts gate gains DOC_POINTER_STATIC_TEXT_HOSTS (the MapLibre-attribution precedent): the published repo origin may appear ONLY as a bare URL literal in bot copy (D-08/D-09), never fetched - class is NARROW and pinned by SA6: a fetch( target naming the same host is still a violation - Rule 1 (found by SA6's RED): the fetch( handler's declaration heuristic misclassified any quoted absolute URL as a TS declaration — quoted strings are targets; they now reach the host check - docs/PRIVACY-AUDIT.md: adjudication documented + dated re-run addendum - deferred-items.md #1 closed: npm run privacy-audit exits 0 again (PRIVACY AUDIT CLEAN — 5 gate(s) passed, 0 violations; suite 13/13) --- docs/PRIVACY-AUDIT.md | 40 +++++++++++++++++++++++++++++++++++- scripts/lib/source-gates.mjs | 31 ++++++++++++++++++++++++---- 2 files changed, 66 insertions(+), 5 deletions(-) diff --git a/docs/PRIVACY-AUDIT.md b/docs/PRIVACY-AUDIT.md index ad82b4c..4477114 100644 --- a/docs/PRIVACY-AUDIT.md +++ b/docs/PRIVACY-AUDIT.md @@ -56,7 +56,11 @@ only as `navigator.sendBeacon`; the MapLibre library's own static-text URLs (`ma `wiki.openstreetmap.org`, `github.com` diagnostics, `maplibre.invalid` protocol-test placeholder) and our own dataset's ODbL license strings (`opendatacommons.org`) are printed in the inventory as a documented static-text class — never fetched; `http://www.w3.org/{2000/svg,1999/xhtml}` are W3C -spec identifiers. +spec identifiers. The SOURCE-side `runtime-hosts` gate carries the same kind of adjudication for +the `/help` Known-limits doc pointer (see the 2026-10-25 addendum below): the published repo +origin appears ONLY as a bare URL literal in the bot strings — user-clicked navigation, never +fetched — and the class is NARROW (a `fetch(` target naming the same host is still a violation; +pinned by the SA6 vector). ## Storage inventory @@ -102,6 +106,7 @@ assumed, A4): | `maplibre.org`, `wiki.openstreetmap.org`, `github.com`, `maplibre.invalid` | documented static-text class | MapLibre's own attribution/diagnostic constants inside the bundled library — displayed text, never fetched | | `opendatacommons.org` | documented static-text class | our own gazetteer/fixture ODbL license metadata strings | | `www.w3.org` | documented spec identifiers | SVG/XHTML namespace constants — never fetched | +| `gitmirah` (the published repo origin) | documented static-text class (SOURCE-side, URL literals only) | the `/help` Known-limits doc pointer in `bot/src/i18n/strings.ts` (D-08/D-09) — user-facing copy in a chat card, never fetched; a `fetch(` to the same host is still a violation (SA6) | **ZERO telemetry (D-02):** no analytics, no beacons, no tracking pixels, no third-party CDN, no error-reporting host, no social pixels — the shipped bundle contains no analytics or beacon token @@ -125,6 +130,39 @@ expiry metadata, deployed cache headers) were observed NOT RUN in this environme re-run with `npm run privacy-audit -- --live` at the launch gate — exit 0 only when they PASS, never when they are skipped. +## 2026-10-25 — Phase 7 gate-prep re-run: the /help doc pointer adjudication + +**Why this addendum exists:** plan 07-05's committed `/help` Known-limits pointer (commit +`78482ab`) put the repo's actual published-origin URL (`http://gitmirah:3000/c-fu/arah2/…`, +the maintainer's Gitea remote per `git remote -v`) into the bot strings (en + ms), and the +`runtime-hosts` gate — a positive allowlist with no static-text escape — flagged it as 2 +violations (`PRIVACY AUDIT FAILED`). Logged in the phase's `deferred-items.md`; fixed by the +07-06 gate prep, following the audit's OWN laws (the MapLibre static-text adjudication +precedent), not by weakening them. + +**The fix (source-side, `scripts/lib/source-gates.mjs`):** +- A documented `DOC_POINTER_STATIC_TEXT_HOSTS` class on the SOURCE-side `runtime-hosts` gate: + the published repo origin may appear ONLY as a bare URL literal in bot copy (the /help + Known-limits line — user-clicked navigation, never fetched). Each entry names its reason; + forks replace it with their own published origin. +- The class is **NARROW by construction and pinned by the SA6 vector**: it is consulted ONLY + for bare URL literals. A `fetch(` target naming the same host is STILL a violation. This + required fixing a latent gate bug (SA6's RED proved it): the `fetch(` call-site handler's + declaration heuristic (`argText.includes(":")`) misclassified any QUOTED absolute URL as a + TS method declaration and skipped it — a quoted string is always a target, so quoted fetch + targets now reach the host check. +- **Result:** `npm run privacy-audit` → `runtime-hosts: PASS — host inventory (printed, not + assumed): www.w3.org×3, gitmirah×2, telegram.org×1; 4 relative fetch target(s) to the app's + own Worker, 1 fetch declaration(s) skipped; every absolute URL host is in the positive + allowlist [tiles.openfreemap.org, telegram.org] or the documented doc-pointer static-text + class (URL literals only — never fetched)` → **PRIVACY AUDIT CLEAN — 5 gate(s) passed, + 0 violations**; the suite is 13/13 (SA1–6, BG1–2, CP1–2, AU1–3). The Findings table above + remains the 07-03 run's historical record; this section is the gate-time re-run record. + +**Driver-resolution caveat (unchanged from 07-05's decision):** the URL resolves for a driver +only once the D-09 launch checklist's "repo pushed public" step lands main on the remote — +the launch gate verifies the pointer line resolves before signing. + ## Signature The audit record is SIGNED BY THE HUMAN at the launch gate (the tool produces the evidence; the diff --git a/scripts/lib/source-gates.mjs b/scripts/lib/source-gates.mjs index a855801..363772d 100644 --- a/scripts/lib/source-gates.mjs +++ b/scripts/lib/source-gates.mjs @@ -230,6 +230,17 @@ export function runLogVocabularyGate(opts = {}) { * allowed. */ const ALLOWED_FETCH_HOSTS = ["tiles.openfreemap.org", "telegram.org"]; +/** Documented static-text hosts (adjudication, the MapLibre-attribution + * precedent in bundle-gates.mjs) — user-facing DOC-POINTER copy in the bot + * strings (the /help Known-limits line, D-08), never fetched at runtime. + * Scoped NARROW by construction: this class is consulted ONLY for bare URL + * literals — a `fetch(` target naming one of these hosts is still a + * violation. Forks: replace with your own published repo origin. */ +const DOC_POINTER_STATIC_TEXT_HOSTS = { + gitmirah: + "the maintainer's published Gitea origin — the /help Known-limits doc pointer (D-08/D-09's 'repo pushed public' remote), user-clicked navigation, never fetched", +}; + /** Documented, deliberate scope adjudication (the theme-audit precedent): * the SVG and XHTML namespace URIs are W3C spec IDENTIFIERS * (createElementNS / innerHTML parsing), never fetched. Any other http:// @@ -263,12 +274,15 @@ export function runRuntimeHostGate(opts = {}) { hostInventory.set(host, (hostInventory.get(host) ?? 0) + 1); } - function checkUrl(rel, urlText, where) { + function checkUrl(rel, urlText, where, fromFetch = false) { noteHost(urlText); const host = hostOf(urlText); if (host === null) return; // not a parseable URL — leave it to the bundle scan / live probe if (SPEC_NAMESPACE_IDENTIFIERS.includes(urlText)) return; // documented adjudication — a spec identifier, never fetched if (ALLOWED_FETCH_HOSTS.includes(host)) return; + // the doc-pointer static-text class applies ONLY to bare URL literals — + // a fetch target naming one of these hosts is still a violation + if (!fromFetch && DOC_POINTER_STATIC_TEXT_HOSTS[host] !== undefined) return; violations.push(`${rel} — absolute URL to an unapproved host (${where}): ${host}`); } @@ -286,12 +300,21 @@ export function runRuntimeHostGate(opts = {}) { for (const m of code.matchAll(/\bfetch\s*\(/g)) { const rest = code.slice(m.index + m[0].length, code.indexOf("\n", m.index)); const argText = rest.split(/(? `${h}×${n}`).join(", ") || "none"; const skippedNote = skipped.length > 0 ? ` (skipped, absent: ${skipped.join(", ")})` : ""; const evidence = pass - ? `host inventory (printed, not assumed): ${inventory}; ${fetchTargets.length} relative fetch target(s) to the app's own Worker, ${fetchDeclarations} fetch declaration(s) skipped; every absolute URL host is in the positive allowlist [${ALLOWED_FETCH_HOSTS.join(", ")}]${skippedNote}` + ? `host inventory (printed, not assumed): ${inventory}; ${fetchTargets.length} relative fetch target(s) to the app's own Worker, ${fetchDeclarations} fetch declaration(s) skipped; every absolute URL host is in the positive allowlist [${ALLOWED_FETCH_HOSTS.join(", ")}] or the documented doc-pointer static-text class (URL literals only — never fetched)${skippedNote}` : `host inventory: ${inventory}; ${violations.length} violation(s): ${violations.join("; ")}`; return { id, pass, evidence, violations: violations.length }; }