PackSec: Package-level capability-based security
What: A capability-secure version of Node.js, and an ecosystem of capability-secure repackaged versions of existing NPM packages, community-contributed and hosted on GitHub like Homebrew & DefinitelyTyped.
Why: Immediately, this provides strong defense against malicious dependencies (supply chain attacks) like event-stream
, electron-native-notify
, typosquatting like crossenv
, and thousands more; as well as vulnerable dependencies like JS-YAML, express-fileupload
, and more.
More broadly, if you break down computer security into authentication and authorization, a huge swath of authorization problems—privilege escalation, directory traversal, injection attacks, etc—can be addressed by thorough application of capability-based security principles. (Authorization problems not addressed by capsec are primarily low-level attacks like race conditions, memory safety, or side-channel attacks.) PackSec alone can't address all those problems, but it's a foot-in-the-door to immediately begin applying capsec principles towards solving real-world problems.
How: For new projects, use npsx
instead of node
to run your scripts, and use npsm
instead of npm
to install dependencies. If a repackaged capabilities-secure version of the package you want isn't available, contribute one (TODO: link to tutorial)! :) Of course, an escape hatch will also be available to use capabilities-insecure dependencies without having to repackage them.
For existing projects, we will have tools to automatically convert individual files to use capabilities-secure dependencies where possible, and "capabilities coverage" tools to report on your progress converting an overall project to be capabilities-secure (and perhaps complain when new capabilities-insecure files are introduced).
Who: You! Me! Together, we can end software supply chain attacks and authorization problems of many kinds.
When: The state of computer security is unsustainable, help fix it today!
What's Next: More languages, platforms, and package ecosystems: PyPI, RubyGems, Cargo/Rust, WASI, Flatpak, apt-get, Homebrew?
Sketch
- Capabilities-secure packages should have similar security properties as WASM modules: most importantly no side-effects, also no intrinsic non-determinism nor capability to measure time (either/both can be provided by the library consumer, ofc). This is enforced by running them in a sandbox where we mock out:
- globals allowing side-effects like
process
- built-in modules that can have side-effects, like
fs
,http
,os
,child_process
etc - C++ addon modules
- globals allowing side-effects like
- How to repackage existing NPM packages?
- Hopefully many are like sharp, which can already take in either a filename string (bad, capabilities-insecure) or pipe in a file (good, capabilities-secure). I think for those we can just stub out
fs
or whatever, which should just disable features like opening a file by name without breaking anything else - Otherwise, repackaging means a new API is presented to the library consumer, and we provide an API to the shim code to mock out side-effectful globals/built-in modules
- C++ addons have to be imported at the top level (ie by the app itself) and passed to libraries that want to use them, possibly transitively
- Hopefully many are like sharp, which can already take in either a filename string (bad, capabilities-insecure) or pipe in a file (good, capabilities-secure). I think for those we can just stub out
- Escape hatch to insecurely import modules
- Incremental transition
- Processes for submitting & reviewing repackaged libraries
- Homebrew & DefinitelyTyped show that a community-contributed package management ecosystem, where code is repackaged by third-parties unaffiliated with the authors and submitted to the package repository, can be workable
- Choosing NPM to start because:
- JS is designed to run in a sandbox, only
require()
/import
needs to be overridden to contain it; Ruby has a built-in syntax for shelling out, Python has.__closure__
, etc - I quickly found
vm2
&isolate-vm
sandboxing options - NPM has had prominent in-the-wild supply-chain-attacks like
electron-native-notify
andevent-stream
- JS is designed to run in a sandbox, only
- The 2 main options for secure containment are
vm2
andisolate-vm
: https://github.com/laverdet/isolated-vm#alternatives- If
vm2
allows dynamically interceptingrequire()
that would be easiest, but it's not clear if that's possible isolate-vm
has a more secure design (starting with a secure underlying primitive (V8Isolate
) and adding functionality, rather than starting with an insecure underlying primitive (Nodevm
) and plugging holes), and it seems more official in terms of who uses it and how its run like having actual security advisories and stuff, but its advertised advantages overvm2
like threading and memory limits and stuff we don't really need, and it seems like we'd have to re-implementrequire()
ourselves
- If