A command-line tool for generating labeled bot-submission data to train and evaluate form-filling bot detectors. Form Feeder uses an LLM to autonomously fill out a form you control, while recording rich telemetry about how the form was filled — producing a clean, labeled "bot" class you can pair with real human submissions to build a detection dataset.
Built on Playwright and Gemini 2.5 Flash.
Automated form submissions are a real problem — survey fraud, fake sign-ups, poll manipulation. Detecting them is hard because a modern LLM produces plausible, in-range, varied answers; the content alone is a weak signal. The strong signal is behavioral and environmental: how the form was filled, not what was entered.
Form Feeder exists to generate the bot half of a detection dataset: realistic automated submissions, fully labeled, captured alongside the telemetry a detector would actually learn from. You pair it with consented human submissions to the same form, train a classifier, and measure how well it separates the two.
- Run the bot only against forms you own or are explicitly authorized to test.
- Collect human submissions with consent, and store telemetry per your privacy obligations.
- Every record this tool produces is labeled
bot— it is training data, never meant to be mixed into a live response set or passed off as human.
Using automation to submit responses to forms you don't control corrupts other people's data and violates most platforms' terms of service. This project is for building defenses against that, not for doing it.
The most discriminative bot-detection features — mouse movement, keystroke cadence, per-field dwell time, scroll behavior — require capturing telemetry from the client filling the form. You can't inject that capture into a Google Form you don't host. So there are two collection modes:
googlemode — fills a real Google Form. You only get observable signals: submission timing, inter-submission intervals, and the answer content itself. Thinner, but zero setup.hostedmode (recommended) — point the tool at a small self-hosted form that mirrors your target's structure and ships with the telemetry-capture script bundled in this repo. You control both the bot and the instrumentation, so you get the full behavioral feature set for both classes. This is the mode that produces a serious dataset.
Use the same form for both your bot and human classes so the form itself doesn't become a confound.
In hosted mode, each submission record includes:
- Behavioral — total time-on-page, per-field dwell and inter-keystroke timing, mouse-movement path features, scroll pattern, field focus/blur order, paste-vs-type events, and corrections (backspaces, re-edits). Across a run, inter-submission intervals.
- Environmental —
navigator.webdriver, plugin/canvas/WebGL fingerprint, timezone-vs-IP consistency, user-agent and automation-flag anomalies. - Content — answer-length distribution, lexical diversity, templated-phrasing markers, cross-response consistency. Included so you can measure how much weaker content-only detection is.
In google mode, only submission timing and content features are available.
- Python 3.10+
- A Gemini API key (free tier works)
- A form you own or are authorized to test — a Google Form (
googlemode) or the bundled self-hosted form (hostedmode)
pip install git+https://github.com/RextonRZ/formfeeder.git
python -m playwright install chromiumpython -m formfeeder --url "<your-form-url>" --mode <google|hosted> --count <number> --key "<gemini-api-key>"Example (hosted mode, full telemetry):
python -m formfeeder \
--url "http://localhost:8000/form" \
--mode hosted \
--count 50 \
--key "AIzaSy..."| Flag | Required | Description |
|---|---|---|
--url |
Yes | URL of a form you own or are authorized to test |
--mode |
No | google (observable signals only) or hosted (full telemetry). Default: google |
--count |
No | Number of bot submissions to generate (default: 5) |
--key |
No* | Gemini API key — can also be set via GEMINI_API_KEY env var |
--bot-type |
No | Label tag for this run, e.g. llm-gemini, so your dataset can distinguish bot families (default: llm-gemini) |
--hint |
No | Extra context to vary the synthetic profiles, e.g. "Respondents are students aged 18–25" |
--debug |
No | Show the browser window while filling so you can watch it work |
*Required if GEMINI_API_KEY is not set in your environment.
Windows (PowerShell):
$env:GEMINI_API_KEY = "AIzaSy..."
python -m formfeeder --url "..." --mode hosted --count 50macOS / Linux:
export GEMINI_API_KEY=AIzaSy...
python -m formfeeder --url "..." --mode hosted --count 50Each submission is appended to runs.jsonl in your working directory. Every record carries an explicit label and the telemetry available for the mode:
{"run_index": 1, "label": "bot", "bot_type": "llm-gemini", "telemetry": {"time_on_page_ms": 4120, "keystroke_count": 0, "mouse_path_points": 0, "navigator_webdriver": true, ...}, "profile": {"age": 22, "occupation": "student"}, "timestamp": 1747123456}
{"run_index": 2, "label": "bot", "bot_type": "llm-gemini", "telemetry": {"time_on_page_ms": 3890, "keystroke_count": 0, "mouse_path_points": 0, "navigator_webdriver": true, ...}, "profile": {"age": 35, "occupation": "engineer"}, "timestamp": 1747123512}Capture your consented human submissions in the same schema with "label": "human", then extract a per-response feature vector for your classifier.
- Hold out whole forms, not rows, in your test split — so you measure generalization to unseen forms rather than memorization of one.
- Label by bot family (
--bot-type) — collect runs from this LLM tool and simpler baselines (naive autofill, record-replay macros) so your detector generalizes beyond one bot. - Watch class balance between bot and human, and across forms.
- Same form for both classes, as above, to avoid the form becoming the thing your model actually learns.
- Google mode is signal-poor — without client instrumentation you only get timing and content. For a real detector, use
hostedmode. - Conditional branching — if a form routes to different sections by answer, some profiles may take an unexpected path. The tool detects this, skips the run, and continues.
- Gemini free tier rate limit — ~15 requests/minute on the free tier; large runs will pace themselves.
pip install --upgrade git+https://github.com/RextonRZ/formfeeder.gitFree API keys are available at aistudio.google.com/apikey.