Skip to content

Bureau — Blue Team (defensive)

Celeste

Drone delivery, autonomous trucks, financial-trading time-sync, and power-grid sensors all assume their GPS is real. A spoofer can lie to GPS receivers cheaply (~$300 of equipment). Celeste catches spoofers by comparing what two distant receivers SHOULD see – a real signal disagrees slightly across distance; a spoof agrees too well.

Posture: 🔵 Blue Team (defensive)   ·   Status: alpha

What it does

GPS spoofing means broadcasting a fake satellite signal that's stronger than the real ones, so any receiver in earshot computes a wrong position or a wrong time. It's been done with off-the-shelf software-defined radios and open-source code; it's used to misdirect drones, falsify shipping tracker positions, and shift the timestamps that financial systems rely on. (GPS is the U.S. system. Most receivers also use GLONASS, Galileo, and BeiDou – the catch-all is GNSS, Global Navigation Satellite System. Celeste works on any of them.)

Celeste exploits geometry. Two GNSS receivers physically separated by tens of miles see the same satellites from slightly different angles, so the line-of-sight velocity (the Doppler shift) is slightly different at each receiver. They also typically disagree on signal strength because of local terrain. A spoofer broadcasts one synthesized signal across an area, so two receivers in earshot of the spoofer see suspiciously identical Doppler vectors. Celeste ingests signed receiver fixes from geographically distinct receivers, computes the divergence, and when the divergence is too small to be physical, emits a Spoof proof.

Who would use it

  • A drone-delivery operator wanting a defensible record that no spoofing was detected during a delivery (insurance + post-incident forensics).
  • A power-utility compliance officer filing NERC-CIP (North American Electric Reliability Corporation Critical Infrastructure Protection) records about GPS time-source integrity at substations.
  • A maritime-fleet auditor proving a ship was where the AIS log says it was – Celeste says the ship's time was honest, Cosmos says the ship was the ship.
  • A high-frequency-trading firm whose timestamping must remain provably authentic during the trading window.
  • A journalist investigating a port that allegedly turned its AIS off and tampered with GPS during a sanctions-busting transit.

What you'll need

  • The Pluck CLI installed (pnpm add -g @sizls/pluck-cli).
  • Two or more GNSS receivers with raw-fix output (most surveying-grade receivers expose this; consumer-grade phones do not). The u-blox ZED-F9P module (~$220) is a common starting point. Receivers must be at geographically distinct locations.
  • (Optional but recommended) OSNMA support (Open Service Navigation Message Authentication – Galileo's authenticated-navigation tag, which a spoofer can't forge without the long-term key). Modern u-blox receivers support OSNMA.
  • An operator key per receiver host.

Step-by-step

The alpha runs an in-memory demo on synthetic GNSS fixes. The production path (live u-blox bridge, multi-receiver ingestion daemon, OSNMA validator integration) ships in a follow-up. To see the contradiction engine work today:

Shell
pluck bureau celeste demo

You'll see something like:

celeste/demo: ingesting 4 synthetic GNSS fixes (2 receivers, divergent fingerprints)...
celeste/demo: spoof candidates emitted = 2
celeste/demo: candidate spoofId=59872aec41ec… triggers=doppler-divergence confidence=0.75
[Bureau/Celeste] spoof confirmed spoofId=59872aec41ec… bucket=2026-04-26T12:00:00Z|dq8hd
celeste/demo: spoofs notarized = 1

The synthesized fixes come from two receivers in geographically distinct geohashes. Receiver A's fixes look normal. Receiver B's share Receiver A's exact Doppler vector (impossible for two receivers physically apart) and have a uniformly elevated C/N0 (Carrier-to-Noise density ratio – basically signal strength per satellite, a hallmark of synthesizer leakage). The constraint engine detects the divergence at confidence 0.75 (above the 0.7 publication threshold), one operator co-signs, and the spoof publishes.

Run it yourself

