cyborg-hunter
v0.5.0
Published
Detect LLM-assisted 'cyborg' behavior in online behavioral experiments
Maintainers
Readme
cyborg-hunter
Detects AI-tool use during browser-based behavioral experiments. Captures paste, copy, drag, tab-away, mouse trajectories, browser sidebar openings, and other signals that compromise data quality on Prolific / MTurk / classroom studies. Produces a triage report ranking participants by suspiciousness.
Optional companion deterrence modules (extension-guard-friction.js, extension-guard-honeypot.js) ship in the same package: friction enforces fullscreen + blocks sidebars + scrambles content during violations + asks cooperative LLMs to refuse; honeypot exposes both hidden and visible bait fields that AI agents fill while human participants don't see them.
Example: 100-participant Prolific pilot
| Rank | Participant | Score | Hard | Reason | |------|-------------|-------|------|--------| | 1 | 67c1d6d98c8787be36609212 | 4078 | no | 1 tab-away 3–10s; 2 flickers <3s; fast typing on 9 trials; 1015 synthetic insertions | | 2 | 69bc21dd22f17b2337957511 | 179 | YES | 21 paste events; 3 copy events; 9 tab-aways ≥10s; 3 sidebar events; 21 layout shifts | | 3 | 69b9f3184e0896ef41b99aa8 | 152 | no | 38 synthetic insertions | | 4 | 5d350282cec7150015d16494 | 131 | YES | 5 paste events; 10 copy events; 22 tab-aways ≥10s; 5 tab-aways 3–10s; 15 flickers <3s |
6 hard-flagged + 12 soft-flagged out of 100. Per-participant tab-away timeline (rank 4, 42 events across 15 trials):

Repo layout
src/core/— signal-collection library (the monitor)src/jspsych/— jsPsych extension adapters (one per concern)src/cli/+bin/— CLI that turns saved data into the triage reporttests/,docs/,studies/— tests, package docs, full-study fixtures
Install
CLI (analysis):
npm install -g cyborg-hunterBrowser (experiment page) — load via unpkg or copy dist/*.js into your project:
<!-- Signal collection -->
<script src="https://unpkg.com/cyborg-hunter/dist/cyborg-hunter.min.js"></script>
<script src="https://unpkg.com/cyborg-hunter/dist/extension-cyborg-hunter.js"></script>
<!-- Optional: deterrence + bait detection -->
<script src="https://unpkg.com/cyborg-hunter/dist/extension-guard-friction.js"></script>
<script src="https://unpkg.com/cyborg-hunter/dist/extension-guard-honeypot.js"></script>For production studies, pin a version: https://unpkg.com/[email protected]/dist/....
Plug into a jsPsych experiment
const jsPsych = initJsPsych({
extensions: [
{ type: jsPsychCyborgHunter, params: { participantId: participant_id, preset: 'standard' } },
{ type: jsPsychGuardFriction }, // optional
{ type: jsPsychGuardHoneypot } // optional
],
on_finish: function () {
jsPsych.extensions['guard-friction'].finalize(); // stop friction first
jsPsych.extensions['guard-honeypot'].finalize(); // then attach data
jsPsych.extensions['cyborg-hunter'].finalize(); // then save
jsPsych.data.get().localSave('csv', 'data.csv');
}
});
timeline.push(jsPsychGuardFriction.entryTrial()); // user-gesture fullscreen entry
timeline.forEach(t => t.extensions = (t.extensions || []).concat([
{ type: jsPsychCyborgHunter },
{ type: jsPsychGuardFriction },
{ type: jsPsychGuardHoneypot }
]));
jsPsych.run(timeline);participantId must exist before initJsPsych. Full walk-through (per-trial params, standalone non-jsPsych use, opt-in/exclude modes): docs/using-cyborg-hunter.md.
Generate a report
cd <your-data-dir>
cyborg-hunter init # writes cyborg-hunter.config.json
# edit config: dataDir, filePattern, participantIdField
cyborg-hunter report # writes ./cyborg-hunter-report/
open cyborg-hunter-report/index.htmlOutput: summary.csv (per-participant columns), triage.md (ranked list), event-log.csv (chronological events), images/ (per-participant mouse paths, tab timelines, typing profiles), index.html (landing page).
What it detects
| Signal | How | Class |
|---|---|---|
| Paste | Clipboard paste events | Hard (count threshold) |
| Drag-and-drop | drop events on inputs | Hard (count threshold) |
| Copy | Clipboard copy events | Soft (weighted) |
| Tab-away | visibilitychange + blur/focus | Soft (weighted) |
| Browser sidebar | innerWidth delta + layout compression | Soft (weighted) |
| Suspicious typing speed | chars/sec exceeding preset threshold | Soft (weighted) |
| Synthetic insertion | text appearing without keystrokes | Soft (weighted) |
| Foreign input | typing landing outside experiment container | Soft (weighted) |
| Idle gaps | input inactivity | Soft (weighted) |
| AI-extension content scripts | DOM scan for known extension selectors | Soft (weighted) |
| Mouse trajectories | 20Hz polling + path-efficiency metrics | Diagnostic |
| Window/screen geometry | polled + resize-event capture, with zoom inference | Diagnostic |
Three presets: permissive / standard (default) / strict. Per-signal thresholds: docs/signals-reference.md.
The optional guard extensions add: fullscreen / sidebar / focus enforcement (with content-scrambling overlay on violation), AI refusal notices in the DOM, and honeypot fields (hidden + visible bait) that catch sidebar-LLMs and agentic browsers (Browser Use, Operator, Computer Use) — captured as ai_use / ai_report columns.
What it doesn't detect
- Native browser AI sidebars (Chrome Gemini, Edge Copilot) — they leave no extension content scripts. The
innerWidth_deltaheuristic still catches them as generic sidebar events, but the named-extension column stays empty. - Screen-share / second device — an LLM on a phone reading the screen leaves no in-browser trace.
- AI text edited and retyped — a determined participant who retypes character-by-character without tab-switching looks clean. Mouse-trajectory and typing-rhythm signals raise the bar but it isn't a polygraph.
Documentation
- docs/using-cyborg-hunter.md — full integration guide
- docs/signals-reference.md — every signal with thresholds per preset
- docs/configuration.md — config file fields and CLI flags
- docs/cli-reference.md — commands and output structure
- docs/design-decisions.md — architecture rationale
Citation
@software{konuk2026cyborghunter,
author = {Konuk, Can},
title = {cyborg-hunter: integrity monitoring for online behavioral experiments},
year = {2026},
url = {https://github.com/konukcan/cyborg-hunter},
version = {0.4.0}
}A "Cite this repository" button is rendered from CITATION.cff.
License
MIT
