Skip to content

Release v0.10.0 - #316

Merged
Greaker merged 18 commits into
mainfrom
development
Oct 22, 2026
Merged

Greaker merged 18 commits into
mainfrom
development

Conversation

@Greaker

@Greaker Greaker commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

Release

Promote development at db99e2bd9291416e4f441b5af384198cbf7936aa to main for sandbox v0.10.0.

Included

Compatibility and live-release notes

Minor release with chain, API and platform changes.

Contracts and the NB Bond API

  • Tbd has a new constructor and ABI (no reserve argument, no govReserve or isGovernmentNominated, new CctFromNotCaller).
  • BondToken gains removeController, and BondManager gains BondAuctionAddressZero.
  • OrderBook gains getOrder, and Broker restricts revocation.
  • The deploy scripts change accordingly (04_Tbd, 07_WnokSetup, 11_BondSetup, 06_OrderBook): the reserve is no longer allowlisted on a TBD, BondDvP and BondManager get the narrowed roles, and the deployer renounces admin after wiring, so wiring must finish before that step.
  • The bond stack, the TBDs and the order book are redeployed with new addresses. Positions under the previous contracts are not representable, and the local sandbox is recreated from genesis.
  • The NB Bond API and UI must be deployed together with the new contracts:
    • The TBD resource no longer carries government (the TbdGovernment schema is removed).
    • Bidder privateKey is nullable.
    • Bank creation deploys the six-argument Tbd.
    • In entra mode, non-operator callers get 403 on issuer actions.
  • /v1/health reports contracts.bondManagerCompatible. Reset the projection database (POST /v1/admin/restart-ingestion?fromBlock=0) once contracts and API are both live.

Platform

  • Besu 26.10.0 upgrades Bonsai databases on first start (here, the validator). Rolling back needs besu storage revert-code-format, and a Forest archive downgrade also needs a version-compatibility override on one old-version start.
  • NGINX Gateway Fabric 2.8.0 needs Kubernetes 1.32 or later. Its CRDs are applied server-side before the chart upgrade, because Helm does not upgrade CRDs.
  • smart-contract-verifier v1.11.0 needs SMART_CONTRACT_VERIFIER__COMPILERS__EXECUTION__TYPE=native.
  • Postgres 18.6 needs no dump or restore within 18.x.
  • Every repo-owned image rebuilds once because its tag now includes its base image.
  • The Blockscout backend and frontend images are still built from source.

Behaviour change for operators

  • ./sandbox.sh stop no longer deletes anything; use ./sandbox.sh delete for a clean slate.

Verification

On a local sandbox at db99e2b:

  • Lifecycle: passed, with balances matching the previous release's validated trail:
    • issuance at clearing rates 425 and 415
    • a buyback at 99.00
    • coupons to maturity on two bonds, both reaching matured at zero supply with wNOK supply unchanged
    • bank creation with the six-argument Tbd
  • Contract verification: contracts.sh verify-latest verified 13 of 13.
  • CI: green on development.

No Besu node, chain, contract, deployment-environment, or live application change occurs by merging this PR. Artifact publication and GitOps promotion remain separate gated operations.

Land four open Dependabot bumps as one rollup with the inventory rows
the bot cannot write: zod 4.5.4 -> 4.6.5 and typescript-eslint
8.69.0 -> 8.70.1 in nb-bond-api, @vitejs/plugin-react 6.0.3 -> 6.1.1
and @testing-library/react 16.3.2 -> 16.3.3 in nb-ui. typescript-eslint
goes to 8.70.1 rather than the 8.70.0 the bot proposed.

