3va

> Veni, Vidi, Vici, Abiit — He came, he saw, he conquered, he left.
3va is a JavaScript and TypeScript runtime written in Rust with deny-by-default security. It bundles a package manager, process manager, test runner, bundler, and dev server — no pm2, no separate build tool, no extra config.
Philosophy
The JavaScript ecosystem has a supply chain security problem. Post-install scripts run arbitrary code at install time. Packages silently access the filesystem and network. There is no enforced boundary between trusted application code and untrusted dependencies.
3va starts from a different premise: deny everything, grant explicitly. The design draws from QubesOS, WASI, and the Chrome sandbox — not from Node.js. Every capability (filesystem, network, env vars, child processes, native addons) is blocked by default and must be declared at the command line. This applies uniformly to application code and to every dependency it pulls in.
Comparison
| Feature | Node.js 25 | Bun 1.3 | 3va 2.0 |
|---|
| JavaScript runtime | ✓ | ✓ | ✓ |
| TypeScript (no config) | ✗ | ✓ | ✓ |
| Package manager | via npm | ✓ | ✓ |
| Process manager | via pm2 | ✗ | ✓ built-in |
| Test runner | via Jest | ✓ | ✓ Jest-compatible |
| Bundler | via webpack/esbuild | ✓ | ✓ |
| Dev server + HMR | ✗ | ✓ | ✓ |
| Deny-by-default permissions | ✗ | ✗ | ✓ |
| Post-install scripts blocked | ✗ | ✗ | ✓ always |
| Malware + secrets audit | ✗ | ✗ | ✓ |
| OSV CVE scan (24h cache) | ✗ | ✗ | ✓ |
CDP Debugger (--inspect) | ✓ | ✗ | ✓ |
| NAPI native addons | ✓ | ✓ | ✓ |
| WebAssembly (WASI) | partial | ✓ | ✓ |
| Post-quantum TLS (ML-KEM-768) | ✗ | ✗ | ✓ |
| Startup time (hello world) | 175 ms | 16 ms | 94 ms¹ |
| HTTP throughput (100k req) | — ² | 20,758 req/s | 1,572 req/s¹ |
| Memory (minimal HTTP server) | 44 MB | 32 MB | 29 MB |
| Install time (warm cache) | 984 ms | 7.8 ms | 16.8 ms |
¹ Measured with a debug build (cargo build, not --release). Release performance is higher.
² Node.js was not running an HTTP server during the throughput measurement; connection refused on all requests.
Where Bun wins: startup latency and raw HTTP throughput. Bun's JSC engine and native HTTP stack are faster in both metrics. 3va trades throughput for security guarantees that Bun does not provide.
Where 3va wins: memory footprint, integrated security tooling, and permission isolation that applies at runtime — not just at install time.
Installation
npm (all platforms)
npm install -g @edge_166/3va
Scoop (Windows)
scoop bucket add 3va https://github.com/OdinoCano/3va
scoop install 3va
Chocolatey (Windows)
choco install 3va
winget (Windows)
winget install OdinoCano.3va
Snap (Linux)
snap install vvva-cli
Build locally from source (for testing):
snapcraft --use-lxd # uses dist/snap/snapcraft.yaml
sudo snap install ./vvva-cli_*.snap --dangerous
Nix
The flake lives under dist/nix/:
nix run "github:OdinoCano/3va?dir=dist/nix"
Termux (Android)
bash <(curl -fsSL https://github.com/OdinoCano/3va/releases/latest/download/termux-install.sh)
Build from source
git clone https://github.com/OdinoCano/3va.git
cd 3va
cargo build --release
sudo cp target/release/3va /usr/local/bin/
Direct binary download
Pre-built binaries for every platform are attached to each GitHub Release:
| Platform | File |
|---|
| Linux x64 | 3va-vX.Y.Z-x86_64-unknown-linux-gnu.tar.gz |
| Linux arm64 | 3va-vX.Y.Z-aarch64-unknown-linux-gnu.tar.gz |
| macOS x64 | 3va-vX.Y.Z-x86_64-apple-darwin.tar.gz |
| macOS arm64 (M-series) | 3va-vX.Y.Z-aarch64-apple-darwin.tar.gz |
| Windows x64 | 3va-vX.Y.Z-x86_64-pc-windows-msvc.zip |
| Android arm64 | 3va-vX.Y.Z-aarch64-linux-android.tar.gz |
Quick Start
git clone https://github.com/OdinoCano/3va.git
cd 3va
cargo build --release
sudo cp target/release/3va /usr/local/bin/
# Run a script (no permissions granted by default)
3va run app.ts # or: 3va r app.ts
# Grant specific capabilities
3va run app.ts --allow-net=api.example.com --allow-read=./data
# Install packages
3va install express --allow-net=registry.npmjs.org # or: 3va i express …
# Audit the dependency tree
3va audit --secrets
# Start as a background daemon
3va start app.js --name my-api
> Common aliases: r=run · i/add=install · t/spec=test · d=dev · b=bundle · ws=workspace · sh/shell=sandbox
>
> Output: 3va run only prints your script's own output. Pass -v / --verbose to also see runtime status messages.
Permissions
Every capability is blocked by default. Permissions are granted per-invocation via flags and apply equally to application code and all loaded dependencies.
3va run app.ts \
--allow-read=/app/config \ # filesystem read, scoped to a path
--allow-write=/tmp \ # filesystem write, scoped to a path
--allow-net=api.stripe.com \ # outbound network, scoped to a host
--allow-env=DATABASE_URL \ # env var access, scoped to a variable
--allow-child-process \ # spawn child processes
--allow-ffi=./build/addon.node # load native addon (NAPI)
Omitting a flag means the capability is blocked and cannot be enabled from inside the script. In an attended terminal (stderr is a TTY), the runtime asks interactively at the point of first access; in CI, pipes, or redirected output, ungranted capabilities are denied silently.
Permission scopes can be widened to cover all values:
3va run app.ts --allow-read --allow-net # unrestricted read + network
Permission analysis commands
Two commands help derive the minimum permission set:
3va permissions suggest # static analysis of source files → suggested flags
3va permissions learn app.ts # run with all permissions, report which were used
Package-level permission declarations
Besides CLI flags, 3va run reads permission grants from package.json under
a "3va" key — a pattern already established by "jest", "eslint", and
"prettier". Node.js, Bun, pnpm, and Yarn ignore unknown keys, so there is no
conflict. CLI flags and package.json grants are merged (CLI only adds, never
revokes a package.json grant).
{
"name": "my-app",
"dependencies": {
"express": "^4.18.0",
"axios": "^1.6.0"
},
"3va": {
"no-prompt": true,
"permissions": {
".": {
"allow-net": ["api.example.com"],
"allow-read": ["./config", "${NODE_MODULES_ROOT}/express@4.22.2"],
"deny-read": ["${NODE_MODULES_ROOT}/express@4.22.2/node_modules/express/lib/express.js"]
},
"axios": {
"allow-net": ["api.example.com"]
},
"express": {
"allow-net": ["*"]
}
}
}
}
- Scope keys (
., axios, express) are for human readability only — every
scope's grants are merged into one flat set for the whole process (the
capability engine has no notion of "which module is calling").
deny-* fields win over any broader allow-*, letting you grant a vendored
directory by prefix while excluding one file with a known CVE.
- Relative paths resolve against the directory containing
package.json, not
the invocation cwd — the same file works from any working directory.
${VAR} in path fields expands against the environment of the host process
running 3va run (evaluated before any capability exists), so one absolute
root that differs per server/team (/var/node_module, /local/bin/node_modules, …)
can be declared once and switched per environment without editing
package.json. An undefined variable is left as a literal placeholder
(fails closed) rather than collapsing to an empty string.
"no-prompt": true is equivalent to passing --no-prompt on every
invocation: any capability not covered by allow-* is denied silently
instead of prompting.
3va permissions suggest (available today) will be extended to generate this
section directly, and 3va permissions learn to persist the observed
permission set into it. Full reference:
docs/06-permissions/06-package-json-permissions.md.
Package Manager
3va install axios --allow-net=registry.npmjs.org # from npm
3va install react@18 --allow-net=registry.npmjs.org # specific version
3va install @std/path --allow-net=jsr.io # from JSR
3va reinstall axios --allow-net=registry.npmjs.org # force-reinstall one package
3va update --allow-net=registry.npmjs.org # update to latest versions
Post-install scripts are never executed. There are no exceptions. This is enforced at the package manager level, not as a flag.
Package storage uses global deduplication (same model as pnpm): a package at version X is stored once on disk; multiple projects reference it and declare their own permissions independently.
Install benchmarks (warm cache, 10 runs each)
| Tool | Mean | Range |
|---|
| bun install | 7.8 ms | 4.9–23.0 ms |
| 3va install | 16.8 ms | 12.5–31.1 ms |
| npm install | 984 ms | 911–1,307 ms |
| pnpm install | 1,368 ms | 1,204–1,603 ms |
Audit
3va audit # malware scan + OSV CVE scan (24h cache)
3va audit --secrets # also scan for leaked credentials
3va audit --deny # exit non-zero on CRITICAL/HIGH findings
3va audit --json # machine-readable output
Audit runs in three phases:
- Malware scan — static analysis of
node_modules for known malicious patterns
- OSV CVE scan — queries
api.osv.dev for known vulnerabilities; results cached 24 hours
- Secrets detection (opt-in via
--secrets) — 21 patterns covering AWS keys, GitHub tokens, Stripe keys, private certificates, JWT secrets, database connection strings, and more, scanned over the current project's source files
Process Manager
Built into the runtime. No pm2, no separate daemon process.
3va start server.js --name api
3va start server.js --name worker -- --port 4000 # pass args after --
3va status # all processes
3va status api # one process
3va logs api --lines 200
3va restart api
3va stop api # SIGTERM → SIGKILL after 1.5s
3va delete api # stop + remove logs
Process metadata and logs live in ~/.3va/processes/. If a managed process dies unexpectedly, 3va status reports it as error; restart it with 3va restart . (Automatic restart on crash is on the roadmap.)
HTTP Performance and Load Behavior
Baseline benchmarks, 100,000 requests at 1,000 concurrent connections:
| Runtime | Req/s | P50 | P99 | Success |
|---|
| Bun 1.3 | 20,758 | 4.4 ms | 16.0 ms | 100% |
| 3va 2.0 (debug) | 1,572 | 61 ms | 143 ms | 100% |
At 2,000 concurrent connections (stress test, 1,000,000 requests):
| Runtime | Success rate | Req/s | Notes |
|---|
| Bun 1.3 | 100% | 21,650 | No connection limiting |
| Node.js 25 | 99.97% | 8,869 | 281 connection errors |
| 3va 2.0 (debug) | 70.4% | 327 | Rate-limited by design |
3va deliberately limits active connections to protect the process from overload. At 2,000 concurrent connections the rate limiter drops excess connections rather than queuing them indefinitely. This is the intended behavior for production deployments. Slowloris protection is also built into the HTTP layer.
v2 roadmap targets RUDY (R-U-Dead-Yet) detection and adaptive rate limiting.
Dev Tooling
package.json scripts fallback
3va runs package.json.scripts. (via the project's actual package manager — pnpm/yarn/bun/npm, detected by lockfile) whenever `` isn't one of 3va's own subcommands — the same convention npm run /pnpm follow:
3va build # not a 3va subcommand → runs `pnpm run build` (or npm/yarn/bun)
Built-in subcommands always win: 3va dev/3va test/etc. run 3va's own implementation, never a same-named script.
This is not sandboxed — the delegated script is a real external process (the actual package manager, running arbitrary shell), completely outside vvva_permissions' capability model (which only governs JS executed inside 3va's own QuickJS engine). Consistent with 3va install never running postinstall scripts, running it requires explicit consent: a [y/N] prompt in a TTY, --yes to skip it, "3va": { "no-prompt": true } in package.json to skip it permanently, or a hard deny with no prompt at all outside a TTY (CI, pipes).
Test runner
Jest-compatible. No configuration required.
3va test
3va test tests/unit
3va test --watch
3va test --coverage
3va test --update-snapshots
Supports describe, test, expect, all standard matchers, toMatchSnapshot, watch mode, and coverage reporting. The test runner is implemented in vvva_test — no Jest install needed.
Bundler
3va bundle src/index.ts
3va bundle src/index.ts -o dist/bundle.js --minify
Walks the real import graph from the entry — project files, node_modules (both ESM and CommonJS packages), .json, and .css — and inlines everything into one self-contained file, runnable standalone via 3va run dist/bundle.js or in a browser `