Never let transferred state arrive with code already enabled - #5
Open
neoliminal wants to merge 1 commit into
Open
Never let transferred state arrive with code already enabled#5neoliminal wants to merge 1 commit into
neoliminal wants to merge 1 commit into
Conversation
applyImportedCardCode documents the rule and enforces it on the character
card file import: the decision to run code belongs to the person at the
keyboard and cannot be carried in a transferred blob. Two other paths
reach the same executors and had no such gate.
restoreFromServer() parses the /state blob and writes it straight into
IndexedDB or localStorage, then reloads; boot then calls applyExtensions()
and applyCardCode() against it. /state is password-gated, but it is a
single shared document every paired device can overwrite, so a
compromised phone -- or anyone who learned the shared password -- could
hand every other device a card with customCodeEnabled already true, or an
extensions list whose raw scripts get appended to <head>. The restore that
fires when local storage is empty (a new LAN IP, a cleared cache) does not
even prompt first.
importAllData('all') has the same shape: it assigns characterCards and
extensions from an arbitrary backup file and calls applyExtensions() in
the same tick, behind a confirm() that only mentions replacing data.
Add neutralizeUntrustedCode() next to applyImportedCardCode and call it on
both paths. Code text is preserved so it can still be read and opted into;
only the enable flags are cleared, and the user is told rather than it
happening silently. Restores are rare -- a new device, a cleared cache, a
quota recovery -- rather than part of the routine push, so the cost is
re-ticking a box once per device, which is what the file-import path
already asks for.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 20, 2026
ElodineOfficial
added a commit
that referenced
this pull request
Aug 21, 2026
@neoliminal - pin SHA-256 of engine + embed model (#8), strip run-flags from transferred state (#5), sanitize GGUF metadata reaching generated .cmd (#3), escapeHtml fallback (#4), firewall / URL ACL scoping (#7). @wizzense - fall back to loopback when the LAN bind is denied (#10). @jmccardle - Linux wine catch, roadmaps for Linux (#2). @DawidKorach - stable port assignments, llama health registration in the CMD (#14, #15). Co-authored-by: neoliminal <john.kipling.lewis@gmail.com> Co-authored-by: wizzense <37890504+wizzense@users.noreply.github.com> Co-authored-by: John McCardle <mccardle.john@gmail.com> Co-authored-by: Dawid Korach <dawidk6@gmail.com>
Contributor
|
I recommend closing this PR - jmccardle#5 (comment) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The problem
applyImportedCardCode(js/23-card-code.js:301) documents the rule and enforces it on the character-card file import: the decision to run code belongs to the person at the keyboard and can't be carried in a transferred blob. Two other paths reach the same executors and had no such gate.restoreFromServer()(js/06-state-sync.js:376-381) parses the/stateblob and writes it straight into IndexedDB or localStorage, then reloads. Boot then callsapplyExtensions()(js/24-boot.js:43) andapplyCardCode()(:46) against it.state.extensions.scripts[].rawgets appended to<head>as a script element (js/19-extensions.js:70);card.customCodegoes throughnew Function(js/23-card-code.js:204), gated only bycustomCodeEnabled, which survives the restore intact./stateis password-gated, so this needs an authenticated peer — but it's a single shared document every paired device can overwrite. A compromised phone, or anyone who learned the shared password, can hand every other device code that runs at next boot. The restore that fires when local storage is empty (js/06-state-sync.js:265) doesn't prompt at all, which is exactly the state of a phone hitting a new DHCP address or a freshly cleared browser.importAllData('all')(js/21-data.js:122-157) has the same shape: it assignscharacterCardsandextensionsfrom an arbitrary backup file and callsapplyExtensions()in the same tick, behind aconfirm()that only mentions replacing data.The fix
neutralizeUntrustedCode(), added next toapplyImportedCardCodeand called on both paths. Code text is preserved so it can still be read and opted into; only the enable flags are cleared, and the user is told rather than it happening silently.On the cost
Restores are rare — a new device, a cleared cache, a quota recovery — rather than part of the routine push, which is a PUT from this device and unaffected. So the practical cost is re-ticking a box once per device, which is what the file-import path already asks for.
I considered preserving the flag when the incoming code is byte-identical to what's already enabled locally, which would remove even that cost. It's more moving parts for a case that only comes up on a device that already has the card, so I went with the simpler rule — happy to switch if you'd prefer.
Testing
Unit-tested: enabled cards are disabled and their code kept, the extensions list is disabled and its scripts kept,
nulland{}are handled, and the call lands beforesaveState()/applyExtensions()on the import path.Found during a security review.