Skip to main content

Static review of online survey instruments for resistance to AI/bot respondents

Project description

๐Ÿ›ก๏ธ Survey Shield

Static review of online survey instruments for resistance to AI/bot respondents โ€” plus an optional live runtime that drives a real browser through your survey.

What is Survey Shield?

Survey Shield gives researchers feedback on whether their survey instrument is hardened against AI respondents. Two paths:

  1. Instrument Review (primary, static, no browser) โ€” point it at a Qualtrics .qsf export. Multi-agent LLM reviewers fan out across bot-resistance dimensions (attention checks, identity questions, visual-perceptual traps), produce a peer-review-style verdict with verbatim-grounded findings, and render a self-contained HTML report with a copy-paste Methods statement and APA/BibTeX citation.
  2. Take Survey (live runtime) (optional [live] extra) โ€” drives a real browser (browser-use) through a live Qualtrics URL, reports detected mechanisms after the fact. Costs ~5โ€“10 minutes and real LLM credit per run, so the hosted demo doesn't expose it; install the [live] extra to run it locally.

Researchers using Survey Shield mostly want Instrument Review. Reach for the live runtime when you need to exercise the survey end-to-end.

Install

pip install surveyshield-py            # static review only โ€” small, no browser
pip install "surveyshield-py[live]"    # adds browser-use + Playwright
playwright install chromium            # only if you installed [live]

The package name on PyPI is surveyshield-py (after openreview-py); the import name is surveyshield.

Set an LLM provider key in your environment (or in a .env file in the working directory โ€” Survey Shield loads it via python-dotenv):

OPENAI_API_KEY=sk-...        # used unless the model name starts with "gemini"
GOOGLE_API_KEY=...           # used for Gemini models

CLI

surveyshield review your_survey.qsf
# โ†’ writes your_survey.report.html next to the input

surveyshield review your_survey.qsf --output report.html --json review.json \
                                    --model gpt-4o-mini

surveyshield take https://qualtrics.com/jfe/form/SV_xxx     # requires [live]
                  --model gemini-3-flash-preview --max-steps 150

surveyshield serve --host 127.0.0.1 --port 8000
# โ†’ boots the FastAPI app + bundled React SPA

surveyshield --help lists every command and flag.

Python API

import asyncio
import surveyshield

review, parsed = asyncio.run(
    surveyshield.review_qsf(
        "your_survey.qsf",
        model="gpt-4o-mini",
        # api_key="sk-...",      # or rely on env vars
        # dimensions=["attention_checks"],   # default = all
    )
)

print(review.overall_score, review.overall_feedback.headline)

with open("report.html", "w") as f:
    f.write(surveyshield.render_html(review, parsed))

The review object is a surveyshield.InstrumentReview Pydantic model. Power users can compose the lower-level seams directly: parse_qsf, run_review, drop_unverified_quotes, consolidate_and_summarize, aggregate. See surveyshield/__init__.py for the public surface.

For live runtime:

import asyncio, surveyshield  # surveyshield-py[live] installed

result = asyncio.run(surveyshield.take_survey(
    "https://qualtrics.com/jfe/form/SV_xxx",
    model="gemini-3-flash-preview",
    max_steps=150,
))
print(result.success_probability, [m.name for m in result.detected_mechanisms])

If the [live] extra isn't installed, surveyshield.take_survey resolves to None and the CLI's take command exits with a clear install hint.

Self-host the hosted UI

git clone https://github.com/kiante-fernandez/survey-shield
cd survey-shield
./setup.sh                                   # creates ../.conda env
echo "OPENAI_API_KEY=sk-..." > backend/.env  # or GOOGLE_API_KEY
cd backend && ./start.sh                     # โ†’ http://localhost:8000

The Take Survey tab is gated on live_take_enabled โ€” GET /api/v1/survey/config flips it to true once a key is detected in the env.

Endpoints

Models

