Releases: BlockRunAI/Franklin
Release list
v3.30.0 — native Polymarket betting (end-to-end)
Franklin gains the execution half of the prediction-market story. PredictionMarket researches the bet; the new PolymarketBet tool places, manages, and settles it — signing orders locally with the same `~/.blockrun/.session` key that pays x402 on Base.
- New core tool
PolymarketBet— full lifecycle parity with blockrun-mcp: setup / fund / buy / sell / orders / cancel / positions / redeem / withdraw (CLOB V2, Polygon). - Real-money safety — dry-run unless `confirm:true`, plus an interactive Confirm/Cancel user gate before any on-chain signing (`auto_approve` for headless). Per-order + session USD caps; bet stakes stay off the x402 `--max-spend` ledger — only the $0.01 fund fee is metered.
- Ported byte-faithfully — 11-file module incl. the l1-auth ERC-7739 workaround, pinned to `@polymarket/clob-client-v2@1.0.8`; a golden-vector test guards the signing envelope. Polymarket-only; other venues stay research-only.
Verified: 513/513 local tests, tsc clean, functional smoke test. Design doc: `docs/plans/2026-07-10-franklin-native-polymarket-betting-design.md`.
v3.29.15 — spend-accounting integrity + prompt-injection containment (round-10)
The bash-guard converged (rounds 7-9), so round-10 widened the audit to the non-bash surface — spend/budget, payment proxy, prompt-injection framing, persistence — and found higher-impact issues a narrow bash hunt never would.
| Sev | Finding | Fix |
|---|---|---|
| 🟠 HIGH | Exa tools spend USDC invisibly to --max-spend — never recordUsage |
record each settled Exa charge like every sibling paid tool |
| 🟠 HIGH | Concurrent paid-tool spend escapes the ceiling on stream error/abort/model-switch | failure path now folds the tool-spend delta; cap check trips on it |
| 🟠 HIGH | SearchX returns attacker tweet text UNFRAMED | frameUntrusted on both return paths |
| 🟡 MED | VoiceStatus dumps remote-party transcript UNFRAMED | frameUntrusted |
Documented limitation: --max-spend is a per-round soft ceiling (checked after parallel tools settle); the on-chain wallet balance is the hard floor. Pre-dispatch budget gate deferred to avoid a hot-loop rewrite.
Tests pin the spend-accounting primitive, Exa recording, the failure-path fold, and SearchX/Voice framing. Local suite 510/510.
npm i -g @blockrun/franklin@3.29.15
v3.29.14 — security: brace-expansion wallet read + 6to4 SSRF (round-9)
A third convergence verify (re-attacking the round-8 quote-normalization & rtk-recursion fixes) confirmed 2 NEW classes. The find count is converging (7→6→5→2); survivors are increasingly exotic.
| Sev | Bypass | Fix |
|---|---|---|
| 🔴 CRITICAL | cat {~,}/.bloc{k,}run/.{session,} — brace expansion (4th shell primitive) reads the wallet EVM key, defeating directory+basename+glob guards at once |
comma/.. brace groups now opaque (prompt, like $VAR); deny patterns also run on a brace-collapsed copy |
| 🟡 MEDIUM | http://[2002:a9fe:a9fe::]/ — 6to4 IPv6 embeds 169.254.169.254, survives WHATWG normalization |
decode & re-check like NAT64 / IPv4-mapped |
The brace splice is reader-agnostic (grep/cut/cat). Regression tests pin both classes plus benign controls. Local suite 506/506.
npm i -g @blockrun/franklin@3.29.14
v3.29.13 — security: close 5 more single-command bypasses (round-8)
A second 13-agent adversarial convergence verify of the 3.29.12 guards confirmed 5 NEW single-command bypass classes the round-7 fixes didn't anticipate — one critical (a fresh evasion of the wallet directory rule itself).
| Sev | Bypass | Fix |
|---|---|---|
| 🟠 HIGH | rtk node evil.js — the rtk proxy blanket-allowed any wrapped command (wildcard RCE) |
strip rtk/rtk proxy and recurse; safety = wrapped command's |
| 🔴 CRITICAL | cat ~/.block''run/.sess''ion — empty-quote / backslash / ANSI-C splice reads the wallet EVM key |
deny patterns now run on a shell-normalized copy; $'…'/$"…" opaque |
| 🟠 HIGH | cat ~/.git-credentials & friends — gh hosts, cargo/rclone, Solana CLI keypair, keychain, shell history |
broadened credential-store denylist |
| 🟡 LOW | git branch -d / git tag -d — ref destruction via the read-only allowlist |
delete/rename/force prompt; read + create stay safe |
The critical splice is notable: it defeats the wallet directory rule (.blockrun) itself — the shell strips ''/""/\ before opening the file, but the literal regex saw non-contiguous text. Now matched against a dequoted/unescaped normalization that mimics shell tokenization, closing the splice on all four key basenames (.session, .solana-session, .solana-session-key2, solana-wallet.json).
The recurring lesson: text-classifying shell commands for auto-approval is a denylist treadmill. The canonicalized isWalletKeyPath guard on the file/media tools is the primary wallet protection; the bash-guard is the best-effort shell net and now fails toward prompting on quoting, expansion, wrappers, and anything it can't statically resolve.
Regression tests pin all five classes plus benign controls. Local suite 504/504.
npm i -g @blockrun/franklin@3.29.13
v3.29.12 — security: close 6 convergence-verify single-command bypasses (round-7)
An 8-agent adversarial convergence verify of the 3.29.11 guards confirmed 6 more single-command holes (2 critical). Round-7 fixes them at the root, with the never-published 3.29.11 media-write fix folded in.
Unifying principle: a bash segment is auto-approved ("safe") only when its full effect is statically visible — any runtime indirection the classifier can't see now forces a prompt.
Closed this release
| Sev | Bypass | Fix |
|---|---|---|
| 🔴 CRITICAL | BASH_ENV=./evilrc ls — env-var prefix exec/loader hijack (LD_PRELOAD, DYLD_*, PERL5OPT, NODE_OPTIONS, PATH, IFS) |
only a locale/display allowlist (LANG, LC_*, TZ, TERM) may prefix a safe command |
| 🔴 CRITICAL | cat $HOME/.bl*/.s* — $VAR-rooted glob wallet-key read |
parameter expansion treated as opaque like $(...) |
| 🟠 HIGH | git status > ~/.bashrc — redirect write on git/npm/cargo/bun |
redirect check now runs for every segment |
| 🟡 MEDIUM | cat ~/.ssh/id_rsa — host-credential reads (~/.aws, ~/.gnupg, gcloud, .npmrc, .pgpass, .netrc) |
mirrors the Write tool's dangerous-path block |
| 🟡 MEDIUM | git config core.pager "node evil.js" — config/remote writes (exec hook) |
safe only in get/list / read form |
| 🟡 MEDIUM | metadata.google.internal — SSRF cloud-metadata hostnames (*.internal, instance-data, NAT64) |
denied by name |
Plus (folded-in 3.29.11): media tools (ImageGen/VideoGen/MusicGen/BrowserX) now apply the isWalletKeyPath guard to output_path, and bash-guard splits on newline/CR/lone-& separators.
The canonicalized isWalletKeyPath guard on the file/media tools remains the primary wallet protection; the bash-guard is the best-effort shell net and now prompts on anything it can't statically reason about.
Regression tests pin all six classes plus benign controls. Local suite 500/500.
npm i -g @blockrun/franklin@3.29.12
Franklin 3.29.10 — close the 3.29.9 guard bypasses
An adversarial bypass-hunt of the v3.29.9 security guards (and a re-review of the fixes) found 25 bypasses in the guards themselves — root cause: matching shell text / exact strings instead of canonicalizing. All closed.
- SSRF — redirect bypass (critical). The guard checked only the initial host, but
fetchfollows redirects, so a public URL that 302s to169.254.169.254/127.0.0.1sailed through. WebFetch + reference-image now follow redirects manually, re-validating the host on every hop. Also: IPv4-mapped IPv6, trailing-dot, and a false-positive blocking publicfc*/fd*hosts. - Wallet key — directory rule. Filename matching was trivially obfuscated (
.solana""-session,.\solana-session, globs, unlisted key files). Any reference to~/.blockrunin a shell command now prompts; the file Read/Write/Edit tools have a separate canonicalized guard. Case-insensitive on macOS/Windows. - bash-guard —
gh apimutations in every form (--method=POST/-XDELETE/-ftitle=v);find -exec/-delete/-fprint/-fprintf/-fls;tee/xargsremoved; coreutil writers (sort -o,tree -o,uniq IN OUT,yq -i) and>|/>&FILEredirects now prompt.
The file Read/Write/Edit tools remain the primary canonicalized guard; bash-guard is the best-effort shell net (non-trust mode). New regression tests pin every bypass. Local suite 490/490.
See CHANGELOG.md.
Franklin 3.29.9 — security hardening (round-5)
A fifth review round (fresh non-money angle) found 13 issues — four HIGH security holes around the wallet. Each finding was adversarially verified. All fixed. Local suite 484/484.
Security — wallet exposure (HIGH)
franklin proxynow binds loopback only (was0.0.0.0— the x402 payment proxy was reachable from the LAN).- The wallet key store (
~/.blockrun/.session/.solana-session/solana-wallet.json) is now refused by the agent's Write/Edit/Read tools. bash-guardno longer auto-approvescat-ing the wallet key orxargs;gh apimutations (-X DELETE/POST/PUT) now prompt.
Security — defense in depth
- Web/search output is framed as UNTRUSTED data-not-instructions (prompt-injection containment, parity with MCP/skills).
- SSRF guard on WebFetch + reference-image fetch (blocks loopback/private/metadata;
FRANKLIN_ALLOW_PRIVATE_FETCH=1to opt out).
Correctness & data integrity
sanitizeHistoryadopted on reference-diff (orphan tool_results no longer 400 on strict mode); atomic stats writer + shape coercion;franklin migratekeeps http/sseurl; MCP connect-timeout teardown;task cancelrecycled-PID guard; brain concurrent-merge.
See CHANGELOG.md for full detail.
Franklin 3.29.8 — wallet spend-ceiling + paid-tool accounting (round-4 money audit)
A fourth review round swept the paid-tool surface beyond the changed files. Headline: the --max-spend hard cap and several paid tools were blind to real USDC spend. All adversarially verified; the PR itself was then independently reviewed (which caught a concurrent-tool race in the ceiling fix, now closed).
Wallet spend ceiling (HIGH)
--max-spendnow bounds TOTAL USDC, not just LLM tokens. Every paid tool (ImageGen/VideoGen/MusicGen, Exa, Surf, RealFace, Voice, Phone, Modal, DeFiLlama, RPC, Prediction) settled USDC through its own x402 path and counted $0 against the cap. A new live-spend accumulator is snapshotted before the stream (so mid-stream concurrent tools are captured, not raced past), netted against the LLM's own cost, and folded into the session total.- Charged-but-failed LLM calls now counted — a paid call whose stream then failed (timeout / mid-stream error / Esc) was never folded into the cap (and drift compounded across
--resume).
Paid-but-invisible accounting
- DeFiLlama, MultiChainRPC, PredictionMarket (now persisted), MusicGen, and panel-initiated phone Buy/Renew/List now record their settled USDC — were invisible to
franklin stats.
Money-safety + correctness
- $5 BuyPhoneNumber/RenewPhoneNumber now confirm via
onAskUser. - VoiceStatus records once per invocation, not per 5s poll (was ≤420/call, evicting real spend history).
- RiskEngine per-position cap values the held position at cost basis; over-sized closes flatten instead of throwing.
Contributor PR #89 (thanks @samsamtrum): Jupiter swaps no longer round a sub-precision amount up to one atomic unit; refined to floor excess precision and reject only true dust.
Local suite 479/479. See CHANGELOG.md for full detail.
Franklin 3.29.7 — finish the Exa wire-format fix + budget-safety follow-ups
Rollup release (npm went 3.29.4 → 3.29.7; 3.29.5/3.29.6 were never published). Headlined by completing the Exa wire-format fix and a three-round adversarial review that unified media-budget accounting.
Wallet / budget safety
- Exa wire-format fix completed in the prefetch twin path.
intent-prefetch.ts's own paid/v1/exa/answercall still read the deadbody.data?.answershape — it paid USDC, dropped the answer, and reported $0. Now reads both shapes viareadExaAnswer(type-safe) and books the real cost. - Size-aware ImageGen + unified VideoGen/MusicGen budgets. Per-image/clip/track cost is resolved once (max of live catalog vs margin-adjusted static) and threaded through budget check, spend confirm, stats, and the content-budget record — closing a ~2–3× undercount on large image sizes and catalog-priced video (Seedance) / non-default music models.
- Prefetch stock cost now reflects real spend (telemetry diff), so a 5-min cache hit no longer over-reports $0.001.
Recovery & robustness
- Rate-limit/overload errors leaked as 200-OK text now auto-fall-back to another free provider (overloaded respects a user-pinned paid model); crash-safe persistence (
fsync+ orphan-.tmpcleanup); VoiceCall no-call_idguard.
Test integrity
- Strengthened the paid Exa e2e to assert on tool-rendered markers (not the model's narrative); added paid ImageGen e2e + no-spend regression tests. Local suite 475/475.
See CHANGELOG.md for the full 3.29.5 / 3.29.6 / 3.29.7 detail.
Franklin Agent 3.28.3 — Node version guard
Old Node (< 20.19) crashed at startup with an opaque ERR_REQUIRE_ESM stack trace from deep inside rpc-websockets/uuid. The real requirement was always Node 20.19+ (require(esm)). 3.28.2's >=20.18.0 floor was one patch too low to actually run.
- Runtime preflight guard. New dependency-free
src/preflight.ts, imported before any other module, checks the Node version and on Node < 20.19.0 prints a clear upgrade message (nvm/fnm) and exits cleanly — no node_modules stack trace. - Correct engine floor.
engines.node>=20.18.0→>=20.19.0. - README updated to Node 20.19+ with the
ERR_REQUIRE_ESMcrash in the troubleshooting table.