lastlight 0.6.2 → 0.7.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/dist/admin/routes.d.ts +1 -1
- package/dist/admin/routes.js +1 -1
- package/dist/admin/routes.js.map +1 -1
- package/dist/cli/cli-config.js.map +1 -0
- package/dist/cli/cli-format.js.map +1 -0
- package/dist/{cli-server.d.ts → cli/cli-server.d.ts} +1 -1
- package/dist/{cli-server.js → cli/cli-server.js} +2 -2
- package/dist/cli/cli-server.js.map +1 -0
- package/dist/cli/cli-timeline.js.map +1 -0
- package/dist/{cli.js → cli/cli.js} +2 -0
- package/dist/cli/cli.js.map +1 -0
- package/dist/{fork-cli.d.ts → cli/fork-cli.d.ts} +14 -5
- package/dist/{fork-cli.js → cli/fork-cli.js} +86 -16
- package/dist/cli/fork-cli.js.map +1 -0
- package/dist/{setup.js → cli/setup.js} +1 -1
- package/dist/cli/setup.js.map +1 -0
- package/dist/{skills-install.js → cli/skills-install.js} +1 -1
- package/dist/cli/skills-install.js.map +1 -0
- package/dist/config/config-resolve.js.map +1 -0
- package/dist/{config.js → config/config.js} +2 -2
- package/dist/config/config.js.map +1 -0
- package/dist/config/overlay-assets.js.map +1 -0
- package/dist/config/overlay-bootstrap.js.map +1 -0
- package/dist/engine/agent-executor.d.ts +1 -1
- package/dist/engine/agent-executor.js +2 -2
- package/dist/engine/agent-executor.js.map +1 -1
- package/dist/engine/{chat-runner.d.ts → chat/chat-runner.d.ts} +2 -2
- package/dist/engine/{chat-runner.js → chat/chat-runner.js} +1 -1
- package/dist/engine/chat/chat-runner.js.map +1 -0
- package/dist/engine/{chat-skills.js → chat/chat-skills.js} +1 -1
- package/dist/engine/chat/chat-skills.js.map +1 -0
- package/dist/engine/{chat.d.ts → chat/chat.d.ts} +2 -2
- package/dist/engine/{chat.js → chat/chat.js} +7 -7
- package/dist/engine/chat/chat.js.map +1 -0
- package/dist/engine/{message-batcher.d.ts → chat/message-batcher.d.ts} +1 -1
- package/dist/engine/chat/message-batcher.js.map +1 -0
- package/dist/engine/dispatcher.d.ts +2 -2
- package/dist/engine/executors/backends.d.ts +2 -2
- package/dist/engine/executors/backends.js +1 -1
- package/dist/engine/executors/backends.js.map +1 -1
- package/dist/engine/executors/shared.d.ts +1 -1
- package/dist/engine/github/git-auth.js.map +1 -0
- package/dist/engine/github/github-app-client.js.map +1 -0
- package/dist/engine/github/github-tools.js.map +1 -0
- package/dist/engine/github/github.js.map +1 -0
- package/dist/engine/{profiles.d.ts → github/profiles.d.ts} +1 -1
- package/dist/engine/{profiles.js → github/profiles.js} +1 -1
- package/dist/engine/github/profiles.js.map +1 -0
- package/dist/engine/router.js +3 -3
- package/dist/engine/router.js.map +1 -1
- package/dist/engine/{classifier.d.ts → screen/classifier.d.ts} +1 -1
- package/dist/engine/{classifier.js → screen/classifier.js} +1 -1
- package/dist/engine/screen/classifier.js.map +1 -0
- package/dist/engine/{screen.d.ts → screen/screen.d.ts} +1 -1
- package/dist/engine/{screen.js → screen/screen.js} +1 -1
- package/dist/engine/screen/screen.js.map +1 -0
- package/dist/evals-api.d.ts +3 -3
- package/dist/evals-api.js +1 -1
- package/dist/evals-api.js.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/managed-repos.js +1 -1
- package/dist/managed-repos.js.map +1 -1
- package/dist/notify/transports/github.d.ts +1 -1
- package/dist/telemetry/index.d.ts +1 -1
- package/dist/workflows/loader.d.ts +1 -1
- package/dist/workflows/phase-executor.d.ts +1 -1
- package/dist/workflows/resume.d.ts +3 -3
- package/dist/workflows/runner.d.ts +2 -2
- package/dist/workflows/runner.js +1 -1
- package/dist/workflows/runner.js.map +1 -1
- package/dist/workflows/schema.d.ts +2 -2
- package/dist/workflows/simple.d.ts +2 -2
- package/dist/workflows/simple.js +1 -1
- package/dist/workflows/simple.js.map +1 -1
- package/dist/workflows/triggers.js +1 -1
- package/dist/workflows/triggers.js.map +1 -1
- package/package.json +4 -3
- package/plugins/lastlight/.claude-plugin/plugin.json +1 -1
- package/plugins/lastlight/skills/lastlight-overlay/SKILL.md +7 -1
- package/plugins/lastlight/skills/lastlight-overlay/references/forking.md +23 -4
- package/dist/admin/auth.test.d.ts +0 -1
- package/dist/admin/auth.test.js +0 -60
- package/dist/admin/auth.test.js.map +0 -1
- package/dist/admin/log-search.test.d.ts +0 -1
- package/dist/admin/log-search.test.js +0 -78
- package/dist/admin/log-search.test.js.map +0 -1
- package/dist/admin/routes.test.d.ts +0 -1
- package/dist/admin/routes.test.js +0 -877
- package/dist/admin/routes.test.js.map +0 -1
- package/dist/admin/server-logs.test.d.ts +0 -1
- package/dist/admin/server-logs.test.js +0 -58
- package/dist/admin/server-logs.test.js.map +0 -1
- package/dist/admin/sessions.test.d.ts +0 -1
- package/dist/admin/sessions.test.js +0 -78
- package/dist/admin/sessions.test.js.map +0 -1
- package/dist/admin/version.test.d.ts +0 -1
- package/dist/admin/version.test.js +0 -71
- package/dist/admin/version.test.js.map +0 -1
- package/dist/cli-config.js.map +0 -1
- package/dist/cli-config.test.d.ts +0 -1
- package/dist/cli-config.test.js +0 -92
- package/dist/cli-config.test.js.map +0 -1
- package/dist/cli-format.js.map +0 -1
- package/dist/cli-server.js.map +0 -1
- package/dist/cli-server.test.d.ts +0 -1
- package/dist/cli-server.test.js +0 -41
- package/dist/cli-server.test.js.map +0 -1
- package/dist/cli-timeline.js.map +0 -1
- package/dist/cli-timeline.test.d.ts +0 -1
- package/dist/cli-timeline.test.js +0 -104
- package/dist/cli-timeline.test.js.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/config-overlay.test.d.ts +0 -1
- package/dist/config-overlay.test.js +0 -112
- package/dist/config-overlay.test.js.map +0 -1
- package/dist/config-resolve.js.map +0 -1
- package/dist/config-resolve.test.d.ts +0 -1
- package/dist/config-resolve.test.js +0 -68
- package/dist/config-resolve.test.js.map +0 -1
- package/dist/config.js.map +0 -1
- package/dist/config.test.d.ts +0 -1
- package/dist/config.test.js +0 -288
- package/dist/config.test.js.map +0 -1
- package/dist/connectors/github-webhook.test.d.ts +0 -1
- package/dist/connectors/github-webhook.test.js +0 -156
- package/dist/connectors/github-webhook.test.js.map +0 -1
- package/dist/connectors/messaging/session-manager.test.d.ts +0 -1
- package/dist/connectors/messaging/session-manager.test.js +0 -144
- package/dist/connectors/messaging/session-manager.test.js.map +0 -1
- package/dist/connectors/slack/connector.test.d.ts +0 -1
- package/dist/connectors/slack/connector.test.js +0 -135
- package/dist/connectors/slack/connector.test.js.map +0 -1
- package/dist/connectors/slack/mrkdwn.test.d.ts +0 -1
- package/dist/connectors/slack/mrkdwn.test.js +0 -114
- package/dist/connectors/slack/mrkdwn.test.js.map +0 -1
- package/dist/cron/fanout.test.d.ts +0 -1
- package/dist/cron/fanout.test.js +0 -73
- package/dist/cron/fanout.test.js.map +0 -1
- package/dist/engine/agent-executor.seam.test.d.ts +0 -1
- package/dist/engine/agent-executor.seam.test.js +0 -57
- package/dist/engine/agent-executor.seam.test.js.map +0 -1
- package/dist/engine/agent-executor.test.d.ts +0 -1
- package/dist/engine/agent-executor.test.js +0 -395
- package/dist/engine/agent-executor.test.js.map +0 -1
- package/dist/engine/chat-runner.js.map +0 -1
- package/dist/engine/chat-runner.test.d.ts +0 -1
- package/dist/engine/chat-runner.test.js +0 -104
- package/dist/engine/chat-runner.test.js.map +0 -1
- package/dist/engine/chat-skills.js.map +0 -1
- package/dist/engine/chat-skills.test.d.ts +0 -1
- package/dist/engine/chat-skills.test.js +0 -20
- package/dist/engine/chat-skills.test.js.map +0 -1
- package/dist/engine/chat.js.map +0 -1
- package/dist/engine/chat.test.d.ts +0 -1
- package/dist/engine/chat.test.js +0 -42
- package/dist/engine/chat.test.js.map +0 -1
- package/dist/engine/classifier.js.map +0 -1
- package/dist/engine/classifier.test.d.ts +0 -1
- package/dist/engine/classifier.test.js +0 -115
- package/dist/engine/classifier.test.js.map +0 -1
- package/dist/engine/dispatcher.test.d.ts +0 -1
- package/dist/engine/dispatcher.test.js +0 -511
- package/dist/engine/dispatcher.test.js.map +0 -1
- package/dist/engine/event-shim.test.d.ts +0 -1
- package/dist/engine/event-shim.test.js +0 -194
- package/dist/engine/event-shim.test.js.map +0 -1
- package/dist/engine/git-auth.js.map +0 -1
- package/dist/engine/git-auth.test.d.ts +0 -1
- package/dist/engine/git-auth.test.js +0 -117
- package/dist/engine/git-auth.test.js.map +0 -1
- package/dist/engine/github-app-client.js.map +0 -1
- package/dist/engine/github-app-client.test.d.ts +0 -1
- package/dist/engine/github-app-client.test.js +0 -44
- package/dist/engine/github-app-client.test.js.map +0 -1
- package/dist/engine/github-tools.js.map +0 -1
- package/dist/engine/github.js.map +0 -1
- package/dist/engine/llm.test.d.ts +0 -1
- package/dist/engine/llm.test.js +0 -189
- package/dist/engine/llm.test.js.map +0 -1
- package/dist/engine/message-batcher.js.map +0 -1
- package/dist/engine/message-batcher.test.d.ts +0 -1
- package/dist/engine/message-batcher.test.js +0 -109
- package/dist/engine/message-batcher.test.js.map +0 -1
- package/dist/engine/profiles.js.map +0 -1
- package/dist/engine/router.test.d.ts +0 -1
- package/dist/engine/router.test.js +0 -695
- package/dist/engine/router.test.js.map +0 -1
- package/dist/engine/screen.js.map +0 -1
- package/dist/engine/screen.test.d.ts +0 -1
- package/dist/engine/screen.test.js +0 -82
- package/dist/engine/screen.test.js.map +0 -1
- package/dist/fork-cli.js.map +0 -1
- package/dist/fork-cli.test.d.ts +0 -1
- package/dist/fork-cli.test.js +0 -109
- package/dist/fork-cli.test.js.map +0 -1
- package/dist/managed-repos.test.d.ts +0 -1
- package/dist/managed-repos.test.js +0 -39
- package/dist/managed-repos.test.js.map +0 -1
- package/dist/notify/model.test.d.ts +0 -1
- package/dist/notify/model.test.js +0 -109
- package/dist/notify/model.test.js.map +0 -1
- package/dist/notify/notifier.test.d.ts +0 -1
- package/dist/notify/notifier.test.js +0 -100
- package/dist/notify/notifier.test.js.map +0 -1
- package/dist/notify/render.test.d.ts +0 -1
- package/dist/notify/render.test.js +0 -65
- package/dist/notify/render.test.js.map +0 -1
- package/dist/notify/transports.test.d.ts +0 -1
- package/dist/notify/transports.test.js +0 -71
- package/dist/notify/transports.test.js.map +0 -1
- package/dist/overlay-assets.js.map +0 -1
- package/dist/overlay-assets.test.d.ts +0 -1
- package/dist/overlay-assets.test.js +0 -75
- package/dist/overlay-assets.test.js.map +0 -1
- package/dist/overlay-bootstrap.js.map +0 -1
- package/dist/overlay-bootstrap.test.d.ts +0 -1
- package/dist/overlay-bootstrap.test.js +0 -61
- package/dist/overlay-bootstrap.test.js.map +0 -1
- package/dist/sandbox/command-exec.integration.test.d.ts +0 -1
- package/dist/sandbox/command-exec.integration.test.js +0 -183
- package/dist/sandbox/command-exec.integration.test.js.map +0 -1
- package/dist/sandbox/docker-compose.test.d.ts +0 -1
- package/dist/sandbox/docker-compose.test.js +0 -128
- package/dist/sandbox/docker-compose.test.js.map +0 -1
- package/dist/sandbox/docker.test.d.ts +0 -1
- package/dist/sandbox/docker.test.js +0 -137
- package/dist/sandbox/docker.test.js.map +0 -1
- package/dist/sandbox/egress-allowlist.test.d.ts +0 -1
- package/dist/sandbox/egress-allowlist.test.js +0 -65
- package/dist/sandbox/egress-allowlist.test.js.map +0 -1
- package/dist/sandbox/egress-firewall-config.test.d.ts +0 -1
- package/dist/sandbox/egress-firewall-config.test.js +0 -244
- package/dist/sandbox/egress-firewall-config.test.js.map +0 -1
- package/dist/sandbox/index.test.d.ts +0 -1
- package/dist/sandbox/index.test.js +0 -157
- package/dist/sandbox/index.test.js.map +0 -1
- package/dist/sandbox/smol.integration.test.d.ts +0 -1
- package/dist/sandbox/smol.integration.test.js +0 -130
- package/dist/sandbox/smol.integration.test.js.map +0 -1
- package/dist/session-log.test.d.ts +0 -1
- package/dist/session-log.test.js +0 -85
- package/dist/session-log.test.js.map +0 -1
- package/dist/setup.js.map +0 -1
- package/dist/setup.test.d.ts +0 -1
- package/dist/setup.test.js +0 -281
- package/dist/setup.test.js.map +0 -1
- package/dist/skills-install.js.map +0 -1
- package/dist/state/build-assets.test.d.ts +0 -1
- package/dist/state/build-assets.test.js +0 -106
- package/dist/state/build-assets.test.js.map +0 -1
- package/dist/state/db.test.d.ts +0 -1
- package/dist/state/db.test.js +0 -484
- package/dist/state/db.test.js.map +0 -1
- package/dist/state/workflow-run-store.test.d.ts +0 -1
- package/dist/state/workflow-run-store.test.js +0 -264
- package/dist/state/workflow-run-store.test.js.map +0 -1
- package/dist/telemetry/index.test.d.ts +0 -1
- package/dist/telemetry/index.test.js +0 -73
- package/dist/telemetry/index.test.js.map +0 -1
- package/dist/telemetry/pi-events.test.d.ts +0 -1
- package/dist/telemetry/pi-events.test.js +0 -69
- package/dist/telemetry/pi-events.test.js.map +0 -1
- package/dist/workflows/dag.test.d.ts +0 -1
- package/dist/workflows/dag.test.js +0 -197
- package/dist/workflows/dag.test.js.map +0 -1
- package/dist/workflows/golden-build.test.d.ts +0 -1
- package/dist/workflows/golden-build.test.js +0 -45
- package/dist/workflows/golden-build.test.js.map +0 -1
- package/dist/workflows/loader-overlay.test.d.ts +0 -1
- package/dist/workflows/loader-overlay.test.js +0 -101
- package/dist/workflows/loader-overlay.test.js.map +0 -1
- package/dist/workflows/loader.test.d.ts +0 -1
- package/dist/workflows/loader.test.js +0 -375
- package/dist/workflows/loader.test.js.map +0 -1
- package/dist/workflows/loop-eval.test.d.ts +0 -1
- package/dist/workflows/loop-eval.test.js +0 -89
- package/dist/workflows/loop-eval.test.js.map +0 -1
- package/dist/workflows/phase-executor.test.d.ts +0 -1
- package/dist/workflows/phase-executor.test.js +0 -497
- package/dist/workflows/phase-executor.test.js.map +0 -1
- package/dist/workflows/phase-ref.test.d.ts +0 -1
- package/dist/workflows/phase-ref.test.js +0 -24
- package/dist/workflows/phase-ref.test.js.map +0 -1
- package/dist/workflows/runner.test.d.ts +0 -1
- package/dist/workflows/runner.test.js +0 -1174
- package/dist/workflows/runner.test.js.map +0 -1
- package/dist/workflows/simple.test.d.ts +0 -1
- package/dist/workflows/simple.test.js +0 -107
- package/dist/workflows/simple.test.js.map +0 -1
- package/dist/workflows/templates.test.d.ts +0 -1
- package/dist/workflows/templates.test.js +0 -258
- package/dist/workflows/templates.test.js.map +0 -1
- package/dist/workflows/verdict.test.d.ts +0 -1
- package/dist/workflows/verdict.test.js +0 -44
- package/dist/workflows/verdict.test.js.map +0 -1
- package/dist/worktree/manager.test.d.ts +0 -1
- package/dist/worktree/manager.test.js +0 -87
- package/dist/worktree/manager.test.js.map +0 -1
- /package/dist/{cli-config.d.ts → cli/cli-config.d.ts} +0 -0
- /package/dist/{cli-config.js → cli/cli-config.js} +0 -0
- /package/dist/{cli-format.d.ts → cli/cli-format.d.ts} +0 -0
- /package/dist/{cli-format.js → cli/cli-format.js} +0 -0
- /package/dist/{cli-timeline.d.ts → cli/cli-timeline.d.ts} +0 -0
- /package/dist/{cli-timeline.js → cli/cli-timeline.js} +0 -0
- /package/dist/{cli.d.ts → cli/cli.d.ts} +0 -0
- /package/dist/{setup.d.ts → cli/setup.d.ts} +0 -0
- /package/dist/{skills-install.d.ts → cli/skills-install.d.ts} +0 -0
- /package/dist/{config-resolve.d.ts → config/config-resolve.d.ts} +0 -0
- /package/dist/{config-resolve.js → config/config-resolve.js} +0 -0
- /package/dist/{config.d.ts → config/config.d.ts} +0 -0
- /package/dist/{overlay-assets.d.ts → config/overlay-assets.d.ts} +0 -0
- /package/dist/{overlay-assets.js → config/overlay-assets.js} +0 -0
- /package/dist/{overlay-bootstrap.d.ts → config/overlay-bootstrap.d.ts} +0 -0
- /package/dist/{overlay-bootstrap.js → config/overlay-bootstrap.js} +0 -0
- /package/dist/engine/{chat-skills.d.ts → chat/chat-skills.d.ts} +0 -0
- /package/dist/engine/{message-batcher.js → chat/message-batcher.js} +0 -0
- /package/dist/engine/{git-auth.d.ts → github/git-auth.d.ts} +0 -0
- /package/dist/engine/{git-auth.js → github/git-auth.js} +0 -0
- /package/dist/engine/{github-app-client.d.ts → github/github-app-client.d.ts} +0 -0
- /package/dist/engine/{github-app-client.js → github/github-app-client.js} +0 -0
- /package/dist/engine/{github-tools.d.ts → github/github-tools.d.ts} +0 -0
- /package/dist/engine/{github-tools.js → github/github-tools.js} +0 -0
- /package/dist/engine/{github.d.ts → github/github.d.ts} +0 -0
- /package/dist/engine/{github.js → github/github.js} +0 -0
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { readFileSync } from "fs";
|
|
3
|
-
import { resolve } from "path";
|
|
4
|
-
import { parse } from "yaml";
|
|
5
|
-
import { COREDNS_OPEN_IP, COREDNS_STRICT_IP, NGINX_OPEN_IP, NGINX_STRICT_IP, OTEL_COLLECTOR_IP, OTEL_COLLECTOR_UID, SANDBOX_EGRESS_SUBNET, } from "./egress-firewall-config.js";
|
|
6
|
-
const compose = parse(readFileSync(resolve(__dirname, "../../docker-compose.yml"), "utf-8"));
|
|
7
|
-
const FIREWALL_SERVICES = [
|
|
8
|
-
"nginx-egress-strict",
|
|
9
|
-
"nginx-egress-open",
|
|
10
|
-
"coredns-strict",
|
|
11
|
-
"coredns-open",
|
|
12
|
-
];
|
|
13
|
-
const HARNESS_SERVICES = ["agent", "caddy"];
|
|
14
|
-
function networkNamesOf(service) {
|
|
15
|
-
const nets = compose.services[service]?.networks;
|
|
16
|
-
if (!nets)
|
|
17
|
-
return [];
|
|
18
|
-
if (Array.isArray(nets))
|
|
19
|
-
return nets;
|
|
20
|
-
return Object.keys(nets);
|
|
21
|
-
}
|
|
22
|
-
function ipv4OnNetwork(service, net) {
|
|
23
|
-
const nets = compose.services[service]?.networks;
|
|
24
|
-
if (!nets || Array.isArray(nets))
|
|
25
|
-
return undefined;
|
|
26
|
-
const cfg = nets[net];
|
|
27
|
-
if (cfg && typeof cfg === "object" && "ipv4_address" in cfg) {
|
|
28
|
-
return String(cfg.ipv4_address);
|
|
29
|
-
}
|
|
30
|
-
return undefined;
|
|
31
|
-
}
|
|
32
|
-
describe("docker-compose egress topology", () => {
|
|
33
|
-
it("declares the three expected networks", () => {
|
|
34
|
-
expect(Object.keys(compose.networks).sort()).toEqual(["internal", "proxy-egress", "sandbox-egress"].sort());
|
|
35
|
-
});
|
|
36
|
-
it("marks sandbox-egress as internal: true with the expected static subnet", () => {
|
|
37
|
-
const net = compose.networks["sandbox-egress"];
|
|
38
|
-
expect(net?.internal).toBe(true);
|
|
39
|
-
// Subnet must match SANDBOX_EGRESS_SUBNET so the constants in
|
|
40
|
-
// egress-firewall-config.ts are valid IPs in that range.
|
|
41
|
-
const subnet = net?.ipam?.config?.[0]?.subnet;
|
|
42
|
-
expect(subnet).toBe(SANDBOX_EGRESS_SUBNET);
|
|
43
|
-
});
|
|
44
|
-
it("proxy-egress is a regular bridge (no internal: true) so firewalls can reach the internet", () => {
|
|
45
|
-
const net = compose.networks["proxy-egress"];
|
|
46
|
-
// YAML `proxy-egress:` parses to null when no fields are set.
|
|
47
|
-
expect(net == null || net.internal !== true).toBe(true);
|
|
48
|
-
});
|
|
49
|
-
for (const svc of FIREWALL_SERVICES) {
|
|
50
|
-
describe(svc, () => {
|
|
51
|
-
const nets = networkNamesOf(svc);
|
|
52
|
-
it("does NOT attach to the harness `internal` network", () => {
|
|
53
|
-
// Security-critical: if regressed, an unrestricted sandbox can
|
|
54
|
-
// bridge into compose-internal services via the firewall.
|
|
55
|
-
expect(nets).not.toContain("internal");
|
|
56
|
-
});
|
|
57
|
-
it("attaches to sandbox-egress (so sandboxes can reach it)", () => {
|
|
58
|
-
expect(nets).toContain("sandbox-egress");
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
}
|
|
62
|
-
it("nginx firewalls attach to proxy-egress (so they can reach the public internet)", () => {
|
|
63
|
-
for (const svc of ["nginx-egress-strict", "nginx-egress-open"]) {
|
|
64
|
-
expect(networkNamesOf(svc)).toContain("proxy-egress");
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
describe("otel-collector", () => {
|
|
68
|
-
const nets = networkNamesOf("otel-collector");
|
|
69
|
-
it("is dual-homed on sandbox-egress (ingress from sandboxes) + proxy-egress (outbound to backend)", () => {
|
|
70
|
-
expect(nets).toContain("sandbox-egress");
|
|
71
|
-
expect(nets).toContain("proxy-egress");
|
|
72
|
-
});
|
|
73
|
-
it("does NOT attach to the harness `internal` network (can't bridge a sandbox into harness services)", () => {
|
|
74
|
-
expect(nets).not.toContain("internal");
|
|
75
|
-
});
|
|
76
|
-
it("uses the static IP the harness bakes into the sandbox OTLP endpoint", () => {
|
|
77
|
-
expect(ipv4OnNetwork("otel-collector", "sandbox-egress")).toBe(OTEL_COLLECTOR_IP);
|
|
78
|
-
});
|
|
79
|
-
it("does NOT force root — runs as the image's non-root UID", () => {
|
|
80
|
-
// The harness writes the mode-0600 config as UID OTEL_COLLECTOR_UID (its
|
|
81
|
-
// `lastlight` user is pinned to it), so the collector reads it as owner
|
|
82
|
-
// without root. If this ever regresses to `user: "0:0"`, we'd be running
|
|
83
|
-
// a sandbox-facing service as root unnecessarily.
|
|
84
|
-
expect(compose.services["otel-collector"]?.user).toBeUndefined();
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
it("pins the harness `lastlight` UID to the collector image UID so it can read the 0600 config", () => {
|
|
88
|
-
// The collector reads the harness-written mode-0600 OTLP config as its
|
|
89
|
-
// owner. That only works if the harness writes it as the collector's UID,
|
|
90
|
-
// so the Dockerfile must `useradd -u <OTEL_COLLECTOR_UID> lastlight`. If
|
|
91
|
-
// the collector image's UID ever changes, this test fails loudly to force
|
|
92
|
-
// bumping both together.
|
|
93
|
-
const dockerfile = readFileSync(resolve(__dirname, "../../Dockerfile"), "utf-8");
|
|
94
|
-
expect(dockerfile).toMatch(new RegExp(`useradd[^\\n]*-u\\s+${OTEL_COLLECTOR_UID}\\s+lastlight`));
|
|
95
|
-
});
|
|
96
|
-
it("coredns containers do NOT attach to proxy-egress (no internet needed)", () => {
|
|
97
|
-
// CoreDNS only synthesises answers from its config; it never recurses.
|
|
98
|
-
// Keeping it off proxy-egress is defence in depth.
|
|
99
|
-
for (const svc of ["coredns-strict", "coredns-open"]) {
|
|
100
|
-
expect(networkNamesOf(svc)).not.toContain("proxy-egress");
|
|
101
|
-
}
|
|
102
|
-
});
|
|
103
|
-
it("static IPs match the constants the harness bakes into the generated configs", () => {
|
|
104
|
-
expect(ipv4OnNetwork("coredns-strict", "sandbox-egress")).toBe(COREDNS_STRICT_IP);
|
|
105
|
-
expect(ipv4OnNetwork("coredns-open", "sandbox-egress")).toBe(COREDNS_OPEN_IP);
|
|
106
|
-
expect(ipv4OnNetwork("nginx-egress-strict", "sandbox-egress")).toBe(NGINX_STRICT_IP);
|
|
107
|
-
expect(ipv4OnNetwork("nginx-egress-open", "sandbox-egress")).toBe(NGINX_OPEN_IP);
|
|
108
|
-
});
|
|
109
|
-
for (const svc of HARNESS_SERVICES) {
|
|
110
|
-
it(`harness service \`${svc}\` does NOT attach to proxy-egress or sandbox-egress`, () => {
|
|
111
|
-
// Keeps the egress fabric isolated from anything carrying secrets.
|
|
112
|
-
const nets = networkNamesOf(svc);
|
|
113
|
-
expect(nets).not.toContain("proxy-egress");
|
|
114
|
-
expect(nets).not.toContain("sandbox-egress");
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
it("proxy-egress contains exactly the two nginx firewalls and the otel-collector", () => {
|
|
118
|
-
// These are the only services allowed an outbound path: the firewalls
|
|
119
|
-
// tunnel sandbox HTTPS, and the collector re-exports sandbox telemetry.
|
|
120
|
-
// None touch `internal`, so none can bridge a sandbox into harness
|
|
121
|
-
// services. Any other service appearing here is a regression.
|
|
122
|
-
const onProxyEgress = Object.keys(compose.services)
|
|
123
|
-
.filter((name) => networkNamesOf(name).includes("proxy-egress"))
|
|
124
|
-
.sort();
|
|
125
|
-
expect(onProxyEgress).toEqual(["nginx-egress-open", "nginx-egress-strict", "otel-collector"]);
|
|
126
|
-
});
|
|
127
|
-
});
|
|
128
|
-
//# sourceMappingURL=docker-compose.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docker-compose.test.js","sourceRoot":"","sources":["../../src/sandbox/docker-compose.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
2
|
-
import { EventEmitter } from "events";
|
|
3
|
-
vi.mock("child_process", async (importOriginal) => {
|
|
4
|
-
const actual = await importOriginal();
|
|
5
|
-
return {
|
|
6
|
-
...actual,
|
|
7
|
-
spawn: vi.fn(),
|
|
8
|
-
// create() runs `docker run` via execFileSync and polls readiness via
|
|
9
|
-
// execFile (promisified). Stub both so create() can be exercised without a
|
|
10
|
-
// real docker daemon.
|
|
11
|
-
execFileSync: vi.fn().mockReturnValue("container-xyz\n"),
|
|
12
|
-
execFile: vi.fn((_cmd, _args, opts, cb) => {
|
|
13
|
-
const done = (typeof opts === "function" ? opts : cb);
|
|
14
|
-
done(null, { stdout: "", stderr: "" });
|
|
15
|
-
}),
|
|
16
|
-
};
|
|
17
|
-
});
|
|
18
|
-
vi.mock("fs", async (importOriginal) => {
|
|
19
|
-
const actual = await importOriginal();
|
|
20
|
-
return { ...actual, existsSync: vi.fn().mockReturnValue(true), readFileSync: vi.fn().mockReturnValue("{}") };
|
|
21
|
-
});
|
|
22
|
-
import { spawn, execFileSync } from "child_process";
|
|
23
|
-
import { DockerSandbox } from "./docker.js";
|
|
24
|
-
const mockSpawn = vi.mocked(spawn);
|
|
25
|
-
const mockExecFileSync = vi.mocked(execFileSync);
|
|
26
|
-
function makeFakeChild() {
|
|
27
|
-
const stdin = { write: vi.fn(), end: vi.fn() };
|
|
28
|
-
const stdout = Object.assign(new EventEmitter(), { setEncoding: vi.fn() });
|
|
29
|
-
const stderr = Object.assign(new EventEmitter(), { setEncoding: vi.fn() });
|
|
30
|
-
const child = Object.assign(new EventEmitter(), { stdin, stdout, stderr, kill: vi.fn() });
|
|
31
|
-
return child;
|
|
32
|
-
}
|
|
33
|
-
describe("DockerSandbox.runAgent — prompt via stdin, not shell arg", () => {
|
|
34
|
-
let manager;
|
|
35
|
-
let fakeChild;
|
|
36
|
-
beforeEach(() => {
|
|
37
|
-
vi.clearAllMocks();
|
|
38
|
-
fakeChild = makeFakeChild();
|
|
39
|
-
mockSpawn.mockReturnValue(fakeChild);
|
|
40
|
-
manager = new DockerSandbox({
|
|
41
|
-
imageName: "test-image",
|
|
42
|
-
env: {},
|
|
43
|
-
timeoutSeconds: 5,
|
|
44
|
-
});
|
|
45
|
-
manager
|
|
46
|
-
.activeContainers.set("task-001", {
|
|
47
|
-
containerId: "abc123",
|
|
48
|
-
containerName: "test-container",
|
|
49
|
-
worktreePath: "/tmp/work",
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
it("spawn is called with stdin: 'pipe'", async () => {
|
|
53
|
-
const runPromise = manager.runAgent("task-001", "hello world");
|
|
54
|
-
process.nextTick(() => fakeChild.emit("close", 0));
|
|
55
|
-
await runPromise;
|
|
56
|
-
const spawnOpts = mockSpawn.mock.calls[0][2];
|
|
57
|
-
expect(spawnOpts.stdio[0]).toBe("pipe");
|
|
58
|
-
});
|
|
59
|
-
it("prompt is written to child.stdin", async () => {
|
|
60
|
-
const prompt = "Do something dangerous'; rm -rf /; echo '";
|
|
61
|
-
const runPromise = manager.runAgent("task-001", prompt);
|
|
62
|
-
process.nextTick(() => fakeChild.emit("close", 0));
|
|
63
|
-
await runPromise;
|
|
64
|
-
expect(fakeChild.stdin.write).toHaveBeenCalledWith(prompt);
|
|
65
|
-
expect(fakeChild.stdin.end).toHaveBeenCalled();
|
|
66
|
-
});
|
|
67
|
-
it("prompt is not embedded in the docker exec args", async () => {
|
|
68
|
-
const prompt = "secret'; rm -rf /;'";
|
|
69
|
-
const runPromise = manager.runAgent("task-001", prompt);
|
|
70
|
-
process.nextTick(() => fakeChild.emit("close", 0));
|
|
71
|
-
await runPromise;
|
|
72
|
-
const dockerArgs = mockSpawn.mock.calls[0][1].join(" ");
|
|
73
|
-
expect(dockerArgs).not.toContain(prompt);
|
|
74
|
-
expect(dockerArgs).not.toContain("rm -rf");
|
|
75
|
-
});
|
|
76
|
-
it("docker exec args contain -i flag for stdin", async () => {
|
|
77
|
-
const runPromise = manager.runAgent("task-001", "test prompt");
|
|
78
|
-
process.nextTick(() => fakeChild.emit("close", 0));
|
|
79
|
-
await runPromise;
|
|
80
|
-
const dockerArgs = mockSpawn.mock.calls[0][1];
|
|
81
|
-
expect(dockerArgs).toContain("-i");
|
|
82
|
-
});
|
|
83
|
-
it("agentic-pi command runs in --sandbox none mode and does not embed the prompt", async () => {
|
|
84
|
-
const runPromise = manager.runAgent("task-001", "test prompt");
|
|
85
|
-
process.nextTick(() => fakeChild.emit("close", 0));
|
|
86
|
-
await runPromise;
|
|
87
|
-
const dockerArgs = mockSpawn.mock.calls[0][1];
|
|
88
|
-
const shCmd = dockerArgs[dockerArgs.length - 1];
|
|
89
|
-
expect(shCmd).toContain("agentic-pi run");
|
|
90
|
-
expect(shCmd).toContain("--sandbox none");
|
|
91
|
-
expect(shCmd).not.toContain("--no-file-search");
|
|
92
|
-
expect(shCmd).not.toContain("test prompt");
|
|
93
|
-
});
|
|
94
|
-
});
|
|
95
|
-
describe("DockerSandbox.create — shared package cache (issue #107)", () => {
|
|
96
|
-
beforeEach(() => {
|
|
97
|
-
vi.clearAllMocks();
|
|
98
|
-
mockExecFileSync.mockReturnValue("container-xyz\n");
|
|
99
|
-
});
|
|
100
|
-
function dockerRunArgv() {
|
|
101
|
-
const call = mockExecFileSync.mock.calls.find((c) => Array.isArray(c[1]) && c[1][0] === "run");
|
|
102
|
-
return call?.[1] ?? [];
|
|
103
|
-
}
|
|
104
|
-
it("mounts the shared cache volume and wires npm/pnpm/yarn env", async () => {
|
|
105
|
-
const manager = new DockerSandbox({ imageName: "img", env: {} });
|
|
106
|
-
await manager.create({
|
|
107
|
-
taskId: "repo-1-pr-review",
|
|
108
|
-
worktreePath: "/tmp/work",
|
|
109
|
-
workspaceMount: { type: "bind", hostPath: "/tmp/work" },
|
|
110
|
-
});
|
|
111
|
-
const argv = dockerRunArgv();
|
|
112
|
-
expect(argv).toContain("lastlight_pkg-cache:/cache");
|
|
113
|
-
expect(argv).toContain("npm_config_cache=/cache/npm");
|
|
114
|
-
expect(argv).toContain("npm_config_store_dir=/cache/pnpm");
|
|
115
|
-
expect(argv).toContain("YARN_CACHE_FOLDER=/cache/yarn");
|
|
116
|
-
});
|
|
117
|
-
it("honours LASTLIGHT_PKG_CACHE_VOLUME override", async () => {
|
|
118
|
-
const prev = process.env.LASTLIGHT_PKG_CACHE_VOLUME;
|
|
119
|
-
process.env.LASTLIGHT_PKG_CACHE_VOLUME = "my-cache";
|
|
120
|
-
try {
|
|
121
|
-
const manager = new DockerSandbox({ imageName: "img", env: {} });
|
|
122
|
-
await manager.create({
|
|
123
|
-
taskId: "t",
|
|
124
|
-
worktreePath: "/tmp/work",
|
|
125
|
-
workspaceMount: { type: "bind", hostPath: "/tmp/work" },
|
|
126
|
-
});
|
|
127
|
-
expect(dockerRunArgv()).toContain("my-cache:/cache");
|
|
128
|
-
}
|
|
129
|
-
finally {
|
|
130
|
-
if (prev === undefined)
|
|
131
|
-
delete process.env.LASTLIGHT_PKG_CACHE_VOLUME;
|
|
132
|
-
else
|
|
133
|
-
process.env.LASTLIGHT_PKG_CACHE_VOLUME = prev;
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
});
|
|
137
|
-
//# sourceMappingURL=docker.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"docker.test.js","sourceRoot":"","sources":["../../src/sandbox/docker.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { ALLOW_ALL_SENTINEL, DEFAULT_ALLOWLIST, GITHUB_HOSTS, PACKAGE_REGISTRY_HOSTS, PROVIDER_HOSTS, mergeAllowlist, normalizeAllowlistHost, } from "./egress-allowlist.js";
|
|
3
|
-
describe("egress-allowlist source of truth", () => {
|
|
4
|
-
it("groups are non-empty and disjoint", () => {
|
|
5
|
-
for (const group of [GITHUB_HOSTS, PROVIDER_HOSTS, PACKAGE_REGISTRY_HOSTS]) {
|
|
6
|
-
expect(group.length).toBeGreaterThan(0);
|
|
7
|
-
}
|
|
8
|
-
const all = [...GITHUB_HOSTS, ...PROVIDER_HOSTS, ...PACKAGE_REGISTRY_HOSTS];
|
|
9
|
-
expect(new Set(all).size).toBe(all.length);
|
|
10
|
-
});
|
|
11
|
-
it("DEFAULT_ALLOWLIST is the union of the three groups in declaration order", () => {
|
|
12
|
-
const expected = [...GITHUB_HOSTS, ...PROVIDER_HOSTS, ...PACKAGE_REGISTRY_HOSTS];
|
|
13
|
-
expect([...DEFAULT_ALLOWLIST]).toEqual(expected);
|
|
14
|
-
});
|
|
15
|
-
it("covers the critical host categories the runtime depends on", () => {
|
|
16
|
-
// GitHub apex covers api.github.com, codeload.github.com, raw.…
|
|
17
|
-
expect(GITHUB_HOSTS).toContain("github.com");
|
|
18
|
-
// Provider hosts — the docker backend dials these from inside the
|
|
19
|
-
// sandbox container.
|
|
20
|
-
expect(PROVIDER_HOSTS).toContain("openai.com");
|
|
21
|
-
expect(PROVIDER_HOSTS).toContain("anthropic.com");
|
|
22
|
-
// npm — covers registry.npmjs.org, auth.npmjs.org, www.npmjs.org.
|
|
23
|
-
expect(PACKAGE_REGISTRY_HOSTS).toContain("npmjs.org");
|
|
24
|
-
});
|
|
25
|
-
it("entries are bare hostnames (no leading dot, no wildcard prefix)", () => {
|
|
26
|
-
// Convention is "every entry matches apex+subdomains" — see file
|
|
27
|
-
// docstring. The config generator emits the right syntax for each
|
|
28
|
-
// backend. If someone tries to write `.github.com` or `*.github.com`
|
|
29
|
-
// here, fail fast.
|
|
30
|
-
for (const host of DEFAULT_ALLOWLIST) {
|
|
31
|
-
expect(host).toMatch(/^[A-Za-z0-9][A-Za-z0-9.-]*[A-Za-z0-9]$/);
|
|
32
|
-
expect(host.startsWith(".")).toBe(false);
|
|
33
|
-
expect(host.includes("*")).toBe(false);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
it("ALLOW_ALL_SENTINEL is the wildcard string the gondolin matcher honours", () => {
|
|
37
|
-
expect(ALLOW_ALL_SENTINEL).toBe("*");
|
|
38
|
-
});
|
|
39
|
-
it("normalizes and merges extra allowlist hosts", () => {
|
|
40
|
-
expect(normalizeAllowlistHost("https://Collector.Example.com:4318/v1/traces")).toBe("collector.example.com");
|
|
41
|
-
expect(normalizeAllowlistHost("169.254.169.254")).toBeNull();
|
|
42
|
-
expect(mergeAllowlist(["github.com"], ["GitHub.com", "otel.example.com"])).toEqual([
|
|
43
|
-
"github.com",
|
|
44
|
-
"otel.example.com",
|
|
45
|
-
]);
|
|
46
|
-
});
|
|
47
|
-
it("rejects private and internal collector endpoints", () => {
|
|
48
|
-
for (const host of [
|
|
49
|
-
"0.0.0.0",
|
|
50
|
-
"127.0.0.1",
|
|
51
|
-
"10.0.0.4",
|
|
52
|
-
"172.16.0.1",
|
|
53
|
-
"192.168.1.10",
|
|
54
|
-
"http://[::1]:4318",
|
|
55
|
-
"http://[fe80::1]:4318",
|
|
56
|
-
"http://[fd00::1]:4318",
|
|
57
|
-
"http://[::ffff:127.0.0.1]:4318",
|
|
58
|
-
"metadata.google.internal",
|
|
59
|
-
"localhost",
|
|
60
|
-
]) {
|
|
61
|
-
expect(normalizeAllowlistHost(host), host).toBeNull();
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
//# sourceMappingURL=egress-allowlist.test.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"egress-allowlist.test.js","sourceRoot":"","sources":["../../src/sandbox/egress-allowlist.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,244 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect } from "vitest";
|
|
2
|
-
import { COREDNS_OPEN_IP, COREDNS_STRICT_IP, NGINX_OPEN_IP, NGINX_STRICT_IP, OTEL_COLLECTOR_IP, OTEL_COLLECTOR_OTLP_GRPC_PORT, OTEL_COLLECTOR_OTLP_HTTP_PORT, OTEL_COLLECTOR_SANDBOX_ENDPOINT, renderCorefileOpen, renderCorefileStrict, renderNginxOpenConf, renderNginxStrictConf, renderOtelCollectorConfig, SANDBOX_EGRESS_SUBNET, } from "./egress-firewall-config.js";
|
|
3
|
-
import { DEFAULT_ALLOWLIST } from "./egress-allowlist.js";
|
|
4
|
-
import { parse as parseYaml } from "yaml";
|
|
5
|
-
describe("static IP constants", () => {
|
|
6
|
-
it("all four service IPs sit inside the sandbox-egress subnet", () => {
|
|
7
|
-
const prefix = SANDBOX_EGRESS_SUBNET.split("/")[0].split(".").slice(0, 3).join(".");
|
|
8
|
-
for (const ip of [COREDNS_STRICT_IP, COREDNS_OPEN_IP, NGINX_STRICT_IP, NGINX_OPEN_IP]) {
|
|
9
|
-
expect(ip.startsWith(prefix + ".")).toBe(true);
|
|
10
|
-
}
|
|
11
|
-
});
|
|
12
|
-
it("strict and open IPs are distinct in both pairs", () => {
|
|
13
|
-
expect(COREDNS_STRICT_IP).not.toBe(COREDNS_OPEN_IP);
|
|
14
|
-
expect(NGINX_STRICT_IP).not.toBe(NGINX_OPEN_IP);
|
|
15
|
-
});
|
|
16
|
-
});
|
|
17
|
-
describe("nginx strict config", () => {
|
|
18
|
-
const conf = renderNginxStrictConf();
|
|
19
|
-
it("listens on 443 with ssl_preread enabled", () => {
|
|
20
|
-
expect(conf).toMatch(/listen\s+443;/);
|
|
21
|
-
expect(conf).toMatch(/ssl_preread\s+on;/);
|
|
22
|
-
});
|
|
23
|
-
it("defaults unknown SNIs to a black-hole upstream (instant reset)", () => {
|
|
24
|
-
expect(conf).toMatch(/default\s+127\.0\.0\.1:1;/);
|
|
25
|
-
});
|
|
26
|
-
it("emits a leading-dot map entry per allowlist host (apex+subdomain match)", () => {
|
|
27
|
-
for (const host of DEFAULT_ALLOWLIST) {
|
|
28
|
-
// nginx's `.foo.com` syntax matches `foo.com` and any subdomain.
|
|
29
|
-
// Upstream is the live SNI value, not pinned at config time.
|
|
30
|
-
expect(conf).toContain(`.${host} $ssl_preread_server_name:443;`);
|
|
31
|
-
}
|
|
32
|
-
});
|
|
33
|
-
it("uses docker's embedded DNS as the upstream resolver", () => {
|
|
34
|
-
expect(conf).toMatch(/resolver\s+127\.0\.0\.11/);
|
|
35
|
-
});
|
|
36
|
-
it("declares `hostnames` in the map block so leading-dot wildcards work", () => {
|
|
37
|
-
// Without `hostnames;`, nginx's `map` does exact string match only —
|
|
38
|
-
// `.github.com` becomes a literal key that never matches anything.
|
|
39
|
-
// We hit this in prod: every allowlisted host fell through to the
|
|
40
|
-
// black-hole default. Pin the contract so it can't regress.
|
|
41
|
-
expect(conf).toMatch(/map\s+\$ssl_preread_server_name\s+\$upstream_target\s*\{\s*\n\s*hostnames;/);
|
|
42
|
-
});
|
|
43
|
-
});
|
|
44
|
-
describe("nginx open config", () => {
|
|
45
|
-
const conf = renderNginxOpenConf();
|
|
46
|
-
it("tunnels whatever SNI was sent — no allowlist map", () => {
|
|
47
|
-
expect(conf).toMatch(/proxy_pass\s+\$ssl_preread_server_name:443;/);
|
|
48
|
-
expect(conf).not.toMatch(/map\s+\$ssl_preread_server_name/);
|
|
49
|
-
});
|
|
50
|
-
it("still listens on 443 with ssl_preread enabled", () => {
|
|
51
|
-
expect(conf).toMatch(/listen\s+443;/);
|
|
52
|
-
expect(conf).toMatch(/ssl_preread\s+on;/);
|
|
53
|
-
});
|
|
54
|
-
});
|
|
55
|
-
describe("coredns strict Corefile", () => {
|
|
56
|
-
const conf = renderCorefileStrict();
|
|
57
|
-
it("uses a SINGLE template block with one match line per allowlist host", () => {
|
|
58
|
-
// CoreDNS only honours one `template` block per (class, type, zone) —
|
|
59
|
-
// multiple blocks silently shadow each other. Catching a regression
|
|
60
|
-
// matters because we hit this exact bug in prod.
|
|
61
|
-
const templateBlocks = conf.match(/template\s+IN\s+A\s*\{/g) || [];
|
|
62
|
-
expect(templateBlocks.length).toBe(1);
|
|
63
|
-
for (const host of DEFAULT_ALLOWLIST) {
|
|
64
|
-
const escaped = host.replaceAll(".", "\\.");
|
|
65
|
-
expect(conf).toContain(`(^|\\.)${escaped}\\.$`);
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
it("answers with the nginx-strict IP", () => {
|
|
69
|
-
expect(conf).toContain(`IN A ${NGINX_STRICT_IP}`);
|
|
70
|
-
});
|
|
71
|
-
it("catches every unmatched query with an NXDOMAIN template", () => {
|
|
72
|
-
expect(conf).toMatch(/template\s+IN\s+ANY\s*\{[\s\S]*rcode\s+NXDOMAIN[\s\S]*\}/);
|
|
73
|
-
});
|
|
74
|
-
});
|
|
75
|
-
describe("coredns open Corefile", () => {
|
|
76
|
-
const conf = renderCorefileOpen();
|
|
77
|
-
it("returns the nginx-open IP for arbitrary A queries", () => {
|
|
78
|
-
expect(conf).toContain(`IN A ${NGINX_OPEN_IP}`);
|
|
79
|
-
});
|
|
80
|
-
it("hard-denies cloud metadata literals even in unrestricted mode", () => {
|
|
81
|
-
// Each hard-deny host gets its own zone block so CoreDNS's
|
|
82
|
-
// longest-suffix routing intercepts the apex + subdomains before
|
|
83
|
-
// the catch-all `.` zone sees them.
|
|
84
|
-
expect(conf).toMatch(/metadata\.google\.internal:53\s*\{[\s\S]*?rcode\s+NXDOMAIN/);
|
|
85
|
-
expect(conf).toMatch(/169\.254\.169\.254:53\s*\{[\s\S]*?rcode\s+NXDOMAIN/);
|
|
86
|
-
});
|
|
87
|
-
it("returns NOERROR / empty for AAAA so IPv6 doesn't accidentally bypass us", () => {
|
|
88
|
-
expect(conf).toMatch(/template\s+IN\s+AAAA\s*\{[\s\S]*rcode\s+NOERROR[\s\S]*\}/);
|
|
89
|
-
});
|
|
90
|
-
});
|
|
91
|
-
describe("otel collector config", () => {
|
|
92
|
-
// Most tests run with forwarding active; the gating tests below cover inactive.
|
|
93
|
-
const renderActive = (env) => parseYaml(renderOtelCollectorConfig({ active: true, env }));
|
|
94
|
-
it("sandbox endpoint points at the collector's static IP + OTLP/HTTP port", () => {
|
|
95
|
-
expect(OTEL_COLLECTOR_SANDBOX_ENDPOINT).toBe(`http://${OTEL_COLLECTOR_IP}:${OTEL_COLLECTOR_OTLP_HTTP_PORT}`);
|
|
96
|
-
});
|
|
97
|
-
it("receives OTLP on both http and grpc, on all interfaces", () => {
|
|
98
|
-
const cfg = renderActive({});
|
|
99
|
-
expect(cfg.receivers.otlp.protocols.http.endpoint).toBe(`0.0.0.0:${OTEL_COLLECTOR_OTLP_HTTP_PORT}`);
|
|
100
|
-
expect(cfg.receivers.otlp.protocols.grpc.endpoint).toBe(`0.0.0.0:${OTEL_COLLECTOR_OTLP_GRPC_PORT}`);
|
|
101
|
-
});
|
|
102
|
-
it("re-exports to the generic endpoint (as a base URL) with parsed auth headers on every signal", () => {
|
|
103
|
-
const cfg = renderActive({
|
|
104
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://collector.example.com:4318",
|
|
105
|
-
OTEL_EXPORTER_OTLP_HEADERS: "api-key=secret-123,x-tenant=acme",
|
|
106
|
-
});
|
|
107
|
-
for (const signal of ["traces", "metrics", "logs"]) {
|
|
108
|
-
const exp = cfg.exporters[`otlphttp/${signal}`];
|
|
109
|
-
// Generic endpoint → `endpoint` field (collector appends /v1/<signal>).
|
|
110
|
-
expect(exp.endpoint).toBe("https://collector.example.com:4318");
|
|
111
|
-
expect(exp[`${signal}_endpoint`]).toBeUndefined();
|
|
112
|
-
expect(exp.headers).toEqual({ "api-key": "secret-123", "x-tenant": "acme" });
|
|
113
|
-
expect(cfg.service.pipelines[signal].exporters).toEqual([`otlphttp/${signal}`]);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
it("routes per-signal endpoints to their own pipeline (no fallback-chain misrouting)", () => {
|
|
117
|
-
// The bug the reviewer flagged: separate traces/metrics endpoints must not
|
|
118
|
-
// both export to whichever one won a `||` chain.
|
|
119
|
-
const cfg = renderActive({
|
|
120
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.example.com/v1/traces",
|
|
121
|
-
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT: "https://metrics.example.com/v1/metrics",
|
|
122
|
-
});
|
|
123
|
-
// Signal-specific endpoints are used verbatim via `<signal>_endpoint`.
|
|
124
|
-
expect(cfg.exporters["otlphttp/traces"].traces_endpoint).toBe("https://traces.example.com/v1/traces");
|
|
125
|
-
expect(cfg.exporters["otlphttp/metrics"].metrics_endpoint).toBe("https://metrics.example.com/v1/metrics");
|
|
126
|
-
expect(cfg.service.pipelines.traces.exporters).toEqual(["otlphttp/traces"]);
|
|
127
|
-
expect(cfg.service.pipelines.metrics.exporters).toEqual(["otlphttp/metrics"]);
|
|
128
|
-
// logs had no endpoint configured → debug, not silently misrouted.
|
|
129
|
-
expect(cfg.service.pipelines.logs.exporters).toEqual(["debug"]);
|
|
130
|
-
expect(cfg.exporters.debug).toBeDefined();
|
|
131
|
-
});
|
|
132
|
-
it("honours a logs-only endpoint instead of dropping it (the silent-drop case)", () => {
|
|
133
|
-
const cfg = renderActive({
|
|
134
|
-
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: "https://logs.example.com/v1/logs",
|
|
135
|
-
});
|
|
136
|
-
expect(cfg.exporters["otlphttp/logs"].logs_endpoint).toBe("https://logs.example.com/v1/logs");
|
|
137
|
-
expect(cfg.service.pipelines.logs.exporters).toEqual(["otlphttp/logs"]);
|
|
138
|
-
expect(cfg.service.pipelines.traces.exporters).toEqual(["debug"]);
|
|
139
|
-
expect(cfg.service.pipelines.metrics.exporters).toEqual(["debug"]);
|
|
140
|
-
});
|
|
141
|
-
it("lets a signal-specific endpoint override the generic one for just that signal", () => {
|
|
142
|
-
const cfg = renderActive({
|
|
143
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://generic.example.com",
|
|
144
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.example.com/v1/traces",
|
|
145
|
-
});
|
|
146
|
-
// traces → verbatim specific URL; metrics/logs → generic base.
|
|
147
|
-
expect(cfg.exporters["otlphttp/traces"].traces_endpoint).toBe("https://traces.example.com/v1/traces");
|
|
148
|
-
expect(cfg.exporters["otlphttp/metrics"].endpoint).toBe("https://generic.example.com");
|
|
149
|
-
expect(cfg.exporters["otlphttp/logs"].endpoint).toBe("https://generic.example.com");
|
|
150
|
-
});
|
|
151
|
-
it("applies per-signal header overrides, falling back to generic headers", () => {
|
|
152
|
-
const cfg = renderActive({
|
|
153
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://generic.example.com",
|
|
154
|
-
OTEL_EXPORTER_OTLP_HEADERS: "authorization=generic",
|
|
155
|
-
OTEL_EXPORTER_OTLP_TRACES_HEADERS: "authorization=traces-only",
|
|
156
|
-
});
|
|
157
|
-
expect(cfg.exporters["otlphttp/traces"].headers.authorization).toBe("traces-only");
|
|
158
|
-
expect(cfg.exporters["otlphttp/metrics"].headers.authorization).toBe("generic");
|
|
159
|
-
});
|
|
160
|
-
it("supports a non-443 / custom-port HTTPS backend the strict SNI firewall could not reach", () => {
|
|
161
|
-
// This is the case the reviewer flagged for the old direct-forward path.
|
|
162
|
-
// It now works because the collector dials the backend on its trusted
|
|
163
|
-
// outbound leg, not through ssl_preread.
|
|
164
|
-
const cfg = renderActive({ OTEL_EXPORTER_OTLP_ENDPOINT: "https://otel.internal:4318" });
|
|
165
|
-
expect(cfg.exporters["otlphttp/traces"].endpoint).toBe("https://otel.internal:4318");
|
|
166
|
-
});
|
|
167
|
-
it("preserves '=' inside header values (bearer tokens survive intact)", () => {
|
|
168
|
-
const cfg = renderActive({
|
|
169
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://b.example.com",
|
|
170
|
-
OTEL_EXPORTER_OTLP_HEADERS: "authorization=Bearer abc=def==",
|
|
171
|
-
});
|
|
172
|
-
expect(cfg.exporters["otlphttp/traces"].headers.authorization).toBe("Bearer abc=def==");
|
|
173
|
-
});
|
|
174
|
-
it("falls back to a debug exporter (no data leaves) when active but no backend is configured", () => {
|
|
175
|
-
const cfg = renderActive({});
|
|
176
|
-
expect(cfg.exporters.debug).toBeDefined();
|
|
177
|
-
expect(cfg.exporters["otlphttp/traces"]).toBeUndefined();
|
|
178
|
-
for (const signal of ["traces", "metrics", "logs"]) {
|
|
179
|
-
expect(cfg.service.pipelines[signal].exporters).toEqual(["debug"]);
|
|
180
|
-
}
|
|
181
|
-
});
|
|
182
|
-
it("produces valid YAML even with quote/backslash-bearing header values", () => {
|
|
183
|
-
// Crucially, a single quote in a header value no longer fails anything:
|
|
184
|
-
// it lives in the host-side collector config, never a sandbox shell wrap.
|
|
185
|
-
const cfg = renderActive({
|
|
186
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://b.example.com",
|
|
187
|
-
OTEL_EXPORTER_OTLP_HEADERS: `x-quote=it's "quoted" \\ backslash`,
|
|
188
|
-
});
|
|
189
|
-
expect(cfg.exporters["otlphttp/traces"].headers["x-quote"]).toBe(`it's "quoted" \\ backslash`);
|
|
190
|
-
});
|
|
191
|
-
it("drops CR/LF-bearing header pairs (no header injection, no broken YAML) but keeps the rest", () => {
|
|
192
|
-
// A newline in a header value would be header injection on the
|
|
193
|
-
// collector→backend leg and would also break the YAML scalar.
|
|
194
|
-
const raw = renderOtelCollectorConfig({
|
|
195
|
-
active: true,
|
|
196
|
-
env: {
|
|
197
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://b.example.com",
|
|
198
|
-
OTEL_EXPORTER_OTLP_HEADERS: "x-good=ok,x-bad=line1\nline2",
|
|
199
|
-
},
|
|
200
|
-
});
|
|
201
|
-
expect(() => parseYaml(raw)).not.toThrow();
|
|
202
|
-
const cfg = parseYaml(raw);
|
|
203
|
-
expect(cfg.exporters["otlphttp/traces"].headers).toEqual({ "x-good": "ok" });
|
|
204
|
-
});
|
|
205
|
-
describe("forwarding gate (security floor)", () => {
|
|
206
|
-
it("when inactive, drops every signal to debug and emits no backend endpoint or credential", () => {
|
|
207
|
-
const raw = renderOtelCollectorConfig({
|
|
208
|
-
active: false,
|
|
209
|
-
env: {
|
|
210
|
-
OTEL_EXPORTER_OTLP_ENDPOINT: "https://real-backend.example.com",
|
|
211
|
-
OTEL_EXPORTER_OTLP_HEADERS: "authorization=super-secret-token",
|
|
212
|
-
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: "https://traces.example.com",
|
|
213
|
-
},
|
|
214
|
-
});
|
|
215
|
-
// No backend URL or secret leaks into the generated file at all.
|
|
216
|
-
expect(raw).not.toContain("real-backend.example.com");
|
|
217
|
-
expect(raw).not.toContain("super-secret-token");
|
|
218
|
-
expect(raw).not.toContain("traces.example.com");
|
|
219
|
-
expect(raw).not.toContain("otlphttp");
|
|
220
|
-
const cfg = parseYaml(raw);
|
|
221
|
-
expect(cfg.exporters.debug).toBeDefined();
|
|
222
|
-
for (const signal of ["traces", "metrics", "logs"]) {
|
|
223
|
-
expect(cfg.service.pipelines[signal].exporters).toEqual(["debug"]);
|
|
224
|
-
}
|
|
225
|
-
// Receiver still listens so sandboxes don't get connection errors.
|
|
226
|
-
expect(cfg.receivers.otlp.protocols.http.endpoint).toBe(`0.0.0.0:${OTEL_COLLECTOR_OTLP_HTTP_PORT}`);
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
describe("apex + subdomain match sanity", () => {
|
|
231
|
-
// Pin behaviour: a bare entry like "github.com" must match both
|
|
232
|
-
// the apex and any subdomain in both backends.
|
|
233
|
-
it("'github.com' generates an nginx leading-dot map entry", () => {
|
|
234
|
-
const conf = renderNginxStrictConf();
|
|
235
|
-
expect(conf).toMatch(/\s\.github\.com\s+\$ssl_preread_server_name:443;/);
|
|
236
|
-
});
|
|
237
|
-
it("'github.com' generates a CoreDNS pattern matching apex + subdomains", () => {
|
|
238
|
-
const conf = renderCorefileStrict();
|
|
239
|
-
// (^|\.)github\.com\.$ matches both "github.com." and "api.github.com."
|
|
240
|
-
// in FQDN-with-trailing-dot form.
|
|
241
|
-
expect(conf).toContain("(^|\\.)github\\.com\\.$");
|
|
242
|
-
});
|
|
243
|
-
});
|
|
244
|
-
//# sourceMappingURL=egress-firewall-config.test.js.map
|