NCP Security Architecture
This document describes the security measures implemented in NCP's Code-Mode execution environment.
Overview
NCP's Code-Mode allows execution of TypeScript/JavaScript code with access to MCP tools. This creates potential security risks that are mitigated through a defense-in-depth approach:
- Static Analysis - Block dangerous patterns before execution
- Semantic Validation - Detect malicious intent patterns
- Runtime Sandboxing - Isolate code execution with resource limits
- Sandboxed File System - Confine file operations to a workspace directory
- Network Policy - Control and audit network requests
Execution Sandbox Hierarchy
Code execution uses a tiered approach, selecting the most secure available option:
| Tier | Sandbox Type | Isolation Level | Description |
|---|---|---|---|
| 1 | IsolatedVM | Highest | Separate V8 isolate (Cloudflare Workers technology) |
| 2 | Subprocess | High | Separate Node.js process |
| 3 | Worker Thread | Medium | Separate V8 context with resource limits |
| 4 | VM Module | Basic | Same-process with context isolation |
Resource Limits
All execution sandboxes enforce:
- Memory: 128MB maximum
- Execution time: 30 seconds default, 5 minutes maximum
- Code size: 16MB maximum
Static Analysis (AST-Based)
Before execution, code is parsed using TypeScript's compiler API to detect dangerous patterns:
Blocked Global Access
eval,Function- Arbitrary code executionprocess,global,globalThis- Node.js runtime accessrequire,module- Unrestricted module loading (whitelisted packages allowed)__dirname,__filename- Path disclosureReflect,Proxy,Symbol- Metaprogramming (sandbox escape vectors)WeakRef,FinalizationRegistry- GC probing
Blocked Property Access
__proto__,constructor,prototype- Prototype pollutiondefineProperty,setPrototypeOf- Object manipulationgetOwnPropertyDescriptor- Descriptor access
Blocked Modules
Direct imports of dangerous Node.js modules are blocked:
fs,child_process,cluster- System accessnet,dgram,dns,tls- Network accessvm,worker_threads,v8- Runtime manipulationos,path,crypto- System information
Semantic Validation
After static analysis, semantic patterns are checked:
Malicious Intent Detection
- Data Exfiltration: Reading credentials + external network access
- Credential Harvesting: Multiple secret/password accesses
- Reconnaissance: System enumeration patterns
- Persistence: Cron jobs, startup scripts
- Backdoor: Reverse shells, remote access
- Privilege Escalation: sudo, admin, root access
- Data Destruction: Bulk deletion patterns
Risk Level Classification
- Low: Standard operations within allowed scope
- Medium: Potentially sensitive operations requiring review
- High: Dangerous patterns that are blocked
Sandboxed File System
All file operations in Code-Mode are confined to a workspace directory.
Workspace Location
~/.ncp/workspace/ # Global installation
.ncp/workspace/ # Local installation (when .ncp exists in project)Security Features
- Path Confinement: All paths are resolved relative to workspace root
- Traversal Prevention:
../and absolute paths outside workspace are blocked - Escape Detection: Paths are normalized and validated before any operation
Example
// ✅ Allowed - within workspace
await fs.writeFile('output/report.pdf', data);
await fs.readFile('data/input.json');
// ❌ Blocked - escape attempt
await fs.readFile('../config.json'); // SandboxEscapeError
await fs.readFile('/etc/passwd'); // SandboxEscapeErrorSandboxed FS API
The following async operations are available:
| Operation | Description |
|---|---|
readFile(path, encoding?) | Read file contents |
writeFile(path, data, encoding?) | Write file (creates parent dirs) |
appendFile(path, data, encoding?) | Append to file |
readdir(path) | List directory contents |
mkdir(path, options?) | Create directory |
stat(path) | Get file statistics |
exists(path) | Check if path exists |
unlink(path) | Delete file |
rmdir(path, options?) | Remove directory |
rm(path, options?) | Remove file/directory |
rename(oldPath, newPath) | Move/rename file |
copyFile(src, dest) | Copy file |
createReadStream(path) | Create read stream |
createWriteStream(path) | Create write stream |
Package Whitelist
The require() function is available for whitelisted packages only:
| Package | Purpose |
|---|---|
pdf-lib | PDF creation and manipulation |
docx | Word document generation |
pptxgenjs | PowerPoint generation |
xlsx | Excel spreadsheet handling |
papaparse | CSV parsing |
cheerio | HTML/XML parsing |
axios | HTTP requests (via network policy) |
lodash | Utility functions |
date-fns | Date manipulation |
uuid | UUID generation |
crypto-js | Cryptographic functions |
canvas | Image manipulation |
sharp | High-performance image processing |
jimp | Image processing |
path | Path utilities (safe) |
Runtime Package Approval
When code requires a package not in the built-in whitelist:
- Pre-execution scan: Code is analyzed for
require()calls before execution - Blocked packages rejected: Node.js built-ins (fs, child_process, etc.) cannot be approved
- User elicitation: User is prompted to approve with scoped options:
- "This operation only" - Approved for single execution
- "This session" - Approved until server restarts
- "1 hour" - Approved with 1-hour TTL
- "24 hours" - Approved with 24-hour TTL
- Temporary approval: Approvals are stored in-memory only (no persistent security holes)
- Execution proceeds: If approved, the package is added to the effective whitelist for this run
This approach ensures:
- No permanent expansion of security permissions
- Case-by-case user control
- If a package is constantly needed, it signals a candidate for the built-in whitelist
Adding Packages
Users must install packages in their project:
npm install xlsx pdf-lib # etc.Network Policy
Network requests from Code-Mode are controlled:
- Policy Enforcement: Requests checked against allowed domains/patterns
- Request Auditing: All network calls are logged
- Rate Limiting: Prevent abuse through excessive requests
- Elicitation: User can be prompted for permission for new domains
Runtime Hardening
Package Pre-Loading
To ensure compatibility with whitelisted packages, the sandbox uses a pre-loading strategy:
- Scan code for
require()calls before execution - Pre-load packages that are in the whitelist BEFORE prototype freezing
- Freeze prototypes after packages have initialized their classes
- Execute code with pre-loaded packages available
This solves compatibility issues with packages like pdf-lib that need to define class methods during initialization.
Prototype Freezing
Built-in prototypes are frozen to prevent pollution:
Object.prototypeArray.prototypeFunction.prototypeString.prototype,Number.prototype,Boolean.prototypeRegExp.prototype,Error.prototype,Promise.prototype
Global Cleanup
Dangerous globals are removed from the execution context:
Reflect,ProxyWeakRef,FinalizationRegistry
Audit Logging
All code execution is logged for security monitoring:
- Execution start/end times
- Code fingerprint (hash)
- Detected patterns and intents
- Success/failure status
- Resource usage
Known Limitations
- Static Requires Only: Only static
require('package-name')is allowed (dynamic strings blocked) - Import Statements: ES6
importis blocked (userequire()) - Symlinks: Symlinks within workspace are not specially handled
- Non-Whitelisted Packages: Packages not in the whitelist cannot be used even if installed
Security Best Practices
- Minimal Permissions: Only enable Code-Mode when needed
- Workspace Isolation: Keep sensitive files outside
.ncp/workspace/ - Package Auditing: Review whitelisted packages for vulnerabilities
- Network Monitoring: Review network policy for overly permissive rules
- Log Review: Periodically review audit logs for suspicious activity
Reporting Security Issues
If you discover a security vulnerability, please report it via:
- GitHub Security Advisory: https://github.com/anthropics/ncp/security/advisories
Do not disclose security issues publicly until they have been addressed.