@wrongstack/cli 1.0.4 → 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-AB54FWM2.js → acp-GOQURNC2.js} +9 -6
- package/dist/{auth-WDMSNZ3S.js → auth-WBXXIQUO.js} +2 -2
- package/dist/boot/short-circuit-desktop.d.ts +1 -0
- package/dist/{chunk-ONOT5OJ2.js → chunk-5A5GCSJC.js} +1 -1
- package/dist/{chunk-I7V2OYVH.js → chunk-5EA6OTLJ.js} +3 -6
- package/dist/{chunk-O4A3HHKD.js → chunk-672UXYL3.js} +2 -2
- package/dist/{chunk-U6PGWDMZ.js → chunk-NR7BD73C.js} +2 -2
- package/dist/{chunk-3I5ZJXM5.js → chunk-PQZPHGGD.js} +128 -61
- package/dist/{chunk-BIWE22O5.js → chunk-QDQPF4PB.js} +5 -5
- package/dist/{chunk-VFCJOWJB.js → chunk-VPS2SJW4.js} +4 -5
- package/dist/{cli-context-WFC3BNGM.js → cli-context-BYKJDAFF.js} +19 -10
- package/dist/{cli-main-GU3YZLMO.js → cli-main-5WZBH3WA.js} +127 -109
- package/dist/{execution-RAZDDQFC.js → execution-Z6A26EG6.js} +26 -26
- package/dist/{hq-5XHEIIH6.js → hq-V4PHEETW.js} +3 -3
- package/dist/hq-server/auth-state.d.ts +6 -0
- package/dist/hq-server/auth.d.ts +10 -1
- 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/types.d.ts +18 -0
- package/dist/{hq-server-CNUTBWTK.js → hq-server-MZ7DV6X7.js} +3 -3
- package/dist/index.js +8 -5
- package/dist/{mcp-HOMLC4WE.js → mcp-FX7TKPTV.js} +10 -8
- package/dist/mcp-serve.d.ts +31 -0
- package/dist/{short-circuit-flags-YDMLAQ5R.js → short-circuit-flags-KBTYSJTW.js} +2 -2
- package/dist/{subcommands-7N4RIK6H.js → subcommands-DEYSYHIM.js} +2 -2
- package/package.json +28 -28
|
@@ -649,13 +649,16 @@ async function runACPWebSocketServer(deps, port) {
|
|
|
649
649
|
const acpToken = process.env["WRONGSTACK_ACP_TOKEN"]?.trim() || randomBytes(32).toString("hex");
|
|
650
650
|
const gate = createAcpConnectionGate({ host, port, token: acpToken });
|
|
651
651
|
let liveConnections = 0;
|
|
652
|
-
const
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
socket.close(verdict.code ?? 1008, verdict.reason ?? "forbidden");
|
|
652
|
+
const verifyClient = (info, cb) => {
|
|
653
|
+
const verdict = gate.check(info.req, liveConnections);
|
|
654
|
+
if (verdict.ok) {
|
|
655
|
+
cb(true);
|
|
657
656
|
return;
|
|
658
657
|
}
|
|
658
|
+
cb(false, 403, verdict.reason ?? "forbidden");
|
|
659
|
+
};
|
|
660
|
+
const wss = new WebSocketServer({ host, port, maxPayload: 20 * 1024 * 1024, verifyClient });
|
|
661
|
+
wss.on("connection", (socket) => {
|
|
659
662
|
liveConnections++;
|
|
660
663
|
socket.once("close", () => {
|
|
661
664
|
liveConnections--;
|
|
@@ -1099,4 +1102,4 @@ ${renderAcpBenchText(result)}
|
|
|
1099
1102
|
export {
|
|
1100
1103
|
acpCmd
|
|
1101
1104
|
};
|
|
1102
|
-
//# sourceMappingURL=acp-
|
|
1105
|
+
//# sourceMappingURL=acp-GOQURNC2.js.map
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
runOAuthLoginKind,
|
|
15
15
|
runOAuthLoginMenu,
|
|
16
16
|
validateFamily
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-5A5GCSJC.js";
|
|
18
18
|
import {
|
|
19
19
|
renderAuthLocalHelpToString,
|
|
20
20
|
wantsLocalHelp
|
|
@@ -619,4 +619,4 @@ async function runAuthRemove(deps, providerId) {
|
|
|
619
619
|
export {
|
|
620
620
|
authCmd
|
|
621
621
|
};
|
|
622
|
-
//# sourceMappingURL=auth-
|
|
622
|
+
//# sourceMappingURL=auth-WBXXIQUO.js.map
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export declare function stripDesktopLauncherArgs(argv: string[]): string[];
|
|
2
|
+
export declare function selectDesktopLauncherExecutable(execPath: string, runningUnderBun: boolean, nodeOverride?: string): string;
|
|
2
3
|
export declare function handleDesktopShortCircuit(flags: Record<string, string | boolean>, argv: string[]): Promise<number | null>;
|
|
3
4
|
//# sourceMappingURL=short-circuit-desktop.d.ts.map
|
|
@@ -16,6 +16,7 @@ import * as fs from "node:fs/promises";
|
|
|
16
16
|
import * as os2 from "node:os";
|
|
17
17
|
import * as path from "node:path";
|
|
18
18
|
import { resolveHqDataDir } from "@wrongstack/core/hq";
|
|
19
|
+
import { isSafeSessionId as coreIsSafeSessionId } from "@wrongstack/core/utils";
|
|
19
20
|
import { sanitizeApiError, scrubErrorDetail } from "@wrongstack/core/security";
|
|
20
21
|
var TRANSCRIPT_RING_MAX = 4e3;
|
|
21
22
|
var MAX_TRANSCRIPT_SESSIONS = 400;
|
|
@@ -99,11 +100,7 @@ function isSafePathSegment(value) {
|
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
101
102
|
function isSafeSessionId(value) {
|
|
102
|
-
|
|
103
|
-
if (value.includes("\\") || value.includes("\0") || value.includes(":")) return false;
|
|
104
|
-
const parts = value.split("/");
|
|
105
|
-
if (parts.length > 2) return false;
|
|
106
|
-
return parts.every((p) => p.length > 0 && p !== "." && p !== "..");
|
|
103
|
+
return coreIsSafeSessionId(value);
|
|
107
104
|
}
|
|
108
105
|
function displayHost(host) {
|
|
109
106
|
return host === "0.0.0.0" ? "127.0.0.1" : host;
|
|
@@ -239,4 +236,4 @@ export {
|
|
|
239
236
|
recordTimeseriesSignal,
|
|
240
237
|
sanitizeApiError
|
|
241
238
|
};
|
|
242
|
-
//# sourceMappingURL=chunk-
|
|
239
|
+
//# sourceMappingURL=chunk-5EA6OTLJ.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
// src/preflight.ts
|
|
3
|
-
import { ensureSessionShell } from "@wrongstack/tools";
|
|
3
|
+
import { ensureSessionShell } from "@wrongstack/tools/session-shell";
|
|
4
4
|
|
|
5
5
|
// src/cli-update-notice.ts
|
|
6
6
|
import { writeErr } from "@wrongstack/core/utils";
|
|
@@ -62,4 +62,4 @@ export {
|
|
|
62
62
|
applySessionShellDefault,
|
|
63
63
|
runPreflight
|
|
64
64
|
};
|
|
65
|
-
//# sourceMappingURL=chunk-
|
|
65
|
+
//# sourceMappingURL=chunk-672UXYL3.js.map
|
|
@@ -22,7 +22,7 @@ async function handleHelpVersionShortCircuit(argv) {
|
|
|
22
22
|
}
|
|
23
23
|
if (earlyFlags["help"] === true) {
|
|
24
24
|
if (positional.length > 0) {
|
|
25
|
-
const { subcommandNames } = await import("./subcommands-
|
|
25
|
+
const { subcommandNames } = await import("./subcommands-DEYSYHIM.js");
|
|
26
26
|
if (subcommandNames.includes(positional[0])) {
|
|
27
27
|
const first = positional[0];
|
|
28
28
|
const deep = positional[1];
|
|
@@ -43,4 +43,4 @@ async function handleHelpVersionShortCircuit(argv) {
|
|
|
43
43
|
export {
|
|
44
44
|
handleHelpVersionShortCircuit
|
|
45
45
|
};
|
|
46
|
-
//# sourceMappingURL=chunk-
|
|
46
|
+
//# sourceMappingURL=chunk-NR7BD73C.js.map
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
sanitizeApiError,
|
|
25
25
|
truncateHqSummary,
|
|
26
26
|
writeInvalidBody
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-5EA6OTLJ.js";
|
|
28
28
|
import {
|
|
29
29
|
createHqIpAllowlist
|
|
30
30
|
} from "./chunk-Q7E3BPDU.js";
|
|
@@ -44,7 +44,7 @@ import {
|
|
|
44
44
|
hqTokenKey as hqTokenKey2,
|
|
45
45
|
isTokenExpired as isTokenExpired4,
|
|
46
46
|
mintHqCookieSecret as mintHqCookieSecret2,
|
|
47
|
-
mutateHqAuthFile as
|
|
47
|
+
mutateHqAuthFile as mutateHqAuthFile4,
|
|
48
48
|
toAlertMessage,
|
|
49
49
|
watchHqAuthFile
|
|
50
50
|
} from "@wrongstack/core/hq";
|
|
@@ -135,10 +135,15 @@ function hasTrustedRequestAuthority(req, boundHost, boundPort, trustedPublicOrig
|
|
|
135
135
|
return false;
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
|
-
function hasTrustedBrowserOrigin(req, boundHost, boundPort, trustedPublicOrigins = /* @__PURE__ */ new Set(), allowFileOrigin = false) {
|
|
138
|
+
function hasTrustedBrowserOrigin(req, boundHost, boundPort, trustedPublicOrigins = /* @__PURE__ */ new Set(), allowFileOrigin = false, credentialedSurface = false) {
|
|
139
139
|
if (!hasTrustedRequestAuthority(req, boundHost, boundPort, trustedPublicOrigins)) return false;
|
|
140
140
|
const origin = req.headers.origin;
|
|
141
|
-
if (origin === void 0)
|
|
141
|
+
if (origin === void 0) {
|
|
142
|
+
if (!credentialedSurface) {
|
|
143
|
+
return boundHost !== void 0 && isLoopbackHost(boundHost);
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
142
147
|
try {
|
|
143
148
|
const parsed = new URL(origin);
|
|
144
149
|
if (parsed.protocol === "file:") return allowFileOrigin;
|
|
@@ -222,7 +227,7 @@ function isCookieAuth(auth) {
|
|
|
222
227
|
return auth !== void 0 && auth.kind === "cookie";
|
|
223
228
|
}
|
|
224
229
|
function hqAuthRequired(mutableAuth, requireBrowserAuth) {
|
|
225
|
-
return requireBrowserAuth === true || mutableAuth.requireAuthFloor === true || mutableAuth.browserTokens.size > 0 || mutableAuth.passwordHash !== void 0;
|
|
230
|
+
return (requireBrowserAuth ?? mutableAuth.requireBrowserAuth) === true || mutableAuth.requireAuthFloor === true || mutableAuth.browserTokens.size > 0 || mutableAuth.passwordHash !== void 0;
|
|
226
231
|
}
|
|
227
232
|
function hqClientAuthRequired(mutableAuth) {
|
|
228
233
|
return mutableAuth.requireAuthFloor === true || mutableAuth.clientTokens.size > 0;
|
|
@@ -364,7 +369,8 @@ function createHqAuthState(authFile, dataDir, opts = {}) {
|
|
|
364
369
|
clientTokens: /* @__PURE__ */ new Set(),
|
|
365
370
|
browserTokenObjs: /* @__PURE__ */ new Map(),
|
|
366
371
|
clientTokenObjs: /* @__PURE__ */ new Map(),
|
|
367
|
-
alertRules: void 0
|
|
372
|
+
alertRules: void 0,
|
|
373
|
+
requireBrowserAuth: opts.requireBrowserAuth
|
|
368
374
|
};
|
|
369
375
|
projectAuthFile(mutableAuth, authFile);
|
|
370
376
|
return {
|
|
@@ -727,7 +733,32 @@ async function serveHqStatic(_req, res, urlPath, distDir) {
|
|
|
727
733
|
}
|
|
728
734
|
|
|
729
735
|
// src/hq-server/routes/auth/common.ts
|
|
730
|
-
import { isLoopbackHost as isLoopbackHost2 } from "@wrongstack/core/hq";
|
|
736
|
+
import { isLoopbackHost as isLoopbackHost2, mutateHqAuthFile } from "@wrongstack/core/hq";
|
|
737
|
+
import { verifyTotpCounter } from "@wrongstack/core/security";
|
|
738
|
+
function recordVerifyFailure(loginAttempts, clientIp) {
|
|
739
|
+
const prev = loginAttempts.get(clientIp);
|
|
740
|
+
const count = (prev?.count ?? 0) + 1;
|
|
741
|
+
loginAttempts.recordFailure(clientIp);
|
|
742
|
+
return count;
|
|
743
|
+
}
|
|
744
|
+
async function consumeTotpCode(code, mutableAuth, dataDir, applyAuthFile) {
|
|
745
|
+
if (!mutableAuth.totpSecret) return "invalid";
|
|
746
|
+
const matchedCounter = verifyTotpCounter(code, mutableAuth.totpSecret);
|
|
747
|
+
if (matchedCounter === void 0) return "invalid";
|
|
748
|
+
if (mutableAuth.totpLastUsedCounter !== void 0 && matchedCounter <= mutableAuth.totpLastUsedCounter) {
|
|
749
|
+
return "replayed";
|
|
750
|
+
}
|
|
751
|
+
mutableAuth.totpLastUsedCounter = matchedCounter;
|
|
752
|
+
try {
|
|
753
|
+
const next = await mutateHqAuthFile(dataDir, (current) => ({
|
|
754
|
+
...current,
|
|
755
|
+
totpLastUsedCounter: matchedCounter
|
|
756
|
+
}));
|
|
757
|
+
applyAuthFile(next);
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
760
|
+
return "ok";
|
|
761
|
+
}
|
|
731
762
|
function isLoopbackRequest(req) {
|
|
732
763
|
const address = req.socket.remoteAddress?.replace(/^::ffff:/, "");
|
|
733
764
|
return address !== void 0 && isLoopbackHost2(address);
|
|
@@ -775,7 +806,8 @@ import { randomUUID } from "node:crypto";
|
|
|
775
806
|
import {
|
|
776
807
|
hashHqPassword,
|
|
777
808
|
mintHqCookieSecret,
|
|
778
|
-
mutateHqAuthFile,
|
|
809
|
+
mutateHqAuthFile as mutateHqAuthFile2,
|
|
810
|
+
hqPasswordNeedsUpgrade,
|
|
779
811
|
verifyHqPassword
|
|
780
812
|
} from "@wrongstack/core/hq";
|
|
781
813
|
|
|
@@ -844,7 +876,7 @@ async function handleApiAuthStatus(req, res, url, mutableAuth, sessions, require
|
|
|
844
876
|
})
|
|
845
877
|
);
|
|
846
878
|
}
|
|
847
|
-
async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, secureCookies, trustedProxyHops, sessionCapabilities) {
|
|
879
|
+
async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, secureCookies, trustedProxyHops, dataDir, sessionCapabilities) {
|
|
848
880
|
if (!mutableAuth.passwordHash) {
|
|
849
881
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
850
882
|
res.end(
|
|
@@ -895,6 +927,14 @@ async function handleApiLogin(req, res, mutableAuth, sessions, loginAttempts, se
|
|
|
895
927
|
return;
|
|
896
928
|
}
|
|
897
929
|
const ok = await verifyHqPassword(body.password, mutableAuth.passwordHash);
|
|
930
|
+
if (ok && hqPasswordNeedsUpgrade(mutableAuth.passwordHash)) {
|
|
931
|
+
try {
|
|
932
|
+
const upgraded = await hashHqPassword(body.password);
|
|
933
|
+
await mutateHqAuthFile2(dataDir, (current) => ({ ...current, passwordHash: upgraded }));
|
|
934
|
+
mutableAuth.passwordHash = upgraded;
|
|
935
|
+
} catch {
|
|
936
|
+
}
|
|
937
|
+
}
|
|
898
938
|
if (!ok || !mutableAuth.cookieSecret) {
|
|
899
939
|
loginAttempts.recordFailure(clientIp, body.password);
|
|
900
940
|
res.writeHead(401, { "Content-Type": "application/json" });
|
|
@@ -953,7 +993,7 @@ async function handleApiLogout(req, res, mutableAuth, sessions, secureCookies) {
|
|
|
953
993
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
954
994
|
res.end(JSON.stringify({ loggedIn: false }));
|
|
955
995
|
}
|
|
956
|
-
async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secureCookies, requireBrowserAuth, applyAuthFile) {
|
|
996
|
+
async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secureCookies, requireBrowserAuth, applyAuthFile, loginAttempts, trustedProxyHops) {
|
|
957
997
|
const auth = authenticateBrowserRequest(
|
|
958
998
|
req,
|
|
959
999
|
new URL(req.url ?? "/", "http://localhost"),
|
|
@@ -993,21 +1033,43 @@ async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secur
|
|
|
993
1033
|
res.end(JSON.stringify({ error: { code: "BAD_REQUEST", message: "Invalid JSON body." } }));
|
|
994
1034
|
return;
|
|
995
1035
|
}
|
|
996
|
-
|
|
997
|
-
|
|
1036
|
+
if (!localOpenBootstrap && mutableAuth.passwordHash !== void 0) {
|
|
1037
|
+
const clientIp = resolveClientAddress(req, trustedProxyHops);
|
|
1038
|
+
const { blocked, retryAfter } = loginAttempts.checkBlocked(clientIp);
|
|
1039
|
+
if (blocked) {
|
|
1040
|
+
res.writeHead(429, {
|
|
1041
|
+
"Content-Type": "application/json",
|
|
1042
|
+
"Retry-After": String(retryAfter)
|
|
1043
|
+
});
|
|
1044
|
+
res.end(
|
|
1045
|
+
JSON.stringify({
|
|
1046
|
+
error: { code: "RATE_LIMITED", message: "Too many attempts. Try again later." }
|
|
1047
|
+
})
|
|
1048
|
+
);
|
|
1049
|
+
return;
|
|
1050
|
+
}
|
|
998
1051
|
const currentPassword = typeof body.currentPassword === "string" ? body.currentPassword : "";
|
|
999
|
-
|
|
1052
|
+
let confirmed = currentPassword.length > 0 && await verifyHqPassword(currentPassword, mutableAuth.passwordHash);
|
|
1053
|
+
let replayed = false;
|
|
1054
|
+
if (!confirmed && typeof body.code === "string" && mutableAuth.totpSecret) {
|
|
1055
|
+
const outcome = await consumeTotpCode(body.code, mutableAuth, dataDir, applyAuthFile);
|
|
1056
|
+
confirmed = outcome === "ok";
|
|
1057
|
+
replayed = outcome === "replayed";
|
|
1058
|
+
}
|
|
1059
|
+
if (!confirmed) {
|
|
1060
|
+
recordVerifyFailure(loginAttempts, clientIp);
|
|
1000
1061
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
1001
1062
|
res.end(
|
|
1002
1063
|
JSON.stringify({
|
|
1003
1064
|
error: {
|
|
1004
|
-
code: "INVALID_CURRENT_PASSWORD",
|
|
1005
|
-
message: "Current password is required to change or remove it."
|
|
1065
|
+
code: replayed ? "TOTP_ALREADY_USED" : "INVALID_CURRENT_PASSWORD",
|
|
1066
|
+
message: replayed ? "That authenticator code has already been used. Wait for the next one." : "Current password or a TOTP code is required to change or remove it."
|
|
1006
1067
|
}
|
|
1007
1068
|
})
|
|
1008
1069
|
);
|
|
1009
1070
|
return;
|
|
1010
1071
|
}
|
|
1072
|
+
loginAttempts.delete(clientIp);
|
|
1011
1073
|
}
|
|
1012
1074
|
if (req.method === "DELETE") {
|
|
1013
1075
|
if (requireBrowserAuth && mutableAuth.browserTokens.size === 0) {
|
|
@@ -1022,7 +1084,7 @@ async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secur
|
|
|
1022
1084
|
);
|
|
1023
1085
|
return;
|
|
1024
1086
|
}
|
|
1025
|
-
const next2 = await
|
|
1087
|
+
const next2 = await mutateHqAuthFile2(dataDir, (current) => {
|
|
1026
1088
|
const updated = { ...current };
|
|
1027
1089
|
delete updated.passwordHash;
|
|
1028
1090
|
delete updated.cookieSecret;
|
|
@@ -1059,7 +1121,7 @@ async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secur
|
|
|
1059
1121
|
}
|
|
1060
1122
|
const passwordHash = await hashHqPassword(newPassword);
|
|
1061
1123
|
const cookieSecret = mintHqCookieSecret();
|
|
1062
|
-
const next = await
|
|
1124
|
+
const next = await mutateHqAuthFile2(dataDir, (current) => ({
|
|
1063
1125
|
...current,
|
|
1064
1126
|
passwordHash,
|
|
1065
1127
|
cookieSecret
|
|
@@ -1083,25 +1145,18 @@ async function handleApiPassword(req, res, mutableAuth, sessions, dataDir, secur
|
|
|
1083
1145
|
|
|
1084
1146
|
// src/hq-server/routes/auth/totp-routes.ts
|
|
1085
1147
|
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
1086
|
-
import { mutateHqAuthFile as
|
|
1148
|
+
import { mutateHqAuthFile as mutateHqAuthFile3, verifyHqPassword as verifyHqPassword2 } from "@wrongstack/core/hq";
|
|
1087
1149
|
import {
|
|
1088
1150
|
buildOtpAuthUri,
|
|
1089
1151
|
generateRecoveryCodes,
|
|
1090
1152
|
generateTotpSecret,
|
|
1091
1153
|
hashRecoveryCode,
|
|
1092
1154
|
verifyRecoveryCode,
|
|
1093
|
-
verifyTotp
|
|
1094
|
-
verifyTotpCounter
|
|
1155
|
+
verifyTotp
|
|
1095
1156
|
} from "@wrongstack/core/security";
|
|
1096
1157
|
var PENDING_2FA_TTL_MS = 5 * 6e4;
|
|
1097
1158
|
var recoveryCodeLock = Promise.resolve();
|
|
1098
1159
|
var MAX_2FA_VERIFY_FAILURES = 5;
|
|
1099
|
-
function recordVerifyFailure(loginAttempts, clientIp) {
|
|
1100
|
-
const prev = loginAttempts.get(clientIp);
|
|
1101
|
-
const count = (prev?.count ?? 0) + 1;
|
|
1102
|
-
loginAttempts.recordFailure(clientIp);
|
|
1103
|
-
return count;
|
|
1104
|
-
}
|
|
1105
1160
|
async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, loginAttempts, dataDir, secureCookies, applyAuthFile, trustedProxyHops) {
|
|
1106
1161
|
if (!mutableAuth.cookieSecret) {
|
|
1107
1162
|
res.writeHead(503, { "Content-Type": "application/json" });
|
|
@@ -1164,9 +1219,9 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
|
|
|
1164
1219
|
);
|
|
1165
1220
|
return;
|
|
1166
1221
|
}
|
|
1167
|
-
const
|
|
1168
|
-
const replayed =
|
|
1169
|
-
if (
|
|
1222
|
+
const outcome = await consumeTotpCode(body.code, mutableAuth, dataDir, applyAuthFile);
|
|
1223
|
+
const replayed = outcome === "replayed";
|
|
1224
|
+
if (outcome !== "ok") {
|
|
1170
1225
|
if (recordVerifyFailure(loginAttempts, clientIp) >= MAX_2FA_VERIFY_FAILURES) {
|
|
1171
1226
|
sessions.delete(sessionId);
|
|
1172
1227
|
}
|
|
@@ -1181,15 +1236,6 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
|
|
|
1181
1236
|
);
|
|
1182
1237
|
return;
|
|
1183
1238
|
}
|
|
1184
|
-
mutableAuth.totpLastUsedCounter = matchedCounter;
|
|
1185
|
-
try {
|
|
1186
|
-
const next = await mutateHqAuthFile2(dataDir, (current) => ({
|
|
1187
|
-
...current,
|
|
1188
|
-
totpLastUsedCounter: matchedCounter
|
|
1189
|
-
}));
|
|
1190
|
-
applyAuthFile(next);
|
|
1191
|
-
} catch {
|
|
1192
|
-
}
|
|
1193
1239
|
loginAttempts.clearOnSuccess(clientIp);
|
|
1194
1240
|
sessions.delete(sessionId);
|
|
1195
1241
|
const fullSessionId = randomUUID2();
|
|
@@ -1241,7 +1287,7 @@ async function handleApiLoginVerify(req, res, _url, mutableAuth, sessions, login
|
|
|
1241
1287
|
releaseLock = resolve;
|
|
1242
1288
|
});
|
|
1243
1289
|
try {
|
|
1244
|
-
const next = await
|
|
1290
|
+
const next = await mutateHqAuthFile3(dataDir, (current) => {
|
|
1245
1291
|
const diskHashes = current.totpRecoveryCodes ?? [];
|
|
1246
1292
|
const idx = diskHashes.indexOf(usedHash);
|
|
1247
1293
|
if (idx === -1) {
|
|
@@ -1311,7 +1357,7 @@ async function handleApiTotpSetup(req, res, mutableAuth, sessions, dataDir, appl
|
|
|
1311
1357
|
}
|
|
1312
1358
|
const secret = generateTotpSecret();
|
|
1313
1359
|
const uri = buildOtpAuthUri(secret, "HQ");
|
|
1314
|
-
const next = await
|
|
1360
|
+
const next = await mutateHqAuthFile3(dataDir, (current) => ({
|
|
1315
1361
|
...current,
|
|
1316
1362
|
totpPendingSecret: secret
|
|
1317
1363
|
}));
|
|
@@ -1362,7 +1408,7 @@ async function handleApiTotpEnable(req, res, mutableAuth, sessions, dataDir, app
|
|
|
1362
1408
|
}
|
|
1363
1409
|
const recoveryCodes = generateRecoveryCodes();
|
|
1364
1410
|
const recoveryHashes = recoveryCodes.map(hashRecoveryCode);
|
|
1365
|
-
const next = await
|
|
1411
|
+
const next = await mutateHqAuthFile3(dataDir, (current) => {
|
|
1366
1412
|
const updated = { ...current };
|
|
1367
1413
|
updated.totpSecret = pendingSecret;
|
|
1368
1414
|
delete updated.totpPendingSecret;
|
|
@@ -1399,8 +1445,11 @@ async function handleApiTotpDisable(req, res, mutableAuth, sessions, loginAttemp
|
|
|
1399
1445
|
if (typeof body.password === "string" && body.password.length > 0 && mutableAuth.passwordHash) {
|
|
1400
1446
|
confirmed = await verifyHqPassword2(body.password, mutableAuth.passwordHash);
|
|
1401
1447
|
}
|
|
1448
|
+
let replayed = false;
|
|
1402
1449
|
if (!confirmed && typeof body.code === "string" && mutableAuth.totpSecret) {
|
|
1403
|
-
|
|
1450
|
+
const outcome = await consumeTotpCode(body.code, mutableAuth, dataDir, applyAuthFile);
|
|
1451
|
+
confirmed = outcome === "ok";
|
|
1452
|
+
replayed = outcome === "replayed";
|
|
1404
1453
|
}
|
|
1405
1454
|
if (!confirmed) {
|
|
1406
1455
|
recordVerifyFailure(loginAttempts, clientIp);
|
|
@@ -1408,15 +1457,15 @@ async function handleApiTotpDisable(req, res, mutableAuth, sessions, loginAttemp
|
|
|
1408
1457
|
res.end(
|
|
1409
1458
|
JSON.stringify({
|
|
1410
1459
|
error: {
|
|
1411
|
-
code: "CONFIRMATION_REQUIRED",
|
|
1412
|
-
message: "Provide the current password or a TOTP code."
|
|
1460
|
+
code: replayed ? "TOTP_ALREADY_USED" : "CONFIRMATION_REQUIRED",
|
|
1461
|
+
message: replayed ? "That authenticator code has already been used. Wait for the next one." : "Provide the current password or a TOTP code."
|
|
1413
1462
|
}
|
|
1414
1463
|
})
|
|
1415
1464
|
);
|
|
1416
1465
|
return;
|
|
1417
1466
|
}
|
|
1418
1467
|
loginAttempts.delete(clientIp);
|
|
1419
|
-
const next = await
|
|
1468
|
+
const next = await mutateHqAuthFile3(dataDir, (current) => {
|
|
1420
1469
|
const updated = { ...current };
|
|
1421
1470
|
delete updated.totpSecret;
|
|
1422
1471
|
delete updated.totpPendingSecret;
|
|
@@ -2830,7 +2879,11 @@ function createHqRouter(deps) {
|
|
|
2830
2879
|
host,
|
|
2831
2880
|
listeningPort(),
|
|
2832
2881
|
trustedPublicOrigins,
|
|
2833
|
-
allowFileOrigin
|
|
2882
|
+
allowFileOrigin,
|
|
2883
|
+
// WS-SEC-06: tells the guard whether anything else can authenticate
|
|
2884
|
+
// this request. In open mode a missing Origin is only acceptable on
|
|
2885
|
+
// a loopback bind.
|
|
2886
|
+
hqAuthRequired(mutableAuth, requireBrowserAuth)
|
|
2834
2887
|
)) {
|
|
2835
2888
|
res.writeHead(403, { "Content-Type": "application/json" });
|
|
2836
2889
|
res.end(JSON.stringify({ error: "forbidden: untrusted request origin" }));
|
|
@@ -2912,6 +2965,7 @@ function createHqRouter(deps) {
|
|
|
2912
2965
|
loginAttempts,
|
|
2913
2966
|
secureCookies,
|
|
2914
2967
|
trustedProxyHops,
|
|
2968
|
+
dataDir,
|
|
2915
2969
|
url.pathname === "/api/mobile/login" ? ["control.enqueue"] : void 0
|
|
2916
2970
|
);
|
|
2917
2971
|
return;
|
|
@@ -2985,7 +3039,9 @@ function createHqRouter(deps) {
|
|
|
2985
3039
|
dataDir,
|
|
2986
3040
|
secureCookies,
|
|
2987
3041
|
requireBrowserAuth,
|
|
2988
|
-
applyAuthFile
|
|
3042
|
+
applyAuthFile,
|
|
3043
|
+
loginAttempts,
|
|
3044
|
+
trustedProxyHops
|
|
2989
3045
|
);
|
|
2990
3046
|
return;
|
|
2991
3047
|
}
|
|
@@ -3466,6 +3522,7 @@ import { randomUUID as randomUUID5 } from "node:crypto";
|
|
|
3466
3522
|
import {
|
|
3467
3523
|
HQ_PROTOCOL_VERSION as HQ_PROTOCOL_VERSION2,
|
|
3468
3524
|
parseHqFrame,
|
|
3525
|
+
redactHqEvent as redactHqEvent2,
|
|
3469
3526
|
resolveHqRedactionPolicy,
|
|
3470
3527
|
tightenHqRedactionPolicy as tightenHqRedactionPolicy2,
|
|
3471
3528
|
tokenHasCapability as tokenHasCapability3
|
|
@@ -4128,16 +4185,22 @@ function handleClient(ws, clients, browsers, eventLog, auth, snapshotBroadcaster
|
|
|
4128
4185
|
sendGuarded(ws, JSON.stringify({ type: "hq.kanban_snapshot", payload: p }));
|
|
4129
4186
|
}).catch(() => void 0);
|
|
4130
4187
|
}
|
|
4131
|
-
const event =
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4188
|
+
const event = redactHqEvent2(
|
|
4189
|
+
{
|
|
4190
|
+
id: randomUUID5(),
|
|
4191
|
+
type: "client.hello",
|
|
4192
|
+
schemaVersion: HQ_PROTOCOL_VERSION2,
|
|
4193
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4194
|
+
clientId: payload.client.clientId,
|
|
4195
|
+
projectId: payload.project.projectId,
|
|
4196
|
+
seq: 0,
|
|
4197
|
+
payload: { client: payload.client, project: payload.project }
|
|
4198
|
+
},
|
|
4199
|
+
{
|
|
4200
|
+
policy: tightenHqRedactionPolicy2(declaredRedactionPolicy, auth.getOperatorPolicy()),
|
|
4201
|
+
...payload.project.projectRoot ? { projectRoot: payload.project.projectRoot } : {}
|
|
4202
|
+
}
|
|
4203
|
+
).value;
|
|
4141
4204
|
persistEvent(event);
|
|
4142
4205
|
snapshotBroadcaster.broadcast();
|
|
4143
4206
|
broadcastEvent(event, browsers);
|
|
@@ -4251,7 +4314,10 @@ function handleHqUpgrade(req, socket, head, deps) {
|
|
|
4251
4314
|
deps.host,
|
|
4252
4315
|
deps.listeningPort(),
|
|
4253
4316
|
deps.trustedPublicOrigins,
|
|
4254
|
-
deps.allowFileOrigin
|
|
4317
|
+
deps.allowFileOrigin,
|
|
4318
|
+
// WS-SEC-06 — see the guard. `/ws/browser` and `/ws/client` are the two
|
|
4319
|
+
// surfaces this branch left uncontrolled in open mode.
|
|
4320
|
+
hqAuthRequired(deps.mutableAuth, deps.requireBrowserAuth)
|
|
4255
4321
|
)) {
|
|
4256
4322
|
socket.write(
|
|
4257
4323
|
"HTTP/1.1 403 Forbidden\r\nContent-Type: application/json\r\nConnection: close\r\n\r\n" + JSON.stringify({
|
|
@@ -4396,14 +4462,15 @@ async function startHqServerWithAuth(options, host, port, dataDir, firstRunAuth)
|
|
|
4396
4462
|
}
|
|
4397
4463
|
};
|
|
4398
4464
|
const authState = createHqAuthState(authFile, dataDir, {
|
|
4399
|
-
onApplied: (live) => reassessExposureFloor(live)
|
|
4465
|
+
onApplied: (live) => reassessExposureFloor(live),
|
|
4466
|
+
requireBrowserAuth: options.requireBrowserAuth
|
|
4400
4467
|
});
|
|
4401
4468
|
const { mutableAuth } = authState;
|
|
4402
4469
|
const loginAttempts = new LoginAttemptStore(dataDir);
|
|
4403
4470
|
await loginAttempts.load();
|
|
4404
4471
|
if (!mutableAuth.cookieSecret && mutableAuth.browserTokens.size > 0) {
|
|
4405
4472
|
const secret = mintHqCookieSecret2();
|
|
4406
|
-
const next = await
|
|
4473
|
+
const next = await mutateHqAuthFile4(dataDir, (current) => ({
|
|
4407
4474
|
...current,
|
|
4408
4475
|
cookieSecret: secret
|
|
4409
4476
|
}));
|
|
@@ -4832,4 +4899,4 @@ export {
|
|
|
4832
4899
|
startHqServer,
|
|
4833
4900
|
HqInsecureExposureError2 as HqInsecureExposureError
|
|
4834
4901
|
};
|
|
4835
|
-
//# sourceMappingURL=chunk-
|
|
4902
|
+
//# sourceMappingURL=chunk-PQZPHGGD.js.map
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
|
|
2
2
|
// src/subcommands/index.ts
|
|
3
3
|
var loaders = {
|
|
4
|
-
acp: async () => (await import("./acp-
|
|
4
|
+
acp: async () => (await import("./acp-GOQURNC2.js")).acpCmd,
|
|
5
5
|
init: async () => (await import("./init-DRMMFG2G.js")).initCmd,
|
|
6
|
-
auth: async () => (await import("./auth-
|
|
6
|
+
auth: async () => (await import("./auth-WBXXIQUO.js")).authCmd,
|
|
7
7
|
update: async () => (await import("./update-5DPB4YD4.js")).updateCmd,
|
|
8
8
|
sessions: async () => (await import("./sessions-config-UL45XKX7.js")).sessionsCmd,
|
|
9
9
|
config: async () => (await import("./sessions-config-UL45XKX7.js")).configCmd,
|
|
@@ -16,7 +16,7 @@ var loaders = {
|
|
|
16
16
|
skills: async () => (await import("./tools-skills-GH24UCBY.js")).skillsCmd,
|
|
17
17
|
providers: async () => (await import("./providers-models-6MOGAOVK.js")).providersCmd,
|
|
18
18
|
models: async () => (await import("./providers-models-6MOGAOVK.js")).modelsCmd,
|
|
19
|
-
mcp: async () => (await import("./mcp-
|
|
19
|
+
mcp: async () => (await import("./mcp-FX7TKPTV.js")).mcpCmd,
|
|
20
20
|
plugin: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
|
|
21
21
|
plugins: async () => (await import("./plugin-usage-M6KET4OZ.js")).pluginCmd,
|
|
22
22
|
diag: async () => (await import("./diag-doctor-PCER6KHH.js")).diagCmd,
|
|
@@ -31,7 +31,7 @@ var loaders = {
|
|
|
31
31
|
quick: async () => (await import("./quick-CNB4Z3HS.js")).quickCmd,
|
|
32
32
|
bench: async () => (await import("./bench-YBU6BVQP.js")).benchCmd,
|
|
33
33
|
chronicle: async () => (await import("./chronicle-UTFETHP6.js")).chronicleCmd,
|
|
34
|
-
hq: async () => (await import("./hq-
|
|
34
|
+
hq: async () => (await import("./hq-V4PHEETW.js")).hqCmd,
|
|
35
35
|
mailbox: async () => (await import("./mailbox-serve-X3CR6VFW.js")).mailboxServeCmd,
|
|
36
36
|
permissions: async () => (await import("./permissions-CFDLXETM.js")).permissionsCmd,
|
|
37
37
|
project: async () => (await import("./project-AOQI5XTK.js")).projectCmd,
|
|
@@ -51,4 +51,4 @@ export {
|
|
|
51
51
|
subcommands,
|
|
52
52
|
subcommandNames
|
|
53
53
|
};
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
54
|
+
//# sourceMappingURL=chunk-QDQPF4PB.js.map
|
|
@@ -74,15 +74,14 @@ function stringifyInput(input) {
|
|
|
74
74
|
var PREVIEW_MAX_LINES = 40;
|
|
75
75
|
var PREVIEW_MAX_CHARS = 8e3;
|
|
76
76
|
function clipPreview(body, decorate = (l) => l) {
|
|
77
|
-
const
|
|
78
|
-
const { text, truncated } = sanitizeTerminalPreview(body, {
|
|
77
|
+
const { text, truncated, sanitizedLength, sanitizedLines } = sanitizeTerminalPreview(body, {
|
|
79
78
|
maxLines: PREVIEW_MAX_LINES,
|
|
80
79
|
maxChars: PREVIEW_MAX_CHARS
|
|
81
80
|
});
|
|
82
81
|
const rendered = text.split("\n").map(decorate).join("\n");
|
|
83
82
|
if (!truncated) return rendered;
|
|
84
|
-
const hiddenLines =
|
|
85
|
-
const detail = hiddenLines > 0 ? `${hiddenLines} more line${hiddenLines === 1 ? "" : "s"} not shown` : `${
|
|
83
|
+
const hiddenLines = sanitizedLines - text.split("\n").length;
|
|
84
|
+
const detail = hiddenLines > 0 ? `${hiddenLines} more line${hiddenLines === 1 ? "" : "s"} not shown` : `${sanitizedLength - text.length} more characters not shown`;
|
|
86
85
|
return `${rendered}
|
|
87
86
|
${color2.dim(`\u2026 ${detail} \u2014 review the file before approving`)}`;
|
|
88
87
|
}
|
|
@@ -481,4 +480,4 @@ export {
|
|
|
481
480
|
detectProjectFacts,
|
|
482
481
|
renderAgentsTemplate
|
|
483
482
|
};
|
|
484
|
-
//# sourceMappingURL=chunk-
|
|
483
|
+
//# sourceMappingURL=chunk-VPS2SJW4.js.map
|