@paramira/cover-traffic-scheduler
v0.1.0
Published
Sends decoy traffic at randomized intervals on a steady cadence, whether or not there's anything real to send — so message timing alone can't reveal real activity to an observer.
Readme
@paramira/cover-traffic-scheduler
Home: this package lives in proto (
packages/cover-traffic-scheduler), the public home of the hardened relay layer (proto ADR-0013). It moved here from the Rubanda apps monorepo on 2026-10-17, as its current code without that repo's history.
Calls send at randomized intervals on a steady cadence — whether or
not there's anything real to send. An observer watching when messages
go out (frame timing, even fully encrypted content) can't tell real
activity from noise if the noise never stops.
import { createCoverTrafficScheduler } from '@paramira/cover-traffic-scheduler'
const scheduler = createCoverTrafficScheduler({
minIntervalMs: 30_000,
maxIntervalMs: 90_000,
send: () => ws.send(JSON.stringify({ type: 'ping' })),
})
scheduler.start()
// ... later
scheduler.stop()send can return a promise — the next send is scheduled only after the
current one resolves, never fired concurrently with it.
Where this came from, and where it'd go
Ported from a censorship-resistant DIDComm messaging spike
(/home/pat/dev/proto) — the scheduler itself had zero
DIDComm/Credo coupling already, just converted from a class to a factory
function for consistency with every other package here.
No paramira app sends cover traffic today. demo/relay-timing.mjs shows
concretely where it would matter: iwacu-rooms/abantu-rooms's WebSocket
connections (see either service's server.js) only ever send a frame
when a real event happens, so an observer watching connection timing
alone — not content, just when frames go out — can tell "this device
just did something" from "this device has been silent for hours." A
relay-side or client-side scheduler sending decoy frames on the same
connection closes exactly that gap, without touching the real event
protocol at all.
node demo/relay-timing.mjsWhat this doesn't address
Frame timing is covered; frame size isn't — a decoy frame that's conspicuously smaller than a real event payload would still leak "this one's probably real." Padding decoy frames to a fixed size (or padding every frame, real or not, to a common size class) is the natural next step if this gets used for real; not done here, since it depends on knowing the real protocol's actual payload sizes, which varies per app.
