@molecule/api-code-sandbox-docker 1.1.6 → 1.2.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 +26 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/inspect.d.ts +7 -0
- package/dist/inspect.d.ts.map +1 -1
- package/dist/inspect.js +10 -1
- package/dist/provider.d.ts.map +1 -1
- package/dist/provider.js +70 -4
- package/dist/types.d.ts +22 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@ AUTO-GENERATED — DO NOT EDIT THIS FILE.
|
|
|
3
3
|
Generated by `mlcl sync-docs` from the package's src/index.ts JSDoc + mlcl/registry.json.
|
|
4
4
|
Edits here are overwritten on the next commit (molecule's pre-commit hook regenerates).
|
|
5
5
|
To change this document, edit the module-level JSDoc in src/index.ts.
|
|
6
|
-
Generated: 2026-10-
|
|
6
|
+
Generated: 2026-10-27T12:31:00.294Z
|
|
7
7
|
-->
|
|
8
8
|
|
|
9
9
|
# @molecule/api-code-sandbox-docker
|
|
@@ -81,6 +81,26 @@ interface DockerConfig {
|
|
|
81
81
|
labelPrefix?: string
|
|
82
82
|
/** Preview URL template. Use {port} placeholder. */
|
|
83
83
|
previewUrlTemplate?: string
|
|
84
|
+
/**
|
|
85
|
+
* The interface published sandbox ports bind to on the daemon's host.
|
|
86
|
+
* Defaults to `127.0.0.1` (loopback — nothing outside the host reaches a
|
|
87
|
+
* sandbox port), or the `DOCKER_PUBLISH_HOST_IP` env var.
|
|
88
|
+
*
|
|
89
|
+
* The one knob that makes a REMOTE daemon (`host`/`DOCKER_HOST`) usable for
|
|
90
|
+
* hosting: the control plane's front proxies to the address `describe()`
|
|
91
|
+
* reports, and that address is this bind. A specific routable IP (the box's
|
|
92
|
+
* address on a network the front can reach) is the scoped choice; the
|
|
93
|
+
* wildcards `0.0.0.0`/`::` bind EVERY interface and expose every published
|
|
94
|
+
* port of every sandbox on that daemon to every network it touches, so in
|
|
95
|
+
* `NODE_ENV=production` they are refused unless `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1`
|
|
96
|
+
* is set (honored with a loud warning). A wildcard bind on a remote daemon is
|
|
97
|
+
* reported as the daemon's own host.
|
|
98
|
+
*
|
|
99
|
+
* Exposure is the operator's contract either way: whatever address this
|
|
100
|
+
* names must be reachable by the front and FIREWALLED against everything
|
|
101
|
+
* else — binding a port is a deliberate exposure decision, not a default.
|
|
102
|
+
*/
|
|
103
|
+
publishHostIp?: string
|
|
84
104
|
/**
|
|
85
105
|
* Docker repository holding sandbox TEMPLATES — one tag per template id.
|
|
86
106
|
* Defaults to `molecule-sandbox-template` (or `SANDBOX_TEMPLATE_REPOSITORY`).
|
|
@@ -132,6 +152,8 @@ interface ProcessEnv {
|
|
|
132
152
|
DOCKER_HOST?: string
|
|
133
153
|
/** Docker network sandbox containers attach to (default `molecule-sandbox`, ICC-off). Overridden by `config.network`. [C1-1] */
|
|
134
154
|
SANDBOX_DOCKER_NETWORK?: string
|
|
155
|
+
/** Interface published ports bind to on the daemon's host (default `127.0.0.1`, loopback-only). Overridden by `config.publishHostIp`. */
|
|
156
|
+
DOCKER_PUBLISH_HOST_IP?: string
|
|
135
157
|
/** Docker repository holding sandbox templates (default `molecule-sandbox-template`). Overridden by `config.templateRepository`. */
|
|
136
158
|
SANDBOX_TEMPLATE_REPOSITORY?: string
|
|
137
159
|
/** Registry host for sharing templates across hosts. Overridden by `config.templateRegistry`. */
|
|
@@ -276,6 +298,9 @@ proxy. When the plain-TCP endpoint comes from the ambient `DOCKER_HOST` env
|
|
|
276
298
|
(not explicit config) and `NODE_ENV=production`, the provider REFUSES to
|
|
277
299
|
connect — exec payloads would cross the network unencrypted — unless
|
|
278
300
|
`MOL_DOCKER_ALLOW_PLAIN_TCP=1` is set, which is honored with a loud warning.
|
|
301
|
+
Published ports bind loopback by default; a remote daemon used for hosting
|
|
302
|
+
names a routable interface with `config.publishHostIp` / `DOCKER_PUBLISH_HOST_IP`
|
|
303
|
+
(wildcard binds need `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1` in production).
|
|
279
304
|
Caller-supplied container ids are shape-validated (`[A-Za-z0-9][A-Za-z0-9_.:-]*`,
|
|
280
305
|
so Docker hex ids, names, and fly-style `app:machine` ids pass) before they are
|
|
281
306
|
interpolated into Docker API paths. The provider never pulls images, so `create()` fails with a no-such-image
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* (not explicit config) and `NODE_ENV=production`, the provider REFUSES to
|
|
22
22
|
* connect — exec payloads would cross the network unencrypted — unless
|
|
23
23
|
* `MOL_DOCKER_ALLOW_PLAIN_TCP=1` is set, which is honored with a loud warning.
|
|
24
|
+
* Published ports bind loopback by default; a remote daemon used for hosting
|
|
25
|
+
* names a routable interface with `config.publishHostIp` / `DOCKER_PUBLISH_HOST_IP`
|
|
26
|
+
* (wildcard binds need `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1` in production).
|
|
24
27
|
* Caller-supplied container ids are shape-validated (`[A-Za-z0-9][A-Za-z0-9_.:-]*`,
|
|
25
28
|
* so Docker hex ids, names, and fly-style `app:machine` ids pass) before they are
|
|
26
29
|
* interpolated into Docker API paths. The provider never pulls images, so `create()` fails with a no-such-image
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8FG;AAEH,cAAc,oBAAoB,CAAA;AAClC,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAE1B,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAA;AAMjE;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,eAUrB,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -21,6 +21,9 @@
|
|
|
21
21
|
* (not explicit config) and `NODE_ENV=production`, the provider REFUSES to
|
|
22
22
|
* connect — exec payloads would cross the network unencrypted — unless
|
|
23
23
|
* `MOL_DOCKER_ALLOW_PLAIN_TCP=1` is set, which is honored with a loud warning.
|
|
24
|
+
* Published ports bind loopback by default; a remote daemon used for hosting
|
|
25
|
+
* names a routable interface with `config.publishHostIp` / `DOCKER_PUBLISH_HOST_IP`
|
|
26
|
+
* (wildcard binds need `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1` in production).
|
|
24
27
|
* Caller-supplied container ids are shape-validated (`[A-Za-z0-9][A-Za-z0-9_.:-]*`,
|
|
25
28
|
* so Docker hex ids, names, and fly-style `app:machine` ids pass) before they are
|
|
26
29
|
* interpolated into Docker API paths. The provider never pulls images, so `create()` fails with a no-such-image
|
package/dist/inspect.d.ts
CHANGED
|
@@ -30,6 +30,13 @@ export interface InspectContext {
|
|
|
30
30
|
labelPrefix: string;
|
|
31
31
|
warn?: (message: string, meta?: Record<string, unknown>) => void;
|
|
32
32
|
debug?: (message: string, meta?: Record<string, unknown>) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Where a port bound to EVERY interface is actually reachable, when that is
|
|
35
|
+
* somewhere other than this process's loopback — the host of a remote
|
|
36
|
+
* (`tcp://`) daemon. Unset for a local daemon, whose wildcard binds answer
|
|
37
|
+
* on loopback and nowhere the caller can name.
|
|
38
|
+
*/
|
|
39
|
+
advertisedHost?: string;
|
|
33
40
|
}
|
|
34
41
|
/** Shape of the fields this module reads from `GET /containers/{id}/json`. */
|
|
35
42
|
interface ContainerInspect {
|
package/dist/inspect.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAElB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACX,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjD,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAA;IACtB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;
|
|
1
|
+
{"version":3,"file":"inspect.d.ts","sourceRoot":"","sources":["../src/inspect.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAElB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,UAAU,EACX,MAAM,4BAA4B,CAAA;AAEnC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAEjD,+DAA+D;AAC/D,MAAM,WAAW,cAAc;IAC7B,OAAO,EAAE,aAAa,CAAA;IACtB,uEAAuE;IACvE,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IAChE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAA;IACjE;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AASD,8EAA8E;AAC9E,UAAU,gBAAgB;IACxB,EAAE,EAAE,MAAM,CAAA;IACV,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IACjD,MAAM,CAAC,EAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC5D,MAAM,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;IAChD,eAAe,CAAC,EAAE;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC;YAAE,MAAM,CAAC,EAAE,MAAM,CAAC;YAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAA;KACpF,CAAA;CACF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,EAAE,gBAAgB,GAAG,iBAAiB,CAoC3F;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,cAAc,EACnB,EAAE,EAAE,MAAM,GACT,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAYnC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,cAAc,EACnB,KAAK,CAAC,EAAE,YAAY,GACnB,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAwB9B;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,iBAAiB,CACrC,GAAG,EAAE,cAAc,EACnB,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,UAAU,EAAE,CAAC,CAkCvB;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,wBAAwB,CAC5C,GAAG,EAAE,cAAc,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,EAAE,OAAO,CAAC,gBAAgB,CAAC,GACnC,OAAO,CAAC,IAAI,CAAC,CAqBf"}
|
package/dist/inspect.js
CHANGED
|
@@ -53,7 +53,16 @@ export function toDescriptor(ctx, info) {
|
|
|
53
53
|
const port = Number.parseInt(binding.HostPort ?? '', 10);
|
|
54
54
|
if (!Number.isInteger(port) || port <= 0)
|
|
55
55
|
continue;
|
|
56
|
-
|
|
56
|
+
// A bind on every interface has no single address of its own: it is
|
|
57
|
+
// reachable wherever the daemon's host is reachable, which the context
|
|
58
|
+
// names for a remote daemon and which is loopback for a local one.
|
|
59
|
+
const hostIp = binding.HostIp;
|
|
60
|
+
const bindAll = !hostIp || hostIp === '0.0.0.0' || hostIp === '::';
|
|
61
|
+
ports.push({
|
|
62
|
+
sandboxPort,
|
|
63
|
+
host: bindAll ? (ctx.advertisedHost ?? '127.0.0.1') : hostIp,
|
|
64
|
+
port,
|
|
65
|
+
});
|
|
57
66
|
}
|
|
58
67
|
}
|
|
59
68
|
return {
|
package/dist/provider.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH,OAAO,KAAK,EAeV,eAAe,EAOhB,MAAM,4BAA4B,CAAA;AAoBnC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AA4K9C;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAQ9D;AAyED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,kBAAkB,CAAC,CAAC,EACxC,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACpB,IAAI,EAAE;IACJ,KAAK,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAA;IACjC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAA;IACrC,GAAG,CAAC,EAAE;QAAE,IAAI,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;KAAE,CAAA;CAC1D,GACA,OAAO,CAAC,CAAC,CAAC,CA2BZ;AA0hDD;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,eAAe,CAErE"}
|
package/dist/provider.js
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { readFile, statfs } from 'fs/promises';
|
|
10
10
|
import http from 'http';
|
|
11
|
+
import net from 'net';
|
|
11
12
|
import { freemem } from 'os';
|
|
12
13
|
import { Readable } from 'stream';
|
|
13
14
|
import { getLogger } from '@molecule/api-bond';
|
|
@@ -318,6 +319,20 @@ export async function withTransientRetry(op, opts) {
|
|
|
318
319
|
*/
|
|
319
320
|
class DockerSandboxProvider {
|
|
320
321
|
name = 'docker';
|
|
322
|
+
/**
|
|
323
|
+
* Containers this provider runs live until they are destroyed (optionally
|
|
324
|
+
* surviving crashes via `SandboxConfig.restartPolicy`) and every published
|
|
325
|
+
* port is host-mapped at an address `describe()` reports — the two things a
|
|
326
|
+
* long-lived hosting role needs, which is why this provider is selectable
|
|
327
|
+
* for it. A LOCAL daemon's default loopback bind is routable only from the
|
|
328
|
+
* control plane's own host, which is exactly what a co-located front needs;
|
|
329
|
+
* a remote daemon needs `publishHostIp` to name an address the front can
|
|
330
|
+
* reach.
|
|
331
|
+
*/
|
|
332
|
+
hostingCapabilities = {
|
|
333
|
+
longLived: true,
|
|
334
|
+
routable: true,
|
|
335
|
+
};
|
|
321
336
|
/** Resolved daemon endpoint (unix socket or TCP host:port); see {@link resolveDockerEndpoint}. */
|
|
322
337
|
endpoint;
|
|
323
338
|
baseImage;
|
|
@@ -353,6 +368,10 @@ class DockerSandboxProvider {
|
|
|
353
368
|
endpointSafetyChecked = false;
|
|
354
369
|
/** Whether the plain-TCP override warning has fired (once per provider). */
|
|
355
370
|
plainTcpOverrideWarned = false;
|
|
371
|
+
/** Explicit publish bind from `config.publishHostIp`, if any; env is consulted at create time. */
|
|
372
|
+
configPublishHostIp;
|
|
373
|
+
/** Whether the wildcard-bind warning has fired (once per provider). */
|
|
374
|
+
publicBindOverrideWarned = false;
|
|
356
375
|
constructor(config = {}) {
|
|
357
376
|
const resolved = resolveDockerEndpoint(config);
|
|
358
377
|
this.endpoint = resolved.endpoint;
|
|
@@ -363,6 +382,7 @@ class DockerSandboxProvider {
|
|
|
363
382
|
this.defaultCpu = config.defaultCpu ?? 1;
|
|
364
383
|
this.defaultMemoryMB = config.defaultMemoryMB ?? 1024;
|
|
365
384
|
this.configNetwork = config.network;
|
|
385
|
+
this.configPublishHostIp = config.publishHostIp;
|
|
366
386
|
this.configTemplateRepository = config.templateRepository;
|
|
367
387
|
this.configTemplateRegistry = config.templateRegistry;
|
|
368
388
|
this.configTemplateRegistryAuth = config.templateRegistryAuth;
|
|
@@ -404,8 +424,50 @@ class DockerSandboxProvider {
|
|
|
404
424
|
labelPrefix: this.labelPrefix,
|
|
405
425
|
warn: (message, meta) => logger.warn(message, meta),
|
|
406
426
|
debug: (message, meta) => logger.debug(message, meta),
|
|
427
|
+
// A wildcard port bind on a REMOTE daemon is reachable at the daemon's
|
|
428
|
+
// own host, not at the control plane's loopback — that is the address a
|
|
429
|
+
// front needs. A local daemon leaves this unset, so a wildcard there
|
|
430
|
+
// still reports loopback.
|
|
431
|
+
advertisedHost: 'host' in this.endpoint ? this.endpoint.host : undefined,
|
|
407
432
|
};
|
|
408
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* Resolve the interface published ports bind to on the daemon's host.
|
|
436
|
+
* Precedence: `config.publishHostIp` (captured at construction) →
|
|
437
|
+
* `DOCKER_PUBLISH_HOST_IP` env (read here, at create time) → loopback.
|
|
438
|
+
*
|
|
439
|
+
* @returns the bind address for `HostConfig.PortBindings`.
|
|
440
|
+
* @throws {Error} when the value is not an IP address, or is a wildcard
|
|
441
|
+
* (`0.0.0.0`/`::`) while `NODE_ENV=production` without
|
|
442
|
+
* `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1` — a wildcard publishes every port of
|
|
443
|
+
* every sandbox on that daemon to every network it touches, and that is an
|
|
444
|
+
* exposure someone must accept by name, never inherit by default.
|
|
445
|
+
*/
|
|
446
|
+
resolvePublishHostIp() {
|
|
447
|
+
const raw = this.configPublishHostIp ?? process.env.DOCKER_PUBLISH_HOST_IP ?? '127.0.0.1';
|
|
448
|
+
const value = raw.trim();
|
|
449
|
+
if (net.isIP(value) === 0) {
|
|
450
|
+
throw new Error(`DOCKER_PUBLISH_HOST_IP/publishHostIp must be an IP address (got ${JSON.stringify(raw)}); ` +
|
|
451
|
+
'a hostname would need Docker to resolve it at bind time, when it may answer differently.');
|
|
452
|
+
}
|
|
453
|
+
const wildcard = value === '0.0.0.0' || value === '::';
|
|
454
|
+
if (wildcard && process.env.NODE_ENV === 'production') {
|
|
455
|
+
const override = process.env.MOL_DOCKER_ALLOW_PUBLIC_BINDS;
|
|
456
|
+
if (override !== '1' && override !== 'true') {
|
|
457
|
+
throw new Error('Publishing sandbox ports on every interface (0.0.0.0/::) while NODE_ENV=production — ' +
|
|
458
|
+
'refusing. Bind a specific IP the front can reach, or set ' +
|
|
459
|
+
'MOL_DOCKER_ALLOW_PUBLIC_BINDS=1 to explicitly accept exposing every published port ' +
|
|
460
|
+
'of every sandbox on this daemon.');
|
|
461
|
+
}
|
|
462
|
+
if (!this.publicBindOverrideWarned) {
|
|
463
|
+
this.publicBindOverrideWarned = true;
|
|
464
|
+
logger.warn('Sandbox ports bind EVERY interface (MOL_DOCKER_ALLOW_PUBLIC_BINDS accepted) — ' +
|
|
465
|
+
'every published port of every sandbox on this daemon is reachable from every network ' +
|
|
466
|
+
'it touches. Firewall the host so only the front can dial them.');
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return value;
|
|
470
|
+
}
|
|
409
471
|
/**
|
|
410
472
|
* Resolve the Docker network to attach containers to. Precedence:
|
|
411
473
|
* `config.network` (captured at construction) → `SANDBOX_DOCKER_NETWORK` env
|
|
@@ -489,6 +551,7 @@ class DockerSandboxProvider {
|
|
|
489
551
|
image = template.ref;
|
|
490
552
|
}
|
|
491
553
|
const publishPorts = resolvePublishPorts(config.publishPorts);
|
|
554
|
+
const publishHostIp = this.resolvePublishHostIp();
|
|
492
555
|
const env = Object.entries(config.env ?? {}).map(([k, v]) => `${k}=${v}`);
|
|
493
556
|
const labels = {
|
|
494
557
|
[`${this.labelPrefix}.projectId`]: config.projectId,
|
|
@@ -523,10 +586,13 @@ class DockerSandboxProvider {
|
|
|
523
586
|
// bridge. Egress filtering must be enforced host-side, not in a tenant-rooted
|
|
524
587
|
// container.
|
|
525
588
|
CapAdd: ['CHOWN', 'SETGID', 'SETUID'],
|
|
526
|
-
// Bind ports to
|
|
527
|
-
// the
|
|
528
|
-
//
|
|
529
|
-
|
|
589
|
+
// Bind published ports to a chosen interface — loopback by default, so
|
|
590
|
+
// nothing outside the host reaches a sandbox port. Which ports is the
|
|
591
|
+
// caller's business (`config.publishPorts`); the historical API + Vite
|
|
592
|
+
// pair is the default so existing callers are unaffected. The interface
|
|
593
|
+
// is `resolvePublishHostIp()`'s: loopback until an operator names an
|
|
594
|
+
// address their front can reach (a remote-hosting daemon).
|
|
595
|
+
PortBindings: Object.fromEntries(publishPorts.map((port) => [`${port}/tcp`, [{ HostIp: publishHostIp, HostPort: '' }]])),
|
|
530
596
|
Tmpfs: { '/tmp': 'rw,noexec,nosuid,size=256m' },
|
|
531
597
|
ReadonlyPaths: [
|
|
532
598
|
'/proc/asound',
|
package/dist/types.d.ts
CHANGED
|
@@ -42,6 +42,26 @@ export interface DockerConfig {
|
|
|
42
42
|
labelPrefix?: string;
|
|
43
43
|
/** Preview URL template. Use {port} placeholder. */
|
|
44
44
|
previewUrlTemplate?: string;
|
|
45
|
+
/**
|
|
46
|
+
* The interface published sandbox ports bind to on the daemon's host.
|
|
47
|
+
* Defaults to `127.0.0.1` (loopback — nothing outside the host reaches a
|
|
48
|
+
* sandbox port), or the `DOCKER_PUBLISH_HOST_IP` env var.
|
|
49
|
+
*
|
|
50
|
+
* The one knob that makes a REMOTE daemon (`host`/`DOCKER_HOST`) usable for
|
|
51
|
+
* hosting: the control plane's front proxies to the address `describe()`
|
|
52
|
+
* reports, and that address is this bind. A specific routable IP (the box's
|
|
53
|
+
* address on a network the front can reach) is the scoped choice; the
|
|
54
|
+
* wildcards `0.0.0.0`/`::` bind EVERY interface and expose every published
|
|
55
|
+
* port of every sandbox on that daemon to every network it touches, so in
|
|
56
|
+
* `NODE_ENV=production` they are refused unless `MOL_DOCKER_ALLOW_PUBLIC_BINDS=1`
|
|
57
|
+
* is set (honored with a loud warning). A wildcard bind on a remote daemon is
|
|
58
|
+
* reported as the daemon's own host.
|
|
59
|
+
*
|
|
60
|
+
* Exposure is the operator's contract either way: whatever address this
|
|
61
|
+
* names must be reachable by the front and FIREWALLED against everything
|
|
62
|
+
* else — binding a port is a deliberate exposure decision, not a default.
|
|
63
|
+
*/
|
|
64
|
+
publishHostIp?: string;
|
|
45
65
|
/**
|
|
46
66
|
* Docker repository holding sandbox TEMPLATES — one tag per template id.
|
|
47
67
|
* Defaults to `molecule-sandbox-template` (or `SANDBOX_TEMPLATE_REPOSITORY`).
|
|
@@ -88,6 +108,8 @@ export interface ProcessEnv {
|
|
|
88
108
|
DOCKER_HOST?: string;
|
|
89
109
|
/** Docker network sandbox containers attach to (default `molecule-sandbox`, ICC-off). Overridden by `config.network`. [C1-1] */
|
|
90
110
|
SANDBOX_DOCKER_NETWORK?: string;
|
|
111
|
+
/** Interface published ports bind to on the daemon's host (default `127.0.0.1`, loopback-only). Overridden by `config.publishHostIp`. */
|
|
112
|
+
DOCKER_PUBLISH_HOST_IP?: string;
|
|
91
113
|
/** Docker repository holding sandbox templates (default `molecule-sandbox-template`). Overridden by `config.templateRepository`. */
|
|
92
114
|
SANDBOX_TEMPLATE_REPOSITORY?: string;
|
|
93
115
|
/** Registry host for sharing templates across hosts. Overridden by `config.templateRegistry`. */
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sHAAsH;IACtH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0IAA0I;IAC1I,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kHAAkH;IAClH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,wJAAwJ;IACxJ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gIAAgI;IAChI,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,oIAAoI;IACpI,2BAA2B,CAAC,EAAE,MAAM,CAAA;IACpC,iGAAiG;IACjG,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,mHAAmH;IACnH,8BAA8B,CAAC,EAAE,MAAM,CAAA;CACxC"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,sHAAsH;IACtH,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,0IAA0I;IAC1I,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,sCAAsC;IACtC,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,sCAAsC;IACtC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,iDAAiD;IACjD,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;;;;;;;;;;;;;;OAkBG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B;;;;;OAKG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAA;IACzB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAClD;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,kHAAkH;IAClH,kBAAkB,CAAC,EAAE,MAAM,CAAA;IAC3B,wJAAwJ;IACxJ,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,gIAAgI;IAChI,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,yIAAyI;IACzI,sBAAsB,CAAC,EAAE,MAAM,CAAA;IAC/B,oIAAoI;IACpI,2BAA2B,CAAC,EAAE,MAAM,CAAA;IACpC,iGAAiG;IACjG,yBAAyB,CAAC,EAAE,MAAM,CAAA;IAClC,mHAAmH;IACnH,8BAA8B,CAAC,EAAE,MAAM,CAAA;CACxC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@molecule/api-code-sandbox-docker",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Docker-based code sandbox container provider",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"license": "Apache-2.0",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@molecule/api-bond": "1.0.4",
|
|
33
|
-
"@molecule/api-code-sandbox": "1.
|
|
33
|
+
"@molecule/api-code-sandbox": "1.5.0",
|
|
34
34
|
"@types/node": "26.6.5",
|
|
35
35
|
"typescript": "6.0.3",
|
|
36
36
|
"vitest": "4.1.12"
|