@moondeck/station 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/meta/_journal.json +223 -0
- package/db/schema.ts +850 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1453 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +891 -0
- package/lib/env.ts +195 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +131 -4
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2583 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2899 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6349 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5545 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Task-local authenticated stream forwarder (ADR-153).
|
|
5
|
+
*
|
|
6
|
+
* The generation secret is read as exactly 32 raw bytes from stdin, converted
|
|
7
|
+
* to a KeyObject, and never placed in argv, the environment, a file or a
|
|
8
|
+
* network frame. The runner proves each connection with a fresh nonce/HMAC.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHmac, createSecretKey } from "node:crypto";
|
|
12
|
+
import { createConnection, createServer } from "node:net";
|
|
13
|
+
import { pathToFileURL } from "node:url";
|
|
14
|
+
|
|
15
|
+
const PREAMBLE_MAGIC = Buffer.from([
|
|
16
|
+
0x00, 0x55, 0x41, 0x49, 0x46, 0x57, 0x44, 0x01,
|
|
17
|
+
]);
|
|
18
|
+
const CHALLENGE_MAGIC = Buffer.from("UAICHL01", "ascii");
|
|
19
|
+
const RESPONSE_MAGIC = Buffer.from("UAIRES01", "ascii");
|
|
20
|
+
const ACCEPT_MAGIC = Buffer.from("UAIOK001", "ascii");
|
|
21
|
+
const HMAC_DOMAIN = Buffer.from("uai-task-forwarder-v1\0", "ascii");
|
|
22
|
+
const FIELD_SEPARATOR = Buffer.from([0]);
|
|
23
|
+
|
|
24
|
+
const SECRET_BYTES = 32;
|
|
25
|
+
const NONCE_BYTES = 32;
|
|
26
|
+
const FORWARDER_UID = 65_532;
|
|
27
|
+
const PREAUTH_TIMEOUT_MS = 3_000;
|
|
28
|
+
const SECRET_INPUT_TIMEOUT_MS = 5_000;
|
|
29
|
+
const MAX_PENDING_PREAUTH = 64;
|
|
30
|
+
const MAX_TASK_ID_BYTES = 128;
|
|
31
|
+
const MAX_GENERATION_BYTES = 64;
|
|
32
|
+
|
|
33
|
+
function fail(message) {
|
|
34
|
+
throw new Error(message);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parsePort(value, name) {
|
|
38
|
+
if (!/^\d+$/.test(value ?? "")) fail(`invalid ${name}`);
|
|
39
|
+
const parsed = Number(value);
|
|
40
|
+
if (!Number.isSafeInteger(parsed) || parsed < 1 || parsed > 65_535) {
|
|
41
|
+
fail(`invalid ${name}`);
|
|
42
|
+
}
|
|
43
|
+
return parsed;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function parseForwarderArgs(argv) {
|
|
47
|
+
const values = new Map();
|
|
48
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
49
|
+
const flag = argv[index];
|
|
50
|
+
const value = argv[index + 1];
|
|
51
|
+
if (!flag?.startsWith("--") || value === undefined || values.has(flag)) {
|
|
52
|
+
fail("invalid forwarder arguments");
|
|
53
|
+
}
|
|
54
|
+
values.set(flag, value);
|
|
55
|
+
}
|
|
56
|
+
const taskId = values.get("--task-id") ?? "";
|
|
57
|
+
const generation = values.get("--generation") ?? "";
|
|
58
|
+
const gatewayHost = values.get("--gateway-host") ?? "";
|
|
59
|
+
const allowed = new Set([
|
|
60
|
+
"--task-id",
|
|
61
|
+
"--generation",
|
|
62
|
+
"--gateway-host",
|
|
63
|
+
"--gateway-port",
|
|
64
|
+
"--listen-port",
|
|
65
|
+
]);
|
|
66
|
+
if ([...values.keys()].some((key) => !allowed.has(key))) {
|
|
67
|
+
fail("invalid forwarder arguments");
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
!/^[a-z0-9][a-z0-9_-]*$/.test(taskId) ||
|
|
71
|
+
Buffer.byteLength(taskId) > MAX_TASK_ID_BYTES ||
|
|
72
|
+
!/^[A-Za-z0-9_-]{16,64}$/.test(generation) ||
|
|
73
|
+
Buffer.byteLength(generation) > MAX_GENERATION_BYTES ||
|
|
74
|
+
gatewayHost.length < 1 ||
|
|
75
|
+
gatewayHost.length > 255 ||
|
|
76
|
+
/[\0\s]/.test(gatewayHost)
|
|
77
|
+
) {
|
|
78
|
+
fail("invalid forwarder arguments");
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
taskId,
|
|
82
|
+
generation,
|
|
83
|
+
gatewayHost,
|
|
84
|
+
gatewayPort: parsePort(values.get("--gateway-port") ?? "5877", "gateway port"),
|
|
85
|
+
listenPort: parsePort(values.get("--listen-port") ?? "5877", "listen port"),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function encodePreamble(taskId, generation) {
|
|
90
|
+
const task = Buffer.from(taskId, "utf8");
|
|
91
|
+
const generationBytes = Buffer.from(generation, "ascii");
|
|
92
|
+
return Buffer.concat([
|
|
93
|
+
PREAMBLE_MAGIC,
|
|
94
|
+
Buffer.from([task.length, generationBytes.length]),
|
|
95
|
+
task,
|
|
96
|
+
generationBytes,
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function responseMac(secret, taskId, generation, nonce) {
|
|
101
|
+
return createHmac("sha256", secret)
|
|
102
|
+
.update(HMAC_DOMAIN)
|
|
103
|
+
.update(taskId, "utf8")
|
|
104
|
+
.update(FIELD_SEPARATOR)
|
|
105
|
+
.update(generation, "ascii")
|
|
106
|
+
.update(FIELD_SEPARATOR)
|
|
107
|
+
.update(nonce)
|
|
108
|
+
.digest();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function readExact(socket, length, timeoutMs) {
|
|
112
|
+
return await new Promise((resolve, reject) => {
|
|
113
|
+
const chunks = [];
|
|
114
|
+
let received = 0;
|
|
115
|
+
let done = false;
|
|
116
|
+
const finish = (error, value) => {
|
|
117
|
+
if (done) return;
|
|
118
|
+
done = true;
|
|
119
|
+
clearTimeout(timer);
|
|
120
|
+
socket.pause();
|
|
121
|
+
socket.off("data", onData);
|
|
122
|
+
socket.off("error", onError);
|
|
123
|
+
socket.off("end", onEnd);
|
|
124
|
+
socket.off("close", onClose);
|
|
125
|
+
if (error) reject(error);
|
|
126
|
+
else resolve(value);
|
|
127
|
+
};
|
|
128
|
+
const onData = (chunk) => {
|
|
129
|
+
received += chunk.length;
|
|
130
|
+
if (received > length) {
|
|
131
|
+
finish(new Error("oversized forwarder handshake frame"));
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
chunks.push(chunk);
|
|
135
|
+
if (received === length) finish(null, Buffer.concat(chunks, length));
|
|
136
|
+
};
|
|
137
|
+
const onError = () => finish(new Error("forwarder handshake socket error"));
|
|
138
|
+
const onEnd = () => finish(new Error("truncated forwarder handshake frame"));
|
|
139
|
+
const onClose = () => finish(new Error("forwarder handshake socket closed"));
|
|
140
|
+
const timer = setTimeout(
|
|
141
|
+
() => finish(new Error("forwarder handshake timed out")),
|
|
142
|
+
timeoutMs,
|
|
143
|
+
);
|
|
144
|
+
timer.unref();
|
|
145
|
+
socket.on("data", onData);
|
|
146
|
+
socket.once("error", onError);
|
|
147
|
+
socket.once("end", onEnd);
|
|
148
|
+
socket.once("close", onClose);
|
|
149
|
+
socket.resume();
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function readSecret(input) {
|
|
154
|
+
const chunks = [];
|
|
155
|
+
let size = 0;
|
|
156
|
+
const timeout = setTimeout(() => input.destroy(), SECRET_INPUT_TIMEOUT_MS);
|
|
157
|
+
timeout.unref();
|
|
158
|
+
try {
|
|
159
|
+
for await (const chunk of input) {
|
|
160
|
+
const bytes = Buffer.from(chunk);
|
|
161
|
+
size += bytes.length;
|
|
162
|
+
if (size > SECRET_BYTES) fail("invalid forwarder secret input");
|
|
163
|
+
chunks.push(bytes);
|
|
164
|
+
}
|
|
165
|
+
} finally {
|
|
166
|
+
clearTimeout(timeout);
|
|
167
|
+
}
|
|
168
|
+
if (size !== SECRET_BYTES) fail("invalid forwarder secret input");
|
|
169
|
+
return Buffer.concat(chunks, SECRET_BYTES);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function createTaskForwarder(options, secret, dependencies = {}) {
|
|
173
|
+
const identityPreamble = encodePreamble(options.taskId, options.generation);
|
|
174
|
+
const connectUpstream = dependencies.createConnection ?? createConnection;
|
|
175
|
+
const activeSockets = new Set();
|
|
176
|
+
let pendingPreauth = 0;
|
|
177
|
+
|
|
178
|
+
const server = createServer({ pauseOnConnect: true }, (local) => {
|
|
179
|
+
activeSockets.add(local);
|
|
180
|
+
local.once("close", () => activeSockets.delete(local));
|
|
181
|
+
if (pendingPreauth >= MAX_PENDING_PREAUTH) {
|
|
182
|
+
local.destroy();
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
pendingPreauth += 1;
|
|
186
|
+
const upstream = connectUpstream({
|
|
187
|
+
host: options.gatewayHost,
|
|
188
|
+
port: options.gatewayPort,
|
|
189
|
+
});
|
|
190
|
+
const preauthTimer = setTimeout(() => {
|
|
191
|
+
local.destroy();
|
|
192
|
+
upstream.destroy();
|
|
193
|
+
}, PREAUTH_TIMEOUT_MS);
|
|
194
|
+
preauthTimer.unref();
|
|
195
|
+
activeSockets.add(upstream);
|
|
196
|
+
upstream.setNoDelay(true);
|
|
197
|
+
upstream.once("close", () => activeSockets.delete(upstream));
|
|
198
|
+
local.once("error", () => upstream.destroy());
|
|
199
|
+
local.once("close", () => upstream.destroy());
|
|
200
|
+
upstream.once("error", () => local.destroy());
|
|
201
|
+
|
|
202
|
+
void (async () => {
|
|
203
|
+
try {
|
|
204
|
+
await new Promise((resolve, reject) => {
|
|
205
|
+
let settled = false;
|
|
206
|
+
const finish = (error) => {
|
|
207
|
+
if (settled) return;
|
|
208
|
+
settled = true;
|
|
209
|
+
upstream.off("connect", onConnect);
|
|
210
|
+
upstream.off("error", onError);
|
|
211
|
+
upstream.off("close", onClose);
|
|
212
|
+
if (error) reject(error);
|
|
213
|
+
else resolve();
|
|
214
|
+
};
|
|
215
|
+
const onConnect = () => finish(null);
|
|
216
|
+
const onError = () => finish(new Error("forwarder upstream failed"));
|
|
217
|
+
const onClose = () => finish(new Error("forwarder upstream closed"));
|
|
218
|
+
upstream.once("connect", onConnect);
|
|
219
|
+
upstream.once("error", onError);
|
|
220
|
+
upstream.once("close", onClose);
|
|
221
|
+
if (upstream.readyState === "open") finish(null);
|
|
222
|
+
else if (upstream.destroyed) onClose();
|
|
223
|
+
});
|
|
224
|
+
upstream.write(identityPreamble);
|
|
225
|
+
const challenge = await readExact(
|
|
226
|
+
upstream,
|
|
227
|
+
CHALLENGE_MAGIC.length + NONCE_BYTES,
|
|
228
|
+
PREAUTH_TIMEOUT_MS,
|
|
229
|
+
);
|
|
230
|
+
if (!challenge.subarray(0, CHALLENGE_MAGIC.length).equals(CHALLENGE_MAGIC)) {
|
|
231
|
+
fail("invalid forwarder challenge");
|
|
232
|
+
}
|
|
233
|
+
const nonce = challenge.subarray(CHALLENGE_MAGIC.length);
|
|
234
|
+
const mac = responseMac(
|
|
235
|
+
secret,
|
|
236
|
+
options.taskId,
|
|
237
|
+
options.generation,
|
|
238
|
+
nonce,
|
|
239
|
+
);
|
|
240
|
+
upstream.write(Buffer.concat([RESPONSE_MAGIC, mac]));
|
|
241
|
+
const accepted = await readExact(
|
|
242
|
+
upstream,
|
|
243
|
+
ACCEPT_MAGIC.length,
|
|
244
|
+
PREAUTH_TIMEOUT_MS,
|
|
245
|
+
);
|
|
246
|
+
if (!accepted.equals(ACCEPT_MAGIC)) fail("forwarder authentication refused");
|
|
247
|
+
local.pipe(upstream);
|
|
248
|
+
upstream.pipe(local);
|
|
249
|
+
local.resume();
|
|
250
|
+
upstream.resume();
|
|
251
|
+
} catch {
|
|
252
|
+
local.destroy();
|
|
253
|
+
upstream.destroy();
|
|
254
|
+
} finally {
|
|
255
|
+
clearTimeout(preauthTimer);
|
|
256
|
+
pendingPreauth -= 1;
|
|
257
|
+
}
|
|
258
|
+
})();
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
server.shutdown = () => {
|
|
262
|
+
for (const socket of activeSockets) socket.destroy();
|
|
263
|
+
activeSockets.clear();
|
|
264
|
+
server.close();
|
|
265
|
+
};
|
|
266
|
+
return server;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async function main() {
|
|
270
|
+
// The launcher additionally applies no_new_privs and clears capabilities.
|
|
271
|
+
// Refuse a wiring regression that runs this secret holder as root or node.
|
|
272
|
+
if (
|
|
273
|
+
process.getuid?.() !== FORWARDER_UID ||
|
|
274
|
+
process.getgid?.() !== FORWARDER_UID
|
|
275
|
+
) {
|
|
276
|
+
fail(`task forwarder must run as uid/gid ${FORWARDER_UID}`);
|
|
277
|
+
}
|
|
278
|
+
const options = parseForwarderArgs(process.argv.slice(2));
|
|
279
|
+
const rawSecret = await readSecret(process.stdin);
|
|
280
|
+
const secret = createSecretKey(rawSecret);
|
|
281
|
+
rawSecret.fill(0);
|
|
282
|
+
const server = createTaskForwarder(options, secret);
|
|
283
|
+
server.once("error", () => {
|
|
284
|
+
process.stderr.write("uai task forwarder could not start\n");
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
});
|
|
287
|
+
server.listen(options.listenPort, "127.0.0.1", () => {
|
|
288
|
+
process.stdout.write("READY\n");
|
|
289
|
+
});
|
|
290
|
+
const stop = () => server.shutdown();
|
|
291
|
+
process.once("SIGINT", stop);
|
|
292
|
+
process.once("SIGTERM", stop);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (
|
|
296
|
+
process.argv[1] &&
|
|
297
|
+
import.meta.url === pathToFileURL(process.argv[1]).href
|
|
298
|
+
) {
|
|
299
|
+
main().catch(() => {
|
|
300
|
+
process.stderr.write("uai task forwarder failed\n");
|
|
301
|
+
process.exitCode = 1;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Dana (designer) — skills
|
|
2
|
+
|
|
3
|
+
An index of the reference material prepared for you. Each document is its own
|
|
4
|
+
file in `skills/` beside this one. Before work a skill covers, read its
|
|
5
|
+
file in full, for example with `cat`.
|
|
6
|
+
|
|
7
|
+
- **Brand voice** — covers: How we sound in product copy and marketing. → `skills/brand-voice.md`
|
|
8
|
+
- **Design tokens** — covers: The colour, type and spacing scales every screen uses. → `skills/design-tokens.md`
|
|
9
|
+
- **Accessibility checklist** — covers: Check every screen against these before calling it done. → `skills/accessibility-checklist.md`
|
|
10
|
+
- **Figma library**: Link: https://figma.example.test/library
|