@legioncodeinc/hive 0.2.0 → 0.3.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.
Files changed (108) hide show
  1. package/README.md +2 -2
  2. package/dist/daemon/dashboard/app.js +26 -25
  3. package/dist/daemon/dashboard/host.d.ts +7 -0
  4. package/dist/daemon/dashboard/host.js +16 -0
  5. package/dist/daemon/dashboard/host.js.map +1 -1
  6. package/dist/daemon/dashboard/web-assets.d.ts +2 -0
  7. package/dist/daemon/dashboard/web-assets.js +19 -0
  8. package/dist/daemon/dashboard/web-assets.js.map +1 -1
  9. package/dist/daemon/gate.d.ts +2 -2
  10. package/dist/daemon/gate.js +15 -4
  11. package/dist/daemon/gate.js.map +1 -1
  12. package/dist/daemon/installer/bin-resolver.d.ts +34 -0
  13. package/dist/daemon/installer/bin-resolver.js +86 -0
  14. package/dist/daemon/installer/bin-resolver.js.map +1 -0
  15. package/dist/daemon/installer/config.d.ts +63 -0
  16. package/dist/daemon/installer/config.js +74 -0
  17. package/dist/daemon/installer/config.js.map +1 -0
  18. package/dist/daemon/installer/detection.d.ts +20 -0
  19. package/dist/daemon/installer/detection.js +73 -0
  20. package/dist/daemon/installer/detection.js.map +1 -0
  21. package/dist/daemon/installer/funnel-telemetry.d.ts +54 -0
  22. package/dist/daemon/installer/funnel-telemetry.js +134 -0
  23. package/dist/daemon/installer/funnel-telemetry.js.map +1 -0
  24. package/dist/daemon/installer/index.d.ts +12 -0
  25. package/dist/daemon/installer/index.js +10 -0
  26. package/dist/daemon/installer/index.js.map +1 -0
  27. package/dist/daemon/installer/install-state.d.ts +50 -0
  28. package/dist/daemon/installer/install-state.js +142 -0
  29. package/dist/daemon/installer/install-state.js.map +1 -0
  30. package/dist/daemon/installer/manifest-snapshot.json +25 -0
  31. package/dist/daemon/installer/manifest.d.ts +47 -0
  32. package/dist/daemon/installer/manifest.js +103 -0
  33. package/dist/daemon/installer/manifest.js.map +1 -0
  34. package/dist/daemon/installer/products.d.ts +33 -0
  35. package/dist/daemon/installer/products.js +42 -0
  36. package/dist/daemon/installer/products.js.map +1 -0
  37. package/dist/daemon/installer/routes.d.ts +43 -0
  38. package/dist/daemon/installer/routes.js +201 -0
  39. package/dist/daemon/installer/routes.js.map +1 -0
  40. package/dist/daemon/installer/security.d.ts +33 -0
  41. package/dist/daemon/installer/security.js +80 -0
  42. package/dist/daemon/installer/security.js.map +1 -0
  43. package/dist/daemon/installer/spawn.d.ts +48 -0
  44. package/dist/daemon/installer/spawn.js +51 -0
  45. package/dist/daemon/installer/spawn.js.map +1 -0
  46. package/dist/daemon/installer/token.d.ts +23 -0
  47. package/dist/daemon/installer/token.js +56 -0
  48. package/dist/daemon/installer/token.js.map +1 -0
  49. package/dist/daemon/server.d.ts +6 -0
  50. package/dist/daemon/server.js +7 -0
  51. package/dist/daemon/server.js.map +1 -1
  52. package/dist/dashboard/web/app.js +42 -20
  53. package/dist/dashboard/web/app.js.map +1 -1
  54. package/dist/dashboard/web/boot-route.d.ts +11 -7
  55. package/dist/dashboard/web/boot-route.js +12 -6
  56. package/dist/dashboard/web/boot-route.js.map +1 -1
  57. package/dist/dashboard/web/main.js +2 -1
  58. package/dist/dashboard/web/main.js.map +1 -1
  59. package/dist/dashboard/web/onboarding/advanced-picker.d.ts +16 -0
  60. package/dist/dashboard/web/onboarding/advanced-picker.js +59 -0
  61. package/dist/dashboard/web/onboarding/advanced-picker.js.map +1 -0
  62. package/dist/dashboard/web/onboarding/contracts.d.ts +188 -0
  63. package/dist/dashboard/web/onboarding/contracts.js +161 -0
  64. package/dist/dashboard/web/onboarding/contracts.js.map +1 -0
  65. package/dist/dashboard/web/onboarding/health-view.d.ts +17 -0
  66. package/dist/dashboard/web/onboarding/health-view.js +79 -0
  67. package/dist/dashboard/web/onboarding/health-view.js.map +1 -0
  68. package/dist/dashboard/web/onboarding/install-card.d.ts +27 -0
  69. package/dist/dashboard/web/onboarding/install-card.js +170 -0
  70. package/dist/dashboard/web/onboarding/install-card.js.map +1 -0
  71. package/dist/dashboard/web/onboarding/login-step.d.ts +29 -0
  72. package/dist/dashboard/web/onboarding/login-step.js +104 -0
  73. package/dist/dashboard/web/onboarding/login-step.js.map +1 -0
  74. package/dist/dashboard/web/onboarding/onboarding-client.d.ts +52 -0
  75. package/dist/dashboard/web/onboarding/onboarding-client.js +133 -0
  76. package/dist/dashboard/web/onboarding/onboarding-client.js.map +1 -0
  77. package/dist/dashboard/web/onboarding/onboarding-hero.d.ts +24 -0
  78. package/dist/dashboard/web/onboarding/onboarding-hero.js +70 -0
  79. package/dist/dashboard/web/onboarding/onboarding-hero.js.map +1 -0
  80. package/dist/dashboard/web/onboarding/onboarding-screen.d.ts +43 -0
  81. package/dist/dashboard/web/onboarding/onboarding-screen.js +161 -0
  82. package/dist/dashboard/web/onboarding/onboarding-screen.js.map +1 -0
  83. package/dist/dashboard/web/onboarding/onboarding-selection-store.d.ts +20 -0
  84. package/dist/dashboard/web/onboarding/onboarding-selection-store.js +76 -0
  85. package/dist/dashboard/web/onboarding/onboarding-selection-store.js.map +1 -0
  86. package/dist/dashboard/web/onboarding/product-copy.d.ts +40 -0
  87. package/dist/dashboard/web/onboarding/product-copy.js +70 -0
  88. package/dist/dashboard/web/onboarding/product-copy.js.map +1 -0
  89. package/dist/dashboard/web/onboarding/use-install-dwell.d.ts +22 -0
  90. package/dist/dashboard/web/onboarding/use-install-dwell.js +37 -0
  91. package/dist/dashboard/web/onboarding/use-install-dwell.js.map +1 -0
  92. package/dist/dashboard/web/onboarding/use-onboarding-token.d.ts +9 -0
  93. package/dist/dashboard/web/onboarding/use-onboarding-token.js +34 -0
  94. package/dist/dashboard/web/onboarding/use-onboarding-token.js.map +1 -0
  95. package/dist/dashboard/web/route-daemon-owner.d.ts +7 -0
  96. package/dist/dashboard/web/route-daemon-owner.js +15 -0
  97. package/dist/dashboard/web/route-daemon-owner.js.map +1 -0
  98. package/dist/dashboard/web/wire.d.ts +1 -1
  99. package/dist/shared/onboarding-types.d.ts +79 -0
  100. package/dist/shared/onboarding-types.js +12 -0
  101. package/dist/shared/onboarding-types.js.map +1 -0
  102. package/dist/telemetry/emit.d.ts +30 -3
  103. package/dist/telemetry/emit.js +25 -2
  104. package/dist/telemetry/emit.js.map +1 -1
  105. package/dist/telemetry/onboarding-session-ledger.d.ts +31 -0
  106. package/dist/telemetry/onboarding-session-ledger.js +78 -0
  107. package/dist/telemetry/onboarding-session-ledger.js.map +1 -0
  108. package/package.json +1 -1
