- Docs
- Bureau — Blue Team (defensive)
- Icarus
Bureau — Blue Team (defensive)
Icarus
A drone is controlled by a stream of small signed commands and broadcasts a Part 89 RemoteID identity once per second. Icarus notarizes both. When the command stream contradicts the operator's signed flight plan, or a witnessed RemoteID broadcast disagrees with where the drone reports it is, Icarus emits a signed proof identifying the contradiction.
Posture: 🔵 Blue Team (defensive) · Status: alpha
What it does
A drone is driven by a stream of small commands sent from a ground-control station: "arm motors," "take off," "go to waypoint 2," "tilt the gimbal 30 degrees." That stream is C2 (Command and Control). Separately, the FAA's Part 89 rule (and EASA's equivalent in Europe) requires drones to broadcast their identity and position over the air every second – that's RemoteID. Anyone with a phone app can decode those broadcasts.
Icarus is a notary for both. Before a flight, the operator publishes a signed Flight plan – a hashed drone serial, authorized waypoints, time window, optional geofence polygon. During the flight, the ground-control station signs every C2 command. Independent witnesses (a fixed receiver near the airfield, a passing pilot's app, a delivery customer's phone) capture RemoteID broadcasts and sign them. Icarus ingests all three, hashes the C2 commands into a per-flight Merkle tree, and runs five contradiction checks: an unauthorized command (signed by a key that's not the flight's operator – somebody seized control), a flight-plan deviation (commanded position outside authorized waypoints), a RemoteID disagreement (witnessed broadcast doesn't match where the drone says it is), a geofence violation, or an out-of-window event.
Who would use it
- A delivery-drone operator (Amazon Prime Air, Wing, Zipline) needing tamper-evident flight logs for FAA Part 89 + insurance + post-incident forensics.
- An infrastructure inspector (gas utility, cell tower, wind farm) producing a defensible record that their drone stayed within authorized airspace.
- A film-production company needing court-admissible evidence the drone wasn't where the angry homeowner thinks it was.
- A counter-drone operator (airport, prison, stadium) building an observation trail of unauthorized incursions for prosecution.
- An insurance carrier auditing claims after a drone-on-property incident.
What you'll need
- The Pluck CLI installed (
pnpm add -g @sizls/pluck-cli). - An operator key for the ground-control station that issues C2 commands.
- For RemoteID corroboration: at least one witness device. Any Bluetooth 5 / WiFi-aware phone or tablet works (FAA Part 89 specifies these as the standard RemoteID broadcast channels). Open-source apps like Drone Scanner and OpenDroneID will decode broadcasts.
- A signed flight plan published before the flight begins.
Step-by-step
The alpha runs an in-memory demo on synthetic flights. Live capture (a ground-control-station bridge, a RemoteID receiver bridge, a daemon ingesting both) ships in a follow-up. To see the contradiction engine work today:
pluck bureau icarus demo
You'll see something like:
icarus/demo: ingesting 1 flight plan + 5 C2 commands (4 authorized, 1 hijack) + 3 RemoteID broadcasts...
[Bureau/Icarus] hijack proof=7ae27d6ad476… class=unauthorized-command flightId=842c30ff29de…
[Bureau/Icarus] hijack proof=de3cc9d134b9… class=remote-id-disagreement flightId=842c30ff29de…
icarus/demo: hijack proofs emitted = 2
The synthesized run has one Flight (3 waypoints, 30-minute window), 5 C2 commands (4 from the operator's key, 1 from an attacker's key driving the drone 30 km off plan), and 3 witness-signed RemoteID broadcasts (2 on plan, 1 disagreeing). The constraint engine emits one unauthorized-command proof (the attacker key) and one remote-id-disagreement proof (the witness saying the drone is somewhere it shouldn't be).
Run it yourself
Drop this into a Node 18+ project (npm install @sizls/pluck-bureau-icarus @sizls/pluck-bureau-core tsx):
// index.ts
import { createHash } from "node:crypto";
import {
createIcarusSystem,
fingerprintPrivateKey,
signCanonicalBody,
} from "@sizls/pluck-bureau-icarus";
import { generateOperatorKey } from "@sizls/pluck-bureau-core";
const hashOf = (s: string) => createHash("sha256").update(s).digest("hex");
const flush = (n = 8) =>
new Promise<void>((res) => {
let i = 0;
const tick = () => (++i >= n ? res() : setImmediate(tick));
setImmediate(tick);
});
function sign<T extends Record<string, unknown>>(body: T, idKey: string, key: string): any {
const skel = { schemaVersion: 1, ...body };
const id = createHash("sha256").update(JSON.stringify(skel)).digest("hex");
const signed = signCanonicalBody({ ...skel, [idKey]: id }, key);
return { ...skel, [idKey]: id, signature: signed.signature };
}
async function main() {
const operator = generateOperatorKey();
const operatorKey = operator.privateKeyPem;
const operatorFp = fingerprintPrivateKey(operatorKey);
const witnessKey = (generateOperatorKey()).privateKeyPem;
const witnessFp = fingerprintPrivateKey(witnessKey);
const attackerKey = (generateOperatorKey()).privateKeyPem;
const attackerFp = fingerprintPrivateKey(attackerKey);
const droneSerialHash = hashOf("drone:DEMO-001");
const wp1 = { lat: 37.4275, lon: -122.1697, altMeters: 100 };
const wp2 = { lat: 37.4280, lon: -122.1700, altMeters: 100 };
const icarus = createIcarusSystem({
signingKey: operatorKey, disablePausePoll: true, disableLogging: true,
});
try {
const flight = sign({
droneSerialHash, operatorFingerprint: operatorFp,
windowStart: "2026-04-26T12:00:00Z", windowEnd: "2026-04-26T12:30:00Z",
waypoints: [
{ id: "wp-1", point: wp1, radiusMeters: 200 },
{ id: "wp-2", point: wp2, radiusMeters: 200 },
],
signedAt: "2026-04-26T11:55:00Z",
}, "flightId", operatorKey);
icarus.observeFlight(flight);
// Authorized takeoff command.
icarus.observeCommand(sign({
flightId: flight.flightId, sequence: 0, kind: "takeoff",
issuedAt: "2026-04-26T12:00:30Z", signerFingerprint: operatorFp,
}, "commandId", operatorKey));
// HIJACK – attacker key signs a takeover divert.
icarus.observeCommand(sign({
flightId: flight.flightId, sequence: 1, kind: "waypoint-update",
target: { lat: 37.6, lon: -122.4, altMeters: 100 },
issuedAt: "2026-04-26T12:10:00Z", signerFingerprint: attackerFp,
}, "commandId", attackerKey));
// RemoteID broadcast says the drone is 30km off plan.
icarus.observeRemoteId(sign({
droneSerialHash, position: { lat: 37.7, lon: -122.45, altMeters: 100 },
observedAt: "2026-04-26T12:11:00Z", witnessFingerprint: witnessFp,
}, "observationId", witnessKey));
await flush();
const proofs = icarus.facts.hijackProofs();
console.log(`hijack proofs: ${proofs.length}`);
for (const p of proofs) console.log(` class=${p.class}`);
} finally {
await icarus.shutdown();
}
}
main().catch((err) => { console.error(err); process.exit(1); });
Run with tsx index.ts. Expected output:
hijack proofs: 2
class=unauthorized-command
class=remote-id-disagreement
▶ Open in StackBlitz – runs in your browser, no install required.
What you get
A signed Icarus.Hijack cassette per contradiction containing:
- The signed flight plan (the ground-truth contract).
- The offending C2 commands and/or RemoteID broadcasts, verbatim, each signed by their original signer.
- The contradiction class.
- A Rekor uuid pinning the bundle to the public log.
For an FAA Part 107 investigation, a courtroom, or an insurance claim, the bundle is independently checkable: any expert can re-run the on-plan / in-window / nearest-waypoint geometry against the carried flight plan and confirm the hijack class is justified by the carried observations.
What it can't do
- No witness, no RemoteID-disagreement proof. Operators with no third-party witness can still detect unauthorized-command and flight-plan-deviation, since both are checked against the operator's own signed flight plan.
- The drone serial is hashed. A Rekor verifier learns only that two flights belong to the same airframe, never the fleet roster.
- Control-station bytes are excluded. The optional "control-station location" RemoteID field would leak operator location, so it's never carried in the signed body.
- Compromised operator key. A leaked key produces signed-but-illegitimate flights. Rotate revocation invalidates after the rotation epoch; pre-rotation flights remain valid.
- Spoofed RemoteID (an attacker broadcasting a fake RemoteID frame for a drone that isn't there) is detectable only if multiple witnesses disagree.
A real-world example
A regional electric utility uses inspection drones to walk transmission lines after every storm. After a customer files a complaint that "a utility drone hovered over my pool for 45 minutes," the utility pulls the flight's Icarus dossier: a signed flight plan covering a transmission-corridor inspection on the relevant Saturday, 12 signed C2 commands all from the licensed pilot's key, 4 witness-signed RemoteID broadcasts – none of which place the drone within 1.4 km of the complainant's address. The utility's lawyer attaches the Rekor uuids to the response. Two independent drone-forensics consultants verify the bundles separately; both confirm the drone never entered the airspace claimed. The complaint is dismissed. The bundles cost the utility nothing extra to produce – they're a byproduct of the same operator key the pilots already use.
For developers
Predicate URIs
| URI | What it attests |
|---|---|
https://pluck.run/Icarus.Flight/v1 | The operator's signed flight plan – hashed drone serial, authorized waypoints, time window, optional geofence polygon. |
https://pluck.run/Icarus.Command/v1 | One signed C2 command – kind, optional commanded position, monotonic per-flight sequence number, signer fingerprint. |
https://pluck.run/Icarus.RemoteID/v1 | One witness's signed observation of one RemoteID broadcast – drone-serial hash, position, capture timestamp, witness fingerprint. |
https://pluck.run/Icarus.Hijack/v1 | Signed hijack/contradiction proof – carries the flight plan + offending commands + offending broadcasts verbatim. |
Programs composed
- Oath – operator and witness identities are Oath-managed keys
- Custody – every flight, command, and broadcast is a Custody leaf, hash-linked into the per-flight Merkle dossier
- Rotate – when an operator key is revoked, commands after the revocation epoch are flagged
- Sigstore Rekor – proofs are notarized as DSSE in-toto envelopes with a public inclusion proof
Threat model + adversary
No witness, no RemoteID-disagreement detection (operators can still detect unauthorized-command + flight-plan-deviation against their own signed plan). Drone serial is hashed (droneSerialHash = sha256(serial)) – verifiers learn only same-airframe linkage, never fleet roster. RemoteID's control-station-location field is excluded from signed bodies (operator-PII protection). Compromised operator key produces signed-but-illegitimate flights – Rotate revocation invalidates after the rotation epoch; pre-rotation flights remain valid. Spoofed RemoteID is detectable only if multiple witnesses disagree.
Verify a published cassette
pluck bureau verify <bundle-dir>
cosign verify-attestation \
--certificate-identity-regexp '.*' \
--certificate-oidc-issuer-regexp '.*' \
<envelope-hash>.intoto.jsonl
See also
- Bureau Foundations
- Threat Model
- Verify a dossier
- Oath – operator/witness key management
- Rotate – key revocation ledger
- Custody – chain-of-custody Merkle dossiers
- Embodied-Ledger – sibling robot/AV black-box