Chatouk

+New post
⋯

Scrub

An RSS namespace for declaring privacy transformations — version 0.1

English · Français

Status: experimental. This is a working draft, published to make a running implementation legible, not a standard. The number 0.1 is there precisely so that nothing here is presumed stable. Element names, attribute names and the core vocabulary may all change without notice, and any change to one of them will produce a new namespace URI rather than a silent redefinition of this one.

Published on 18 August 2026 by soFab; last revised on 19 September 2026 (see the changelog). Feedback is welcome and is meant to move this document forward.

This English text is the reference version. A French translation is maintained alongside it; where the two differ, the English text prevails.

Namespace URI

https://sofab.ch/public/ns/scrub/0.1

This URI is the identity of the vocabulary. Fetching it returns this document, following the convention that a namespace URI should resolve to its own documentation.

All examples below bind this URI to the prefix scrub:. The prefix is a convention of this document, not part of the format. XML prefixes are local aliases: a consumer binds to the URI and MUST accept whatever prefix the producer chose. A feed declaring xmlns:privacy="https://sofab.ch/public/ns/scrub/0.1" is just as valid and MUST be read identically.

The problem addressed

Services that publish user-contributed media routinely transform it before publication, to protect the people it shows or the person who uploaded it. They strip EXIF and GPS data. They blur faces. They alter voices. Where this happens, it is described in prose — in a privacy policy, a help page, a footer — and is therefore unreadable by the software that redistributes the content.

The information is lost at exactly the moment it becomes useful. A feed reader, an aggregator or a downstream node relaying an item has no way of knowing whether the media it just received has already been cleaned, whether it should clean it itself, or what an earlier node in the chain already did. The result is either duplicated work or a silent gap where everyone assumed someone else had taken care of it.

Scrub takes that claim out of the prose and puts it in the feed, next to the content it describes, in a form a program can read.

Relationship to C2PA

C2PA specifies cryptographically signed provenance manifests for media files, covering capture, editing and authorship. That is a broader scope than this vocabulary, considerably more rigorous, and where verifiable integrity is required, C2PA is the right tool — this one does not replace it.

Scrub makes a deliberately weaker claim in exchange for trivial production and consumption. It is a good-faith declaration carried in a feed that already exists, with no manifest format, no signing infrastructure and no key distribution. A publisher that already strips EXIF can declare it in a few lines of feed generation. A consumer can read it with the XML parser it already has.

The two compose rather than compete. Where a signed manifest exists, an item MAY point to it via <scrub:c2pa href="…"/>, letting a consumer that understands C2PA verify what this vocabulary merely asserts. That element is reserved in 0.1 and not yet specified.

The model

There is a single unit of information — a transform, describing one operation performed by one agent at one moment — and two places to put it.

A transform placed in the <channel>, inside <scrub:policy>, is a standing claim about the feed: this instance does this to everything it publishes. A transform placed in an <item>, inside <scrub:provenance>, is a claim about that item alone: this specific operation was performed on this specific content.

The two levels answer different questions and neither replaces the other. A policy tells a consumer what to expect of every item, including ones it has not seen yet, but cannot record when a given operation ran. Per-item provenance records exactly that, but says nothing about the items around it.

scrub:policy

Zero or one <scrub:policy> element MAY appear as a direct child of <channel>. It contains one or more <scrub:transform> elements, each describing an operation the publisher applies systematically.

<scrub:policy>
  <scrub:transform type="metadata-strip"
                   scope="exif,gps"
                   agent="chatouk.sofab.ch"
                   enforced="always"/>
</scrub:policy>

A policy transform describes a rule, not an event: it MUST NOT carry a date attribute. It SHOULD carry enforced to state how reliably the rule holds — the gap between a guarantee and an intention is the whole value of the declaration.

scrub:provenance

Zero or one <scrub:provenance> element MAY appear as a direct child of an <item>. It contains one or more <scrub:transform> elements, each describing an operation actually performed on this item's content.

<scrub:provenance>
  <scrub:transform type="metadata-strip"
                   scope="exif,gps"
                   agent="chatouk.sofab.ch"
                   date="2026-09-16T09:12:00.000Z"
                   ref="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"/>
</scrub:provenance>

Each transform describes exactly one operation. Two operations are two elements, never one element with a compound type.

Transforms are ordered by date, oldest first, which makes the element a chain rather than a set. A node that relays an item and performs its own transformations appends them and MUST NOT touch existing entries, including ones it does not understand. This is what lets the chain survive federation: each hop adds its link and the reader sees the whole path.

scrub:transform

The common unit. Always empty — all information is carried by attributes.

