Skip to content

v0.40.1

Choose a tag to compare

@github-actions github-actions released this 13 Aug 18:08
· 41 commits to main since this release

An audit release: no new features, eleven confirmed defects fixed, nearly all of
them found by probing the live gateway rather than by reading the code. Seven
touch money directly.

Provenance is worth stating plainly, because it is the argument for auditing in
rounds. Two came in with 0.40.0. Six were older — one of them a guard that had
been inverted since 0.33, under a comment mandating the opposite order. And
three were introduced by THIS AUDIT's own first round and caught by its second,
including a $0 budget reservation and an incomplete version of the very fix
below it. Every fix here was re-attacked before it shipped.

Budget gate

  • fix(budget) — chat reserved against two hardcoded constants, both wrong.
    $5/M input and 4 chars/token produced a reserve that live 402 quotes beat
    on a 100k-character prompt: gpt-5.4-pro by 9.90x ($1.460020 charged against
    $0.147480 reserved), gpt-5.2-pro 6.96x, o1 4.93x, claude-fable-5 3.30x, and
    claude-opus-5 — the DEFAULT primary of mode:"powerful" — 1.65x.
    mode:"fast" was 2.46x short because 0.40.0 documented gemini-3.5-flash's 3x
    reprice without moving the estimator off the cheap heuristic. Reserves now
    come from a real per-model table (the named model, or the tier's most
    EXPENSIVE member, since the loop can fall through to any of them) at the
    gateway's own ~2.08 chars/token. Cushion is 1.12–1.20x across 18 probed cases.

  • fix(budget) — a settled call that then failed was never booked. x402
    settles on the 200, before the body is read, and every paid path streams — so
    a mid-stream failure left the USDC gone, budget.spent unmoved, and the
    reservation released: the ledger showed a free call. The routing loop then
    tried the next model and settled a SECOND payment under the same single
    reservation. Settled spend is now booked on the throw path, and the loop stops
    once anything has settled (free models settle $0, so mode:"free" still falls
    through as designed).

  • fix(budget) — native Claude calls were booked at Anthropic's list price.
    The ledger multiplied response tokens by $15/$75 for opus, while the gateway
    resells opus at $5/$25 and settles the QUOTE (which prices output at 10% of
    max_tokens, floors at $0.001, and adds the transaction fee). A default
    claude-opus-5 call settles $0.003660 and was booked as $0.03 — an 8x
    over-count, so a budget cap tripped at an eighth of its allowance.

  • fix(budget) — a prototype key reserved $0. CHAT_PRICE_PER_MTOKEN[model] ?? DEFAULT let Object.prototype members through (model:"constructor"
    resolves to a function, which survives ??), producing NaN and then a $0
    reservation the gate approves unconditionally. The same fail-open the modal
    GPU table already documents — reintroduced by the fix above and caught by this
    audit's own regression pass.

Security

  • fix(security) — an expensive route could be bought at the cheap tier's
    reserve.
    phone/numbers/b<TAB>uy (fetch deletes the tab) and
    phone/numbers/%62uy (the gateway decodes it) both reach the $5.001 buy route
    while the classifier matched neither and reserved $0.012 — 417x short, which
    clears every budget cap. blockrun_modal was worse: it never used the shared
    helper at all, so sandbox/cre<TAB>ate reserved $0.003 against a $192.00
    non-refundable 24h H100 create. normalizeClassifyPath now decodes once and
    deletes tab/LF/CR, mirroring hasPathTraversal, which has done both since
    0.33 — the same asymmetry, in the same file, for two releases.

Correctness

  • fix(video) — grok honours resolution, and lost its margin.
    xai/grok-imagine-video now prices 480p ($0.05/sec, default) and 720p
    ($0.07/sec) and rejects anything higher, but the handler forwarded resolution
    only for Seedance — so a caller asking for 720p silently got 480p and paid for
    480p. The same probes show grok no longer carries the 5% margin (Sora still
    does), so the two per-second models needed separate formulas; forwarding the
    parameter without splitting the rate would have under-reserved 720p by 1.33x.

  • fix(wallet) — the first-run chain pin overrode SOLANA_WALLET_KEY.
    ensureBothWallets() preserved the active chain through setChain(), i.e. an
    explicit ~/.blockrun/.chain, which outranks everything — so a clean install
    wrote .chain=base, and an operator who later set SOLANA_WALLET_KEY stayed
    on Base with no way to discover why. The pin moved to its own .chain-auto
    file, ranked below the env var and above session autodetect, and setChain()
    now deletes it.

Found by this audit's own second pass (each fix was re-attacked; two of the
three were defects in the round-one fixes themselves)

  • fix(security) — the control-char strip ran AFTER the decode, in both path
    helpers.
    Each transformation is right alone, which is why every test passed;
    nesting one inside the other defeats both. A tab SPLITTING a percent-escape
    makes decodeURIComponent throw, the catch falls back to the raw string, and
    the later strip leaves the escape uninterpreted:
    phone/numbers/%<TAB>62uy classified as the $0.012 unknown while the gateway
    quotes $5.001, and sandbox/%<TAB>63reate as $0.003 against $192.001. Worse,
    hasPathTraversal had the same inversion — since 0.33, under a doc comment
    mandating the opposite order — so %<TAB>2e%<TAB>2e/phone/numbers/buy escaped
    the tool's namespace entirely and reached the $5 phone route on
    blockrun_surf's $0.0095 reserve, defeating profile scoping too.

  • fix(budget) — vendor-less model ids reserved the default. The gateway
    serves gpt-5.4-pro and openai/gpt-5.4-pro at the identical price (and bare
    gpt-oss-120b free), but the tables key on the prefixed form only, so a bare
    id fell to $5/$30: 5.16x short on gpt-5.4-pro. The mirror case made a bare free
    id reserve like a paid one and silenced its truncation warning.

  • fix(speech) — the 60s timeout was shorter than the model's own ceiling.
    bytedance/seed-audio-1.0 can emit 120 seconds of audio, so a long render was
    aborted after its payment signature had been sent — losing the clip, and with
    it any record of a settlement that did go through. Raised to 180s, and the
    timeout message now says a charge may stand.

  • fix(wallet) — existing installs are told when their stored preference is
    overriding SOLANA_WALLET_KEY.
    Deleting a pre-0.40.1 .chain automatically
    is not safe: nothing distinguishes the machine-written file from a real
    action:"chain" choice — that is exactly the information the old behaviour
    destroyed. So blockrun_wallet now says so, with the one command that fixes it.

Tooling

  • npm run verify:prices gains a row per routing tier plus the five
    above-default models; it had probed only balanced/gpt-5.6-terra for four
    releases, which is why none of the above was visible. Its Solana check no
    longer conflates "dearer than Base" with "not covered by the reserve" — only
    the latter blocks a release, so the gate stops crying wolf over the deliberate
    cross-chain price difference.