Three consumers reconstructed is_ordinary_url from prose we sent in coordination messages and each produced a different wrong number on a real corpus: one omitted the base64 20-char floor and fired on path words like /blog/; one omitted the opaque-token condition entirely and undercounted; one computed Shannon entropy over whole filenames instead of tokens and concluded the 4.4 floor over-blocks ordinary documents. Same cause each time -- our prose described the rules without their tokenizer. docs/URL-SHAPE.md states the algorithm in order, spells out the separator class and all three length floors, and lists the three reconstruction errors as worked counter-examples. Its example table is parsed and asserted against the real predicate by tests/test_url_shape_doc.py, so the reference cannot drift from the code -- all 18 rows verified load-bearing. LIMITATIONS.md brought current with the field measurements: - Percent-escape is no longer zero. Two English corpora measured 0; a 389-file Norwegian/Microsoft corpus found 10, all Norwegian (%C3%B8, %C3%A5 are just o-slash and a-ring). It is a non-ASCII-language tax, and both zero-measuring corpora being English was a sampling bias invisible from inside. - The query over-block now has THREE disjoint benign populations: utm_* tracking, content identity (?v=, ?channel_id=), and Microsoft Learn's ?view= version selector. No parameter-level remedy covers any two, which moves this from a conclusion to a settled constraint on 0.4.0. - Legitimate CDN asset ids trip the hex branch permanently; the branch is otherwise precise (no other FP in 2401 distinct URLs) and stays. - Raw HTML with a relative URL attribute is HIGH though it reaches no external host, and end tags are counted. - OKF frontmatter: a one-key block-sequence item is silently misparsed to a string where two keys hard-reject, so a pointer can ride past the resource allowlist. Consequence: a conformant OKF v0.2 concept cannot traverse door C at all, since both backward-breaking migration targets are nested. Fail-secure, but a compatibility wall that needs a deliberate parse-safety decision. - A persist gate cannot cover execution risk, and that boundary is unowned. New behaviour claims are pinned by tests so a closed concession fails and forces this doc to be updated. 593 -> 631 passed.
6.6 KiB
The URL-shape rule, stated so it can be reconstructed
is_ordinary_url decides whether a URL merely names a remote document or can
carry data outward. Severity grades on that answer (0.3.1), so a consumer
reasoning about its own corpus needs the rule exactly, not approximately.
This document exists because approximate statements of it failed three times in
two days. Three independent consumers reconstructed the rule from prose we sent
in coordination messages, and each produced a wrong number in a different way —
see Commonly reconstructed wrong. The worked
examples below are asserted against the real implementation by
tests/test_url_shape_doc.py, so this file cannot drift from the code.
The algorithm, in order
A URL is ordinary only if every step passes. Any failure means carrying.
- Scheme gate.
^(?:https?://|//)— case-insensitive. Only http(s) and protocol-relative URLs have an ordinary form at all. Every other scheme (javascript:,data:,ftp:,file:, …) is active or fetches out-of-band on its own terms and never grades down. - Parse.
urlsplit(url). AValueError(malformed authority — bad IPv6, bad port) is not ordinary: unparseable means ungradable, and ungradable fails closed. - Query and userinfo.
parts.query,parts.username,parts.password— any non-empty value means carrying. A query can move a value to the host; userinfo is a credential in the URL. - Percent-escapes.
"%" in (parts.netloc + parts.path)means carrying. - Opaque tokens. Split
parts.netloc + parts.pathon the separator class and test every token. Any opaque token means carrying.
The fragment is deliberately excluded from all of this. It is never sent to the
server, so it cannot carry data to a host that a renderer auto-fetches, and
…/overview#prerequisites is the single most common shape in real documentation.
Step 5 in full — the part that is always missed
Tokenization happens first, and the entropy/base64/hex tests apply to each resulting token, never to the whole path or filename:
_URL_TOKEN_RE = re.compile(r"[/._\-~+,;:=&$!*'()]+")
tokens = [t for t in _URL_TOKEN_RE.split(parts.netloc + parts.path) if t]
Note what is in that character class: / . _ - ~ + , ; : = &
$ ! * ' ( ). Hyphens, underscores and dots are separators, so a long
hyphenated filename shatters into short tokens. Note also what is not in it:
% is not a separator, so a percent-escaped path yields longer tokens than the
same path with literal spaces.
A token is opaque if any of three branches fires — cheapest first:
| Branch | Floor | Test |
|---|---|---|
| base64 | ≥ 20 chars | matches [A-Za-z0-9+/]{20,}={0,3} and b64-decodes to ≥80% printable text |
| hex | ≥ 32 chars | matches (?:0x)?[0-9a-fA-F]{32,} |
| entropy | ≥ 24 chars | Shannon entropy ≥ 4.4 |
Every branch has a length floor. Nothing shorter than 20 characters can ever be opaque by any branch.
Worked examples
Asserted against is_ordinary_url by tests/test_url_shape_doc.py. ordinary
means "names a document" (LOW); carrying keeps the carrier's full severity.
| URL | Verdict | Why |
|---|---|---|
https://learn.microsoft.com/en-us/azure/overview |
ordinary | bare path |
https://learn.microsoft.com/en-us/azure/overview#prerequisites |
ordinary | fragment excluded |
https://youtu.be/dQw4w9WgXcQ |
ordinary | 11 chars, under every floor |
https://www.bbc.com/news/articles/c8x9k2m1l0po |
ordinary | 12 chars, under every floor |
https://example.com/a/550e8400-e29b-41d4-a716-446655440000/doc |
ordinary | UUID splits on -; longest token 12 |
https://example.com/docs/CISPE-Buying-Cloud-Services-in-Public-Sector-Handbook-v2-FEB-2022_EN-Source_v2_Norwegian.pdf |
ordinary | 16 tokens, longest 9, worst H=3.17 |
https://ai.meta.com/blog/ |
ordinary | blog is 4 chars — below the base64 floor |
https://www.youtube.com/watch?v=dQw4w9WgXcQ |
carrying | non-empty query |
https://claude.com/pricing?utm_source=docs |
carrying | non-empty query |
https://learn.microsoft.com/azure/ai/overview?view=azureml-api-2 |
carrying | non-empty query |
https://nsm.no/NSMs%20Grunnprinsipper%20for%20IKT-sikkerhet.pdf |
carrying | percent-escapes |
https://lovdata.no/nav/lov/2025-06-20-81/kap2/%C2%A710 |
carrying | percent-escapes |
https://token:s3cr3t@evil.test/p.png |
carrying | userinfo |
https://example.com/d41d8cd98f00b204e9800998ecf8427e |
carrying | 32-char hex token |
https://evil.test/c3RvbGVuIHNlc3Npb24gdG9rZW4gdmFsdWU/p.png |
carrying | base64 decoding to text |
https://c3RvbGVuIHNlc3Npb24gdG9rZW4gdmFsdWU.evil.test/p.png |
carrying | opaque host label |
ftp://example.com/pub/file.txt |
carrying | scheme gate |
javascript:alert(1) |
carrying | scheme gate |
Commonly reconstructed wrong
Each of these produced a real, wrong number on a real corpus.
- Omitting tokenization. Computing Shannon entropy over the whole filename
scores
CISPE-Buying-…-Norwegian.pdfat H=4.75 and concludes the 4.4 floor over-blocks ordinary documents. Under the real tokenizer its worst token isNorwegianat H=3.17 and the URL is ordinary. Entropy is per token. - Omitting the base64 length floor. Testing "b64-decodes to printable text"
without
len ≥ 20fires on ordinary path words —blog,digiand1544decode to printable bytes, whilenewsanddocsdo not, so the reconstruction appears to discriminate on something meaningful and does not. - Omitting step 5 entirely. Applying only "query or percent-escape" is strictly weaker than the real rule, so it undercounts carrying URLs. It can never overcount: every URL it calls carrying really is.
Grading, once the shape is known
Shape decides severity, not disposition. Per-construct severities live in
calibration.py; an ordinary URL grades ACTIVE_CONTENT_ORDINARY_SEVERITY (LOW).
| Carrier | Ordinary URL | Carrying URL |
|---|---|---|
| markdown link, autolink, refdef | LOW | MEDIUM |
markdown image, raw HTML, data: URI |
LOW | HIGH |
Disposition then applies trust: under PRESET_USER_UPLOAD a MEDIUM reaches
QUARANTINE_REVIEW and a HIGH fails secure; under PRESET_TRUSTED_SOURCE both warn.
MEDIUM never hard-fails under either preset — a query-carrying link is held or
warned, never blocked. Two consumers inferred otherwise; it is pinned in
tests/test_wiring.py.
Known over-blocks measured against real corpora are listed in
LIMITATIONS.md.