- Docs
- Bureau — Blue Team (defensive)
- Press-Pipe
Bureau — Blue Team (defensive)
Press-Pipe
The chain of custody from a confidential source's drop through editorial review and downstream citation has historically been informal. Press-Pipe signs every link in that chain so each hop is independently verifiable.
Posture: 🔵 Blue Team (defensive) · Status: alpha
What it does
When a source provides sensitive documents to a newsroom, the chain from intake to publication is typically a mix of PGP, encrypted messaging, and informal notes. Three failure modes are common: documents are altered between source and editor; a downstream outlet "cites" a leaked dossier whose bytes do not actually match the source; and criminal evidence does not reach regulators because the routing decision is informal and unauditable.
Press-Pipe addresses all three by signing every hop. The source drop is signed at the Tor intake (only the SHA-256 digest of the document, never the cleartext). The editor reproduces the digest and signs an editor receipt asserting "I received the document with this exact hash." The newsroom publishes a dossier; downstream outlets that cite it sign their citations as leaves on the same tree. If document bytes are altered, the digests no longer match and a drop-tampered proof is emitted. If a citation does not reproduce, a citation-fabricated proof is emitted. When evidence is flagged for regulator routing, the Bounty composition routes it to SEC, FTC, or ICC and signs the routing decision.
Who would use it
- An investigative newsroom (ProPublica, ICIJ, Bellingcat) standardizing source-chain integrity.
- A whistleblower-facing intake organization (SecureDrop operators, GlobaLeaks deployers).
- A downstream aggregator (404 Media, The Verge) that cites primary newsroom dossiers.
- A regulator (SEC, FTC, ICC) accepting cryptographically-routed criminal evidence.
- A nonprofit fact-checker validating that "leaked" documents actually match source-side commitments.
What you'll need
- The Pluck CLI (
npm install -g @sizls/pluck-cli). - For source intake: a Tor hidden service running the Whistle composition, plus an Ed25519 signing key for the intake operator.
- For editorial: the editor's signing key and reproduce-digest tooling. The Pluck CLI ships a Node-based reproducer.
- For publication: a signed dossier and a public Rekor notarization.
- For regulator routing: a Bounty configuration mapping evidence type to SEC, FTC, or ICC endpoints.
Step-by-step
The alpha runs the full constraint chain on synthetic drops, receipts, and citations – there is no live Tor intake daemon yet. Production capture and verify ship in a follow-up. To exercise the system today:
pluck bureau press-pipe demo
Expected output: the system ingests two source drops, two editor receipts, and two downstream citations (one legitimate chain plus one tampered drop and one fabricated citation), and emits two signed proofs (drop-tampered and citation-fabricated). Each proof carries the digests in question and an Ed25519 signature you can independently verify against the Rekor entry.
What to do with the output: in production the WHISTLE intake daemon runs continuously on a Tor hidden service, the editor runs the reproduce-digest pass on every received document, and downstream outlets sign their citations into the same tree. Verifiers fetch Rekor entries and re-derive the chain – no newsroom or platform cooperation required.
Run it yourself
Drop this into a Node 18+ project (npm install @sizls/pluck-bureau-press-pipe @sizls/pluck-bureau-core tsx):
// index.ts
import { createHash } from "node:crypto";
import {
createPressPipeSystem,
fingerprintPrivateKey,
signCanonicalBody,
type Citation,
type EditorReceipt,
type SourceDrop,
} from "@sizls/pluck-bureau-press-pipe";
import { generateOperatorKey } from "@sizls/pluck-bureau-core";
async function main() {
const operator = generateOperatorKey();
const whistle = generateOperatorKey();
const editor = generateOperatorKey();
const outlet = generateOperatorKey();
const whistleFp = fingerprintPrivateKey(whistle.privateKeyPem);
const editorFp = fingerprintPrivateKey(editor.privateKeyPem);
const outletFp = fingerprintPrivateKey(outlet.privateKeyPem);
const torPipe = digest("pipe:tor-onion-redacted");
const proPublica = digest("outlet:propublica");
const fourOhFour = digest("outlet:404media");
const dropDigest = digest("evidence:dump-A");
const wrongDigest = digest("evidence:dump-A:tampered");
const dossierHash = digest("dossier:propublica:legit");
const press = createPressPipeSystem({
signingKey: operator.privateKeyPem,
disablePausePoll: true,
disableLogging: true,
});
try {
// 1) Source drop signed by WHISTLE.
const drop = buildDrop(torPipe, dropDigest, undefined, "2026-04-26T00:01:00.000Z", whistle.privateKeyPem, whistleFp);
press.observeDrop(drop);
// 2) Editor reproduces a DIFFERENT digest – fires drop-tampered.
press.registerReceipt(buildReceipt(drop.dropId, wrongDigest, proPublica, dossierHash, "2026-04-26T00:06:00.000Z", editor.privateKeyPem, editorFp));
// 3) Citation in reproduction mode whose cassetteRef doesn't match the dossierHash – fires citation-fabricated.
press.observeCitation(buildCitation("reproduction", dossierHash, digest("dossier:404media:fabricated"), fourOhFour, "2026-04-26T00:31:00.000Z", outlet.privateKeyPem, outletFp));
for (let i = 0; i < 60; i++) await new Promise((r) => setImmediate(r));
const proofs = press.facts.proofs();
console.log(`press-pipe proofs = ${proofs.length}`);
for (const p of proofs) console.log(`kind=${p.kind} proofId=${p.proofId.slice(0, 16)}…`);
} finally {
await press.shutdown();
}
}
function digest(s: string): string { return createHash("sha256").update(s).digest("hex"); }
function buildDrop(pipeHash: string, dropDigest: string, prevDigest: string | undefined, receivedAt: string, whistleKey: string, whistleFingerprint: string): SourceDrop {
const skeleton = { schemaVersion: 1 as const, pipeHash, dropDigest, ...(prevDigest !== undefined ? { prevDigest } : {}), receivedAt, whistleFingerprint };
const dropId = createHash("sha256").update(JSON.stringify(skeleton)).digest("hex");
const signed = signCanonicalBody({ ...skeleton, dropId }, whistleKey);
return { ...skeleton, dropId, signature: signed.signature };
}
function buildReceipt(dropId: string, dropDigest: string, outletHash: string, cassetteRef: string, signedAt: string, editorKey: string, editorFingerprint: string): EditorReceipt {
const chainDigest = createHash("sha256").update(`${dropDigest}|${cassetteRef}|${signedAt}`).digest("hex");
const skeleton = { schemaVersion: 1 as const, dropId, dropDigest, outletHash, chainDigest, signedAt, editorFingerprint };
const receiptId = createHash("sha256").update(JSON.stringify(skeleton)).digest("hex");
const signed = signCanonicalBody({ ...skeleton, receiptId }, editorKey);
return { ...skeleton, receiptId, signature: signed.signature };
}
function buildCitation(mode: Citation["mode"], dossierHash: string, cassetteRef: string, citingOutletHash: string, citedAt: string, outletKey: string, citingFingerprint: string): Citation {
const skeleton = { schemaVersion: 1 as const, mode, dossierHash, cassetteRef, citingOutletHash, citedAt, citingFingerprint };
const citationId = createHash("sha256").update(JSON.stringify(skeleton)).digest("hex");
const signed = signCanonicalBody({ ...skeleton, citationId }, outletKey);
return { ...skeleton, citationId, signature: signed.signature };
}
main().catch((err) => { console.error(err); process.exit(1); });
Run with tsx index.ts. Expected output:
press-pipe proofs = 2
kind=drop-tampered proofId=…
kind=citation-fabricated proofId=…
Open in StackBlitz – runs in your browser, no install required.
What you get
A signed PressPipe.Proof with one of three kind values:
drop-tampered– the source document at the editor's end does not reproduce the digest signed at intake.citation-fabricated– a downstream outlet asserts they reproduced the dossier, but their digest doesn't match.auto-file-routed– Bureau-signed routing record showing criminal evidence moved to a regulator with the timestamp and destination signed.
The Bureau never carries cleartext source material. Even if a notarized Press-Pipe proof is published worldwide, it cannot deanonymize the source.
What it can't do
- Press-Pipe does not protect the source's identity outside the Tor channel. Source security still depends on operational discipline (no metadata leakage, no in-band identity).
- A compromised editor key can sign false receipts. The proof catches downstream chain breaks but cannot detect a corrupted editor unless paired with a multi-party editorial signature.
- Press-Pipe does not adjudicate truth or significance – only integrity. A falsifiable chain with verified digests still has to be reported responsibly.
- The auto-file routing depends on a working regulator endpoint. The system signs the routing decision; the regulator's response is out of scope.
A real-world example
An employee at a financial regulator obtains internal memos relating to an investigation. They submit the memos via the newsroom's Whistle Tor intake. The intake daemon emits a SourceDrop with a digest the employee can recompute locally; they retain the receipt. The newsroom's editor reproduces the digest and signs the receipt. The newsroom publishes a 5,000-word feature with a published dossier root. A second outlet picks up the story, cites the dossier, and signs a Citation. The Bounty composition flags the memos as SEC-routable and emits a signed auto-file-routed proof. Subsequent disputes about whether the memos exist or were materially altered can be resolved by recomputing the digests against the published Rekor entries.
For developers
Predicate URIs
| URI | What it attests |
|---|---|
https://pluck.run/PressPipe.SourceDrop/v1 | Anonymous source submission digest plus per-pipe chain link. |
https://pluck.run/PressPipe.EditorReceipt/v1 | Editor's reproduction-of-digest commitment plus downstream chain link. |
https://pluck.run/PressPipe.Citation/v1 | Downstream outlet's reproduction or commentary citation. |
https://pluck.run/PressPipe.Proof/v1 | Published proof with kind (drop-tampered, citation-fabricated, auto-file-routed). |
Programs composed
- Whistle – anonymous source intake (Tor, post-quantum hybrid signing).
- Custody – drop → editor → publication chain-of-custody.
- Bounty – auto-file routing to SEC, FTC, ICC for criminal evidence.
- Nuclei – newsroom-published verification probe-packs (third-party reproducibility).
Threat model + adversary
Adversaries are: a hostile party in the source-to-editor channel, a corrupted editor, a downstream outlet that fabricates citations to capture clout, and a regulator that wants the criminal evidence to disappear. Press-Pipe closes the byte-integrity holes; operational source security and regulator follow-through remain organizational. See Threat Model.
Verify a published cassette
pluck bureau verify <bundle-dir>
cosign verify-blob --key <pubkey.pem> --signature <sig> --type https://pluck.run/PressPipe.Proof/v1 <body.json>
Every published proof is a DSSE envelope notarized to Rekor. Independent verifiers fetch the Rekor entry, verify the Ed25519 signature against the operator's published signing-key fingerprint, and re-derive the chain digests against the source drops. No newsroom or platform participation required.
See also
- Bureau Foundations
- Threat Model
- Verify a dossier
- Whistle – Tor intake composition
- Custody – chain-of-custody primitives
- Bounty – regulator routing
- Nuclei – verification probe-packs