The hosted UI does not expose a model picker โ€” Instrument Review reviewers run on a sensible default (gpt-4o-mini). Self-hosters who want a different model can pass model_name directly to the API or CLI. The backend has no allowlist; any model name langchain-openai's ChatOpenAI or langchain-google-genai's ChatGoogleGenerativeAI accept will be routed by prefix:

  • Names starting with gemini โ†’ Google (requires GOOGLE_API_KEY)
  • Everything else โ†’ OpenAI (requires OPENAI_API_KEY)

API usage (self-host)

Instrument Review (primary)

# Submit a QSF for review
curl -F "file=@your_survey.qsf" \
     http://localhost:8000/api/v1/instrument/review
# โ†’ {"review_id": "<uuid>", "status": "queued", ...}

# Poll
curl http://localhost:8000/api/v1/instrument/status/<uuid>
# queued โ†’ running โ†’ completed (~30โ€“90 s)

# Structured JSON
curl http://localhost:8000/api/v1/instrument/results/<uuid>

# Human-readable HTML report
curl "http://localhost:8000/api/v1/instrument/report/<uuid>"

# Download as a file
curl -OJ "http://localhost:8000/api/v1/instrument/report/<uuid>?download=1"

Live runtime (self-host only)

curl -X POST "http://localhost:8000/api/v1/survey/analyze" \
     -H "Content-Type: application/json" \
     -d '{
       "survey_url": "https://example.com/survey",
       "model_name": "gpt-4o-mini",
       "max_steps": 150,
       "use_vision": true
     }'
# Then poll /api/v1/survey/status/<id> and fetch /api/v1/survey/results/<id>.

What Survey Shield evaluates

Instrument Review dimensions (primary)

The reviewer fans out across plug-in dimensions defined in surveyshield/review/dimensions.py. v1 ships three:

  • attention_checks โ€” explicit IMCs and instructional manipulation checks (Westwood, 2025; PNAS).
  • identity_questions โ€” direct LLM-resistance items (identity probes, reverse-shibboleth questions, impossible-event questions).
  • visual_perceptual_traps โ€” image- and layout-based cognitive traps that exploit vision-language model architectural constraints (Affonso, 2026; JCR).

Adding a new dimension is one entry in dimensions.py plus a Pydantic output schema โ€” the reviewer fan-out, aggregator, and HTML report are all plug-in-driven.

Findings are grounded: every reviewer finding must cite a verbatim excerpt from the source survey. Survey Shield substring-checks each excerpt against the parsed survey and drops anything that can't be located. What reaches the report is grounded in real survey content.

The product is scoped strictly to bot resistance. We do not critique a survey's substantive research design, theoretical framing, or question wording โ€” those remain the researcher's domain.

Live-runtime mechanisms

When you run a live analysis against a real Qualtrics URL, Survey Shield's browser-use Agent navigates the survey end-to-end and inventories detected mechanisms by category โ€” hover traps, invisible text, timing requirements, CAPTCHAs, honeypot fields, attention checks, and behavioural / mouse-tracking signals. Per-mechanism severity weights (1โ€“10) feed into success-probability and difficulty scores. See surveyshield/live/analyzer.py for the taxonomy and surveyshield/live/prompts.py for the agent task.

Development

Project structure

