@isol8/core 0.13.0-alpha.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/README.md +39 -0
- package/dist/client/remote.d.ts +64 -0
- package/dist/client/remote.d.ts.map +1 -0
- package/dist/config.d.ts +36 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/docker/Dockerfile +42 -0
- package/dist/docker/proxy-handler.sh +180 -0
- package/dist/docker/proxy.sh +57 -0
- package/dist/docker/seccomp-profile.json +67 -0
- package/dist/engine/audit.d.ts +31 -0
- package/dist/engine/audit.d.ts.map +1 -0
- package/dist/engine/code-fetcher.d.ts +21 -0
- package/dist/engine/code-fetcher.d.ts.map +1 -0
- package/dist/engine/concurrency.d.ts +46 -0
- package/dist/engine/concurrency.d.ts.map +1 -0
- package/dist/engine/default-seccomp-profile.d.ts +8 -0
- package/dist/engine/default-seccomp-profile.d.ts.map +1 -0
- package/dist/engine/docker.d.ts +167 -0
- package/dist/engine/docker.d.ts.map +1 -0
- package/dist/engine/image-builder.d.ts +71 -0
- package/dist/engine/image-builder.d.ts.map +1 -0
- package/dist/engine/pool.d.ts +94 -0
- package/dist/engine/pool.d.ts.map +1 -0
- package/dist/engine/stats.d.ts +35 -0
- package/dist/engine/stats.d.ts.map +1 -0
- package/dist/engine/utils.d.ts +71 -0
- package/dist/engine/utils.d.ts.map +1 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2777 -0
- package/dist/index.js.map +30 -0
- package/dist/runtime/adapter.d.ts +63 -0
- package/dist/runtime/adapter.d.ts.map +1 -0
- package/dist/runtime/adapters/bash.d.ts +3 -0
- package/dist/runtime/adapters/bash.d.ts.map +1 -0
- package/dist/runtime/adapters/bun.d.ts +4 -0
- package/dist/runtime/adapters/bun.d.ts.map +1 -0
- package/dist/runtime/adapters/deno.d.ts +10 -0
- package/dist/runtime/adapters/deno.d.ts.map +1 -0
- package/dist/runtime/adapters/node.d.ts +4 -0
- package/dist/runtime/adapters/node.d.ts.map +1 -0
- package/dist/runtime/adapters/python.d.ts +4 -0
- package/dist/runtime/adapters/python.d.ts.map +1 -0
- package/dist/runtime/index.d.ts +15 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/types.d.ts +532 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/utils/logger.d.ts +32 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/version.d.ts +15 -0
- package/dist/version.d.ts.map +1 -0
- package/docker/Dockerfile +42 -0
- package/docker/proxy-handler.sh +180 -0
- package/docker/proxy.sh +57 -0
- package/docker/seccomp-profile.json +67 -0
- package/package.json +48 -0
- package/schema/isol8.config.schema.json +315 -0
package/README.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# @isol8/core
|
|
2
|
+
|
|
3
|
+
Core engine for isol8 secure code execution.
|
|
4
|
+
|
|
5
|
+
> **Note**: The `isol8` package is deprecated. Use `@isol8/core` instead.
|
|
6
|
+
|
|
7
|
+
For full documentation, usage examples, and contribution guidelines, see the main [isol8 README](../README.md).
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install @isol8/core
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
import { DockerIsol8 } from "@isol8/core";
|
|
19
|
+
|
|
20
|
+
const engine = new DockerIsol8();
|
|
21
|
+
await engine.start();
|
|
22
|
+
|
|
23
|
+
const result = await engine.execute({
|
|
24
|
+
code: "print('Hello from isol8!')",
|
|
25
|
+
runtime: "python",
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log(result.stdout); // "Hello from isol8!\n"
|
|
29
|
+
|
|
30
|
+
await engine.stop();
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## API
|
|
34
|
+
|
|
35
|
+
See [API Documentation](https://isol8.dev/docs/api) for full API reference.
|
|
36
|
+
|
|
37
|
+
## License
|
|
38
|
+
|
|
39
|
+
MIT - See [../LICENSE](../LICENSE)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module client/remote
|
|
3
|
+
*
|
|
4
|
+
* HTTP client for communicating with a remote isol8 server. Implements the
|
|
5
|
+
* {@link Isol8Engine} interface, so it can be used interchangeably with
|
|
6
|
+
* {@link DockerIsol8} for local-vs-remote execution.
|
|
7
|
+
*/
|
|
8
|
+
import type { ExecutionRequest, ExecutionResult, Isol8Engine, Isol8Options, StartOptions, StreamEvent } from "../types";
|
|
9
|
+
/** Connection options for the remote isol8 client. */
|
|
10
|
+
export interface RemoteIsol8Options {
|
|
11
|
+
/** Base URL of the isol8 server (e.g. `"http://localhost:3000"`). */
|
|
12
|
+
host: string;
|
|
13
|
+
/** API key for Bearer token authentication. */
|
|
14
|
+
apiKey: string;
|
|
15
|
+
/** Optional session ID for persistent mode. If set, the server maintains container state across calls. */
|
|
16
|
+
sessionId?: string;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Remote isol8 client that communicates with an isol8 server over HTTP.
|
|
20
|
+
* Implements the {@link Isol8Engine} interface for seamless local/remote switching.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```typescript
|
|
24
|
+
* const isol8 = new RemoteIsol8(
|
|
25
|
+
* { host: "http://localhost:3000", apiKey: "secret" },
|
|
26
|
+
* { network: "none" }
|
|
27
|
+
* );
|
|
28
|
+
* await isol8.start();
|
|
29
|
+
* const result = await isol8.execute({ code: "print(1)", runtime: "python" });
|
|
30
|
+
* await isol8.stop();
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare class RemoteIsol8 implements Isol8Engine {
|
|
34
|
+
private readonly host;
|
|
35
|
+
private readonly apiKey;
|
|
36
|
+
private readonly sessionId?;
|
|
37
|
+
private readonly isol8Options?;
|
|
38
|
+
/**
|
|
39
|
+
* @param options - Connection options (host, API key, session ID).
|
|
40
|
+
* @param isol8Options - Isol8 configuration to send to the server.
|
|
41
|
+
*/
|
|
42
|
+
constructor(options: RemoteIsol8Options, isol8Options?: Isol8Options);
|
|
43
|
+
/** Verify the remote server is reachable by hitting the `/health` endpoint. */
|
|
44
|
+
start(_options?: StartOptions): Promise<void>;
|
|
45
|
+
/** Destroy the remote session (if persistent). No-op for ephemeral mode. */
|
|
46
|
+
stop(): Promise<void>;
|
|
47
|
+
/** Execute code on the remote server and return the result. */
|
|
48
|
+
execute(req: ExecutionRequest): Promise<ExecutionResult>;
|
|
49
|
+
/**
|
|
50
|
+
* Execute code on the remote server and stream output chunks via SSE.
|
|
51
|
+
* Yields {@link StreamEvent} objects as they arrive from the server.
|
|
52
|
+
*/
|
|
53
|
+
executeStream(req: ExecutionRequest): AsyncIterable<StreamEvent>;
|
|
54
|
+
/**
|
|
55
|
+
* Upload a file to the remote container (persistent mode only).
|
|
56
|
+
* Content is Base64-encoded for transport.
|
|
57
|
+
*/
|
|
58
|
+
putFile(path: string, content: Buffer | string): Promise<void>;
|
|
59
|
+
/** Download a file from the remote container (persistent mode only). */
|
|
60
|
+
getFile(path: string): Promise<Buffer>;
|
|
61
|
+
/** Internal fetch wrapper that attaches auth and content-type headers. */
|
|
62
|
+
private fetch;
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=remote.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"remote.d.ts","sourceRoot":"","sources":["../../src/client/remote.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,gBAAgB,EAChB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,YAAY,EACZ,WAAW,EACZ,MAAM,UAAU,CAAC;AAElB,sDAAsD;AACtD,MAAM,WAAW,kBAAkB;IACjC,qEAAqE;IACrE,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,0GAA0G;IAC1G,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;;;;;;;;;GAcG;AACH,qBAAa,WAAY,YAAW,WAAW;IAC7C,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAS;IAC9B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAe;IAE7C;;;OAGG;gBACS,OAAO,EAAE,kBAAkB,EAAE,YAAY,CAAC,EAAE,YAAY;IAOpE,+EAA+E;IACzE,KAAK,CAAC,QAAQ,CAAC,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD,4EAA4E;IACtE,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAM3B,+DAA+D;IACzD,OAAO,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC;IAkB9D;;;OAGG;IACI,aAAa,CAAC,GAAG,EAAE,gBAAgB,GAAG,aAAa,CAAC,WAAW,CAAC;IA0DvE;;;OAGG;IACG,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAwBpE,wEAAwE;IAClE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgB5C,0EAA0E;YAC5D,KAAK;CAUpB"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module config
|
|
3
|
+
*
|
|
4
|
+
* Configuration discovery and loading for isol8. Searches for `isol8.config.json`
|
|
5
|
+
* in the working directory and then in `~/.isol8/config.json`, merging any found
|
|
6
|
+
* config with built-in defaults.
|
|
7
|
+
*/
|
|
8
|
+
import type { Isol8Config } from "./types";
|
|
9
|
+
/**
|
|
10
|
+
* Built-in default configuration. Used as the base for all config merges.
|
|
11
|
+
* All values here are the "safe defaults" — network disabled, conservative limits.
|
|
12
|
+
*/
|
|
13
|
+
declare const DEFAULT_CONFIG: Isol8Config;
|
|
14
|
+
/**
|
|
15
|
+
* Discovers and loads the isol8 configuration file.
|
|
16
|
+
*
|
|
17
|
+
* Search order (first match wins):
|
|
18
|
+
* 1. `isol8.config.json` in CWD (or the provided `cwd` argument)
|
|
19
|
+
* 2. `~/.isol8/config.json`
|
|
20
|
+
*
|
|
21
|
+
* If no config file is found, returns a copy of {@link DEFAULT_CONFIG}.
|
|
22
|
+
* Partial configs are deep-merged with defaults — you only need to specify
|
|
23
|
+
* the fields you want to override.
|
|
24
|
+
*
|
|
25
|
+
* @param cwd - Optional working directory to search from (defaults to `process.cwd()`).
|
|
26
|
+
* @returns The resolved configuration.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```typescript
|
|
30
|
+
* const config = loadConfig();
|
|
31
|
+
* console.log(config.defaults.timeoutMs); // 30000
|
|
32
|
+
* ```
|
|
33
|
+
*/
|
|
34
|
+
export declare function loadConfig(cwd?: string): Isol8Config;
|
|
35
|
+
export { DEFAULT_CONFIG };
|
|
36
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE3C;;;GAGG;AACH,QAAA,MAAM,cAAc,EAAE,WA0DrB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,WAAW,CAepD;AAiDD,OAAO,EAAE,cAAc,EAAE,CAAC"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# ── Base ──────────────────────────────────────────────────────────────
|
|
2
|
+
FROM alpine:3.21 AS base
|
|
3
|
+
RUN apk add --no-cache tini curl ca-certificates iptables bash \
|
|
4
|
+
&& addgroup -S sandbox && adduser -S sandbox -G sandbox -h /sandbox
|
|
5
|
+
COPY proxy.sh /usr/local/bin/proxy.sh
|
|
6
|
+
COPY proxy-handler.sh /usr/local/bin/proxy-handler.sh
|
|
7
|
+
RUN chmod +x /usr/local/bin/proxy.sh /usr/local/bin/proxy-handler.sh
|
|
8
|
+
WORKDIR /sandbox
|
|
9
|
+
ENTRYPOINT ["/sbin/tini", "--"]
|
|
10
|
+
|
|
11
|
+
# ── Python ────────────────────────────────────────────────────────────
|
|
12
|
+
FROM base AS python
|
|
13
|
+
RUN apk add --no-cache python3 py3-pip
|
|
14
|
+
CMD ["python3"]
|
|
15
|
+
|
|
16
|
+
# ── Node ──────────────────────────────────────────────────────────────
|
|
17
|
+
FROM base AS node
|
|
18
|
+
RUN apk add --no-cache nodejs npm
|
|
19
|
+
CMD ["node"]
|
|
20
|
+
|
|
21
|
+
# ── Bun ───────────────────────────────────────────────────────────────
|
|
22
|
+
FROM base AS bun
|
|
23
|
+
RUN apk add --no-cache unzip libstdc++ libgcc \
|
|
24
|
+
&& curl -fsSL https://bun.sh/install | bash \
|
|
25
|
+
&& mv /root/.bun/bin/bun /usr/local/bin/bun \
|
|
26
|
+
&& ln -s /usr/local/bin/bun /usr/local/bin/bunx
|
|
27
|
+
CMD ["bun"]
|
|
28
|
+
|
|
29
|
+
# ── Deno ──────────────────────────────────────────────────────────────
|
|
30
|
+
FROM denoland/deno:alpine AS deno
|
|
31
|
+
RUN apk add --no-cache tini curl ca-certificates iptables bash \
|
|
32
|
+
&& addgroup -S sandbox && adduser -S sandbox -G sandbox -h /sandbox
|
|
33
|
+
COPY proxy.sh /usr/local/bin/proxy.sh
|
|
34
|
+
COPY proxy-handler.sh /usr/local/bin/proxy-handler.sh
|
|
35
|
+
RUN chmod +x /usr/local/bin/proxy.sh /usr/local/bin/proxy-handler.sh
|
|
36
|
+
WORKDIR /sandbox
|
|
37
|
+
ENTRYPOINT ["/sbin/tini", "--"]
|
|
38
|
+
CMD ["deno"]
|
|
39
|
+
|
|
40
|
+
# ── Bash ──────────────────────────────────────────────────────────────
|
|
41
|
+
FROM base AS bash
|
|
42
|
+
CMD ["bash"]
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# isol8 proxy handler — handles a single proxied connection.
|
|
3
|
+
#
|
|
4
|
+
# Invoked by: nc -lk -e proxy-handler.sh
|
|
5
|
+
# stdin/stdout are wired to the client socket by nc.
|
|
6
|
+
#
|
|
7
|
+
# Env vars (inherited from proxy.sh launcher):
|
|
8
|
+
# ISOL8_WHITELIST_FILE - Path to file with whitelist regex patterns
|
|
9
|
+
# ISOL8_BLACKLIST_FILE - Path to file with blacklist regex patterns
|
|
10
|
+
#
|
|
11
|
+
# Supports:
|
|
12
|
+
# - HTTPS CONNECT tunneling (bidirectional relay via exec nc)
|
|
13
|
+
# - HTTP forwarding (GET/POST/etc via bash /dev/tcp)
|
|
14
|
+
|
|
15
|
+
WL="${ISOL8_WHITELIST_FILE:-}"
|
|
16
|
+
BL="${ISOL8_BLACKLIST_FILE:-}"
|
|
17
|
+
|
|
18
|
+
log_network() {
|
|
19
|
+
local method="$1"
|
|
20
|
+
local host="$2"
|
|
21
|
+
local path="$3"
|
|
22
|
+
local action="$4"
|
|
23
|
+
local duration_ms="$5"
|
|
24
|
+
|
|
25
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
26
|
+
# Handle path: output proper JSON null if path is "null", otherwise quote it
|
|
27
|
+
if [ "$path" = "null" ] || [ -z "$path" ]; then
|
|
28
|
+
printf '{"timestamp":"%s","method":"%s","host":"%s","path":null,"action":"%s","durationMs":%d}\n' \
|
|
29
|
+
"$(date -Iseconds)" "$method" "$host" "$action" "$duration_ms" >> /tmp/isol8-proxy/network.jsonl
|
|
30
|
+
else
|
|
31
|
+
printf '{"timestamp":"%s","method":"%s","host":"%s","path":"%s","action":"%s","durationMs":%d}\n' \
|
|
32
|
+
"$(date -Iseconds)" "$method" "$host" "$path" "$action" "$duration_ms" >> /tmp/isol8-proxy/network.jsonl
|
|
33
|
+
fi
|
|
34
|
+
fi
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
is_allowed() {
|
|
38
|
+
local host="$1"
|
|
39
|
+
|
|
40
|
+
# Check blacklist first
|
|
41
|
+
if [ -n "$BL" ] && [ -s "$BL" ]; then
|
|
42
|
+
if echo "$host" | grep -qEf "$BL" 2>/dev/null; then
|
|
43
|
+
return 1
|
|
44
|
+
fi
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# If whitelist is empty or missing, allow all
|
|
48
|
+
if [ -z "$WL" ] || [ ! -s "$WL" ]; then
|
|
49
|
+
return 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
# Must match at least one whitelist pattern
|
|
53
|
+
if echo "$host" | grep -qEf "$WL" 2>/dev/null; then
|
|
54
|
+
return 0
|
|
55
|
+
fi
|
|
56
|
+
|
|
57
|
+
return 1
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
# Read the request line
|
|
61
|
+
# e.g. "CONNECT host:443 HTTP/1.1" or "GET http://host/path HTTP/1.1"
|
|
62
|
+
read -r request_line || exit 0
|
|
63
|
+
request_line="${request_line%%$'\r'}"
|
|
64
|
+
|
|
65
|
+
method="${request_line%% *}"
|
|
66
|
+
rest="${request_line#* }"
|
|
67
|
+
target="${rest%% *}"
|
|
68
|
+
|
|
69
|
+
# Read and store all headers until blank line
|
|
70
|
+
headers=""
|
|
71
|
+
content_length=0
|
|
72
|
+
while IFS= read -r hline; do
|
|
73
|
+
hline="${hline%%$'\r'}"
|
|
74
|
+
[ -z "$hline" ] && break
|
|
75
|
+
headers="${headers}${hline}"$'\n'
|
|
76
|
+
# Extract Content-Length
|
|
77
|
+
case "$hline" in
|
|
78
|
+
[Cc]ontent-[Ll]ength:*)
|
|
79
|
+
content_length="${hline#*: }"
|
|
80
|
+
content_length="${content_length// /}"
|
|
81
|
+
;;
|
|
82
|
+
esac
|
|
83
|
+
done
|
|
84
|
+
|
|
85
|
+
# ── CONNECT (HTTPS tunneling) ──────────────────────────────────────────
|
|
86
|
+
if [ "$method" = "CONNECT" ]; then
|
|
87
|
+
host="${target%%:*}"
|
|
88
|
+
port="${target##*:}"
|
|
89
|
+
[ "$port" = "$host" ] && port=443
|
|
90
|
+
|
|
91
|
+
if ! is_allowed "$host"; then
|
|
92
|
+
msg="isol8: CONNECT to ${host} blocked by network filter"
|
|
93
|
+
# Log security event
|
|
94
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
95
|
+
printf '{"type":"network_blocked","timestamp":"%s","details":{"method":"CONNECT","host":"%s","reason":"filter_mismatch"}}\n' "$(date -Iseconds)" "$host" >> /tmp/isol8-proxy/security-events.jsonl
|
|
96
|
+
fi
|
|
97
|
+
# Log network event
|
|
98
|
+
log_network "CONNECT" "$host" "null" "BLOCK" 0
|
|
99
|
+
printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
|
|
100
|
+
"${#msg}" "$msg"
|
|
101
|
+
exit 0
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
# Log allowed CONNECT (duration will be 0 since we can't measure after exec)
|
|
105
|
+
log_network "CONNECT" "$host" "null" "ALLOW" 0
|
|
106
|
+
|
|
107
|
+
# Send 200 then replace this process with nc for bidirectional relay.
|
|
108
|
+
# nc inherits the client socket on stdin/stdout from the nc -lk -e parent.
|
|
109
|
+
printf "HTTP/1.1 200 Connection Established\r\n\r\n"
|
|
110
|
+
exec nc "$host" "$port"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
# ── HTTP forwarding ────────────────────────────────────────────────────
|
|
114
|
+
# Proxy HTTP requests use absolute URLs: GET http://host:port/path HTTP/1.1
|
|
115
|
+
url_rest="${target#*://}"
|
|
116
|
+
hostport="${url_rest%%/*}"
|
|
117
|
+
path="/${url_rest#*/}"
|
|
118
|
+
# Handle URLs with no path component
|
|
119
|
+
[ "$path" = "/${url_rest}" ] && path="/"
|
|
120
|
+
|
|
121
|
+
host="${hostport%%:*}"
|
|
122
|
+
port="${hostport##*:}"
|
|
123
|
+
[ "$port" = "$host" ] && port=80
|
|
124
|
+
|
|
125
|
+
if ! is_allowed "$host"; then
|
|
126
|
+
msg="isol8: request to ${host} blocked by network filter"
|
|
127
|
+
# Log security event
|
|
128
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
129
|
+
printf '{"type":"network_blocked","timestamp":"%s","details":{"method":"%s","host":"%s","reason":"filter_mismatch"}}\n' "$(date -Iseconds)" "$method" "$host" >> /tmp/isol8-proxy/security-events.jsonl
|
|
130
|
+
fi
|
|
131
|
+
# Log network event
|
|
132
|
+
log_network "$method" "$host" "$path" "BLOCK" 0
|
|
133
|
+
printf "HTTP/1.1 403 Forbidden\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
|
|
134
|
+
"${#msg}" "$msg"
|
|
135
|
+
exit 0
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
# Record start time for duration measurement
|
|
139
|
+
if [ -d "/tmp/isol8-proxy" ]; then
|
|
140
|
+
start_time=$(date +%s%3N)
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
# Open TCP connection via bash /dev/tcp
|
|
144
|
+
if ! exec 3<>/dev/tcp/"$host"/"$port" 2>/dev/null; then
|
|
145
|
+
msg="isol8: proxy error: connection to ${host}:${port} failed"
|
|
146
|
+
printf "HTTP/1.1 502 Bad Gateway\r\nContent-Type: text/plain\r\nContent-Length: %d\r\n\r\n%s" \
|
|
147
|
+
"${#msg}" "$msg"
|
|
148
|
+
exit 0
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Send request line with relative path (not absolute URL)
|
|
152
|
+
printf "%s %s HTTP/1.1\r\n" "$method" "$path" >&3
|
|
153
|
+
|
|
154
|
+
# Forward headers, skipping Proxy-* headers
|
|
155
|
+
while IFS= read -r h; do
|
|
156
|
+
[ -z "$h" ] && continue
|
|
157
|
+
case "$h" in
|
|
158
|
+
Proxy-*|proxy-*) continue ;;
|
|
159
|
+
esac
|
|
160
|
+
printf "%s\r\n" "$h" >&3
|
|
161
|
+
done <<< "$headers"
|
|
162
|
+
printf "\r\n" >&3
|
|
163
|
+
|
|
164
|
+
# Forward request body if present
|
|
165
|
+
if [ "$content_length" -gt 0 ] 2>/dev/null; then
|
|
166
|
+
head -c "$content_length" >&3
|
|
167
|
+
fi
|
|
168
|
+
|
|
169
|
+
# Relay response back to client
|
|
170
|
+
cat <&3
|
|
171
|
+
|
|
172
|
+
# Calculate duration and log the network event
|
|
173
|
+
if [ -n "$start_time" ] && [ -d "/tmp/isol8-proxy" ]; then
|
|
174
|
+
end_time=$(date +%s%3N)
|
|
175
|
+
duration=$((end_time - start_time))
|
|
176
|
+
log_network "$method" "$host" "$path" "ALLOW" "$duration"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
exec 3>&-
|
|
180
|
+
exit 0
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# isol8 proxy launcher — parses env vars and starts the proxy listener.
|
|
3
|
+
#
|
|
4
|
+
# Env vars:
|
|
5
|
+
# ISOL8_WHITELIST - JSON array of regex strings (allow these)
|
|
6
|
+
# ISOL8_BLACKLIST - JSON array of regex strings (block these)
|
|
7
|
+
# ISOL8_PROXY_PORT - Port to listen on (default: 8118)
|
|
8
|
+
#
|
|
9
|
+
# This script:
|
|
10
|
+
# 1. Parses ISOL8_WHITELIST/BLACKLIST JSON arrays into grep-compatible pattern files
|
|
11
|
+
# 2. Starts nc -lk -e /usr/local/bin/proxy-handler.sh on the specified port
|
|
12
|
+
#
|
|
13
|
+
# The pattern files are stored in /tmp/isol8-proxy/ and exported as env vars
|
|
14
|
+
# so the handler (forked by nc -e) can access them via inherited environment.
|
|
15
|
+
|
|
16
|
+
PORT="${ISOL8_PROXY_PORT:-8118}"
|
|
17
|
+
PROXY_DIR="/tmp/isol8-proxy"
|
|
18
|
+
mkdir -p "$PROXY_DIR"
|
|
19
|
+
|
|
20
|
+
# Create log files
|
|
21
|
+
touch "$PROXY_DIR/security-events.jsonl"
|
|
22
|
+
touch "$PROXY_DIR/network.jsonl"
|
|
23
|
+
|
|
24
|
+
WL_FILE="$PROXY_DIR/whitelist"
|
|
25
|
+
BL_FILE="$PROXY_DIR/blacklist"
|
|
26
|
+
|
|
27
|
+
# Parse JSON array of regex strings into a file with one ERE pattern per line.
|
|
28
|
+
# Input: JSON like '["^example\\.com$","^api\\."]'
|
|
29
|
+
# Output: file with one grep -E compatible pattern per line
|
|
30
|
+
parse_patterns() {
|
|
31
|
+
local json="$1" outfile="$2"
|
|
32
|
+
: > "$outfile"
|
|
33
|
+
if [ -z "$json" ] || [ "$json" = "[]" ]; then
|
|
34
|
+
return
|
|
35
|
+
fi
|
|
36
|
+
# Strip brackets, split on "," → one quoted pattern per line
|
|
37
|
+
# Then strip quotes and unescape doubled backslashes from JSON encoding
|
|
38
|
+
echo "$json" \
|
|
39
|
+
| sed 's/^\[//; s/\]$//' \
|
|
40
|
+
| sed 's/","/"\n"/g' \
|
|
41
|
+
| sed 's/^"//; s/"$//' \
|
|
42
|
+
| sed 's/\\\\/\\/g' \
|
|
43
|
+
> "$outfile"
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
parse_patterns "${ISOL8_WHITELIST:-}" "$WL_FILE"
|
|
47
|
+
parse_patterns "${ISOL8_BLACKLIST:-}" "$BL_FILE"
|
|
48
|
+
|
|
49
|
+
# Export paths so the handler (forked by nc -e) can find them
|
|
50
|
+
export ISOL8_WHITELIST_FILE="$WL_FILE"
|
|
51
|
+
export ISOL8_BLACKLIST_FILE="$BL_FILE"
|
|
52
|
+
|
|
53
|
+
echo "isol8 proxy listening on 127.0.0.1:${PORT}"
|
|
54
|
+
|
|
55
|
+
# Start listening — nc -lk provides a persistent server that forks
|
|
56
|
+
# a handler for each connection with stdin/stdout wired to the socket
|
|
57
|
+
exec nc -lk -s 127.0.0.1 -p "$PORT" -e /usr/local/bin/proxy-handler.sh
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"defaultAction": "SCMP_ACT_ALLOW",
|
|
3
|
+
"architectures": [
|
|
4
|
+
"SCMP_ARCH_X86_64",
|
|
5
|
+
"SCMP_ARCH_X86",
|
|
6
|
+
"SCMP_ARCH_X32",
|
|
7
|
+
"SCMP_ARCH_AARCH64"
|
|
8
|
+
],
|
|
9
|
+
"syscalls": [
|
|
10
|
+
{
|
|
11
|
+
"names": [
|
|
12
|
+
"acct",
|
|
13
|
+
"add_key",
|
|
14
|
+
"bpf",
|
|
15
|
+
"clock_adjtime",
|
|
16
|
+
"clock_settime",
|
|
17
|
+
"create_module",
|
|
18
|
+
"delete_module",
|
|
19
|
+
"finit_module",
|
|
20
|
+
"get_mempolicy",
|
|
21
|
+
"init_module",
|
|
22
|
+
"ioperm",
|
|
23
|
+
"iopl",
|
|
24
|
+
"kcmp",
|
|
25
|
+
"kexec_file_load",
|
|
26
|
+
"kexec_load",
|
|
27
|
+
"keyctl",
|
|
28
|
+
"lookup_dcookie",
|
|
29
|
+
"mbind",
|
|
30
|
+
"mount",
|
|
31
|
+
"move_pages",
|
|
32
|
+
"name_to_handle_at",
|
|
33
|
+
"open_by_handle_at",
|
|
34
|
+
"perf_event_open",
|
|
35
|
+
"pivot_root",
|
|
36
|
+
"process_vm_readv",
|
|
37
|
+
"process_vm_writev",
|
|
38
|
+
"ptrace",
|
|
39
|
+
"query_module",
|
|
40
|
+
"quotactl",
|
|
41
|
+
"reboot",
|
|
42
|
+
"request_key",
|
|
43
|
+
"set_mempolicy",
|
|
44
|
+
"setns",
|
|
45
|
+
"settimeofday",
|
|
46
|
+
"stime",
|
|
47
|
+
"swapon",
|
|
48
|
+
"swapoff",
|
|
49
|
+
"sysfs",
|
|
50
|
+
"syslog",
|
|
51
|
+
"umount",
|
|
52
|
+
"umount2",
|
|
53
|
+
"unshare",
|
|
54
|
+
"uselib",
|
|
55
|
+
"userfaultfd",
|
|
56
|
+
"ustat",
|
|
57
|
+
"vm86",
|
|
58
|
+
"vm86old"
|
|
59
|
+
],
|
|
60
|
+
"action": "SCMP_ACT_ERRNO",
|
|
61
|
+
"args": [],
|
|
62
|
+
"comment": "",
|
|
63
|
+
"includes": {},
|
|
64
|
+
"excludes": {}
|
|
65
|
+
}
|
|
66
|
+
]
|
|
67
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pluggable audit logger for execution provenance.
|
|
3
|
+
*
|
|
4
|
+
* Records ExecutionAudit objects to various destinations based on configuration.
|
|
5
|
+
* Supports filesystem and stdout logging, with extensibility for cloud services.
|
|
6
|
+
*/
|
|
7
|
+
import type { AuditConfig, ExecutionAudit } from "../types";
|
|
8
|
+
export declare class AuditLogger {
|
|
9
|
+
private readonly config;
|
|
10
|
+
private readonly auditFile;
|
|
11
|
+
constructor(config: AuditConfig);
|
|
12
|
+
/**
|
|
13
|
+
* Clean up audit log files older than retentionDays.
|
|
14
|
+
* Checks both the main executions.log and any rotated/archived logs.
|
|
15
|
+
*/
|
|
16
|
+
private cleanupOldLogs;
|
|
17
|
+
/**
|
|
18
|
+
* Record an audit entry based on the current configuration.
|
|
19
|
+
*/
|
|
20
|
+
record(audit: ExecutionAudit): void;
|
|
21
|
+
/**
|
|
22
|
+
* Run the configured post-log script.
|
|
23
|
+
* The script receives the audit file path as its first argument.
|
|
24
|
+
*/
|
|
25
|
+
private runPostLogScript;
|
|
26
|
+
/**
|
|
27
|
+
* Apply privacy filtering to audit data based on configuration.
|
|
28
|
+
*/
|
|
29
|
+
private filterAuditData;
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=audit.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.d.ts","sourceRoot":"","sources":["../../src/engine/audit.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAKH,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAG5D,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAc;IACrC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAEvB,MAAM,EAAE,WAAW;IAqB/B;;;OAGG;IACH,OAAO,CAAC,cAAc;IAwCtB;;OAEG;IACH,MAAM,CAAC,KAAK,EAAE,cAAc,GAAG,IAAI;IAoCnC;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAuBxB;;OAEG;IACH,OAAO,CAAC,eAAe;CAuCxB"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { RemoteCodePolicy } from "../types";
|
|
2
|
+
export interface FetchCodeRequest {
|
|
3
|
+
codeUrl: string;
|
|
4
|
+
codeHash?: string;
|
|
5
|
+
allowInsecureCodeUrl?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface FetchCodeResult {
|
|
8
|
+
code: string;
|
|
9
|
+
url: string;
|
|
10
|
+
hash: string;
|
|
11
|
+
}
|
|
12
|
+
interface CodeFetcherDeps {
|
|
13
|
+
fetchFn?: (input: string, init?: RequestInit) => Promise<Response>;
|
|
14
|
+
lookupFn?: (hostname: string) => Promise<Array<{
|
|
15
|
+
address: string;
|
|
16
|
+
family: number;
|
|
17
|
+
}>>;
|
|
18
|
+
}
|
|
19
|
+
export declare function fetchRemoteCode(request: FetchCodeRequest, policy: RemoteCodePolicy, deps?: CodeFetcherDeps): Promise<FetchCodeResult>;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=code-fetcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"code-fetcher.d.ts","sourceRoot":"","sources":["../../src/engine/code-fetcher.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAEjD,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;CACd;AAED,UAAU,eAAe;IACvB,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnE,QAAQ,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC,CAAC;CACtF;AA2GD,wBAAsB,eAAe,CACnC,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,gBAAgB,EACxB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,eAAe,CAAC,CAkH1B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module engine/concurrency
|
|
3
|
+
*
|
|
4
|
+
* Async semaphore for limiting the number of concurrent container executions.
|
|
5
|
+
* Used by {@link DockerIsol8} to prevent resource exhaustion.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* A counting semaphore for limiting concurrent async operations.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const sem = new Semaphore(3); // allow 3 concurrent operations
|
|
13
|
+
*
|
|
14
|
+
* await sem.acquire();
|
|
15
|
+
* try {
|
|
16
|
+
* await doWork();
|
|
17
|
+
* } finally {
|
|
18
|
+
* sem.release();
|
|
19
|
+
* }
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export declare class Semaphore {
|
|
23
|
+
private readonly max;
|
|
24
|
+
private current;
|
|
25
|
+
private readonly queue;
|
|
26
|
+
/**
|
|
27
|
+
* @param max - Maximum number of concurrent acquisitions. Must be ≥ 1.
|
|
28
|
+
* @throws {Error} If `max` is less than 1.
|
|
29
|
+
*/
|
|
30
|
+
constructor(max: number);
|
|
31
|
+
/** The number of permits currently available. */
|
|
32
|
+
get available(): number;
|
|
33
|
+
/** The number of callers waiting to acquire a permit. */
|
|
34
|
+
get pending(): number;
|
|
35
|
+
/**
|
|
36
|
+
* Acquire a permit. Resolves immediately if one is available,
|
|
37
|
+
* otherwise queues the caller until a permit is released.
|
|
38
|
+
*/
|
|
39
|
+
acquire(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Release a permit. If callers are queued, the next one is resolved.
|
|
42
|
+
* Must be called exactly once for each successful `acquire()`.
|
|
43
|
+
*/
|
|
44
|
+
release(): void;
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=concurrency.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"concurrency.d.ts","sourceRoot":"","sources":["../../src/engine/concurrency.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;;GAcG;AACH,qBAAa,SAAS;IAQR,OAAO,CAAC,QAAQ,CAAC,GAAG;IAPhC,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAsB;IAE5C;;;OAGG;gBAC0B,GAAG,EAAE,MAAM;IAMxC,iDAAiD;IACjD,IAAI,SAAS,IAAI,MAAM,CAEtB;IAED,yDAAyD;IACzD,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAW9B;;;OAGG;IACH,OAAO,IAAI,IAAI;CAQhB"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Embedded default seccomp profile.
|
|
3
|
+
*
|
|
4
|
+
* This keeps strict seccomp available in standalone compiled binaries where
|
|
5
|
+
* docker/seccomp-profile.json may not be present on disk.
|
|
6
|
+
*/
|
|
7
|
+
export declare const EMBEDDED_DEFAULT_SECCOMP_PROFILE: string;
|
|
8
|
+
//# sourceMappingURL=default-seccomp-profile.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default-seccomp-profile.d.ts","sourceRoot":"","sources":["../../src/engine/default-seccomp-profile.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,QA6D3C,CAAC"}
|