@@ -0,0 +1,43 @@
1
+ /**
2
+ * PRD-009a: the `/api/onboarding/*` installer surface and its service factory.
3
+ *
4
+ * These Hono routes are registered on hive's app BEFORE the generic `/api/*` BFF proxy (the same
5
+ * registration-order discipline as `/api/fleet-status` and `/api/telemetry/stream`), so hive itself
6
+ * answers them rather than proxying to a workload daemon. Every route runs the three-check guard
7
+ * (`security.ts`): Host, Origin, and the one-time token. Manifest resolution, detection, the install
8
+ * state machine, and the token store are all reached through the injectable config, so a test never
9
+ * hits the network, real npm, or the real filesystem.
10
+ */
11
+ import type { Hono } from "hono";
12
+ import type { EmitDeps } from "../../telemetry/emit.js";
13
+ import { type InstallStateStore } from "./install-state.js";
14
+ import { type InstallerConfig } from "./config.js";
15
+ import { type ManifestResolver } from "./manifest.js";
16
+ import { type TokenStore } from "./token.js";
17
+ import { type FunnelTelemetry } from "./funnel-telemetry.js";
18
+ import { type SetupAuthFetchImpl } from "../setup-auth.js";
19
+ import { type FetchImpl as FleetFetchImpl } from "../fleet-status.js";
20
+ /** Service options: the installer config seams plus the fleet-status inputs the health check reuses. */
21
+ export interface InstallerServiceOptions extends Partial<InstallerConfig> {
22
+ /** The fetch used by the health check's `fetchFleetStatus` (defaults to the global `fetch`). */
23
+ readonly fleetStatusFetch?: FleetFetchImpl;
24
+ /** Override doctor's status URL for the health check (defaults to the fixed loopback constant). */
25
+ readonly doctorStatusUrl?: string;
26
+ /** Fetch seam for `/setup/state` auth observation (`login_completed`, PRD-009c). */
27
+ readonly setupAuthFetch?: SetupAuthFetchImpl;
28
+ /** Injectable telemetry deps for funnel emission (tests record POST bodies). */
29
+ readonly funnelEmitDeps?: EmitDeps;
30
+ /** Override onboarding session ledger dir (tests). */
31
+ readonly funnelStateDir?: string;
32
+ }
33
+ /** The assembled installer service: a route registrar plus its state (exposed for tests). */
34
+ export interface InstallerService {
35
+ register(app: Hono): void;
36
+ readonly config: InstallerConfig;
37
+ readonly store: InstallStateStore;
38
+ readonly tokenStore: TokenStore;
39
+ readonly manifest: ManifestResolver;
40
+ readonly funnel: FunnelTelemetry;
41
+ }
42
+ /** Build the installer service with a memoized npm-prefix resolver over the injected seams. */
43
+ export declare function createInstallerService(options?: InstallerServiceOptions): InstallerService;
@@ -0,0 +1,201 @@
1
+ /**
2
+ * PRD-009a: the `/api/onboarding/*` installer surface and its service factory.
3
+ *
4
+ * These Hono routes are registered on hive's app BEFORE the generic `/api/*` BFF proxy (the same
5
+ * registration-order discipline as `/api/fleet-status` and `/api/telemetry/stream`), so hive itself
6
+ * answers them rather than proxying to a workload daemon. Every route runs the three-check guard
7
+ * (`security.ts`): Host, Origin, and the one-time token. Manifest resolution, detection, the install
8
+ * state machine, and the token store are all reached through the injectable config, so a test never
9
+ * hits the network, real npm, or the real filesystem.
10
+ */
11
+ import { z } from "zod";
12
+ import { DOCTOR_STATUS_URL } from "../../shared/constants.js";
13
+ import { createInstallStateStore } from "./install-state.js";
14
+ import { createInstallerConfig } from "./config.js";
15
+ import { createManifestResolver } from "./manifest.js";
16
+ import { createTokenStore } from "./token.js";
17
+ import { detectFleet, installedVersion } from "./detection.js";
18
+ import { isInstallableProduct } from "./products.js";
19
+ import { resolveNpmPrefixViaCli } from "./bin-resolver.js";
20
+ import { guardInstallerRequest } from "./security.js";
21
+ import { createFunnelTelemetry, OnboardingEventBodySchema } from "./funnel-telemetry.js";
22
+ import { fetchSetupAuthenticated } from "../setup-auth.js";
23
+ import { fetchFleetStatus, isFleetReady } from "../fleet-status.js";
24
+ const InstallBodySchema = z.object({ product: z.string() });
25
+ function jsonError(c, status, error) {
26
+ return c.json({ error }, status);
27
+ }
28
+ async function readJsonBody(c) {
29
+ try {
30
+ return await c.req.json();
31
+ }
32
+ catch {
33
+ return undefined;
34
+ }
35
+ }
36
+ /** Build the installer service with a memoized npm-prefix resolver over the injected seams. */
37
+ export function createInstallerService(options = {}) {
38
+ const base = createInstallerConfig(options);
39
+ // Memoize `npm prefix -g` so it runs at most once per daemon session (is-AC: prefix cached).
40
+ let prefixPromise = null;
41
+ const config = {
42
+ ...base,
43
+ resolveNpmPrefix: () => {
44
+ if (prefixPromise === null) {
45
+ prefixPromise = options.resolveNpmPrefix ? options.resolveNpmPrefix() : resolveNpmPrefixViaCli(config);
46
+ }
47
+ return prefixPromise;
48
+ }
49
+ };
50
+ const funnel = createFunnelTelemetry({
51
+ config,
52
+ emitDeps: options.funnelEmitDeps,
53
+ stateDir: options.funnelStateDir
54
+ });
55
+ const store = createInstallStateStore(config, {
56
+ onInstallStarted: (product) => funnel.recordProductInstallStarted(product),
57
+ onInstallCompleted: (product) => funnel.recordProductInstallCompleted(product),
58
+ onInstallFailed: (product, stage) => funnel.recordProductInstallFailed(product, stage)
59
+ });
60
+ const tokenStore = createTokenStore(config);
61
+ const manifest = createManifestResolver(config);
62
+ const fleetStatusFetch = options.fleetStatusFetch ?? fetch;
63
+ const setupAuthFetch = options.setupAuthFetch ?? fetch;
64
+ const doctorStatusUrl = options.doctorStatusUrl ?? DOCTOR_STATUS_URL;
65
+ const register = (app) => {
66
+ // 1) Detection (is-AC-1/2). Token required only while a session is active (is-AC-10 carve-out).
67
+ app.get("/api/onboarding/detect", async (c) => {
68
+ const rejection = guardInstallerRequest(c, tokenStore, "detect");
69
+ if (rejection !== null)
70
+ return rejection;
71
+ return c.json(await detectFleet(config, store));
72
+ });
73
+ // 2) Install start (is-AC-3/4/5/15/16). Server resolves the target; the request carries only a slug.
74
+ app.post("/api/onboarding/install", async (c) => {
75
+ const rejection = guardInstallerRequest(c, tokenStore, "always");
76
+ if (rejection !== null)
77
+ return rejection;
78
+ const body = await readJsonBody(c);
79
+ const parsed = InstallBodySchema.safeParse(body);
80
+ if (!parsed.success)
81
+ return jsonError(c, 400, "invalid_body");
82
+ // is-AC-3: only the four slugs; `hive` is not installable. Either way a 400 with no spawn.
83
+ const product = parsed.data.product;
84
+ if (!isInstallableProduct(product))
85
+ return jsonError(c, 400, "invalid_product");
86
+ // is-AC-4/5: resolve `packageName@version` server-side, refusing rather than falling to @latest.
87
+ const resolution = await manifest.resolve(product);
88
+ if (resolution.kind === "unpublished")
89
+ return jsonError(c, 409, "unpublished");
90
+ if (resolution.kind === "manifest_unresolved")
91
+ return jsonError(c, 409, "manifest_unresolved");
92
+ // is-AC-15: already installed at the pinned version -> short-circuit, never spawn npm.
93
+ const snapshot = store.detectState(product);
94
+ const detectedVersion = await installedVersion(config, product);
95
+ if (snapshot.status === "installed" || detectedVersion === resolution.version) {
96
+ return c.json({ product, state: "installed" }, 200);
97
+ }
98
+ // is-AC-16: begin (or attach to an in-flight install); either way the wire state is in_progress.
99
+ store.begin(product, {
100
+ packageName: resolution.packageName,
101
+ version: resolution.version,
102
+ target: resolution.target
103
+ });
104
+ return c.json({ product, state: "install_in_progress" }, 202);
105
+ });
106
+ // 3) SSE progress (is-AC-11/12/14). Mirrors the telemetry-proxy relay discipline: a streamed
107
+ // body, no buffering beyond the current event, tied to the request's abort signal.
108
+ app.get("/api/onboarding/install/:product/events", (c) => {
109
+ const rejection = guardInstallerRequest(c, tokenStore, "always");
110
+ if (rejection !== null)
111
+ return rejection;
112
+ const product = c.req.param("product");
113
+ if (!isInstallableProduct(product))
114
+ return jsonError(c, 400, "invalid_product");
115
+ const signal = c.req.raw.signal;
116
+ const stream = new ReadableStream({
117
+ start(controller) {
118
+ const encoder = new TextEncoder();
119
+ let closed = false;
120
+ const subscriber = {
121
+ send(event) {
122
+ if (closed)
123
+ return;
124
+ try {
125
+ controller.enqueue(encoder.encode(`data: ${JSON.stringify(event)}\n\n`));
126
+ }
127
+ catch {
128
+ closed = true;
129
+ }
130
+ },
131
+ close() {
132
+ if (closed)
133
+ return;
134
+ closed = true;
135
+ try {
136
+ controller.close();
137
+ }
138
+ catch {
139
+ // The stream may already be closed by a client disconnect; nothing to do.
140
+ }
141
+ }
142
+ };
143
+ const unsubscribe = store.subscribe(product, subscriber);
144
+ const onAbort = () => {
145
+ unsubscribe();
146
+ subscriber.close();
147
+ };
148
+ // is-AC-14: a client disconnect removes this subscriber but the install continues.
149
+ if (signal.aborted)
150
+ onAbort();
151
+ else
152
+ signal.addEventListener("abort", onAbort, { once: true });
153
+ }
154
+ });
155
+ return new Response(stream, {
156
+ status: 200,
157
+ headers: {
158
+ "content-type": "text/event-stream",
159
+ "cache-control": "no-store",
160
+ connection: "keep-alive"
161
+ }
162
+ });
163
+ });
164
+ // 4) Health check (is-AC-18): reuse the existing readiness projection, do not re-derive it.
165
+ app.get("/api/onboarding/health", async (c) => {
166
+ const rejection = guardInstallerRequest(c, tokenStore, "always");
167
+ if (rejection !== null)
168
+ return rejection;
169
+ const status = await fetchFleetStatus(fleetStatusFetch, doctorStatusUrl);
170
+ const ready = isFleetReady(status);
171
+ funnel.observeHealthReady(ready);
172
+ const authenticated = await fetchSetupAuthenticated(setupAuthFetch, { signal: c.req.raw.signal });
173
+ funnel.observeAuthenticated(authenticated);
174
+ return c.json({ ready, status });
175
+ });
176
+ // 5) Completion (is-AC-10): invalidate the token (delete the file + set the memory flag), 204.
177
+ app.post("/api/onboarding/complete", (c) => {
178
+ const rejection = guardInstallerRequest(c, tokenStore, "always");
179
+ if (rejection !== null)
180
+ return rejection;
181
+ tokenStore.invalidate();
182
+ return c.body(null, 204);
183
+ });
184
+ // 6) Funnel events (PRD-009c): validate token + closed UI event set, emit through chokepoint.
185
+ app.post("/api/onboarding/event", async (c) => {
186
+ const rejection = guardInstallerRequest(c, tokenStore, "always");
187
+ if (rejection !== null)
188
+ return rejection;
189
+ const body = await readJsonBody(c);
190
+ const parsed = OnboardingEventBodySchema.safeParse(body);
191
+ if (!parsed.success)
192
+ return jsonError(c, 400, "invalid_body");
193
+ funnel.recordUiEvent(parsed.data);
194
+ const authenticated = await fetchSetupAuthenticated(setupAuthFetch, { signal: c.req.raw.signal });
195
+ funnel.observeAuthenticated(authenticated);
196
+ return c.body(null, 202);
197
+ });
198
+ };
199
+ return { register, config, store, tokenStore, manifest, funnel };
200
+ }
201
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../../../src/daemon/installer/routes.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * PRD-009a: the three non-negotiable installer mitigations (is-AC-7/8/9/10).
3
+ *
4
+ * A loopback endpoint that shells out to `npm install` is a drive-by target: any page the operator
5
+ * visits can `fetch` or form-POST at `127.0.0.1:3853`. Every installer request therefore passes:
6
+ * - HOST validation (is-AC-8, DNS-rebinding defense): a rebound hostname that resolves to
7
+ * 127.0.0.1 still fails because its `Host` header is not the portal's own host.
8
+ * - ORIGIN validation (is-AC-7): a foreign Origin is rejected 403; a missing Origin on a
9
+ * state-changing (non-GET) request is rejected too.
10
+ * - TOKEN validation (is-AC-9): the one-time bootstrap token, constant-time compared. State-
11
+ * changing endpoints always require it; read-only detection requires it only while a session is
12
+ * active, staying available token-free after completion for the re-entry short-circuit (is-AC-10).
13
+ */
14
+ import type { Context } from "hono";
15
+ import type { TokenStore } from "./token.js";
16
+ /** The request header carrying the onboarding token (the SSE path uses the `t` query param instead). */
17
+ export declare const TOKEN_HEADER: "x-onboarding-token";
18
+ /** Whether detection-style read endpoints require the token unconditionally or only while active. */
19
+ export type TokenMode = "always" | "detect";
20
+ /** True iff the `Host` header is the portal's own host (is-AC-8). */
21
+ export declare function hostAllowed(host: string | undefined): boolean;
22
+ /**
23
+ * Origin policy (is-AC-7): a present Origin must be the portal's own; a missing Origin is allowed
24
+ * only on a safe (GET) request and rejected on any state-changing method.
25
+ */
26
+ export declare function originAllowed(method: string, origin: string | undefined): boolean;
27
+ /** Read the presented token from the header, falling back to the `t` query param only for EventSource. */
28
+ export declare function extractToken(c: Context): string | null;
29
+ /**
30
+ * Run the full guard for an installer request. Returns a rejection {@link Response} to short-circuit
31
+ * the handler, or `null` when the request passed all three checks. Never logs or echoes the token.
32
+ */
33
+ export declare function guardInstallerRequest(c: Context, tokenStore: TokenStore, tokenMode: TokenMode): Response | null;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * PRD-009a: the three non-negotiable installer mitigations (is-AC-7/8/9/10).
3
+ *
4
+ * A loopback endpoint that shells out to `npm install` is a drive-by target: any page the operator
5
+ * visits can `fetch` or form-POST at `127.0.0.1:3853`. Every installer request therefore passes:
6
+ * - HOST validation (is-AC-8, DNS-rebinding defense): a rebound hostname that resolves to
7
+ * 127.0.0.1 still fails because its `Host` header is not the portal's own host.
8
+ * - ORIGIN validation (is-AC-7): a foreign Origin is rejected 403; a missing Origin on a
9
+ * state-changing (non-GET) request is rejected too.
10
+ * - TOKEN validation (is-AC-9): the one-time bootstrap token, constant-time compared. State-
11
+ * changing endpoints always require it; read-only detection requires it only while a session is
12
+ * active, staying available token-free after completion for the re-entry short-circuit (is-AC-10).
13
+ */
14
+ import { HIVE_HOST, HIVE_PORT } from "../../shared/constants.js";
15
+ /** The portal's own hosts (is-AC-8). Only these `Host` header values are accepted. */
16
+ const ALLOWED_HOSTS = new Set([`${HIVE_HOST}:${HIVE_PORT}`, `localhost:${HIVE_PORT}`]);
17
+ /** The portal's own origins (is-AC-7). Only these `Origin` header values are accepted. */
18
+ const ALLOWED_ORIGINS = new Set([`http://${HIVE_HOST}:${HIVE_PORT}`, `http://localhost:${HIVE_PORT}`]);
19
+ /** The request header carrying the onboarding token (the SSE path uses the `t` query param instead). */
20
+ export const TOKEN_HEADER = "x-onboarding-token";
21
+ /** True iff the `Host` header is the portal's own host (is-AC-8). */
22
+ export function hostAllowed(host) {
23
+ return host !== undefined && ALLOWED_HOSTS.has(host);
24
+ }
25
+ /**
26
+ * Origin policy (is-AC-7): a present Origin must be the portal's own; a missing Origin is allowed
27
+ * only on a safe (GET) request and rejected on any state-changing method.
28
+ */
29
+ export function originAllowed(method, origin) {
30
+ if (origin === undefined)
31
+ return method === "GET" || method === "HEAD";
32
+ return ALLOWED_ORIGINS.has(origin);
33
+ }
34
+ function forbidden() {
35
+ return new Response(JSON.stringify({ error: "forbidden" }), {
36
+ status: 403,
37
+ headers: { "content-type": "application/json" }
38
+ });
39
+ }
40
+ function unauthorized() {
41
+ return new Response(JSON.stringify({ error: "unauthorized" }), {
42
+ status: 401,
43
+ headers: { "content-type": "application/json" }
44
+ });
45
+ }
46
+ function allowsQueryToken(c) {
47
+ const path = new URL(c.req.url).pathname;
48
+ return c.req.method === "GET" && /^\/api\/onboarding\/install\/[^/]+\/events$/.test(path);
49
+ }
50
+ /** Read the presented token from the header, falling back to the `t` query param only for EventSource. */
51
+ export function extractToken(c) {
52
+ const header = c.req.header(TOKEN_HEADER);
53
+ if (header !== undefined && header.length > 0)
54
+ return header;
55
+ if (!allowsQueryToken(c))
56
+ return null;
57
+ const query = c.req.query("t");
58
+ return query !== undefined && query.length > 0 ? query : null;
59
+ }
60
+ /**
61
+ * Run the full guard for an installer request. Returns a rejection {@link Response} to short-circuit
62
+ * the handler, or `null` when the request passed all three checks. Never logs or echoes the token.
63
+ */
64
+ export function guardInstallerRequest(c, tokenStore, tokenMode) {
65
+ if (!hostAllowed(c.req.header("host")))
66
+ return forbidden();
67
+ if (!originAllowed(c.req.method, c.req.header("origin")))
68
+ return forbidden();
69
+ const provided = extractToken(c);
70
+ if (tokenMode === "always") {
71
+ if (!tokenStore.requireValid(provided))
72
+ return unauthorized();
73
+ return null;
74
+ }
75
+ // "detect": token required only while an onboarding session is active (is-AC-10 carve-out).
76
+ if (tokenStore.isActive() && !tokenStore.requireValid(provided))
77
+ return unauthorized();
78
+ return null;
79
+ }
80
+ //# sourceMappingURL=security.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"security.js","sourceRoot":"","sources":["../../../src/daemon/installer/security.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * PRD-009a: the argv-safe child-process seam (is-AC-6).
3
+ *
4
+ * Every npm invocation and every product registration verb runs through {@link SpawnFn}, whose
5
+ * signature is structurally injection-proof: the command and its arguments are ALWAYS an argv
6
+ * array, so there is no code path that can concatenate request-derived data into a shell string.
7
+ * The default node implementation ({@link createNodeSpawn}) passes `shell: false` explicitly.
8
+ *
9
+ * Windows footgun this design side-steps: `npm.cmd` cannot be spawned with `shell:false` on
10
+ * Node >= 20 (EINVAL). Callers therefore never spawn a `.cmd`; they spawn `process.execPath`
11
+ * with a resolved `*.js` entry as the first argv element (see `bin-resolver.ts`), so no `.cmd`
12
+ * and no shell is ever involved.
13
+ */
14
+ import type { ChildProcess } from "node:child_process";
15
+ /** The bounded tail we keep of each stream (is-AC-17: a bounded stderr excerpt, ~2 KB). */
16
+ export declare const SPAWN_TAIL_LIMIT = 2048;
17
+ /** The terminal outcome of a spawned process: its exit code plus bounded stdout/stderr tails. */
18
+ export interface SpawnOutcome {
19
+ /** The process exit code, or `null` when it was terminated by a signal without a code. */
20
+ readonly code: number | null;
21
+ readonly stdoutTail: string;
22
+ readonly stderrTail: string;
23
+ }
24
+ /** Optional streaming hooks + an abort signal for a single spawn. */
25
+ export interface SpawnInvocationOptions {
26
+ readonly signal?: AbortSignal;
27
+ /** Called for each stdout chunk (used to derive observable stage milestones, is-AC-12). */
28
+ readonly onStdout?: (chunk: string) => void;
29
+ /** Called for each stderr chunk. */
30
+ readonly onStderr?: (chunk: string) => void;
31
+ }
32
+ /**
33
+ * The injectable spawn surface. `command` + `args` are an argv array by construction; a shell
34
+ * string is not expressible. Resolves with the terminal {@link SpawnOutcome}; rejects only on a
35
+ * spawn-level error (ENOENT, EINVAL), which callers translate into a `failed` stage.
36
+ */
37
+ export type SpawnFn = (command: string, args: readonly string[], options?: SpawnInvocationOptions) => Promise<SpawnOutcome>;
38
+ /** The low-level node spawn seam, injected by tests to assert `shell:false` + the argv array. */
39
+ export type RawSpawn = (command: string, args: readonly string[], options: {
40
+ readonly shell: false;
41
+ readonly signal?: AbortSignal;
42
+ }) => ChildProcess;
43
+ /**
44
+ * Build the default {@link SpawnFn} over node's `child_process.spawn`, pinned to `shell: false`.
45
+ * A test injects `rawSpawn` to assert the argv array and the disabled shell without touching a
46
+ * real process.
47
+ */
48
+ export declare function createNodeSpawn(rawSpawn?: RawSpawn): SpawnFn;
@@ -0,0 +1,51 @@
1
+ /**
2
+ * PRD-009a: the argv-safe child-process seam (is-AC-6).
3
+ *
4
+ * Every npm invocation and every product registration verb runs through {@link SpawnFn}, whose
5
+ * signature is structurally injection-proof: the command and its arguments are ALWAYS an argv
6
+ * array, so there is no code path that can concatenate request-derived data into a shell string.
7
+ * The default node implementation ({@link createNodeSpawn}) passes `shell: false` explicitly.
8
+ *
9
+ * Windows footgun this design side-steps: `npm.cmd` cannot be spawned with `shell:false` on
10
+ * Node >= 20 (EINVAL). Callers therefore never spawn a `.cmd`; they spawn `process.execPath`
11
+ * with a resolved `*.js` entry as the first argv element (see `bin-resolver.ts`), so no `.cmd`
12
+ * and no shell is ever involved.
13
+ */
14
+ import { spawn as nodeSpawn } from "node:child_process";
15
+ /** The bounded tail we keep of each stream (is-AC-17: a bounded stderr excerpt, ~2 KB). */
16
+ export const SPAWN_TAIL_LIMIT = 2048;
17
+ /** Keep only the last `limit` characters of `current + chunk` so a chatty child cannot grow memory. */
18
+ function appendTail(current, chunk, limit) {
19
+ const next = current + chunk;
20
+ return next.length > limit ? next.slice(next.length - limit) : next;
21
+ }
22
+ /**
23
+ * Build the default {@link SpawnFn} over node's `child_process.spawn`, pinned to `shell: false`.
24
+ * A test injects `rawSpawn` to assert the argv array and the disabled shell without touching a
25
+ * real process.
26
+ */
27
+ export function createNodeSpawn(rawSpawn = nodeSpawn) {
28
+ return (command, args, options = {}) => new Promise((resolve, reject) => {
29
+ let stdoutTail = "";
30
+ let stderrTail = "";
31
+ // The whole point of is-AC-6: an argv array and `shell: false`, never a shell string.
32
+ const child = rawSpawn(command, [...args], { shell: false, signal: options.signal });
33
+ child.stdout?.on("data", (data) => {
34
+ const chunk = String(data);
35
+ stdoutTail = appendTail(stdoutTail, chunk, SPAWN_TAIL_LIMIT);
36
+ options.onStdout?.(chunk);
37
+ });
38
+ child.stderr?.on("data", (data) => {
39
+ const chunk = String(data);
40
+ stderrTail = appendTail(stderrTail, chunk, SPAWN_TAIL_LIMIT);
41
+ options.onStderr?.(chunk);
42
+ });
43
+ child.on("error", (error) => {
44
+ reject(error);
45
+ });
46
+ child.on("close", (code) => {
47
+ resolve({ code, stdoutTail, stderrTail });
48
+ });
49
+ });
50
+ }
51
+ //# sourceMappingURL=spawn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"spawn.js","sourceRoot":"","sources":["../../../src/daemon/installer/spawn.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAGxD,2FAA2F;AAC3F,MAAM,CAAC,MAAM,gBAAgB,GAAG,IAAI,CAAC;AAqCrC,uGAAuG;AACvG,SAAS,UAAU,CAAC,OAAe,EAAE,KAAa,EAAE,KAAa;IAC/D,MAAM,IAAI,GAAG,OAAO,GAAG,KAAK,CAAC;IAC7B,OAAO,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,eAAe,CAAC,WAAqB,SAAgC;IACnF,OAAO,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,GAAG,EAAE,EAAE,EAAE,CACrC,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC5C,IAAI,UAAU,GAAG,EAAE,CAAC;QACpB,IAAI,UAAU,GAAG,EAAE,CAAC;QAEpB,sFAAsF;QACtF,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,EAAE,CAAC,GAAG,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAErF,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,EAAE;YACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YAC3B,UAAU,GAAG,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;YAC7D,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAa,EAAE,EAAE;YACzC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;YAC3B,UAAU,GAAG,UAAU,CAAC,UAAU,EAAE,KAAK,EAAE,gBAAgB,CAAC,CAAC;YAC7D,OAAO,CAAC,QAAQ,EAAE,CAAC,KAAK,CAAC,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAY,EAAE,EAAE;YACjC,MAAM,CAAC,KAAK,CAAC,CAAC;QAChB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAmB,EAAE,EAAE;YACxC,OAAO,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,CAAC,CAAC;QAC5C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * PRD-009a: the daemon side of the one-time onboarding token contract (is-AC-9/10).
3
+ *
4
+ * The bootstrap mints the token and writes it to `~/.honeycomb/hive/onboarding-token` at mode
5
+ * 0600 BEFORE the daemon starts (PRD-009d). The daemon reads it LAZILY at request time, not at
6
+ * startup, because on re-entry the daemon may already be running when a fresh bootstrap writes a
7
+ * new token. Comparison is constant time (`crypto.timingSafeEqual`). Completion invalidates the
8
+ * token: the file is deleted and an in-memory flag is set, so state-changing endpoints refuse all
9
+ * requests until a fresh bootstrap mints a new one (is-AC-10). The token is never logged, never
10
+ * returned, and never placed in an error body.
11
+ */
12
+ import type { InstallerConfig } from "./config.js";
13
+ /** The token gate: whether onboarding is active, whether a presented token is valid, and invalidation. */
14
+ export interface TokenStore {
15
+ /** True while a token file exists and has not been invalidated (an active onboarding session). */
16
+ isActive(): boolean;
17
+ /** True iff `provided` matches the on-disk token in constant time and the token is not invalidated. */
18
+ requireValid(provided: string | null | undefined): boolean;
19
+ /** Invalidate the token: delete the file and set the in-memory flag (idempotent). */
20
+ invalidate(): void;
21
+ }
22
+ /** Build the token store over the injected config seams. */
23
+ export declare function createTokenStore(config: InstallerConfig): TokenStore;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * PRD-009a: the daemon side of the one-time onboarding token contract (is-AC-9/10).
3
+ *
4
+ * The bootstrap mints the token and writes it to `~/.honeycomb/hive/onboarding-token` at mode
5
+ * 0600 BEFORE the daemon starts (PRD-009d). The daemon reads it LAZILY at request time, not at
6
+ * startup, because on re-entry the daemon may already be running when a fresh bootstrap writes a
7
+ * new token. Comparison is constant time (`crypto.timingSafeEqual`). Completion invalidates the
8
+ * token: the file is deleted and an in-memory flag is set, so state-changing endpoints refuse all
9
+ * requests until a fresh bootstrap mints a new one (is-AC-10). The token is never logged, never
10
+ * returned, and never placed in an error body.
11
+ */
12
+ import { timingSafeEqual } from "node:crypto";
13
+ /** Constant-time string comparison. Length is compared first (timingSafeEqual requires equal length). */
14
+ function constantTimeEquals(a, b) {
15
+ const bufA = Buffer.from(a, "utf8");
16
+ const bufB = Buffer.from(b, "utf8");
17
+ if (bufA.length !== bufB.length) {
18
+ // Still run a same-length comparison so a length mismatch is not a faster reject path.
19
+ timingSafeEqual(bufA, bufA);
20
+ return false;
21
+ }
22
+ return timingSafeEqual(bufA, bufB);
23
+ }
24
+ /** Build the token store over the injected config seams. */
25
+ export function createTokenStore(config) {
26
+ let invalidated = false;
27
+ const readToken = () => {
28
+ const raw = config.readTextFile(config.tokenPath);
29
+ if (raw === null)
30
+ return null;
31
+ const trimmed = raw.trim();
32
+ return trimmed.length > 0 ? trimmed : null;
33
+ };
34
+ return {
35
+ isActive() {
36
+ if (invalidated)
37
+ return false;
38
+ return config.fileExists(config.tokenPath);
39
+ },
40
+ requireValid(provided) {
41
+ if (invalidated)
42
+ return false;
43
+ if (provided === null || provided === undefined || provided.length === 0)
44
+ return false;
45
+ const stored = readToken();
46
+ if (stored === null)
47
+ return false;
48
+ return constantTimeEquals(stored, provided);
49
+ },
50
+ invalidate() {
51
+ invalidated = true;
52
+ config.deleteFile(config.tokenPath);
53
+ }
54
+ };
55
+ }
56
+ //# sourceMappingURL=token.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/daemon/installer/token.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAI9C,yGAAyG;AACzG,SAAS,kBAAkB,CAAC,CAAS,EAAE,CAAS;IAC9C,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpC,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,uFAAuF;QACvF,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC5B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;AACrC,CAAC;AAYD,4DAA4D;AAC5D,MAAM,UAAU,gBAAgB,CAAC,MAAuB;IACtD,IAAI,WAAW,GAAG,KAAK,CAAC;IAExB,MAAM,SAAS,GAAG,GAAkB,EAAE;QACpC,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAClD,IAAI,GAAG,KAAK,IAAI;YAAE,OAAO,IAAI,CAAC;QAC9B,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC;IAC7C,CAAC,CAAC;IAEF,OAAO;QACL,QAAQ;YACN,IAAI,WAAW;gBAAE,OAAO,KAAK,CAAC;YAC9B,OAAO,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QAC7C,CAAC;QACD,YAAY,CAAC,QAAQ;YACnB,IAAI,WAAW;gBAAE,OAAO,KAAK,CAAC;YAC9B,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO,KAAK,CAAC;YACvF,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;YAC3B,IAAI,MAAM,KAAK,IAAI;gBAAE,OAAO,KAAK,CAAC;YAClC,OAAO,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;QAC9C,CAAC;QACD,UAAU;YACR,WAAW,GAAG,IAAI,CAAC;YACnB,MAAM,CAAC,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC;QACtC,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -4,6 +4,7 @@ import { type LockPaths } from "../lock.js";
4
4
  import { type FetchImpl } from "./fleet-status.js";
