@dharma-ai-labs/agent-fabric 0.2.135 → 0.2.136

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
@@ -37,7 +37,18 @@ command accepts no grant, policy, provider or task arguments. Inspect the
37
37
  credential-free plan with `relay container-entrypoint --dry-run` before image
38
38
  installation; it makes no local changes. Set an explicit
39
39
  private `DHARMA_HOME` before launch. An npm wrapper, shell that does not exec,
40
- `sleep` or init shim as PID1 does not qualify.
40
+ `sleep` or an arbitrary init shim as PID1 does not qualify. A narrowly verified
41
+ Docker `--init` route also supports the canonical CLI as the non-root direct
42
+ child of Docker's root-owned, non-writable `/sbin/docker-init` executable. This
43
+ route retains the existing official Node-image command
44
+ `docker-entrypoint.sh sleep infinity`; an explicitly approved public entrypoint
45
+ migration must exec the canonical Node/CLI command as that init's main child.
46
+ The CLI checks the executable inode, UID, both process start times and the
47
+ kernel's first init-child identity, retaining zombies rather than promoting
48
+ adopted processes. The controller must also retain init's session and its own
49
+ process group. The main-child and process identities are checked again before
50
+ control is admitted; a foreground or orphaned `docker exec` or a fabricated
51
+ marker cannot claim startup.
41
52
 
42
53
  The image/operator must provide its own protected Secret Service, private
43
54
  session bus and durable private home. Never mount a host bus or copy another
@@ -52,7 +63,7 @@ The entrypoint owns one relay for that container OS user. It never creates a
52
63
  second host service or changes an existing systemd user unit. Owned start/stop
53
64
  controls use atomic private receipts; disable removes the registration. It
54
65
  reaps its child on normal shutdown and retries an exited child with a bounded
55
- delay. A new container boot renews only its own PID1 lease while retaining its
66
+ delay. A new container boot renews only its own entrypoint lease while retaining its
56
67
  configuration and encrypted state. Runtime-version mismatch blocks restart;
57
68
  upgrade the reviewed image/runtime together through the normal release path.