survey-shield/
โ”œโ”€โ”€ surveyshield/                # the importable package
โ”‚   โ”œโ”€โ”€ __init__.py              # public API (review_qsf, render_html, take_survey, โ€ฆ)
โ”‚   โ”œโ”€โ”€ cli.py                   # Typer CLI (review / take / serve)
โ”‚   โ”œโ”€โ”€ models/                  # Pydantic schemas
โ”‚   โ”œโ”€โ”€ review/                  # static review pipeline
โ”‚   โ”‚   โ”œโ”€โ”€ parser.py            #   QSF โ†’ ParsedSurvey
โ”‚   โ”‚   โ”œโ”€โ”€ dimensions.py        #   plug-in dimension registry
โ”‚   โ”‚   โ”œโ”€โ”€ reviewer.py          #   fan-out, verify, consolidate, aggregate
โ”‚   โ”‚   โ”œโ”€โ”€ mechanism_context.py
โ”‚   โ”‚   โ””โ”€โ”€ templates/           #   Jinja2 self-contained HTML report
โ”‚   โ”œโ”€โ”€ live/                    # browser-use runtime ([live] extra)
โ”‚   โ”‚   โ”œโ”€โ”€ analyzer.py          #   SurveyAnalyzer / take_survey
โ”‚   โ”‚   โ”œโ”€โ”€ prompts.py
โ”‚   โ”‚   โ””โ”€โ”€ patches.py
โ”‚   โ””โ”€โ”€ serve/                   # FastAPI app + bundled React SPA
โ”‚       โ”œโ”€โ”€ app.py
โ”‚       โ”œโ”€โ”€ config.py
โ”‚       โ”œโ”€โ”€ api/{survey,instrument}.py
โ”‚       โ””โ”€โ”€ static/              # built React (populated by bin/build.sh)
โ”œโ”€โ”€ frontend/                    # React/TypeScript source (CRA)
โ”œโ”€โ”€ tests/                       # pytest suite + tiny QSF fixture
โ”œโ”€โ”€ backend/start.sh             # convenience wrapper around `surveyshield serve`
โ”œโ”€โ”€ pyproject.toml               # canonical package metadata
โ”œโ”€โ”€ Procfile                     # web: gunicorn surveyshield.serve.app:app
โ”œโ”€โ”€ bin/build.sh                 # React build โ†’ surveyshield/serve/static/
โ””โ”€โ”€ .github/workflows/           # test.yml + release.yml (PyPI trusted publishing)

Local dev

./setup.sh                                   # one-time: conda env at ../.conda
pip install -e ".[dev,live]"                 # editable install + tests + browser-use
pytest -q                                    # ~30 tests, no LLM calls
cd frontend && npx tsc --noEmit && npm run build

Releasing

git tag v0.1.0 && git push --tags
# .github/workflows/release.yml builds the wheel + sdist (with the React SPA
# bundled into surveyshield/serve/static/) and publishes to PyPI via OIDC.

The PyPI project must be configured with this repo + release.yml as a Trusted Publisher before the first push.

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make changes with tests
  4. Submit a pull request

License

MIT License โ€” see LICENSE.

Citation

If you use Survey Shield in published work:

@misc{fernandez2026surveyshield,
  author = {Fernandez, K. and Low, A. and Bogard, J. and Fox, C. R.},
  title  = {Survey Shield: Static review of online survey instruments for resistance to non-human responses},
  year   = {2026},
  note   = {Manuscript in preparation},
}

Every report includes the same citation pre-formatted (APA + BibTeX).

Disclaimer

Survey Shield is intended for research and testing purposes.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

surveyshield_py-0.1.1.tar.gz (858.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

surveyshield_py-0.1.1-py3-none-any.whl (865.0 kB view details)

Uploaded Python 3

File details

Details for the file surveyshield_py-0.1.1.tar.gz.

File metadata

  • Download URL: surveyshield_py-0.1.1.tar.gz
  • Upload date:
  • Size: 858.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for surveyshield_py-0.1.1.tar.gz
Algorithm Hash digest
SHA256 4d01b374ce6c0c943fec07ef56ab930666eb6377604eab2fd7b01034cb1c39ba
MD5 7488a8373d246fd2f29dcbf0cf721065
BLAKE2b-256 e6434788fa002744a8da272a3e3e7684b9b2d3ac71ad976eb4fd7dce34b4b029

See more details on using hashes here.

Provenance

The following attestation bundles were made for surveyshield_py-0.1.1.tar.gz:

Publisher: release.yml on kiante-fernandez/survey-shield

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file surveyshield_py-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: surveyshield_py-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 865.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for surveyshield_py-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 e4e0187fca6394f8258604045e76d1c04cd9750fe321f209a52b9d5df61b29a9
MD5 0a8163235b2b4cec51b95cf15548e784
BLAKE2b-256 1156f8e55343176f3afcdb3869c5641289a66905a7a225dabe5a751876dc1fd5

See more details on using hashes here.

Provenance

The following attestation bundles were made for surveyshield_py-0.1.1-py3-none-any.whl:

Publisher: release.yml on kiante-fernandez/survey-shield

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page