Drop this into a Node 18+ project (npm install @sizls/pluck-bureau-celeste @sizls/pluck-bureau-core tsx):

TypeScript
// index.ts
import { createHash } from "node:crypto";
import {
  createCelesteSystem,
  fingerprintPrivateKey,
  signCanonicalBody,
} from "@sizls/pluck-bureau-celeste";
import { generateOperatorKey } from "@sizls/pluck-bureau-core";

const flush = () => new Promise((r) => setImmediate(r));

function signFix(body: any, key: string) {
  const skel = { schemaVersion: 1, ...body };
  const fixId = createHash("sha256").update(JSON.stringify(skel)).digest("hex");
  const signed = signCanonicalBody({ ...skel, fixId }, key);
  return { ...skel, fixId, signature: signed.signature };
}

async function main() {
  const operator = generateOperatorKey();
  const rxAKey = (generateOperatorKey()).privateKeyPem;
  const rxBKey = (generateOperatorKey()).privateKeyPem;
  const rxAFp = fingerprintPrivateKey(rxAKey);
  const rxBFp = fingerprintPrivateKey(rxBKey);

  const celeste = createCelesteSystem({
    signingKey: operator.privateKeyPem,
    quorumThreshold: { required: 1, outOf: 1 },
    disablePausePoll: true,
    disableLogging: true,
  });

  // Same Doppler vector across geographically distinct receivers = spoofer's tell.
  const sharedDoppler = [120.5, -84.1, 56.0, -201.3, 33.8, 14.7, -91.0, 47.2];

  try {
    // Receiver A in Mountain View.
    for (let i = 0; i < 2; i++) {
      celeste.observeFix(signFix({
        observedAt: `2026-04-26T12:00:0${i}Z`, geohash: "dr5ru",
        latitude: 37.42, longitude: -122.08, altitudeM: 30,
        hdop: 0.9, vdop: 1.4,
        cn0: [42, 41, 39, 38, 37, 36, 33, 30],
        doppler: sharedDoppler,
        pseudoRangeResiduals: [1.2, 0.8, -0.5, 0.3, 0.1, -0.2, 0.6, 0.4],
        osnmaValid: true, receiverFingerprint: rxAFp,
      }, rxAKey));
    }
    // Receiver B in Las Vegas – uniform C/N0 (synthesizer leak), same Doppler.
    for (let i = 0; i < 2; i++) {
      celeste.observeFix(signFix({
        observedAt: `2026-04-26T12:00:0${i}Z`, geohash: "dq8hd",
        latitude: 36.10, longitude: -115.17, altitudeM: 610,
        hdop: 0.9, vdop: 1.5,
        cn0: [55, 55, 55, 55, 55, 55, 55, 55],
        doppler: sharedDoppler,
        pseudoRangeResiduals: [1, 1, 1, 1, 1, 1, 1, 1],
        osnmaValid: true, receiverFingerprint: rxBFp,
      }, rxBKey));
    }

    await flush();
    const candidates = celeste.facts.spoofCandidates();
    console.log(`spoof candidates: ${candidates.length}`);

    // Single-operator quorum cosign.
    const fp = fingerprintPrivateKey(operator.privateKeyPem);
    celeste.receiveWitness({
      schemaVersion: 1, fingerprint: fp, spoofId: candidates[0]!.spoofId,
      signature: createHash("sha256").update(`${fp}|${candidates[0]!.spoofId}`).digest("base64"),
      signedAt: new Date().toISOString(),
    });
    await flush();

    const confirmed = celeste.facts.confirmedSpoofs();
    console.log(`spoofs notarized: ${confirmed.length}`);
  } finally {
    await celeste.shutdown();
  }
}

main().catch((err) => { console.error(err); process.exit(1); });

Run with tsx index.ts. Expected output:

spoof candidates: 1
spoofs notarized: 1

▶ Open in StackBlitz – runs in your browser, no install required.

What you get

