@replayio/app-building 1.32.0 → 1.33.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 CHANGED
@@ -74,14 +74,45 @@ The secrets server spawns the command with the requested secrets in its environm
74
74
 
75
75
  The agent can also run `list-secrets` to see which secrets are available, and `set-branch-secret` to store new branch-level secrets (e.g., `DATABASE_URL` created at deploy time). The server rejects credential values that have already appeared in logs.
76
76
 
77
+ ### Allowlist mode
78
+
79
+ Set `ContainerConfig.secretAllowlist` to restrict which commands the agent may invoke via `exec-secrets`. The allowlist constrains the *target* — the agent still names which secrets to inject in each call. Each entry is `{ name, helpString, shellCommand }`:
80
+
81
+ ```ts
82
+ const config: ContainerConfig = {
83
+ ...,
84
+ secretAllowlist: [
85
+ {
86
+ name: "neon-query",
87
+ helpString: "Run a SQL query against the project's Neon database. Usage: exec-secrets DATABASE_URL -- neon-query <sql>",
88
+ shellCommand: 'psql "$DATABASE_URL" -c "$1"',
89
+ },
90
+ {
91
+ name: "netlify-deploy",
92
+ helpString: "Deploy the app to production.",
93
+ shellCommand: "netlify deploy --prod --auth $NETLIFY_AUTH_TOKEN --site $NETLIFY_SITE_ID",
94
+ },
95
+ ],
96
+ };
97
+ ```
98
+
99
+ When configured:
100
+
101
+ - `list-secrets` returns both `secrets` (available secret names) and `allowlist` (`name — helpString` per entry).
102
+ - `exec-secrets` keeps the same surface: `exec-secrets <SECRET1> [SECRET2 …] -- <name> [args…]`. The named secrets are still injected into env (and only those values are redacted from output); `<name>` must match an allowlist entry, and `[args…]` become positional params (`$1`, `$2`, …) inside that entry's `shellCommand`. `$0` is `"exec-secrets"`.
103
+ - Targets not in the allowlist are rejected — the agent can't invoke arbitrary binaries.
104
+
105
+ `secretAllowlist` is serialized into the container as `SECRET_ALLOWLIST_JSON`; the secrets server parses it on startup. To swap or clear the allowlist on a running container without restarting, POST to `/reconfigure` (see Container HTTP API).
106
+
77
107
  ## Exported API
78
108
 
79
109
  ### Domain objects
80
110
 
81
111
  | Export | Description |
82
112
  |---|---|
83
- | `ContainerConfig` | `infisical` (required `InfisicalConfig`), optional `projectRoot` (local Docker only), `registry`, `flyToken`/`flyApp` (set both for remote Fly.io), `flyGuest` (override Fly Machine guest sizing; default: 16 performance CPUs / 32 GiB), `flyVolumeSizeGb` (override Fly Volume size in GiB; default: 50), `imageRef`, `webhookUrl`/`webhookSecret`, `taskWebhookUrl` (GET endpoint for external task queue), `addTaskWebhookUrl` (POST endpoint for tasks added by `add-task` script), `detached`, `initialPrompt`, `localPort`, `absorbTasks`, `namePrefix` (default: `"app-building"`), `env` (extra env vars to inject into the container; cannot clobber package-reserved vars). |
113
+ | `ContainerConfig` | `infisical` (required `InfisicalConfig`), optional `projectRoot` (local Docker only), `registry`, `flyToken`/`flyApp` (set both for remote Fly.io), `flyGuest` (override Fly Machine guest sizing; default: 16 performance CPUs / 32 GiB), `flyVolumeSizeGb` (override Fly Volume size in GiB; default: 50), `imageRef`, `webhookUrl`/`webhookSecret`, `taskWebhookUrl` (GET endpoint for external task queue), `addTaskWebhookUrl` (POST endpoint for tasks added by `add-task` script), `detached`, `initialPrompt`, `localPort`, `absorbTasks`, `namePrefix` (default: `"app-building"`), `env` (extra env vars to inject into the container; cannot clobber package-reserved vars), `secretAllowlist` (curated `exec-secrets` commands — see Secrets architecture > Allowlist mode). |
84
114
  | `FlyGuest` | Fly Machine guest spec: `cpu_kind` (`"shared"` \| `"performance"`), `cpus`, `memory_mb`. |
115
+ | `SecretAllowlistEntry` | `name` (verb used with `exec-secrets`), `helpString` (one-line description shown by `list-secrets`), `shellCommand` (sh script body; args become `$1`, `$2`, …). |
85
116
  | `RepoOptions` | Per-invocation git settings: `repoUrl`, `cloneBranch`, `pushBranch`. |
86
117
  | `AgentState` | Returned by `startContainer`. Contains `type`, `containerName`, `port`, `baseUrl`, and Fly-specific fields for remote containers. |
87
118
  | `ContainerRegistry` | Interface for container registry storage. Methods: `log`, `markStopped`, `clearStopped`, `getRecent`, `find`, `findAlive`. |
