@bakers-build/addon-runtime-docker 0.9.1-next.5 → 0.9.1-next.6

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.
@@ -13,11 +13,10 @@ export interface DockerOptions {
13
13
  }
14
14
  /** Host facts the launch depends on, resolved by the backend. */
15
15
  export interface HostPaths {
16
- gitCommonDir: string;
17
- /** The worktree's own Git dir; the common dir itself for a main checkout. */
18
- gitDir: string;
19
- /** `<worktree>/.git` when it is a gitlink file. */
20
- gitLink?: string;
16
+ /** The run clone's Git directory, mounted as `<workspace>/.git`. */
17
+ runGitDir?: string;
18
+ /** A `shared` clone's parent object store, mounted read-only at its own path. */
19
+ sharedObjects?: string;
21
20
  /** The per-run home, mounted at its host path. */
22
21
  runHome: string;
23
22
  /** The bakers CLI build, mounted read-only at its host path. */
package/dist/src/args.js CHANGED
@@ -1,4 +1,3 @@
1
- import { join } from 'node:path';
2
1
  /** The image's PATH, behind the run home's `bin` (the `bakers` shim lives there). */
3
2
  export const RUN_PATH = `/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin`;
4
3
  export const PROXY_PORTS = { egress: 3128, api: 8081, tools: 8082 };
@@ -37,9 +36,6 @@ export function dockerArgs(input, opts, where) {
37
36
  !overridden.has(k) &&
38
37
  // Allowlist mode keeps the model credential in the proxy only.
39
38
  !(allowlist && credentials.includes(k)));
40
- const common = where.gitCommonDir;
41
- const gitDir = where.gitDir;
42
- const linked = gitDir !== common;
43
39
  const ro = (path) => bind(path, path, ':ro');
44
40
  const argv = [
45
41
  opts.docker,
@@ -58,30 +54,16 @@ export function dockerArgs(input, opts, where) {
58
54
  ...computed.flatMap((c) => ['-e', c]),
59
55
  ...forwarded.flatMap((k) => ['-e', k]),
60
56
  ...bind(ws),
61
- ...(where.gitLink ? bind(where.gitLink, undefined, ':ro') : []),
62
- ...bind(common),
63
- // The run must not plant a hook, rewrite Git config or repoint a worktree's
64
- // Git dir: host git in these repos would run what the run chose.
65
- ...ro(join(common, 'hooks')),
66
- ...ro(join(common, 'config')),
67
- // Read by host git in the main checkout once `extensions.worktreeConfig` is on.
68
- ...ro(join(common, 'config.worktree')),
69
- ...ro(join(common, 'modules')),
70
- ...ro(join(common, 'worktrees')),
71
- ...(linked
72
- ? [
73
- // Docker mounts by depth, so this reopens only the run's own Git dir.
74
- ...bind(gitDir),
75
- ...ro(join(gitDir, 'commondir')),
76
- ...ro(join(gitDir, 'gitdir')),
77
- ...ro(join(gitDir, 'config.worktree')),
78
- ]
79
- : []),
57
+ // The only Git the run sees; the parent repository is not mounted.
58
+ ...(where.runGitDir ? bind(where.runGitDir, `${ws}/.git`) : []),
59
+ ...(where.sharedObjects ? ro(where.sharedObjects) : []),
80
60
  ...bind(where.cliRoot, undefined, ':ro'),
81
61
  ...opts.mounts.flatMap((m) => bind(m, m, ':ro')),
82
62
  ...bind(where.runHome),
83
63
  ...(input.readPaths ?? []).flatMap((path) => ro(path)),
84
64
  ...(input.writePaths ?? []).flatMap((path) => bind(path)),
65
+ // Shared host files over their placeholders in the run home.
66
+ ...(input.bindPaths ?? []).flatMap(({ source, target }) => bind(source, target)),
85
67
  '-w',
86
68
  ws,
87
69
  opts.image,
@@ -31,6 +31,5 @@ export interface BackendDeps {
31
31
  }
32
32
  /** The first 12 hex chars of the sha256 of `text`. */
33
33
  export declare const shortHash: (text: string) => string;
34
- export { quarantinePlanted } from '@bakers-build/core';
35
34
  export declare const realExec: BackendDeps['exec'];
36
35
  export declare function dockerRuntime(options: RuntimeDockerOptions, deps: BackendDeps): BbRuntime;
@@ -1,15 +1,14 @@
1
1
  import { execFile } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
- import { existsSync, mkdirSync, readFileSync, realpathSync, rmSync, statSync, writeFileSync, } from 'node:fs';
3
+ import { existsSync, mkdirSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
4
4
  import { homedir } from 'node:os';
5
- import { isAbsolute, join, resolve, sep } from 'node:path';
5
+ import { dirname, isAbsolute, join, resolve, sep } from 'node:path';
6
6
  import { promisify } from 'node:util';
7
- import { quarantinePlanted, shellQuote, } from '@bakers-build/core';
7
+ import { shellQuote } from '@bakers-build/core';
8
8
  import { API_KEY_PLACEHOLDER, dockerArgs, PROXY_PORTS, } from './args.js';
9
9
  const EGRESS_NETWORK = 'bb-runtime-egress';
10
10
  /** The first 12 hex chars of the sha256 of `text`. */
11
11
  export const shortHash = (text) => createHash('sha256').update(text).digest('hex').slice(0, 12);
12
- export { quarantinePlanted } from '@bakers-build/core';
13
12
  const run = promisify(execFile);
14
13
  export const realExec = (file, args) => run(file, args);
15
14
  export function dockerRuntime(options, deps) {
@@ -35,8 +34,6 @@ export function dockerRuntime(options, deps) {
35
34
  throw new Error(`runtime-docker: invalid run uuid ${JSON.stringify(uuid)}`);
36
35
  return join(cacheHome, 'bakers/runtime', uuid);
37
36
  };
38
- // Beside the run home, never mounted: cleanup trusts it to find the worktree.
39
- const recordOf = (uuid) => `${runHomeOf(uuid)}.workspace.json`;
40
37
  // One image per agent install; built once each.
41
38
  const imagesReady = new Map();
42
39
  const ensureImage = async (image, install) => {
@@ -111,13 +108,6 @@ export function dockerRuntime(options, deps) {
111
108
  ]);
112
109
  await deps.exec(docker, ['network', 'connect', opts.runNetwork, proxyName]);
113
110
  };
114
- const git = async (ws, flag) => (await deps.exec('git', [
115
- '-C',
116
- ws,
117
- 'rev-parse',
118
- '--path-format=absolute',
119
- flag,
120
- ])).stdout.trim();
121
111
  const credentialsFor = (agent) => {
122
112
  const api = agent.modelApi;
123
113
  if (allowlist && !api)
@@ -176,14 +166,21 @@ export function dockerRuntime(options, deps) {
176
166
  await proxyReady.ready;
177
167
  }
178
168
  const ws = launch.workspacePath;
179
- const gitCommonDir = await git(ws, '--git-common-dir');
180
- const gitDir = await git(ws, '--git-dir');
181
- const gitLink = join(ws, '.git');
169
+ const runGitDir = launch.gitDir;
182
170
  const within = (path, root) => path === root || path.startsWith(root + sep);
171
+ if (within(realpathSync(dirname(runHome)), realpathSync(ws)))
172
+ throw new Error('runtime-docker: private state must stay outside the worktree');
173
+ if (runGitDir && within(realpathSync(runGitDir), realpathSync(ws)))
174
+ throw new Error('runtime-docker: the run Git directory must stay outside the workspace');
183
175
  const canonical = (path) => existsSync(path) ? realpathSync(path) : resolve(path);
176
+ const binds = launch.bindPaths ?? [];
177
+ for (const { target } of binds)
178
+ if (target.includes(':') || !within(resolve(target), resolve(runHome)))
179
+ throw new Error(`runtime-docker: shared file target outside the run home ${target}`);
184
180
  for (const [paths, write] of [
185
181
  [launch.readPaths ?? [], false],
186
182
  [launch.writePaths ?? [], true],
183
+ [binds.map(({ source }) => source), true],
187
184
  ]) {
188
185
  for (const path of paths) {
189
186
  if (!isAbsolute(path) || path.includes(':') || !existsSync(path))
@@ -193,21 +190,19 @@ export function dockerRuntime(options, deps) {
193
190
  (deps.env.HOME && within(canonical(deps.env.HOME), actual)))
194
191
  throw new Error(`runtime-docker: refusing broad path ${path}`);
195
192
  if (write &&
196
- [gitCommonDir, gitLink, deps.cliRoot].some((root) => within(actual, canonical(root)) ||
193
+ [
194
+ ...(runGitDir ? [runGitDir] : []),
195
+ ...(launch.sharedObjects ? [launch.sharedObjects] : []),
196
+ dirname(runHome),
197
+ join(ws, '.git'),
198
+ deps.cliRoot,
199
+ ...(launch.readPaths ?? []),
200
+ ...opts.mounts,
201
+ ].some((root) => within(actual, canonical(root)) ||
197
202
  within(canonical(root), actual)))
198
203
  throw new Error(`runtime-docker: protected write path ${path}`);
199
204
  }
200
205
  }
201
- // Bound read-only, so each must exist before docker would create it as root.
202
- for (const d of ['hooks', 'modules', 'worktrees'])
203
- mkdirSync(join(gitCommonDir, d), { recursive: true });
204
- for (const dir of new Set([gitCommonDir, gitDir])) {
205
- const worktreeConfig = join(dir, 'config.worktree');
206
- if (!existsSync(worktreeConfig))
207
- writeFileSync(worktreeConfig, '');
208
- }
209
- mkdirSync(runHome, { recursive: true });
210
- writeFileSync(recordOf(launch.run.uuid), JSON.stringify({ workspace: ws, gitCommonDir }));
211
206
  // The CLI build is mounted read-only; this puts the conductor's own CLI on PATH.
212
207
  if (deps.bakersEntry) {
213
208
  mkdirSync(join(runHome, 'bin'), { recursive: true });
@@ -219,10 +214,9 @@ export function dockerRuntime(options, deps) {
219
214
  for (const k of credentials)
220
215
  env[k] = deps.env[k];
221
216
  const { argv, env: runEnv } = dockerArgs({ ...launch, env }, { ...opts, image }, {
222
- gitCommonDir,
223
- gitDir,
224
- ...(statSync(gitLink, { throwIfNoEntry: false })?.isFile()
225
- ? { gitLink }
217
+ ...(runGitDir ? { runGitDir } : {}),
218
+ ...(launch.sharedObjects
219
+ ? { sharedObjects: launch.sharedObjects }
226
220
  : {}),
227
221
  runHome,
228
222
  cliRoot: deps.cliRoot,
@@ -253,20 +247,6 @@ export function dockerRuntime(options, deps) {
253
247
  deps.logger.warn({ run: r.uuid, err: String(err) }, 'runtime container cleanup failed');
254
248
  return;
255
249
  }
256
- // After the container is gone, so the run cannot plant anything behind the scan.
257
- const record = recordOf(r.uuid);
258
- if (existsSync(record)) {
259
- try {
260
- const { workspace, gitCommonDir } = JSON.parse(readFileSync(record, 'utf8'));
261
- const moved = await quarantinePlanted(workspace, gitCommonDir, deps.exec);
262
- if (moved.length > 0)
263
- deps.logger.warn({ run: r.uuid, moved }, 'runtime quarantined repositories the run planted');
264
- rmSync(record);
265
- }
266
- catch (err) {
267
- deps.logger.error({ run: r.uuid, err: String(err) }, 'runtime quarantine failed: host git in the worktree may run what the run planted');
268
- }
269
- }
270
250
  rmSync(runHomeOf(r.uuid), { recursive: true, force: true });
271
251
  },
272
252
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bakers-build/addon-runtime-docker",
3
- "version": "0.9.1-next.5",
3
+ "version": "0.9.1-next.6",
4
4
  "description": "Start each agent run in its own Docker or Podman container.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,11 +23,11 @@
23
23
  "directory": "bakers-cli/addons/runtime-docker"
24
24
  },
25
25
  "peerDependencies": {
26
- "@bakers-build/conductor": "^0.9.1-next.5",
27
- "@bakers-build/core": "^0.9.1-next.5"
26
+ "@bakers-build/conductor": "^0.9.1-next.6",
27
+ "@bakers-build/core": "^0.9.1-next.6"
28
28
  },
29
29
  "bakersBuild": {
30
- "commit": "d9242cad6cabee1faf010b5d46ac1bac33f6e0a1",
30
+ "commit": "e7d9921d51d5c045f29ca7e35d1e6a348e6ddf08",
31
31
  "channel": "next"
32
32
  }
33
33
  }