@wrongstack/cli 1.0.3 → 1.0.6
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/{acp-JK4ZQO3R.js → acp-GOQURNC2.js} +128 -43
- package/dist/acp-agent-list.d.ts +14 -0
- package/dist/acp-hq-telemetry.d.ts +9 -2
- package/dist/acp-mcp-servers.d.ts +49 -0
- package/dist/acp-server-agent.d.ts +18 -6
- package/dist/{auth-XD3GN5RJ.js → auth-WBXXIQUO.js} +3 -3
- package/dist/boot/short-circuit-desktop.d.ts +1 -0
- package/dist/boot/short-circuit-hq.d.ts +2 -0
- package/dist/boot/tool-registry.d.ts +18 -1
- package/dist/{chunk-JHPXJRRI.js → chunk-4XCBEUBX.js} +13 -3
- package/dist/{chunk-ONOT5OJ2.js → chunk-5A5GCSJC.js} +1 -1
- package/dist/chunk-5EA6OTLJ.js +239 -0
- package/dist/{chunk-O4A3HHKD.js → chunk-672UXYL3.js} +2 -2
- package/dist/{chunk-FPIWTJ2G.js → chunk-6WRKAACA.js} +4 -4
- package/dist/chunk-AUQDGU5D.js +32 -0
- package/dist/{chunk-VOTTENR5.js → chunk-CDY4D5QK.js} +2 -2
- package/dist/{chunk-E6RFXHWD.js → chunk-EK2P53GL.js} +3 -2
- package/dist/{chunk-MXEJEYT7.js → chunk-JX6KL7OF.js} +15 -5
- package/dist/{chunk-GMY45LO6.js → chunk-KEPABQYT.js} +101 -2
- package/dist/{chunk-IMWXXF4J.js → chunk-KNMIBKLV.js} +79 -1
- package/dist/{chunk-4NHD5OXJ.js → chunk-NR7BD73C.js} +5 -5
- package/dist/{chunk-C2E24IXZ.js → chunk-PQZPHGGD.js} +272 -299
- package/dist/chunk-Q7E3BPDU.js +62 -0
- package/dist/{chunk-IUEKNHPT.js → chunk-QDQPF4PB.js} +11 -11
- package/dist/{chunk-VFCJOWJB.js → chunk-VPS2SJW4.js} +4 -5
- package/dist/{chunk-U6KCWRMC.js → chunk-XJ7K67PP.js} +5 -1
- package/dist/{cli-context-Q6QP534K.js → cli-context-BYKJDAFF.js} +129 -25
- package/dist/{cli-main-JQXCDZEA.js → cli-main-5WZBH3WA.js} +1345 -800
- package/dist/{diag-doctor-7LYUYU35.js → diag-doctor-PCER6KHH.js} +2 -2
- package/dist/{execution-KH7WJNAX.js → execution-Z6A26EG6.js} +132 -40
- package/dist/execution-specialist-trigger.d.ts +28 -0
- package/dist/fleet/acp-live-progress.d.ts +19 -0
- package/dist/fleet/host-acp-runner-cache.d.ts +3 -1
- package/dist/fleet/host-acp.d.ts +14 -2
- package/dist/fleet/host.d.ts +1 -0
- package/dist/{hq-IG3YRGCO.js → hq-V4PHEETW.js} +127 -8
- package/dist/hq-command-controller.d.ts +31 -2
- package/dist/hq-server/auth-state.d.ts +6 -0
- package/dist/hq-server/auth.d.ts +10 -1
- package/dist/hq-server/client-address.d.ts +2 -0
- package/dist/hq-server/ip-allowlist.d.ts +16 -0
- package/dist/hq-server/preflight.d.ts +1 -0
- package/dist/hq-server/routes/auth/common.d.ts +34 -0
- package/dist/hq-server/routes/auth/password-routes.d.ts +4 -2
- package/dist/hq-server/routes.d.ts +5 -4
- package/dist/hq-server/secret-input.d.ts +7 -0
- package/dist/hq-server/types.d.ts +21 -2
- package/dist/hq-server/upgrade-handler.d.ts +2 -0
- package/dist/hq-server/utils.d.ts +6 -0
- package/dist/{hq-server-OILU3NPI.js → hq-server-MZ7DV6X7.js} +4 -3
- package/dist/hq-server.d.ts +4 -0
- package/dist/hq-service-L5INQFM4.js +507 -0
- package/dist/hq-service.d.ts +22 -0
- package/dist/index.js +9 -6
- package/dist/ip-allowlist-NHXO6AKJ.js +9 -0
- package/dist/live-settings-input.d.ts +2 -0
- package/dist/{mcp-HOMLC4WE.js → mcp-FX7TKPTV.js} +10 -8
- package/dist/mcp-serve.d.ts +31 -0
- package/dist/{per-subcommand-help-BWBHYVJO.js → per-subcommand-help-DHTYU524.js} +2 -2
- package/dist/{plugins-U7KGQAUG.js → plugins-WJLW6ULV.js} +2 -2
- package/dist/projects-LMNAGGEU.js +36 -0
- package/dist/{proxy-wiring-MGRSKZZB.js → proxy-wiring-K2DORFFL.js} +3 -3
- package/dist/short-circuit-flags-KBTYSJTW.js +10 -0
- package/dist/slash-commands/acp.d.ts +3 -0
- package/dist/slash-commands/command-context.d.ts +22 -0
- package/dist/slash-commands/ensemble.d.ts +1 -1
- package/dist/slash-commands/profile.d.ts +7 -0
- package/dist/subcommands/handlers/hq.d.ts +1 -0
- package/dist/subcommands/handlers/projects.d.ts +14 -0
- package/dist/{subcommands-CUTGBDKP.js → subcommands-DEYSYHIM.js} +2 -2
- package/dist/{version-help-MYKF724O.js → version-help-WXI42PZ3.js} +2 -2
- package/dist/{webui-server-QWBUT7BO.js → webui-server-OBV5JYJV.js} +9 -3
- package/dist/wiring/cli-slash-commands-setup.d.ts +7 -0
- package/dist/wiring/codebase-embeddings.d.ts +2 -2
- package/dist/wiring/command-host-state.d.ts +7 -0
- package/dist/wiring/pipeline.d.ts +2 -0
- package/dist/wiring/provider-runtime-setup.d.ts +1 -1
- package/dist/wiring/session-command-handlers.d.ts +2 -1
- package/dist/wiring/slash-commands.d.ts +1 -0
- package/package.json +28 -28
- package/dist/chunk-Q5GTM25S.js +0 -17
- package/dist/projects-L6C6XLDY.js +0 -37
- package/dist/short-circuit-flags-ZNZX44MC.js +0 -10
|
@@ -2,8 +2,15 @@ import {
|
|
|
2
2
|
parseTokenTtlValue
|
|
3
3
|
} from "./chunk-UZ2TMHNH.js";
|
|
4
4
|
import {
|
|
5
|
+
resolveHqPasswordInput
|
|
6
|
+
} from "./chunk-AUQDGU5D.js";
|
|
7
|
+
import {
|
|
8
|
+
normalizeHqPublicOrigin,
|
|
5
9
|
resolveAuditActor
|
|
6
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-5EA6OTLJ.js";
|
|
11
|
+
import {
|
|
12
|
+
parseHqIpAllowlist
|
|
13
|
+
} from "./chunk-Q7E3BPDU.js";
|
|
7
14
|
|
|
8
15
|
// src/subcommands/handlers/hq.ts
|
|
9
16
|
import * as fs from "node:fs/promises";
|
|
@@ -14,6 +21,7 @@ import {
|
|
|
14
21
|
hqAuthAuditPath,
|
|
15
22
|
hqAuthContentHash,
|
|
16
23
|
hqAuthFilePath,
|
|
24
|
+
isLoopbackHost,
|
|
17
25
|
logHqAuthAudit,
|
|
18
26
|
mintHqToken,
|
|
19
27
|
mutateHqAuthFile,
|
|
@@ -40,6 +48,10 @@ var hqCmd = async (args, deps) => {
|
|
|
40
48
|
if (sub === "audit") {
|
|
41
49
|
return hqAuditCmd(args.slice(1), deps);
|
|
42
50
|
}
|
|
51
|
+
if (sub === "service") {
|
|
52
|
+
const { hqServiceCmd } = await import("./hq-service-L5INQFM4.js");
|
|
53
|
+
return hqServiceCmd(args.slice(1), deps);
|
|
54
|
+
}
|
|
43
55
|
if (sub === "help" || sub === "--help") {
|
|
44
56
|
printHelp(deps);
|
|
45
57
|
return 0;
|
|
@@ -50,15 +62,53 @@ var hqCmd = async (args, deps) => {
|
|
|
50
62
|
return 1;
|
|
51
63
|
};
|
|
52
64
|
async function startServer(deps) {
|
|
53
|
-
const { startHqServer } = await import("./hq-server-
|
|
65
|
+
const { startHqServer } = await import("./hq-server-MZ7DV6X7.js");
|
|
54
66
|
const dataDir = resolveDataDir(deps);
|
|
55
67
|
const flags = deps.flags ?? {};
|
|
56
|
-
const
|
|
68
|
+
const rawPublicUrl = typeof flags["hq-public-url"] === "string" ? flags["hq-public-url"] : process.env.WRONGSTACK_HQ_PUBLIC_URL;
|
|
69
|
+
let publicOrigin;
|
|
70
|
+
if (rawPublicUrl !== void 0 && rawPublicUrl.trim() !== "") {
|
|
71
|
+
try {
|
|
72
|
+
publicOrigin = normalizeHqPublicOrigin(rawPublicUrl);
|
|
73
|
+
} catch (cause) {
|
|
74
|
+
deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
|
|
75
|
+
`);
|
|
76
|
+
return 1;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const host = typeof flags["host"] === "string" ? flags["host"] : publicOrigin !== void 0 ? "127.0.0.1" : HQ_CLI_DEFAULT_HOST;
|
|
57
80
|
const port = typeof flags["port"] === "string" ? Number.parseInt(flags["port"], 10) : 3499;
|
|
81
|
+
if (publicOrigin !== void 0 && !isLoopbackHost(host)) {
|
|
82
|
+
deps.renderer.writeError(
|
|
83
|
+
"Persistent public HQ requires a loopback bind. Remove --host or use --host 127.0.0.1.\n"
|
|
84
|
+
);
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
58
87
|
const strictPort = flags["strict-port"] === true;
|
|
59
88
|
const open = flags["open"] === true;
|
|
60
|
-
|
|
89
|
+
let password;
|
|
90
|
+
try {
|
|
91
|
+
password = await resolveHqPasswordInput({
|
|
92
|
+
...typeof flags["password"] === "string" ? { explicitPassword: flags["password"] } : {}
|
|
93
|
+
});
|
|
94
|
+
} catch (cause) {
|
|
95
|
+
deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
|
|
96
|
+
`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
const bootstrapPasswordOnly = /^(?:1|true)$/i.test(
|
|
100
|
+
process.env.WRONGSTACK_HQ_BOOTSTRAP_PASSWORD_ONLY?.trim() ?? ""
|
|
101
|
+
);
|
|
61
102
|
const allowInsecureOpen = flags["insecure-open"] === true;
|
|
103
|
+
const rawAllowlist = typeof flags["hq-allowlist"] === "string" ? flags["hq-allowlist"] : process.env.WRONGSTACK_HQ_ALLOWLIST;
|
|
104
|
+
let ipAllowlist;
|
|
105
|
+
try {
|
|
106
|
+
ipAllowlist = parseHqIpAllowlist(rawAllowlist);
|
|
107
|
+
} catch (cause) {
|
|
108
|
+
deps.renderer.writeError(`${cause instanceof Error ? cause.message : String(cause)}
|
|
109
|
+
`);
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
62
112
|
const rawTtl = typeof flags["hq-token-ttl"] === "string" ? flags["hq-token-ttl"] : void 0;
|
|
63
113
|
let tokenTtlMs;
|
|
64
114
|
if (rawTtl !== void 0 && rawTtl.length > 0) {
|
|
@@ -94,9 +144,13 @@ async function startServer(deps) {
|
|
|
94
144
|
strictPort,
|
|
95
145
|
dataDir,
|
|
96
146
|
allowInsecureOpen,
|
|
147
|
+
secureCookies: publicOrigin !== void 0,
|
|
148
|
+
requireBrowserAuth: publicOrigin !== void 0,
|
|
97
149
|
...password !== void 0 ? { password } : {},
|
|
150
|
+
...bootstrapPasswordOnly ? { bootstrapPasswordOnly: true } : {},
|
|
98
151
|
...tokenTtlMs !== void 0 ? { tokenTtlMs } : {},
|
|
99
|
-
...trustedProxyHops !== void 0 ? { trustedProxyHops } : {}
|
|
152
|
+
...trustedProxyHops !== void 0 ? { trustedProxyHops } : {},
|
|
153
|
+
...ipAllowlist !== void 0 ? { ipAllowlist } : {}
|
|
100
154
|
});
|
|
101
155
|
} catch (err) {
|
|
102
156
|
if (err instanceof HqInsecureExposureError) {
|
|
@@ -106,14 +160,34 @@ async function startServer(deps) {
|
|
|
106
160
|
}
|
|
107
161
|
throw err;
|
|
108
162
|
}
|
|
163
|
+
if (publicOrigin !== void 0) {
|
|
164
|
+
if (handle.firstRunSetup?.passwordMode !== true) {
|
|
165
|
+
await handle.close();
|
|
166
|
+
deps.renderer.writeError(
|
|
167
|
+
"Persistent public HQ requires password mode. Set WRONGSTACK_HQ_PASSWORD and retry.\n"
|
|
168
|
+
);
|
|
169
|
+
return 1;
|
|
170
|
+
}
|
|
171
|
+
handle.trustPublicOrigin(publicOrigin);
|
|
172
|
+
deps.renderer.write(`Public endpoint: ${publicOrigin}/
|
|
173
|
+
`);
|
|
174
|
+
deps.renderer.write(`Public mobile: ${publicOrigin}/mobile
|
|
175
|
+
`);
|
|
176
|
+
}
|
|
109
177
|
if (open) {
|
|
110
178
|
try {
|
|
111
179
|
const { openBrowser } = await import("@wrongstack/webui-server");
|
|
112
|
-
openBrowser(
|
|
180
|
+
openBrowser(
|
|
181
|
+
publicOrigin ?? handle.firstRunSetup?.browserUrl ?? `http://${handle.host}:${handle.port}`
|
|
182
|
+
);
|
|
113
183
|
} catch {
|
|
114
184
|
}
|
|
115
185
|
}
|
|
116
186
|
writeStartupInfo(deps, handle);
|
|
187
|
+
deps.renderer.write(
|
|
188
|
+
ipAllowlist === void 0 ? "Network allowlist: disabled (authentication remains required when configured).\n" : `Network allowlist: active (${ipAllowlist.length} configured rule${ipAllowlist.length === 1 ? "" : "s"} + loopback).
|
|
189
|
+
`
|
|
190
|
+
);
|
|
117
191
|
await new Promise((resolve) => {
|
|
118
192
|
const shutdown = async () => {
|
|
119
193
|
try {
|
|
@@ -137,9 +211,34 @@ function writeStartupInfo(deps, handle) {
|
|
|
137
211
|
`);
|
|
138
212
|
deps.renderer.write(`Browser endpoint: http://${handle.host}:${handle.port}
|
|
139
213
|
`);
|
|
214
|
+
deps.renderer.write(
|
|
215
|
+
`Mobile endpoint: http://${handle.host}:${handle.port}/mobile (password required)
|
|
216
|
+
`
|
|
217
|
+
);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (process.env.WRONGSTACK_HQ_SUPPRESS_STARTUP_SECRETS === "1") {
|
|
221
|
+
deps.renderer.write(`Browser endpoint: http://${handle.host}:${handle.port}
|
|
222
|
+
`);
|
|
223
|
+
deps.renderer.write(
|
|
224
|
+
`Mobile endpoint: http://${handle.host}:${handle.port}/mobile (password required)
|
|
225
|
+
`
|
|
226
|
+
);
|
|
227
|
+
deps.renderer.write(`Client endpoint: ws://${handle.host}:${handle.port}/ws/client
|
|
228
|
+
`);
|
|
229
|
+
deps.renderer.write(
|
|
230
|
+
`Startup credentials suppressed; auth state is stored in ${handle.firstRunSetup.dataDir}.
|
|
231
|
+
`
|
|
232
|
+
);
|
|
140
233
|
return;
|
|
141
234
|
}
|
|
235
|
+
const mobileUrl = new URL(handle.firstRunSetup.browserUrl);
|
|
236
|
+
mobileUrl.pathname = "/mobile";
|
|
237
|
+
mobileUrl.search = "";
|
|
238
|
+
mobileUrl.hash = "";
|
|
142
239
|
deps.renderer.write(`Browser endpoint: ${handle.firstRunSetup.browserUrl}
|
|
240
|
+
`);
|
|
241
|
+
deps.renderer.write(`Mobile endpoint: ${mobileUrl.toString()} (password required)
|
|
143
242
|
`);
|
|
144
243
|
deps.renderer.write(`Client endpoint: ${handle.firstRunSetup.clientUrl}
|
|
145
244
|
`);
|
|
@@ -537,7 +636,7 @@ async function tokenRevoke(args, deps) {
|
|
|
537
636
|
return 0;
|
|
538
637
|
}
|
|
539
638
|
function printHelp(deps) {
|
|
540
|
-
deps.renderer.write(`Usage: wstack hq <serve | token | audit>
|
|
639
|
+
deps.renderer.write(`Usage: wstack hq <serve | token | audit | service>
|
|
541
640
|
`);
|
|
542
641
|
deps.renderer.write("\n");
|
|
543
642
|
deps.renderer.write(` wstack hq Start the HQ command center server.
|
|
@@ -564,6 +663,18 @@ function printHelp(deps) {
|
|
|
564
663
|
`);
|
|
565
664
|
deps.renderer.write(
|
|
566
665
|
` wstack hq audit verify Re-derive auth.json contentHash for forensic comparison.
|
|
666
|
+
`
|
|
667
|
+
);
|
|
668
|
+
deps.renderer.write(
|
|
669
|
+
` wstack hq service install Install persistent Ubuntu/systemd service + updater timer.
|
|
670
|
+
`
|
|
671
|
+
);
|
|
672
|
+
deps.renderer.write(` wstack hq service status Show service and updater status.
|
|
673
|
+
`);
|
|
674
|
+
deps.renderer.write(` wstack hq service update Run the guarded update job now.
|
|
675
|
+
`);
|
|
676
|
+
deps.renderer.write(
|
|
677
|
+
` wstack hq service uninstall Remove units but preserve HQ data and secret file.
|
|
567
678
|
`
|
|
568
679
|
);
|
|
569
680
|
deps.renderer.write("\n");
|
|
@@ -579,6 +690,10 @@ function printHelp(deps) {
|
|
|
579
690
|
);
|
|
580
691
|
deps.renderer.write(
|
|
581
692
|
` --tunnel Publish a temporary *.trycloudflare.com URL (requires cloudflared).
|
|
693
|
+
`
|
|
694
|
+
);
|
|
695
|
+
deps.renderer.write(
|
|
696
|
+
` --hq-public-url <https-origin> Trust a persistent TLS reverse-proxy origin; binds loopback and requires password mode.
|
|
582
697
|
`
|
|
583
698
|
);
|
|
584
699
|
deps.renderer.write(` --password <value> Set or rotate the browser login password.
|
|
@@ -599,6 +714,10 @@ function printHelp(deps) {
|
|
|
599
714
|
` --hq-trusted-proxy-hops <n> Trust the rightmost n X-Forwarded-For entries when rate-limiting logins.
|
|
600
715
|
Default 0 (ignore the header). Set to the real hop count behind a tunnel,
|
|
601
716
|
otherwise every user shares one backoff bucket. Never guess high.
|
|
717
|
+
`
|
|
718
|
+
);
|
|
719
|
+
deps.renderer.write(
|
|
720
|
+
` --hq-allowlist <csv> Optional source IP/CIDR allowlist (for example 203.0.113.4,10.0.0.0/8).
|
|
602
721
|
`
|
|
603
722
|
);
|
|
604
723
|
deps.renderer.write(` --open Open the dashboard in the default browser.
|
|
@@ -708,4 +827,4 @@ export {
|
|
|
708
827
|
hqCmd,
|
|
709
828
|
resolveAuditActor
|
|
710
829
|
};
|
|
711
|
-
//# sourceMappingURL=hq-
|
|
830
|
+
//# sourceMappingURL=hq-V4PHEETW.js.map
|
|
@@ -6,8 +6,9 @@
|
|
|
6
6
|
* (`director`, the rebound `interruptLeader`) are not yet defined at HQ-connect
|
|
7
7
|
* time.
|
|
8
8
|
*
|
|
9
|
-
*
|
|
10
|
-
* project mailbox
|
|
9
|
+
* Conversational commands route through the agent's own decision loop or the
|
|
10
|
+
* project mailbox; Kanban commands route through its project IPC owner, so
|
|
11
|
+
* both inherit existing guardrails. `run-command` (raw
|
|
11
12
|
* shell) is deliberately NOT wired here — it requires a separately-audited
|
|
12
13
|
* escalation policy (operator flag + per-token `control.execute` + allowlist)
|
|
13
14
|
* and is left as a stub that rejects until that policy exists.
|
|
@@ -67,7 +68,35 @@ export interface HqCommandController {
|
|
|
67
68
|
ownsSession?: ((sessionId: string) => boolean) | undefined;
|
|
68
69
|
/** Whether raw shell execution is explicitly opted-in by the operator. */
|
|
69
70
|
allowRunCommand: () => boolean;
|
|
71
|
+
/** Apply a lifecycle transition through the project's Kanban IPC client. */
|
|
72
|
+
kanbanTransition?: ((input: {
|
|
73
|
+
boardId: string;
|
|
74
|
+
taskId: string;
|
|
75
|
+
to: 'backlog' | 'todo' | 'running' | 'review' | 'done';
|
|
76
|
+
comment: string;
|
|
77
|
+
sessionId?: string | undefined;
|
|
78
|
+
}) => Promise<string>) | undefined;
|
|
79
|
+
/** Assign a card through the project owner while preserving active leases. */
|
|
80
|
+
kanbanAssign?: ((input: {
|
|
81
|
+
boardId: string;
|
|
82
|
+
taskId: string;
|
|
83
|
+
agentId: string;
|
|
84
|
+
assignee: string;
|
|
85
|
+
comment: string;
|
|
86
|
+
sessionId?: string | undefined;
|
|
87
|
+
}) => Promise<string>) | undefined;
|
|
88
|
+
/** Claim and dispatch one ready card through this host's canonical Director path. */
|
|
89
|
+
kanbanDispatch?: ((input: {
|
|
90
|
+
boardId: string;
|
|
91
|
+
taskId: string;
|
|
92
|
+
comment: string;
|
|
93
|
+
sessionId?: string | undefined;
|
|
94
|
+
}) => Promise<string>) | undefined;
|
|
70
95
|
}
|
|
96
|
+
/** Bind HQ transitions to the same project-scoped IPC path every other client uses. */
|
|
97
|
+
export declare function createProjectKanbanTransitionHandler(projectRoot: string): NonNullable<HqCommandController['kanbanTransition']>;
|
|
98
|
+
/** Bind guarded HQ assignment to the canonical project-scoped Kanban owner. */
|
|
99
|
+
export declare function createProjectKanbanAssignHandler(projectRoot: string): NonNullable<HqCommandController['kanbanAssign']>;
|
|
71
100
|
/**
|
|
72
101
|
* Build the `onCommand` handler for an HQ publisher. The handler validates
|
|
73
102
|
* the command, dispatches through the controller, and returns an ack result
|
|
@@ -6,6 +6,12 @@ export interface HqAuthState {
|
|
|
6
6
|
tokenStats(): NonNullable<HqSnapshot['totals']['tokenStats']>;
|
|
7
7
|
}
|
|
8
8
|
interface HqAuthStateOptions {
|
|
9
|
+
/**
|
|
10
|
+
* The server's `requireBrowserAuth` option, stamped onto the state so every
|
|
11
|
+
* `hqAuthRequired(mutableAuth)` gate observes it without having to be handed
|
|
12
|
+
* it separately. See the field's doc on `HqRouterMutableAuth`.
|
|
13
|
+
*/
|
|
14
|
+
requireBrowserAuth?: boolean | undefined;
|
|
9
15
|
/**
|
|
10
16
|
* Invoked after every {@link HqAuthState.apply}, with the freshly projected
|
|
11
17
|
* `mutableAuth`. The server wires this to the WS-010 exposure re-assessment
|
package/dist/hq-server/auth.d.ts
CHANGED
|
@@ -38,7 +38,16 @@ export type HqBrowserAuthResult = HqBrowserAuthContext | HqCookieAuthContext | u
|
|
|
38
38
|
* inline style is not script execution.
|
|
39
39
|
*/
|
|
40
40
|
export declare function setHqSecurityHeaders(res: http.ServerResponse): void;
|
|
41
|
-
export declare function hasTrustedBrowserOrigin(req: http.IncomingMessage, boundHost?: string, boundPort?: number, trustedPublicOrigins?: ReadonlySet<string>, allowFileOrigin?: boolean
|
|
41
|
+
export declare function hasTrustedBrowserOrigin(req: http.IncomingMessage, boundHost?: string, boundPort?: number, trustedPublicOrigins?: ReadonlySet<string>, allowFileOrigin?: boolean,
|
|
42
|
+
/**
|
|
43
|
+
* Whether this surface has any credential configured — pass
|
|
44
|
+
* `hqAuthRequired(mutableAuth, requireBrowserAuth)`.
|
|
45
|
+
*
|
|
46
|
+
* Defaults to `false`, the STRICT side, deliberately: a caller that forgets
|
|
47
|
+
* to wire it gets the tighter rule rather than the looser one. That is the
|
|
48
|
+
* opposite of how the WS-077 gates drifted.
|
|
49
|
+
*/
|
|
50
|
+
credentialedSurface?: boolean): boolean;
|
|
42
51
|
export declare function serializeHqSessionCookie(sessionId: string, secret: string): string;
|
|
43
52
|
export declare function parseHqSessionCookie(value: string, secret: string): string | undefined;
|
|
44
53
|
export declare function parseCookieHeader(cookieHeader: string | undefined): Record<string, string>;
|
|
@@ -35,6 +35,8 @@
|
|
|
35
35
|
* @module hq-server/client-address
|
|
36
36
|
*/
|
|
37
37
|
import type * as http from 'node:http';
|
|
38
|
+
/** The actual TCP peer, with IPv4-mapped IPv6 normalized. Never reads headers. */
|
|
39
|
+
export declare function resolveSocketAddress(req: http.IncomingMessage): string | undefined;
|
|
38
40
|
/**
|
|
39
41
|
* Parse one `X-Forwarded-For` element into a bare address, or `undefined` when
|
|
40
42
|
* it is not one. Handles `1.2.3.4`, `1.2.3.4:5678`, `[::1]`, and `[::1]:5678`.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface HqIpAllowlist {
|
|
2
|
+
readonly entries: readonly string[];
|
|
3
|
+
allows(address: string | undefined): boolean;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Build the optional HQ admission allowlist.
|
|
7
|
+
*
|
|
8
|
+
* Matching is intentionally performed against the TCP peer, never a forwarded
|
|
9
|
+
* header. When HQ is directly reachable on 0.0.0.0, trusting a client-supplied
|
|
10
|
+
* X-Forwarded-For value here would turn the allowlist into a bypass. Loopback
|
|
11
|
+
* remains implicit so local administration and health checks cannot be locked
|
|
12
|
+
* out by a remote-only list.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createHqIpAllowlist(rules: readonly string[] | undefined): HqIpAllowlist | undefined;
|
|
15
|
+
export declare function parseHqIpAllowlist(value: string | undefined): string[] | undefined;
|
|
16
|
+
//# sourceMappingURL=ip-allowlist.d.ts.map
|
|
@@ -1,7 +1,41 @@
|
|
|
1
1
|
import type * as http from 'node:http';
|
|
2
2
|
import { type HqAuthFile } from '@wrongstack/core/hq';
|
|
3
3
|
import { type HqBrowserAuthResult } from '../../auth.js';
|
|
4
|
+
import type { LoginAttemptStore } from '../../login-attempt-store.js';
|
|
4
5
|
import type { HqRouterMutableAuth, HqSessionEntry } from '../../types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Record a failed second-factor confirmation and return the new failure count.
|
|
8
|
+
*
|
|
9
|
+
* Shared by every route that verifies a factor outside `/api/login`
|
|
10
|
+
* (totp/disable, password change). Kept here rather than copied per route: the
|
|
11
|
+
* whole point is that each of those routes feeds the SAME per-IP counter, so a
|
|
12
|
+
* attacker cannot get a fresh budget by switching endpoints.
|
|
13
|
+
*/
|
|
14
|
+
export declare function recordVerifyFailure(loginAttempts: LoginAttemptStore, clientIp: string): number;
|
|
15
|
+
/** Outcome of {@link consumeTotpCode}. */
|
|
16
|
+
export type TotpConsumeResult = 'ok' | 'invalid' | 'replayed';
|
|
17
|
+
/**
|
|
18
|
+
* Verify a TOTP code AND burn it, so it cannot be presented a second time.
|
|
19
|
+
*
|
|
20
|
+
* A TOTP code stays valid for its whole time step plus the verification
|
|
21
|
+
* window — roughly 90 seconds. Without a single-use record, one observed code
|
|
22
|
+
* can be replayed at a *different* endpoint for as long as that window lasts.
|
|
23
|
+
* That is not theoretical here: `/api/login/verify` advanced
|
|
24
|
+
* `totpLastUsedCounter` and the other two verification sites
|
|
25
|
+
* (`totp/disable`, `auth/password`) called the counter-less `verifyTotp`, so a
|
|
26
|
+
* code already spent on a login replayed into removing 2FA outright or
|
|
27
|
+
* rotating the password and cookie secret.
|
|
28
|
+
*
|
|
29
|
+
* Kept in this module for the same reason as {@link recordVerifyFailure}: the
|
|
30
|
+
* property only holds if EVERY site that verifies a code shares one counter.
|
|
31
|
+
* A route that verifies a TOTP code by any other means is a bug —
|
|
32
|
+
* `hq-totp-single-use.test.ts` enumerates the call sites to keep it that way.
|
|
33
|
+
*
|
|
34
|
+
* The counter is advanced in memory first and persisted best-effort, matching
|
|
35
|
+
* the pre-existing behaviour at `/api/login/verify`: a failed write must not
|
|
36
|
+
* turn a consumed code back into an unconsumed one for this process.
|
|
37
|
+
*/
|
|
38
|
+
export declare function consumeTotpCode(code: string, mutableAuth: HqRouterMutableAuth, dataDir: string, applyAuthFile: ApplyHqAuthFile): Promise<TotpConsumeResult>;
|
|
5
39
|
export declare function isLoopbackRequest(req: http.IncomingMessage): boolean;
|
|
6
40
|
export declare const HQ_AUTH_ADMIN_CAPABILITY = "auth.admin";
|
|
7
41
|
export declare function callerCanAdministerAuth(auth: HqBrowserAuthResult): boolean;
|
|
@@ -3,7 +3,9 @@ import type { LoginAttemptStore } from '../../login-attempt-store.js';
|
|
|
3
3
|
import type { HqRouterMutableAuth, HqSessionEntry } from '../../types.js';
|
|
4
4
|
import { type ApplyHqAuthFile } from './common.js';
|
|
5
5
|
export declare function handleApiAuthStatus(req: http.IncomingMessage, res: http.ServerResponse, url: URL, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, requireBrowserAuth: boolean | undefined, trustedPublicOrigins: Set<string>, secureCookies: boolean | undefined): Promise<void>;
|
|
6
|
-
export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number
|
|
6
|
+
export declare function handleApiLogin(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, loginAttempts: LoginAttemptStore, secureCookies: boolean | undefined, trustedProxyHops: number,
|
|
7
|
+
/** Where `auth.json` lives — needed to persist a transparent KDF upgrade. */
|
|
8
|
+
dataDir: string, sessionCapabilities?: string[]): Promise<void>;
|
|
7
9
|
export declare function handleApiLogout(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, secureCookies: boolean | undefined): Promise<void>;
|
|
8
|
-
export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile): Promise<void>;
|
|
10
|
+
export declare function handleApiPassword(req: http.IncomingMessage, res: http.ServerResponse, mutableAuth: HqRouterMutableAuth, sessions: Map<string, HqSessionEntry>, dataDir: string, secureCookies: boolean | undefined, requireBrowserAuth: boolean | undefined, applyAuthFile: ApplyHqAuthFile, loginAttempts: LoginAttemptStore, trustedProxyHops: number): Promise<void>;
|
|
9
11
|
//# sourceMappingURL=password-routes.d.ts.map
|
|
@@ -8,10 +8,8 @@
|
|
|
8
8
|
* @module hq-server/routes
|
|
9
9
|
*/
|
|
10
10
|
import type * as http from 'node:http';
|
|
11
|
-
import type { createMailboxHttpRouter } from '@wrongstack/core/coordination';
|
|
12
|
-
import type {
|
|
13
|
-
import type { HqAlertEngine, HqCommandAuditLog, HqEventEnvelope, HqSnapshot, HqTranscriptEntry } from '@wrongstack/core/hq';
|
|
14
|
-
import type { createHqPersistence } from '@wrongstack/core/hq';
|
|
11
|
+
import type { createMailboxHttpRouter, Mailbox, MailboxHttpAccessDecision, MailboxHttpRateLimiter } from '@wrongstack/core/coordination';
|
|
12
|
+
import type { createHqPersistence, HqAlertEngine, HqCommandAuditLog, HqEventEnvelope, HqSnapshot, HqTranscriptEntry } from '@wrongstack/core/hq';
|
|
15
13
|
import type { TrustBoundary } from '@wrongstack/core/security';
|
|
16
14
|
import type { WebSocket } from 'ws';
|
|
17
15
|
import * as HqServerAuth from './auth.js';
|
|
@@ -41,6 +39,7 @@ export declare const agentMessageToEntry: typeof HqServerUtils.agentMessageToEnt
|
|
|
41
39
|
export declare const readLocalSubagentTranscript: typeof HqServerUtils.readLocalSubagentTranscript;
|
|
42
40
|
export declare const lanIPv4Addresses: typeof HqServerUtils.lanIPv4Addresses;
|
|
43
41
|
export declare const hqRuntimeMarkerPath: typeof HqServerUtils.hqRuntimeMarkerPath;
|
|
42
|
+
import type { HqIpAllowlist } from './ip-allowlist.js';
|
|
44
43
|
import type { LoginAttemptStore } from './login-attempt-store.js';
|
|
45
44
|
import type { ConnectedClient, HqRouterMutableAuth, HqSessionEntry, HqSnapshotBroadcaster, ProjectDetail, TranscriptRing } from './types.js';
|
|
46
45
|
export type { ConnectedClient, HqSnapshotBroadcaster, ProjectDetail, TranscriptRing };
|
|
@@ -99,6 +98,8 @@ export interface HqRouterDeps {
|
|
|
99
98
|
* Defaults to 0, which ignores `X-Forwarded-For` entirely.
|
|
100
99
|
*/
|
|
101
100
|
trustedProxyHops?: number | undefined;
|
|
101
|
+
/** TCP-peer allowlist shared by HTTP and WebSocket admission. */
|
|
102
|
+
ipAllowlist?: HqIpAllowlist | undefined;
|
|
102
103
|
}
|
|
103
104
|
/**
|
|
104
105
|
* Create the HTTP request listener that handles all `/api/*` routes as well
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ResolveHqPasswordOptions {
|
|
2
|
+
explicitPassword?: string | undefined;
|
|
3
|
+
environment?: NodeJS.ProcessEnv | undefined;
|
|
4
|
+
}
|
|
5
|
+
/** Resolve HQ password input without putting Docker/Kubernetes secrets in argv or env values. */
|
|
6
|
+
export declare function resolveHqPasswordInput(options?: ResolveHqPasswordOptions): Promise<string | undefined>;
|
|
7
|
+
//# sourceMappingURL=secret-input.d.ts.map
|
|
@@ -103,11 +103,30 @@ export interface HqRouterMutableAuth {
|
|
|
103
103
|
* the surface fails closed instead of opening up.
|
|
104
104
|
*/
|
|
105
105
|
requireAuthFloor?: boolean | undefined;
|
|
106
|
+
/**
|
|
107
|
+
* The server's `requireBrowserAuth` option, carried on the state rather than
|
|
108
|
+
* passed to every gate.
|
|
109
|
+
*
|
|
110
|
+
* `hqAuthRequired` takes it as an optional second argument, and five gates
|
|
111
|
+
* called it with one — `command-handlers`, three in `mailbox-handlers`, and
|
|
112
|
+
* `mailbox-gateway-manager` — so a public relay did not raise the auth
|
|
113
|
+
* requirement on those routes. That is the WS-077 drift class the helper's
|
|
114
|
+
* own docstring warns about, recurring on the same routes. Holding the value
|
|
115
|
+
* on the state makes the one-argument call correct instead of incomplete.
|
|
116
|
+
*
|
|
117
|
+
* Required, not optional, and deliberately so. Optional would mean a second
|
|
118
|
+
* construction site could omit it and every one-argument gate would silently
|
|
119
|
+
* read `undefined` — the same drift this field exists to end, one level down.
|
|
120
|
+
* With `exactOptionalPropertyTypes` the compiler now makes every builder say
|
|
121
|
+
* what it means, including `undefined`.
|
|
122
|
+
*/
|
|
123
|
+
requireBrowserAuth: boolean | undefined;
|
|
106
124
|
}
|
|
107
125
|
/**
|
|
108
126
|
* Session record stored in the server-side `sessions` Map.
|
|
109
127
|
*
|
|
110
|
-
* - `kind: 'password'` — created by `/api/login`; full browser access
|
|
128
|
+
* - `kind: 'password'` — created by `/api/login`; full browser access unless
|
|
129
|
+
* the dedicated mobile endpoint stamped a least-privilege capability set.
|
|
111
130
|
* - `kind: 'token'` — created by `/api/auth/bootstrap`; scoped to the
|
|
112
131
|
* originating browser token's ID and capabilities so revocation/expiry
|
|
113
132
|
* of that token invalidates the session too.
|
|
@@ -117,7 +136,7 @@ export interface HqSessionEntry {
|
|
|
117
136
|
readonly kind: 'password' | 'token';
|
|
118
137
|
/** For token sessions: the browser-token ID this session was minted from. */
|
|
119
138
|
readonly tokenId?: string | undefined;
|
|
120
|
-
/**
|
|
139
|
+
/** Token capabilities, or a server-stamped scope for a mobile password session. */
|
|
121
140
|
readonly capabilities?: string[] | undefined;
|
|
122
141
|
/**
|
|
123
142
|
* Set when a password login passed but 2FA verification is still pending.
|
|
@@ -8,6 +8,7 @@ import type { Duplex } from 'node:stream';
|
|
|
8
8
|
import { type HqToken } from '@wrongstack/core/hq';
|
|
9
9
|
import type { WebSocket, WebSocketServer } from 'ws';
|
|
10
10
|
import type { HqAuthState } from './auth-state.js';
|
|
11
|
+
import type { HqIpAllowlist } from './ip-allowlist.js';
|
|
11
12
|
import type { ConnectedClient, HqSessionEntry, TranscriptRing } from './types.js';
|
|
12
13
|
interface HqUpgradeHandlerDeps {
|
|
13
14
|
host: string;
|
|
@@ -28,6 +29,7 @@ interface HqUpgradeHandlerDeps {
|
|
|
28
29
|
auditLog: import('@wrongstack/core/hq').HqCommandAuditLog;
|
|
29
30
|
snapshotBroadcaster: ReturnType<typeof import('./snapshot.js').createSnapshotBroadcaster>;
|
|
30
31
|
wss: WebSocketServer;
|
|
32
|
+
ipAllowlist?: HqIpAllowlist | undefined;
|
|
31
33
|
}
|
|
32
34
|
export declare function handleHqUpgrade(req: IncomingMessage, socket: Duplex, head: Buffer, deps: HqUpgradeHandlerDeps): void;
|
|
33
35
|
export declare function handleHqConnection(ws: WebSocket, req: IncomingMessage, pathname: string, deps: HqUpgradeHandlerDeps): void;
|
|
@@ -95,6 +95,12 @@ export declare function buildHttpUrl(host: string, port: number, token?: string)
|
|
|
95
95
|
*/
|
|
96
96
|
export declare function buildBootstrapHttpUrl(host: string, port: number, bootstrapCode: string): string;
|
|
97
97
|
export declare function buildClientWsUrl(host: string, port: number, token?: string): string;
|
|
98
|
+
/**
|
|
99
|
+
* Validate and normalize a browser-facing HQ origin supplied for a persistent
|
|
100
|
+
* HTTPS reverse proxy. Paths, credentials, query strings and fragments are
|
|
101
|
+
* refused because the value becomes a Host/Origin trust-boundary entry.
|
|
102
|
+
*/
|
|
103
|
+
export declare function normalizeHqPublicOrigin(value: string): string;
|
|
98
104
|
export declare function hqRuntimeMarkerPath(dataDir: string): string;
|
|
99
105
|
/** Non-internal IPv4 addresses, so we can print URLs reachable from other machines. */
|
|
100
106
|
export declare function lanIPv4Addresses(): string[];
|
|
@@ -10,9 +10,10 @@ import {
|
|
|
10
10
|
readLocalSubagentTranscript,
|
|
11
11
|
sanitizeApiError,
|
|
12
12
|
startHqServer
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-PQZPHGGD.js";
|
|
14
14
|
import "./chunk-HXJYHAR4.js";
|
|
15
|
-
import "./chunk-
|
|
15
|
+
import "./chunk-5EA6OTLJ.js";
|
|
16
|
+
import "./chunk-Q7E3BPDU.js";
|
|
16
17
|
export {
|
|
17
18
|
DEFAULT_HOST,
|
|
18
19
|
DEFAULT_PORT,
|
|
@@ -26,4 +27,4 @@ export {
|
|
|
26
27
|
sanitizeApiError,
|
|
27
28
|
startHqServer
|
|
28
29
|
};
|
|
29
|
-
//# sourceMappingURL=hq-server-
|
|
30
|
+
//# sourceMappingURL=hq-server-MZ7DV6X7.js.map
|
package/dist/hq-server.d.ts
CHANGED
|
@@ -30,12 +30,16 @@ interface HqServerOptions {
|
|
|
30
30
|
clientCleanupIntervalMs?: number;
|
|
31
31
|
sessionSnapshotTtlMs?: number;
|
|
32
32
|
password?: string;
|
|
33
|
+
/** Keep an existing Settings-managed password instead of reapplying the startup secret. */
|
|
34
|
+
bootstrapPasswordOnly?: boolean;
|
|
33
35
|
allowInsecureOpen?: boolean;
|
|
34
36
|
secureCookies?: boolean;
|
|
35
37
|
requireBrowserAuth?: boolean;
|
|
36
38
|
allowFileOrigin?: boolean;
|
|
37
39
|
tokenTtlMs?: number;
|
|
38
40
|
trustedProxyHops?: number;
|
|
41
|
+
/** Optional exact-IP/CIDR admission list. Empty/absent means unrestricted. */
|
|
42
|
+
ipAllowlist?: readonly string[];
|
|
39
43
|
}
|
|
40
44
|
import type { HqServerHandle } from './hq-server/handle-types.js';
|
|
41
45
|
export type { HqFirstRunSetup, HqServerHandle, HqStartupConnectionInfo, } from './hq-server/handle-types.js';
|