Skip to content

Commit 1178919

Browse files
committed
fix: align init start scripts, docs, and security policy
docs: update runtime and security notes
1 parent 45fcdb8 commit 1178919

9 files changed

Lines changed: 93 additions & 17 deletions

File tree

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,13 @@ Ignite runs JavaScript/TypeScript code in **secure, isolated Docker containers**
5151

5252
| Metric | Value |
5353
|--------|-------|
54-
| **Runtimes** | Bun |
54+
| **Runtimes** | Bun (default), Node, Deno, QuickJS |
5555
| **Base Images** | Alpine (minimal) |
5656
| **Platforms** | Linux x64/ARM64, macOS x64/ARM64 |
5757
| **Dependencies** | Docker only |
5858

59+
Note: Bun is the default runtime. Other runtimes are supported but increase the security attack surface; use them only when required and review service code and dependencies carefully.
60+
5961
<img src="https://raw.githubusercontent.com/andreasbm/readme/master/assets/lines/rainbow.png" alt="rainbow" width="100%">
6062

6163
## Install

docs/api.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -647,6 +647,8 @@ service:
647647
| `deno` | 1.40, 1.41, 1.42, 2.0 | index.ts | Secure by default |
648648
| `quickjs` | latest | index.js | Ultra-fast cold start (~10ms) |
649649

650+
Security note: Bun is the default runtime. Using other runtimes increases the attack surface; only use them when required and keep runtime versions pinned.
651+
650652
**Runtime Version Syntax:**
651653

