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:
+39
-1
@@ -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
|
||||
|
||||
@@ -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 };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user