Skip to content
OdinoCanoPublic

About

It's a secure-by-default JavaScript and TypeScript runtime written in Rust.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Latest commit

 

History

456 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

3va

3va logo

CI Security License: MIT OpenSSF Best Practices Rust Edition 2024 Docs Audio Overview Audio Overview (English)

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.

📖 Full documentation · 🎧 Resumen en audio (Español) vía NotebookLM · 🎧 Audio overview (English) via NotebookLM


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.


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.

For install methods on your platform (npm, Cargo, Scoop, Chocolatey, winget, Snap, Nix, prebuilt binaries), see Installation below.


Comparison

All numbers in this table are generated by CI, not hand-edited — .github/workflows/benchmark.yml runs bench/run.sh on a GitHub-hosted runner (4 vCPU, 15 GiB RAM), builds all three runtimes in release mode (cargo build --release) in the same run, and publishes the table straight to the job summary. The figures below come from the latest run of that workflow on main (all runs) and are independently reproducible — run bash bench/run.sh yourself (see bench/README.md for methodology and caveats). That run measures main, not the 2.11.0 release, so the 3va column includes changes that haven't shipped yet. GitHub's shared runners are weaker and noisier than dedicated hardware: absolute numbers vary from run to run, and a difference of a few percent between two runtimes (plain HTTP below) is within that noise. This table supersedes any other performance numbers elsewhere in this document or its history — treat it as the single source of truth.

Feature Node.js 24 Bun 1.3 3va 2.13.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 soon¹ partial² ✓ always, no allowlist
Permission grants scoped per-dependency ✗ ✗ ✓
Malware + secrets audit ✗ ✗ ✓
OSV CVE scan (24h cache) ✗ ✗ ✓
CDP Debugger (--inspect) ✓ ✗ ✓
NAPI native addons ✓ ✓ ✓
WebAssembly (WASI) partial ✓ ✓
Post-quantum TLS (hybrid, RFC 10024) ✗ ✗ ✓ client (tls.pqConnect) and https.createServer
Node.js API compat (fs/http/stream/…) ✓ (is Node) high, self-reported 21.2%⁴, measured
ECMAScript (test262) conformance⁷ ~100%, self-reported (is V8) ~99.9%, self-reported (JavaScriptCore) 97.5% (96035/98532, 0 skipped)
Platforms Linux/macOS/Windows/Android Linux/macOS/Windows Linux/macOS/Windows⁵
License MIT MIT MIT
First release → latest 2009 → 24.x 2023 → 1.3 v1.0.0 → v2.13.0
Startup time (hello world) 21.0 ms 3.8 ms 13.5 ms
HTTP throughput (100k req, c=1000) 44,476 req/s 87,067 req/s 81,851 req/s³
Express 5 (100k req, c=1000) 32,884 req/s 41,641 req/s 44,643 req/s
Express 5 over HTTPS (100k req, c=1000) 18,790 req/s 34,115 req/s 44,134 req/s
Memory, idle → under load 47.4 → 87.0 MB 24.7 → 38.7 MB 43.3 → 110.2 MB
Install time (warm cache)⁶ 293.6 ms 2.2 ms 5.0 ms

