cyborg-hunter 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +145 -0
- package/bin/cyborg-hunter.js +20 -0
- package/dist/cyborg-hunter.esm.js +1534 -0
- package/dist/cyborg-hunter.min.js +6 -0
- package/dist/jspsych-cyborg-hunter.js +1 -0
- package/package.json +56 -0
- package/src/cli/analyzers/edge-exit.js +56 -0
- package/src/cli/analyzers/summary.js +99 -0
- package/src/cli/analyzers/triage.js +104 -0
- package/src/cli/config.js +99 -0
- package/src/cli/ingest.js +343 -0
- package/src/cli/init.js +31 -0
- package/src/cli/renderers/event-log.js +66 -0
- package/src/cli/renderers/extensions.js +43 -0
- package/src/cli/renderers/html-index.js +329 -0
- package/src/cli/renderers/summary-csv.js +70 -0
- package/src/cli/renderers/tab-timeline.js +149 -0
- package/src/cli/renderers/trajectories.js +607 -0
- package/src/cli/renderers/triage-md.js +29 -0
- package/src/cli/renderers/typing-profile.js +200 -0
- package/src/cli/report.js +100 -0
- package/src/core/index.js +13 -0
- package/src/core/monitor.js +404 -0
- package/src/core/scoring.js +153 -0
- package/src/core/signals/browser.js +303 -0
- package/src/core/signals/clipboard.js +91 -0
- package/src/core/signals/dom-protection.js +285 -0
- package/src/core/signals/focus.js +101 -0
- package/src/core/signals/mouse.js +117 -0
- package/src/core/signals/typing.js +110 -0
- package/src/core/state-machine.js +88 -0
- package/src/jspsych/extension.js +199 -0
- package/src/shared/constants.js +162 -0
- package/src/shared/schema.js +56 -0
- package/src/shared/validation.js +73 -0
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# cyborg-hunter
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
### Example: 100-participant Prolific pilot
|
|
6
|
+
|
|
7
|
+
Top of the triage list (excerpt — full ranked CSV in the report):
|
|
8
|
+
|
|
9
|
+
| Rank | Participant | Score | Hard | Reason |
|
|
10
|
+
|------|-------------|-------|------|--------|
|
|
11
|
+
| 1 | 67c1d6d98c8787be36609212 | 4078 | no | 1 tab-away 3–10s; 2 flickers <3s; fast typing on 9 trials; **1015 synthetic insertions** |
|
|
12
|
+
| 2 | 69bc21dd22f17b2337957511 | 179 | **YES** | 21 paste events; 3 copy events; 9 tab-aways ≥10s; 3 sidebar events; 21 layout shifts |
|
|
13
|
+
| 3 | 69b9f3184e0896ef41b99aa8 | 152 | no | 38 synthetic insertions |
|
|
14
|
+
| 4 | 5d350282cec7150015d16494 | 131 | **YES** | 5 paste events; 10 copy events; 22 tab-aways ≥10s; 5 tab-aways 3–10s; 15 flickers <3s |
|
|
15
|
+
| 5 | 696a9773a84c5f9d4a930874 | 128 | **YES** | 15 paste events; 23 tab-aways ≥10s; 11 tab-aways 3–10s; 9 flickers <3s |
|
|
16
|
+
| 6 | 69b594dccc64df571f092147 | 123 | **YES** | 12 paste events; 16 tab-aways ≥10s; 4 tab-aways 3–10s |
|
|
17
|
+
| 7 | 69cd5149cf6fc07126897938 | 120 | **YES** | 15 paste events; 1 copy events; 19 tab-aways ≥10s; 16 flickers <3s |
|
|
18
|
+
| 8 | 6314bc49055e150c92f4736e | 112 | **YES** | 12 paste events; 14 tab-aways ≥10s; 46 tab-aways 3–10s; 3 flickers <3s |
|
|
19
|
+
|
|
20
|
+
Of 100 participants, 6 hard-flagged (count thresholds crossed) and 12 soft-flagged (weighted score over threshold). Per-participant tab-away timeline below — this is rank 4, with 42 tab-away events across 15 trials. Reviewers can scan the timeline visually instead of staring at a 30-column CSV:
|
|
21
|
+
|
|
22
|
+