58
69
 
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=containerDockerInit.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerDockerInit.test.d.ts","sourceRoot":"","sources":["../src/containerDockerInit.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,187 @@
1
+ import assert from 'node:assert/strict';
2
+ import { spawn } from 'node:child_process';
3
+ import { mkdtemp, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { join } from 'node:path';
6
+ import test from 'node:test';
7
+ import * as lifecycle from './containerRelayLifecycle.js';
8
+ import { enableRelayAutostart, relayAutostartStatus } from './relayAutostart.js';
9
+ const linux = { skip: process.platform !== 'linux' };
10
+ async function fixture() {
11
+ const root = await mkdtemp(join(tmpdir(), 'dharma-docker-init-'));
12
+ const home = join(root, 'device');
13
+ const workspace = join(root, 'checkout');
14
+ await mkdir(join(home, 'relay'), { recursive: true, mode: 0o700 });
15
+ await mkdir(join(workspace, '.dharma'), { recursive: true, mode: 0o700 });
16
+ const uid = process.getuid();
17
+ const init = { pid: 1, parentPid: 0, processGroupId: 1, sessionId: 1, uid, startTicks: '123', argv: ['/sbin/docker-init', '--', 'docker-entrypoint.sh', 'sleep', 'infinity'] };
18
+ const controller = { pid: 42, parentPid: 1, processGroupId: 42, sessionId: 1, uid, startTicks: '124',
19
+ argv: ['/usr/local/bin/node', '/fixture/cli/dist/bin.js', 'relay', 'container-entrypoint'] };
20
+ const marker = { schema: 'dharma.container-entrypoint/v2', home, pid: controller.pid, uid,
21
+ startTicks: controller.startTicks, initStartTicks: init.startTicks };
22
+ await writeFile(join(home, 'relay', 'container-entrypoint.json'), JSON.stringify(marker), { mode: 0o600 });
23
+ let verifiedInit = true;
24
+ let verifiedNode = true;
25
+ let mainChild = controller.pid;
26
+ const runtime = { identity: async () => init, canonicalEntrypoint: controller.argv[1], runtimeVersion: '0.2.135',
27
+ controllerIdentity: async (pid) => { assert.equal(pid, controller.pid); return controller; },
28
+ dockerInitVerified: async () => verifiedInit,
29
+ dockerInitMainChild: async () => mainChild,
30
+ controllerExecutableVerified: async () => verifiedNode,
31
+ childIdentity: async (pid) => ({ ...await lifecycle.readContainerChildIdentity(pid), parentPid: controller.pid }) };
32
+ const options = { platform: 'linux', home, uid, userHome: root, workspace,
33
+ launcher: join(workspace, '.dharma', 'bin', 'dharma'), policy: join(workspace, '.dharma', 'approved-policy.json'),
34
+ version: '0.2.135', containerRuntime: runtime,
35
+ run: async () => { throw new Error('must never use systemd for the owned init child'); } };
36
+ return { root, home, init, controller, marker, runtime, options,
37
+ untrustInit: () => { verifiedInit = false; }, untrustNode: () => { verifiedNode = false; },
38
+ setMainChild: (pid) => { mainChild = pid; } };
39
+ }
40
+ async function until(check) {
41
+ const deadline = Date.now() + 3000;
42
+ while (!check()) {
43
+ if (Date.now() > deadline)
44
+ throw new Error('fixture did not progress');
45
+ await new Promise(r => setTimeout(r, 10));
46
+ }
47
+ }
48
+ test('canonical direct child of verified Docker init can own persistent container startup', linux, async () => {
49
+ const f = await fixture();
50
+ assert.equal((await enableRelayAutostart(f.options)).backend, 'container-entrypoint');
51
+ assert.equal((await relayAutostartStatus(f.options)).state, 'enabled');
52
+ });
53
+ test('adopted canonical exec child cannot replace the Docker init main startup child', linux, async () => {
54
+ for (const kind of ['different-main-child', 'foreign-session', 'foreign-process-group', 'missing-main-child']) {
55
+ const f = await fixture();
56
+ if (kind === 'different-main-child')
57
+ f.setMainChild(7);
58
+ if (kind === 'missing-main-child')
59
+ f.setMainChild(0);
60
+ if (kind === 'foreign-session')
61
+ f.controller.sessionId = 99;
62
+ if (kind === 'foreign-process-group')
63
+ f.controller.processGroupId = 99;
64
+ await assert.rejects(enableRelayAutostart(f.options), /container_startup_unavailable/, kind);
65
+ }
66
+ });
67
+ test('Docker init main-child identity is checked again before control is admitted', linux, async () => {
68
+ const f = await fixture();
69
+ let reads = 0;
70
+ f.runtime.dockerInitMainChild = async () => ++reads === 1 ? f.controller.pid : 7;
71
+ await assert.rejects(enableRelayAutostart(f.options), /container_startup_unavailable/);
72
+ });
73
+ test('Docker init session and controller process group cannot change during attribution', linux, async () => {
74
+ for (const kind of ['init-session', 'init-group', 'controller-session', 'controller-group']) {
75
+ const f = await fixture();
76
+ let reads = 0;
77
+ if (kind.startsWith('init-'))
78
+ f.runtime.identity = async () => {
79
+ reads++;
80
+ return { ...f.init, ...(reads > 1 ? kind === 'init-session' ? { sessionId: 99 } : { processGroupId: 99 } : {}) };
81
+ };
82
+ else
83
+ f.runtime.controllerIdentity = async () => {
84
+ reads++;
85
+ return { ...f.controller, ...(reads > 1 ? kind === 'controller-session' ? { sessionId: 99 } : { processGroupId: 99 } : {}) };
86
+ };
87
+ await assert.rejects(enableRelayAutostart(f.options), /container_startup_unavailable/, kind);
88
+ }
89
+ });
90
+ test('Docker init route rejects foreign parent owner argv executable boot and controller ticks', linux, async () => {
91
+ for (const kind of ['parent', 'uid', 'argv', 'executable', 'node-executable', 'init-parent', 'init-argv', 'init-boot', 'controller-boot', 'extra-argument']) {
92
+ const f = await fixture();
93
+ if (kind === 'parent')
94
+ f.controller.parentPid = 9;
95
+ if (kind === 'uid')
96
+ f.controller.uid++;
97
+ if (kind === 'argv')
98
+ f.controller.argv[1] = '/foreign/cli/dist/bin.js';
99
+ if (kind === 'executable')
100
+ f.untrustInit();
101
+ if (kind === 'node-executable')
102
+ f.untrustNode();
103
+ if (kind === 'init-parent')
104
+ f.init.parentPid = 3;
105
+ if (kind === 'init-argv')
106
+ f.init.argv[3] = 'foreign-command';
107
+ if (kind === 'init-boot')
108
+ f.init.startTicks = '999';
109
+ if (kind === 'controller-boot')
110
+ f.controller.startTicks = '999';
111
+ if (kind === 'extra-argument')
112
+ f.controller.argv.push('--grant');
113
+ await assert.rejects(enableRelayAutostart(f.options), /container_startup_unavailable/, kind);
114
+ }
115
+ });
116
+ test('init marker cannot omit the Docker boot identity or promote an ordinary exec child to the entrypoint', linux, async () => {
117
+ const f = await fixture();
118
+ await writeFile(join(f.home, 'relay', 'container-entrypoint.json'), JSON.stringify({ ...f.marker, initStartTicks: undefined }));
119
+ await assert.rejects(enableRelayAutostart(f.options), /container_startup_unavailable/);
120
+ });
121
+ test('controller identity changes during executable verification are rejected before startup control', linux, async () => {
122
+ const f = await fixture();
123
+ let reads = 0;
124
+ f.runtime.controllerIdentity = async (pid) => {
125
+ assert.equal(pid, f.controller.pid);
126
+ reads++;
127
+ return { ...f.controller, startTicks: reads > 1 ? 'CANARY_REUSED_PROCESS' : f.controller.startTicks };
128
+ };
129
+ await assert.rejects(enableRelayAutostart(f.options), error => {
130
+ assert.match(String(error), /container_startup_unavailable/);
131
+ assert.doesNotMatch(String(error), /CANARY_REUSED_PROCESS/);
132
+ return true;
133
+ });
134
+ });
135
+ test('same Docker-init boot cannot take over another controller lease', linux, async () => {
136
+ const f = await fixture();
137
+ await writeFile(join(f.home, 'relay', 'container-entrypoint.lock'), JSON.stringify({ ...f.marker, pid: 77, startTicks: '99' }), { mode: 0o600 });
138
+ const abort = new AbortController();
139
+ await assert.rejects(lifecycle.runOwnedContainerEntrypoint({ home: f.home, uid: f.options.uid, signal: abort.signal,
140
+ containerRuntime: { ...f.runtime, currentControllerPid: f.controller.pid } }), /container_entrypoint_busy/);
141
+ });
142
+ test('different verified Docker-init boot renews only its own previous lifecycle metadata', linux, async () => {
143
+ const f = await fixture();
144
+ await writeFile(join(f.home, 'relay', 'container-entrypoint.lock'), JSON.stringify(f.marker), { mode: 0o600 });
145
+ f.init.startTicks = '223';
146
+ f.controller.startTicks = '224';
147
+ const abort = new AbortController();
148
+ const result = lifecycle.runOwnedContainerEntrypoint({ home: f.home, uid: f.options.uid, signal: abort.signal,
149
+ containerRuntime: { ...f.runtime, currentControllerPid: f.controller.pid }, pollMs: 10,
150
+ spawnRelay: () => { throw new Error('unconfigured controller cannot launch'); } });
151
+ try {
152
+ await new Promise(resolve => setTimeout(resolve, 50));
153
+ const marker = JSON.parse(await readFile(join(f.home, 'relay', 'container-entrypoint.json'), 'utf8'));
154
+ assert.equal(marker.initStartTicks, '223');
155
+ assert.equal(marker.startTicks, '224');
156
+ }
157
+ finally {
158
+ abort.abort();
159
+ }
160
+ await result;
161
+ });
162
+ test('Docker init controller fences its own supervisor and remains blocked while the consumer is locked', linux, async () => {
163
+ const f = await fixture();
164
+ await enableRelayAutostart(f.options);
165
+ const abort = new AbortController();
166
+ let ready = false;
167
+ const children = [];
168
+ const result = lifecycle.runOwnedContainerEntrypoint({ home: f.home, uid: f.options.uid, signal: abort.signal,
169
+ containerRuntime: { ...f.runtime, currentControllerPid: f.controller.pid }, pollMs: 10,
170
+ consumerStoreReady: async () => ready,
171
+ spawnRelay: () => { const child = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { stdio: 'ignore' }); children.push(child); return child; } });
172
+ try {
173
+ await new Promise(resolve => setTimeout(resolve, 50));
174
+ const heartbeat = JSON.parse(await readFile(join(f.home, 'relay', 'container-heartbeat.json'), 'utf8'));
175
+ assert.equal(heartbeat.lifecycle, 'blocked');
176
+ assert.equal(children.length, 0);
177
+ ready = true;
178
+ await until(() => children.length === 1);
179
+ assert.equal((await relayAutostartStatus(f.options)).lifecycle, 'running');
180
+ }
181
+ finally {
182
+ abort.abort();
183
+ }
184
+ await result;
185
+ assert.equal(children.every(child => child.exitCode !== null || child.signalCode !== null), true);
186
+ });
187
+ //# sourceMappingURL=containerDockerInit.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"containerDockerInit.test.js","sourceRoot":"","sources":["../src/containerDockerInit.test.ts"],"names":[],"mappings":"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"}
@@ -4,6 +4,9 @@ export interface ContainerProcessIdentity {
4
4
  uid: number;
5
5
  startTicks: string;
6
6
  argv: string[];
7
+ parentPid?: number;
8
+ processGroupId?: number;
9
+ sessionId?: number;
7
10
  }
