guardpost
v1.0.1
Published
Zero-dependency import-time behavioral policy engine for Node.js dependencies
Maintainers
Readme
🛡️ guardpost
Import-Time Behavioral Policy Engine for Node.js Dependencies
Declare what your npm packages are allowed to do — and kill them instantly if they violate policy.
🚨 Why guardpost Exists (The Problem)
npm v12 disabled lifecycle scripts by default. That solved installation-time malware (postinstall).
However, the AsyncAPI incident (July 2026) and the Shai-Hulud worm proved that modern npm malware executes at import / require time.
When your application executes import 'compromised-package', that package runs inside your process with full user privileges. It can instantly:
- 🔑 Read
process.env(stealDATABASE_URL,AWS_SECRET_ACCESS_KEY,STRIPE_KEY) - 🌐 Call
fetch()ornet.connect()(exfiltrate data to rogue C2 servers) - 🖥️ Spawn child processes (
child_process.execto launch reverse shells) - 📁 Read/write arbitrary files (
/etc/passwd,~/.ssh/id_rsa)
Why Existing Security Tools Fail:
- Node.js
--permissionAPI: All-or-nothing process-wide flag; cannot restrict specificnode_modulespackages. - Static Analysis (Socket.dev / Snyk): Analyzes static ASTs and known CVEs, but cannot block zero-day dynamic runtime execution.
guardpost bridges this gap. It enforces fine-grained, per-package behavioral boundaries at runtime with zero external dependencies and microsecond performance overhead.
✨ Key Features
- 🔒 Per-Package Access Control: Restrict
env,fs,net, andspawncapabilities per dependency. - ⚡ Microsecond Latency (<9µs/call): Optimized Callsite LRU Cache (4096 entries) and fast-path root application bypasses.
- 📦 Zero Third-Party Dependencies: Pure Node.js built-ins (
node:module,node:async_hooks,node:fs,node:net,node:child_process). - 🧠 Auto-Discovery Learning Mode (
GUARDPOST_MODE=learn): Run your app once;guardpostrecords real behavior and generates your baselineguardpost.config.ts. - 💬 Interactive Terminal Dev Prompts: In development mode, prompt developers when an unlisted capability is requested (
Allow once / Allow always / Deny). - 🌐 Telemetry & Webhook Reporting: Stream structured security violation JSON payloads asynchronously to remote endpoints or Sentry/Datadog sinks.
- 🛡️ Anti-Tamper Guard: Seals internal prototypes and reference hooks via
Object.freezebefore application code executes.
💻 Tech Stack & Requirements
- Runtime: Node.js
>=20.0.0or>=22.0.0 LTS - Module System: ESM (
import) & CommonJS (require) - Dependencies: 0 (Zero external dependencies)
- Language: TypeScript 5+ (strictly typed)
📦 Installation
npm install guardpost🚀 Quick Start & Usage
1. Initialize Configuration
Generate a starter guardpost.config.ts policy file in your project root:
npx guardpost init2. Define Your Policy (guardpost.config.ts)
import { definePolicy } from 'guardpost';
export default definePolicy({
// Default for unlisted packages: deny dangerous APIs
defaults: {
net: false,
fs: false,
env: false,
spawn: false
},
// Explicit per-package allowlists
allow: {
'stripe': {
net: ['api.stripe.com:443'],
env: ['STRIPE_SECRET_KEY']
},
'pino': {
fs: { write: ['./logs/*'] },
env: ['LOG_LEVEL', 'NODE_ENV']
},
'drizzle-orm': {
fs: { read: ['./drizzle/*'] },
net: true
},
'@aws-sdk/*': {
net: true
}
},
// What to do when an unauthorized API access occurs
onViolation: 'throw' // Options: 'throw' | 'log' | 'report' | 'dry-run'
});3. Run Your Application
Preload guardpost using Node.js module preload flags:
node --import guardpost/register app.jsOr add it to your package.json scripts:
{
"scripts": {
"start": "node --import guardpost/register dist/index.js",
"dev": "GUARDPOST_INTERACTIVE=true node --import guardpost/register src/index.js"
}
}🧠 Auto-Discovery Learning Mode (GUARDPOST_MODE=learn)
Don't want to write policy configs manually? Let guardpost learn your application's baseline automatically:
# 1. Run your app under learning mode
GUARDPOST_MODE=learn node --import guardpost/register app.js
# 2. Interact with your app (or run your test suite)
# 3. Upon exit, guardpost synthesizes a ready-to-use guardpost.config.ts!💬 Interactive Dev Prompts (GUARDPOST_INTERACTIVE=true)
During development, enable interactive terminal prompts when an unconfigured dependency requests access:
GUARDPOST_INTERACTIVE=true node --import guardpost/register app.jsTerminal Output:
[guardpost SECURITY PROMPT]
Package 'sharp' requested unauthorized access:
Capability: fs.write
Resource: /tmp/output.png
Choices: [1] Allow Once [2] Allow Always (Save to Config) [3] Deny
Select (1-3): 2
[guardpost] Rule saved for sharp in guardpost.config.ts⚙️ Configuration Reference
| Option | Type | Description |
|:---|:---|:---|
| defaults | object | Base capability flags (net, fs, env, spawn) for unlisted packages. |
| allow | object | Map of package names (or wildcards like @aws-sdk/*) to specific rules. |
| allow[pkg].env | boolean \| string[] | Allowed environment variable keys. |
| allow[pkg].fs | boolean \| { read?, write? } | Allowed file system paths/globs. |
| allow[pkg].net | boolean \| string[] | Allowed remote hosts (api.stripe.com:443). |
| allow[pkg].spawn | boolean \| string[] | Allowed executable binaries (git, ffmpeg). |
| onViolation | 'throw' \| 'log' \| 'report' \| 'dry-run' | Action triggered upon policy violation. |
| reportSinkUrl | string | Optional Webhook URL for asynchronous violation JSON streaming. |
🛠️ CLI Reference (npx guardpost)
npx guardpost init # Scaffolds starter guardpost.config.ts
npx guardpost verify # Validates syntax and node_modules coverage
npx guardpost --help # Displays CLI usage documentation🏗️ Architecture & Mechanics
[ Application Boot: node --import guardpost/register app.js ]
│
▼
┌────────────────────────────────────────────────────────┐
│ 1. Initialize Policy Engine (guardpost.config.ts) │
│ 2. Attach Subsystem Proxy Traps (process.env, fs, net) │
│ 3. Lock Internal Prototypes (TamperProofGuard) │
└───────────────────────────┬────────────────────────────┘
│
▼
[ API Call Triggered (e.g. process.env.SECRET or fs.readFile) ]
│
▼
┌────────────────────────────────────────────────────────┐
│ Caller Package Attribution Engine │
│ - Check Callsite LRU Cache (4096 entries) │
│ - Fast-Path Bypass for Root App Code (src/*) │
│ - V8 Stack Frame Parser + AsyncLocalStorage │
└───────────────────────────┬────────────────────────────┘
│
┌──────────────────┴──────────────────┐
▼ ▼
[ Rule: ALLOW ] [ Rule: DENY ]
│ │
▼ ▼
Execute Native API Trigger Violation Action
(Throw / Log Warning / Sink)⚡ Performance Benchmarks
Running 100,000 intercepted process.env lookups under active policy enforcement:
npm testResults:
- Execution Time: 865ms for 100,000 calls
- Latency Overhead:
8.65 microseconds (µs)per call - Target Budget: <200µs (<0.2ms) — 20x faster than target budget!
🧪 Testing & Verification
# Run unit & integration tests
npm test
# Typecheck codebase (0 errors)
npm run typecheck
# Build production distribution
npm run build🤝 Contributing
Contributions are welcome! Please follow these rules:
- Zero External Dependencies: All core code must rely solely on native Node.js built-ins.
- File Size Limit: Keep individual files under 200 LOC following Clean Architecture & DDD principles.
- Strict Typing: Ensure
npm run typecheckpasses with zero errors. - Test Coverage: Include unit and integration tests for any new capability interceptors in
tests/.
git clone https://github.com/your-org/guardpost.git
cd guardpost/code
npm install
npm test📄 License
Distributed under the MIT License. See LICENSE for details.
