Repository navigation
Release v1.0.0
Release v1.0.0
Major Changes
- #158 Thanks @Hazzng! - Add a sandbox
gitcommand backed by just-git, export serverGITHUB_TOKENinto sandbox GitHub-compatible Git/curl env, and let MCP-created sandboxes request network access for clone/fetch/push. A per-requestenv.GITHUB_TOKENre-points git's HTTP credentials at that token, refusing plaintexthttp://remotes, validating each redirect hop by hand, dropping credentials on cross-origin redirects, and rewriting a redirected POST to GET on 301/302/303 the wayfetchdoes — so a push packfile is never replayed at a host it was merely forwarded to.
Minor Changes
- #162 Thanks @Hazzng! - Add file access to MCP —
file_read,file_write,file_edit— plusPATCH /v1/sandboxes/:id/files/*pathfor exact-string edits.
file_editrequiresoldStringto match exactly once (replaceAllopts into multiple), rejecting an ambiguous match asEDIT_NOT_UNIQUErather than guessing; it runs in one script-tx scope on Postgres so a concurrent reader never sees the file mid-edit, refuses non-UTF-8 content and unpaired surrogates, and preserves file mode and a leading BOM.file_readpages viaoffset/limit/byteOffset, bounding both the file it opens (MAX_MCP_READ_FILE_BYTES) and the reply it returns (MAX_MCP_READ_RESPONSE_BYTES).file_writecreates parent directories and refuses to clobber a directory on every backend. HTTP and MCP share one implementation insrc/api/lib/file-ops.ts.
Patch Changes
- #162 Thanks @Hazzng! - Document container sizing for the write cap: a single write costs roughly 7x the file size in
externalmemory above steady state, so a 50 MiB write needs on the order of 700 MB of headroom (a 512 MiB container OOM'd on one request; 768 MiB survived). - #162 Thanks @Hazzng! - Apply the single-file write limit to each entry of a bulk write, not just the combined total, so one oversized entry in
POST /writeFilescan no longer land a blob the contentCache cannot hold. - #162 Thanks @Hazzng! - Roll back a
PATCHedit or bulk write when the distributed exec lock is definitively lost mid-request, instead of committing it and reporting a retryableELOCKLOST. Both routes now go throughrunInScriptTx, which checks for lock loss before the commit. - #162 Thanks @Hazzng! - Reject an edit whose
oldString/newStringcarries an unpaired surrogate asEDIT_LONE_SURROGATE, and enforce the write limit on the encoded result rather than on a projected size that assumed one match encodes to the bytes it replaces. - #162 Thanks @Hazzng! - Clean up the destination of a
git clonethat fails partway (e.g. on a symlink, sinceallowSymlinksdefaults to false), instead of leaving a half-checked-out tree whose complete index made every missing file look like a staged deletion — an agent then following up withgit add -A && git commit && git pushturned that into a real destructive commit. - #162 Thanks @Hazzng! - Refuse to replay a git request body across origins on a 307/308 redirect, since both preserve the method and body — for git, the packfile being pushed — and could otherwise forward a whole push to an attacker-chosen host.
- #162 Thanks @Hazzng! - Budget the whole
file_readreply, not just the content string, againstMAX_MCP_READ_RESPONSE_BYTES, and normalize the path MCP tools echo back instead of only prefixing a slash. - #162 Thanks @Hazzng! - Size a
file_readpage against the reply as the transport actually serializes it (which re-escapes once more), and stop callingsplit("\n")on the whole file just to count lines — both scanning-based fixes remove a real-world 170 KiB overshoot and a multi-million-element allocation. - #162 Thanks @Hazzng! - Enforce the file-write limit on
PUT /v1/sandboxes/:id/files/*pathas the body streams, instead of trustingContent-Lengthand buffering the whole request first. - #162 Thanks @Hazzng! - Keep a leading UTF-8 BOM in what
file_readandfs_exportreturn, matching theignoreBOMdecodingeditFilealready used, so content andstat.sizeround-trip byte for byte. - #162 Thanks @Hazzng! - Return
RESPONSE_BUDGET_TOO_SMALLinstead of a reply that can never fit any content whenMAX_MCP_READ_RESPONSE_BYTESis configured below the size of the response envelope, which previously left a client resuming a page in an infinite loop. - #162 Thanks @Hazzng! - Keep
file_readpaging identical to thesplit/joinit replaced when a file ends in a newline, instead of returning a trailing newline the old implementation would have dropped. - #162 Thanks @Hazzng! - Ignore a relative
PWDwhen recording a session's working directory instead of rooting it into a path that never existed. - #162 Thanks @Hazzng! - Build a
replaceAlledit by assembling flushed chunks instead ofsplit(oldString).join(newString), cutting peak RSS on a worst-case near-limit file from 1184 MB to 357 MB with no regression on ordinary single-match edits. - #162 Thanks @Hazzng! - Stop an abort that races script-tx opening from rejecting a promise with no listener, which was fatal under Node's default
--unhandled-rejections=throw. - #162 Thanks @Hazzng! - Stop a late-arriving transaction open from adopting into a finished scope (each open now carries a generation checked before adopting), and refuse cache-served reads (
stat,readFile,readdir,exists,getAllPaths) once a script-tx is lost. - #162 Thanks @Hazzng! - Fail every remaining operation in a script scope once its transaction's connection is lost, instead of letting a write silently self-commit outside the scope on a reconnected-but-transactionless connection — previously a 600-file bulk write could answer HTTP 500 with 599 of them durable.
- #162 Thanks @Hazzng! - Write a whole file through one shared transactional path (
writeFileAtPath) on both the MCPfile_writetool andPUT /v1/sandboxes/:id/files/*, so parent directories and file content commit together andPUTmatches MCP in refusing to clobber a directory (400 EISDIR). - #162 Thanks @Hazzng! - Default the single-file write limit to the contentCache cap (50 MiB) instead of 64 MiB — load testing found memory cost doubles just past the cache cap, so the old default pinned 256 MB per warm session for a single large read.
Image: ghcr.io/hazzng/sql-fs:v1.0.0