652654
```yaml

docs/architecture.md

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ Parses `service.yaml` configuration and validates service structure.
2525

2626
### Runtime Registry
2727
Manages runtime configuration for the execution environment:
28-
- **bun**: Bun runtime with native TypeScript support
28+
- **bun**: Bun runtime with native TypeScript support (default)
29+
- **node**: Node.js runtime for JS compatibility
30+
- **deno**: Deno runtime with secure defaults
31+
- **quickjs**: QuickJS runtime for minimal overhead
2932

3033
### Docker Runtime
3134
Manages Docker image building and container execution.
@@ -55,7 +58,7 @@ service.yaml
5558
5659
5760
┌─────────────────┐
58-
│Runtime Registry │──► Select Bun
61+
│Runtime Registry │──► Select runtime (Bun default)
5962
└────────┬────────┘
6063
6164
@@ -83,13 +86,15 @@ Each service runs in its own Docker container with:
8386
- Environment variable injection
8487
- Metrics emission via entrypoint wrapper
8588

89+
Security note: Bun is the default runtime. Supporting additional runtimes increases the attack surface, so use them only when required and keep versions pinned.
90+
8691
## Runtime Registry
8792

8893
The runtime registry (`packages/core/src/runtime/runtime-registry.ts`) provides:
8994

9095
```typescript
9196
interface RuntimeConfig {
92-
name: RuntimeName; // 'bun'
97+
name: RuntimeName; // 'bun' (default), 'node', 'deno', 'quickjs'
9398
dockerfileDir: string; // Directory containing Dockerfile
9499
defaultEntry: string; // Default entry file
95100
fileExtensions: string[]; // Supported file extensions

docs/threat-model.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@ Ignite aims to provide **defense-in-depth** for executing untrusted JavaScript/T
2020
┌─────────────────────────────────────────────────────────────┐
2121
│ HOST SYSTEM │
2222
│ ┌───────────────────────────────────────────────────────┐ │
23-
│ │ DOCKER DAEMON │ │
23+
│ │ DOCKER DAEMON │ │
2424
│ │ ┌─────────────────────────────────────────────────┐ │ │
25-
│ │ │ IGNITE CONTAINER │ │ │
25+
│ │ │ IGNITE CONTAINER │ │ │
2626
│ │ │ ┌─────────────────────────────────────────────┐│ │ │
2727
│ │ │ │ UNTRUSTED CODE ││ │ │
2828
│ │ │ │ ││ │ │
29-
│ │ │ │ This is where AI-generated or user code ││ │ │
30-
│ │ │ │ executes. Assume fully malicious. ││ │ │
29+
│ │ │ │ This is where AI-generated or user code ││ │ │
30+
│ │ │ │ executes. Assume fully malicious. ││ │ │
3131
│ │ │ └─────────────────────────────────────────────┘│ │ │
3232
│ │ └─────────────────────────────────────────────────┘ │ │
3333
│ └───────────────────────────────────────────────────────┘ │

docs/walkthrough.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ ignite run . --input '{"data": [10, 20, 30], "operation": "average"}'
143143
service:
144144
# Required
145145
name: my-service # Service identifier
146-
runtime: bun # "bun" or "node"
146+
runtime: bun # bun, node, deno, quickjs (optional version with @)
147147
entry: index.ts # Entry file
148148

149149
# Resource Limits
@@ -194,7 +194,12 @@ service:
194194

195195
### Runtime
196196

197-
Ignite currently supports the Bun runtime for TypeScript and modern ESM modules.
197+
Ignite supports Bun, Node, Deno, and QuickJS runtimes. Bun is the default and recommended option.
198+
199+
**Security considerations:**
200+
- Additional runtimes increase the attack surface and dependency complexity.
201+
- Use non-Bun runtimes only when required by your code or dependencies.
202+
- Audit dependencies and keep runtime versions pinned (e.g., `node@20`, `deno@2.0`) to reduce drift.
198203

199204
### Using Dependencies
200205

packages/cli/src/commands/init.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { writeFile, mkdir } from 'node:fs/promises';
22
import { join } from 'node:path';
33
import { logger } from '@ignite/shared';
44
import { isValidRuntime, getRuntimeConfig } from '@ignite/core';
5+
import { parseRuntime } from '@ignite/shared';
56

67
interface InitOptions {
78
path?: string;
@@ -21,8 +22,10 @@ function getServiceYamlTemplate(serviceName: string, runtime: string, entry: str
2122
}
2223

2324
function getPackageJsonTemplate(serviceName: string, runtime: string, entry: string): string {
24-
const startCmd = runtime === 'bun' ? `bun run ${entry}` :
25-
runtime === 'deno' ? `deno run ${entry}` :
25+
const runtimeName = parseRuntime(runtime).name;
26+
const startCmd = runtimeName === 'bun' ? `bun run ${entry}` :
27+
runtimeName === 'deno' ? `deno run ${entry}` :
28+
runtimeName === 'quickjs' ? `qjs ${entry}` :
2629
`node ${entry}`;
2730
return JSON.stringify({
2831
name: serviceName,

packages/core/src/security/audit.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ export function parseAuditFromOutput(
2525
{ pattern: /permission denied.*?['"]?([\/\w.-]+)['"]?/gi, action: 'blocked' as const },
2626
];
2727

28+
const processPatterns = [
29+
{ pattern: /spawn.*EACCES/gi, action: 'spawn' as const },
30+
{ pattern: /child_process.*blocked/gi, action: 'spawn' as const },
31+
];
32+
2833
const combined = stdout + '\n' + stderr;
2934

3035
for (const { pattern, action } of networkPatterns) {
@@ -56,6 +61,20 @@ export function parseAuditFromOutput(
5661
}
5762
}
5863

64+
for (const { pattern, action } of processPatterns) {
65+
const matches = combined.matchAll(pattern);
66+
for (const match of matches) {
67+
events.push({
68+
type: 'process',
69+
action,
70+
target: extractCommand(match[0]) || 'unknown command',
71+
timestamp: now,
72+
allowed: false,
73+
details: match[0],
74+
});
75+
}
76+
}
77+
5978
const summary = calculateSummary(events);
6079

6180
return { events, summary, policy };
@@ -76,9 +95,15 @@ function extractPath(text: string): string | null {
7695
return pathMatch?.[1] ?? null;
7796
}
7897

98+
function extractCommand(text: string): string | null {
99+
const cmdMatch = text.match(/spawn\s+['"]?(\w+)['"]?/i);
100+
return cmdMatch?.[1] ?? null;
101+
}
102+
79103
function calculateSummary(events: SecurityEvent[]): SecuritySummary {
80104
const networkEvents = events.filter(e => e.type === 'network');
81105
const filesystemEvents = events.filter(e => e.type === 'filesystem');
106+
const processEvents = events.filter(e => e.type === 'process');
82107

83108
const hasViolations = events.some(e => !e.allowed);
84109

@@ -88,6 +113,8 @@ function calculateSummary(events: SecurityEvent[]): SecuritySummary {
88113
filesystemReads: filesystemEvents.filter(e => e.action === 'read').length,
89114
filesystemWrites: filesystemEvents.filter(e => e.action === 'write').length,
90115
filesystemBlocked: filesystemEvents.filter(e => !e.allowed).length,
116+
processSpawns: processEvents.length,
117+
processBlocked: processEvents.filter(e => !e.allowed).length,
91118
overallStatus: hasViolations ? 'violations' : 'clean',
92119
};
93120
}
@@ -109,6 +136,7 @@ export function formatSecurityAudit(audit: SecurityAudit): string {
109136
lines.push(` ${DIM}Policy:${NC}`);
110137
lines.push(` Network: ${audit.policy.network.enabled ? `${YELLOW}enabled${NC}` : `${GREEN}blocked${NC}`}`);
111138
lines.push(` Filesystem: ${audit.policy.filesystem.readOnly ? `${GREEN}read-only${NC}` : `${YELLOW}read-write${NC}`}`);
139+
lines.push(` Process spawn: ${audit.policy.process.allowSpawn ? `${YELLOW}allowed${NC}` : `${GREEN}blocked${NC}`}`);
112140
lines.push('');
113141

114142
if (audit.events.length === 0) {
@@ -119,6 +147,7 @@ export function formatSecurityAudit(audit: SecurityAudit): string {
119147

120148
const networkEvents = audit.events.filter(e => e.type === 'network');
121149
const fsEvents = audit.events.filter(e => e.type === 'filesystem');
150+
const procEvents = audit.events.filter(e => e.type === 'process');
122151

123152
if (networkEvents.length > 0) {
124153
lines.push('');
@@ -139,6 +168,16 @@ export function formatSecurityAudit(audit: SecurityAudit): string {
139168
lines.push(` ${icon} ${event.action}: ${event.target} ${DIM}(${status})${NC}`);
140169
}
141170
}
171+
172+
if (procEvents.length > 0) {
173+
lines.push('');
174+
lines.push(` ${BOLD}Process${NC}`);
175+
for (const event of procEvents) {
176+
const icon = event.allowed ? `${GREEN}${NC}` : `${RED}${NC}`;
177+
const status = event.allowed ? 'allowed' : 'blocked';
178+
lines.push(` ${icon} ${event.action}: ${event.target} ${DIM}(${status})${NC}`);
179+
}
180+
}
142181
}
143182

144183
lines.push('');
@@ -148,7 +187,7 @@ export function formatSecurityAudit(audit: SecurityAudit): string {
148187
if (summary.overallStatus === 'clean') {
149188
lines.push(` ${GREEN}✓ Security Status: CLEAN${NC}`);
150189
} else {
151-
const violations = summary.networkBlocked + summary.filesystemBlocked;
190+
const violations = summary.networkBlocked + summary.filesystemBlocked + summary.processBlocked;
152191
lines.push(` ${RED}✗ Security Status: ${violations} VIOLATION(S) BLOCKED${NC}`);
153192
}
154193
lines.push('');

packages/core/src/security/policy.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { readFile } from 'node:fs/promises';
22
import { resolve } from 'node:path';
33
import { parse as parseYaml } from 'yaml';
4-
import type { SecurityPolicy, NetworkPolicy, FilesystemPolicy } from './security.types.js';
4+
import type { SecurityPolicy, NetworkPolicy, FilesystemPolicy, ProcessPolicy } from './security.types.js';
55
import { DEFAULT_POLICY } from './security.types.js';
66

77
export interface PolicyFile {
@@ -12,6 +12,10 @@ export interface PolicyFile {
1212
filesystem?: {
1313
readOnly?: boolean;
1414
};
15+
process?: {
16+
allowSpawn?: boolean;
17+
allowedCommands?: string[];
18+
};
1519
};
1620
}
1721

@@ -47,7 +51,12 @@ function mergePolicies(base: SecurityPolicy, file: PolicyFile): SecurityPolicy {
4751
readOnly: security.filesystem?.readOnly ?? base.filesystem.readOnly,
4852
};
4953

50-
return { network, filesystem };
54+
const process: ProcessPolicy = {
55+
allowSpawn: security.process?.allowSpawn ?? base.process.allowSpawn,
56+
allowedCommands: security.process?.allowedCommands ?? base.process.allowedCommands,
57+
};
58+
59+
return { network, filesystem, process };
5160
}
5261

5362
export function policyToDockerOptions(policy: SecurityPolicy): {

packages/core/src/security/security.types.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export interface SecurityPolicy {
22
network: NetworkPolicy;
33
filesystem: FilesystemPolicy;
4+
process: ProcessPolicy;
45
}
56

67
export interface NetworkPolicy {
@@ -11,9 +12,14 @@ export interface FilesystemPolicy {
1112
readOnly: boolean;
1213
}
1314

15+
export interface ProcessPolicy {
16+
allowSpawn: boolean;
17+
allowedCommands?: string[];
18+
}
19+
1420
export interface SecurityEvent {
15-
type: 'network' | 'filesystem';
16-
action: 'read' | 'write' | 'connect' | 'blocked';
21+
type: 'network' | 'filesystem' | 'process';
22+
action: 'read' | 'write' | 'connect' | 'spawn' | 'blocked';
1723
target: string;
1824
timestamp: number;
1925
allowed: boolean;
@@ -32,6 +38,8 @@ export interface SecuritySummary {
3238
filesystemReads: number;
3339
filesystemWrites: number;
3440
filesystemBlocked: number;
41+
processSpawns: number;
42+
processBlocked: number;
3543
overallStatus: 'clean' | 'violations';
3644
}
3745

@@ -42,4 +50,7 @@ export const DEFAULT_POLICY: SecurityPolicy = {
4250
filesystem: {
4351
readOnly: true,
4452
},
53+
process: {
54+
allowSpawn: false,
55+
},
4556
};

0 commit comments

Comments
 (0)