Security and input bounds¶
vibe-sketcher runs as a local server that the browser shell, the desktop window and agents talk to, and it reads files that come from elsewhere: a co-author’s ChemDraw file, a QVF from another program, a SMILES list from a database. This page records what it trusts, what it bounds and what is still open. It is the first part of the 0.8 reliability and security qualification (issue #69). That qualification as a whole is not complete, and no support claim rests on this page.
What is trusted¶
Party |
Trusted with |
Why |
|---|---|---|
The user at this machine |
Everything the server can do, including writing files they name |
It is their sketcher. |
Agents the user starts ( |
The same, through the REST API |
They act for the user. |
Web pages in the user’s browser |
Nothing |
Any site can make the browser send requests to 127.0.0.1. |
Files and pasted data |
Their contents only |
They are read, never run. |
Other machines on the network |
Nothing, unless the user binds to a network interface |
The default bind is 127.0.0.1. |
The local server¶
vibe-sketcher serve and the desktop window bind to 127.0.0.1 by default.
Such a loopback server:
answers only requests whose
Hostis a name of this machine (127.0.0.1,localhost,::1), and refuses others with 421. A site that points its own name at 127.0.0.1 (DNS rebinding) shares the server’s origin in the browser, but it still sends its own name.refuses a request whose
Originis another site’s, ornull, with 403. The browser shell, the desktop window and a development server onlocalhostare on this machine and pass. MalformedHostorOriginvalues, including invalid bracketed addresses, receive the same structured 421 or 403 refusal before document operations run; they do not cause a server error or prevent a subsequent valid request.
A server bound to another interface (--host 0.0.0.0) was asked for by
name. It takes any Host, prints that anyone who can reach it can use the
API, and leaves access control to the network. It has no login.
Every request body is read up to 272 MiB (the largest QVF in base64, with room to spare) before the application parses it; a longer one answers 413.
What a file may hold¶
Each check runs before the work it protects, and a refusal says which
bound was met (vibesketch.limits):
Input |
Bound |
Protects |
|---|---|---|
An imported file or text, a |
64 MiB |
parsing |
A QVF archive |
192 MiB packed; 10,000 files; 64 MiB for a JSON member and 1 GiB for any other member, unpacked; 2 GiB in all |
memory: a small archive that unpacks to gigabytes (a zip bomb) is refused from its directory, before anything is unpacked |
An imported run result or result-view companion |
32 MiB |
result parsing and retained companion data |
JSON (KET, |
64 levels of nesting |
the parser and the validator; JSON too deep for the parser itself is refused too |
One import |
25,000 atoms |
derivation of species and reactions |
A structure without 2D coordinates (SMILES, InChI, a name, XYZ) |
2,500 atoms |
2D layout, which grows faster than linearly: 5,000 atoms take about 17 s; a drawn structure of that size, with coordinates, imports |
All the structures without 2D coordinates in one import (several SMILES, a FASTA file of sequences) |
5,000 atoms |
2D layout: molecules of 500 to 1,000 atoms are the slowest per atom, and five of them take about 19 s; structures with coordinates (MOL, SDF, KET) do not count |
CDXML |
expat’s limits on entity expansion |
memory: the “billion laughs” document is refused by the XML parser |
A campaign |
2,000 nodes (two per species and stage); 4,096 cpus and 16 TiB of memory per node |
disk: every node is a directory of files written before the queue sees it |
A JSON specification or campaign record: the CLI’s |
64 MiB (a result container: the QVF bounds); 64 levels of nesting; |
memory and the parser: a co-author’s specification or campaign is read like an import |
Document loading (SDK and server startup) and CLI file conversion check the
size of the opened file before reading it into memory. They also cap the
read at the permitted size plus one detection byte, so a file that grows
after the size check is refused too. The limits count file bytes, before
UTF-8 decoding. Pasted or SDK text is counted as UTF-8 bytes too. JSON numbers
that overflow the finite floating-point range (for example 1e999) are refused
alongside explicit NaN and Infinity; they cannot silently become infinite
energies or coefficients. CLI conversion uses the selected input format’s limit,
including an explicit --from qvf for an archive with another suffix.
A refused conversion leaves an existing output file unchanged.
The same capped reads apply to CLI import-result and attach-view-file
(32 MiB), and return-geometry (192 MiB for a returned QVF). These commands
refuse oversized or growing source files before result parsing or attachment,
and leave the input document and existing output unchanged on refusal.
The CLI’s specification files are read the same way, from a file or from
standard input (-), and a refusal names the file’s role (the review decision, the stage runtimes) and the bound it met. It leaves the document
and any output directory as they were. A campaign’s records are read within
the same bounds when results, reviews and renders read them, and a stage’s
result container passes the QVF archive checks before anything in it is
unpacked. The stage driver and validator that run on a compute node read only
the files vibe-sketcher and vibe-scheduler staged for them, and use the Python
standard library alone, so they are not bounded here.
Result artifacts retained for vibe-viewer are read back from vibe-sketcher’s cache within the same 32 MiB, and an existing cache file is checked by a streamed digest, never read whole. The cache is the user’s directory and can change outside vibe-sketcher, so an oversized entry is refused with the way out (reimport the file). vibe-viewer’s converted result is read up to the QVF bound. Campaign source pins are digested in streamed chunks, so a large source file is hashed without being held in memory, and the desktop viewer’s log is read only up to its last 16 KiB.
MCP open_document and import_run_result apply these limits locally before
decoding text, encoding uploads or sending REST requests. Starting MCP with
QVF files against an existing server also caps each local read. A refused
file is not uploaded and cannot create or update a server document.
A canvas that is not one, such as KET whose nodes are not a list, is refused as an invalid canvas instead of failing inside the server.
Files are read, never executed: formats are parsed into data, and no imported file names a program to run. The routes that take a filesystem path (campaign and render directories) and Save, which writes to the file the server opened, act for whoever passes the checks above: the user and their agents.
Saving¶
Every save writes a hidden temporary file beside the target, makes it
reach the disk, renames it over the target and then syncs the directory.
A crash, a full disk or an interrupted save therefore leaves either the
old file or the new one, never a mixture. A crash at the wrong moment can
leave a hidden .NAME.*.tmp file beside the document; it is safe to
delete.
A document the server opened from a file (vibe-sketcher serve FILE, the
browser’s Save) remembers the file’s bytes as it read or last wrote
them. If another program changed, replaced, deleted or created the file
since, Save is refused with 409 and the file is left as the other program
wrote it. The edit stays in the open document: reopen the file to see the
other change, or keep this version with a download under another name.
The desktop window checks the same before its Save. A stale revision (two
clients editing at once) answers 409 with the current revision, and the
client re-reads; missing optional programs answer with an installation
hint instead of failing.
External-change digests stream the complete file in bounded chunks, even if another program replaced it with a much larger file. Inspecting a QVF save destination uses the same 192 MiB packed limit as opening one, including symlink targets and files that grow while being inspected. An oversized destination is left untouched; use Save As to keep the new drawing elsewhere.
When the server cannot save because the disk is full, its save route returns 507; other filesystem errors return 500 with a readable explanation. Edits remain open and unsaved. Check disk space and permissions and retry, or download a copy. Failures before the atomic rename leave the original file unchanged and remove the temporary file.
Directories¶
Compiling a campaign and rendering its stages write into a directory the client names. It must be absolute, and new or empty; a directory with anything in it is never touched. If the write fails (no permission, a full disk), the refusal says why. A directory that was made for the write is removed, and one the client gave is left empty, as it was. The paths a campaign’s own files and records hold may come from a document someone else wrote, so a render refuses any that lead outside the campaign, and a campaign whose files are missing or unreadable is refused with the reason.
A drawing sent over REST (PUT /api/documents/{id}/canvas) and a posted
document meet the same bounds as an import: nesting, atoms, and a canvas
that is one.
Updating¶
scripts/update.sh only fast-forwards the checkout, so an interrupted
fetch leaves it as it was. With --recreate-venv, the previous virtualenv
is set aside as VENV.previous until the new one is installed and answers;
if the update fails or is interrupted (Ctrl-C included), the new one is
removed and the previous one restored. A VENV.previous left behind by a
killed update stops the next update with the way out: move it back or
delete it. An update without --recreate-venv reinstalls into the existing
virtualenv, where pip replaces one package at a time; run the update again
if it was interrupted.
Desktop startup¶
The desktop window starts its own backend and waits for it. Only the
backend’s own answer counts as ready (200 {"status": "ok"} from
/health), so another program on the port, or an error page, never opens
the editor. A backend that dies while starting ends the wait at once and the
setup window says it did not start, with its exit status in the log. A
backend that stops later closes the editor window and shows The
vibe-sketcher server stopped, with Try again to start a new one;
changes since the last save were held by the stopped backend.
Still open¶
These parts of #69 are not done yet:
Clean installation and packaged flows on each supported platform.