An extension was require()d into the browser's main process. Its declared
capabilities bound only an honest one: there it could load electron, hook
the unlock prompt for the master password, read the vault files and the
wallet's store, call the OS keystore, and reach every tab.
Extensions that do not ship with Theseus now run in a separate process, in
Node mode under Node's permission model: read access to their own folder,
write access to a scratch folder, no child processes, no workers, no native
add-ons, no Electron, and none of the browser's memory. They reach the
browser only through an allow-listed message API that calls the same
functions, with the same capability checks, as before. Built-in add-ons are
unchanged and stay in-process.
For extension authors: host calls return promises (tabs.active included);
api.require / api.import are gone, so dependencies must be bundled;
registerRequestFilter and registerSiteRoute are not offered; the store is
handed over at start and written through, so storage.get stays synchronous.
An approval raised while handling a page message is still tied to that
page's tab. If the sandbox cannot start, the extension does not run.
The channel is JSON with tagged bytes: the binary IPC format depends on the
exact V8 build on both ends.
Start of the next batch; 0.32.1 is published.
Bitcoin inputs were given the BIP125 sequence that opts in to
replace-by-fee. Aegis does not replace transactions on any chain and has
no way to bump one, so advertising a payment as replaceable only told the
recipient to distrust it until confirmed. Inputs are final again.
Since Theseus 0.3.80 the vault PIN can be 6 to 8 digits, but Aegis's
pads still submitted at six, so anyone with a 7- or 8-digit PIN got
"wrong length" from every Aegis prompt and had to use the master
password. The lock-screen, transaction and reveal pads now draw as many
dots as the host reports (pinLength) and submit at that length; a
wrong length is explained instead of shown as a wrong PIN. Aegis's own
PIN on older hosts stays at six.
- The password manager offers to save, and fills, logins in sign-in forms
embedded in iframes (payment providers, single sign-on widgets), keyed to
the frame's own site.
- PIN pads: Unlock (or Enter) beside Cancel instead of submitting on the
last digit; a new PIN is any 6 to 8 digits with Next; larger keys; calmer
hover so the pad no longer seems to flash as you type.
A PIN can be 6, 7 or 8 digits, but the pads submitted on the last expected
digit, and setting one meant first picking its length. Now:
- The unlock prompt fills the dots for the PIN's length and sends it with
Unlock (or Enter) beside Cancel, so a slip can still be fixed with the
delete key first.
- Setting a PIN takes 6 to 8 digits with Next (the last two dots are marked
optional), and the repeat step saves with Save PIN once it has as many.
- Keys are larger (76x56, 24px digits) and the dots a little bigger.
Sign-in widgets and embedded checkouts often put the login form in an
iframe, and the password hooks only ran in a tab's top frame, so those
logins were never offered for saving or filling.
- Web tabs now run preloads in iframes (nodeIntegrationInSubFrames; pages
still get no Node). Only the password hooks act there: the home-page
bridge, window.bcnr, add-on page scripts and window.theseusId return early
outside the top frame, exactly as before.
- A login in a frame belongs to the frame's own site, taken from that
frame's committed URL. The save prompt says "login.example (in a frame on
shop.example)", the fill offer names both, and the fill goes into that
exact frame only while it is still that tab's and still on that site.
- The "did the login go through" check runs against the frame.
Verified with a shop page embedding a cross-site login frame: save offer,
fill offer and fill all target the frame; the outer page gets nothing;
top-frame logins behave as before.
Moving the pointer across the keypad lit each key it crossed with a bright
acid border and digit, and every filled dot glowed, which read as the pad
flashing while a PIN was typed. Keys now only lighten slightly on hover and
press in a little when clicked; dots fill without the glow.
0.3.80 shipped with the Settings left menu squeezed into a short centred
box (a .app class clash from the Apps list, fixed in 882b6fb). This is
0.3.80 plus that fix.
The Apps list styled its rows with .app, the class the Settings page itself
uses for its layout. The later rule won on the page too: align-items:center
pulled the left menu into a short box in the middle of the window, with a
border, padding and rounded corners it never had. The rows are .webapp now.
- Theseus ID: window.theseusId.signIn lets Silent Mode projects (and sites
whose own origin list allows it) sign the user in with an ID derived from
the vault: private per project by default, or one shared ID. Settings ›
Theseus ID lists the projects, changes or renews an ID, revokes, and shows
the recovery key. The login service is https://id.theseus.x.
- BNS sites on an ip record get the page's own POSTs, headers and cookies,
so forms and sign-ins on hephaestus.x and id.theseus.x work.
- Vault: PINs of 6 to 8 digits set up step by step on a keypad; the unlock
prompt is centred and its keypad no longer jumps under the cursor.
- Logins: after a sign-in or sign-up, Theseus offers to save the password
(optionally behind a PIN check), offers saved logins on login forms, and
keeps sign-ins for vault sites when cookies are cleared on quit.
Export JSON… gives scripts and agents one file with endpoint, key, secret
and drive. The desktop app now ships for Linux too (AppImage, .deb,
tar.gz), using per-user s3d paths there.
Quick links sat in a small block under General, and the panel behaviour
added this week (per-app widths, pins, the Opera-style auto-hide) had no
place in Settings at all. Settings › Sidebar now holds the strip switch,
the web apps (drag to reorder like Search engines, pin, reset a remembered
width, remove), the extension panels on the strip (show or hide each one,
pin, reset width) and three switches for when the open panel steps aside:
a new tab, the right side panel opening, a click in the page. Apps gains
"Add to sidebar", so an installed site can also open in the strip.
Theseus fetched pinned ip-record sites with a bare GET whatever the page
asked for, so forms and JSON POSTs on sites like hephaestus.x or
id.theseus.x never reached the server, and redirects and cookies were
dropped on the way back. Requests now carry the method, body and the
headers a site needs (content type, auth, its own cookies, x-* headers),
with the page's bns:// origin presented as https://<name>, the mapping
Theseus already uses for that origin; responses keep Location and
Set-Cookie.
The vault prompt rebuilt its whole dialog on each digit, which replayed the
pop-in animation, and because the dialog is centred, every change in the
message line's height moved the whole pad under the user's finger. Presses
and answers now update the dots and message in place, the message lines
keep a fixed two-line height, and switching between PIN and password clears
a half-typed PIN. The Settings PIN dialog keeps the 6/7/8 row's space on the
repeat step so the pad stays put between steps. Measured on a scratch
profile: key positions identical across presses, errors and steps.
Ships 0279b91 (the left panel resizes from its edge, per app, and hides
when a new tab or the right sidebar opens), d113c6d (a page click closes it
unless the app is pinned) and 6a2f6fe (an installed web app can be closed
and removed; Settings lists apps).
Opera's other sidebar rule. A click in the active tab's page now hides the
left panel (web app or add-on panel), so a chat opened for a quick look
gets out of the way on its own. A pin button in the strip, shown while a
panel is open, keeps that app open instead; pins are per app and survive
restarts (settings.leftPanelPinned). The click is reported by
home-preload, which every web tab already runs; main ignores it unless a
panel is open and the click came from the active tab.
Web apps in the left panel were a fixed 380 px, too narrow for WhatsApp,
and add-on panels a fixed 400. A grip on the panel's right edge now
resizes it, each app keeps its own width (settings.leftPanelWidths), a
double-click resets it, and the page always keeps at least 320 px. The
grip is a small view of Theseus's own laid over the edge, since the panel
may be a third-party page; while dragging it covers the window so the drag
keeps its events.
Like Opera's sidebar, the left panel now hides when a new tab is opened or
the right sidebar opens. It is only hidden, so reopening lands where the
user left off.
Add-on panels on the left used to get the right sidebar's grip on their
left edge, where dragging resized the right sidebar; that grip is hidden
there and its drags are ignored.
Pages of Silent Mode projects can now sign the user in with their Theseus
ID instead of a wallet phrase typed into the page. Theseus writes the
sign-in message itself, takes the origin from the committed top frame, and
signs as a project only on an origin that project's list includes, so a
phishing page cannot get another project's signature and no page can use
the ID key to sign anything else.
- lib/theseus-id.cjs: the policy (first sign-in always asks and lets the
user pick a private or One ID; Silent Mode projects are silent after
that while the vault is open; per-site "always"; 10 silent signatures per
minute per origin), the per-project record encrypted under a key derived
from the vault, origin-list fetching with a 1 h cache and a 7-day stale
fallback, and ID moves that send a proof signed by both keys and only
finish once the project confirms.
- A locked vault is unlocked only for a page the user just clicked or typed
in: navigator.userActivation alone is true on load for pages opened with
loadURL, which would let a page pop the vault prompt by itself.
- Settings › Theseus ID: default mode, One ID, automatic sign-in toggle,
signed-in projects (always, change ID, new ID, revoke) and a recovery key
behind a fresh PIN / password check.
- TheseusID/registry/projects.json is the first-party list (Hephaestus,
Sirius, Pithos); it and TheseusID/lib ship as extraResources.
- Token-aware cashaddrs (BNS owners) now decode for owner-signed lists.
Verified on a scratch profile against a local test project whose server
checks signatures with TheseusID/lib/verify.mjs: locked vault on load gives
"locked" with no prompt, first sign-in prompt, silent second sign-in, a
claimed foreign project refused without a prompt, an ID move that keeps the
project's account, and the recovery key behind the confirm prompt.
The file opened with the 0.0.2 hashes and rollback points from August, which
reads as the current state to a session skimming it. Releases live in the
manifest and in git history; the collision-policy group below is still
accurate and stays.
The PIN could only be six digits and was set from three bare inputs; the
unlock prompt sat at the top of the page; and the password manager only
filled when you found the key chip, never offered to save, and "Clear
cookies on quit" signed you out of every site, including the ones whose
login the vault already holds.
- PINs are 6 to 8 digits. The PIN record stores its length so pads draw the
right number of dots and submit on the last digit; a PIN of the wrong
length is refused without a strike, so an older Aegis pad cannot burn the
count against an 8-digit PIN.
- Settings sets a PIN in steps: master password, choose the PIN on a pad
(6/7/8), repeat it, done. The locked vault opens Theseus's own prompt,
which is now centred, with the PIN pad or the master password field.
- After a sign-in or sign-up form is sent and the page moves on, Theseus
offers to save (or update) the login, with an optional "ask for my PIN or
password before filling it". Focusing a login form offers the saved
logins under it; on a locked vault it offers to unlock first. A failed
login (the password field still showing) gets no offer.
- "Keep sign-ins for sites in your vault" (on): the quit clear spares the
cookies and site storage of sites with a saved login. Their hostnames are
kept sealed with the OS keystore so the list is readable at quit while
the vault is locked. Verified end to end on a scratch profile: signed in,
restarted, still signed in; another site's cookie was cleared.
An app whose page has a beforeunload handler (CoinSpectrum) could not be
closed: tabs answer the page's "stay?" request, app windows had nothing
listening, and Electron reads silence as a veto. Closing the window is now
always the user's call; a reload or navigation inside the app asks, as a
tab does.
Removing it looked dead for a related reason. The confirmation was drawn
as Theseus's sheet in the main window, where the user was not looking (or
nowhere, with the main window closed), and the removal then closed the app
window with the same call the page vetoes. The question is now asked on
the app window that asked, and removal destroys the window.
Installed apps were only reachable from the address-bar chip while on the
site. Settings gets an Apps page, at theseus://settings/apps, that lists
them with Open and Remove and follows installs, removals and open windows.
The three list calls it uses are settings-only now.
Settings > Plug-Ins > Ariadne's Thread now switches the daemon on and off
on every install scheme. The toggle used to look for a scheduled task
named 'BNS Resolver Daemon' (and 'BNS Sia Bridge') -- the compat names
from before Ariadne 0.2.0. A fresh 0.2.0 install under the Just-me scope
registers the daemon as 'Ariadne BNS Resolver (<user>)' instead, so the
toggle silently did nothing. The handler now walks every scheduled task
whose name matches the Ariadne pattern -- the pre-0.2 compat names,
the 0.2.0 All-users names and the Just-me scoped names -- and starts or
stops each one. Unrelated 'BNS Indexer'-style tasks on the box are left
alone.
Files in your own Sia account can now be shared with anyone through a
navigate.st/sia link that keeps working with this computer off; the
navigate.st service is live, so the buttons can ship.
User report (2026-10-04): "Plug-Ins/Ariadne's Thread is not working
properly, it doesn't switch on / off the daemon. Other PC can't turn it
on, and other browsers on it don't recognize BNS names."
Root cause. ariadneSetState's elevated script hard-coded two names,
'BNS Resolver Daemon' and 'BNS Sia Bridge'. Those are the pre-0.2 names
and the All-users / All-browsers compat names. A machine that received a
fresh 0.2.0 install under Just-me scope has NO task with either of those
names -- the resolver there is 'Ariadne BNS Resolver (<user>)' and the
indexer is 'Ariadne BNS Indexer (<user>)'. The toggle silently did nothing
(exit 2 handling returned 'no BNS Resolver Daemon'), and the panel's
state query -- which already knows about 'Ariadne BNS Indexer' -- still
displayed the previous state because it was reading the correct task but
nothing had actually started it.
Fix. The inner script now enumerates scheduled tasks dynamically and
anchors a regex at:
^(Ariadne BNS Indexer
|Ariadne BNS Indexer \(.+\)
|Ariadne BNS Resolver \(.+\)
|BNS Resolver Daemon
|BNS Sia Bridge)$
That hits every Ariadne task across all three install schemes without
touching the user's own unrelated 'BNS Indexer' task (which has no
'Ariadne' prefix -- memory://ariadne-task-names). Enable/Start (or
Stop/Disable) runs against each match; exit code is:
0 = at least one task accepted the verb
2 = no Ariadne task found at all (needs reinstall)
3 = tasks found but none accepted (surface in a clearer error)
Note. Full 0.2.0 integration (named-pipe IPC via Argus/src/lib/bns-pipe.js,
scope-aware policy.json path) ships via step 3 on branch
claude/agitated-bell-bd4ac2 (see TheseusNavigator/DESIGN-bns-indexer-service.md).
This commit fixes the specific toggle bug on master so the user's "can't
turn it on" symptom is resolved WITHOUT waiting for that larger merge.
The 0.1.13 settings-panel sections (collision policy / sources /
status-report via http://127.0.0.1/api/status) still display 'Daemon
unreachable' honestly on 0.2.0 installs; they'll be superseded by the
pipe-based status feed when step 3 lands.
The blocklist consumer existed in the resolver library and the gateway, but
the browser opened a flagged name without comment. Now the indexer process
reads the subscribed lists from the chain every ten minutes and hands the
flags to main. A flagged name loads a warning page naming the reason, the
list and the report; the user may continue, and that is remembered per name.
Two gates, because content is reached two ways. loadBns shows the real
interstitial. serveBns refuses with an inline page on every path that skips
it: reload, back and forward, bns:// links, web app windows. The inline page
has no button, since a page at the site's own origin must not be able to
approve itself; only blocked.html may ask to continue, checked by file URL.
Settings › Naming has the policy: warn (default), never open, or ignore the
lists. The csam reason is never offered a way through. A list that cannot be
read keeps the last known flags and never stops a name from resolving.
The gateway put its own warning in front of flagged sites, which this
browser could not get past: it fetches files itself, with no cookie jar. It
now sends x-bns-policy: client and the gateway stays out of the way for a
client that says it decides for itself.
dev/blocklist-selftest.js runs the real protocol handler and decision
functions against a scratch profile.
Applying an extension update without a restart swapped the files and
restarted the add-on, but a panel or tab that was already open kept
running the previous version's page, so the extension looked unchanged
until Theseus restarted (Pithos showed its old layout this way). The
add-on's open left panel, sidebar panel and tabs now reload after the
swap. Extensions can also apply their own staged update with
api.applySelfUpdate(), so their own update button needs no restart;
plug-ins such as wallets still update on the next launch.
- Settings → Language gets a Translator engine radio: pick between
LibreTranslate (hosted — current default) and Bergamot (on-device —
WASM engine, bundled in a later release; falls back to LibreTranslate
for now with a one-time notice). See bergamot.x for the plan.
- Bundles Pithos 0.3.13: menu reflows in setup order with the account on
top, files waiting to upload are shown and go up on their own, s3d
one-shot commands never open the database at once.
- Error page handles Cloudflare Bot Fight 503s: branded page explains the
Electron TLS fingerprint and offers a one-click retry to a .bch mirror
when one is published (path preserved).
- Fixes did-fail-load suppression: a cert error during the HTTPS fallback
now surfaces the branded error page instead of being swallowed.
These three commits were pushed to the forge's theseus and site repos
from their branch but never reached master, so pushing master would
have dropped them there. Merged so master carries them: the branded
error page on BCNR-to-clearnet fallback failures, the Cloudflare Bot
Fight 503 hint with a .bch mirror suggestion, and the site's docs page
for the p-record reverse-proxy workaround.
A new row in Settings › Language, Translator engine: a radio between the
hosted LibreTranslate backend (the current default) and the on-device
Bergamot engine. The chip and auto-translate route through a single
dispatcher (translatorCall) that picks the provider by setting; adding
or removing an engine only touches the dispatcher.
Bergamot's WASM runtime and model manager are not bundled yet, so the
Bergamot provider falls back to LibreTranslate for now and prints a
one-time console notice. The setting itself is real today — a user can
declare their preference and the real engine lands in a later release
without the user touching Settings again. See bergamot.x for status.
The wiz:// page scan ran in the page's own world, where the page can
replace RegExp, querySelectorAll or Set and shape what the wallet is
handed; it now runs in an isolated world of its own and the results
are checked again in main. The extension install sheet now says what a
community extension can reach while the vault is unlocked (passwords
and the wallet), since extensions still run in the main process.
Approvals were one global queue drawn over whatever tab was in front,
so a background tab could time a request to pop up while the user was
in the middle of something on a trusted dapp, and a request outlived
the page that made it. The host now carries the tab of a page message
through the add-on's handler (AsyncLocalStorage across its awaits); an
approval from a page shows only while its tab is in front, hides and
comes back re-armed when the user switches tabs, and is cancelled when
the tab closes or navigates. Approvals from a panel or from
WizardConnect have no tab and behave as before.
Stores nothing reads any more kept whatever they held when they were
copied: the pre-rename addons-data/ folder, the bchwallet.json left by
the Aegis absorb, and parse-failure copies. For Aegis before 0.31 that
could include the master password behind only an unsealed 6-digit PIN
and the stay-unlocked blob. Those two keys are removed from such copies
at startup; nothing else in them is touched, and the live store is not
among them. extensions-backups/, which kept a copy of an add-on on
every reseed and update forever, is pruned to the newest three per
add-on.
Reserved ids came from the current bundle only, so an add-on dropped
from a later release became an id anyone could publish under, and the
newcomer inherited its extensions-data store and vault.derive
namespace. Every id that has shipped is now reserved permanently.
A community install strips category and absorbs and asks the user, but
an update of the same extension was staged and promoted verbatim, so
version 2 could claim first-party placement or quietly add
capabilities and page-inject origins. Publisher-signed updates now get
the same manifest rewrite, and one that asks for new capabilities or
new pages is not staged; the user approves it by reinstalling from
theseus.x.
The right-hand panel had neither a will-navigate nor a window-open
handler, so a link in a panel navigated the privileged view itself to
a remote page that kept sidebar-preload, and window.open made a bare
window with it; panel events were routed by the selected panel id, so
Aegis's state (every address, balances, WizardConnect sessions) would
then have reached that page. Both panels now open web links as tabs and
refuse to navigate away from file://, and events go only to a view that
has the add-on's own page loaded.
The approval box had no height limit inside a fixed mask, so a long
message or many rows pushed the end of the text and the buttons out of
view. The box now scrolls, long values scroll in place, and the
buttons stay pinned at the bottom.
wallet-imports-signer hands out raw seeds and WIFs while the vault is
open, and hermes-* sends and reads the user's Nostr messages; none of
these handlers checked who was asking. No preload exposes the
wallet-imports channels (add-ons use the vaultImports shim), so they
are now Settings-only like the password channels; the Hermes channels
answer only the Messages window.
A dapp could request a signature and then call alert(): the page's
sheet was raised over the approval, and closing it focused the page
again while the approval's buttons were already armed, so a
double-click on the sheet's OK landed on Approve. Page dialogs and
Theseus's own sheets are now held until the approval or unlock prompt
is answered, and nothing hands focus to the page while one is open.
bcnr.installExtension is in every page's main world, and install links
were honoured from any page and any frame, with no user gesture. Any
site could raise the install sheet timed so that a double-click landed
on Install, whose two buttons are always in the same place; an
installed community extension runs in the main process at once. The
call and the links now work only from theseus.x's top frame, the call
needs a real click (checked in the isolated world), and Theseus's own
sheets ignore every choice except Cancel for 800 ms, like the approval
overlay.
On a host with api.vault.pin, Aegis no longer keeps a PIN of its own:
its lock-screen, reveal and transaction pads send the digits to the
Theseus vault PIN, which is checked in main against the one strike
counter Settings, the unlock prompt and Pithos also use. The vault is
opened there, and the panel receives a single-use proof (two minutes)
where it used to receive the master password; vaultUnlock, revealSecret
and pinGateSatisfied accept it, still bound to the request it was
entered for. The master password no longer passes through Aegis or its
panel for a PIN entry.
An existing Aegis PIN moves to the vault PIN on its first correct entry.
If Theseus already has a different PIN, the user is asked once which
one to keep. Setting and removing the PIN act on the vault PIN, and
Settings says that it is shared. Hosts without the API (Theseus
0.3.74-0.3.76) keep Aegis's own PIN exactly as before.
Theseus and Aegis each wrapped the same master password under their
own PIN: two offline targets, two guess budgets, and two PINs to keep
in step. The vault PIN is now the only one. Built-in add-ons get
api.vault.pin {status, unlock, set, clear} (advertised by
features.vaultPin); unlock(pin) opens the vault in main and answers
only { ok } or why not, so the master password stays in main.
The policy is the one Aegis's PIN screens describe: five wrong PINs
lock the PIN for 15 minutes, every further wrong one locks it again,
and the master password always works. The unlock prompt uses the same
PIN pad and the same wording as Aegis, and Settings says so.