Skip to content

feat!: upgrade to ALTCHA v3.2.1 and add a strict-CSP mode - #1

Closed
hleroy wants to merge 1 commit into
mainfrom
claude/django-altcha-v3-strict-csp-ljime8
Closed

feat!: upgrade to ALTCHA v3.2.1 and add a strict-CSP mode#1
hleroy wants to merge 1 commit into
mainfrom
claude/django-altcha-v3-strict-csp-ljime8

Conversation

@hleroy

@hleroy hleroy commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Upgrades the bundled ALTCHA widget from v2.3.0 to v3.2.1, moves the server side from the v1 Proof-of-Work API to the v2 one, and adds an opt-in strict Content-Security-Policy mode.

Widget: v2.3.0 → v3.2.1

Assets are vendored from the v3.2.1 tag and verified byte-identical against jsDelivr (sha256 match on all 8 files):

static/altcha/altcha.min.js            ← dist/main/altcha.min.js
static/altcha/i18n/all.js              ← dist/i18n/all.js
static/altcha/external/altcha.min.js   ← dist/external/altcha.min.js
static/altcha/external/altcha.css      ← dist/external/altcha.css
static/altcha/workers/{pbkdf2,sha,argon2id,scrypt}.js
static/altcha/external/altcha-workers.js   (new, provided by django-altcha)

.ABOUT files updated and added for each vendored group.

Server: v1 → v2 Proof-of-Work

ALTCHA v3 replaces the v1 hash-matching PoW with a KDF-based one. create_challenge(algorithm, cost, …) and verify_solution(payload, hmac_secret) replace the *_v1 shims introduced in aboutcode-org#48; verify_solution returns a result object rather than a (verified, error) tuple, and challenges serialize via to_dict() rather than __dict__. The altcha requirement moves to >=2.1.0,<3.0.0.

Replay protection now keys the cache on the challenge signature — an HMAC over parameters that include a random nonce and salt — because the v3 payload no longer carries a challenge string.

Note

aboutcode-org#48 deliberately stayed on the v1 API. That remains a working option: the v3 widget keeps a v1 compatibility path, and I verified a create_challenge_v1 challenge is still solved by the v3.2.1 widget and accepted by verify_solution_v1. The case for moving to v2 anyway is capability, not compatibility — v1 has no memory-hard algorithm at all, so no ARGON2ID and no SCRYPT, and its PoW is materially slower.

Strict CSP

ALTCHA_STRICT_CSP = True switches the widget template and the new AltchaWidget.media to the modular build: the stylesheet is served as a separate file and the PoW workers load from static files instead of a blob: URL.

default build ALTCHA_STRICT_CSP = True
CSP required style-src 'unsafe-inline'; worker-src blob: style-src 'self'; worker-src 'self'

The modular build registers no algorithm on its own — without registration it throws Unsupported algorithm. altcha-workers.js wires $altcha.algorithms to the bundled workers, resolving their URLs from import.meta.url (overridable via ALTCHA_WORKERS_URL). This requires dropping async from the script tags so module evaluation order holds.

Breaking changes

  1. Challenges and payloads are not compatible across versions. Any challenge issued before the upgrade is rejected after it; in-flight submissions fail validation once and succeed on retry.
  2. AltchaField options are the v3 ones. challengeurl / challengejson map to the unified challenge option with a DeprecationWarning; every other removed option (floating, overlay, hidefooter, maxnumber, strings, delay, mockerror, …) raises TypeError. Options the widget does not accept as an HTML attribute are collected into the JSON-encoded configuration attribute.
  3. get_altcha_challenge() takes algorithm / cost instead of max_number; AltchaChallengeView exposes algorithm / cost accordingly.
  4. Bundled translations moved from static/altcha/dist_i18n/all.min.js to static/altcha/i18n/all.js, following the upstream v3 layout. ALTCHA v3 publishes no minified combined-translations build. Projects setting ALTCHA_JS_TRANSLATIONS_URL are unaffected.

New settings