AttributeUseValue
type Required The operation performed. A term from the core vocabulary, or an extension term prefixed x-.
agent Required The hostname of the service that performed the operation, or that enforces it. A hostname rather than a display name, so that entries in a federated chain can be told apart reliably.
date Required in scrub:provenance, forbidden in scrub:policy When the operation ran, as an ISO 8601 timestamp with an explicit UTC offset. Note that this is not RFC 822. RSS uses RFC 822 for pubDate, but timestamps in this namespace are ISO 8601, which sorts lexicographically.
scope Optional What the operation was applied to. Either a medium — video, audio, image, text — or a comma-separated list of finer targets such as exif,gps. Absence means the whole item.
ref Optional, meaningful only in scrub:provenance The URL of the media file the operation was applied to — the same URL the item exposes through its <enclosure url="…"> or <media:content url="…">, compared as an exact string. This is what lets a consumer attach each transform to a specific file when an item carries several. Absence means the transform applies to the item's media as a whole; a producer that emits more than one media file per item SHOULD set it on every transform. Added 16 September 2026; see the changelog.
hash Optional, meaningful only in scrub:provenance A digest of the media file after the operation, as <algorithm>:<lowercase hex> — sha256:… in this version. It ties the claim to specific bytes rather than to a URL whose content may change; a consumer that downloads the file can confirm it is looking at what was described. Absence means the claim is about whatever the URL serves. Added 17 September 2026.
inspect Optional, meaningful only in scrub:provenance The URL of a page that downloads the file named by ref and shows the metadata containers it actually holds, so a person can check the declaration against the bytes. To be worth anything, such a page MUST perform the reading on the reader's side (in the browser) and MUST show what it looked for, not only what it found. It is a convenience, not an authority: a consumer that can run its own check SHOULD do so and MAY show the link. Added 17 September 2026.
tool Optional The implementation used, ideally with a version, such as example-stripper/1.2. Publishing it makes the claim auditable and lets the reader judge it; withholding it is permitted but weakens the declaration.
enforced Optional, meaningful only in scrub:policy How reliably the rule holds. always means the operation is fail-closed: content that cannot be transformed is not published. best-effort means it is attempted and may be skipped on failure. Absence SHOULD be read as best-effort, the weaker claim.

Core vocabulary

Six terms are defined. The list is deliberately short — a term only deserves defining once several implementations would use it the same way.

TermMeaning
metadata-stripEmbedded metadata removed from a file. Use scope to say which, such as exif,gps.
face-blurFaces detected and obscured in an image or video.
plate-blurLicence plates detected and obscured.
voice-anonymizeAudio altered to hinder speaker recognition.
redactionIdentifying content removed or masked, typically in text.
location-fuzzGeographic precision deliberately reduced.

Any other term MUST use an x- prefix, such as x-plate-redaction. Consumers MUST ignore transforms whose type they do not recognise, and MUST NOT treat an unknown type as an error or discard the enclosing element. This is what lets the vocabulary grow without coordination: a producer can coin a term today, and if several implementations converge on it, it becomes a candidate for the core vocabulary in a later version.

Rules for producers

Rules for consumers

What this format does not do

Saying so clearly is part of the specification, because a privacy claim believed stronger than it is produces precisely the harm it claimed to prevent.

Full example

Both feeds below are running, and are abridged here to the elements that matter; identifiers and hashes are shortened. The first is what chatouk.sofab.ch publishes. It strips EXIF and GPS from every photograph before storing it, and refuses the upload if it cannot, so its policy says enforced="always". The item carries two photographs, hence two executions.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:scrub="https://sofab.ch/public/ns/scrub/0.1"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>Chatouk</title>
    <link>https://chatouk.sofab.ch/</link>
    <description>All public posts</description>

    <scrub:policy>
      <scrub:transform type="metadata-strip"
                       scope="exif,gps"
                       agent="chatouk.sofab.ch"
                       enforced="always"/>
    </scrub:policy>

    <item>
      <title>Market day</title>
      <link>https://chatouk.sofab.ch/p/Zk3vQ8tXw2Lm</link>
      <guid isPermaLink="true">https://chatouk.sofab.ch/p/Zk3vQ8tXw2Lm</guid>
      <pubDate>Wed, 16 Sep 2026 09:15:00 GMT</pubDate>
      <source url="https://chatouk.sofab.ch/users/demo/rss.xml">demo</source>
      <enclosure url="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                 type="image/jpeg" length="482113"/>
      <media:content url="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                     type="image/jpeg" fileSize="482113" medium="image"/>
      <media:content url="https://chatouk.sofab.ch/media/Tf4mWs6KjA1x"
                     type="image/jpeg" fileSize="351870" medium="image"/>

      <scrub:provenance>
        <scrub:transform type="metadata-strip"
                         scope="exif,gps"
                         agent="chatouk.sofab.ch"
                         date="2026-09-16T09:12:00.000Z"
                         ref="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                         hash="sha256:9f2c…e41b"
                         inspect="https://chatouk.sofab.ch/public/scrub/inspect/?url=…"/>
        <scrub:transform type="metadata-strip"
                         scope="exif,gps"
                         agent="chatouk.sofab.ch"
                         date="2026-09-16T09:12:04.000Z"
                         ref="https://chatouk.sofab.ch/media/Tf4mWs6KjA1x"
                         hash="sha256:41d7…0a3c"
                         inspect="https://chatouk.sofab.ch/public/scrub/inspect/?url=…"/>
      </scrub:provenance>
    </item>
  </channel>
