Repository navigation
Artemis HTTP transport: save access without an ns2jb session - #9
Merged
Merged
Conversation
Add tools/transports/artemis-http.cjs and tools/artemis-http.cjs: a transport that uses only the installed launcher's four upstream endpoints and needs no ns2jb session. - status/health (Lua 5.1, n2h.platform), eval (status:error is a failure), stat, bounded hex reads, uploads, delete with read-back, discovery; - downloads: chunked offset reads, byte-count/hex validation, per-chunk and final SHA-256, resume from a verified .part state, local atomic promotion; - uploads: direct /save under the body cap, chunked /eval append above it, explicit overwrite authorization, protected-file refusal on client and server. os.rename is refused on hardware, so --atomic fails closed and chunked writes are documented as non-atomic; - backup is explicit-file with scope=explicit-files and complete=false: the server has no listing, so it cannot authorize a destructive restore; - kit gains --http <ip> and a menu that exposes only this transport's operations; the RPC flow is unchanged. Hardware (Switch 2 23.0.1, Seedsow demo 1.0.0): status, Hello World, Lua 5.1/Ounce, text + 4 KiB/16 KiB/64 KiB binary transfers with matching SHA-256, protected/traversal/missing refusals, delete+read-back, relaunch and reconnect with data intact, explicit-file backup, and a legacy migration using that backup (original baseline unchanged). Tests: tests/test_artemis_http.cjs (20). Docs: docs/ARTEMIS-HTTP.md and docs/artemis-http-2026-11-01.txt.
…tp-transport # Conflicts: # README.md
ArtemisHttp.upload() returned the hash of the PC-side buffer, which only proves what was sent. It now reads the remote file back through /eval and compares its SHA-256 with the source before reporting success: - default verify=true; the returned sha256 is the verified remote hash and localSha256 is kept separately; --no-verify marks the result unverified; - EHASH on a remote mismatch, with the partial size; - a failed PUT/append is wrapped as EINCOMPLETE with the partial byte count, and is attempted once (write requests are never retried); - atomic uploads verify the temporary copy before rename; os.rename remains unavailable on hardware, so --atomic still fails closed. Tests grow to 24 (remote-hash verification, corrupted upload, interrupted chunked upload, no-retry write). Hardware: 4/16/64 KiB uploads all reported remote-verified hashes, downloaded byte-identical, deleted with read-back. Docs and transcript updated.
Rename probe (P2): fixed .ns2http-probe-a/b names could collide with files left by an earlier run or another tool, and cleanup deleted names without proving ownership. The probe now: - uses two 128-bit random scratch names and refuses if either exists; - writes recognizable content and reads it back before treating a file as disposable; - cleans up only files whose content still matches a probe payload, leaves anything else untouched and reports it (left/cleanupError); - runs cleanup in a finally block so a timeout after a successful upload is handled; reports name collision instead of deleting; - makes no race-free claim: the HTTP API has no exclusive create, so the pre-check plus random names is the available mitigation. Path validation (P2): saveName()/anyName() accepted '.', '..' and any 'dotdot' string, and only some operations used them. One shared validator now rejects empty names, '.', '..', any '..', separators/backslashes, NUL and control characters, unsupported characters and leading '/' (absolute), and is applied to stat, readChunk, download, upload, putRaw, appendRaw, renameRaw, remove and backup. Reads may inspect protected files; writes refuse them. Tests: 35 HTTP tests (was 24) covering probe success/unsupported/collision/ failed upload/timeout/interrupt/corrupt destination/cleanup failure/ consecutive/concurrent, validator acceptance/rejection at every entry point, and no-request-before-rejection. Hardware: randomized probe returned left:[], cleaned both scratch names, path refusals local, 4/16/64 KiB verified round trips, relaunch reconnect.
dougchansan
changed the base branch from
feature/alula-artemis-launcher
to
main
November 2, 2026 21:01
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.
What this adds
ns2-savekitcan now talk to the installed Artemis Launcher over its own HTTPserver on port 8080, with no ns2jb RPC session. The launcher and its
protected files are not modified; only the four upstream endpoints are used:
GET /status,POST /eval,PUT /save?path=,DELETE /delete?path=.Stacked on PR #8 (
feature/alula-artemis-launcher), because it uses theinstaller's pinned upstream/expected-file helpers. Merge #8 first.
Transport
tools/transports/artemis-http.cjs+ CLItools/artemis-http.cjs:/24discovery, timeouts and bounded retries(retry transport/5xx only, never a 4xx or a write), structured error codes;
Lua 5.1,n2h.platform(Ounce), engine type;status: erroris a failed operation eventhough HTTP is 200; request-size cap enforced before sending;
/evalhex reads with offset/length, exact byte and hexlength checks, per-chunk SHA-256 state, final SHA-256, resume after a failed
chunk, local temp file promoted only after verification;
/saveunder the server body cap, chunked/evalappendsabove it, explicit
--forcefor overwrite, protected files refused on bothclient and server. The reported hash is read back from the console and
compared with the source before success (
EHASHon mismatch);--no-verifymarks the result unverified. An interrupted upload is
EINCOMPLETEwith thepartial byte count, and a failed PUT/append is attempted once (writes are
never retried);
stat, error if still present;scope: explicit-files,complete: false) -the server has no listing, so it cannot authorize a destructive full restore;
node tools/kit.cjs --http <ip>opens a menu exposing only theseoperations. The ns2jb RPC flow is unchanged.
Hardware validation (Switch 2, firmware 23.0.1, Seedsow Demo 1.0.0)
Server started with button Y; no RPC session.
/statusnora2hax/seedsowhello worldLua 5.1/Ouncecmp+ SHA-256)/savechunk +/evalappends; 4 KiB download readsstatabsent;getexit 9core.lua,save9999.datsave:/../escapeos.renameprobeOperation not permitted) -> chunked uploads are non-atomiccomplete:falseHashes:
Final review round — probe safety and path validation
earlier run, and cleanup deleted names without proving ownership. The probe
now uses two 128-bit random suffixes, refuses if either exists (reports
collided), writes recognizable content and reads it back before treating afile as disposable, deletes only files that still match a probe payload, and
reports anything else in
left/cleanupError. Cleanup runs in afinally,so a timeout after a successful upload is handled. No race-free claim: the API
has no exclusive create.
empty names,
.,.., any.., separators/backslashes, NUL/controlcharacters, unsupported characters and leading
/(absolute); reads mayinspect protected files, writes refuse them. Invalid paths are rejected
before any HTTP request.
upload/timeout/interrupt/corrupt destination/cleanup failure/consecutive/
concurrent, validator acceptance/rejection at every entry point, no-request
before rejection). Full branch suite: 192 Node assertions across 15 files,
Python 8, Lua 13+12.
left: [], cleaned bothunique scratch names and all scratch
stats were absent; path refusals werelocal; 4/16/64 KiB remote-verified transfers matched and were deleted;
relaunch + Y reconnected with
core.lua/save9999.datpresent.3798646; base remainsfeature/alula-artemis-launcher. The installer PR now records the blocked hardware rollback validation indocs/ARTEMIS-ROLLBACK-HARDWARE.md; this HTTP PR makes no rollback claim..github/workflows/offline-tests.yml(from PR Install alula's Artemis Launcher as a separate, verified option #8) now runs Lua 5.4 andreports pass on this PR.
Limits
io.open("save:/")yields an unreadablehandle and the engine metatable
__indexis a function, so backups areexplicit-file and explicitly not a full archive.
os.renameonsave:/is refused;--atomicfails closed and removes its temp. Chunked uploads write the target directly.
/evalis arbitraryLua and can read protected files; the transport refuses protected writes.
Tests
tests/test_artemis_http.cjs: 35 passed against a local HTTP server withthe real endpoints/body cap (status/health, eval errors, cap, stat, binary
chunked download, resume, hash/size refusal, 4/16/64 KiB uploads, remote-hash
verification, corrupted upload, interrupted chunked upload, no-retry writes,
overwrite, protected refusals, delete verification, explicit backup, retry,
timeout, connection failure, atomic refusal, randomized probe cases, path
validation, CLI exits).
Lua 13 + 12, all passing.
d69770c; base remainsfeature/alula-artemis-launcher.Docs
docs/ARTEMIS-HTTP.md- usage, protocol details, security, limitations andthe hardware report.
docs/artemis-http-2026-11-01.txt- sanitized command/output transcript.Not merged, not draft. Base is
feature/alula-artemis-launcher.