Case Study
threadr
OSINT tool for security professionals. Maps relationships between digital identities to help defenders understand their attack surface.
ReactTypeScriptHonoBullMQNeo4jRedisSQLiteTailwindDockerVitest
00 / Live Demo
What a scan looks like
One email address. 17 plugins. 47 nodes discovered in under 30 seconds.
threadr
0 nodes0 edges
01 / Engineering
What I had to think about
The plugin system is plumbing. These are the parts where the maths matters.
02 / The Code
From the source
Three functions from the repo. The maths is the implementation.
Dempster’s Combination Rule
Fuses two mass functions over Θ = {SAME, DIFFERENT}. Conflict mass K normalises the result. Total conflict → pure uncertainty.
function combine(m1: MassFunction, m2: MassFunction): MassFunction {
// Non-empty intersections
const sameSame = m1.same * m2.same
const sameUncertain = m1.same * m2.uncertain
const uncertainSame = m1.uncertain * m2.same
const diffDiff = m1.different * m2.different
const diffUncertain = m1.different * m2.uncertain
const uncertainDiff = m1.uncertain * m2.different
const uncertainUncertain = m1.uncertain * m2.uncertain
// Conflict: {SAME} ∩ {DIFFERENT} = ∅
const K = m1.same * m2.different + m1.different * m2.same
const norm = 1 - K
if (norm <= 0) return { same: 0, different: 0, uncertain: 1 }
return {
same: (sameSame + sameUncertain + uncertainSame) / norm,
different: (diffDiff + diffUncertain + uncertainDiff) / norm,
uncertain: uncertainUncertain / norm,
}
}Lévy Stable Sampling
Chambers-Mallows-Stuck (1976) algorithm. α=1.5 gives infinite variance — defeats Kolmogorov-Smirnov timing correlation that catches Poisson.
function levySample(scale: number, min = 100, max = 30000): number {
const alpha = 1.5 // heavy tail without Cauchy extremes
const beta = 1.0 // right-skewed (positive delays)
// Chambers-Mallows-Stuck algorithm
const U = (Math.random() - 0.5) * Math.PI
const W = -Math.log(Math.random() || 1e-10)
const phi0 = Math.atan(beta * Math.tan(Math.PI * alpha / 2)) / alpha
const factor = Math.pow(
Math.cos(U - alpha * phi0) / W,
(1 - alpha) / alpha
) * Math.sin(alpha * (U - phi0))
/ Math.pow(Math.cos(U), 1 / alpha)
return Math.min(Math.abs(factor) * scale + min, max)
}k-Anonymity Decoy Guarantee
For every real target, k decoys of the same type are queried. Observer identification probability: 1/(k+1). Cryptographic random selection from curated pools.
function generateDecoys(real: string, type: SeedType, k: number): string[] {
const pool = POOLS[type].filter(t => t !== real.toLowerCase())
const count = Math.min(k, pool.length)
const decoys: string[] = []
const used = new Set<number>()
const randomBytes = new Uint32Array(count)
crypto.getRandomValues(randomBytes)
for (let i = 0; i < count; i++) {
let idx = randomBytes[i] % pool.length
while (used.has(idx)) idx = (idx + 1) % pool.length
used.add(idx)
decoys.push(pool[idx])
}
return decoys
}
// k=3 → 25% identification probability
// k=5 → 16.7%
function identificationProbability(k: number): number {
return 1 / (k + 1)
}0
plugins
0
tests
0
analytics engines
0
anonymity layers