</rss>

The second is the same item as re-emitted by a relaying node, sofab.ch, which copies items byte for byte from the feed above and performs no transformation of its own.

<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
     xmlns:scrub="https://sofab.ch/public/ns/scrub/0.1"
     xmlns:media="http://search.yahoo.com/mrss/">
  <channel>
    <title>soFab relay · chatouk-relay</title>
    <link>https://sofab.ch/public/inbound-rss/chatouk-relay/</link>
    <description>Relay feed for demonstration. Items are copied verbatim
                 from https://chatouk.sofab.ch/rss.xml …</description>

    <!-- no scrub:policy here: the relay applies no rule of its own -->

    <item>
      <title>Market day</title>
      <link>https://chatouk.sofab.ch/p/Zk3vQ8tXw2Lm</link>
      <guid isPermaLink="true">https://chatouk.sofab.ch/p/Zk3vQ8tXw2Lm</guid>
      <pubDate>Wed, 16 Sep 2026 09:15:00 GMT</pubDate>
      <source url="https://chatouk.sofab.ch/users/demo/rss.xml">demo</source>
      <enclosure url="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                 type="image/jpeg" length="482113"/>
      <media:content url="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                     type="image/jpeg" fileSize="482113" medium="image"/>
      <media:content url="https://chatouk.sofab.ch/media/Tf4mWs6KjA1x"
                     type="image/jpeg" fileSize="351870" medium="image"/>

      <scrub:provenance>
        <scrub:transform type="metadata-strip"
                         scope="exif,gps"
                         agent="chatouk.sofab.ch"
                         date="2026-09-16T09:12:00.000Z"
                         ref="https://chatouk.sofab.ch/media/b7Hn2PqYc9Rd"
                         hash="sha256:9f2c…e41b"
                         inspect="https://chatouk.sofab.ch/public/scrub/inspect/?url=…"/>
        <scrub:transform type="metadata-strip"
                         scope="exif,gps"
                         agent="chatouk.sofab.ch"
                         date="2026-09-16T09:12:04.000Z"
                         ref="https://chatouk.sofab.ch/media/Tf4mWs6KjA1x"
                         hash="sha256:41d7…0a3c"
                         inspect="https://chatouk.sofab.ch/public/scrub/inspect/?url=…"/>
      </scrub:provenance>
    </item>
  </channel>
</rss>

Read as a chain: chatouk stripped the metadata from each photograph as it was uploaded, a few seconds apart, before the post that references them was published. The relay then re-emitted the item unchanged, so the provenance reaches the readers of the second feed intact and still attributed to chatouk.sofab.ch. The relay's channel carries no <scrub:policy>, because a policy is a publisher's claim about its own feed and the relay has none to make. Had it transformed the media it would have appended its own <scrub:transform> after the existing two; it did not, so it added nothing.

The ref attribute is what tells the reader which photograph each operation concerns, hash ties the claim to specific bytes, and inspect points at a page where a person can check the file. The metadata-strip entry appears both in the policy and in the item — the policy states the standing rule, the item records the specific execution. Both are true and neither is redundant.

At the time of writing the two feeds are served at https://chatouk.sofab.ch/rss.xml and https://sofab.ch/public/inbound-rss/chatouk-relay/relay.xml; the relay is a demonstration and may not stay up.

Versioning

The version is part of the namespace URI. Any change that would alter the interpretation of an existing feed — renaming an element or attribute, removing a core term, changing a term's meaning — produces a new URI. Feeds already published against https://sofab.ch/public/ns/scrub/0.1 therefore keep their meaning permanently, whatever happens to later versions.

Adding a term to the core vocabulary is not such a change, since consumers are already required to ignore terms they do not recognise. The same holds for adding an optional attribute, since consumers are required to ignore unknown attributes. New terms and optional attributes may be added to this version, and this document records the date of each.

At 0.1 the vocabulary is experimental and adoption is nil. Should it come to be used outside soFab, the URI might migrate to a neutral host; the old one would then keep resolving, because breaking published feeds to tidy a hostname would be a poor trade.

Changelog