Skip to main content

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

Project description

๐Ÿ›ก๏ธ Survey Shield

PyPI version Python versions Tests License: MIT

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 PyPI name is surveyshield-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 + writes .env stub
echo "OPENAI_API_KEY=sk-..." >> .env    # or GOOGLE_API_KEY
surveyshield serve --reload             # โ†’ 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
โ”œโ”€โ”€ 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.4.tar.gz (860.2 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.4-py3-none-any.whl (866.4 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: surveyshield_py-0.1.4.tar.gz
  • Upload date:
  • Size: 860.2 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.4.tar.gz
Algorithm Hash digest
SHA256 258a55156b2e8a66af013e02e594c6aea9998db8a48de3f3ea63282e068f1f4b
MD5 bb008ef021b5fc91f3d763dff842f3d3
BLAKE2b-256 e9d93f1b220e3fc054f717818e96419ca41f233c123760c39adc219eec0b7f6a

See more details on using hashes here.

Provenance

The following attestation bundles were made for surveyshield_py-0.1.4.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.4-py3-none-any.whl.

File metadata

  • Download URL: surveyshield_py-0.1.4-py3-none-any.whl
  • Upload date:
  • Size: 866.4 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.4-py3-none-any.whl
Algorithm Hash digest
SHA256 bca5d8a7547dc385f0be3186fb85c514cbc5649ae9262179a6b89fdeb62e914f
MD5 da42517f1d380eff880e1199a3435dfa
BLAKE2b-256 8b8dd416a48946a3d53d4cb05c3a86b626971fd971d487e6ad6371fa80a98159

See more details on using hashes here.

Provenance

The following attestation bundles were made for surveyshield_py-0.1.4-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