Skip to main content

Stateless anti-bot form shield — server-side validation (Wesolowski VDF + HMAC tokens + behavior + honeypots) plus a last-resort captcha. Standard library + Pillow.

Project description

pineward (Python)

Stateless anti-bot form shield — server-side validation in pure Python (standard library + Pillow for the captcha renderer).

This is the Python reference implementation of Pineward's server validation: it verifies the HMAC tokens, Wesolowski VDF proofs, honeypots, behavior metrics, and instrumentation fingerprints produced by the JavaScript client. It is byte-compatible with @pineward/server — verified against the shared golden vectors in tests/vectors/v0-vectors.json (generated by the JS core).

Install

pip install pineward

Requires Python 3.10+. One runtime dependency — Pillow (renders the last-resort captcha); everything else is standard library. So pip install pineward ships the captcha out of the box.

Usage

from pineward import validate, ValidateOptions, MemoryNonceStore

opts = ValidateOptions(
    secret=b"my-secret-minimum-32-characters-long!!",
    expected_origin="https://yoursite.com",
    nonce_store=MemoryNonceStore(),   # use Redis/DB in serverless/multi-instance
)

# `submission` is the parsed form body: the __pw_* fields + formData.
submission = {
    "token": form["__pw_token"],
    "vdfProof": json.loads(form["__pw_vdf"]),
    "behavior": json.loads(form["__pw_behavior"]),
    "instrFingerprint": form["__pw_instr"],
    "formData": form,
}

result = validate(submission, opts)
if not result.ok:
    abort(403, f"pineward: {result.reason}")
# result.payload is the verified token payload

result.reason is one of: bad-token, expired, wrong-origin, replay, honeypot-tripped, bad-vdf, bad-instrumentation, bad-behavior.

For lightweight integrations, set skip_vdf=True and/or skip_instrumentation=True.

Last-resort captcha

Every layer above is invisible. The one visible layer is a server-rendered, adversarial-text captcha — the escalation target when a request looks automated. It verifies an answer (not forgeable client-reported motion): the challenge is rendered server-side with secret randomness, so the answer lives in the pixels and can't be precomputed from this open-source code — an agent has to actually read an image tuned to break general OCR/VLMs while staying human-legible. The signed token carries only an HMAC of the answer, never the text.

from pineward import issue_captcha, verify_captcha, CaptchaVerifyOptions, MemoryNonceStore

# escalate (e.g. when validate() soft-fails a forgeable signal) instead of hard-blocking:
ch = issue_captcha(secret=SECRET, form_id="contact", origin="https://yoursite.com")
#   -> ch.token (opaque, signed) + ch.png (bytes) / ch.data_url (for <img src>)

res = verify_captcha(
    {"captchaToken": token, "answer": user_typed},
    CaptchaVerifyOptions(secret=SECRET, expected_origin="https://yoursite.com",
                         nonce_store=MemoryNonceStore()),
)
# res.reason: bad-token | expired | wrong-origin | wrong-kind | replay | wrong-answer

Optionally also require the answer to have been typed (a forgeable, naive-bot filter — not a guarantee): pass keystrokes=KeystrokeConfig() to CaptchaVerifyOptions and include typing ({"t": [per-key ms], "paste": bool}) in the submission; a direct POST or pasted answer then fails with bad-keystrokes.

Rendering uses Pillow, which ships with pineward — nothing extra to install.

Bound brute-force with PineGuard (per-IP rate-limit) — see examples/captcha for the full escalation flow.

Public API

  • validate(submission, opts) · ValidateOptions · ValidateResult
  • verify_token / sign_token · verify_vdf / hash_to_prime / miller_rabin
  • score_behavior · check_honeypots / generate_honeypots · is_valid_instr_fingerprint
  • RateTracker / compute_t · MemoryNonceStore / NonceStore
  • PineGuard / GuardRule / GuardDecision
  • issue_captcha / verify_captcha / render_challenge · CaptchaVerifyOptions · check_keystrokes / KeystrokeConfig
  • ScoringConfig · DifficultyConfig · RSA_2048_MODULUS · FIELD_*

Develop

pip install -e '.[dev]'
pytest

The wire format is specified in the JS repo's spec/v0.md; the vectors here are the cross-language contract. If the token/VDF encoding changes, regenerate vectors on the JS side and copy them here.

License

MIT

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

pineward-0.1.0.tar.gz (25.7 kB view details)

Uploaded Source

Built Distribution

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

pineward-0.1.0-py3-none-any.whl (24.0 kB view details)

Uploaded Python 3

File details

Details for the file pineward-0.1.0.tar.gz.

File metadata

  • Download URL: pineward-0.1.0.tar.gz
  • Upload date:
  • Size: 25.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for pineward-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2b751ee22c23db6db7e6172f7827f93504063a0df532fff33096d725100f0826
MD5 3ff95ddc00f9f32173e416166f14efef
BLAKE2b-256 1dde1e570a4d302d4280e6b28ba46779831f9d801275b8cfae5562cefe0a057c

See more details on using hashes here.

File details

Details for the file pineward-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: pineward-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 24.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for pineward-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 de69c5c8737d7ca7ce756ada81f7b2bf1f5d1bd4fec185fde1ceb2434b4ee888
MD5 e0dcaddb9ee66ac25819c6e772e174ac
BLAKE2b-256 c8fd2530a05fab2923cf6012c0132f8265b44537c7877f1565dba3d477024c54

See more details on using hashes here.

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