Skip to content

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:

  1. Static Analysis - Block dangerous patterns before execution
  2. Semantic Validation - Detect malicious intent patterns
  3. Runtime Sandboxing - Isolate code execution with resource limits
  4. Sandboxed File System - Confine file operations to a workspace directory
  5. Network Policy - Control and audit network requests

Execution Sandbox Hierarchy

Code execution uses a tiered approach, selecting the most secure available option:

TierSandbox TypeIsolation LevelDescription
1IsolatedVMHighestSeparate V8 isolate (Cloudflare Workers technology)
2SubprocessHighSeparate Node.js process
3Worker ThreadMediumSeparate V8 context with resource limits
4VM ModuleBasicSame-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 execution
  • process, global, globalThis - Node.js runtime access
  • require, module - Unrestricted module loading (whitelisted packages allowed)
  • __dirname, __filename - Path disclosure
  • Reflect, Proxy, Symbol - Metaprogramming (sandbox escape vectors)
  • WeakRef, FinalizationRegistry - GC probing

Blocked Property Access

  • __proto__, constructor, prototype - Prototype pollution
  • defineProperty, setPrototypeOf - Object manipulation
  • getOwnPropertyDescriptor - Descriptor access

Blocked Modules

Direct imports of dangerous Node.js modules are blocked:

  • fs, child_process, cluster - System access
  • net, dgram, dns, tls - Network access
  • vm, worker_threads, v8 - Runtime manipulation
  • os, 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

  1. Path Confinement: All paths are resolved relative to workspace root
  2. Traversal Prevention: ../ and absolute paths outside workspace are blocked
  3. Escape Detection: Paths are normalized and validated before any operation

Example

typescript
// ✅ 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');        // SandboxEscapeError

Sandboxed FS API

The following async operations are available:

OperationDescription
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:

PackagePurpose
pdf-libPDF creation and manipulation
docxWord document generation
pptxgenjsPowerPoint generation
xlsxExcel spreadsheet handling
papaparseCSV parsing
cheerioHTML/XML parsing
axiosHTTP requests (via network policy)
lodashUtility functions
date-fnsDate manipulation
uuidUUID generation
crypto-jsCryptographic functions
canvasImage manipulation
sharpHigh-performance image processing
jimpImage processing
pathPath utilities (safe)

Runtime Package Approval

When code requires a package not in the built-in whitelist:

  1. Pre-execution scan: Code is analyzed for require() calls before execution
  2. Blocked packages rejected: Node.js built-ins (fs, child_process, etc.) cannot be approved
  3. 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
  4. Temporary approval: Approvals are stored in-memory only (no persistent security holes)
  5. 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:

bash
npm install xlsx pdf-lib  # etc.

Network Policy

Network requests from Code-Mode are controlled:

  1. Policy Enforcement: Requests checked against allowed domains/patterns
  2. Request Auditing: All network calls are logged
  3. Rate Limiting: Prevent abuse through excessive requests
  4. 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:

  1. Scan code for require() calls before execution
  2. Pre-load packages that are in the whitelist BEFORE prototype freezing
  3. Freeze prototypes after packages have initialized their classes
  4. 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.prototype
  • Array.prototype
  • Function.prototype
  • String.prototype, Number.prototype, Boolean.prototype
  • RegExp.prototype, Error.prototype, Promise.prototype

Global Cleanup

Dangerous globals are removed from the execution context:

  • Reflect, Proxy
  • WeakRef, 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

  1. Static Requires Only: Only static require('package-name') is allowed (dynamic strings blocked)
  2. Import Statements: ES6 import is blocked (use require())
  3. Symlinks: Symlinks within workspace are not specially handled
  4. Non-Whitelisted Packages: Packages not in the whitelist cannot be used even if installed

Security Best Practices

  1. Minimal Permissions: Only enable Code-Mode when needed
  2. Workspace Isolation: Keep sensitive files outside .ncp/workspace/
  3. Package Auditing: Review whitelisted packages for vulnerabilities
  4. Network Monitoring: Review network policy for overly permissive rules
  5. Log Review: Periodically review audit logs for suspicious activity

Reporting Security Issues

If you discover a security vulnerability, please report it via:

Do not disclose security issues publicly until they have been addressed.

Released under the Elastic License 2.0.