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·ValidateResultverify_token/sign_token·verify_vdf/hash_to_prime/miller_rabinscore_behavior·check_honeypots/generate_honeypots·is_valid_instr_fingerprintRateTracker/compute_t·MemoryNonceStore/NonceStorePineGuard/GuardRule/GuardDecisionissue_captcha/verify_captcha/render_challenge·CaptchaVerifyOptions·check_keystrokes/KeystrokeConfigScoringConfig·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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b751ee22c23db6db7e6172f7827f93504063a0df532fff33096d725100f0826
|
|
| MD5 |
3ff95ddc00f9f32173e416166f14efef
|
|
| BLAKE2b-256 |
1dde1e570a4d302d4280e6b28ba46779831f9d801275b8cfae5562cefe0a057c
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
de69c5c8737d7ca7ce756ada81f7b2bf1f5d1bd4fec185fde1ceb2434b4ee888
|
|
| MD5 |
e0dcaddb9ee66ac25819c6e772e174ac
|
|
| BLAKE2b-256 |
c8fd2530a05fab2923cf6012c0132f8265b44537c7877f1565dba3d477024c54
|