Repository navigation
Security
Report privately, not in a public issue: open a security advisory. That channel stays invisible until a fix is released, so a report does not become an exploit notice while there is nothing to upgrade to. You get an answer within a week, and credit in the release notes unless you would rather not have it.
DeepSharp runs inside your process, on data you supply, with no network of its own and no service behind it.
The libraries and the notebook open no sockets, start no processes and read no files unless you name them — save
two: a saved notebook keeps the columns it decided in a file beside itself, named after it, and reads that file
back; and an ONNX graph PyTorch's exporter wrote with its larger numbers in a file beside it has that file read from
beside the graph's own, under the name the graph gives it — a name that leads outside the graph's folder is refused.
That narrows what can go wrong to three things, and all three are worth reporting. The one exception is
deepsharp-serve, DeepSharp's own server, which is a fourth.
A way past the server's boundary. deepsharp-serve listens on a port and opens a browser, and the notebook it
serves runs code as the person who started it — so it is shut to everyone else. It listens on that computer
alone. It answers only a request that carries the token it printed when it started, in the address or in the cookie
its first page sets. A browser carries that cookie for a page from any port of the same computer, so the server opens
a notebook's socket, which carries everything a page asks of it, and makes a change only for its own page, as the
browser names the page a request comes from: without that, a page on another port could close the notebook and throw
away what was typed, as it did before the check. It answers only under a name of that computer, so a site that makes
its own name point there cannot reach it through the browser either. And it serves its own page, which it carries,
and nothing from the folder it runs in; into that folder it writes only the notebooks it serves and what they save
beside themselves — a notebook saved over its own file, the columns a pipeline's blocks keep beside it — and a new
notebook a page asks for, under the bare name of a .verso file and never over a file that is there. The page loads
nothing from anywhere else — the libraries it draws diagrams and formulas with are carried inside it — and a widget a
notebook shows runs in a sandboxed frame of its own, which reaches nothing of the page: it can say how tall it is and
hand the page a file to save, and take its look from the page that made it, and no more. Opening a notebook is not
running it: every piece of HTML the page places — what a cell shows, which a notebook file carries from the last time it
ran; what a layout draws; a button's icon — passes DOMPurify, carried in the page as well, and what in it would run is
taken out. A request that gets past any of these, a widget that reaches the page, its cookie or its socket, or HTML a
notebook carries that runs as the page, is a vulnerability.
A file that takes over the process when it is read. Loading a model, a checkpoint or a dataset must never be able to run code. A deserializer that instantiates arbitrary types, a reader that trusts a length field, a path inside an archive that escapes the directory it is unpacked into — each of those turns "open this model someone sent me" into "run this program someone sent me". A network's file names the kinds it is made of, and only a kind registered with the catalog that reads it is ever made — never a type looked up by its name — so a file that names anything else is refused, not obeyed.
The .pt file PyTorch's torch.save writes is the sharpest case, because the pickle inside it is a program: its
instructions name functions and call them. So no pickle library reads it. DeepSharp.Import.PyTorch carries an
interpreter of its own that carries out only the instructions PyTorch's own weights-only reader,
torch.load(weights_only=True), carries out, and that constructs nothing it does not name itself: an OrderedDict, a
tensor rebuilt by torch._utils._rebuild_tensor_v2 or _rebuild_parameter, and the storages of a kind of number. Every
other name — os.system, builtins.eval, subprocess.Popen, a type of .NET's — is refused at the byte that names it,
before anything is looked up, built or run, and nothing in the package finds a type or a member by a name; a test reads
the package's own metadata to hold it to that. Of 49 files written wrongly or with hostile intent on purpose, each
handed to PyTorch's own reader first, every one PyTorch refuses is refused here too.
What a file can make a reader do is bounded by the file and by the network it is read into, never by what the file says of itself:
- the records an archive holds together hold no more bytes than the file, each read to the length it says and checked against the check the archive keeps of it — an archive of one megabyte inflating to a gigabyte is refused with 2 MB taken, before a byte of it is inflated;
- a tensor's numbers are gathered only for a slot that takes them, and a view that repeats its storage's numbers is refused — a hundred views of a megabyte's storage for no slot cost 4.6 MB to refuse, where gathering them took 109 MB;
- a dictionary is keyed by nothing deeper than a tuple of plain values, and its keys are hashed as no file can aim at — eighty thousand keys chosen to share the runtime's own hash take a tenth of a second, where they took a minute and a half;
- a file is an archive only when it begins as one does, as PyTorch tells one, and a name the archive holds twice, in either case of its letters, is refused rather than read as either;
- a file longer than one array holds, 2,147,483,591 bytes, is refused by name, before a byte of it is read when its stream can say its length.
Every name any reader shows in a refusal — a tensor's, a layer's, a node's, a key's, a step's — is shown as words and nothing else: a line break, a control or format character, or one that turns text round is written as its escape, and a name past 200 characters is cut short with its length, so a file cannot write a line of its own into a log. A safetensors file is read by a port of safetensors' own reader, a Keras file by .NET's own zip and JSON readers and PureHDF, an ONNX graph by the messages ONNX's own format defines; Importing a model has what each refuses.
An operation that reads or writes outside its own buffer. Shapes are checked where they are handed over for exactly this reason: a wrong shape is a refusal at the point it is written, never a stray read of whatever happened to be next in memory. An operation that skips the check, or computes an index that the check did not cover, is a vulnerability rather than a bug.
A dependency with a known advisory. Report it even if you are not sure it is reachable from here — deciding that is our job, not the reporter's.
- A model that gives bad answers, trains poorly or overfits. That is correctness: open an issue.
- Running out of memory on a tensor you asked for. A shape that cannot exist is refused where it is written;
one that fits in an
intbut not in your machine is your own arithmetic. - Anything that needs an attacker to already be running code in your process. At that point they do not need this library.
CodeQL runs the security queries on every change and weekly, over the C# and over the script of the server's page (Quality has the detail), and Dependabot raises a pull request for each advisory in NuGet or in the actions. Neither replaces a report: a scanner finds patterns, and the interesting vulnerabilities are the ones that look like ordinary code.
The most recent released version gets the fix. There are no long-term support branches. If you are pinned to something older, say so in the report and you will be told what the upgrade involves.