ALTCHA_ALGORITHM ("PBKDF2/SHA-256"), ALTCHA_COST (5000), ALTCHA_STRICT_CSP (False), ALTCHA_JS_STRICT_CSP_URL, ALTCHA_CSS_URL, ALTCHA_WORKERS_REGISTER_URL, ALTCHA_WORKERS_URL. The asset settings reuse the _STATIC_ASSET_SETTINGS resolution added in aboutcode-org#45 — relative paths go through STATIC_URL, absolute paths and full URLs pass through untouched. ARGON2ID needs argon2-cffi, available via a new argon2 extra.

ALTCHA_JS_URL is ignored when ALTCHA_STRICT_CSP is on — the two builds are different artifacts and a standard-build URL would be wrong in strict mode. ALTCHA_JS_STRICT_CSP_URL overrides it there.

Verification

54 tests pass (35 existing + 19 new). Ruff clean; Sphinx builds with --fail-on-warning; doc8 clean; the wheel packages all new assets.

Beyond the unit tests, the widget was driven in Chromium against a live server with the policy actually set as a response header:

scenario result
strict CSP, PBKDF2/SHA-256 0 violations, CSS applied, workers same-origin, payload verified server-side
strict CSP + challenge URL via AltchaChallengeView 0 violations, payload verified
default build, permissive CSP 0 violations, payload verified
default build under strict CSP fails as expected — inline style refused, 4 blob: workers blocked
SCRYPT under strict CSP fails without script-src 'wasm-unsafe-eval', passes with it

That last row is documented in the README: ARGON2ID and SCRYPT are WebAssembly and need 'wasm-unsafe-eval'; the default PBKDF2/SHA-256 does not.

Not addressed

  • fetch and verifyFunction take JS function values and cannot be expressed from Python; they need $altcha.defaults.set() from your own script.
  • refetchonexpire and customfetch have no v3 equivalent.

🤖 Generated with Claude Code

https://claude.ai/code/session_01956qnAS7qztaS7tghEKDta

Upgrade the bundled JS widget from ALTCHA v2.3.0 to v3.2.1 and move the
server side from the v1 Proof-of-Work API to the v2 one, raising the altcha
requirement to >=2.1.0,<3.0.0. PR aboutcode-org#48 took the altcha v2 package while
staying on the *_v1 compatibility functions; this replaces those with the
real v2 API, so the widget and the library speak the same KDF-based
Proof-of-Work. Challenges and payloads are not compatible across versions.

`get_altcha_challenge()` now takes `algorithm` and `cost` in place of
`max_number`, driven by the new ALTCHA_ALGORITHM and ALTCHA_COST settings,
and `verify_solution()` returns a result object rather than a tuple. Replay
protection keys the cache on the challenge signature, as the v3 payload no
longer carries a challenge string.

Widget side, the v3 options replace the v2 ones. `challengeurl` and
`challengejson` are mapped to the unified `challenge` option with a
DeprecationWarning; options the widget does not accept as an HTML attribute
are collected into the JSON-encoded `configuration` attribute.

Add an ALTCHA_STRICT_CSP setting that switches the widget template and the
new form Media to the modular /dist_external build: the stylesheet is served
as a separate file and the Proof-of-Work workers are loaded from the static
files instead of a blob: URL, removing the need for `style-src
'unsafe-inline'` and `worker-src blob:`. The modular build registers no
algorithm on its own, so a small altcha-workers.js module wires
$altcha.algorithms to the bundled worker scripts.

The new asset settings — ALTCHA_JS_STRICT_CSP_URL, ALTCHA_CSS_URL,
ALTCHA_WORKERS_REGISTER_URL and ALTCHA_WORKERS_URL — go through the
STATIC_URL resolution added in aboutcode-org#45.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01956qnAS7qztaS7tghEKDta
@hleroy hleroy changed the title fix(deps): cap altcha at <2.0.0 for incompatible v2 release (#44) feat!: upgrade to ALTCHA v3.2.1 and add a strict-CSP mode Aug 6, 2026
@hleroy hleroy closed this Aug 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants