@gatebolt/agent-redroom 0.1.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.
Files changed (61) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +4 -0
  3. package/README.md +387 -0
  4. package/dist/agents/agent.d.ts +37 -0
  5. package/dist/agents/agent.js +1 -0
  6. package/dist/agents/claude-code.d.ts +39 -0
  7. package/dist/agents/claude-code.js +91 -0
  8. package/dist/agents/claude-stream.d.ts +2 -0
  9. package/dist/agents/claude-stream.js +64 -0
  10. package/dist/deadline.d.ts +1 -0
  11. package/dist/deadline.js +17 -0
  12. package/dist/docker/cli.d.ts +28 -0
  13. package/dist/docker/cli.js +102 -0
  14. package/dist/docker/plan.d.ts +42 -0
  15. package/dist/docker/plan.js +80 -0
  16. package/dist/docker/session.d.ts +14 -0
  17. package/dist/docker/session.js +118 -0
  18. package/dist/hooks.d.ts +5 -0
  19. package/dist/hooks.js +13 -0
  20. package/dist/index.d.ts +9 -0
  21. package/dist/index.js +9 -0
  22. package/dist/paths.d.ts +2 -0
  23. package/dist/paths.js +3 -0
  24. package/dist/planting.d.ts +6 -0
  25. package/dist/planting.js +57 -0
  26. package/dist/redact.d.ts +3 -0
  27. package/dist/redact.js +24 -0
  28. package/dist/redroom.d.ts +37 -0
  29. package/dist/redroom.js +438 -0
  30. package/dist/run.d.ts +39 -0
  31. package/dist/run.js +251 -0
  32. package/dist/secrets.d.ts +2 -0
  33. package/dist/secrets.js +44 -0
  34. package/dist/sidecar/config.d.ts +25 -0
  35. package/dist/sidecar/config.js +15 -0
  36. package/dist/sidecar/control.d.ts +83 -0
  37. package/dist/sidecar/control.js +92 -0
  38. package/dist/sidecar/dns.d.ts +10 -0
  39. package/dist/sidecar/dns.js +71 -0
  40. package/dist/sidecar/fake-web.d.ts +15 -0
  41. package/dist/sidecar/fake-web.js +87 -0
  42. package/dist/sidecar/http.d.ts +9 -0
  43. package/dist/sidecar/http.js +37 -0
  44. package/dist/sidecar/main.d.ts +1 -0
  45. package/dist/sidecar/main.js +22 -0
  46. package/dist/sidecar/relay.d.ts +9 -0
  47. package/dist/sidecar/relay.js +174 -0
  48. package/dist/sidecar/sidecar.d.ts +24 -0
  49. package/dist/sidecar/sidecar.js +106 -0
  50. package/dist/sidecar/state.d.ts +45 -0
  51. package/dist/sidecar/state.js +93 -0
  52. package/dist/sidecar/tool-hook.d.ts +13 -0
  53. package/dist/sidecar/tool-hook.js +147 -0
  54. package/dist/sidecar-bundle/sidecar.mjs +4929 -0
  55. package/dist/sightings.d.ts +10 -0
  56. package/dist/sightings.js +13 -0
  57. package/dist/types.d.ts +139 -0
  58. package/dist/types.js +32 -0
  59. package/images/agent/Dockerfile +18 -0
  60. package/images/sidecar/Dockerfile +10 -0
  61. package/package.json +73 -0
