@mhingston5/conduit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -0
- package/.github/workflows/ci.yml +88 -0
- package/.github/workflows/pr-checks.yml +90 -0
- package/.tool-versions +2 -0
- package/README.md +177 -0
- package/conduit.yaml.test +3 -0
- package/docs/ARCHITECTURE.md +35 -0
- package/docs/CODE_MODE.md +33 -0
- package/docs/SECURITY.md +52 -0
- package/logo.png +0 -0
- package/package.json +74 -0
- package/src/assets/deno-shim.ts +93 -0
- package/src/assets/python-shim.py +21 -0
- package/src/core/asset.utils.ts +42 -0
- package/src/core/concurrency.service.ts +70 -0
- package/src/core/config.service.ts +147 -0
- package/src/core/execution.context.ts +37 -0
- package/src/core/execution.service.ts +209 -0
- package/src/core/interfaces/app.config.ts +17 -0
- package/src/core/interfaces/executor.interface.ts +31 -0
- package/src/core/interfaces/middleware.interface.ts +12 -0
- package/src/core/interfaces/url.validator.interface.ts +3 -0
- package/src/core/logger.ts +64 -0
- package/src/core/metrics.service.ts +112 -0
- package/src/core/middleware/auth.middleware.ts +56 -0
- package/src/core/middleware/error.middleware.ts +21 -0
- package/src/core/middleware/logging.middleware.ts +25 -0
- package/src/core/middleware/middleware.builder.ts +24 -0
- package/src/core/middleware/ratelimit.middleware.ts +31 -0
- package/src/core/network.policy.service.ts +106 -0
- package/src/core/ops.server.ts +74 -0
- package/src/core/otel.service.ts +41 -0
- package/src/core/policy.service.ts +77 -0
- package/src/core/registries/executor.registry.ts +26 -0
- package/src/core/request.controller.ts +297 -0
- package/src/core/security.service.ts +68 -0
- package/src/core/session.manager.ts +44 -0
- package/src/core/types.ts +47 -0
- package/src/executors/deno.executor.ts +342 -0
- package/src/executors/isolate.executor.ts +281 -0
- package/src/executors/pyodide.executor.ts +327 -0
- package/src/executors/pyodide.worker.ts +195 -0
- package/src/gateway/auth.service.ts +104 -0
- package/src/gateway/gateway.service.ts +345 -0
- package/src/gateway/schema.cache.ts +46 -0
- package/src/gateway/upstream.client.ts +244 -0
- package/src/index.ts +92 -0
- package/src/sdk/index.ts +2 -0
- package/src/sdk/sdk-generator.ts +245 -0
- package/src/sdk/tool-binding.ts +86 -0
- package/src/transport/socket.transport.ts +203 -0
- package/tests/__snapshots__/assets.test.ts.snap +97 -0
- package/tests/assets.test.ts +50 -0
- package/tests/auth.service.test.ts +78 -0
- package/tests/code-mode-lite-execution.test.ts +84 -0
- package/tests/code-mode-lite-gateway.test.ts +150 -0
- package/tests/concurrency.service.test.ts +50 -0
- package/tests/concurrency.test.ts +41 -0
- package/tests/config.service.test.ts +70 -0
- package/tests/contract.test.ts +43 -0
- package/tests/deno.executor.test.ts +68 -0
- package/tests/deno_hardening.test.ts +45 -0
- package/tests/dynamic.tool.test.ts +237 -0
- package/tests/e2e_stdio_upstream.test.ts +197 -0
- package/tests/fixtures/stdio-server.ts +42 -0
- package/tests/gateway.manifest.test.ts +82 -0
- package/tests/gateway.service.test.ts +58 -0
- package/tests/gateway.strict.unit.test.ts +74 -0
- package/tests/gateway.validation.unit.test.ts +89 -0
- package/tests/gateway_validation.test.ts +86 -0
- package/tests/hardening.test.ts +139 -0
- package/tests/hardening_v1.test.ts +72 -0
- package/tests/isolate.executor.test.ts +100 -0
- package/tests/log-limit.test.ts +55 -0
- package/tests/middleware.test.ts +106 -0
- package/tests/ops.server.test.ts +65 -0
- package/tests/policy.service.test.ts +90 -0
- package/tests/pyodide.executor.test.ts +101 -0
- package/tests/reference_mcp.ts +40 -0
- package/tests/remediation.test.ts +119 -0
- package/tests/routing.test.ts +148 -0
- package/tests/schema.cache.test.ts +27 -0
- package/tests/sdk/sdk-generator.test.ts +205 -0
- package/tests/socket.transport.test.ts +182 -0
- package/tests/stdio_upstream.test.ts +54 -0
- package/tsconfig.json +25 -0
- package/tsup.config.ts +22 -0
package/.env.example
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Server Configuration
|
|
2
|
+
PORT=3000
|
|
3
|
+
LOG_LEVEL=info
|
|
4
|
+
NODE_ENV=development
|
|
5
|
+
|
|
6
|
+
# Security
|
|
7
|
+
# Set this to a secure secret for production
|
|
8
|
+
IPC_BEARER_TOKEN=change_me_to_a_secure_token
|
|
9
|
+
|
|
10
|
+
# Upstream Configuration Examples
|
|
11
|
+
# GITHUB_MCP_URL=http://localhost:8080/mcp
|
|
12
|
+
# GITHUB_CLIENT_ID=...
|
|
13
|
+
# GITHUB_CLIENT_SECRET=...
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main, develop]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- name: Checkout code
|
|
16
|
+
uses: actions/checkout@v4
|
|
17
|
+
|
|
18
|
+
- name: Install pnpm
|
|
19
|
+
uses: pnpm/action-setup@v4
|
|
20
|
+
with:
|
|
21
|
+
version: 10.8.0
|
|
22
|
+
|
|
23
|
+
- name: Setup Node.js
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: 24.x
|
|
27
|
+
cache: 'pnpm'
|
|
28
|
+
|
|
29
|
+
- name: Install build dependencies
|
|
30
|
+
run: |
|
|
31
|
+
sudo apt-get update
|
|
32
|
+
sudo apt-get install -y python3 make g++
|
|
33
|
+
|
|
34
|
+
- name: Setup Deno
|
|
35
|
+
uses: denoland/setup-deno@v2
|
|
36
|
+
with:
|
|
37
|
+
deno-version: v2.x
|
|
38
|
+
|
|
39
|
+
- name: Install dependencies
|
|
40
|
+
run: pnpm install --frozen-lockfile
|
|
41
|
+
|
|
42
|
+
- name: Rebuild native modules
|
|
43
|
+
env:
|
|
44
|
+
PYTHON: python3
|
|
45
|
+
run: |
|
|
46
|
+
# Force rebuild isolated-vm which sometimes fails to build automatically
|
|
47
|
+
# Find the directory and build it
|
|
48
|
+
find node_modules/.pnpm -name "isolated-vm" -type d -exec bash -c 'cd "$0" && npm install --build-from-source' {} \;
|
|
49
|
+
# Also run general rebuild just in case
|
|
50
|
+
pnpm rebuild --recursive
|
|
51
|
+
|
|
52
|
+
- name: Run tests
|
|
53
|
+
run: pnpm test
|
|
54
|
+
|
|
55
|
+
- name: Build
|
|
56
|
+
run: pnpm build
|
|
57
|
+
|
|
58
|
+
lint:
|
|
59
|
+
name: Lint
|
|
60
|
+
runs-on: ubuntu-latest
|
|
61
|
+
|
|
62
|
+
steps:
|
|
63
|
+
- name: Checkout code
|
|
64
|
+
uses: actions/checkout@v4
|
|
65
|
+
|
|
66
|
+
- name: Install pnpm
|
|
67
|
+
uses: pnpm/action-setup@v4
|
|
68
|
+
with:
|
|
69
|
+
version: 10.8.0
|
|
70
|
+
|
|
71
|
+
- name: Setup Node.js
|
|
72
|
+
uses: actions/setup-node@v4
|
|
73
|
+
with:
|
|
74
|
+
node-version: 24.x
|
|
75
|
+
cache: 'pnpm'
|
|
76
|
+
|
|
77
|
+
- name: Install dependencies
|
|
78
|
+
run: pnpm install --frozen-lockfile
|
|
79
|
+
|
|
80
|
+
- name: Rebuild native modules
|
|
81
|
+
env:
|
|
82
|
+
PYTHON: python3
|
|
83
|
+
run: |
|
|
84
|
+
find node_modules/.pnpm -name "isolated-vm" -type d -exec bash -c 'cd "$0" && npm install --build-from-source' {} \;
|
|
85
|
+
pnpm rebuild --recursive
|
|
86
|
+
|
|
87
|
+
- name: Check TypeScript
|
|
88
|
+
run: pnpm exec tsc --noEmit
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
name: PR Checks
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
branches: [main, develop]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
validate:
|
|
9
|
+
name: Validate PR
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout code
|
|
14
|
+
uses: actions/checkout@v4
|
|
15
|
+
with:
|
|
16
|
+
fetch-depth: 0
|
|
17
|
+
|
|
18
|
+
- name: Install pnpm
|
|
19
|
+
uses: pnpm/action-setup@v4
|
|
20
|
+
with:
|
|
21
|
+
version: 10.8.0
|
|
22
|
+
|
|
23
|
+
- name: Setup Node.js
|
|
24
|
+
uses: actions/setup-node@v4
|
|
25
|
+
with:
|
|
26
|
+
node-version: 24.x
|
|
27
|
+
cache: 'pnpm'
|
|
28
|
+
|
|
29
|
+
- name: Setup Deno
|
|
30
|
+
uses: denoland/setup-deno@v2
|
|
31
|
+
with:
|
|
32
|
+
deno-version: v2.x
|
|
33
|
+
|
|
34
|
+
- name: Install dependencies
|
|
35
|
+
run: pnpm install --frozen-lockfile
|
|
36
|
+
|
|
37
|
+
- name: Rebuild native modules
|
|
38
|
+
env:
|
|
39
|
+
PYTHON: python3
|
|
40
|
+
run: |
|
|
41
|
+
find node_modules/.pnpm -name "isolated-vm" -type d -exec bash -c 'cd "$0" && npm install --build-from-source' {} \;
|
|
42
|
+
pnpm rebuild --recursive
|
|
43
|
+
|
|
44
|
+
- name: Run tests
|
|
45
|
+
run: pnpm test
|
|
46
|
+
|
|
47
|
+
- name: Build
|
|
48
|
+
run: pnpm build
|
|
49
|
+
|
|
50
|
+
- name: Check TypeScript
|
|
51
|
+
run: pnpm exec tsc --noEmit
|
|
52
|
+
|
|
53
|
+
- name: Check for uncommitted changes
|
|
54
|
+
run: |
|
|
55
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
56
|
+
echo "Error: Found uncommitted changes after build"
|
|
57
|
+
git status --porcelain
|
|
58
|
+
exit 1
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
size-check:
|
|
62
|
+
name: Bundle Size Check
|
|
63
|
+
runs-on: ubuntu-latest
|
|
64
|
+
|
|
65
|
+
steps:
|
|
66
|
+
- name: Checkout code
|
|
67
|
+
uses: actions/checkout@v4
|
|
68
|
+
|
|
69
|
+
- name: Install pnpm
|
|
70
|
+
uses: pnpm/action-setup@v4
|
|
71
|
+
with:
|
|
72
|
+
version: 10.8.0
|
|
73
|
+
|
|
74
|
+
- name: Setup Node.js
|
|
75
|
+
uses: actions/setup-node@v4
|
|
76
|
+
with:
|
|
77
|
+
node-version: 24.x
|
|
78
|
+
cache: 'pnpm'
|
|
79
|
+
|
|
80
|
+
- name: Install dependencies
|
|
81
|
+
run: pnpm install --frozen-lockfile
|
|
82
|
+
|
|
83
|
+
- name: Build
|
|
84
|
+
run: pnpm build
|
|
85
|
+
|
|
86
|
+
- name: Check bundle size
|
|
87
|
+
run: |
|
|
88
|
+
echo "Bundle size:"
|
|
89
|
+
du -sh dist/
|
|
90
|
+
find dist/ -name "*.js" -exec du -h {} \; | sort -h
|
package/.tool-versions
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="./logo.png" alt="Conduit Logo" width="400"/>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
# Conduit
|
|
6
|
+
|
|
7
|
+
<div align="center">
|
|
8
|
+
|
|
9
|
+
[](https://www.npmjs.com/package/@mhingston/conduit)
|
|
10
|
+
[](https://opensource.org/licenses/MIT)
|
|
11
|
+
[](https://www.typescriptlang.org/)
|
|
12
|
+
[](https://nodejs.org/)
|
|
13
|
+
[](https://modelcontextprotocol.io/)
|
|
14
|
+
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
## What is Conduit?
|
|
18
|
+
|
|
19
|
+
Conduit is a **secure Code Mode execution substrate** for [MCP](https://modelcontextprotocol.io/) agents.
|
|
20
|
+
|
|
21
|
+
It lets agents:
|
|
22
|
+
- generate **real TypeScript or Python code**
|
|
23
|
+
- call tools via **language-native APIs** (`tools.github.createIssue()`)
|
|
24
|
+
- run that code in **isolated, resource-governed sandboxes**
|
|
25
|
+
- without exposing credentials or the host environment
|
|
26
|
+
|
|
27
|
+
Conduit is optimized for:
|
|
28
|
+
- [Code Mode](./docs/CODE_MODE.md) (not JSON tool calling)
|
|
29
|
+
- composable multi-tool execution
|
|
30
|
+
- strict safety, limits, and observability
|
|
31
|
+
|
|
32
|
+
## What Conduit Is Not
|
|
33
|
+
|
|
34
|
+
- ❌ A general-purpose script runner
|
|
35
|
+
- ❌ An LLM gateway or provider abstraction
|
|
36
|
+
- ❌ A plugin UI or agent framework
|
|
37
|
+
- ❌ A long-lived compute environment
|
|
38
|
+
|
|
39
|
+
Conduit executes **short-lived, isolated programs** with explicit limits.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
npm install @mhingston5/conduit
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 5-Minute Quick Start (Code Mode)
|
|
50
|
+
|
|
51
|
+
### 1. Start Conduit
|
|
52
|
+
```bash
|
|
53
|
+
pnpm install
|
|
54
|
+
# Build the project
|
|
55
|
+
npm run build
|
|
56
|
+
# Start the server
|
|
57
|
+
node dist/index.js
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### 2. Register an upstream MCP server
|
|
61
|
+
|
|
62
|
+
Create a `conduit.yaml` in the root:
|
|
63
|
+
```yaml
|
|
64
|
+
upstreams:
|
|
65
|
+
- id: github
|
|
66
|
+
type: http
|
|
67
|
+
url: "http://localhost:3000/mcp"
|
|
68
|
+
# Or use local stdio for testing:
|
|
69
|
+
- id: filesystem
|
|
70
|
+
type: stdio
|
|
71
|
+
command: npx
|
|
72
|
+
args: ["-y", "@modelcontextprotocol/server-filesystem", "/tmp"]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Execute TypeScript
|
|
76
|
+
|
|
77
|
+
Using any [MCP Client](https://modelcontextprotocol.io/clients) (Claude Desktop, etc.), call `mcp.executeTypeScript`:
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
// The agent writes this code:
|
|
81
|
+
const result = await tools.filesystem.list_allowed_directories();
|
|
82
|
+
console.log("Files:", result);
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### 4. Result
|
|
86
|
+
|
|
87
|
+
Conduit runs the code, handles the tool call securely, and returns:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"stdout": "Files: ['/tmp']\n",
|
|
92
|
+
"stderr": "",
|
|
93
|
+
"exitCode": 0
|
|
94
|
+
}
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## How It Works (High Level)
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
LLM → generates code
|
|
103
|
+
↓
|
|
104
|
+
Client → sends code to Conduit
|
|
105
|
+
↓
|
|
106
|
+
Conduit:
|
|
107
|
+
- injects a `tools.*` SDK
|
|
108
|
+
- enforces limits + allowlists
|
|
109
|
+
- runs code in an isolated runtime (Deno / Pyodide / Isolate)
|
|
110
|
+
↓
|
|
111
|
+
Tools are called via the Gateway
|
|
112
|
+
↓
|
|
113
|
+
Results returned as stdout / stderr
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
For implementation details, see [Architecture](./docs/ARCHITECTURE.md).
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
## Security & Isolation Guarantees
|
|
121
|
+
|
|
122
|
+
Each execution:
|
|
123
|
+
- runs in a fresh sandbox (no state reuse)
|
|
124
|
+
- has strict CPU, memory, output, and log limits
|
|
125
|
+
- cannot access host credentials or filesystem
|
|
126
|
+
- can only call explicitly allowed tools
|
|
127
|
+
- is forcibly terminated on violation
|
|
128
|
+
|
|
129
|
+
**SSRF protection**:
|
|
130
|
+
- private IP ranges blocked
|
|
131
|
+
- DNS rebinding prevented
|
|
132
|
+
- IPv6-mapped IPv4 handled
|
|
133
|
+
|
|
134
|
+
**Secrets**:
|
|
135
|
+
- never injected into user code
|
|
136
|
+
- redacted from logs by default
|
|
137
|
+
|
|
138
|
+
See [Security](./docs/SECURITY.md) for the full threat model.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Strict vs Permissive Tool Validation
|
|
143
|
+
|
|
144
|
+
By default, Conduit runs in **Permissive Mode** to allow easy exploration.
|
|
145
|
+
|
|
146
|
+
**Strict mode**:
|
|
147
|
+
- blocks unknown tools
|
|
148
|
+
- blocks tools without schemas
|
|
149
|
+
- enforces argument validation
|
|
150
|
+
|
|
151
|
+
**Recommended**:
|
|
152
|
+
- permissive mode for exploration
|
|
153
|
+
- strict mode for production agents
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## Design Principles
|
|
158
|
+
|
|
159
|
+
- **Code over configuration**: Logic belongs in code, not yaml.
|
|
160
|
+
- **Isolation over reuse**: Every execution is fresh.
|
|
161
|
+
- **Explicit limits over best-effort**: Fail fast if limits are breached.
|
|
162
|
+
- **SDKs over RPC**: Agents should write code against libraries, not protocols.
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Advanced Documentation
|
|
167
|
+
|
|
168
|
+
- [Architecture](./docs/ARCHITECTURE.md) - Internals, IPC, Executors
|
|
169
|
+
- [Security](./docs/SECURITY.md) - Threat model, specific mitigations
|
|
170
|
+
- [Code Mode Philosophy](./docs/CODE_MODE.md) - Why we generate code
|
|
171
|
+
|
|
172
|
+
## Note on Unix Sockets
|
|
173
|
+
|
|
174
|
+
When using Unix domain sockets (`path` in configuration), Conduit does not automatically `unlink` the socket file on startup. It is recommended to ensure the socket path is cleaned up by the deployment environment or startup script to avoid `EADDRINUSE` errors.
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Conduit Architecture
|
|
2
|
+
|
|
3
|
+
Conduit is built with a modular architecture, designed to be secure, observable, and composable.
|
|
4
|
+
|
|
5
|
+
## Core Components
|
|
6
|
+
|
|
7
|
+
- **`src/core`**: Core services (Config, Logger, Concurrency, Ops, Request Dispatching).
|
|
8
|
+
- **`src/executors`**: Secure runtime environments for code execution.
|
|
9
|
+
- **`src/gateway`**: Upstream client management, auth (OAuth2/API Keys), and schema caching.
|
|
10
|
+
- **`src/transport`**: Network abstraction layer.
|
|
11
|
+
- **`src/sdk`**: SDK generation for typed tool bindings.
|
|
12
|
+
|
|
13
|
+
## Detailed Flow
|
|
14
|
+
|
|
15
|
+
1. **Client Request**: A client (like VS Code or Claude Desktop) sends a JSON-RPC request (`mcp.executeTypeScript`).
|
|
16
|
+
2. **Transportation**: The request is received via `SocketTransport` (TCP/UDS/Pipe).
|
|
17
|
+
3. **Dispatch**: `RequestController` validates the request and session tokens.
|
|
18
|
+
4. **Tool Discovery**: `GatewayService` aggregates tools from all upstream MCP servers.
|
|
19
|
+
5. **SDK Generation**: The `SdkService` generates a type-safe SDK (`tools.*`) based on discovered schemas.
|
|
20
|
+
6. **Execution**:
|
|
21
|
+
- **Deno**: Spawns a Deno subprocess with limited permissions.
|
|
22
|
+
- **Browser-style (In-Process)**: Uses `isolated-vm` for high-speed JS logic.
|
|
23
|
+
- **Python**: Uses Pyodide in a worker thread.
|
|
24
|
+
7. **Result**: Stdout/stderr and return values are captured and returned to the client.
|
|
25
|
+
|
|
26
|
+
## IPC & Transport
|
|
27
|
+
|
|
28
|
+
Conduit supports multiple transports:
|
|
29
|
+
- **TCP**: Standard network sockets.
|
|
30
|
+
- **Unix Domain Sockets**: For local IPC.
|
|
31
|
+
- **Windows Named Pipes**: For local IPC on Windows.
|
|
32
|
+
|
|
33
|
+
## Schema Caching
|
|
34
|
+
|
|
35
|
+
To optimize performance, upstreams are polled for tool schemas, which are cached with a TTL. This prevents repeated network calls during high-frequency execution loops.
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Code Mode Philosophy
|
|
2
|
+
|
|
3
|
+
Conduit is built for **Code Mode**.
|
|
4
|
+
|
|
5
|
+
## What is Code Mode?
|
|
6
|
+
|
|
7
|
+
"Code Mode" is an architectural pattern for AI Agents where:
|
|
8
|
+
- **LLMs generate code**, not JSON tool calls.
|
|
9
|
+
- **Tools are libraries**, not RPC endpoints.
|
|
10
|
+
- **Execution is sandboxed**, not local.
|
|
11
|
+
|
|
12
|
+
Reference: [Cloudflare Code Mode](https://developers.cloudflare.com/agents/code-mode/)
|
|
13
|
+
|
|
14
|
+
## Why Code Mode?
|
|
15
|
+
|
|
16
|
+
### 1. Context Efficiency (98% Reduction)
|
|
17
|
+
Traditional "Tool Use" requires pasting typically huge JSON schemas for every available tool into the LLM system prompt.
|
|
18
|
+
In Code Mode, you paste **0 schemas**. The agent writes code to *discover* tools dynamically at runtime, or assumes standard SDK shapes.
|
|
19
|
+
|
|
20
|
+
### 2. Composition & Logic
|
|
21
|
+
Agent logic (loops, conditionals, retries, variable transformations) happens **in the code**, not in the LLM's context window.
|
|
22
|
+
- **Old Way**: LLM -> Tool Call -> LLM -> Tool Call -> LLM -> Result
|
|
23
|
+
- **Code Mode**: LLM -> `for (item in items) { await tool(item) }` -> Result
|
|
24
|
+
|
|
25
|
+
### 3. Safety
|
|
26
|
+
Because logic executes in a sandbox, you can enforce limits on loops, memory, and duration that are impossible to enforce on an LLM's token stream.
|
|
27
|
+
|
|
28
|
+
## Implementation in Conduit
|
|
29
|
+
|
|
30
|
+
Conduit provides:
|
|
31
|
+
1. **`executeTypeScript` / `executePython`**: The entry points.
|
|
32
|
+
2. **`tools.*` SDK**: A dynamically generated client injected into the runtime.
|
|
33
|
+
3. **Sandboxes**: Deno, Pyodide, and isolated-vm to run the code safely.
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Security & Isolation Guarantees
|
|
2
|
+
|
|
3
|
+
Conduit implements a defense-in-depth security model to ensure safe code execution.
|
|
4
|
+
|
|
5
|
+
## Isolation Guarantees
|
|
6
|
+
|
|
7
|
+
Each execution:
|
|
8
|
+
- runs in a **fresh sandbox** (no state reuse).
|
|
9
|
+
- has strict **CPU, memory, output, and log limits**.
|
|
10
|
+
- cannot access **host credentials or filesystem** (unless explicitly allowed via tools).
|
|
11
|
+
- can only call **explicitly allowed tools**.
|
|
12
|
+
- is **forcibly terminated** on violation.
|
|
13
|
+
|
|
14
|
+
## SSRF Protection
|
|
15
|
+
|
|
16
|
+
Conduit enforces strict Server-Side Request Forgery (SSRF) protections on upstreams:
|
|
17
|
+
- **Private IP ranges blocked** (unless explicitly allowed).
|
|
18
|
+
- **DNS rebinding prevented** by verifying IP resolution before connection.
|
|
19
|
+
- **IPv6-mapped IPv4** addresses are handled correctly.
|
|
20
|
+
- **HTTP Redirects** are visually disabled or strictly validated.
|
|
21
|
+
|
|
22
|
+
## Secrets Management
|
|
23
|
+
|
|
24
|
+
- **Injection**: Secrets are never injected into user code as environment variables (unless via specific secure tool config).
|
|
25
|
+
- **Redaction**: Logs are automatically scrubbed for known secrets and PII patterns.
|
|
26
|
+
|
|
27
|
+
## Authorization
|
|
28
|
+
|
|
29
|
+
- **Master Token**: Full access to all methods (set via `IPC_BEARER_TOKEN`).
|
|
30
|
+
- **Session Tokens**: Generated per-execution, restricted to `mcp.discoverTools` and `mcp.callTool` only.
|
|
31
|
+
- **Tool Allowlisting**: Per-request scope limits which tools code can discover/call (e.g., `["github.*"]`).
|
|
32
|
+
|
|
33
|
+
## Runtime Security
|
|
34
|
+
|
|
35
|
+
- **Deno**: Uses OS-level sandbox permissions (`--allow-net`, `--allow-read` are restricted).
|
|
36
|
+
- **Pyodide**: Runs in a Worker Thread with no access to the main thread's DOM or context.
|
|
37
|
+
- **In-Process JS (isolated-vm)**: Uses V8 isolates for memory isolation but shares the host process.
|
|
38
|
+
|
|
39
|
+
## Production Hardening Recommendations
|
|
40
|
+
|
|
41
|
+
While Conduit provides robust application-level sandboxing, `isolated-vm` and Deno subprocesses still share the host kernel. For **multi-tenant** or **hostile** workloads, you must implement defense-in-depth by wrapping Conduit itself.
|
|
42
|
+
|
|
43
|
+
### Tiered Isolation Model
|
|
44
|
+
|
|
45
|
+
| Component | Protection Against | Vulnerable To |
|
|
46
|
+
|-----------|--------------------|---------------|
|
|
47
|
+
| **Conduit (Code)** | Logical errors, resource exhaustion, unauthorized tool use | Runtime/V8 escapes, Kernel exploits |
|
|
48
|
+
| **Container (Docker)** | Filesystem access, network enumeration | Kernel exploits, Container breakouts |
|
|
49
|
+
| **MicroVM (Firecracker/gVisor)** | Kernel exploits, complete system compromise | Hypervisor exploits (rare) |
|
|
50
|
+
|
|
51
|
+
**Recommendation:**
|
|
52
|
+
For production deployments executing untrusted code, deploy Conduit inside a **gVisor-backed container** or a **Firecracker MicroVM** (like AWS Fargate or Fly.io Machines). This prevents a V8/Deno escape from compromising the host infrastructure.
|
package/logo.png
ADDED
|
Binary file
|
package/package.json
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mhingston5/conduit",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "A secure Code Mode execution substrate for MCP agents",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"publishConfig": {
|
|
8
|
+
"access": "public"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "vitest run",
|
|
12
|
+
"test:watch": "vitest",
|
|
13
|
+
"build": "tsup"
|
|
14
|
+
},
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"agent",
|
|
18
|
+
"sandbox",
|
|
19
|
+
"code-execution",
|
|
20
|
+
"typescript",
|
|
21
|
+
"python",
|
|
22
|
+
"isolation"
|
|
23
|
+
],
|
|
24
|
+
"author": "Mark Hingston",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/mhingston/conduit.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/mhingston/conduit/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/mhingston/conduit#readme",
|
|
34
|
+
"packageManager": "pnpm@10.8.0",
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": "24.0.0"
|
|
37
|
+
},
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
40
|
+
"@opentelemetry/api": "^1.9.0",
|
|
41
|
+
"@opentelemetry/auto-instrumentations-node": "^0.67.3",
|
|
42
|
+
"@opentelemetry/exporter-prometheus": "^0.208.0",
|
|
43
|
+
"@opentelemetry/instrumentation-pino": "^0.55.1",
|
|
44
|
+
"@opentelemetry/resources": "^2.2.0",
|
|
45
|
+
"@opentelemetry/sdk-node": "^0.208.0",
|
|
46
|
+
"@opentelemetry/semantic-conventions": "^1.38.0",
|
|
47
|
+
"ajv": "^8.17.1",
|
|
48
|
+
"ajv-formats": "^3.0.1",
|
|
49
|
+
"axios": "^1.13.2",
|
|
50
|
+
"dotenv": "^17.2.3",
|
|
51
|
+
"fastify": "^5.6.2",
|
|
52
|
+
"isolated-vm": "^6.0.2",
|
|
53
|
+
"js-yaml": "^4.1.1",
|
|
54
|
+
"lru-cache": "^11.2.4",
|
|
55
|
+
"p-limit": "^7.2.0",
|
|
56
|
+
"pino": "^10.1.0",
|
|
57
|
+
"pyodide": "^0.29.0",
|
|
58
|
+
"uuid": "^13.0.0",
|
|
59
|
+
"zod": "^4.3.5"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@types/js-yaml": "^4.0.9",
|
|
63
|
+
"@types/node": "^25.0.3",
|
|
64
|
+
"@types/pino": "^7.0.5",
|
|
65
|
+
"@types/uuid": "^11.0.0",
|
|
66
|
+
"pino-pretty": "^13.1.3",
|
|
67
|
+
"ts-node": "^10.9.2",
|
|
68
|
+
"tsup": "^8.5.1",
|
|
69
|
+
"tsx": "^4.21.0",
|
|
70
|
+
"typescript": "^5.9.3",
|
|
71
|
+
"vitest": "^4.0.16",
|
|
72
|
+
"vitest-mock-extended": "^3.1.0"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
// Deno Shim for Conduit - Code Mode SDK
|
|
3
|
+
const IPC_ADDRESS = '__CONDUIT_IPC_ADDRESS__';
|
|
4
|
+
const IPC_TOKEN = '__CONDUIT_IPC_TOKEN__';
|
|
5
|
+
|
|
6
|
+
async function sendIPCRequest(method: string, params: any) {
|
|
7
|
+
if (!IPC_ADDRESS) throw new Error('Conduit IPC address not configured');
|
|
8
|
+
|
|
9
|
+
let conn: any;
|
|
10
|
+
try {
|
|
11
|
+
if (IPC_ADDRESS.includes(':')) {
|
|
12
|
+
const lastColon = IPC_ADDRESS.lastIndexOf(':');
|
|
13
|
+
const hostname = IPC_ADDRESS.substring(0, lastColon);
|
|
14
|
+
const port = IPC_ADDRESS.substring(lastColon + 1);
|
|
15
|
+
|
|
16
|
+
// Normalize hostname for Deno connect
|
|
17
|
+
let targetHost = hostname.replace(/[\[\]]/g, '');
|
|
18
|
+
if (targetHost === '0.0.0.0' || targetHost === '::' || targetHost === '::1' || targetHost === '') {
|
|
19
|
+
targetHost = '127.0.0.1';
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
conn = await (Deno as any).connect({
|
|
23
|
+
hostname: targetHost,
|
|
24
|
+
port: Number(port)
|
|
25
|
+
});
|
|
26
|
+
} else {
|
|
27
|
+
conn = await (Deno as any).connect({ transport: 'unix', path: IPC_ADDRESS });
|
|
28
|
+
}
|
|
29
|
+
} catch (err: any) {
|
|
30
|
+
throw new Error(`Failed to connect to Conduit IPC (${IPC_ADDRESS}): ${err.message}`);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const id = Math.random().toString(36).substring(7);
|
|
35
|
+
const request = {
|
|
36
|
+
jsonrpc: '2.0',
|
|
37
|
+
id,
|
|
38
|
+
method,
|
|
39
|
+
params: params || {},
|
|
40
|
+
auth: { bearerToken: IPC_TOKEN }
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const encoder = new TextEncoder();
|
|
44
|
+
await conn.write(encoder.encode(JSON.stringify(request) + '\n'));
|
|
45
|
+
|
|
46
|
+
const decoder = new TextDecoder();
|
|
47
|
+
let buffer = '';
|
|
48
|
+
const chunk = new Uint8Array(2 * 1024 * 1024); // 2MB buffer for large tool returns
|
|
49
|
+
|
|
50
|
+
while (true) {
|
|
51
|
+
const n = await conn.read(chunk);
|
|
52
|
+
if (n === null) throw new Error('IPC connection closed by host before receiving response');
|
|
53
|
+
|
|
54
|
+
buffer += decoder.decode(chunk.subarray(0, n));
|
|
55
|
+
const lines = buffer.split('\n');
|
|
56
|
+
buffer = lines.pop() || ''; // Keep partial line
|
|
57
|
+
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
if (!line.trim()) continue;
|
|
60
|
+
try {
|
|
61
|
+
const response = JSON.parse(line);
|
|
62
|
+
if (response.id === id) {
|
|
63
|
+
if (response.error) {
|
|
64
|
+
const error = new Error(response.error.message);
|
|
65
|
+
(error as any).code = response.error.code;
|
|
66
|
+
(error as any).data = response.error.data;
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
return response.result;
|
|
70
|
+
}
|
|
71
|
+
} catch (e: any) {
|
|
72
|
+
if (e.message.includes('JSON')) continue;
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
} finally {
|
|
78
|
+
conn.close();
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Internal tool call function - used by generated SDK
|
|
83
|
+
const __internalCallTool = async (name: string, params: any) => {
|
|
84
|
+
return await sendIPCRequest('mcp.callTool', { name, arguments: params });
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// Tool discovery - still available for dynamic scenarios
|
|
88
|
+
(globalThis as any).discoverMCPTools = async (options: any) => {
|
|
89
|
+
const result = await sendIPCRequest('mcp.discoverTools', options);
|
|
90
|
+
return result.tools || [];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// __CONDUIT_SDK_INJECTION__
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# Python Shim for Conduit - Code Mode SDK
|
|
2
|
+
import asyncio
|
|
3
|
+
|
|
4
|
+
async def discover_mcp_tools(options=None):
|
|
5
|
+
"""Discover available MCP tools from the gateway."""
|
|
6
|
+
# These functions are injected into the Python global scope by the executor
|
|
7
|
+
res = await discover_mcp_tools_js(options)
|
|
8
|
+
# Pyodide's JS proxy handles conversion broadly, but we might need to convert the tools list
|
|
9
|
+
if hasattr(res, 'to_py'):
|
|
10
|
+
data = res.to_py()
|
|
11
|
+
return data.get('tools', []) if isinstance(data, dict) else []
|
|
12
|
+
return []
|
|
13
|
+
|
|
14
|
+
async def _internal_call_tool(name, arguments):
|
|
15
|
+
"""Internal tool call function - used by generated SDK."""
|
|
16
|
+
res = await call_mcp_tool_js(name, arguments)
|
|
17
|
+
if hasattr(res, 'to_py'):
|
|
18
|
+
return res.to_py()
|
|
19
|
+
return res
|
|
20
|
+
|
|
21
|
+
# __CONDUIT_SDK_INJECTION__
|