The lockfile changes 16 entries and adds or removes none: the four
packages, the ten exact-pinned @typescript-eslint/* siblings, and the
two workspace entries with exact ranges. Incidental movements from the
scratch resolve (ignore 7.0.10, a plugin-react hoist, and removals
caused by legacy peer resolution) are left out. Root overrides still
resolve at their pins.

No source changes were needed: lint adds no findings, openapi.json
regenerates byte-identical, nb-bond-api 249 jest and nb-ui 125 vitest
tests pass, and both builds pass. The better-sqlite3 13 major stays
out for its own PR. The plan folder is archived in this change.
Move the NB Bond API's SQLite driver from 12.11.1 to 13.0.3, the
version Dependabot proposed. Version 13 is a Node-API addon: prebuilt
binaries now ship inside the npm package, so prebuild-install and its
build-time download are gone. The bundled SQLite moves from 3.53.2 to
3.53.4, which keeps the on-disk format.

The lockfile changes better-sqlite3, adds node-addon-api 8.9.2 (MIT),
removes the 29 entries only prebuild-install and bindings needed, and
marks detect-libc dev-only; the workspace range stays exact. Root
overrides still resolve at their pins. The inventory row is updated.

No source change was needed: nb-bond-api 249 jest tests and nb-ui 125
vitest tests pass, and both builds pass. On the local sandbox the
rebuilt image loads prebuilds/linux-arm64.node without compiling, and
a resync from block 0 reproduces every table count and API response of
the 12.11.1 baseline, including the system-of-record tables. The plan
folder is archived in this change.
./sandbox.sh stop used to uninstall every Helm release and delete the
Blockscout namespace, which removed the chain, the explorer database,
and the NB Bond API database. It now stops the Kind node container
gracefully with SANDBOX_STOP_TIMEOUT_SECONDS (default 330) and keeps
everything; ./sandbox.sh start resumes a stopped node before its usual
run and skips the contract deploy. ./sandbox.sh delete is unchanged.

Component scripts follow the same rule: stop scales the component to
zero and keeps its volumes and release, start restores it through
Helm, and a new delete verb carries the old uninstall or namespace
delete. contracts.sh stop is a no-op; contracts.sh delete removes the
registry ConfigMap.

The NB Bond API now handles SIGTERM and SIGINT: it closes the HTTP
server and open connections, stops ingestion, waits for queued work
with a 10-second bound, and exits. As PID 1 without a handler it used
to ignore SIGTERM and be killed after its 30-second grace period; its
pod now stops in about a second.

Every helm and kubectl call now names the sandbox's own context. They
used to follow the current kubectl context, so a component command, or
the old stop, could act on another local cluster with same-named
releases.

Live proof on the local sandbox: three stop/start cycles kept every
API response, API table count, and Blockscout count, with PostgreSQL
starting cleanly each time. A stop takes about 90 seconds; workloads,
PostgreSQL, and etcd exit within 8 seconds and the rest is systemd in
the Kind node waiting for kube-apiserver and the containerd shims.
Component stop/start and nb-ui delete/start were exercised too.

Docs describe the new verbs wherever the old ones appeared, KNOWN_ISSUES
explains stopping before Docker quits and the catch-up indexer gap, and
the Blockscout debugging playbook documents recovering a PostgreSQL
whose replication-origin checkpoint was zeroed by an unclean shutdown.
The plan folder is archived in this change.
The local Blockscout ran with DISABLE_CATCHUP_INDEXER set, so it only
indexed blocks as they arrived. Blocks mined while it was stopped,
broken, or freshly deleted were never indexed, and neither was the
genesis block, so block indexing never reported finished. No record
says why it was disabled; the setting dates from the initial release.

Catch-up is now on and every other indexer setting is unchanged. On the
local sandbox, enabling it indexed block 0 and block indexing reports
finished. Blocks mined while Blockscout was stopped were filled in
within seconds of start, and after blockscout.sh delete a fresh start
re-indexed all 109 blocks from block 0 within 77 seconds, with
verify-latest restoring 13 of 13 verifications. Besu logged no warnings.

Docs drop the gap warnings from KNOWN_ISSUES, services/DEVELOPMENT.md,
the Blockscout debugging playbook, and the blockscout.sh stop comment.
The plan folder is archived in this change.
…300)

* Tbd: bind cctFrom to the payer and remove the unused nomination path

cctFrom now moves only the caller's own deposit, unless the caller is the
DvP contract passed to the Tbd constructor, which keeps settling on behalf
of the payer. CCT_FROM_CALLER_ROLE is still required. Any other caller
reverts with the new CctFromNotCaller error, so the payer rule lives in
the contract rather than in how the role is assigned.

The government-nomination path has had no consumer since bond cash legs
moved to wNOK, so it is removed: the constructor loses its reserve
argument, and govReserve, isGovernmentNominated, the reserve mint branch
in _update and NotGovernmentNominated are gone. The TBD and wNOK setup
scripts no longer allowlist the reserve on TBD Nordea or give it a wNOK
allowance there. The DvP settlement fixture now constructs its TBDs with
the DvP that settles, instead of deploying a second one afterwards.

The NB Bond API deploys created banks with the six-argument constructor
from refreshed Tbd ABI and bytecode artifacts, drops the government field
from the TBD DTO (OpenAPI regenerated), and the Banking page drops the
matching KPI. Contract reference, NatSpec pages and the TBD diagrams
describe the caller rule.

Tests: forge 393 passed (new: payer-only cctFrom and DvP settlement on
behalf of the payer); nb-bond-api jest 254 passed; nb-ui vitest 125
passed.

* ERC-1410: keep operator transfers in their partition (#301)

operatorTransferByPartition no longer reads a destination partition from
the first 32 bytes of data. Like transferByPartition, it now always moves
units within the partition it is given and treats data and operatorData
as opaque event payload. The internal _transferByPartition and _move
helpers take a single partition, so a partition's supply changes only
through mint and redemption, and ChangedPartition stays in IERC1410 as
part of the standard surface but is never emitted. The bond stack never
used partition changes: BondDvP always passes empty data. The
transferByPartition NatSpec, which described a data-driven partition
switch it never performed, is corrected.

The BondToken invariant suite now runs two ISINs and a holder transfer
action that names either partition in data, and checks supply against
offering and holder balances per partition.

Tests: forge 395 passed (new: an operator transfer stays in its source
partition and emits no ChangedPartition; a unit moved with another
partition in data does not reduce that partition's issuance headroom;
extended invariants). The BondToken ABI is unchanged.
…#304)

The API now honours the testMode query flag (unsealing bids on open
auctions, skipping the close end-time pre-check) only for operator
callers. In the local none auth mode every caller counts as an operator,
so local behaviour is unchanged; in entra mode a tester's flag is ignored
rather than rejected, so a stale browser setting still yields a normal
sealed read. The rule lives in a small test-mode module built on a new
isOperatorRequest helper in auth.ts, and the OpenAPI description states it.

Bidder DTOs return privateKey only in none mode and never for a fixture
bidder whose key comes from a PK_* environment override; otherwise the
field is null, matching how bank keys are already handled. The OpenAPI
schema marks the field nullable and describes the rule.

The UI mirrors this with a canOperate capability delivered through a
CapabilitiesContext: the top-bar test-mode toggle is shown only to
accounts that can operate, getTestMode() ignores a stored flag for anyone
else, and the Bidders page shows "Held by the server" when no key is
returned. The disabled "Place bid" hint mentions Test mode only to
accounts that can use it. Docs describe the rule in both services and
in ARCHITECTURE.md.

Tests: jest 258 (39 suites), vitest 131 (26 files), all green.
* Remove the unused BondOrderBook and its factory

BondOrderBook and BondOrderBookFactory were never deployed by the sandbox
scripts and have no caller in the API or UI. The broker-level venue in
the closed-loop settlement plan is meant to replace them, so keeping and
maintaining a second, holder-level bond secondary market in source is
unused code rather than a supported feature. Both contracts, their test
suites and their NatSpec pages are removed.

TbdAddressZero and PartitionZero were only raised by the removed
contracts and are dropped from Errors; BondTokenAddressZero moves under
the BondManager heading. With this change the bond stack no longer
imports anything from the private-bank contracts.

The contract reference, the contract topology note, the NB Bond API
development guide and the active closed-loop settlement plan now state
that no on-chain bond secondary venue exists.

Tests: forge 347 passed (the 48 removed tests covered only the deleted
contracts).

* Narrow bond and broker roles; hand deployer admin to the owner (#303)

BondToken.addController now only makes an address an ERC-1410
controller (an operator over every holder's units); lifecycle rights
come from BOND_CONTROLLER_ROLE, granted separately. A new
removeController (BOND_ADMIN_ROLE) takes controller status away again.
The bond setup script gives BondManager BOND_CONTROLLER_ROLE only, since
it moves units solely through BondDvP, and makes BondDvP both a
controller and a BOND_CONTROLLER_ROLE holder for settlement, redemption
and buyback. Once wiring is done, the deployer key grants
DEFAULT_ADMIN_ROLE on BondManager, BondAuction, BondToken and BondDvP
(and BOND_ADMIN_ROLE on BondToken) to the Norges Bank owner key and
renounces its own, so no deployment-only key keeps admin on the bond
contracts while roles can still be rotated.

The BondManager constructor now rejects a zero wNOK, controller,
auction, bond token or DvP address, not only a zero reserve, adding
BondAuctionAddressZero.

OrderBook gains a getOrder view, and Broker lets a client revoke only
orders placed for that client's own securities wallet
(OrderNotOwnedByClient otherwise). The OrderBook script stops granting
SUBMIT_ORDER_ROLE to the broker EOAs; only the Broker contracts hold it.

The NB Bond API BondToken and BondManager ABIs are refreshed for the new
function and errors. Contract reference, security notes and NatSpec
pages describe the role shape.

Tests: forge 358 passed (new: controller add without lifecycle role,
controller removal and its effect on operator transfers, BondManager
constructor zero checks in a separate small suite, and cross-client
revoke rejection for buy and sell orders); nb-bond-api jest 254 passed.
In entra mode, creating and disabling bonds and creating, closing,
cancelling, and finalising auctions now require an operator role, in line
with coupon payments, admin, and the Central Bank surface. The gate runs
ahead of request validation, so other recognised roles get 403 before any
input is inspected. Testers keep reads, bidding, and Banking; none mode
stays fully open. Each gated operation documents the rule in the OpenAPI
contract.

A new route-authorization suite drives every mutating route through the
real auth middleware as an operator and as a tester, with invalid input so
a cleared gate answers without reaching the chain. Removing any gate fails
a row.

The UI mirrors the API: issuer controls (new bond, new auction, disable
bond, and the close, finalise, and cancel lifecycle actions) and the
Network Health Reconnect and Resync actions are shown only to accounts
that can operate. An unrecognised AUTH_MODE now fails closed: the UI grants
no capability and shows only a configuration error naming the value.
Docs carry the route matrix and the UI role behaviour.

Tests: jest 291 (39 suites), vitest 139 (27 files), all green.
Six plan folders under docs/plans/, each with intent, design, plan, and
progress files, grounded in a review of the current development branch:

- bond-coupon-maturity-correctness: per-holding coupon rounding, RATE
  auction gating after a cancel, matured partitions closed to new
  issuance, the BUYBACK cancel revert, and bond transfer eligibility.
- ingestion-projection-correctness: abortable ingestion loop that closes
  its connection, chain-ordered log application, idempotent bond state,
  newest-first history, and finalisation that tolerates invalid bids.
- nb-bond-api-hardening: per-signer nonces, one RFC 7807 error handler,
  liveness probe and chart defaults, scoped runtime image, cleanup.
- nb-ui-correctness-and-hardening: router and error boundaries, one 401
  path, matured KPI, live coupon preview, bidder soft delete, history
  card, nginx headers, dead-code removal.
- sandbox-scripts-and-ci-hardening: flag precedence, failing readiness
  timeouts, command validation, bid-tool install, loopback ports, image
  pins, pin-drift check, CI gaps.
- documentation-refresh: neutral deployment wording, plan-tree
  housekeeping, removed behaviour still documented, known issues,
  NatSpec source links, licence inventory.

All plans are Proposed; the documentation index lists them. Four progress
files note what changed on development after planning (#300 to #305)
and which plan steps that makes moot.
Land the five open Dependabot bumps at their newest patches and clear
every open Dependabot alert in one change. nb-bond-api moves to
express-rate-limit 8.7.1 and jose 6.2.13; nb-ui to React and react-dom
19.3.1 (react-dom has to move with react, which is why the bot PR for
react alone failed the UI suite) and vite 8.2.4; both workspaces take
globals 17.13.0 and prettier 3.9.10.

Security fixes use the smallest change on each package's current line:
vite 8.2.4 for the launch-editor advisories, the root undici override
to 8.10.2, both overridden brace-expansion copies to 1.1.21 and 5.0.12
(the 5.x advisories are reported by npm audit but have no alert yet),
and lockfile-only refreshes of ip-address 10.7.4, source-map-js 1.2.2
and the glob brace-expansion copy to 2.1.7. vite's rolldown range moves
rolldown to 1.2.14, which adds one optional Android ARM binding entry.

The lockfile is a classified transplant from a scratch resolve: only
bumped packages, required siblings, override-pinned and security
entries move, workspace ranges stay exact, and nothing is removed.
npm ci passes and npm audit reports 0 vulnerabilities (16 before).
jest 293 and vitest 139 pass, unchanged from baseline. The nb-ui main
chunk grows by about 9 kB gzipped, all of it upstream react-dom.

The plan folder is archived in the same change under
docs/plans/archive/dependabot-rollup-2026-10/.
The content-hash tags for nb-ui, nb-bond-api and bens-microservice did
not include the base images they build from, so a Node, nginx or Python
pin bump changed no hashed input: the build was skipped and the old
image kept running. Each hash now includes its stage base refs, resolved
through the same getters the build uses (Node and nginx for nb-ui, Node
for nb-bond-api, python for BENS).

check-image-hash-inputs.sh asserts the new inputs. The Node refs are
shell variables and are overridden in a subshell; the nginx and python
refs are read from common/images.yaml, so the check edits a throwaway
copy of that file and points IMAGES_CONFIG at it. Removing either line
from the hash makes the check fail.

This is slice 1 of the new image pin refresh plan
(docs/plans/image-pin-refresh/), added here and indexed. The plan
refreshes the image and chart pins Dependabot does not track, one slice
at a time, local sandbox only, with a per-slice note on node wipes,
contract redeployment and one-way data changes for a later non-local
rollout.

Verified: the hash-input check (11 assertions), node-version
consistency, Besu baseline, licence inventory, hygiene and links. On the
local sandbox all three images were rebuilt once under the new hashes
and their pods are Ready; the API, UI and BENS answer.
The NB UI image's nginx runtime base moves from 1.30.3-alpine to
1.30.6-alpine, the newest release on the stable line, in
common/images.yaml, the helpers fallback and the Dockerfile ARG default.
The two commands in the nb-ui hardening plan that run the pinned image
follow.

nginx 1.30.4 to 1.30.6 fix several CVEs. The NB UI serves the stock
nginx-unprivileged config: plain HTTP on 8080, static files, no HTTP/3,
TLS, AIO, SSI, alias or rewrite. So only CVE-2026-93479 (string
expressions with variables, which includes request logging) plausibly
applied, and real exposure was low. The image also moves from Alpine
3.23 to 3.24; its port, uid and temp-path layout are unchanged.

Slice 2 of docs/plans/image-pin-refresh/; progress.md records it and
marks slice 1 (#308) merged. Local sandbox only, and no node wipe,
contract redeployment or data change.

Verified: the newest 1.30.x tag is published for amd64 and arm64; image
hash inputs, node-version consistency, Besu baseline, licence
inventory, hygiene and links pass; nb-ui format, lint, tests and build
pass. On the local sandbox, registry-sync then nb-ui.sh start rebuilt
the image under a new hash from the slice 1 change. The pod reports
nginx/1.30.6, answers 200 through the gateway, and serves config.js from
its ConfigMap with the read-only root filesystem.
)

The explorer's supporting components move to their newest releases:
- postgres 18.4 to 18.6. 18.5 was never released. 18.6 fixes 28 CVEs,
  and a dump or restore is not needed within 18.x.
- python 3.14.5 to 3.14.8 for BENS: libexpat, tarfile, zipfile, ssl and
  urllib fixes. 3.14.9 has no image yet.
- blockscout-stack chart 4.5.1 to 4.5.3: Prometheus rule changes and a
  separateApi strategy option, neither used here.
- smart-contract-verifier v1.10.6 to v1.11.0.
Every copy of each pin moves, along with THIRD_PARTY_NOTES.

Verifier v1.11.0 defaults compiler execution to "disabled", and its
compiler-backed endpoints will not start without a mode. The deployment
now sets SMART_CONTRACT_VERIFIER__COMPILERS__EXECUTION__TYPE=native (solc
runs in the pod, as before; upstream's own compose uses the same).

Slice 3 of docs/plans/image-pin-refresh/; progress.md records it and
marks slice 2 (#309) merged. Local sandbox only, with no node wipe or
contract redeployment. Postgres upgrades in place on its existing
volume.

Verified: the newest tags are published for amd64 and arm64; chart
4.5.1 and 4.5.3 render identically with the sandbox values apart from
the chart label. Image hash inputs, node-version consistency, Besu
baseline, licence inventory, hygiene and links pass.

On the local sandbox, registry-sync then blockscout.sh start brought:
- Postgres up on 18.6 with all 12 verified contracts, then ANALYZE;
- BENS rebuilt on Python 3.14.8;
- the explorer API back to 200 about a minute after the database
  restart, with the latest indexed block at the chain head.
The new verifier recompiled Wnok from its stored standard-JSON input and
returned SUCCESS.
The gateway chart moves from 2.6.6 to 2.8.0 in common/versions.yaml,
the helpers fallback, and the NodePort Service's version label. The
Gateway API CRDs it installs move from v1.5.1 to v1.6.3, and the data
plane's nginx from 1.31.2 to 1.31.7. The gateway is the nginx reachable
from the LAN, and the 1.31.7 data plane carries the same fixes as nginx
stable 1.30.6. 2.8.0 needs Kubernetes 1.32 or later; the Kind node is
v1.32.11.

Helm installs a chart's CRDs only on first install and never upgrades
them, so an existing cluster kept 2.6.6's NGF CRDs and lacked 2.8.0's
three new kinds. deployApiGateway now applies NGF's deploy/crds.yaml at
the pinned release before the chart upgrade. It applies server-side,
because client-side apply fails on nginxproxies (its annotation exceeds
256 KiB). It uses --force-conflicts, because the first install left
Helm as the CRDs' field manager and Helm never manages them again.
infra/DEVELOPMENT.md documents the bump.

Slice 4 of docs/plans/image-pin-refresh/; progress.md records it and
marks #310 merged. Local sandbox only, with no node wipe or contract
redeployment.

Verified with server-side dry runs against the local cluster first,
then by running deployApiGateway on it:
- Gateway API CRDs are at v1.6.3, and all 14 NGF CRDs applied, including
  the three new kinds.
- The data plane settled on 2.8.0 within a minute. Controller errors
  came only while the old pod was still connected.
- The NodePort selector still matches, and the Gateway and all four
  routes are accepted.
- The UI, explorer, API health and Besu JSON-RPC all answer through
  127.0.0.1.
The gateway chart lints, and the hash, Node, Besu, licence, hygiene and
link checks pass.
Hyperledger Besu moves from 26.7.1 to 26.10.0 in common/images.yaml, the
chart fallback, the baseline checker's expected version, and the README,
architecture, infra and third-party notes. The three releases in between
fix several advisories, among them an EIP-7702 consensus split, a QBFT
decode DoS, QBFT withdrawals minting, and a non-validator peer halting
QBFT finality. 26.10.0 also takes Bouncy Castle and Jackson CVE fixes.
None of the options or genesis keys these releases remove are used
here, and discovery is off on both nodes.

26.10.0 upgrades Bonsai databases on first start; here that is only the
validator. The plan documents the rollback:
- run besu storage revert-code-format on each Bonsai node, which was
  rehearsed locally;
- a Forest node also needs one old-version start with version
  protection off, which was not rehearsed because it disables a safety
  check.
Neither the upgrade nor the rollback needs a node wipe or a contract
redeployment.

Slice 5 of docs/plans/image-pin-refresh/; progress.md marks slice 4 (#311) merged.
progress.md records the run. Local sandbox only.

Verified on the local sandbox:
- In place, both nodes run 26.10.0, the validator upgrades its
  database, and a contract creation is mined with to: null and indexed
  by Blockscout and the NB Bond API.
- After revert-code-format the validator on 26.7.1 sealed a block with
  a transaction; both nodes then returned to 26.10.0.
- A full delete and start built a fresh 26.10.0 chain (chain 2018) and
  redeployed the contracts, which all verified through the new
  verifier. That run also covered slice 4's fresh-cluster CRD path.
The Besu baseline check, helm lint and template for infra/besu, and the
hash, Node, licence, hygiene and link checks pass.
Move the shared Node pin in common/node-version.env from 26.5.0 to
26.12.1. The old pin predates two security releases on the 26 line
(26.5.1 and 26.12.1). @types/node moves from 26.5.1 to 26.6.5, the
newest types line, in nb-bond-api, bid-encryption and bid-submitter,
with the three inventory rows. The lockfile changes only that entry
and the three workspace ranges.

The image cache-key fix this needs, so the pin bump rebuilds the
repo-owned images, landed separately in #308. This change adds the base
image refs to the hash-input lists in the nb-bond-api README and the
nb-ui development guide, which #308 did not touch. It also refreshes
the node image example in infra/DEVELOPMENT.md and points the
nb-bond-api hardening plan's slim-image steps at 26.12.1-slim, with a
dated note in its progress file.

On Node 26.12.1: jest 293 and vitest 139 pass, unchanged from
baseline; both CLI workspaces typecheck; npm audit reports 0; the
node-version, image-hash, licence, Besu baseline, hygiene and link
checks pass. Live check: both images rebuilt from node:26.12.1 under
new hashes; the API pod reports v26.12.1 and better-sqlite3 loads; the
read endpoints return the same counts as before the redeploy.

The plan folder is archived in the same change under
docs/plans/archive/node-26-12-pin-bump/.
Move nb-ui's test runner from vitest 4.1.11 to 4.1.12, the first
release patched for GHSA-qxg6-89mg-rqv8 (code injection via
unauthenticated manual mock registration) and GHSA-xp26-3454-mjgp
(Windows backslash traversal past the mocker redirect allowlist). Both
are moderate, dev-only, and were reported by npm audit before GitHub
raised alerts for them.

The lockfile is a classified transplant from a scratch resolve: vitest,
its seven exact-pinned @vitest/* packages, and the nb-ui workspace
range. The resolve also lifted chai and tinyrainbow, but their declared
ranges are unchanged, so those incidental moves are left out.

npm ci passes and npm audit reports 0 (2 before). nb-ui format, lint,
vitest 139 and build pass, and nb-bond-api lint, format, jest 293 and
build pass, all unchanged from baseline. The licence, node-version,
hygiene and link checks pass. The plan folder is archived in the same
change under docs/plans/archive/vitest-4-1-12/.
…315)

In October 2026 the private keys of eight eMudhra intermediate CAs
became derivable from their certificates. Mozilla removed emSign ECC
Root CA - G3 in NSS 3.131.1, and Debian also distrusted AAA Certificate
Services. Any trust store still holding either root accepts the
compromised intermediates.

Contracts CI moves Foundry from v1.7.1 to v1.8.6, which ships
rustls-webpki 0.103.16 and webpki-roots 1.0.10; CI downloads solc and
Soldeer dependencies over TLS. The BENS build and runtime base moves
from python:3.14.8 to 3.14.9. It bundles pip 26.2.2, whose vendored
certifi 2026.10.19 drops the root; pip 26.2.1 still trusted it next to
the system store during the image's pip install. The system bundle is
Debian ca-certificates 20250419+deb13u1. The Node pin (26.12.1)
already carries the NSS 3.131.1 roots.

docs/KNOWN_ISSUES.md records the third-party images that still trust
the root and are waiting on upstream releases. The one that matters
most is the smart-contract verifier, which downloads Solidity compilers
over TLS through an Ubuntu bundle that still holds it.

Verified: python:3.14.9 and a BENS image built from it contain none of
the affected roots; forge 1.8.6 passes soldeer install, fmt --check,
build --sizes, the verify-mapping check and 355 tests; the hygiene,
link, licence, node-version, Besu baseline and image-hash checks pass.
@Greaker
Greaker requested a review from a team October 22, 2026 13:17
@Greaker
Greaker merged commit 5856821 into main Oct 22, 2026
12 checks passed
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.

1 participant