8
11
  export interface ContainerChildIdentity {
9
12
  pid: number;
@@ -16,6 +19,11 @@ export interface ContainerRuntime {
16
19
  childIdentity?: (pid: number) => Promise<ContainerChildIdentity>;
17
20
  canonicalEntrypoint?: string;
18
21
  runtimeVersion?: string;
22
+ controllerIdentity?: (pid: number) => Promise<ContainerProcessIdentity>;
23
+ dockerInitVerified?: () => Promise<boolean>;
24
+ dockerInitMainChild?: () => Promise<number>;
25
+ controllerExecutableVerified?: (identity: ContainerProcessIdentity) => Promise<boolean>;
26
+ currentControllerPid?: number;
19
27
  }
20
28
  export interface ContainerLifecycleOptions {
21
29
  home: string;
@@ -32,6 +40,7 @@ export interface ContainerRelayRegistration {
32
40
  taskName: null;
33
41
  }
34
42
  export declare function readContainerPid1Identity(): Promise<ContainerProcessIdentity>;
43
+ export declare function readContainerProcessIdentity(pid: number): Promise<ContainerProcessIdentity>;
35
44
  export declare function readContainerChildIdentity(pid: number): Promise<ContainerChildIdentity>;
36
45
  export declare function containerEntrypointAvailable(options: ContainerLifecycleOptions): Promise<boolean>;
37
46
  export declare function containerStartupControl(options: ContainerLifecycleOptions, registration: ContainerRelayRegistration, running: boolean): Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"containerRelayLifecycle.d.ts","sourceRoot":"","sources":["../src/containerRelayLifecycle.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAOxE,MAAM,WAAW,wBAAwB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,EAAE,CAAA;CAAE;AAC1G,MAAM,WAAW,sBAAsB;IAAG,GAAG,EAAE,MAAM,CAAC;IAAC,GAAG,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;AAE3G,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,EAAE,MAAM,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACnD,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,sBAAsB,CAAC,CAAC;IACjE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AACD,MAAM,WAAW,yBAAyB;IAAG,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAA;CAAE;AAC9G,MAAM,WAAW,0BAA0B;IACzC,MAAM,EAAE,2BAA2B,CAAC;IAAC,OAAO,EAAE,sBAAsB,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IACvF,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,IAAI,CAAC;CACpE;AA+CD,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,wBAAwB,CAAC,CAQnF;AAyCD,wBAAsB,0BAA0B,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,sBAAsB,CAAC,CAO7F;AASD,wBAAsB,4BAA4B,CAAC,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,OAAO,CAAC,CASvG;AAmBD,wBAAsB,uBAAuB,CAAC,OAAO,EAAE,yBAAyB,EAC9E,YAAY,EAAE,0BAA0B,EAAE,OAAO,EAAE,OAAO,iBAM3D;AAED,wBAAsB,qBAAqB,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,0BAA0B;;;;;;GA2BvH;AAED,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,0BAA0B,oBAUtH;AAED,wBAAsB,0BAA0B,CAAC,OAAO,EAAE,yBAAyB,EAAE,YAAY,EAAE,0BAA0B,iBAE5H;AAYD,wBAAsB,yBAAyB,CAAC,OAAO,EAAE,yBAAyB,8CAKjF;AA0BD,wBAAsB,2BAA2B,IAAI,OAAO,CAAC,OAAO,CAAC,CAapE;AAoBD,wBAAsB,2BAA2B,CAAC,OAAO,EAAE,yBAAyB,GAAG;IACrF,MAAM,EAAE,WAAW,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,YAAY,EAAE,0BAA0B,KAAK,YAAY,CAAC;IAC7F,kBAAkB,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACvF;;;GAoFA"}
1
+ {"version":3,"file":"containerRelayLifecycle.d.ts","sourceRoot":"","sources":["../src/containerRelayLifecycle.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from 'node:crypto';
2
2
  import { execFile, spawn } from 'node:child_process';
3
3
  import { constants } from 'node:fs';
4
- import { lstat, mkdir, open, readFile, realpath, rename, rm, writeFile } from 'node:fs/promises';
4
+ import { lstat, mkdir, open, readFile, realpath, rename, rm, stat, writeFile } from 'node:fs/promises';
5
5
  import { dirname, isAbsolute, join, resolve } from 'node:path';
6
6
  import { promisify } from 'node:util';
7
7
  import { fileURLToPath } from 'node:url';
@@ -50,15 +50,78 @@ function exactObject(value, keys) {
50
50
  && Object.keys(value).length === keys.length && keys.every(key => Object.hasOwn(value, key)));
51
51
  }
52
52
  export async function readContainerPid1Identity() {
53
+ return readContainerProcessIdentity(1);
54
+ }
55
+ export async function readContainerProcessIdentity(pid) {
56
+ if (!Number.isSafeInteger(pid) || pid < 1)
57
+ throw unavailable();
53
58
  const [stat, command, owner] = await Promise.all([
54
- readFile('/proc/1/stat', 'utf8'), readFile('/proc/1/cmdline'), lstat('/proc/1'),
59
+ readFile(`/proc/${pid}/stat`, 'utf8'), readFile(`/proc/${pid}/cmdline`), lstat(`/proc/${pid}`),
55
60
  ]);
56
- if (stat.length > 8192 || command.length > 8192)
61
+ if (stat.length > 8192 || command.length > 8192 || Number(stat.slice(0, stat.indexOf(' '))) !== pid)
57
62
  throw unavailable();
58
63
  const fields = stat.slice(stat.lastIndexOf(')') + 2).trim().split(/\s+/);
59
- return { pid: 1, uid: owner.uid, startTicks: fields[19] || '',
64
+ if (fields[0] === 'Z' || fields[0] === 'X')
65
+ throw unavailable();
66
+ return { pid, uid: owner.uid, parentPid: Number(fields[1]), processGroupId: Number(fields[2]), sessionId: Number(fields[3]), startTicks: fields[19] || '',
60
67
  argv: command.toString('utf8').split('\0').filter(Boolean) };
61
68
  }
69
+ async function dockerInitMainChild() {
70
+ // Tini forks its main child first. Linux appends subsequently forked and
71
+ // adopted children to this kernel list; PPID 1 alone also admits orphans.
72
+ // Retain the first entry even if it is a zombie: never promote an adoptee.
73
+ const file = await open('/proc/1/task/1/children', constants.O_RDONLY);
74
+ try {
75
+ const bytes = Buffer.alloc(4097);
76
+ const { bytesRead } = await file.read(bytes, 0, bytes.length, 0);
77
+ if (bytesRead === 0 || bytesRead > 4096)
78
+ throw unavailable();
79
+ const text = bytes.subarray(0, bytesRead).toString('ascii').trim();
80
+ if (!/^[1-9]\d*(?:\s+[1-9]\d*)*$/.test(text))
81
+ throw unavailable();
82
+ const children = text.split(/\s+/).map(Number);
83
+ if (children.length > 256 || children.some(pid => !Number.isSafeInteger(pid) || pid <= 1)
84
+ || new Set(children).size !== children.length)
85
+ throw unavailable();
86
+ return children[0];
87
+ }
88
+ finally {
89
+ await file.close();
90
+ }
91
+ }
92
+ async function verifiedDockerInit() {
93
+ // Docker's --init executable is supplied by the daemon, root-owned and not
94
+ // writable by the client UID. Names or argv alone cannot establish this.
95
+ try {
96
+ const canonical = await realpath('/sbin/docker-init');
97
+ if (canonical !== '/usr/sbin/docker-init' && canonical !== '/sbin/docker-init')
98
+ return false;
99
+ if (await realpath('/proc/1/exe') !== canonical)
100
+ return false;
101
+ const [file, running] = await Promise.all([lstat(canonical), stat('/proc/1/exe')]);
102
+ return file.isFile() && file.uid === 0 && (file.mode & 0o022) === 0
103
+ && file.dev === running.dev && file.ino === running.ino;
104
+ }
105
+ catch {
106
+ return false;
107
+ }
108
+ }
109
+ async function verifiedControllerExecutable(identity) {
110
+ try {
111
+ const candidate = identity.argv[0];
112
+ if (!candidate || !isAbsolute(candidate))
113
+ return false;
114
+ const canonical = await realpath(candidate);
115
+ if (await realpath(`/proc/${identity.pid}/exe`) !== canonical)
116
+ return false;
117
+ const [file, running] = await Promise.all([lstat(canonical), stat(`/proc/${identity.pid}/exe`)]);
118
+ return file.isFile() && file.uid === 0 && (file.mode & 0o022) === 0
119
+ && file.dev === running.dev && file.ino === running.ino;
120
+ }
121
+ catch {
122
+ return false;
123
+ }
124
+ }
62
125
  async function publicRuntimeBytes(path, limit, executingCaller = false) {
63
126
  const file = await open(path, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
64
127
  try {
@@ -103,10 +166,67 @@ async function canonicalEntrypoint(options, identity) {
103
166
  }
104
167
  }
105
168
  function ownedIdentity(identity, uid, entrypoint) {
106
- return identity.pid === 1 && identity.uid === uid && Number.isSafeInteger(uid) && uid > 0 && /^\d{1,20}$/.test(identity.startTicks)
169
+ return Number.isSafeInteger(identity.pid) && identity.pid >= 1 && identity.uid === uid && Number.isSafeInteger(uid) && uid > 0 && /^\d{1,20}$/.test(identity.startTicks)
107
170
  && identity.argv.length === 4 && identity.argv[2] === 'relay' && identity.argv[3] === 'container-entrypoint'
108
171
  && identity.argv[1] === entrypoint && isAbsolute(entrypoint);
109
172
  }
173
+ function validMarker(value) {
174
+ const init = Boolean(value && typeof value === 'object' && value.schema === 'dharma.container-entrypoint/v2');
175
+ return exactObject(value, ['schema', 'home', 'pid', 'uid', 'startTicks', ...(init ? ['initStartTicks'] : [])])
176
+ && (init ? value.schema === 'dharma.container-entrypoint/v2' && Number.isSafeInteger(value.pid) && Number(value.pid) > 1
177
+ && typeof value.initStartTicks === 'string' && /^\d{1,20}$/.test(value.initStartTicks)
178
+ : value.schema === 'dharma.container-entrypoint/v1' && value.pid === 1)
179
+ && typeof value.home === 'string' && Number.isSafeInteger(value.uid) && Number(value.uid) > 0
180
+ && typeof value.startTicks === 'string' && /^\d{1,20}$/.test(value.startTicks);
181
+ }
182
+ async function ownedController(options, pid) {
183
+ const init = await (options.containerRuntime?.identity || readContainerPid1Identity)();
184
+ if (init.pid !== 1 || init.uid !== ownUid(options) || !/^\d{1,20}$/.test(init.startTicks))
185
+ throw unavailable();
186
+ if (init.argv[2] === 'relay' && init.argv[3] === 'container-entrypoint') {
187
+ if (pid !== undefined && pid !== 1 || !ownedIdentity(init, ownUid(options), await canonicalEntrypoint(options, init)))
188
+ throw unavailable();
189
+ return { identity: init, initStartTicks: undefined };
190
+ }
191
+ // Bounded migration of the existing official Node-image startup. Neither an
192
+ // arbitrary init/wrapper nor a foreground exec can claim this lifecycle.
193
+ if (init.parentPid !== 0 || init.argv.length !== 5 || init.argv[0] !== '/sbin/docker-init' || init.argv[1] !== '--'
194
+ || init.argv[2] !== 'docker-entrypoint.sh' || init.argv[3] !== 'sleep' || init.argv[4] !== 'infinity'
195
+ || !await (options.containerRuntime?.dockerInitVerified || verifiedDockerInit)())
196
+ throw unavailable();
197
+ const controller = await (options.containerRuntime?.controllerIdentity || readContainerProcessIdentity)(pid ?? options.containerRuntime?.currentControllerPid ?? process.pid);
198
+ if (controller.pid <= 1 || controller.parentPid !== 1
199
+ || !Number.isSafeInteger(init.sessionId) || Number(init.sessionId) <= 0
200
+ || controller.sessionId !== init.sessionId || controller.processGroupId !== controller.pid
201
+ || await (options.containerRuntime?.dockerInitMainChild || dockerInitMainChild)() !== controller.pid
202
+ || !ownedIdentity(controller, ownUid(options), await canonicalEntrypoint(options, controller))
203
+ || !await (options.containerRuntime?.controllerExecutableVerified || verifiedControllerExecutable)(controller))
204
+ throw unavailable();
205
+ const [freshInit, freshController, freshMainChild] = await Promise.all([
206
+ (options.containerRuntime?.identity || readContainerPid1Identity)(),
207
+ (options.containerRuntime?.controllerIdentity || readContainerProcessIdentity)(controller.pid),
208
+ (options.containerRuntime?.dockerInitMainChild || dockerInitMainChild)(),
209
+ ]);
210
+ if (freshInit.pid !== init.pid || freshInit.uid !== init.uid || freshInit.startTicks !== init.startTicks
211
+ || JSON.stringify(freshInit.argv) !== JSON.stringify(init.argv)
212
+ || freshInit.sessionId !== init.sessionId || freshInit.processGroupId !== init.processGroupId
213
+ || freshController.pid !== controller.pid || freshController.uid !== controller.uid
214
+ || freshController.parentPid !== 1 || freshController.startTicks !== controller.startTicks
215
+ || freshMainChild !== controller.pid || freshController.sessionId !== init.sessionId
216
+ || freshController.processGroupId !== controller.pid
217
+ || JSON.stringify(freshController.argv) !== JSON.stringify(controller.argv))
218
+ throw unavailable();
219
+ return { identity: controller, initStartTicks: init.startTicks };
220
+ }
221
+ async function liveMarker(options) {
222
+ const marker = await privateJson(options, 'container-entrypoint.json');
223
+ if (!validMarker(marker) || marker.home !== options.home || marker.uid !== ownUid(options))
224
+ throw unavailable();
225
+ const live = await ownedController(options, marker.pid);
226
+ if (live.identity.startTicks !== marker.startTicks || live.initStartTicks !== marker.initStartTicks)
227
+ throw unavailable();
228
+ return marker;
229
+ }
110
230
  export async function readContainerChildIdentity(pid) {
111
231
  if (!Number.isSafeInteger(pid) || pid <= 1)
112
232
  throw unavailable();
@@ -118,9 +238,15 @@ export async function readContainerChildIdentity(pid) {
118
238
  throw unavailable();
119
239
  return { pid, uid: owner.uid, parentPid: Number(fields[1]), startTicks: fields[19] };
120
240
  }
121
- async function ownedChild(options, pid, startTicks) {
241
+ async function ownedChild(options, pid, startTicks, controller) {
242
+ const parent = controller || await liveMarker(options);
243
+ if (controller) {
244
+ const live = await ownedController(options, controller.pid);
245
+ if (live.identity.startTicks !== controller.startTicks || live.initStartTicks !== controller.initStartTicks)
246
+ throw unavailable();
247
+ }
122
248
  const identity = await (options.containerRuntime?.childIdentity || readContainerChildIdentity)(pid);
123
- if (identity.pid !== pid || identity.uid !== ownUid(options) || identity.parentPid !== 1
249
+ if (identity.pid !== pid || identity.uid !== ownUid(options) || identity.parentPid !== parent.pid
124
250
  || !/^\d{1,20}$/.test(identity.startTicks) || startTicks !== undefined && identity.startTicks !== startTicks)
125
251
  throw unavailable();
126
252
  return identity;
@@ -129,13 +255,7 @@ export async function containerEntrypointAvailable(options) {
129
255
  const value = await privateJson(options, 'container-entrypoint.json');
130
256
  if (value === null)
131
257
  return false;
132
- if (!exactObject(value, ['schema', 'home', 'pid', 'uid', 'startTicks']))
133
- throw unavailable();
134
- const identity = await (options.containerRuntime?.identity || readContainerPid1Identity)().catch(() => { throw unavailable(); });
135
- if (value.schema !== 'dharma.container-entrypoint/v1' || value.home !== options.home
136
- || value.pid !== 1 || value.uid !== ownUid(options) || value.startTicks !== identity.startTicks
137
- || !ownedIdentity(identity, ownUid(options), await canonicalEntrypoint(options, identity)))
138
- throw unavailable();
258
+ await liveMarker(options);
139
259
  return true;
140
260
  }
141
261
  function registrationHash(home, registration) {
@@ -279,11 +399,11 @@ export async function containerConsumerStoreReady() {
279
399
  return false;
280
400
  }
281
401
  }
282
- async function stopOwnedChild(child, options, identity) {
402
+ async function stopOwnedChild(child, options, identity, controller) {
283
403
  if (child.exitCode !== null || child.signalCode !== null || !child.pid)
284
404
  return;
285
405
  // Never signal a recycled PID or a process outside this owned parent/UID.
286
- if (identity && !await ownedChild(options, child.pid, identity.startTicks).catch(() => null))
406
+ if (identity && !await ownedChild(options, child.pid, identity.startTicks, controller).catch(() => null))
287
407
  return;
288
408
  // On an initial /proc failure, the freshly spawned ChildProcess handle is
289
409
  // still ours. Drain it immediately; never leave unverified work running.
@@ -298,13 +418,14 @@ async function stopOwnedChild(child, options, identity) {
298
418
  if (timer)
299
419
  clearTimeout(timer);
300
420
  if (child.exitCode === null && child.signalCode === null
301
- && (!identity || await ownedChild(options, child.pid, identity.startTicks).catch(() => null))) {
421
+ && (!identity || await ownedChild(options, child.pid, identity.startTicks, controller).catch(() => null))) {
302
422
  child.kill('SIGKILL');
303
423
  await exited;
304
424
  }
305
425
  }
306
426
  export async function runOwnedContainerEntrypoint(options) {
307
- const identity = await (options.containerRuntime?.identity || readContainerPid1Identity)();
427
+ const live = await ownedController(options);
428
+ const identity = live.identity;
308
429
  const bin = await canonicalEntrypoint(options, identity);
309
430
  const runtimeVersion = options.containerRuntime?.runtimeVersion
310
431
  ?? JSON.parse(await readFile(new URL('../package.json', import.meta.url), 'utf8')).version;
@@ -314,21 +435,19 @@ export async function runOwnedContainerEntrypoint(options) {
314
435
  throw unavailable();
315
436
  await mkdir(join(options.home, 'relay'), { recursive: true, mode: 0o700 });
316
437
  await privateDirectory(options);
317
- const marker = { schema: 'dharma.container-entrypoint/v1', home: options.home,
318
- pid: 1, uid: identity.uid, startTicks: identity.startTicks };
438
+ const marker = { schema: live.initStartTicks === undefined ? 'dharma.container-entrypoint/v1' : 'dharma.container-entrypoint/v2', home: options.home,
439
+ pid: identity.pid, uid: identity.uid, startTicks: identity.startTicks,
440
+ ...(live.initStartTicks === undefined ? {} : { initStartTicks: live.initStartTicks }) };
319
441
  const previous = await privateJson(options, 'container-entrypoint.json');
320
- if (previous !== null && (!exactObject(previous, ['schema', 'home', 'pid', 'uid', 'startTicks'])
321
- || previous.schema !== marker.schema || previous.home !== marker.home || previous.uid !== marker.uid || previous.pid !== 1
322
- || typeof previous.startTicks !== 'string' || !/^\d{1,20}$/.test(previous.startTicks)))
442
+ if (previous !== null && (!validMarker(previous) || previous.schema !== marker.schema
443
+ || previous.home !== marker.home || previous.uid !== marker.uid))
323
444
  throw unavailable();
324
445
  const lockPath = privatePath(options.home, 'container-entrypoint.lock');
325
446
  const oldLock = await privateJson(options, 'container-entrypoint.lock');
326
447
  if (oldLock !== null) {
327
- if (!exactObject(oldLock, ['schema', 'home', 'pid', 'uid', 'startTicks'])
328
- || oldLock.schema !== marker.schema || oldLock.home !== marker.home || oldLock.uid !== marker.uid || oldLock.pid !== 1
329
- || typeof oldLock.startTicks !== 'string' || !/^\d{1,20}$/.test(oldLock.startTicks))
448
+ if (!validMarker(oldLock) || oldLock.schema !== marker.schema || oldLock.home !== marker.home || oldLock.uid !== marker.uid)
330
449
  throw unavailable();
331
- if (oldLock.startTicks === marker.startTicks)
450
+ if (marker.initStartTicks === undefined ? oldLock.startTicks === marker.startTicks : oldLock.initStartTicks === marker.initStartTicks)
332
451
  throw new Error('container_entrypoint_busy: an owned entrypoint is already active.');
333
452
  await rm(lockPath); // an earlier boot's private lock; actual PID1 identity was verified
334
453
  }
@@ -370,7 +489,7 @@ export async function runOwnedContainerEntrypoint(options) {
370
489
  retryAt = Date.now() + restartDelayMs;
371
490
  }
372
491
  if (child && (!shouldRun || hash !== childHash)) {
373
- await stopOwnedChild(child, options, childIdentity);
492
+ await stopOwnedChild(child, options, childIdentity, marker);
374
493
  child = null;
375
494
  childIdentity = null;
376
495
  childHash = null;
@@ -379,7 +498,7 @@ export async function runOwnedContainerEntrypoint(options) {
379
498
  child = spawnRelay(configuration.registration);
380
499
  childHash = hash;
381
500
  child.on('error', () => { childFailed = true; });
382
- childIdentity = child.pid ? await ownedChild(options, child.pid).catch(() => null) : null;
501
+ childIdentity = child.pid ? await ownedChild(options, child.pid, undefined, marker).catch(() => null) : null;
383
502
  if (child.pid && !childIdentity && child.exitCode === null && child.signalCode === null)
384
503
  throw unavailable();
385
504
  }
@@ -401,14 +520,14 @@ export async function runOwnedContainerEntrypoint(options) {
401
520
  }
402
521
  finally {
403
522
  if (child)
404
- await stopOwnedChild(child, options, childIdentity);
523
+ await stopOwnedChild(child, options, childIdentity, marker);
405
524
  const current = await privateJson(options, 'container-entrypoint.json').catch(() => null);
406
- if (current?.startTicks === marker.startTicks && current.home === marker.home) {
525
+ if (current?.startTicks === marker.startTicks && current.home === marker.home && current.pid === marker.pid && current.initStartTicks === marker.initStartTicks) {
407
526
  await rm(privatePath(options.home, 'container-entrypoint.json'), { force: true });
408
527
  await rm(privatePath(options.home, 'container-heartbeat.json'), { force: true });
409
528
  }
410
529
  const lock = await privateJson(options, 'container-entrypoint.lock').catch(() => null);
411
- if (lock?.startTicks === marker.startTicks && lock.home === marker.home)
530
+ if (lock?.startTicks === marker.startTicks && lock.home === marker.home && lock.pid === marker.pid && lock.initStartTicks === marker.initStartTicks)
412
531
  await rm(lockPath, { force: true });
413
532
  }
414
533
  }