Three intertwined fixes that together make the client usable again on
Android and against the new VK captcha flow that started 2026-06-25.
== Fix#182 — captcha parser for new VK error format ==
VK started returning a captcha-required error (error_code 14) at the
calls.getAnonymousToken step in a NEW shape that only carries:
error_code, error_msg, is_enabled_captcha, redirect_uri
and notably NO captcha_sid and NO captcha_img. The previous parser
rejected this payload with "missing captcha_sid in captcha error data"
and returned nil, so the auth loop never reached the captcha solver
and just fell through to the next client_id — exactly the regression
reported in issue #182.
ParseVkCaptchaError now treats redirect_uri, captcha_sid, captcha_img
and error_msg as optional and only requires error_code plus at least
one solvable path (redirect_uri+session_token OR captcha_img/captcha_sid).
The new flow's request body no longer sends an empty captcha_sid when
only success_token is available.
Tests in captcha_parse_test.go cover:
- new format without captcha_sid (issue #182 repro)
- legacy format with captcha_sid + captcha_img
- numeric captcha_sid
- unsolvable payload returns nil
- missing error_code returns nil
== Android TLS — embedded Mozilla CA bundle ==
VK switched *.vk.com to a certificate issued by HARICA TLS RSA Root
CA 2021 (a Greek CA added to the Mozilla trust store only in 2022).
The bogdanfinn/tls-client library leaves RootCAs as nil by default,
which makes Go fall back to x509.SystemCertPool() — and that pool is
EMPTY on Android apps (no /etc/ssl/certs/), so every HTTPS request
failed with:
tls: failed to verify certificate: x509: certificate signed by unknown authority
A new client/ca_bundle.go helper loads a CA pool with the following
priority:
1. SSL_CERT_FILE env var (if set)
2. x509.SystemCertPool() (desktops/servers)
3. Embedded Mozilla CA bundle (client/certs/cacert.pem, 189 KB,
contains HARICA TLS RSA/ECC Root CA 2021)
The embedded bundle is the same one curl ships, downloaded from
https://curl.se/ca/cacert.pem. A VKTURN_INSECURE_TLS env var is
honored for emergency debugging (not exposed as a CLI flag —
production should always verify).
== Auto-captcha improvements ==
After reverse-engineering not_robot_captcha.js (878 KB), I found three
bugs in the existing inline captchaNotRobot.check flow that each
alone caused VK to return status:BOT:
1. PoW hash was sent as bare hex. VK's JS produces:
window.captchaPowResult = "v2." + btoa(JSON.stringify({hash, nonce}))
Now solvePoW returns (hash, nonce) and formatPoWResult wraps them.
2. debug_info was a random per-request MD5. VK's JS sends either
window.vk.brlefapmjnpg or the hardcoded fallback
"4045edac1cb2c18eff209dc09cd5e2e56475a13ed4615413a87618b3c6813f9a".
Now uses the hardcoded fallback.
3. captchaNotRobot.initSession was never called. VK's JS always calls
it before settings; without it the server-side session state is
not fully initialized. Now called as Step 0/4 in both
callCaptchaNotRobot and callCaptchaNotRobotWithSliderPOC.
Also fixed:
- connectionRtt was a constant [50,50,50,...] — now generated with
realistic jitter (45-84ms baseline, occasional spikes).
- connectionDownlink was a constant [9.5,9.5,...] — now generated
with mild per-sample jitter.
- check response is now logged (status, show_captcha_type, redirect)
so future debugging is easier.
NOTE: even with all three fixes, VK still returns status:BOT when the
client runs from a datacenter IP or without a real browser. The reason
is browser_fp = FingerprintJS visitor_id (computed via canvas/webgl/
font fingerprinting in the browser) — this cannot be faked from Go
code. The next commit adds a Playwright-based solver that runs a real
headless Chromium to produce a valid browser_fp.