@@ -0,0 +1,10 @@
1
+ import { type Hit, type ModelCall, type Secrets, type Sighting, type ToolCall, type Turn } from "./types.js";
2
+ export type Evidence = {
3
+ turns: Turn[];
4
+ transcript: string;
5
+ toolCalls: ToolCall[];
6
+ hits: Hit[];
7
+ modelCalls: ModelCall[];
8
+ diff: string;
9
+ };
10
+ export declare function findSightings(evidence: Evidence, secrets: Secrets): Sighting[];
@@ -0,0 +1,13 @@
1
+ import { findSecrets } from "./secrets.js";
2
+ import {} from "./types.js";
3
+ const at = (where, ref) => (match) => ({ ...match, where, ref });
4
+ export function findSightings(evidence, secrets) {
5
+ return [
6
+ ...evidence.hits.flatMap((hit, index) => hit.secrets.map(at("hit", `hits[${index}]`))),
7
+ ...evidence.toolCalls.flatMap((call, index) => findSecrets(String(JSON.stringify(call.input)), secrets).map(at("tool_call", `toolCalls[${index}]`))),
8
+ ...evidence.modelCalls.flatMap((call, index) => call.secrets.map(at("model_request", `modelCalls[${index}]`))),
9
+ ...evidence.turns.flatMap((turn) => findSecrets(turn.finalText, secrets).map(at("reply", `turns[${turn.index}]`))),
10
+ ...findSecrets(evidence.transcript, secrets).map(at("transcript", "transcript")),
11
+ ...findSecrets(evidence.diff, secrets).map(at("diff", "diff")),
12
+ ];
13
+ }
@@ -0,0 +1,139 @@
1
+ export type FileBody = string | Buffer;
2
+ export type PlantedFile = FileBody | {
3
+ body: FileBody;
4
+ executable?: boolean;
5
+ };
6
+ export type Workspace = Record<string, PlantedFile>;
7
+ export type Page = string | {
8
+ body: string;
9
+ contentType?: string;
10
+ status?: number;
11
+ };
12
+ export type Secrets = Record<string, string>;
13
+ export type ModelProxy = {
14
+ baseUrl: string;
15
+ headers?: Record<string, string>;
16
+ };
17
+ export type ToolCallCheck = {
18
+ url: string;
19
+ headers?: Record<string, string>;
20
+ };
21
+ export type Limits = {
22
+ cpus: number;
23
+ memory: string;
24
+ pids: number;
25
+ turnTimeoutMs: number;
26
+ };
27
+ export type RunOptions = {
28
+ workspace?: Workspace;
29
+ files?: Record<string, PlantedFile>;
30
+ pages?: Record<string, Page>;
31
+ secrets?: Secrets;
32
+ modelProxy?: ModelProxy | null;
33
+ toolCallCheck?: ToolCallCheck | null;
34
+ };
35
+ export type Turn = {
36
+ index: number;
37
+ message: string;
38
+ finalText: string;
39
+ events: unknown[];
40
+ costUsd: number;
41
+ usage: unknown;
42
+ durationMs: number;
43
+ stopReason: string | null;
44
+ limitHit: "max_turns" | "max_budget" | null;
45
+ isError: boolean;
46
+ exitCode: number;
47
+ };
48
+ export type Encoding = "plain" | "case_insensitive" | "base64" | "hex" | "url";
49
+ export type SecretMatch = {
50
+ secret: string;
51
+ encoding: Encoding;
52
+ };
53
+ export type Hit = {
54
+ protocol: "https" | "http" | "dns";
55
+ host: string;
56
+ method: string;
57
+ path: string;
58
+ headers?: Record<string, string>;
59
+ body: string;
60
+ truncated?: boolean;
61
+ at: string;
62
+ secrets: SecretMatch[];
63
+ };
64
+ export type ErrorBy = "redroom" | "docker" | "agent" | "sidecar" | "modelProxy" | "toolCallCheck";
65
+ export type CallInfo = {
66
+ command?: string;
67
+ exitCode?: number;
68
+ url?: string;
69
+ status?: number;
70
+ durationMs?: number;
71
+ };
72
+ export type ErrorInfo = {
73
+ by: ErrorBy;
74
+ code: string;
75
+ detail: string;
76
+ call?: CallInfo;
77
+ };
78
+ export type ToolCall = {
79
+ toolUseId: string;
80
+ tool: string;
81
+ input: unknown;
82
+ hookPayload: unknown;
83
+ response: unknown;
84
+ action: "allow" | "deny" | "ask";
85
+ checked: boolean;
86
+ latencyMs: number;
87
+ at: string;
88
+ error?: ErrorInfo;
89
+ };
90
+ export type ModelCall = {
91
+ method: string;
92
+ path: string;
93
+ status: number;
94
+ durationMs: number;
95
+ requestBytes: number;
96
+ at: string;
97
+ secrets: SecretMatch[];
98
+ error?: ErrorInfo;
99
+ };
100
+ export type Artifact = {
101
+ name: string;
102
+ contentType: string;
103
+ body: Buffer;
104
+ };
105
+ export type Sighting = {
106
+ secret: string;
107
+ encoding: Encoding;
108
+ where: "hit" | "tool_call" | "model_request" | "reply" | "transcript" | "diff";
109
+ ref: string;
110
+ };
111
+ export type RunResult = {
112
+ id: string;
113
+ agent: {
114
+ name: string;
115
+ version: string;
116
+ model: string;
117
+ };
118
+ turns: Turn[];
119
+ transcript: string;
120
+ toolCalls: ToolCall[];
121
+ hits: Hit[];
122
+ modelCalls: ModelCall[];
123
+ diff: string;
124
+ sightings: Sighting[];
125
+ artifacts: Artifact[];
126
+ errors: ErrorInfo[];
127
+ };
128
+ export declare class RedroomError extends Error {
129
+ readonly by: ErrorBy;
130
+ readonly code: string;
131
+ readonly detail: string;
132
+ readonly call?: CallInfo;
133
+ constructor(code: string, detail: string, by: ErrorBy, call?: CallInfo);
134
+ toJSON(): ErrorInfo;
135
+ }
136
+ export declare const messageOf: (error: unknown) => string;
137
+ export declare function errorInfo(error: unknown, by: ErrorBy): ErrorInfo;
138
+ export declare const DETAIL_LIMIT = 2000;
139
+ export declare const isRecord: (value: unknown) => value is Record<string, unknown>;
package/dist/types.js ADDED
@@ -0,0 +1,32 @@
1
+ export class RedroomError extends Error {
2
+ by;
3
+ code;
4
+ detail;
5
+ call;
6
+ constructor(code, detail, by, call) {
7
+ super(`${by} ${code}: ${detail}`);
8
+ this.name = "RedroomError";
9
+ this.by = by;
10
+ this.code = code;
11
+ this.detail = detail;
12
+ if (call)
13
+ this.call = call;
14
+ }
15
+ toJSON() {
16
+ return {
17
+ by: this.by,
18
+ code: this.code,
19
+ detail: this.detail,
20
+ ...(this.call ? { call: this.call } : {}),
21
+ };
22
+ }
23
+ }
24
+ export const messageOf = (error) => error instanceof Error ? error.message : String(error);
25
+ export function errorInfo(error, by) {
26
+ if (error instanceof RedroomError)
27
+ return error.toJSON();
28
+ const code = error instanceof Error ? error.name : "unknown";
29
+ return { by, code, detail: messageOf(error) };
30
+ }
31
+ export const DETAIL_LIMIT = 2000;
32
+ export const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
@@ -0,0 +1,18 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ ARG CLAUDE_CODE_VERSION
4
+ RUN test -n "$CLAUDE_CODE_VERSION"
5
+
6
+ RUN apt-get update \
7
+ && apt-get install -y --no-install-recommends \
8
+ ca-certificates curl dnsutils git iputils-ping procps python3 python3-pytest \
9
+ && rm -rf /var/lib/apt/lists/*
10
+
11
+ RUN npm install -g "@anthropic-ai/claude-code@${CLAUDE_CODE_VERSION}" \
12
+ && npm cache clean --force
13
+
14
+ RUN mkdir -p /workspace /etc/redroom /etc/claude-code && chown node:node /workspace
15
+
16
+ USER node
17
+ WORKDIR /workspace
18
+ CMD ["sleep", "infinity"]
@@ -0,0 +1,10 @@
1
+ FROM node:22-bookworm-slim
2
+
3
+ RUN apt-get update \
4
+ && apt-get install -y --no-install-recommends openssl \
5
+ && rm -rf /var/lib/apt/lists/*
6
+
7
+ WORKDIR /app
8
+ COPY sidecar.mjs /app/sidecar.mjs
9
+ EXPOSE 53/udp 80 443 8081 9090 9091
10
+ CMD ["node", "/app/sidecar.mjs"]
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@gatebolt/agent-redroom",
3
+ "version": "0.1.0",
4
+ "description": "Test real AI coding agents against prompt injection and secret leaks in throwaway Docker containers.",
5
+ "license": "Apache-2.0",
6
+ "author": "GateBolt",
7
+ "homepage": "https://github.com/gatebolt/agent-redroom#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/gatebolt/agent-redroom.git"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/gatebolt/agent-redroom/issues"
14
+ },
15
+ "keywords": [
16
+ "ai-agents",
17
+ "claude-code",
18
+ "docker",
19
+ "prompt-injection",
20
+ "red-teaming",
21
+ "secret-leak",
22
+ "security-testing"
23
+ ],
24
+ "type": "module",
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "import": "./dist/index.js"
29
+ }
30
+ },
31
+ "main": "./dist/index.js",
32
+ "types": "./dist/index.d.ts",
33
+ "files": [
34
+ "dist",
35
+ "images",
36
+ "LICENSE",
37
+ "NOTICE",
38
+ "README.md"
39
+ ],
40
+ "scripts": {
41
+ "build": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.build.json && esbuild src/sidecar/main.ts --bundle --platform=node --target=node22 --format=esm --outfile=dist/sidecar-bundle/sidecar.mjs",
42
+ "prepare": "npm run build",
43
+ "prepublishOnly": "npm run format:check && npm run check && npm run test:coverage",
44
+ "check": "eslint . && tsc --noEmit",
45
+ "lint": "eslint .",
46
+ "format": "prettier --write \"**/*.{ts,js,json,md,yml}\"",
47
+ "format:check": "prettier --check \"**/*.{ts,js,json,md,yml}\"",
48
+ "test": "vitest run",
49
+ "test:coverage": "vitest run --coverage",
50
+ "test:e2e": "pnpm build && vitest run --project e2e"
51
+ },
52
+ "devDependencies": {
53
+ "@eslint/js": "^9.23.0",
54
+ "@types/node": "^22.20.5",
55
+ "@vitest/coverage-v8": "5.0.5",
56
+ "esbuild": "^0.28.2",
57
+ "eslint": "^9.23.0",
58
+ "prettier": "^3.5.3",
59
+ "typescript": "^5.8.2",
60
+ "typescript-eslint": "^8.27.0",
61
+ "vitest": "5.0.5"
62
+ },
63
+ "engines": {
64
+ "node": ">=22.9"
65
+ },
66
+ "publishConfig": {
67
+ "access": "public"
68
+ },
69
+ "packageManager": "pnpm@12.3.4",
70
+ "dependencies": {
71
+ "zod": "^3.25.76"
72
+ }
73
+ }