Guide to using external dependencies (npm packages, deno.land modules) in the TEE.
The TEE supports external dependencies through a two-phase approach:
- Setup Phase (with network): Dependencies are downloaded and cached
- Execution Phase (without network): Code runs using cached dependencies
This provides both convenience (use npm/deno.land) and security (no network during execution).
When you create an environment with dependencies:
{
"mainModule": "main.ts",
"modules": {
"main.ts": "import { format } from 'npm:date-fns@3';\nexport async function handler(event, context) {\n return { formatted: format(new Date(), 'yyyy-MM-dd') };\n}"
},
"dependencies": {
"npm": ["date-fns@3", "lodash@4"],
"deno": ["https://deno.land/std@0.224.0/assert/mod.ts"]
},
"ttlSeconds": 3600
}The TEE will:
- Create a Docker volume
- Write your modules to the volume
- Run a dependency installation step with network enabled
- Cache all dependencies in the volume
- Store the environment (ready for execution)
When you execute code:
{
"data": { "value": 42 }
}The TEE will:
- Spawn a container without network (
--network=none) - Mount the volume with cached dependencies
- Run your code using the cached deps
- Return the result
Use the npm: specifier:
// main.ts
import { format } from "npm:date-fns@3";
import _ from "npm:lodash@4";
export async function handler(event, context) {
const date = format(new Date(), "yyyy-MM-dd");
const data = _.chunk([1, 2, 3, 4], 2);
return { date, data };
}Setup request:
{
"mainModule": "main.ts",
"modules": {
"main.ts": "..."
},
"dependencies": {
"npm": ["date-fns@3", "lodash@4"]
}
}Use the https://deno.land/std/ URL:
// main.ts
import { assert } from "https://deno.land/std@0.224.0/assert/mod.ts";
import { delay } from "https://deno.land/std@0.224.0/async/delay.ts";
export async function handler(event, context) {
assert(event.data.value > 0, "Value must be positive");
await delay(100);
return { validated: true };
}Setup request:
{
"mainModule": "main.ts",
"modules": {
"main.ts": "..."
},
"dependencies": {
"deno": [
"https://deno.land/std@0.224.0/assert/mod.ts",
"https://deno.land/std@0.224.0/async/delay.ts"
]
}
}Use full URLs:
// main.ts
import { v4 } from "https://deno.land/x/uuid@v3.0.0/mod.ts";
export async function handler(event, context) {
return { id: v4.generate() };
}Setup request:
{
"mainModule": "main.ts",
"modules": {
"main.ts": "..."
},
"dependencies": {
"deno": ["https://deno.land/x/uuid@v3.0.0/mod.ts"]
}
}curl -X POST http://localhost:8080/environments/setup \
-H "Content-Type: application/json" \
-d '{
"mainModule": "main.ts",
"modules": {
"main.ts": "import { format } from \"npm:date-fns@3\";\nimport { delay } from \"https://deno.land/std@0.224.0/async/delay.ts\";\n\nexport async function handler(event, context) {\n await delay(10);\n const formatted = format(new Date(event.data.timestamp), \"PPP\");\n return {\n formatted,\n executionId: context.executionId\n };\n}"
},
"dependencies": {
"npm": ["date-fns@3"],
"deno": ["https://deno.land/std@0.224.0/async/delay.ts"]
},
"ttlSeconds": 3600
}'Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"volumeName": "tee-env-550e8400-...",
"mainModule": "main.ts",
"status": "ready",
"dependenciesCached": true,
"createdAt": "2025-01-15T10:30:00Z"
}ENV_ID="550e8400-e29b-41d4-a716-446655440000"
curl -X POST http://localhost:8080/environments/$ENV_ID/execute \
-H "Content-Type: application/json" \
-d '{
"data": {
"timestamp": "2025-01-15T10:30:00Z"
}
}'Response:
{
"id": "exec-abc123...",
"exitCode": 0,
"stdout": "{\"formatted\":\"January 15th, 2025\",\"executionId\":\"exec-abc123...\"}",
"stderr": "",
"durationMs": 145
}During setup, the TEE runs a special installation container:
docker run --rm \
--network=bridge \ # Network ENABLED
-v volume:/workspace \
-v volume:/deno-dir \
deno-runtime:latest \
sh -c "
export DENO_DIR=/deno-dir
# Install npm packages
deno cache --node-modules-dir npm:date-fns@3
deno cache --node-modules-dir npm:lodash@4
# Cache deno modules
deno cache https://deno.land/std@0.224.0/assert/mod.ts
deno cache https://deno.land/std@0.224.0/async/delay.ts
"This creates:
/workspace/- Your code modules/deno-dir/- Cached dependencies
During execution, the TEE runs:
docker run --rm \
--network=none \ # Network DISABLED
-v volume:/workspace:ro \
-v volume:/deno-dir:ro \
-e DENO_DIR=/deno-dir \
deno-runtime:latestDependencies are loaded from /deno-dir/ cache - no network needed!
Always specify exact versions:
// ✓ Good
import { format } from "npm:date-fns@3.0.0";
import { assert } from "https://deno.land/std@0.224.0/assert/mod.ts";
// ✗ Bad
import { format } from "npm:date-fns"; // Unpredictable
import { assert } from "https://deno.land/std/assert/mod.ts"; // No versionOnly include what you need:
// ✓ Good - specific import
import { format } from "npm:date-fns@3/format";
// ✗ Bad - imports everything
import * as dateFns from "npm:date-fns@3";Include all transitive dependencies in the setup request:
{
"dependencies": {
"npm": [
"express@4",
"body-parser@1.20.0", // Express dependency
"cookie-parser@1.4.6" // Express dependency
]
}
}Test with Deno locally before uploading:
# Test your code with dependencies
deno run --allow-net --allow-env main.ts
# Verify imports work
deno cache main.tsCause: Dependency wasn't cached during setup
Solution: Add the dependency to the dependencies field:
{
"dependencies": {
"npm": ["missing-package@1.0.0"]
}
}Cause: Dependency not in cache, trying to download during execution
Solution: Ensure dependency was listed in setup request
Cause: Large dependencies being downloaded
Solutions:
- Use specific imports instead of entire packages
- Consider bundling dependencies locally first
- Split large dependencies across multiple environments
Cause: Multiple versions of same package
Solution: Use exact versions and list all dependencies explicitly
During setup (with network):
- Dependencies downloaded from trusted registries (npm, deno.land)
- Code is not executed during dependency installation
- Network is only available during caching, not execution
During execution (without network):
- No network access - dependencies run from cache only
- Can't download additional code or exfiltrate data
- Dependencies are read-only
Risk: Compromised npm/deno packages
Mitigations:
- Pin exact versions
- Review dependency code before use
- Use well-known, audited packages
- Consider vendoring critical dependencies
- Set short TTLs to limit exposure
For maximum performance and security, pre-bundle dependencies:
# Bundle locally
deno bundle main.ts bundle.js
# Upload bundle (no dependencies needed)
curl -X POST http://localhost:8080/environments/setup \
-H "Content-Type: application/json" \
-d '{
"mainModule": "bundle.js",
"modules": {
"bundle.js": "...<bundled code>..."
}
}'This approach:
- ✓ No dependency installation needed
- ✓ Faster setup
- ✓ No external code at runtime
- ✓ Maximum security
Planned features:
- Dependency lockfile - Cryptographic hashes for integrity
- Shared dependency cache - Reuse deps across environments
- Dependency scanning - Automated vulnerability detection
- Allowlist/blocklist - Restrict allowed packages
- Custom registries - Private npm/deno registries
interface SetupRequest {
mainModule: string;
modules: Record<string, string>;
dependencies?: {
npm?: string[]; // npm packages: ["pkg@version"]
deno?: string[]; // deno URLs: ["https://..."]
};
ttlSeconds?: number;
}interface SetupResponse {
id: string;
volumeName: string;
mainModule: string;
status: "ready" | "installing" | "failed";
dependenciesCached: boolean;
dependencyCount?: number;
createdAt: string;
}- TESTING.md - Test dependency handling
- TYPESCRIPT_EXAMPLES.md - Code examples
- SECURITY.md - Security model