@@ -162,6 +193,7 @@ Each container runs an HTTP server that accepts the following requests:
162
193
  | `POST /detach` | | Signal the container to exit once all tasks are done. |
163
194
  | `POST /stop` | | Force-stop the container immediately. Interrupts any running work, commits remaining changes, then exits. |
164
195
  | `POST /interrupt` | | Kill the currently running Claude process without stopping the container. |
196
+ | `POST /reconfigure` | `{ secretAllowlist?: SecretAllowlistEntry[] \| null }` | Live-update container config. Omit to leave unchanged; `null` for unrestricted mode (`exec-secrets <SECRETS…> -- <cmd>` runs any binary); `[]` for restricted mode with zero entries (rejects every target); an array of entries to replace. Returns `{ ok: true }`. |
165
197
  | `GET /status` | | Container state, queue depth, iteration count, cost, revision, etc. |
166
198
  | `GET /events?offset=N` | | Stream of Claude events (JSON lines) since offset. |
167
199
  | `GET /logs?offset=N` | | Stream of log lines since offset. |
package/dist/index.d.ts CHANGED
@@ -125,6 +125,29 @@ interface ContainerConfig {
125
125
  * always take precedence — values here cannot clobber them.
126
126
  */
127
127
  env?: Record<string, string>;
128
+ /**
129
+ * Allowlist of named shell commands the agent may invoke via `exec-secrets`.
130
+ * When set, `list-secrets` returns the allowlist (instead of raw secret
131
+ * names) and `exec-secrets <name> [args…]` runs the named entry's
132
+ * `shellCommand` with `args` mapped to positional params (`$1`, `$2`, …).
133
+ * All secrets are available in the command's environment; their values are
134
+ * redacted from output. When no allowlist is set, the container runs in
135
+ * unrestricted mode and `exec-secrets <SECRET…> -- <cmd>` may invoke any
136
+ * binary; with an allowlist set, the target after `--` must name an entry.
137
+ */
138
+ secretAllowlist?: SecretAllowlistEntry[];
139
+ }
140
+ interface SecretAllowlistEntry {
141
+ /** Name the agent uses with `exec-secrets <name>` (e.g. "neon-query"). */
142
+ name: string;
143
+ /** One-line description shown by `list-secrets`. */
144
+ helpString: string;
145
+ /**
146
+ * Shell script body. Invoked as `sh -c <shellCommand> exec-secrets <args…>`,
147
+ * so caller-supplied args become `$1`, `$2`, … and `$0` is `exec-secrets`.
148
+ * All secrets are present in the environment.
149
+ */
150
+ shellCommand: string;
128
151
  }
129
152
  interface RepoOptions {
130
153
  repoUrl: string;
@@ -202,4 +225,4 @@ interface Task {
202
225
  */
203
226
  declare function findReadyTask(pendingTasks: Task[], completedTasks: Pick<Task, "id" | "parentTaskId">[]): Task | null;
204
227
 
205
- export { type AgentState, type ContainerConfig, type ContainerRegistry, FileContainerRegistry, type FlyGuest, type HttpOptions, type InfisicalConfig, type RegistryEntry, type RepoOptions, type Task, buildImage, createBranchSecret, fetchBranchSecrets, fetchGlobalSecrets, fetchInfisicalSecrets, findReadyTask, getImageRef, getInfisicalConfig, httpGet, httpOptsFor, httpPost, infisicalLogin, loadDotEnv, probeAlive, spawnTestContainer, startContainer, stopContainer };
228
+ export { type AgentState, type ContainerConfig, type ContainerRegistry, FileContainerRegistry, type FlyGuest, type HttpOptions, type InfisicalConfig, type RegistryEntry, type RepoOptions, type SecretAllowlistEntry, type Task, buildImage, createBranchSecret, fetchBranchSecrets, fetchGlobalSecrets, fetchInfisicalSecrets, findReadyTask, getImageRef, getInfisicalConfig, httpGet, httpOptsFor, httpPost, infisicalLogin, loadDotEnv, probeAlive, spawnTestContainer, startContainer, stopContainer };
package/dist/index.js CHANGED
@@ -256,6 +256,9 @@ function buildExtraEnv(config, containerName) {
256
256
  if (config.env && Object.keys(config.env).length > 0) {
257
257
  extra.AGENT_ENV_PASSTHROUGH = Object.keys(config.env).join(",");
258
258
  }
259
+ if (config.secretAllowlist !== void 0) {
260
+ extra.SECRET_ALLOWLIST_JSON = JSON.stringify(config.secretAllowlist);
261
+ }
259
262
  return extra;
260
263
  }
261
264
  function isRemote(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@replayio/app-building",
3
- "version": "1.32.0",
3
+ "version": "1.33.0",
4
4
  "description": "Library for managing agentic app-building containers",
5
5
  "type": "module",
6
6
  "exports": {