From 28b1c3c9d33312cc0b431a1e51dbd63226fad2c6 Mon Sep 17 00:00:00 2001 From: Dan Connolly Date: Tue, 13 Oct 2026 21:05:44 -0500 Subject: [PATCH] capsite/toybox: no encoder object in module scope A module-level TextEncoder is a mutable object every importer shares. Keep only its encode method, bound and hardened; the encoder itself is unreachable. The code hash uses it too. Co-Authored-By: Claude Opus 5.5 (1M context) --- toybox/supervisor.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/toybox/supervisor.js b/toybox/supervisor.js index 2294776..17eaa38 100644 --- a/toybox/supervisor.js +++ b/toybox/supervisor.js @@ -55,7 +55,7 @@ export class Vat extends DurableObject { * @returns {Promise} the code's SHA-256, hex */ async start(src, egress, kind = "app") { - const hash = hex(await crypto.subtle.digest("SHA-256", new TextEncoder().encode(src))); + const hash = hex(await crypto.subtle.digest("SHA-256", encodeUtf8(src))); const { sql } = this.ctx.storage; sql.exec("INSERT OR IGNORE INTO bundle VALUES (?, ?)", hash, src); sql.exec("INSERT OR REPLACE INTO current VALUES (0, ?)", hash); @@ -164,7 +164,8 @@ export class Egress extends WorkerEntrypoint { const VAT_ADMIN = "vatAdmin"; /** The swissnum of an OCapN vat's bootstrap (see vat-runtime.js). */ const VAT_SWISSNUM = "vat"; -const encoder = new TextEncoder(); +/** UTF-8 bytes of a string: the method alone, so no encoder object is in module scope. */ +const encodeUtf8 = harden(TextEncoder.prototype.encode.bind(new TextEncoder())); /** * The vatAdmin: an OCapN peer, and the router through which every OCapN @@ -209,7 +210,7 @@ export class VatAdmin extends DurableObject { #enliven(designator, swissnum) { return this.ocapn.enlivenSturdyRef( - this.ocapn.makeSturdyRef(locationOf(designator), swissnumFromBytes(encoder.encode(swissnum))), + this.ocapn.makeSturdyRef(locationOf(designator), swissnumFromBytes(encodeUtf8(swissnum))), ); }