fix(07-06): the /help doc pointer is a documented static-text class — privacy audit green again

- 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)
This commit is contained in:
araharah-executor
2026-10-25 05:18:47 +08:00
parent b06c64d5fc
commit 2489cca8ec
2 changed files with 66 additions and 5 deletions
+39 -1
View File
@@ -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
+27 -4
View File
@@ -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(/(?<![^,(]\()[,)]/)[0]?.trim() ?? rest.trim();
if (argText.includes(":") || argText === "") {
if (argText === "") {
fetchDeclarations += 1;
continue;
}
const literal = argText.match(/^["'`]([^"'`]*)/);
if (literal === null) {
// TS method DECLARATIONS (`fetch(req: Request, …)`) are skipped — the
// annotation colon identifies them. A quoted string is always a
// TARGET (its URL colon must not masquerade as an annotation) —
// fixed for SA6: the doc-pointer class is URL literals only, so a
// quoted absolute fetch target must still reach checkUrl.
if (argText.includes(":")) {
fetchDeclarations += 1;
continue;
}
violations.push(`${rel} — fetch( first argument is dynamic, not statically verifiable: ${argText}`);
continue;
}
@@ -299,7 +322,7 @@ export function runRuntimeHostGate(opts = {}) {
fetchTargets.push(`${rel} — ${target}`);
if (target.startsWith("/")) continue; // relative — the app's own Worker origin
if (/^https?:\/\//.test(target)) {
checkUrl(rel, target, "fetch target");
checkUrl(rel, target, "fetch target", true);
continue;
}
violations.push(`${rel} — fetch( target is neither relative nor an approved absolute URL: ${target}`);
@@ -318,7 +341,7 @@ export function runRuntimeHostGate(opts = {}) {
const inventory = [...hostInventory.entries()].map(([h, n]) => `${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 };
}