5
5
  import { type ProxyFetch } from "./proxy.js";
6
6
  import { type TelemetryFetch } from "./telemetry-proxy.js";
7
+ import { type InstallerServiceOptions } from "./installer/index.js";
7
8
  import type { SetupAuthFetchImpl } from "./setup-auth.js";
8
9
  export interface CreateHiveOptions {
9
10
  readonly host?: string;
@@ -20,6 +21,11 @@ export interface CreateHiveOptions {
20
21
  readonly doctorEventsUrl?: string;
21
22
  /** The fetch used by the telemetry relay to reach doctor's SSE stream (defaults to the global `fetch`). */
22
23
  readonly telemetryStreamFetch?: TelemetryFetch;
24
+ /**
25
+ * PRD-009a: installer-service seams (manifest fetch, spawn, token/npm-prefix resolution, fs).
26
+ * A test injects fakes here so the onboarding endpoints never touch the network or real npm.
27
+ */
28
+ readonly installer?: InstallerServiceOptions;
23
29
  }
24
30
  export interface StartHiveOptions extends CreateHiveOptions {
25
31
  readonly lockPaths?: Partial<LockPaths>;
@@ -8,6 +8,7 @@ import { createPortalGate } from "./gate.js";
8
8
  import { createApiProxy } from "./proxy.js";
9
9
  import { resolveRegisteredServiceNames } from "./registry.js";
10
10
  import { createTelemetryStreamHandler } from "./telemetry-proxy.js";
11
+ import { createInstallerService } from "./installer/index.js";
11
12
  function closeServer(server) {
12
13
  return new Promise((resolve, reject) => {
13
14
  server.close((error) => {
@@ -72,6 +73,12 @@ export function createHive(options = {}) {
72
73
  doctorEventsUrl: options.doctorEventsUrl,
73
74
  fetchImpl: options.telemetryStreamFetch
74
75
  }));
76
+ // PRD-009a: the onboarding installer service (detection, install start, SSE progress, health,
77
+ // completion, funnel-event stub). Registered BEFORE the generic `/api/*` proxy so these specific
78
+ // `/api/onboarding/*` routes win (same registration-order discipline as `/api/fleet-status`). Its
79
+ // health check reuses the SAME fleet-status fetch + doctor URL the gate uses; a test can override
80
+ // any installer seam (manifest fetch, spawn, token/npm-prefix, fs) via `options.installer`.
81
+ createInstallerService({ fleetStatusFetch, doctorStatusUrl, ...options.installer }).register(app);
75
82
  // Server-side federation (BFF): every other `/api/*` and `/setup/*` request is proxied over
76
83
  // loopback to the workload daemon that owns it (honeycomb or nectar), resolved from
77
84
  // doctor's registry. The browser only ever talks to hive's own origin.
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/daemon/server.ts"],"names":[],"mappings":"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"}