Summary
For DKB (BLZ 12030000, FinTS URL https://fints.dkb.de/fints, TAN mode 940), FinTs::persist() produces a deterministically truncated PHP serialize output. The byte stream ends mid-string within an s:136:"..." declaration of an HICAZSv1 segment, with no closing quote, no semicolon, no closing braces. Calling unserialize() on the result fails:
Error at offset 877 of 923 bytes in vendor/nemiah/php-fints/src/FinTs.php line 213
Reproducibility: 100% deterministic — same DKB account, multiple successful interactive runs, every captured persistence blob is bit-identical (sha256 the same, length 1231 base64 chars, decoded 923 bytes ending at the same offset).
Repro
- Connect to DKB with a fresh FinTs (empty
$persistedInstance).
- Login with TAN mode 940 + pushTAN approve.
- Call
$fin_ts->persist() after successful login.
The returned string, when base64-decoded, ends:
...{i:0;s:136:"HICAZS:16:1:3+1+1+0+450:N:N:urn?:iso?:std
^-- stream ends here, mid-string
s:136 declares a 136-byte string but only the first 40 bytes are emitted; no "; terminator, no closing braces for the parent Fhp\Segment\CAZ\HICAZSv1, Fhp\Protocol\BPD, or the outer a:9:{...}.
What I ruled out (in isolation)
serialize() of a fresh HICAZSv1 populated by Parser::parseSegment(<wire>, $seg) and then re-emitted via Serializer::serializeSegment($seg) round-trips correctly (137-byte input → 137-byte output → 186-byte PHP-serialize → unserialize OK).
- Wrapping such a segment inside a synthetic
BPD->parameters = ["HICAZS" => [1 => $seg]] and PHP-serializing the BPD also round-trips cleanly (359 bytes, ends with }, unserialize OK).
- So the bug needs the production-time
FinTs state assembled by a real DKB dialog. Reproducing only via the synthetic path didn't surface it.
Environment
- nemiah/phpFinTS commit
4800d6a (HEAD of master, 2026-05-23)
- PHP 8.4.19 inside the
bnw/firefly-iii-fints-importer Docker image (php -S server)
- Linux/arm64 (Raspberry Pi 5, 16 GB), rootless Docker
Why this matters
It defeats the persistence model: any caller that wants to reuse a 90-day SCA-exempt session has to retry the login (and consume a fresh TAN challenge) every time, because the saved blob is unloadable. For DKB specifically, this combines badly with their per-day TAN-challenge quota — failed cron jobs quickly trigger error 9040 "Authorization method is locked because too many challenges were requested," sometimes severe enough to invalidate the mobile-app session too.
Happy to help bisect
If you have a hypothesis for which segment property / element is producing the silent truncation, I can instrument my local copy and re-run against my DKB account (carefully — one attempt per day at most). Or if you can point me at the test harness pattern you'd use to replay a captured BPD response, I can capture mine and produce a synthetic repro.
Summary
For DKB (BLZ 12030000, FinTS URL
https://fints.dkb.de/fints, TAN mode 940),FinTs::persist()produces a deterministically truncated PHP serialize output. The byte stream ends mid-string within ans:136:"..."declaration of anHICAZSv1segment, with no closing quote, no semicolon, no closing braces. Callingunserialize()on the result fails:Reproducibility: 100% deterministic — same DKB account, multiple successful interactive runs, every captured persistence blob is bit-identical (sha256 the same, length 1231 base64 chars, decoded 923 bytes ending at the same offset).
Repro
$persistedInstance).$fin_ts->persist()after successful login.The returned string, when base64-decoded, ends:
s:136declares a 136-byte string but only the first 40 bytes are emitted; no";terminator, no closing braces for the parentFhp\Segment\CAZ\HICAZSv1,Fhp\Protocol\BPD, or the outera:9:{...}.What I ruled out (in isolation)
serialize()of a freshHICAZSv1populated byParser::parseSegment(<wire>, $seg)and then re-emitted viaSerializer::serializeSegment($seg)round-trips correctly (137-byte input → 137-byte output → 186-byte PHP-serialize → unserialize OK).BPD->parameters = ["HICAZS" => [1 => $seg]]and PHP-serializing the BPD also round-trips cleanly (359 bytes, ends with}, unserialize OK).FinTsstate assembled by a real DKB dialog. Reproducing only via the synthetic path didn't surface it.Environment
4800d6a(HEAD of master, 2026-05-23)bnw/firefly-iii-fints-importerDocker image (php -Sserver)Why this matters
It defeats the persistence model: any caller that wants to reuse a 90-day SCA-exempt session has to retry the login (and consume a fresh TAN challenge) every time, because the saved blob is unloadable. For DKB specifically, this combines badly with their per-day TAN-challenge quota — failed cron jobs quickly trigger error 9040 "Authorization method is locked because too many challenges were requested," sometimes severe enough to invalidate the mobile-app session too.
Happy to help bisect
If you have a hypothesis for which segment property / element is producing the silent truncation, I can instrument my local copy and re-run against my DKB account (carefully — one attempt per day at most). Or if you can point me at the test harness pattern you'd use to replay a captured BPD response, I can capture mine and produce a synthetic repro.