- Docs
- Bureau — Red & Blue (dual-use)
- Magneto-Air
Bureau — Red & Blue (dual-use)
Magneto-Air
An air-gapped computer (no Wi-Fi, Bluetooth, or Ethernet) can still leak data via covert channels. Malware can modulate CPU activity to encode bits onto the host's magnetic field, which a magnetometer at roughly one meter can decode. Magneto-Air monitors a secure room for those signals and signs each detection.
Posture: 🟣 Red & Blue (dual-use) · Status: alpha
What it does
A CPU under load doesn't just emit electromagnetic radiation (which a Faraday cage blocks) – it also induces a magnetic field, which a Faraday cage does not block. That magnetic field is tiny but measurable at about one meter by the magnetometer chip in any modern smartphone (the same sensor used for compass heading). Academic researchers have published a family of attacks (AIR-FI, MAGNETO, POWERHAMMER) showing that malware on an air-gapped computer can deliberately modulate its CPU's instruction mix to encode bytes onto the magnetic field. A receiver – a phone in someone's pocket walking past the cage, or a planted sensor – picks the modulation up and demodulates it.
Magneto-Air sits on the defender side. It captures one-second magnetometer traces (3-axis, microtesla) at 8-20 kHz sample rate and runs three detectors: fsk-exfil (multiple alternating tones in the 1-5 kHz AIR-FI band), psk-exfil (a single dominant carrier with rapid phase variance), and baseline-anomaly (the room's magnetic field deviates more than 3 sigma from the registered clean state). The Bureau attests the detection, never the cleartext bytes recovered from the covert channel – this is why the program is shippable as open source. It's a defensive countermeasure, not an exfiltration tool.
Who would use it
- A SCIF (Sensitive Compartmented Information Facility) operator who needs continuous attested clean-state magnetic baselines.
- A defense contractor or three-letter agency security team auditing classified-network rooms.
- A datacenter operator hosting customer air-gapped racks who wants to sell tenant-facing covert-channel detection.
- A pentest team validating that a client's AIR-FI countermeasures actually catch the attack class.
- A researcher studying real-world prevalence of magnetic-field exfiltration.
What you'll need
- The Pluck CLI (
npm install -g @sizls/pluck-cli). - A magnetometer. Options: a smartphone with the Pluck mobile reader app, a USB GMR (Giant Magnetoresistance) sensor breakout (about $80), or a high-end fluxgate (about $400) for the sensitive deployment. Sample rate must be at least 8 kHz to cover the AIR-FI envelope.
- A signed clean-state baseline of the room. You generate the baseline by running 10 minutes of capture during commissioning while the room is at known-clean state.
- Wall power and a tripod – magnetometers are sensitive to position. Once placed, do not move the sensor without re-baselining.
Step-by-step
The alpha runs the full constraint chain on synthetic 3-axis traces – there is no live magnetometer integration yet. Production capture ships in a follow-up. To exercise the system today:
pluck bureau magneto-air demo
Expected output: the system registers a SCIF magnetic baseline, ingests four signed traces (one inside the commissioning window that is correctly skipped, plus three contradictions: an fsk-exfil, a psk-exfil, and a baseline-anomaly), and emits three signed exfil/anomaly proofs. Each proof carries the detection facts (tone frequencies, phase variance, sigma distance) but explicitly does NOT carry the demodulated payload.
What to do with the output: in production the magnetometer streams continuously into the SCIF defender daemon. Detections publish to Rekor and trigger the facility's response playbook (audit the network, audit installed hardware, sweep for unauthorized devices). The Rekor entry serves as an observation for the after-action review.
Run it yourself
Drop this into a Node 18+ project (npm install @sizls/pluck-bureau-magneto-air @sizls/pluck-bureau-core tsx):
// index.ts
import { createHash } from "node:crypto";
import {
createMagnetoAirSystem,
fingerprintPrivateKey,
signCanonicalBody,
type BaselineMagneto,
type MagnetoTrace,
} from "@sizls/pluck-bureau-magneto-air";
import { generateOperatorKey } from "@sizls/pluck-bureau-core";
const SAMPLE_RATE = 16_000;
const SAMPLE_LEN = 1_024;
async function main() {
const operator = generateOperatorKey();
const reader = generateOperatorKey();
const auditor = generateOperatorKey();
const readerFp = fingerprintPrivateKey(reader.privateKeyPem);
const auditorFp = fingerprintPrivateKey(auditor.privateKeyPem);
const phoneSensor = digest("sensor:smartphone-mag-001");
const labLocation = digest("location:open-lab-B");
const scifLocation = digest("location:scif-rack-A");
const magneto = createMagnetoAirSystem({
signingKey: operator.privateKeyPem,
disablePausePoll: true,
disableLogging: true,
});
try {
magneto.registerBaseline(buildBaseline(scifLocation, baselineCentroid(), 0.01, 240, "2026-04-26T00:00:00.000Z", "2026-04-26T00:05:00.000Z", "2026-04-26T00:00:00.000Z", auditor.privateKeyPem, auditorFp));
// FSK exfil (alternating 2 kHz / 3 kHz tones).
magneto.observeTrace(buildTrace("2026-04-26T00:10:00.000Z", reader.privateKeyPem, readerFp, phoneSensor, labLocation, fskSignal()));
// PSK exfil (2.5 kHz BPSK).
magneto.observeTrace(buildTrace("2026-04-26T00:11:00.000Z", reader.privateKeyPem, readerFp, phoneSensor, labLocation, pskSignal()));
for (let i = 0; i < 60; i++) await new Promise((r) => setImmediate(r));
const proofs = magneto.facts.proofs();
console.log(`magneto proofs = ${proofs.length}`);
for (const p of proofs) console.log(`kind=${p.kind} location=${p.trace.locationHash.slice(0, 12)}…`);
} finally {
await magneto.shutdown();
}
}
function digest(s: string): string { return createHash("sha256").update(s).digest("hex"); }
function baselineCentroid(): number[] {
const out = new Array<number>(64);
for (let k = 0; k < 64; k++) out[k] = 0.005 + 0.002 * Math.cos((k / 64) * Math.PI * 2);
return out;
}
function ambient(axisAmp: number): number[] {
const out = new Array<number>(SAMPLE_LEN);
for (let i = 0; i < SAMPLE_LEN; i++) {
const t = i / SAMPLE_RATE;
out[i] = axisAmp * 25 + 0.05 * Math.sin(2 * Math.PI * 60 * t) + 0.02 * Math.sin(2 * Math.PI * 120 * t);
}
return out;
}
function fskSignal(): { x: number[]; y: number[]; z: number[] } {
const x = ambient(0.7), y = ambient(0.6), z = ambient(0.4);
for (let i = 0; i < SAMPLE_LEN; i++) {
const t = i / SAMPLE_RATE;
const sym = Math.floor(i / 64) % 2;
x[i]! += 5.0 * Math.sin(2 * Math.PI * (sym === 0 ? 2_000 : 3_000) * t);
}
return { x, y, z };
}
function pskSignal(): { x: number[]; y: number[]; z: number[] } {
const x = ambient(0.7), y = ambient(0.6), z = ambient(0.4);
for (let i = 0; i < SAMPLE_LEN; i++) {
const t = i / SAMPLE_RATE;
const sym = Math.floor(i / 96) % 2;
x[i]! += 6.0 * Math.sin(2 * Math.PI * 2_500 * t + (sym === 0 ? 0 : Math.PI));
}
return { x, y, z };
}
function buildTrace(timestamp: string, readerKey: string, readerFingerprint: string, sensorFingerprint: string, locationHash: string, axes: { x: number[]; y: number[]; z: number[] }): MagnetoTrace {
const seed = timestamp + locationHash + sensorFingerprint + String(SAMPLE_RATE);
const traceDigest = createHash("sha256").update(`trace:${seed}`).digest("hex");
const skeleton = { schemaVersion: 1 as const, locationHash, sensorFingerprint, traceDigest, sampleRate: SAMPLE_RATE, samplesX: axes.x, samplesY: axes.y, samplesZ: axes.z, timestamp, readerFingerprint };
const traceId = createHash("sha256").update(JSON.stringify(skeleton)).digest("hex");
const signed = signCanonicalBody({ ...skeleton, traceId }, readerKey);
return { ...skeleton, traceId, signature: signed.signature };
}
function buildBaseline(locationHash: string, centroid: number[], stddev: number, sampleCount: number, windowStart: string, windowEnd: string, registeredAt: string, auditorKey: string, auditorFingerprint: string): BaselineMagneto {
const skeleton = { schemaVersion: 1 as const, locationHash, centroid, stddev, sampleCount, windowStart, windowEnd, registeredAt, auditorFingerprint };
const baselineId = createHash("sha256").update(JSON.stringify(skeleton)).digest("hex");
const signed = signCanonicalBody({ ...skeleton, baselineId }, auditorKey);
return { ...skeleton, baselineId, signature: signed.signature };
}
main().catch((err) => { console.error(err); process.exit(1); });
Run with tsx index.ts. Expected output:
magneto proofs = 2
kind=fsk-exfil location=…
kind=psk-exfil location=…
Open in StackBlitz – runs in your browser, no install required.
What you get
A signed MagnetoAir.Proof with one of three kind values:
fsk-exfil– alternating tones in the 1-5 kHz AIR-FI band exceed an expected power ratio.psk-exfil– a single dominant carrier in the AIR-FI band with rapid phase variance, measured by Goertzel filter.baseline-anomaly– the room's magnetic field deviates more than 3 sigma from the registered baseline. Possible unauthorized device powered on, or active EM bridge.
The proof carries detection facts (frequencies, sigma distance, phase variance) but never the demodulated payload bytes – a critical privacy property for a defensive open-source tool.
What it can't do
- A malicious receiver (the attacker's phone in someone's pocket) is invisible to Magneto-Air. The program detects the transmitter (compromised host inside the cage), not the receiver.
- Local environmental magnetic noise (a passing forklift, an HVAC compressor cycling) can produce baseline-anomaly false positives. Tune the baseline carefully and revisit during seasonal changes.
- The reader's signing key is a trust anchor. A compromised reader can suppress detections.
- Sub-microtesla signals at 1-5 kHz that fall below your sensor's noise floor are invisible – match sensor sensitivity to your threat model.
A real-world example
A defense contractor running classified AI workloads on an air-gapped SCIF rack starts seeing inconsistent eval results. An internal auditor suspects an insider has installed a malicious driver. Magneto-Air sensors are placed at four corners of the SCIF for a 72-hour capture. Two corners emit fsk-exfil proofs every 14 minutes – alternating tones at 2.1 kHz and 3.4 kHz, well above noise floor. The auditor walks the room with a portable directional magnetometer, triangulates the source to a single workstation, and finds a USB device with custom firmware. The Rekor entries become the evidentiary record for the contractor's incident report to the cognizant security authority.
For developers
Predicate URIs
| URI | What it attests |
|---|---|
https://pluck.run/MagnetoAir.Trace/v1 | Reader-signed 3-axis magnetometer trace: digest of the raw sample stream, bounded per-axis sample arrays for detector math, bound location and sensor fingerprint hashes. |
https://pluck.run/MagnetoAir.Baseline/v1 | Auditor-signed clean-state magnetic baseline for a SCIF location, including the commissioning window range that's excluded from anomaly detection. |
https://pluck.run/MagnetoAir.Proof/v1 | Bureau-signed exfil/anomaly proof with kind (fsk-exfil, psk-exfil, baseline-anomaly). Carries detection facts but NOT the demodulated payload. |
Programs composed
- Thermal-Afterglow – same SCIF-defender market, different physics. Pair both for layered defense.
- Ember – multi-modal side-channel attestor; complementary, focused on AI workload identity.
- Oath / Dragnet – facility and sensor identity binding upstream.
- Evidence-Locker – long-term storage for trace corpora and dossier roots.
Threat model + adversary
The adversary is a state actor or sophisticated insider who has compromised an air-gapped host inside a secure facility. Their next move is exfiltration over an alternate channel (magnetic, acoustic, thermal, optical). Magneto-Air closes the magnetic channel. See Threat Model.
Verify a published cassette
pluck bureau verify <bundle-dir>
cosign verify-blob --key <pubkey.pem> --signature <sig> --type https://pluck.run/MagnetoAir.Proof/v1 <body.json>
Every signed shape is a detached Ed25519 signature over canonical JSON, optionally notarized into Sigstore Rekor as a DSSE in-toto envelope.
See also
- Bureau Foundations
- Threat Model
- Verify a dossier
- Thermal-Afterglow – IR-camera observation chain for the same SCIF-defender market
- Ember – multi-modal side-channel attestor for AI workload identity
- Evidence-Locker – long-term storage for trace corpora