¹ npm v12 (shipping July 2026) disables install scripts by default; npm 11 and earlier run them. ² Bun's trustedDependencies allowlist skips scripts for untrusted packages, but a non-registry specifier (file:/git:/github:) sharing a trusted package's name could bypass it (CVE-2026-24910, fixed in 1.3.5). ³ Measured against 3va's HTTP server with its firewall's per-IP connection/rate limits raised — at the shipped defaults (100 req/s, 50 connections per source IP) a single-machine load test like this one is mostly rejected with 403, which is the firewall working as intended, not a bug. See HTTP Performance and Load Behavior for what happens at default limits. ⁴ Measured by this repo: nodejs/node's own test/parallel suite run with 3va (crates/test/tests/node_tests.rs, baseline in crates/test/tests/node_tests_baseline.txt). Per-module table in docs/12-roadmap/04-compatibility.md. The figure covers the 17 test/parallel prefixes whose baseline is committed; compatibility = passed / (passed + failed), skipped tests excluded. It replaces the previous self-reported ~97%. ⁵ Android arm64 currently broken on main; see Platform Support. ⁶ Re-running is-odd install when it's already in node_modules (npm: npm install --silent, Bun: bun install, 3va: 3va install --allow-net=registry.npmjs.org) — same package, same warm-cache condition, same CI runner, for all three. bench/run.sh now scripts all three; the npm/Bun columns fill in on the next CI run of .github/workflows/benchmark.yml (Node and Bun are already installed there). ⁷ tc39/test262, the official ECMA-262 conformance suite. 3va's number is the only one of the three actually measured by this repo's CI (crates/test/src/test262.rs, all ~53k tests across language/, built-ins/, and intl402/ — see docs/09-testing/06-test262.md for the category breakdown and known gaps: module-record-only semantics needing native v8::Module, $262.agent/createRealm edge cases, and ICU/CLDR data-version gaps in intl402/). Node's and Bun's figures are their own public self-reported numbers (V8's and JavaScriptCore's own test262 CI, respectively) — not run by this repo, so not held to the "CI-measured" bar the rest of this table is.

Where Bun wins: startup latency (3.1 ms vs 3va's 11.6 ms on the same CI runner), memory footprint (37.8 vs 76.1 MB under load) and the warm no-op install (1.7 vs 3.9 ms). 3va trades some of this for security guarantees Bun's allowlist model doesn't provide (see the CVE above). Startup in particular is not understood yet: on a dedicated 32-core desktop it is level with Bun (7.4 vs 7.3 ms), and pinning both runtimes to 4 CPUs there does not reproduce the CI gap, so core count alone doesn't explain it.

Where 3va wins: permission grants scoped to the dependency that requested them, not the whole process; an install path with no allowlist to spoof in the first place; and throughput under a framework — Express 5 (71,881 vs 48,841 req/s) and Express 5 over HTTPS (69,764 vs 43,502 req/s). Plain HTTP is level with Bun (121,871 vs 116,809 req/s, within run-to-run noise). These come from the event-driven HTTP dispatch and the real-TLS https.createServer merged after 2.11.0, and the dedicated 32-core runs in bench/README.md show the same ordering.


Installation

Recommended: if you already have Node's ecosystem tooling, npm install -g @edge_166/3va is the fastest path. If you work primarily in Rust, cargo install vvva_cli avoids depending on npm at all.

npm (all platforms)

npm install -g @edge_166/3va

Cargo (all platforms)

cargo install vvva_cli

Installs a 3va binary on $PATH — the crate is named vvva_cli (not 3va) because crates.io rejects package names starting with a digit.

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"

Build from source

Requires Rust 1.95+ (stable; this is the declared MSRV, rust-version in Cargo.toml, checked by a CI job), via rustup), a C/C++ toolchain, and network access on the first build to download the prebuilt V8 library. FIPS builds also need Go and CMake. See CONTRIBUTING.md § Prerequisites.

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

Termux (Android) — see Known Limitations


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.