|
|
23
|
+
|
|
24
|
+
Two pieces:
|
|
25
|
+
|
|
26
|
+
- A small JS library you load in your experiment that records signals as participants work.
|
|
27
|
+
- A CLI that reads the resulting data files and renders an HTML report.
|
|
28
|
+
|
|
29
|
+
## Install
|
|
30
|
+
|
|
31
|
+
For analysis (CLI), once published to npm:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install -g cyborg-hunter # not yet published — clone the repo for now
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For the experiment page (library), copy `dist/cyborg-hunter.min.js` and `dist/jspsych-cyborg-hunter.js` from this repo into your project. The library is currently distributed as files, not via a CDN.
|
|
38
|
+
|
|
39
|
+
## Plug into a jsPsych experiment
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
const jsPsych = initJsPsych({
|
|
43
|
+
extensions: [
|
|
44
|
+
{ type: jsPsychCyborgHunter, params: { participantId: subject.id, preset: 'standard' } }
|
|
45
|
+
],
|
|
46
|
+
on_finish: function () {
|
|
47
|
+
// REQUIRED: jsPsych 7 has no on_finish_experiment extension hook, so the
|
|
48
|
+
// session-level signals must be flushed manually before saving data.
|
|
49
|
+
jsPsych.extensions['cyborg-hunter'].finalize();
|
|
50
|
+
jsPsych.data.get().localSave('csv', 'data.csv');
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Opt every trial in to monitoring (single forEach beats editing N trials):
|
|
55
|
+
timeline.forEach(t => {
|
|
56
|
+
t.extensions = (t.extensions || []).concat([{ type: jsPsychCyborgHunter }]);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
jsPsych.run(timeline);
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Full setup walk-through, including standalone (non-jsPsych) usage and per-trial parameters: [`docs/using-cyborg-hunter.md`](docs/using-cyborg-hunter.md).
|
|
63
|
+
|
|
64
|
+
## Generate a report
|
|
65
|
+
|
|
66
|
+
After data collection, in the directory holding your data files:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
cyborg-hunter init # writes cyborg-hunter.config.json
|
|
70
|
+
# edit config: dataDir, filePattern, participantIdField
|
|
71
|
+
cyborg-hunter report # writes ./cyborg-hunter-report/
|
|
72
|
+
open cyborg-hunter-report/index.html
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The report contains:
|
|
76
|
+
|
|
77
|
+
- `summary.csv` — one row per participant, every signal as a column
|
|
78
|
+
- `triage.md` — ranked list with one-line "why flagged" per participant
|
|
79
|
+
- `event-log.csv` — chronological copy/paste/drop/tab-away events
|
|
80
|
+
- `images/` — per-participant mouse trajectories, tab timelines, typing-speed profiles
|
|
81
|
+
- `index.html` — landing page that ties it together
|
|
82
|
+
|
|
83
|
+
## What it detects
|
|
84
|
+
|
|
85
|
+
| Signal | How | Class |
|
|
86
|
+
|---|---|---|
|
|
87
|
+
| Paste | Clipboard `paste` events | Hard (count threshold) |
|
|
88
|
+
| Drag-and-drop | `drop` events on inputs | Hard (count threshold) |
|
|
89
|
+
| Copy | Clipboard `copy` events | Soft (weighted) |
|
|
90
|
+
| Tab-away | `visibilitychange` + `blur`/`focus` | Soft (weighted) |
|
|
91
|
+
| Browser sidebar | `innerWidth` delta + layout compression | Soft (weighted) |
|
|
92
|
+
| Suspicious typing speed | chars/sec exceeding the preset's threshold | Soft (weighted) |
|
|
93
|
+
| Synthetic insertion | text appearing without keystrokes | Soft (weighted) |
|
|
94
|
+
| Foreign input | typing landing outside the experiment container | Soft (weighted) |
|
|
95
|
+
| Idle gaps | input inactivity (potential context-switch out) | Soft (weighted) |
|
|
96
|
+
| AI-extension content scripts | DOM scan for known extension selectors | Soft (weighted) |
|
|
97
|
+
| Mouse trajectories | 20Hz polling + path-efficiency / direction-change metrics | Diagnostic |
|
|
98
|
+
| Window/screen geometry | polled + resize-event capture, with zoom inference | Diagnostic |
|
|
99
|
+
|
|
100
|
+
Three presets — `permissive` (pilot), `standard` (default), `strict` (high-stakes). Per-signal thresholds documented in [`docs/signals-reference.md`](docs/signals-reference.md).
|
|
101
|
+
|
|
102
|
+
## What it doesn't detect
|
|
103
|
+
|
|
104
|
+
- **Native browser AI sidebars.** Chrome's built-in Gemini panel and similar are not browser extensions and leave no extension content scripts. The `innerWidth_delta` heuristic still catches them as a generic sidebar event, but the named-extension column will be empty.
|
|
105
|
+
- **Screen-share / second device.** A participant reading questions on screen 1 and querying ChatGPT on a phone produces no detectable trace from the browser.
|
|
106
|
+
- **AI text edited and retyped.** A determined participant who copies AI output, retypes it character-by-character, and never tab-switches will look clean. The mouse-trajectory and typing-rhythm signals make this much harder than it sounds, but the tool isn't a polygraph.
|
|
107
|
+
|
|
108
|
+
These are stated up front so reviewers know what flagging really represents.
|
|
109
|
+
|
|
110
|
+
## How it works
|
|
111
|
+
|
|
112
|
+
The library hooks browser events that are universally available (Clipboard API, Visibility API, MouseEvent, Performance API, MutationObserver) and tracks two scopes in parallel:
|
|
113
|
+
|
|
114
|
+
- **Session-scoped monitors** start at experiment load and run continuously: tab-away, sidebar/extension detection, layout shifts, window position polling.
|
|
115
|
+
- **Trial-scoped trackers** bracket each trial: mouse path, typing speed, paste/copy/drop within the trial's response window, idle gaps.
|
|
116
|
+
|
|
117
|
+
At the end of each trial, an "integrity" object is attached to that trial's data. At the end of the experiment, session-level rollups are attached to the last trial. The CLI ingests both standard jsPsych CSV output and project-specific JSON formats (`docs/configuration.md` describes the config-file fields).
|
|
118
|
+
|
|
119
|
+
## Documentation
|
|
120
|
+
|
|
121
|
+
- [`docs/using-cyborg-hunter.md`](docs/using-cyborg-hunter.md) — full integration guide (jsPsych and standalone)
|
|
122
|
+
- [`docs/signals-reference.md`](docs/signals-reference.md) — every signal with thresholds per preset
|
|
123
|
+
- [`docs/configuration.md`](docs/configuration.md) — config file fields and CLI flags
|
|
124
|
+
- [`docs/cli-reference.md`](docs/cli-reference.md) — commands and output structure
|
|
125
|
+
- [`docs/plans/2026-04-03-integrity-library-and-calibration-design.md`](docs/plans/2026-04-03-integrity-library-and-calibration-design.md) — design rationale (two-tier scoring, signal taxonomy, preset choice)
|
|
126
|
+
|
|
127
|
+
## Citation
|
|
128
|
+
|
|
129
|
+
If you use cyborg-hunter in academic work, please cite:
|
|
130
|
+
|
|
131
|
+
```bibtex
|
|
132
|
+
@software{konuk2026cyborghunter,
|
|
133
|
+
author = {Konuk, Can},
|
|
134
|
+
title = {cyborg-hunter: integrity monitoring for online behavioral experiments},
|
|
135
|
+
year = {2026},
|
|
136
|
+
url = {https://github.com/konukcan/cyborg-hunter},
|
|
137
|
+
version = {0.3.0}
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
GitHub also renders a "Cite this repository" button (sidebar of the repo page) from the CITATION.cff file in the root.
|
|
142
|
+
|
|
143
|
+
## License
|
|
144
|
+
|
|
145
|
+
MIT
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI entry point for Cyborg Hunter.
|
|
3
|
+
// Usage:
|
|
4
|
+
// npx cyborg-hunter init # generate starter config
|
|
5
|
+
// npx cyborg-hunter report [--config path] ... # generate report
|
|
6
|
+
|
|
7
|
+
import { run } from '../src/cli/report.js';
|
|
8
|
+
import { runInit } from '../src/cli/init.js';
|
|
9
|
+
|
|
10
|
+
const args = process.argv.slice(2);
|
|
11
|
+
const subcommand = args[0];
|
|
12
|
+
|
|
13
|
+
if (subcommand === 'init') {
|
|
14
|
+
runInit().catch(err => { console.error('Error:', err.message); process.exit(1); });
|
|
15
|
+
} else if (subcommand === 'report' || !subcommand) {
|
|
16
|
+
run(args).catch(err => { console.error('Error:', err.message); process.exit(1); });
|
|
17
|
+
} else {
|
|
18
|
+
console.error(`Unknown command: ${subcommand}. Use "report" or "init".`);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|