A signed Celeste.Spoof cassette containing:

  • The contradicting fixes from each receiver, signed by each receiver's host.
  • The Doppler / C-N0 divergence math.
  • The OSNMA verdict (if available).
  • A confidence score and the trigger list.
  • A Rekor uuid pinning the bundle to the public log.

The bundle proves a contradiction in physics, not just in records. An insurance adjuster, a court, or a regulator can hand the bundle to any GNSS expert; the math is checkable independent of the publisher.

What it can't do

  • A spoofer with one synthesizer per receiver, each with appropriately diverging Doppler, defeats the divergence math. That's expensive – far beyond a $300 SDR – but a state actor can do it.
  • Multipath in dense urban canyons creates legitimate divergence that looks like spoofing. Confidence threshold tuning is the operator's job per environment.
  • A receiver running malicious firmware can sign whatever fix it wants. Two-of-distinct-receivers quorum is the defense.
  • The alpha consumes pre-validated OSNMA verdicts. A flawed upstream OSNMA validator is invisible to Celeste; trust + audit it independently.
  • ISO 8601 :60 (leap second) timestamps are not parseable by the alpha. Operators must pre-translate GPS time → UTC. GPS week rollover (2019, 2038) affects raw RINEX → UTC conversion at the upstream u-blox bridge layer; not handled by this attestor.
  • Live capture and daemon mode ship in a follow-up.

A real-world example

A regional utility runs PMUs (precision time-synchronized grid sensors) at twelve substations across three counties. After two reliability events traced to clock drift, the operations team adds a Celeste receiver alongside each PMU. Six months in, two adjacent substations report Doppler vectors agreeing to four decimal places for an eleven-minute window – physically impossible across thirty miles. Celeste signs a spoof proof; the operations team correlates the window with a NERC reportable event log and confirms the affected PMUs reported wrong time. The signed bundle goes into the post-incident report. Years later, when a similar attack happens elsewhere, the utility's archived bundles let post-incident analysts compare signatures and identify the same threat actor.


For developers

Predicate URIs

URIWhat it attests
https://pluck.run/Celeste.Fix/v1One receiver produced this GNSS solution at this wall-time, observing these satellites with these per-PRN C/N0 and Doppler vectors, with this OSNMA verdict.
https://pluck.run/Celeste.Spoof/v1Two-or-more receivers in the same wall-time bucket but geohash-distinct positions reported GNSS fingerprints that contradict physical geometry.
https://pluck.run/Celeste.Time/v1A signed local-clock chain has a monotonicity break (t_n+1 < t_n) – time-tamper proof.

Programs composed

  • Raven – IQ-tile substrate (the GNSS L1/L5 band tiles the receiver's RF was extracted from)
  • Custody – the time-anchor chain machinery
  • Pluck core's DSSE in-toto envelopes + Sigstore Rekor client
  • Directive constraints – divergence math, OSNMA-failure detector, time-chain monotonicity check, quorum gate
  • Whistle-style ephemeral witness keys for spoof co-signature
  • Bureau core's pause kill-switch (Celeste pauses if celeste OR upstream raven is paused)

Threat model + adversary

A spoofer with geographically-tailored signals (one synthesizer per receiver with diverging Doppler) defeats the divergence math – expensive but state-actor-feasible. Multipath in dense urban canyons creates legitimate divergence resembling spoofing; threshold tuning is per-environment. A malicious receiver firmware can sign whatever fix it wants – defense is two-of-distinct-receivers quorum. The alpha consumes pre-validated OSNMA verdicts; a flawed upstream OSNMA validator is invisible.

Verify a published cassette

Shell
pluck bureau verify <bundle-dir>

cosign verify-blob \
  --key <pubkey.pem> \
  --signature <signature.sig> \
  --type https://pluck.run/Celeste.Spoof/v1 \
  <body.json>

See also

Edit this page on GitHub
Previous
Karma
Next
Cosmos

Ready to build?

Install Pluck and follow the Quick Start guide to wire MCP-first data pipelines into your agents and fleets in minutes.

Get started →