Unencrypted protocols (http://, ws://, and FTP/IMAP/POP3/IRC/MQTT/gRPC without TLS) are refused for any non-loopback host, even one granted with --allow-net, unless you also pass --allow-insecure. TLS connections require TLS 1.2 or newer and always verify certificates.

Permission scopes can be widened to cover all values:

3va run app.ts --allow-read --allow-net  # unrestricted read + network

Don't want to figure out the flags yourself?

You don't have to read the package.json format below, or hand-write a single --allow-* flag. One command does it for you:

3va permissions learn app.ts

This actually runs your script (with everything open, so it works end-to-end once), watches what it touches, and hands you back the exact flags to lock it down — no guessing, no trial-and-error loop of "run it, see what got denied, add a flag, repeat":

$ 3va permissions learn app.ts
Running 'app.ts' with all permissions to observe usage...
[... your script's normal output ...]

Observed usage — suggested package.json permissions:

{
  "3va": {
    "permissions": {
      ".": {
        "allow-net": ["api.example.com"],
        "allow-read": ["./app.ts"],
        "allow-write": ["./output.txt"],
        "allow-env": ["NODE_ENV"]
      }
    }
  }
}

Equivalent CLI flags:
3va run app.ts --allow-net=api.example.com --allow-read=./app.ts --allow-write=./output.txt --allow-env=NODE_ENV

Copy the last line. Done — that's the minimum permission set your script actually needs, not a guess.

To skip the copy-paste, add --write and the observed grants go straight into package.json under 3va.permissions, ready to commit and review:

3va permissions learn --write app.ts

Existing grants are merged, never replaced — running learn again after a refactor cannot drop a permission some other entry point still needs, and per-package scopes ("express": { ... }) are left untouched. Only what the script actually did is recorded; nothing is widened on its behalf.

If you'd rather not execute anything yet (e.g. reviewing an unfamiliar script before running it), 3va permissions suggest app.ts does the same job by reading the source instead of running it — faster and safer to run blind, but a starting point rather than the final word, since it can't see what a conditional branch or dynamic require() would touch at runtime.

The package.json section further down is for teams who want the grants checked into version control instead of typed on the command line every time — worth doing once a project stabilizes, not something you need on day one.

When something is denied

A denied capability no longer only surfaces as whatever error the built-in happened to raise. Every run ends by reporting what was refused and the flag that would allow it:

$ 3va run app.ts
[!] 2 permissions were denied during this run:
    read /etc/hosts (denied 2x)
      grant with: --allow-read=/etc/hosts
    connect to api.example.com:8443
      grant with: --allow-net=api.example.com:8443

Run `3va permissions learn` to record the script's real requirements in package.json instead of passing flags each time.

--trace-denials prints each refusal the moment it happens instead, which is what you want when the summary can't tell you which call was refused — a retry loop hitting the same wall 400 times looks identical to one incidental read. A host grant may name a port: --allow-net=api.example.com:443 allows that service and nothing else on the host, while a portless --allow-net=api.example.com still covers every port.

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 are enforced, not just documentation: . grants apply globally (the app's own code), while a package name like axios or express only applies while that package's own code is executing — a dependency you didn't name gets none of it, even if it's also calling fs/net from the same process.
  • 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.

For install-time performance (3va vs. npm vs. Bun, same package, same warm-cache condition), see the Comparison table above — those CI-measured numbers are authoritative; treat any other install-timing figure you find elsewhere (older commits, issues, etc.) as stale.

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:

  1. Malware scan — static analysis of node_modules for known malicious patterns
  2. OSV CVE scan — queries api.osv.dev for known vulnerabilities; results cached 24 hours
  3. 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 start server.js --name one-shot --no-autorestart  # never respawn on crash
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/. When a managed process dies unexpectedly, the supervisor restarts it automatically with exponential backoff (500 ms → 1 s → 2 s → … → capped at 30 s), giving up after 15 consecutive crashes (tunable with --max-restarts). 3va stop always stops the process for good — it never triggers a respawn. The Restarts column in 3va status counts both automatic and manual restarts.

To disable automatic restarts for a specific process, start it with --no-autorestart; it will then be reported as error until you 3va restart it by hand. A restarted process keeps the exact same permissions it had at first start (same CLI flags, same package.json 3va grants).


HTTP Performance and Load Behavior

The headline throughput number is in the Comparison table (CI-measured, release build, firewall limits raised for the test). The numbers below describe how 3va behaves at its shipped default limits (100 req/s, 50 connections per source IP), which is a deliberate design choice, not a performance regression — a single-machine load test against defaults will see most requests rejected with 403, and that's intentional.

At high concurrency, 3va's connection limiter deliberately sheds excess connections rather than queuing them indefinitely, trading raw throughput for protection against overload. Slowloris and RUDY (R-U-Dead-Yet) protection are built into the HTTP layer (per-line header deadlines, a bodyTimeoutMs body deadline, and a minBodyRateBps minimum body receive rate — see docs/10-security/08-firewall.md). Rate limiting is adaptive: an IP that repeatedly trips the rate limit is auto-blocked, and each repeat offense within the strike window is blocked for longer (blockDurationSecs × blockEscalationFactor^(strikes-1), capped at maxBlockDurationSecs) until the IP stays calm for strikeDecaySecs.


Dev Tooling

package.json scripts fallback

3va <name> runs package.json.scripts.<name> (via the project's actual package manager — pnpm/yarn/bun/npm, detected by lockfile) whenever <name> isn't one of 3va's own subcommands — the same convention npm run <name>/pnpm <name> 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 the permissions system's capability model (which only governs JS executed inside 3va's own V8 engine; internally this lives in the vvva_permissions crate). 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 the vvva_test crate — 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 <script> tag. .css imports inject a <style> tag when a DOM is present (browser) and no-op otherwise (CLI/server); asset imports (images, fonts) embed the original path as a string, not a copied/hashed file — there's no production asset pipeline yet. The output directory is created automatically if it doesn't exist.

--minify strips whitespace and comments (identifiers are not renamed). --source-map and --split are not implemented yet: 3va bundle exits with an error if you pass them — see Known Limitations. For automatic rebuilds on change, use 3va dev (watches and rebuilds with a 300 ms debounce) — it also serves the project directly via on-demand transpilation without needing a full bundle at all.

Dev server with HMR

3va dev
3va dev --port 3000 --host 0.0.0.0 --open

Automatically detects the project framework (Next.js, Astro, Nuxt, SvelteKit, Remix, Gatsby, SolidStart, Qwik) and delegates to its native dev server. For custom/unrecognized setups, runs a built-in dev server:

  • On-demand ESM serving (Vite-style): a root-level index.html referencing <script type="module" src="/src/main.jsx"> works directly — .js/.jsx/.ts/.tsx files are transpiled per request (JSX/TS stripped via oxc) and their import specifiers rewritten so the browser's native ES module loader can resolve them: project-relative imports stay project-relative, bare specifiers ("react") and anything under node_modules resolve to /@fs/<path>. import "./x.css" is wrapped in a tiny style-injecting module; .css requested directly (a <link> tag) is served as-is.
  • A full bundle to dist/bundle.js still runs on start and on every source change (300ms debounce) — served at /bundle.js, useful for a hand-rolled public/index.html that references it directly instead of the raw entry.
  • SPA fallback checks public/index.html, then a root-level index.html, then a built-in default page. HMR is full-page reload via Server-Sent Events (/__hmr), not granular per-module hot replacement — see Known Limitations.

Two Ctrl+C within the 30s drain window force an immediate shutdown — useful since a browser tab's open /__hmr connection otherwise keeps the server "draining" for the full timeout.

CPU profiler

3va run app.ts --prof                           # writes profile.cpuprofile
3va run app.ts --prof --flamegraph=flame.svg    # also emit SVG flamegraph
3va prof profile.cpuprofile --top 20            # post-hoc analysis

Output is V8-compatible .cpuprofile JSON, loadable in Chrome DevTools and speedscope.app. Flamegraphs use the Inferno format.

Debugger

3va run app.ts --inspect         # CDP on 127.0.0.1:9229
3va run app.ts --inspect=0.0.0.0:9230

Opens a WebSocket CDP server. Connect via chrome://inspect or any DAP-compatible IDE. debugger; statements pause execution and emit Debugger.paused CDP events.

Interactive sandbox

3va sandbox

REPL with permission management. Inside the session: .allow-read=PATH, .allow-write=PATH, .allow-net=HOST, .allow-env, and .permissions to list current grants. Leave with exit, quit, or ^D.

Other commands

3va doctor          # runtime self-checks (sandbox, V8, crypto); exits 1 if any fails
3va --accessible    # EN 301 549 mode: no ANSI, no animations, screen-reader friendly

Environment Variables

3va respects the following environment variables. Priority: CLI flags > environment variables > config file > built-in defaults.

TOKIO_WORKER_THREADS

Number of async I/O worker threads (default: one per logical CPU). 3va uses Tokio's multi-threaded runtime; this variable controls its worker pool size.

TOKIO_WORKER_THREADS=2 3va run app.ts
TOKIO_WORKER_THREADS=1 3va run server.js      # single-threaded mode

Documented in detail at docs/04-core/05-threading.md.

3VA_<SECTION>_<KEY> (config overrides)

Override any field from 3va.config.ts at invocation time. Format: 3VA_<SECTION>_<KEY> (uppercase, underscores for camelCase).

Variable Overrides
3VA_DEV_PORT config.dev.port
3VA_DEV_HOST config.dev.host
3VA_DEV_PUBLIC_DIR config.dev.public_dir
3VA_DEV_OPEN config.dev.open
3VA_DEV_CSP config.dev.csp.enabled
3VA_TEST_COVERAGE config.test.coverage
3VA_TEST_WATCH config.test.watch
3VA_TEST_UPDATE_SNAPSHOTS config.test.update_snapshots
3VA_TEST_CONCURRENCY config.test.concurrency
3VA_AUDIT_DENY config.audit.deny
3VA_AUDIT_SECRETS config.audit.secrets
3VA_AUDIT_UPDATE_CACHE config.audit.update_cache
3VA_BUNDLE_OUT_DIR config.bundle.out_dir
3VA_BUNDLE_MINIFY config.bundle.minify
3VA_BUNDLE_SOURCE_MAP config.bundle.source_map
3VA_BUNDLE_SPLIT config.bundle.split
3VA_WORKSPACE_HOISTING config.workspace.hoisting
3VA_WORKSPACE_PARALLELISM config.workspace.parallelism
3VA_DEV_PORT=8080 3va dev
3VA_TEST_CONCURRENCY=8 3va test
3VA_BUNDLE_MINIFY=true 3va bundle src/index.ts

3VA_ALLOW_SCRIPTS

⚠️ Security — handle with care. By default, 3va never executes npm lifecycle scripts (preinstall, install, postinstall). This is a security guarantee, not a configurable policy. Set 3VA_ALLOW_SCRIPTS=1 to re-enable them:

3VA_ALLOW_SCRIPTS=1 3va install express --allow-net=registry.npmjs.org

Only set this for trusted packages and registries.

_3VA_STORE

Relocate the global content-addressable package store (default: ~/.3va/store/). Useful in containers, CI, or when $HOME is ephemeral:

export _3VA_STORE=/mnt/cache/3va-store
3va install axios --allow-net=registry.npmjs.org

NO_COLOR

Disable ANSI color output.


Platform Support

Platform Status
Linux x64 / arm64 ✓ full support, prebuilt binaries
macOS x64 / arm64 ✓ full support, prebuilt binaries
Windows x64 ✓ full support, prebuilt binary + Scoop/Chocolatey/winget
Android arm64 (Termux) ✗ broken on main since v2.2.0 (V8 migration dropped rusty_v8 Android binaries). Use the android-pre-v8 branch (pinned to the last pre-V8/QuickJS commit) as a workaround — see ANDROID_FALLBACK_HANDOFF.md on that branch for the full diagnosis.

Post-Quantum Cryptography

require('tls').pqConnect(port, host[, options]) performs a real in-handshake hybrid post-quantum key exchange, per RFC 10024 ("Post-Quantum Traditional (PQ/T) Hybrid Key Agreement Mechanisms for TLS 1.3"), group X25519MLKEM768. The ML-KEM-768 shared secret is combined with the X25519 shared secret inside the TLS 1.3 key schedule itself — there's no side-channel exchange and no separate secret exposed to JS, unlike an earlier version of this feature that ran a hand-rolled ML-KEM exchange as plain application data after an otherwise-classical handshake had already completed (that scheme provided no real security benefit and has been replaced). Built on rustls's native aws_lc_rs + prefer-post-quantum support; a server without hybrid-group support still negotiates a normal classical connection (check socket.pqNegotiated/socket.negotiatedGroup to see which happened).

Known limits, stated plainly:

  • tls.pqConnect() is the client-side API. Since 2.12, https.createServer({ key, cert }) terminates TLS itself and offers the hybrid X25519MLKEM768 group to clients that support it.
  • Only this one client path gets the hybrid PQ group; tls.connect(), WebSocket wss://, and gRPC TLS remain classical.
  • require('crypto').pq (standalone ML-KEM-768/ML-DSA-65 for JS, backed by vvva_crypto) is not implemented — only the Rust crate exists today.

See docs/10-security/05-post-quantum.md and docs/10-security/06-pq-tls-hybrid-design.md for the full design, prior-art comparison, and real third-party interop test results.


Known Limitations & Roadmap

  • Android arm64: not built from main since v2.2.0; workaround via the android-pre-v8 branch (see Platform Support).
  • HTTP layer: RUDY (R-U-Dead-Yet) detection is implemented (minBodyRateBps minimum body rate + bodyTimeoutMs body deadline) and rate limiting is adaptive — repeat offenders get escalating auto-block durations (blockEscalationFactor up to maxBlockDurationSecs, reset after strikeDecaySecs of calm). The shipped default is still a fixed base of 100 req/s and 50 connections per source IP; adaptivity adjusts the block penalty for repeat offenders, not the per-IP token-bucket rate. Connection tracking is per source IP only — behind a reverse proxy that collapses clients onto one IP, the proxy's IP is what gets limited (no X-Forwarded-For trust yet). The request parser supports Transfer-Encoding: chunked and rejects requests carrying both Content-Length and Transfer-Encoding with 400 (request-smuggling guard; verified by the content_length_plus_transfer_encoding_rejected_400 end-to-end test).
  • Malware/secrets scanning is not automatic on 3va install — despite docs/10-security/01-static-analysis.md previously implying otherwise, the scanner only runs when you explicitly call 3va audit / 3va audit --secrets. Run it yourself after installing new dependencies.
  • MQTT / IMAP clients: sockets are bounded (MQTT_CONNECT_TIMEOUT/MQTT_IO_TIMEOUT and IMAP_CONNECT_TIMEOUT/IMAP_IO_TIMEOUT, defaults 10 s / 30 s; per-client connectTimeout option overrides for tests or slow links) so an unresponsive broker/server can no longer hang a connection indefinitely. Residual caveat: mqtt's connect still runs synchronously on the JS engine thread (up to that same bound) before the async poll loop takes over; IMAP connects already run on a worker thread.
  • Supply-chain detection gaps: npm provenance is verified against the Sigstore public-good trust root: the DSSE signature, a leaf certificate chaining to the pinned Fulcio root, a Rekor entry (SET, and inclusion proof when present) that records this exact signature and certificate, and a subject digest matching the downloaded tarball. The signer must build from the repository the package declares and stays pinned per package after the first install (.3va/provenance-signers.json), so a later release signed by someone else, or without provenance, is refused. On a package's very first install, a registry that controls both tarball and attestation can still pass its own identity off by rewriting repository. Dependency-confusion protection (.npmrc-pinned scopes resolve only against their private registry, whose host must be in --allow-net), typosquatting detection, tarball integrity (SHA-512, fail-closed when missing, pinned by the lockfile), https-only registries (plaintext only to loopback) and lifecycle-script blocking are implemented.
  • Bundler: --source-map and --split are not implemented; the CLI rejects them with an error. --minify does not rename identifiers. Tree shaking is not yet applied to the multi-file graph.
  • Dev server HMR: full-page reload only, not granular per-module hot replacement.
  • package.json permissions section: 3va permissions suggest/learn don't yet write directly into package.json — that's planned but manual editing is required today.
  • Post-quantum TLS: real in-handshake hybrid PQ-TLS (RFC 10024) has one client code path (tls.pqConnect()); https.createServer() terminates TLS since 2.12 and offers the hybrid group, while wss:///gRPC TLS remain classical. See Post-Quantum Cryptography.

Contributing

Issues and PRs are welcome at github.com/OdinoCano/3va. Please run 3va test and 3va audit before submitting.

Security

Found a vulnerability? Please report it privately rather than opening a public issue — see SECURITY.md in the repository for disclosure instructions.

License

MIT

About

It's a secure-by-default JavaScript and TypeScript runtime written in Rust.

Resources

Code of conduct

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages