@greppleai/grip 0.0.1 → 0.1.4
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/CHANGELOG.md +114 -0
- package/README.md +13 -7
- package/dist/bun/cli.d.ts.map +1 -1
- package/dist/bun/cli.js +4 -2
- package/dist/bun/cli.js.map +1 -1
- package/dist/bun/embedded-assets.d.ts +3 -0
- package/dist/bun/embedded-assets.d.ts.map +1 -0
- package/dist/bun/embedded-assets.js +52 -0
- package/dist/bun/embedded-assets.js.map +1 -0
- package/dist/cli/args.d.ts.map +1 -1
- package/dist/cli/args.js +1 -0
- package/dist/cli/args.js.map +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +17 -2
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/extensions/types.d.ts +5 -1
- package/dist/core/extensions/types.d.ts.map +1 -1
- package/dist/core/extensions/types.js.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.d.ts +13 -4
- package/dist/core/remote-workspace-protocol/contract.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/contract.js +17 -1
- package/dist/core/remote-workspace-protocol/contract.js.map +1 -1
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts +16 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js +54 -0
- package/dist/core/remote-workspace-protocol/guest-shared-client.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts +23 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js +112 -0
- package/dist/core/remote-workspace-protocol/guest-shared-outbound-scheduler.js.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts +30 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js +243 -0
- package/dist/core/remote-workspace-protocol/guest-shared-uplink.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts +3 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js +31 -0
- package/dist/core/remote-workspace-protocol/lima-owner-directory.js.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts +9 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js +89 -0
- package/dist/core/remote-workspace-protocol/lima-owner-lock.js.map +1 -0
- package/dist/core/remote-workspace-protocol/session.d.ts +2 -0
- package/dist/core/remote-workspace-protocol/session.d.ts.map +1 -1
- package/dist/core/remote-workspace-protocol/session.js +3 -0
- package/dist/core/remote-workspace-protocol/session.js.map +1 -1
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts +71 -0
- package/dist/core/remote-workspace-protocol/shared-transport.d.ts.map +1 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js +68 -0
- package/dist/core/remote-workspace-protocol/shared-transport.js.map +1 -0
- package/dist/core/sandbox/command.d.ts +2 -4
- package/dist/core/sandbox/command.d.ts.map +1 -1
- package/dist/core/sandbox/command.js +24 -46
- package/dist/core/sandbox/command.js.map +1 -1
- package/dist/core/sandbox/config.d.ts +91 -0
- package/dist/core/sandbox/config.d.ts.map +1 -0
- package/dist/core/sandbox/config.js +88 -0
- package/dist/core/sandbox/config.js.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts +11 -0
- package/dist/core/sandbox/lima-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/lima-guest-binary.js +140 -0
- package/dist/core/sandbox/lima-guest-binary.js.map +1 -0
- package/dist/core/sandbox/lima.d.ts +22 -3
- package/dist/core/sandbox/lima.d.ts.map +1 -1
- package/dist/core/sandbox/lima.js +434 -42
- package/dist/core/sandbox/lima.js.map +1 -1
- package/dist/core/sandbox/shared-lima-roots.d.ts +11 -0
- package/dist/core/sandbox/shared-lima-roots.d.ts.map +1 -0
- package/dist/core/sandbox/shared-lima-roots.js +25 -0
- package/dist/core/sandbox/shared-lima-roots.js.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts +8 -0
- package/dist/core/sandbox/update-shared-guest-binary.d.ts.map +1 -0
- package/dist/core/sandbox/update-shared-guest-binary.js +100 -0
- package/dist/core/sandbox/update-shared-guest-binary.js.map +1 -0
- package/dist/core/settings-manager.d.ts +1 -5
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/tools/bash.d.ts.map +1 -1
- package/dist/core/tools/bash.js +25 -14
- package/dist/core/tools/bash.js.map +1 -1
- package/dist/core/tools/file-mutation-queue.d.ts +2 -0
- package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
- package/dist/core/tools/file-mutation-queue.js +9 -0
- package/dist/core/tools/file-mutation-queue.js.map +1 -1
- package/dist/core/tools/find.d.ts.map +1 -1
- package/dist/core/tools/find.js +2 -3
- package/dist/core/tools/find.js.map +1 -1
- package/dist/core/tools/grep.d.ts.map +1 -1
- package/dist/core/tools/grep.js +2 -3
- package/dist/core/tools/grep.js.map +1 -1
- package/dist/core/tools/ls.d.ts.map +1 -1
- package/dist/core/tools/ls.js +2 -3
- package/dist/core/tools/ls.js.map +1 -1
- package/dist/core/tools/operations.d.ts +13 -2
- package/dist/core/tools/operations.d.ts.map +1 -1
- package/dist/core/tools/operations.js +132 -36
- package/dist/core/tools/operations.js.map +1 -1
- package/dist/core/tools/read.d.ts.map +1 -1
- package/dist/core/tools/read.js +3 -3
- package/dist/core/tools/read.js.map +1 -1
- package/dist/core/tools/render-utils.d.ts +1 -0
- package/dist/core/tools/render-utils.d.ts.map +1 -1
- package/dist/core/tools/render-utils.js +5 -0
- package/dist/core/tools/render-utils.js.map +1 -1
- package/dist/core/tools/write.d.ts.map +1 -1
- package/dist/core/tools/write.js +2 -3
- package/dist/core/tools/write.js.map +1 -1
- package/dist/daemon/activity-budget.d.ts +10 -0
- package/dist/daemon/activity-budget.d.ts.map +1 -0
- package/dist/daemon/activity-budget.js +34 -0
- package/dist/daemon/activity-budget.js.map +1 -0
- package/dist/daemon/config.d.ts +2 -0
- package/dist/daemon/config.d.ts.map +1 -1
- package/dist/daemon/config.js +15 -0
- package/dist/daemon/config.js.map +1 -1
- package/dist/daemon/outbound-budget.d.ts +10 -0
- package/dist/daemon/outbound-budget.d.ts.map +1 -0
- package/dist/daemon/outbound-budget.js +30 -0
- package/dist/daemon/outbound-budget.js.map +1 -0
- package/dist/daemon/security.d.ts.map +1 -1
- package/dist/daemon/security.js +3 -1
- package/dist/daemon/security.js.map +1 -1
- package/dist/daemon/server.d.ts.map +1 -1
- package/dist/daemon/server.js +104 -24
- package/dist/daemon/server.js.map +1 -1
- package/dist/daemon/shared-connection.d.ts +33 -0
- package/dist/daemon/shared-connection.d.ts.map +1 -0
- package/dist/daemon/shared-connection.js +271 -0
- package/dist/daemon/shared-connection.js.map +1 -0
- package/dist/daemon/shared-revision.d.ts +5 -0
- package/dist/daemon/shared-revision.d.ts.map +1 -0
- package/dist/daemon/shared-revision.js +61 -0
- package/dist/daemon/shared-revision.js.map +1 -0
- package/dist/daemon/shared-roots.d.ts +9 -0
- package/dist/daemon/shared-roots.d.ts.map +1 -0
- package/dist/daemon/shared-roots.js +33 -0
- package/dist/daemon/shared-roots.js.map +1 -0
- package/dist/daemon/workspace-runtime.d.ts +1 -1
- package/dist/daemon/workspace-runtime.d.ts.map +1 -1
- package/dist/daemon/workspace-runtime.js +42 -10
- package/dist/daemon/workspace-runtime.js.map +1 -1
- package/dist/main.d.ts.map +1 -1
- package/dist/main.js +5 -3
- package/dist/main.js.map +1 -1
- package/dist/modes/interactive/components/assistant-message.d.ts +1 -0
- package/dist/modes/interactive/components/assistant-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/assistant-message.js +13 -3
- package/dist/modes/interactive/components/assistant-message.js.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/branch-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/branch-summary-message.js +5 -4
- package/dist/modes/interactive/components/branch-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.d.ts +4 -3
- package/dist/modes/interactive/components/compaction-summary-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/compaction-summary-message.js +5 -4
- package/dist/modes/interactive/components/compaction-summary-message.js.map +1 -1
- package/dist/modes/interactive/components/custom-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/custom-message.js +4 -3
- package/dist/modes/interactive/components/custom-message.js.map +1 -1
- package/dist/modes/interactive/components/hook-execution.d.ts +3 -0
- package/dist/modes/interactive/components/hook-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/hook-execution.js +20 -2
- package/dist/modes/interactive/components/hook-execution.js.map +1 -1
- package/dist/modes/interactive/components/message-card.d.ts +9 -0
- package/dist/modes/interactive/components/message-card.d.ts.map +1 -0
- package/dist/modes/interactive/components/message-card.js +24 -0
- package/dist/modes/interactive/components/message-card.js.map +1 -0
- package/dist/modes/interactive/components/skill-invocation-message.d.ts +4 -3
- package/dist/modes/interactive/components/skill-invocation-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/skill-invocation-message.js +5 -4
- package/dist/modes/interactive/components/skill-invocation-message.js.map +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts +1 -1
- package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
- package/dist/modes/interactive/components/tool-execution.js +27 -21
- package/dist/modes/interactive/components/tool-execution.js.map +1 -1
- package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
- package/dist/modes/interactive/components/user-message.js +3 -2
- package/dist/modes/interactive/components/user-message.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts +1 -3
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +106 -47
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/dist/modes/interactive/theme/theme.d.ts +3 -0
- package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
- package/dist/modes/interactive/theme/theme.js +10 -0
- package/dist/modes/interactive/theme/theme.js.map +1 -1
- package/dist/utils/clipboard-native.d.ts.map +1 -1
- package/dist/utils/clipboard-native.js +1 -1
- package/dist/utils/clipboard-native.js.map +1 -1
- package/dist/utils/image-resize.d.ts.map +1 -1
- package/dist/utils/image-resize.js +5 -0
- package/dist/utils/image-resize.js.map +1 -1
- package/dist/utils/photon.d.ts.map +1 -1
- package/dist/utils/photon.js +1 -0
- package/dist/utils/photon.js.map +1 -1
- package/docs/daemon.md +6 -4
- package/docs/docs.json +1 -1
- package/docs/extensions.md +3 -0
- package/docs/index.md +8 -12
- package/docs/packages.md +3 -3
- package/docs/quickstart.md +11 -19
- package/docs/sandbox.md +40 -117
- package/docs/settings.md +12 -11
- package/docs/termux.md +1 -18
- package/docs/usage.md +2 -2
- package/examples/README.md +0 -2
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/npm-shrinkwrap.json +12 -12
- package/package.json +5 -6
- package/dist/core/sandbox/docker.d.ts +0 -169
- package/dist/core/sandbox/docker.d.ts.map +0 -1
- package/dist/core/sandbox/docker.js +0 -633
- package/dist/core/sandbox/docker.js.map +0 -1
- package/examples/sandbox-docker/Dockerfile +0 -274
- package/examples/sandbox-docker/README.md +0 -88
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RemoteWorkspaceRequestError } from "../core/remote-workspace-protocol/session.js";
|
|
2
|
+
/** Counts running handlers across every v1 socket and v2 channel in this daemon. */
|
|
3
|
+
export class DaemonActivityBudget {
|
|
4
|
+
requests = 0;
|
|
5
|
+
transfers = 0;
|
|
6
|
+
limits;
|
|
7
|
+
constructor(limits) {
|
|
8
|
+
this.limits = limits;
|
|
9
|
+
}
|
|
10
|
+
async run(method, action) {
|
|
11
|
+
const transfer = method.startsWith("transfer.") || method === "artifact.read";
|
|
12
|
+
if (this.requests >= this.limits.maxGlobalRequests ||
|
|
13
|
+
(transfer && this.transfers >= this.limits.maxGlobalTransfers)) {
|
|
14
|
+
throw new RemoteWorkspaceRequestError({
|
|
15
|
+
code: "limit_exceeded",
|
|
16
|
+
message: "Daemon global active request or transfer limit reached",
|
|
17
|
+
executionState: "not_started",
|
|
18
|
+
retryable: true,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
this.requests++;
|
|
22
|
+
if (transfer)
|
|
23
|
+
this.transfers++;
|
|
24
|
+
try {
|
|
25
|
+
return await action();
|
|
26
|
+
}
|
|
27
|
+
finally {
|
|
28
|
+
this.requests--;
|
|
29
|
+
if (transfer)
|
|
30
|
+
this.transfers--;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=activity-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"activity-budget.js","sourceRoot":"","sources":["../../src/daemon/activity-budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,2BAA2B,EAAE,MAAM,8CAA8C,CAAC;AAG3F,oFAAoF;AACpF,MAAM,OAAO,oBAAoB;IACxB,QAAQ,GAAG,CAAC,CAAC;IACb,SAAS,GAAG,CAAC,CAAC;IACL,MAAM,CAAwE;IAC/F,YAAY,MAA6E,EAAE;QAC1F,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IAAA,CACrB;IAED,KAAK,CAAC,GAAG,CAAI,MAAc,EAAE,MAAwB,EAAc;QAClE,MAAM,QAAQ,GAAG,MAAM,CAAC,UAAU,CAAC,WAAW,CAAC,IAAI,MAAM,KAAK,eAAe,CAAC;QAC9E,IACC,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,MAAM,CAAC,iBAAiB;YAC9C,CAAC,QAAQ,IAAI,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,EAC7D,CAAC;YACF,MAAM,IAAI,2BAA2B,CAAC;gBACrC,IAAI,EAAE,gBAAgB;gBACtB,OAAO,EAAE,wDAAwD;gBACjE,cAAc,EAAE,aAAa;gBAC7B,SAAS,EAAE,IAAI;aACf,CAAC,CAAC;QACJ,CAAC;QACD,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,IAAI,QAAQ;YAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QAC/B,IAAI,CAAC;YACJ,OAAO,MAAM,MAAM,EAAE,CAAC;QACvB,CAAC;gBAAS,CAAC;YACV,IAAI,CAAC,QAAQ,EAAE,CAAC;YAChB,IAAI,QAAQ;gBAAE,IAAI,CAAC,SAAS,EAAE,CAAC;QAChC,CAAC;IAAA,CACD;CACD","sourcesContent":["import { RemoteWorkspaceRequestError } from \"../core/remote-workspace-protocol/session.ts\";\nimport type { DaemonConfiguration } from \"./config.ts\";\n\n/** Counts running handlers across every v1 socket and v2 channel in this daemon. */\nexport class DaemonActivityBudget {\n\tprivate requests = 0;\n\tprivate transfers = 0;\n\tprivate readonly limits: Pick<DaemonConfiguration, \"maxGlobalRequests\" | \"maxGlobalTransfers\">;\n\tconstructor(limits: Pick<DaemonConfiguration, \"maxGlobalRequests\" | \"maxGlobalTransfers\">) {\n\t\tthis.limits = limits;\n\t}\n\n\tasync run<T>(method: string, action: () => Promise<T>): Promise<T> {\n\t\tconst transfer = method.startsWith(\"transfer.\") || method === \"artifact.read\";\n\t\tif (\n\t\t\tthis.requests >= this.limits.maxGlobalRequests ||\n\t\t\t(transfer && this.transfers >= this.limits.maxGlobalTransfers)\n\t\t) {\n\t\t\tthrow new RemoteWorkspaceRequestError({\n\t\t\t\tcode: \"limit_exceeded\",\n\t\t\t\tmessage: \"Daemon global active request or transfer limit reached\",\n\t\t\t\texecutionState: \"not_started\",\n\t\t\t\tretryable: true,\n\t\t\t});\n\t\t}\n\t\tthis.requests++;\n\t\tif (transfer) this.transfers++;\n\t\ttry {\n\t\t\treturn await action();\n\t\t} finally {\n\t\t\tthis.requests--;\n\t\t\tif (transfer) this.transfers--;\n\t\t}\n\t}\n}\n"]}
|
package/dist/daemon/config.d.ts
CHANGED
|
@@ -10,6 +10,8 @@ export interface DaemonConfiguration {
|
|
|
10
10
|
readonly host: string;
|
|
11
11
|
readonly port: number;
|
|
12
12
|
readonly workspaceRoot: string;
|
|
13
|
+
/** Canonical fixed parent mount for explicitly enabled shared-channel mode. */
|
|
14
|
+
readonly sharedParentRoot?: string;
|
|
13
15
|
readonly temporaryRoot?: string;
|
|
14
16
|
readonly token?: string;
|
|
15
17
|
readonly allowedOrigins: readonly string[];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/daemon/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,6BAA6B,EAElC,MAAM,+CAA+C,CAAC;AAEvD,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AACvD,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,6BAA6B,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAiND,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAEhE;AA2KD,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,WAAW,GAAE,MAAM,CAAC,UAAwB,EAC5C,UAAU,SAAgB,GACxB,OAAO,CAAC,aAAa,CAAC,CAyFxB","sourcesContent":["import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport {\n\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\ttype RemoteWorkspaceProtocolLimits,\n\tvalidateRemoteWorkspaceProtocolLimits,\n} from \"../core/remote-workspace-protocol/contract.ts\";\n\nexport const DAEMON_WEBSOCKET_PATH = \"/grip/workspace\";\nexport const DAEMON_WEBSOCKET_PROTOCOL = \"grip.workspace.v1\";\n\nexport interface DaemonTlsConfiguration {\n\treadonly cert: Buffer;\n\treadonly key: Buffer;\n\treadonly passphrase?: string;\n}\n\nexport interface DaemonConfiguration {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly workspaceRoot: string;\n\treadonly temporaryRoot?: string;\n\treadonly token?: string;\n\treadonly allowedOrigins: readonly string[];\n\treadonly tls?: DaemonTlsConfiguration;\n\treadonly allowInsecureTransport: boolean;\n\treadonly allowProcessExec: boolean;\n\treadonly allowRoot: boolean;\n\treadonly forwardedEnvironment: readonly string[];\n\treadonly lspConfigPath?: string;\n\treadonly sandboxInstructionsPath?: string;\n\treadonly userAgentsPath?: string;\n\treadonly trustProjectLsp: boolean;\n\treadonly maxConnections: number;\n\treadonly maxGlobalRequests: number;\n\treadonly maxGlobalTransfers: number;\n\treadonly maxBufferedOutputBytes: number;\n\treadonly maxPendingConnections: number;\n\treadonly handshakeTimeoutMs: number;\n\treadonly shutdownTimeoutMs: number;\n\treadonly protocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nexport interface DaemonCommand {\n\treadonly help: boolean;\n\treadonly configuration?: DaemonConfiguration;\n}\n\nexport class DaemonConfigurationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DaemonConfigurationError\";\n\t}\n}\n\ninterface ParsedDaemonInputs {\n\thelp: boolean;\n\thost?: string;\n\tport?: string;\n\tcwd?: string;\n\torigins: string[];\n\ttlsCertPath?: string;\n\ttlsKeyPath?: string;\n\tallowInsecureTransport?: boolean;\n\tallowProcessExec?: boolean;\n\tallowRoot?: boolean;\n\tforwardedEnvironment: string[];\n\tlspConfigPath?: string;\n\ttrustProjectLsp?: boolean;\n\tmaxConnections?: string;\n\tmaxPendingConnections?: string;\n\thandshakeTimeoutMs?: string;\n\tshutdownTimeoutMs?: string;\n}\n\nconst BOOLEAN_VALUES = new Map<string, boolean>([\n\t[\"1\", true],\n\t[\"true\", true],\n\t[\"yes\", true],\n\t[\"0\", false],\n\t[\"false\", false],\n\t[\"no\", false],\n]);\n\nconst VALUE_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-host\", \"host\"],\n\t[\"--daemon-port\", \"port\"],\n\t[\"--daemon-cwd\", \"cwd\"],\n\t[\"--daemon-tls-cert\", \"tlsCertPath\"],\n\t[\"--daemon-tls-key\", \"tlsKeyPath\"],\n\t[\"--daemon-lsp-config\", \"lspConfigPath\"],\n\t[\"--daemon-max-connections\", \"maxConnections\"],\n\t[\"--daemon-max-pending-connections\", \"maxPendingConnections\"],\n\t[\"--daemon-handshake-timeout-ms\", \"handshakeTimeoutMs\"],\n\t[\"--daemon-shutdown-timeout-ms\", \"shutdownTimeoutMs\"],\n]);\n\nconst BOOLEAN_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-allow-insecure-transport\", \"allowInsecureTransport\"],\n\t[\"--daemon-allow-process-exec\", \"allowProcessExec\"],\n\t[\"--daemon-allow-root\", \"allowRoot\"],\n\t[\"--daemon-trust-project-lsp\", \"trustProjectLsp\"],\n]);\n\nfunction parseBoolean(value: string | undefined, name: string, fallback = false): boolean {\n\tif (value === undefined) return fallback;\n\tconst parsed = BOOLEAN_VALUES.get(value.toLowerCase());\n\tif (parsed === undefined) throw new DaemonConfigurationError(`${name} must be one of 1, 0, true, false, yes, or no`);\n\treturn parsed;\n}\n\nfunction parseInteger(\n\tvalue: string | undefined,\n\tname: string,\n\tminimum: number,\n\tmaximum: number,\n\tfallback: number,\n): number {\n\tif (value === undefined) return fallback;\n\tif (!/^(0|[1-9][0-9]*)$/.test(value)) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\treturn parsed;\n}\n\nfunction setSingleton(\n\tinputs: ParsedDaemonInputs,\n\tkey: keyof ParsedDaemonInputs,\n\tvalue: string | boolean,\n\tflag: string,\n): void {\n\tif (inputs[key] !== undefined) throw new DaemonConfigurationError(`${flag} may only be specified once`);\n\t(inputs as Record<keyof ParsedDaemonInputs, unknown>)[key] = value;\n}\n\nfunction requireFlagValue(args: readonly string[], index: number, flag: string): string {\n\tconst value = args[index + 1];\n\tif (value === undefined || value.startsWith(\"-\")) throw new DaemonConfigurationError(`${flag} requires a value`);\n\treturn value;\n}\n\nfunction parseInputs(args: readonly string[]): ParsedDaemonInputs {\n\tif (args[0] !== \"--daemon\") throw new DaemonConfigurationError(\"Daemon command must start with --daemon\");\n\tconst inputs: ParsedDaemonInputs = { help: false, origins: [], forwardedEnvironment: [] };\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst argument = args[index];\n\t\tif (argument === \"--help\" || argument === \"-h\") {\n\t\t\tinputs.help = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--daemon-origin\" || argument === \"--daemon-env\") {\n\t\t\tconst value = requireFlagValue(args, index, argument);\n\t\t\tif (argument === \"--daemon-origin\") inputs.origins.push(value);\n\t\t\telse inputs.forwardedEnvironment.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst valueKey = VALUE_FLAGS.get(argument);\n\t\tif (valueKey) {\n\t\t\tsetSingleton(inputs, valueKey, requireFlagValue(args, index, argument), argument);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst booleanKey = BOOLEAN_FLAGS.get(argument);\n\t\tif (booleanKey) {\n\t\t\tsetSingleton(inputs, booleanKey, true, argument);\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new DaemonConfigurationError(\n\t\t\targument.startsWith(\"-\")\n\t\t\t\t? `Unknown or incompatible option in daemon mode: ${argument}`\n\t\t\t\t: `Daemon mode does not accept positional arguments: ${argument}`,\n\t\t);\n\t}\n\treturn inputs;\n}\n\nfunction parseOrigin(value: string): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${value}`);\n\t}\n\tif (\n\t\t(url.protocol !== \"http:\" && url.protocol !== \"https:\") ||\n\t\turl.username !== \"\" ||\n\t\turl.password !== \"\" ||\n\t\turl.pathname !== \"/\" ||\n\t\turl.search !== \"\" ||\n\t\turl.hash !== \"\" ||\n\t\tvalue !== url.origin\n\t) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon origins must be exact HTTP(S) origins without paths or wildcards: ${value}`,\n\t\t);\n\t}\n\treturn url.origin;\n}\n\nfunction parseEnvironmentNames(values: readonly string[]): string[] {\n\tconst names = values\n\t\t.flatMap((value) => value.split(\",\"))\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean);\n\tconst seen = new Set<string>();\n\tfor (const name of names) {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon environment name: ${name}`);\n\t\tconst identity = process.platform === \"win32\" ? name.toLowerCase() : name;\n\t\tif (seen.has(identity)) throw new DaemonConfigurationError(`Duplicate daemon environment name: ${name}`);\n\t\tseen.add(identity);\n\t}\n\treturn names;\n}\n\nasync function resolveConfinedRoot(value: string, startupCwd: string, label: string): Promise<string> {\n\tconst candidate = isAbsolute(value) ? value : resolve(startupCwd, value);\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = await realpath(candidate);\n\t\tif (!(await stat(canonical)).isDirectory()) throw new Error(\"not a directory\");\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon ${label} must be an existing directory: ${candidate} (${error instanceof Error ? error.message : String(error)})`,\n\t\t);\n\t}\n\treturn canonical;\n}\n\nasync function readTlsFile(path: string, label: string, startupCwd: string): Promise<Buffer> {\n\tconst resolvedPath = isAbsolute(path) ? path : resolve(startupCwd, path);\n\ttry {\n\t\tconst content = await readFile(resolvedPath);\n\t\tif (content.byteLength === 0) throw new Error(\"file is empty\");\n\t\treturn content;\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Unable to read daemon TLS ${label} file ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n}\n\nfunction validateToken(token: string | undefined): string | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.byteLength(token, \"utf8\");\n\tif (bytes < 32 || bytes > 1024 || !/\\S/u.test(token) || /[\\r\\n]/u.test(token)) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_TOKEN must contain 32 to 1024 UTF-8 bytes, include a non-whitespace character, and contain no line breaks\",\n\t\t);\n\t}\n\treturn token;\n}\n\nfunction isRootProcess(): boolean {\n\treturn process.platform !== \"win32\" && typeof process.getuid === \"function\" && process.getuid() === 0;\n}\n\nexport function isDaemonCommand(args: readonly string[]): boolean {\n\treturn args[0] === \"--daemon\";\n}\ninterface DaemonEndpointConfiguration {\n\thost: string;\n\tport: number;\n}\n\ninterface DaemonWorkspaceRootsConfiguration {\n\tallowRoot: boolean;\n\tworkspaceRoot: string;\n\ttemporaryRoot: string | undefined;\n}\n\ninterface DaemonTransportSecurityConfiguration {\n\ttoken: string | undefined;\n\tallowedOrigins: string[];\n\ttls: DaemonTlsConfiguration | undefined;\n}\n\ninterface DaemonCapacityConfiguration {\n\tmaxGlobalRequests: number;\n\tmaxGlobalTransfers: number;\n\tmaxBufferedOutputBytes: number;\n\tprotocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nfunction resolveDaemonEndpoint(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n): DaemonEndpointConfiguration {\n\tconst host = inputs.host ?? environment.GRIP_DAEMON_HOST ?? \"127.0.0.1\";\n\tif (host.length === 0 || /\\s/u.test(host)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a non-empty IP address\");\n\t}\n\tconst port = parseInteger(\n\t\tinputs.port ?? environment.GRIP_DAEMON_PORT,\n\t\t\"GRIP_DAEMON_PORT/--daemon-port\",\n\t\t1,\n\t\t65535,\n\t\t8787,\n\t);\n\treturn { host, port };\n}\n\nasync function resolveDaemonWorkspaceRoots(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonWorkspaceRootsConfiguration> {\n\tconst allowRoot = inputs.allowRoot ?? parseBoolean(environment.GRIP_DAEMON_ALLOW_ROOT, \"GRIP_DAEMON_ALLOW_ROOT\");\n\tif (isRootProcess() && !allowRoot) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"Refusing to run the workspace daemon as root; use --daemon-allow-root only inside an intentional OS sandbox\",\n\t\t);\n\t}\n\tconst workspaceRoot = await resolveConfinedRoot(\n\t\tinputs.cwd ?? environment.GRIP_DAEMON_CWD ?? startupCwd,\n\t\tstartupCwd,\n\t\t\"workspace root\",\n\t);\n\tconst temporaryRoot = environment.GRIP_DAEMON_TEMP_ROOT\n\t\t? await resolveConfinedRoot(environment.GRIP_DAEMON_TEMP_ROOT, startupCwd, \"temporary root\")\n\t\t: undefined;\n\treturn { allowRoot, workspaceRoot, temporaryRoot };\n}\n\nasync function resolveDaemonTransportSecurity(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonTransportSecurityConfiguration> {\n\tconst token = validateToken(environment.GRIP_DAEMON_TOKEN);\n\tconst allowedOrigins = [\n\t\t...(environment.GRIP_DAEMON_ORIGINS?.split(\",\")\n\t\t\t.map((value) => value.trim())\n\t\t\t.filter(Boolean) ?? []),\n\t\t...inputs.origins,\n\t].map(parseOrigin);\n\tif (new Set(allowedOrigins).size !== allowedOrigins.length) {\n\t\tthrow new DaemonConfigurationError(\"Daemon origins must be unique\");\n\t}\n\tconst tlsCertPath = inputs.tlsCertPath ?? environment.GRIP_DAEMON_TLS_CERT;\n\tconst tlsKeyPath = inputs.tlsKeyPath ?? environment.GRIP_DAEMON_TLS_KEY;\n\tif ((tlsCertPath === undefined) !== (tlsKeyPath === undefined)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS requires both a certificate and private key\");\n\t}\n\tconst tls =\n\t\ttlsCertPath && tlsKeyPath\n\t\t\t? Object.freeze({\n\t\t\t\t\tcert: await readTlsFile(tlsCertPath, \"certificate\", startupCwd),\n\t\t\t\t\tkey: await readTlsFile(tlsKeyPath, \"private key\", startupCwd),\n\t\t\t\t\tpassphrase: environment.GRIP_DAEMON_TLS_PASSPHRASE,\n\t\t\t\t})\n\t\t\t: undefined;\n\treturn { token, allowedOrigins, tls };\n}\n\nfunction resolveDaemonCapacity(environment: NodeJS.ProcessEnv): DaemonCapacityConfiguration {\n\tconst maxConnectionRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveRequests,\n\t);\n\tconst maxGlobalRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tmaxConnectionRequests,\n\t);\n\tif (maxGlobalRequests < maxConnectionRequests) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS must be at least GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t);\n\t}\n\tconst maxConnectionTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveTransfers,\n\t);\n\tconst maxGlobalTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tmaxConnectionTransfers,\n\t);\n\tif (maxGlobalTransfers < maxConnectionTransfers) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS must be at least GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t);\n\t}\n\tconst maxMessageBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_FRAME_BYTES,\n\t\t\"GRIP_DAEMON_MAX_FRAME_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxMessageBytes,\n\t);\n\tconst maxTransferBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_UPLOAD_BYTES,\n\t\t\"GRIP_DAEMON_MAX_UPLOAD_BYTES\",\n\t\t1,\n\t\tNumber.MAX_SAFE_INTEGER,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxTransferBytes,\n\t);\n\tconst maxBufferedOutputBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES,\n\t\t\"GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\t8 * 1024 * 1024,\n\t);\n\tconst protocolLimits = validateRemoteWorkspaceProtocolLimits({\n\t\t...DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\t\tmaxMessageBytes,\n\t\tmaxStringBytes: Math.min(DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxStringBytes, maxMessageBytes),\n\t\tmaxPendingInboundBytes: Math.max(\n\t\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxPendingInboundBytes,\n\t\t\tmaxMessageBytes,\n\t\t),\n\t\tmaxTransferBytes,\n\t\tmaxActiveRequests: maxConnectionRequests,\n\t\tmaxActiveTransfers: Math.min(maxConnectionTransfers, maxGlobalTransfers),\n\t});\n\treturn { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits };\n}\n\nexport async function parseDaemonCommand(\n\targs: readonly string[],\n\tenvironment: NodeJS.ProcessEnv = process.env,\n\tstartupCwd = process.cwd(),\n): Promise<DaemonCommand> {\n\tconst inputs = parseInputs(args);\n\tif (inputs.help) return { help: true };\n\n\tconst { host, port } = resolveDaemonEndpoint(inputs, environment);\n\tconst { allowRoot, workspaceRoot, temporaryRoot } = await resolveDaemonWorkspaceRoots(\n\t\tinputs,\n\t\tenvironment,\n\t\tstartupCwd,\n\t);\n\tconst { token, allowedOrigins, tls } = await resolveDaemonTransportSecurity(inputs, environment, startupCwd);\n\tconst { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits } =\n\t\tresolveDaemonCapacity(environment);\n\n\tconst allowInsecureTransport =\n\t\tinputs.allowInsecureTransport ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT, \"GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT\");\n\tconst allowProcessExec =\n\t\tinputs.allowProcessExec ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_PROCESS_EXEC, \"GRIP_DAEMON_ALLOW_PROCESS_EXEC\");\n\tconst trustProjectLsp =\n\t\tinputs.trustProjectLsp ??\n\t\tparseBoolean(environment.GRIP_DAEMON_TRUST_PROJECT_LSP, \"GRIP_DAEMON_TRUST_PROJECT_LSP\");\n\tconst forwardedEnvironment = parseEnvironmentNames([\n\t\t...(environment.GRIP_DAEMON_ENV?.split(\",\") ?? []),\n\t\t...inputs.forwardedEnvironment,\n\t]);\n\tconst maxConnections = parseInteger(\n\t\tinputs.maxConnections ?? environment.GRIP_DAEMON_MAX_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTIONS/--daemon-max-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst maxPendingConnections = parseInteger(\n\t\tinputs.maxPendingConnections ?? environment.GRIP_DAEMON_MAX_PENDING_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_PENDING_CONNECTIONS/--daemon-max-pending-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst handshakeTimeoutMs = parseInteger(\n\t\tinputs.handshakeTimeoutMs ?? environment.GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS/--daemon-handshake-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\tconst shutdownTimeoutMs = parseInteger(\n\t\tinputs.shutdownTimeoutMs ?? environment.GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS/--daemon-shutdown-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\n\treturn {\n\t\thelp: false,\n\t\tconfiguration: Object.freeze({\n\t\t\thost,\n\t\t\tport,\n\t\t\tworkspaceRoot,\n\t\t\ttemporaryRoot,\n\t\t\ttoken,\n\t\t\tallowedOrigins: Object.freeze(allowedOrigins),\n\t\t\ttls,\n\t\t\tallowInsecureTransport,\n\t\t\tallowProcessExec,\n\t\t\tallowRoot,\n\t\t\tforwardedEnvironment: Object.freeze(forwardedEnvironment),\n\t\t\tlspConfigPath:\n\t\t\t\tinputs.lspConfigPath || environment.GRIP_DAEMON_LSP_CONFIG\n\t\t\t\t\t? resolve(startupCwd, inputs.lspConfigPath ?? environment.GRIP_DAEMON_LSP_CONFIG!)\n\t\t\t\t\t: undefined,\n\t\t\ttrustProjectLsp,\n\t\t\tsandboxInstructionsPath: environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS\n\t\t\t\t? resolve(startupCwd, environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS)\n\t\t\t\t: undefined,\n\t\t\tuserAgentsPath: environment.HOME ? join(environment.HOME, \".grip\", \"AGENTS.md\") : undefined,\n\t\t\tmaxConnections,\n\t\t\tmaxGlobalRequests,\n\t\t\tmaxGlobalTransfers,\n\t\t\tmaxBufferedOutputBytes,\n\t\t\tmaxPendingConnections,\n\t\t\thandshakeTimeoutMs,\n\t\t\tshutdownTimeoutMs,\n\t\t\tprotocolLimits: Object.freeze({ ...protocolLimits }),\n\t\t}),\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/daemon/config.ts"],"names":[],"mappings":"AAEA,OAAO,EAEN,KAAK,6BAA6B,EAElC,MAAM,+CAA+C,CAAC;AAEvD,eAAO,MAAM,qBAAqB,oBAAoB,CAAC;AACvD,eAAO,MAAM,yBAAyB,sBAAsB,CAAC;AAE7D,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,cAAc,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3C,QAAQ,CAAC,GAAG,CAAC,EAAE,sBAAsB,CAAC;IACtC,QAAQ,CAAC,sBAAsB,EAAE,OAAO,CAAC;IACzC,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,SAAS,MAAM,EAAE,CAAC;IACjD,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,MAAM,CAAC;IAC1C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,eAAe,EAAE,OAAO,CAAC;IAClC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;IACxC,QAAQ,CAAC,qBAAqB,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,iBAAiB,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,EAAE,6BAA6B,CAAC;CACvD;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,aAAa,CAAC,EAAE,mBAAmB,CAAC;CAC7C;AAED,qBAAa,wBAAyB,SAAQ,KAAK;IAClD,YAAY,OAAO,EAAE,MAAM,EAG1B;CACD;AAmND,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAEhE;AA2LD,wBAAsB,kBAAkB,CACvC,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,WAAW,GAAE,MAAM,CAAC,UAAwB,EAC5C,UAAU,SAAgB,GACxB,OAAO,CAAC,aAAa,CAAC,CA2FxB","sourcesContent":["import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport {\n\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\ttype RemoteWorkspaceProtocolLimits,\n\tvalidateRemoteWorkspaceProtocolLimits,\n} from \"../core/remote-workspace-protocol/contract.ts\";\n\nexport const DAEMON_WEBSOCKET_PATH = \"/grip/workspace\";\nexport const DAEMON_WEBSOCKET_PROTOCOL = \"grip.workspace.v1\";\n\nexport interface DaemonTlsConfiguration {\n\treadonly cert: Buffer;\n\treadonly key: Buffer;\n\treadonly passphrase?: string;\n}\n\nexport interface DaemonConfiguration {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly workspaceRoot: string;\n\t/** Canonical fixed parent mount for explicitly enabled shared-channel mode. */\n\treadonly sharedParentRoot?: string;\n\treadonly temporaryRoot?: string;\n\treadonly token?: string;\n\treadonly allowedOrigins: readonly string[];\n\treadonly tls?: DaemonTlsConfiguration;\n\treadonly allowInsecureTransport: boolean;\n\treadonly allowProcessExec: boolean;\n\treadonly allowRoot: boolean;\n\treadonly forwardedEnvironment: readonly string[];\n\treadonly lspConfigPath?: string;\n\treadonly sandboxInstructionsPath?: string;\n\treadonly userAgentsPath?: string;\n\treadonly trustProjectLsp: boolean;\n\treadonly maxConnections: number;\n\treadonly maxGlobalRequests: number;\n\treadonly maxGlobalTransfers: number;\n\treadonly maxBufferedOutputBytes: number;\n\treadonly maxPendingConnections: number;\n\treadonly handshakeTimeoutMs: number;\n\treadonly shutdownTimeoutMs: number;\n\treadonly protocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nexport interface DaemonCommand {\n\treadonly help: boolean;\n\treadonly configuration?: DaemonConfiguration;\n}\n\nexport class DaemonConfigurationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DaemonConfigurationError\";\n\t}\n}\n\ninterface ParsedDaemonInputs {\n\thelp: boolean;\n\thost?: string;\n\tport?: string;\n\tcwd?: string;\n\tsharedParent?: string;\n\torigins: string[];\n\ttlsCertPath?: string;\n\ttlsKeyPath?: string;\n\tallowInsecureTransport?: boolean;\n\tallowProcessExec?: boolean;\n\tallowRoot?: boolean;\n\tforwardedEnvironment: string[];\n\tlspConfigPath?: string;\n\ttrustProjectLsp?: boolean;\n\tmaxConnections?: string;\n\tmaxPendingConnections?: string;\n\thandshakeTimeoutMs?: string;\n\tshutdownTimeoutMs?: string;\n}\n\nconst BOOLEAN_VALUES = new Map<string, boolean>([\n\t[\"1\", true],\n\t[\"true\", true],\n\t[\"yes\", true],\n\t[\"0\", false],\n\t[\"false\", false],\n\t[\"no\", false],\n]);\n\nconst VALUE_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-host\", \"host\"],\n\t[\"--daemon-port\", \"port\"],\n\t[\"--daemon-cwd\", \"cwd\"],\n\t[\"--daemon-shared-parent\", \"sharedParent\"],\n\t[\"--daemon-tls-cert\", \"tlsCertPath\"],\n\t[\"--daemon-tls-key\", \"tlsKeyPath\"],\n\t[\"--daemon-lsp-config\", \"lspConfigPath\"],\n\t[\"--daemon-max-connections\", \"maxConnections\"],\n\t[\"--daemon-max-pending-connections\", \"maxPendingConnections\"],\n\t[\"--daemon-handshake-timeout-ms\", \"handshakeTimeoutMs\"],\n\t[\"--daemon-shutdown-timeout-ms\", \"shutdownTimeoutMs\"],\n]);\n\nconst BOOLEAN_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-allow-insecure-transport\", \"allowInsecureTransport\"],\n\t[\"--daemon-allow-process-exec\", \"allowProcessExec\"],\n\t[\"--daemon-allow-root\", \"allowRoot\"],\n\t[\"--daemon-trust-project-lsp\", \"trustProjectLsp\"],\n]);\n\nfunction parseBoolean(value: string | undefined, name: string, fallback = false): boolean {\n\tif (value === undefined) return fallback;\n\tconst parsed = BOOLEAN_VALUES.get(value.toLowerCase());\n\tif (parsed === undefined) throw new DaemonConfigurationError(`${name} must be one of 1, 0, true, false, yes, or no`);\n\treturn parsed;\n}\n\nfunction parseInteger(\n\tvalue: string | undefined,\n\tname: string,\n\tminimum: number,\n\tmaximum: number,\n\tfallback: number,\n): number {\n\tif (value === undefined) return fallback;\n\tif (!/^(0|[1-9][0-9]*)$/.test(value)) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\treturn parsed;\n}\n\nfunction setSingleton(\n\tinputs: ParsedDaemonInputs,\n\tkey: keyof ParsedDaemonInputs,\n\tvalue: string | boolean,\n\tflag: string,\n): void {\n\tif (inputs[key] !== undefined) throw new DaemonConfigurationError(`${flag} may only be specified once`);\n\t(inputs as Record<keyof ParsedDaemonInputs, unknown>)[key] = value;\n}\n\nfunction requireFlagValue(args: readonly string[], index: number, flag: string): string {\n\tconst value = args[index + 1];\n\tif (value === undefined || value.startsWith(\"-\")) throw new DaemonConfigurationError(`${flag} requires a value`);\n\treturn value;\n}\n\nfunction parseInputs(args: readonly string[]): ParsedDaemonInputs {\n\tif (args[0] !== \"--daemon\") throw new DaemonConfigurationError(\"Daemon command must start with --daemon\");\n\tconst inputs: ParsedDaemonInputs = { help: false, origins: [], forwardedEnvironment: [] };\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst argument = args[index];\n\t\tif (argument === \"--help\" || argument === \"-h\") {\n\t\t\tinputs.help = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--daemon-origin\" || argument === \"--daemon-env\") {\n\t\t\tconst value = requireFlagValue(args, index, argument);\n\t\t\tif (argument === \"--daemon-origin\") inputs.origins.push(value);\n\t\t\telse inputs.forwardedEnvironment.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst valueKey = VALUE_FLAGS.get(argument);\n\t\tif (valueKey) {\n\t\t\tsetSingleton(inputs, valueKey, requireFlagValue(args, index, argument), argument);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst booleanKey = BOOLEAN_FLAGS.get(argument);\n\t\tif (booleanKey) {\n\t\t\tsetSingleton(inputs, booleanKey, true, argument);\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new DaemonConfigurationError(\n\t\t\targument.startsWith(\"-\")\n\t\t\t\t? `Unknown or incompatible option in daemon mode: ${argument}`\n\t\t\t\t: `Daemon mode does not accept positional arguments: ${argument}`,\n\t\t);\n\t}\n\treturn inputs;\n}\n\nfunction parseOrigin(value: string): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${value}`);\n\t}\n\tif (\n\t\t(url.protocol !== \"http:\" && url.protocol !== \"https:\") ||\n\t\turl.username !== \"\" ||\n\t\turl.password !== \"\" ||\n\t\turl.pathname !== \"/\" ||\n\t\turl.search !== \"\" ||\n\t\turl.hash !== \"\" ||\n\t\tvalue !== url.origin\n\t) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon origins must be exact HTTP(S) origins without paths or wildcards: ${value}`,\n\t\t);\n\t}\n\treturn url.origin;\n}\n\nfunction parseEnvironmentNames(values: readonly string[]): string[] {\n\tconst names = values\n\t\t.flatMap((value) => value.split(\",\"))\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean);\n\tconst seen = new Set<string>();\n\tfor (const name of names) {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon environment name: ${name}`);\n\t\tconst identity = process.platform === \"win32\" ? name.toLowerCase() : name;\n\t\tif (seen.has(identity)) throw new DaemonConfigurationError(`Duplicate daemon environment name: ${name}`);\n\t\tseen.add(identity);\n\t}\n\treturn names;\n}\n\nasync function resolveConfinedRoot(value: string, startupCwd: string, label: string): Promise<string> {\n\tconst candidate = isAbsolute(value) ? value : resolve(startupCwd, value);\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = await realpath(candidate);\n\t\tif (!(await stat(canonical)).isDirectory()) throw new Error(\"not a directory\");\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon ${label} must be an existing directory: ${candidate} (${error instanceof Error ? error.message : String(error)})`,\n\t\t);\n\t}\n\treturn canonical;\n}\n\nasync function readTlsFile(path: string, label: string, startupCwd: string): Promise<Buffer> {\n\tconst resolvedPath = isAbsolute(path) ? path : resolve(startupCwd, path);\n\ttry {\n\t\tconst content = await readFile(resolvedPath);\n\t\tif (content.byteLength === 0) throw new Error(\"file is empty\");\n\t\treturn content;\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Unable to read daemon TLS ${label} file ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n}\n\nfunction validateToken(token: string | undefined): string | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.byteLength(token, \"utf8\");\n\tif (bytes < 32 || bytes > 1024 || !/\\S/u.test(token) || /[\\r\\n]/u.test(token)) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_TOKEN must contain 32 to 1024 UTF-8 bytes, include a non-whitespace character, and contain no line breaks\",\n\t\t);\n\t}\n\treturn token;\n}\n\nfunction isRootProcess(): boolean {\n\treturn process.platform !== \"win32\" && typeof process.getuid === \"function\" && process.getuid() === 0;\n}\n\nexport function isDaemonCommand(args: readonly string[]): boolean {\n\treturn args[0] === \"--daemon\";\n}\ninterface DaemonEndpointConfiguration {\n\thost: string;\n\tport: number;\n}\n\ninterface DaemonWorkspaceRootsConfiguration {\n\tallowRoot: boolean;\n\tworkspaceRoot: string;\n\ttemporaryRoot: string | undefined;\n}\n\ninterface DaemonTransportSecurityConfiguration {\n\ttoken: string | undefined;\n\tallowedOrigins: string[];\n\ttls: DaemonTlsConfiguration | undefined;\n}\n\ninterface DaemonCapacityConfiguration {\n\tmaxGlobalRequests: number;\n\tmaxGlobalTransfers: number;\n\tmaxBufferedOutputBytes: number;\n\tprotocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nfunction resolveDaemonEndpoint(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n): DaemonEndpointConfiguration {\n\tconst host = inputs.host ?? environment.GRIP_DAEMON_HOST ?? \"127.0.0.1\";\n\tif (host.length === 0 || /\\s/u.test(host)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a non-empty IP address\");\n\t}\n\tconst port = parseInteger(\n\t\tinputs.port ?? environment.GRIP_DAEMON_PORT,\n\t\t\"GRIP_DAEMON_PORT/--daemon-port\",\n\t\t1,\n\t\t65535,\n\t\t8787,\n\t);\n\treturn { host, port };\n}\n\nasync function resolveDaemonWorkspaceRoots(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonWorkspaceRootsConfiguration> {\n\tconst allowRoot = inputs.allowRoot ?? parseBoolean(environment.GRIP_DAEMON_ALLOW_ROOT, \"GRIP_DAEMON_ALLOW_ROOT\");\n\tif (isRootProcess() && !allowRoot) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"Refusing to run the workspace daemon as root; use --daemon-allow-root only inside an intentional OS sandbox\",\n\t\t);\n\t}\n\tconst workspaceRoot = await resolveConfinedRoot(\n\t\tinputs.cwd ?? environment.GRIP_DAEMON_CWD ?? startupCwd,\n\t\tstartupCwd,\n\t\t\"workspace root\",\n\t);\n\tconst temporaryRoot = environment.GRIP_DAEMON_TEMP_ROOT\n\t\t? await resolveConfinedRoot(environment.GRIP_DAEMON_TEMP_ROOT, startupCwd, \"temporary root\")\n\t\t: undefined;\n\treturn { allowRoot, workspaceRoot, temporaryRoot };\n}\n\nasync function resolveSharedParentRoot(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n\tworkspaceRoot: string,\n): Promise<string | undefined> {\n\tconst value = inputs.sharedParent ?? environment.GRIP_DAEMON_SHARED_PARENT;\n\tif (value === undefined) return undefined;\n\tif (value === \"\") throw new DaemonConfigurationError(\"Shared parent mount must not be empty\");\n\tconst root = await resolveConfinedRoot(value, startupCwd, \"shared parent mount\");\n\tif (root !== workspaceRoot) {\n\t\tthrow new DaemonConfigurationError(\"Shared parent mount must equal --daemon-cwd (the fixed mounted parent)\");\n\t}\n\treturn root;\n}\n\nasync function resolveDaemonTransportSecurity(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonTransportSecurityConfiguration> {\n\tconst token = validateToken(environment.GRIP_DAEMON_TOKEN);\n\tconst allowedOrigins = [\n\t\t...(environment.GRIP_DAEMON_ORIGINS?.split(\",\")\n\t\t\t.map((value) => value.trim())\n\t\t\t.filter(Boolean) ?? []),\n\t\t...inputs.origins,\n\t].map(parseOrigin);\n\tif (new Set(allowedOrigins).size !== allowedOrigins.length) {\n\t\tthrow new DaemonConfigurationError(\"Daemon origins must be unique\");\n\t}\n\tconst tlsCertPath = inputs.tlsCertPath ?? environment.GRIP_DAEMON_TLS_CERT;\n\tconst tlsKeyPath = inputs.tlsKeyPath ?? environment.GRIP_DAEMON_TLS_KEY;\n\tif ((tlsCertPath === undefined) !== (tlsKeyPath === undefined)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS requires both a certificate and private key\");\n\t}\n\tconst tls =\n\t\ttlsCertPath && tlsKeyPath\n\t\t\t? Object.freeze({\n\t\t\t\t\tcert: await readTlsFile(tlsCertPath, \"certificate\", startupCwd),\n\t\t\t\t\tkey: await readTlsFile(tlsKeyPath, \"private key\", startupCwd),\n\t\t\t\t\tpassphrase: environment.GRIP_DAEMON_TLS_PASSPHRASE,\n\t\t\t\t})\n\t\t\t: undefined;\n\treturn { token, allowedOrigins, tls };\n}\n\nfunction resolveDaemonCapacity(environment: NodeJS.ProcessEnv): DaemonCapacityConfiguration {\n\tconst maxConnectionRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveRequests,\n\t);\n\tconst maxGlobalRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tmaxConnectionRequests,\n\t);\n\tif (maxGlobalRequests < maxConnectionRequests) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS must be at least GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t);\n\t}\n\tconst maxConnectionTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveTransfers,\n\t);\n\tconst maxGlobalTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tmaxConnectionTransfers,\n\t);\n\tif (maxGlobalTransfers < maxConnectionTransfers) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS must be at least GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t);\n\t}\n\tconst maxMessageBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_FRAME_BYTES,\n\t\t\"GRIP_DAEMON_MAX_FRAME_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxMessageBytes,\n\t);\n\tconst maxTransferBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_UPLOAD_BYTES,\n\t\t\"GRIP_DAEMON_MAX_UPLOAD_BYTES\",\n\t\t1,\n\t\tNumber.MAX_SAFE_INTEGER,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxTransferBytes,\n\t);\n\tconst maxBufferedOutputBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES,\n\t\t\"GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\t8 * 1024 * 1024,\n\t);\n\tconst protocolLimits = validateRemoteWorkspaceProtocolLimits({\n\t\t...DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\t\tmaxMessageBytes,\n\t\tmaxStringBytes: Math.min(DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxStringBytes, maxMessageBytes),\n\t\tmaxPendingInboundBytes: Math.max(\n\t\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxPendingInboundBytes,\n\t\t\tmaxMessageBytes,\n\t\t),\n\t\tmaxTransferBytes,\n\t\tmaxActiveRequests: maxConnectionRequests,\n\t\tmaxActiveTransfers: Math.min(maxConnectionTransfers, maxGlobalTransfers),\n\t});\n\treturn { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits };\n}\n\nexport async function parseDaemonCommand(\n\targs: readonly string[],\n\tenvironment: NodeJS.ProcessEnv = process.env,\n\tstartupCwd = process.cwd(),\n): Promise<DaemonCommand> {\n\tconst inputs = parseInputs(args);\n\tif (inputs.help) return { help: true };\n\n\tconst { host, port } = resolveDaemonEndpoint(inputs, environment);\n\tconst { allowRoot, workspaceRoot, temporaryRoot } = await resolveDaemonWorkspaceRoots(\n\t\tinputs,\n\t\tenvironment,\n\t\tstartupCwd,\n\t);\n\tconst sharedParentRoot = await resolveSharedParentRoot(inputs, environment, startupCwd, workspaceRoot);\n\tconst { token, allowedOrigins, tls } = await resolveDaemonTransportSecurity(inputs, environment, startupCwd);\n\tconst { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits } =\n\t\tresolveDaemonCapacity(environment);\n\n\tconst allowInsecureTransport =\n\t\tinputs.allowInsecureTransport ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT, \"GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT\");\n\tconst allowProcessExec =\n\t\tinputs.allowProcessExec ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_PROCESS_EXEC, \"GRIP_DAEMON_ALLOW_PROCESS_EXEC\");\n\tconst trustProjectLsp =\n\t\tinputs.trustProjectLsp ??\n\t\tparseBoolean(environment.GRIP_DAEMON_TRUST_PROJECT_LSP, \"GRIP_DAEMON_TRUST_PROJECT_LSP\");\n\tconst forwardedEnvironment = parseEnvironmentNames([\n\t\t...(environment.GRIP_DAEMON_ENV?.split(\",\") ?? []),\n\t\t...inputs.forwardedEnvironment,\n\t]);\n\tconst maxConnections = parseInteger(\n\t\tinputs.maxConnections ?? environment.GRIP_DAEMON_MAX_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTIONS/--daemon-max-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst maxPendingConnections = parseInteger(\n\t\tinputs.maxPendingConnections ?? environment.GRIP_DAEMON_MAX_PENDING_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_PENDING_CONNECTIONS/--daemon-max-pending-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst handshakeTimeoutMs = parseInteger(\n\t\tinputs.handshakeTimeoutMs ?? environment.GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS/--daemon-handshake-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\tconst shutdownTimeoutMs = parseInteger(\n\t\tinputs.shutdownTimeoutMs ?? environment.GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS/--daemon-shutdown-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\n\treturn {\n\t\thelp: false,\n\t\tconfiguration: Object.freeze({\n\t\t\thost,\n\t\t\tport,\n\t\t\tworkspaceRoot,\n\t\t\tsharedParentRoot,\n\t\t\ttemporaryRoot,\n\t\t\ttoken,\n\t\t\tallowedOrigins: Object.freeze(allowedOrigins),\n\t\t\ttls,\n\t\t\tallowInsecureTransport,\n\t\t\tallowProcessExec,\n\t\t\tallowRoot,\n\t\t\tforwardedEnvironment: Object.freeze(forwardedEnvironment),\n\t\t\tlspConfigPath:\n\t\t\t\tinputs.lspConfigPath || environment.GRIP_DAEMON_LSP_CONFIG\n\t\t\t\t\t? resolve(startupCwd, inputs.lspConfigPath ?? environment.GRIP_DAEMON_LSP_CONFIG!)\n\t\t\t\t\t: undefined,\n\t\t\ttrustProjectLsp,\n\t\t\tsandboxInstructionsPath: environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS\n\t\t\t\t? resolve(startupCwd, environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS)\n\t\t\t\t: undefined,\n\t\t\tuserAgentsPath: environment.HOME ? join(environment.HOME, \".grip\", \"AGENTS.md\") : undefined,\n\t\t\tmaxConnections,\n\t\t\tmaxGlobalRequests,\n\t\t\tmaxGlobalTransfers,\n\t\t\tmaxBufferedOutputBytes,\n\t\t\tmaxPendingConnections,\n\t\t\thandshakeTimeoutMs,\n\t\t\tshutdownTimeoutMs,\n\t\t\tprotocolLimits: Object.freeze({ ...protocolLimits }),\n\t\t}),\n\t};\n}\n"]}
|
package/dist/daemon/config.js
CHANGED
|
@@ -21,6 +21,7 @@ const VALUE_FLAGS = new Map([
|
|
|
21
21
|
["--daemon-host", "host"],
|
|
22
22
|
["--daemon-port", "port"],
|
|
23
23
|
["--daemon-cwd", "cwd"],
|
|
24
|
+
["--daemon-shared-parent", "sharedParent"],
|
|
24
25
|
["--daemon-tls-cert", "tlsCertPath"],
|
|
25
26
|
["--daemon-tls-key", "tlsKeyPath"],
|
|
26
27
|
["--daemon-lsp-config", "lspConfigPath"],
|
|
@@ -196,6 +197,18 @@ async function resolveDaemonWorkspaceRoots(inputs, environment, startupCwd) {
|
|
|
196
197
|
: undefined;
|
|
197
198
|
return { allowRoot, workspaceRoot, temporaryRoot };
|
|
198
199
|
}
|
|
200
|
+
async function resolveSharedParentRoot(inputs, environment, startupCwd, workspaceRoot) {
|
|
201
|
+
const value = inputs.sharedParent ?? environment.GRIP_DAEMON_SHARED_PARENT;
|
|
202
|
+
if (value === undefined)
|
|
203
|
+
return undefined;
|
|
204
|
+
if (value === "")
|
|
205
|
+
throw new DaemonConfigurationError("Shared parent mount must not be empty");
|
|
206
|
+
const root = await resolveConfinedRoot(value, startupCwd, "shared parent mount");
|
|
207
|
+
if (root !== workspaceRoot) {
|
|
208
|
+
throw new DaemonConfigurationError("Shared parent mount must equal --daemon-cwd (the fixed mounted parent)");
|
|
209
|
+
}
|
|
210
|
+
return root;
|
|
211
|
+
}
|
|
199
212
|
async function resolveDaemonTransportSecurity(inputs, environment, startupCwd) {
|
|
200
213
|
const token = validateToken(environment.GRIP_DAEMON_TOKEN);
|
|
201
214
|
const allowedOrigins = [
|
|
@@ -252,6 +265,7 @@ export async function parseDaemonCommand(args, environment = process.env, startu
|
|
|
252
265
|
return { help: true };
|
|
253
266
|
const { host, port } = resolveDaemonEndpoint(inputs, environment);
|
|
254
267
|
const { allowRoot, workspaceRoot, temporaryRoot } = await resolveDaemonWorkspaceRoots(inputs, environment, startupCwd);
|
|
268
|
+
const sharedParentRoot = await resolveSharedParentRoot(inputs, environment, startupCwd, workspaceRoot);
|
|
255
269
|
const { token, allowedOrigins, tls } = await resolveDaemonTransportSecurity(inputs, environment, startupCwd);
|
|
256
270
|
const { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits } = resolveDaemonCapacity(environment);
|
|
257
271
|
const allowInsecureTransport = inputs.allowInsecureTransport ??
|
|
@@ -274,6 +288,7 @@ export async function parseDaemonCommand(args, environment = process.env, startu
|
|
|
274
288
|
host,
|
|
275
289
|
port,
|
|
276
290
|
workspaceRoot,
|
|
291
|
+
sharedParentRoot,
|
|
277
292
|
temporaryRoot,
|
|
278
293
|
token,
|
|
279
294
|
allowedOrigins: Object.freeze(allowedOrigins),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/daemon/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport {\n\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\ttype RemoteWorkspaceProtocolLimits,\n\tvalidateRemoteWorkspaceProtocolLimits,\n} from \"../core/remote-workspace-protocol/contract.ts\";\n\nexport const DAEMON_WEBSOCKET_PATH = \"/grip/workspace\";\nexport const DAEMON_WEBSOCKET_PROTOCOL = \"grip.workspace.v1\";\n\nexport interface DaemonTlsConfiguration {\n\treadonly cert: Buffer;\n\treadonly key: Buffer;\n\treadonly passphrase?: string;\n}\n\nexport interface DaemonConfiguration {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly workspaceRoot: string;\n\treadonly temporaryRoot?: string;\n\treadonly token?: string;\n\treadonly allowedOrigins: readonly string[];\n\treadonly tls?: DaemonTlsConfiguration;\n\treadonly allowInsecureTransport: boolean;\n\treadonly allowProcessExec: boolean;\n\treadonly allowRoot: boolean;\n\treadonly forwardedEnvironment: readonly string[];\n\treadonly lspConfigPath?: string;\n\treadonly sandboxInstructionsPath?: string;\n\treadonly userAgentsPath?: string;\n\treadonly trustProjectLsp: boolean;\n\treadonly maxConnections: number;\n\treadonly maxGlobalRequests: number;\n\treadonly maxGlobalTransfers: number;\n\treadonly maxBufferedOutputBytes: number;\n\treadonly maxPendingConnections: number;\n\treadonly handshakeTimeoutMs: number;\n\treadonly shutdownTimeoutMs: number;\n\treadonly protocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nexport interface DaemonCommand {\n\treadonly help: boolean;\n\treadonly configuration?: DaemonConfiguration;\n}\n\nexport class DaemonConfigurationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DaemonConfigurationError\";\n\t}\n}\n\ninterface ParsedDaemonInputs {\n\thelp: boolean;\n\thost?: string;\n\tport?: string;\n\tcwd?: string;\n\torigins: string[];\n\ttlsCertPath?: string;\n\ttlsKeyPath?: string;\n\tallowInsecureTransport?: boolean;\n\tallowProcessExec?: boolean;\n\tallowRoot?: boolean;\n\tforwardedEnvironment: string[];\n\tlspConfigPath?: string;\n\ttrustProjectLsp?: boolean;\n\tmaxConnections?: string;\n\tmaxPendingConnections?: string;\n\thandshakeTimeoutMs?: string;\n\tshutdownTimeoutMs?: string;\n}\n\nconst BOOLEAN_VALUES = new Map<string, boolean>([\n\t[\"1\", true],\n\t[\"true\", true],\n\t[\"yes\", true],\n\t[\"0\", false],\n\t[\"false\", false],\n\t[\"no\", false],\n]);\n\nconst VALUE_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-host\", \"host\"],\n\t[\"--daemon-port\", \"port\"],\n\t[\"--daemon-cwd\", \"cwd\"],\n\t[\"--daemon-tls-cert\", \"tlsCertPath\"],\n\t[\"--daemon-tls-key\", \"tlsKeyPath\"],\n\t[\"--daemon-lsp-config\", \"lspConfigPath\"],\n\t[\"--daemon-max-connections\", \"maxConnections\"],\n\t[\"--daemon-max-pending-connections\", \"maxPendingConnections\"],\n\t[\"--daemon-handshake-timeout-ms\", \"handshakeTimeoutMs\"],\n\t[\"--daemon-shutdown-timeout-ms\", \"shutdownTimeoutMs\"],\n]);\n\nconst BOOLEAN_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-allow-insecure-transport\", \"allowInsecureTransport\"],\n\t[\"--daemon-allow-process-exec\", \"allowProcessExec\"],\n\t[\"--daemon-allow-root\", \"allowRoot\"],\n\t[\"--daemon-trust-project-lsp\", \"trustProjectLsp\"],\n]);\n\nfunction parseBoolean(value: string | undefined, name: string, fallback = false): boolean {\n\tif (value === undefined) return fallback;\n\tconst parsed = BOOLEAN_VALUES.get(value.toLowerCase());\n\tif (parsed === undefined) throw new DaemonConfigurationError(`${name} must be one of 1, 0, true, false, yes, or no`);\n\treturn parsed;\n}\n\nfunction parseInteger(\n\tvalue: string | undefined,\n\tname: string,\n\tminimum: number,\n\tmaximum: number,\n\tfallback: number,\n): number {\n\tif (value === undefined) return fallback;\n\tif (!/^(0|[1-9][0-9]*)$/.test(value)) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\treturn parsed;\n}\n\nfunction setSingleton(\n\tinputs: ParsedDaemonInputs,\n\tkey: keyof ParsedDaemonInputs,\n\tvalue: string | boolean,\n\tflag: string,\n): void {\n\tif (inputs[key] !== undefined) throw new DaemonConfigurationError(`${flag} may only be specified once`);\n\t(inputs as Record<keyof ParsedDaemonInputs, unknown>)[key] = value;\n}\n\nfunction requireFlagValue(args: readonly string[], index: number, flag: string): string {\n\tconst value = args[index + 1];\n\tif (value === undefined || value.startsWith(\"-\")) throw new DaemonConfigurationError(`${flag} requires a value`);\n\treturn value;\n}\n\nfunction parseInputs(args: readonly string[]): ParsedDaemonInputs {\n\tif (args[0] !== \"--daemon\") throw new DaemonConfigurationError(\"Daemon command must start with --daemon\");\n\tconst inputs: ParsedDaemonInputs = { help: false, origins: [], forwardedEnvironment: [] };\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst argument = args[index];\n\t\tif (argument === \"--help\" || argument === \"-h\") {\n\t\t\tinputs.help = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--daemon-origin\" || argument === \"--daemon-env\") {\n\t\t\tconst value = requireFlagValue(args, index, argument);\n\t\t\tif (argument === \"--daemon-origin\") inputs.origins.push(value);\n\t\t\telse inputs.forwardedEnvironment.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst valueKey = VALUE_FLAGS.get(argument);\n\t\tif (valueKey) {\n\t\t\tsetSingleton(inputs, valueKey, requireFlagValue(args, index, argument), argument);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst booleanKey = BOOLEAN_FLAGS.get(argument);\n\t\tif (booleanKey) {\n\t\t\tsetSingleton(inputs, booleanKey, true, argument);\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new DaemonConfigurationError(\n\t\t\targument.startsWith(\"-\")\n\t\t\t\t? `Unknown or incompatible option in daemon mode: ${argument}`\n\t\t\t\t: `Daemon mode does not accept positional arguments: ${argument}`,\n\t\t);\n\t}\n\treturn inputs;\n}\n\nfunction parseOrigin(value: string): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${value}`);\n\t}\n\tif (\n\t\t(url.protocol !== \"http:\" && url.protocol !== \"https:\") ||\n\t\turl.username !== \"\" ||\n\t\turl.password !== \"\" ||\n\t\turl.pathname !== \"/\" ||\n\t\turl.search !== \"\" ||\n\t\turl.hash !== \"\" ||\n\t\tvalue !== url.origin\n\t) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon origins must be exact HTTP(S) origins without paths or wildcards: ${value}`,\n\t\t);\n\t}\n\treturn url.origin;\n}\n\nfunction parseEnvironmentNames(values: readonly string[]): string[] {\n\tconst names = values\n\t\t.flatMap((value) => value.split(\",\"))\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean);\n\tconst seen = new Set<string>();\n\tfor (const name of names) {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon environment name: ${name}`);\n\t\tconst identity = process.platform === \"win32\" ? name.toLowerCase() : name;\n\t\tif (seen.has(identity)) throw new DaemonConfigurationError(`Duplicate daemon environment name: ${name}`);\n\t\tseen.add(identity);\n\t}\n\treturn names;\n}\n\nasync function resolveConfinedRoot(value: string, startupCwd: string, label: string): Promise<string> {\n\tconst candidate = isAbsolute(value) ? value : resolve(startupCwd, value);\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = await realpath(candidate);\n\t\tif (!(await stat(canonical)).isDirectory()) throw new Error(\"not a directory\");\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon ${label} must be an existing directory: ${candidate} (${error instanceof Error ? error.message : String(error)})`,\n\t\t);\n\t}\n\treturn canonical;\n}\n\nasync function readTlsFile(path: string, label: string, startupCwd: string): Promise<Buffer> {\n\tconst resolvedPath = isAbsolute(path) ? path : resolve(startupCwd, path);\n\ttry {\n\t\tconst content = await readFile(resolvedPath);\n\t\tif (content.byteLength === 0) throw new Error(\"file is empty\");\n\t\treturn content;\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Unable to read daemon TLS ${label} file ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n}\n\nfunction validateToken(token: string | undefined): string | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.byteLength(token, \"utf8\");\n\tif (bytes < 32 || bytes > 1024 || !/\\S/u.test(token) || /[\\r\\n]/u.test(token)) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_TOKEN must contain 32 to 1024 UTF-8 bytes, include a non-whitespace character, and contain no line breaks\",\n\t\t);\n\t}\n\treturn token;\n}\n\nfunction isRootProcess(): boolean {\n\treturn process.platform !== \"win32\" && typeof process.getuid === \"function\" && process.getuid() === 0;\n}\n\nexport function isDaemonCommand(args: readonly string[]): boolean {\n\treturn args[0] === \"--daemon\";\n}\ninterface DaemonEndpointConfiguration {\n\thost: string;\n\tport: number;\n}\n\ninterface DaemonWorkspaceRootsConfiguration {\n\tallowRoot: boolean;\n\tworkspaceRoot: string;\n\ttemporaryRoot: string | undefined;\n}\n\ninterface DaemonTransportSecurityConfiguration {\n\ttoken: string | undefined;\n\tallowedOrigins: string[];\n\ttls: DaemonTlsConfiguration | undefined;\n}\n\ninterface DaemonCapacityConfiguration {\n\tmaxGlobalRequests: number;\n\tmaxGlobalTransfers: number;\n\tmaxBufferedOutputBytes: number;\n\tprotocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nfunction resolveDaemonEndpoint(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n): DaemonEndpointConfiguration {\n\tconst host = inputs.host ?? environment.GRIP_DAEMON_HOST ?? \"127.0.0.1\";\n\tif (host.length === 0 || /\\s/u.test(host)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a non-empty IP address\");\n\t}\n\tconst port = parseInteger(\n\t\tinputs.port ?? environment.GRIP_DAEMON_PORT,\n\t\t\"GRIP_DAEMON_PORT/--daemon-port\",\n\t\t1,\n\t\t65535,\n\t\t8787,\n\t);\n\treturn { host, port };\n}\n\nasync function resolveDaemonWorkspaceRoots(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonWorkspaceRootsConfiguration> {\n\tconst allowRoot = inputs.allowRoot ?? parseBoolean(environment.GRIP_DAEMON_ALLOW_ROOT, \"GRIP_DAEMON_ALLOW_ROOT\");\n\tif (isRootProcess() && !allowRoot) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"Refusing to run the workspace daemon as root; use --daemon-allow-root only inside an intentional OS sandbox\",\n\t\t);\n\t}\n\tconst workspaceRoot = await resolveConfinedRoot(\n\t\tinputs.cwd ?? environment.GRIP_DAEMON_CWD ?? startupCwd,\n\t\tstartupCwd,\n\t\t\"workspace root\",\n\t);\n\tconst temporaryRoot = environment.GRIP_DAEMON_TEMP_ROOT\n\t\t? await resolveConfinedRoot(environment.GRIP_DAEMON_TEMP_ROOT, startupCwd, \"temporary root\")\n\t\t: undefined;\n\treturn { allowRoot, workspaceRoot, temporaryRoot };\n}\n\nasync function resolveDaemonTransportSecurity(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonTransportSecurityConfiguration> {\n\tconst token = validateToken(environment.GRIP_DAEMON_TOKEN);\n\tconst allowedOrigins = [\n\t\t...(environment.GRIP_DAEMON_ORIGINS?.split(\",\")\n\t\t\t.map((value) => value.trim())\n\t\t\t.filter(Boolean) ?? []),\n\t\t...inputs.origins,\n\t].map(parseOrigin);\n\tif (new Set(allowedOrigins).size !== allowedOrigins.length) {\n\t\tthrow new DaemonConfigurationError(\"Daemon origins must be unique\");\n\t}\n\tconst tlsCertPath = inputs.tlsCertPath ?? environment.GRIP_DAEMON_TLS_CERT;\n\tconst tlsKeyPath = inputs.tlsKeyPath ?? environment.GRIP_DAEMON_TLS_KEY;\n\tif ((tlsCertPath === undefined) !== (tlsKeyPath === undefined)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS requires both a certificate and private key\");\n\t}\n\tconst tls =\n\t\ttlsCertPath && tlsKeyPath\n\t\t\t? Object.freeze({\n\t\t\t\t\tcert: await readTlsFile(tlsCertPath, \"certificate\", startupCwd),\n\t\t\t\t\tkey: await readTlsFile(tlsKeyPath, \"private key\", startupCwd),\n\t\t\t\t\tpassphrase: environment.GRIP_DAEMON_TLS_PASSPHRASE,\n\t\t\t\t})\n\t\t\t: undefined;\n\treturn { token, allowedOrigins, tls };\n}\n\nfunction resolveDaemonCapacity(environment: NodeJS.ProcessEnv): DaemonCapacityConfiguration {\n\tconst maxConnectionRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveRequests,\n\t);\n\tconst maxGlobalRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tmaxConnectionRequests,\n\t);\n\tif (maxGlobalRequests < maxConnectionRequests) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS must be at least GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t);\n\t}\n\tconst maxConnectionTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveTransfers,\n\t);\n\tconst maxGlobalTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tmaxConnectionTransfers,\n\t);\n\tif (maxGlobalTransfers < maxConnectionTransfers) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS must be at least GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t);\n\t}\n\tconst maxMessageBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_FRAME_BYTES,\n\t\t\"GRIP_DAEMON_MAX_FRAME_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxMessageBytes,\n\t);\n\tconst maxTransferBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_UPLOAD_BYTES,\n\t\t\"GRIP_DAEMON_MAX_UPLOAD_BYTES\",\n\t\t1,\n\t\tNumber.MAX_SAFE_INTEGER,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxTransferBytes,\n\t);\n\tconst maxBufferedOutputBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES,\n\t\t\"GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\t8 * 1024 * 1024,\n\t);\n\tconst protocolLimits = validateRemoteWorkspaceProtocolLimits({\n\t\t...DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\t\tmaxMessageBytes,\n\t\tmaxStringBytes: Math.min(DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxStringBytes, maxMessageBytes),\n\t\tmaxPendingInboundBytes: Math.max(\n\t\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxPendingInboundBytes,\n\t\t\tmaxMessageBytes,\n\t\t),\n\t\tmaxTransferBytes,\n\t\tmaxActiveRequests: maxConnectionRequests,\n\t\tmaxActiveTransfers: Math.min(maxConnectionTransfers, maxGlobalTransfers),\n\t});\n\treturn { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits };\n}\n\nexport async function parseDaemonCommand(\n\targs: readonly string[],\n\tenvironment: NodeJS.ProcessEnv = process.env,\n\tstartupCwd = process.cwd(),\n): Promise<DaemonCommand> {\n\tconst inputs = parseInputs(args);\n\tif (inputs.help) return { help: true };\n\n\tconst { host, port } = resolveDaemonEndpoint(inputs, environment);\n\tconst { allowRoot, workspaceRoot, temporaryRoot } = await resolveDaemonWorkspaceRoots(\n\t\tinputs,\n\t\tenvironment,\n\t\tstartupCwd,\n\t);\n\tconst { token, allowedOrigins, tls } = await resolveDaemonTransportSecurity(inputs, environment, startupCwd);\n\tconst { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits } =\n\t\tresolveDaemonCapacity(environment);\n\n\tconst allowInsecureTransport =\n\t\tinputs.allowInsecureTransport ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT, \"GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT\");\n\tconst allowProcessExec =\n\t\tinputs.allowProcessExec ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_PROCESS_EXEC, \"GRIP_DAEMON_ALLOW_PROCESS_EXEC\");\n\tconst trustProjectLsp =\n\t\tinputs.trustProjectLsp ??\n\t\tparseBoolean(environment.GRIP_DAEMON_TRUST_PROJECT_LSP, \"GRIP_DAEMON_TRUST_PROJECT_LSP\");\n\tconst forwardedEnvironment = parseEnvironmentNames([\n\t\t...(environment.GRIP_DAEMON_ENV?.split(\",\") ?? []),\n\t\t...inputs.forwardedEnvironment,\n\t]);\n\tconst maxConnections = parseInteger(\n\t\tinputs.maxConnections ?? environment.GRIP_DAEMON_MAX_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTIONS/--daemon-max-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst maxPendingConnections = parseInteger(\n\t\tinputs.maxPendingConnections ?? environment.GRIP_DAEMON_MAX_PENDING_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_PENDING_CONNECTIONS/--daemon-max-pending-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst handshakeTimeoutMs = parseInteger(\n\t\tinputs.handshakeTimeoutMs ?? environment.GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS/--daemon-handshake-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\tconst shutdownTimeoutMs = parseInteger(\n\t\tinputs.shutdownTimeoutMs ?? environment.GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS/--daemon-shutdown-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\n\treturn {\n\t\thelp: false,\n\t\tconfiguration: Object.freeze({\n\t\t\thost,\n\t\t\tport,\n\t\t\tworkspaceRoot,\n\t\t\ttemporaryRoot,\n\t\t\ttoken,\n\t\t\tallowedOrigins: Object.freeze(allowedOrigins),\n\t\t\ttls,\n\t\t\tallowInsecureTransport,\n\t\t\tallowProcessExec,\n\t\t\tallowRoot,\n\t\t\tforwardedEnvironment: Object.freeze(forwardedEnvironment),\n\t\t\tlspConfigPath:\n\t\t\t\tinputs.lspConfigPath || environment.GRIP_DAEMON_LSP_CONFIG\n\t\t\t\t\t? resolve(startupCwd, inputs.lspConfigPath ?? environment.GRIP_DAEMON_LSP_CONFIG!)\n\t\t\t\t\t: undefined,\n\t\t\ttrustProjectLsp,\n\t\t\tsandboxInstructionsPath: environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS\n\t\t\t\t? resolve(startupCwd, environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS)\n\t\t\t\t: undefined,\n\t\t\tuserAgentsPath: environment.HOME ? join(environment.HOME, \".grip\", \"AGENTS.md\") : undefined,\n\t\t\tmaxConnections,\n\t\t\tmaxGlobalRequests,\n\t\t\tmaxGlobalTransfers,\n\t\t\tmaxBufferedOutputBytes,\n\t\t\tmaxPendingConnections,\n\t\t\thandshakeTimeoutMs,\n\t\t\tshutdownTimeoutMs,\n\t\t\tprotocolLimits: Object.freeze({ ...protocolLimits }),\n\t\t}),\n\t};\n}\n"]}
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/daemon/config.ts"],"names":[],"mappings":"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","sourcesContent":["import { readFile, realpath, stat } from \"node:fs/promises\";\nimport { isAbsolute, join, resolve } from \"node:path\";\nimport {\n\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\ttype RemoteWorkspaceProtocolLimits,\n\tvalidateRemoteWorkspaceProtocolLimits,\n} from \"../core/remote-workspace-protocol/contract.ts\";\n\nexport const DAEMON_WEBSOCKET_PATH = \"/grip/workspace\";\nexport const DAEMON_WEBSOCKET_PROTOCOL = \"grip.workspace.v1\";\n\nexport interface DaemonTlsConfiguration {\n\treadonly cert: Buffer;\n\treadonly key: Buffer;\n\treadonly passphrase?: string;\n}\n\nexport interface DaemonConfiguration {\n\treadonly host: string;\n\treadonly port: number;\n\treadonly workspaceRoot: string;\n\t/** Canonical fixed parent mount for explicitly enabled shared-channel mode. */\n\treadonly sharedParentRoot?: string;\n\treadonly temporaryRoot?: string;\n\treadonly token?: string;\n\treadonly allowedOrigins: readonly string[];\n\treadonly tls?: DaemonTlsConfiguration;\n\treadonly allowInsecureTransport: boolean;\n\treadonly allowProcessExec: boolean;\n\treadonly allowRoot: boolean;\n\treadonly forwardedEnvironment: readonly string[];\n\treadonly lspConfigPath?: string;\n\treadonly sandboxInstructionsPath?: string;\n\treadonly userAgentsPath?: string;\n\treadonly trustProjectLsp: boolean;\n\treadonly maxConnections: number;\n\treadonly maxGlobalRequests: number;\n\treadonly maxGlobalTransfers: number;\n\treadonly maxBufferedOutputBytes: number;\n\treadonly maxPendingConnections: number;\n\treadonly handshakeTimeoutMs: number;\n\treadonly shutdownTimeoutMs: number;\n\treadonly protocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nexport interface DaemonCommand {\n\treadonly help: boolean;\n\treadonly configuration?: DaemonConfiguration;\n}\n\nexport class DaemonConfigurationError extends Error {\n\tconstructor(message: string) {\n\t\tsuper(message);\n\t\tthis.name = \"DaemonConfigurationError\";\n\t}\n}\n\ninterface ParsedDaemonInputs {\n\thelp: boolean;\n\thost?: string;\n\tport?: string;\n\tcwd?: string;\n\tsharedParent?: string;\n\torigins: string[];\n\ttlsCertPath?: string;\n\ttlsKeyPath?: string;\n\tallowInsecureTransport?: boolean;\n\tallowProcessExec?: boolean;\n\tallowRoot?: boolean;\n\tforwardedEnvironment: string[];\n\tlspConfigPath?: string;\n\ttrustProjectLsp?: boolean;\n\tmaxConnections?: string;\n\tmaxPendingConnections?: string;\n\thandshakeTimeoutMs?: string;\n\tshutdownTimeoutMs?: string;\n}\n\nconst BOOLEAN_VALUES = new Map<string, boolean>([\n\t[\"1\", true],\n\t[\"true\", true],\n\t[\"yes\", true],\n\t[\"0\", false],\n\t[\"false\", false],\n\t[\"no\", false],\n]);\n\nconst VALUE_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-host\", \"host\"],\n\t[\"--daemon-port\", \"port\"],\n\t[\"--daemon-cwd\", \"cwd\"],\n\t[\"--daemon-shared-parent\", \"sharedParent\"],\n\t[\"--daemon-tls-cert\", \"tlsCertPath\"],\n\t[\"--daemon-tls-key\", \"tlsKeyPath\"],\n\t[\"--daemon-lsp-config\", \"lspConfigPath\"],\n\t[\"--daemon-max-connections\", \"maxConnections\"],\n\t[\"--daemon-max-pending-connections\", \"maxPendingConnections\"],\n\t[\"--daemon-handshake-timeout-ms\", \"handshakeTimeoutMs\"],\n\t[\"--daemon-shutdown-timeout-ms\", \"shutdownTimeoutMs\"],\n]);\n\nconst BOOLEAN_FLAGS = new Map<string, keyof ParsedDaemonInputs>([\n\t[\"--daemon-allow-insecure-transport\", \"allowInsecureTransport\"],\n\t[\"--daemon-allow-process-exec\", \"allowProcessExec\"],\n\t[\"--daemon-allow-root\", \"allowRoot\"],\n\t[\"--daemon-trust-project-lsp\", \"trustProjectLsp\"],\n]);\n\nfunction parseBoolean(value: string | undefined, name: string, fallback = false): boolean {\n\tif (value === undefined) return fallback;\n\tconst parsed = BOOLEAN_VALUES.get(value.toLowerCase());\n\tif (parsed === undefined) throw new DaemonConfigurationError(`${name} must be one of 1, 0, true, false, yes, or no`);\n\treturn parsed;\n}\n\nfunction parseInteger(\n\tvalue: string | undefined,\n\tname: string,\n\tminimum: number,\n\tmaximum: number,\n\tfallback: number,\n): number {\n\tif (value === undefined) return fallback;\n\tif (!/^(0|[1-9][0-9]*)$/.test(value)) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\tconst parsed = Number(value);\n\tif (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {\n\t\tthrow new DaemonConfigurationError(`${name} must be a decimal integer from ${minimum} to ${maximum}`);\n\t}\n\treturn parsed;\n}\n\nfunction setSingleton(\n\tinputs: ParsedDaemonInputs,\n\tkey: keyof ParsedDaemonInputs,\n\tvalue: string | boolean,\n\tflag: string,\n): void {\n\tif (inputs[key] !== undefined) throw new DaemonConfigurationError(`${flag} may only be specified once`);\n\t(inputs as Record<keyof ParsedDaemonInputs, unknown>)[key] = value;\n}\n\nfunction requireFlagValue(args: readonly string[], index: number, flag: string): string {\n\tconst value = args[index + 1];\n\tif (value === undefined || value.startsWith(\"-\")) throw new DaemonConfigurationError(`${flag} requires a value`);\n\treturn value;\n}\n\nfunction parseInputs(args: readonly string[]): ParsedDaemonInputs {\n\tif (args[0] !== \"--daemon\") throw new DaemonConfigurationError(\"Daemon command must start with --daemon\");\n\tconst inputs: ParsedDaemonInputs = { help: false, origins: [], forwardedEnvironment: [] };\n\tfor (let index = 1; index < args.length; index++) {\n\t\tconst argument = args[index];\n\t\tif (argument === \"--help\" || argument === \"-h\") {\n\t\t\tinputs.help = true;\n\t\t\tcontinue;\n\t\t}\n\t\tif (argument === \"--daemon-origin\" || argument === \"--daemon-env\") {\n\t\t\tconst value = requireFlagValue(args, index, argument);\n\t\t\tif (argument === \"--daemon-origin\") inputs.origins.push(value);\n\t\t\telse inputs.forwardedEnvironment.push(value);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst valueKey = VALUE_FLAGS.get(argument);\n\t\tif (valueKey) {\n\t\t\tsetSingleton(inputs, valueKey, requireFlagValue(args, index, argument), argument);\n\t\t\tindex++;\n\t\t\tcontinue;\n\t\t}\n\t\tconst booleanKey = BOOLEAN_FLAGS.get(argument);\n\t\tif (booleanKey) {\n\t\t\tsetSingleton(inputs, booleanKey, true, argument);\n\t\t\tcontinue;\n\t\t}\n\t\tthrow new DaemonConfigurationError(\n\t\t\targument.startsWith(\"-\")\n\t\t\t\t? `Unknown or incompatible option in daemon mode: ${argument}`\n\t\t\t\t: `Daemon mode does not accept positional arguments: ${argument}`,\n\t\t);\n\t}\n\treturn inputs;\n}\n\nfunction parseOrigin(value: string): string {\n\tlet url: URL;\n\ttry {\n\t\turl = new URL(value);\n\t} catch {\n\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${value}`);\n\t}\n\tif (\n\t\t(url.protocol !== \"http:\" && url.protocol !== \"https:\") ||\n\t\turl.username !== \"\" ||\n\t\turl.password !== \"\" ||\n\t\turl.pathname !== \"/\" ||\n\t\turl.search !== \"\" ||\n\t\turl.hash !== \"\" ||\n\t\tvalue !== url.origin\n\t) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon origins must be exact HTTP(S) origins without paths or wildcards: ${value}`,\n\t\t);\n\t}\n\treturn url.origin;\n}\n\nfunction parseEnvironmentNames(values: readonly string[]): string[] {\n\tconst names = values\n\t\t.flatMap((value) => value.split(\",\"))\n\t\t.map((value) => value.trim())\n\t\t.filter(Boolean);\n\tconst seen = new Set<string>();\n\tfor (const name of names) {\n\t\tif (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(name))\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon environment name: ${name}`);\n\t\tconst identity = process.platform === \"win32\" ? name.toLowerCase() : name;\n\t\tif (seen.has(identity)) throw new DaemonConfigurationError(`Duplicate daemon environment name: ${name}`);\n\t\tseen.add(identity);\n\t}\n\treturn names;\n}\n\nasync function resolveConfinedRoot(value: string, startupCwd: string, label: string): Promise<string> {\n\tconst candidate = isAbsolute(value) ? value : resolve(startupCwd, value);\n\tlet canonical: string;\n\ttry {\n\t\tcanonical = await realpath(candidate);\n\t\tif (!(await stat(canonical)).isDirectory()) throw new Error(\"not a directory\");\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Daemon ${label} must be an existing directory: ${candidate} (${error instanceof Error ? error.message : String(error)})`,\n\t\t);\n\t}\n\treturn canonical;\n}\n\nasync function readTlsFile(path: string, label: string, startupCwd: string): Promise<Buffer> {\n\tconst resolvedPath = isAbsolute(path) ? path : resolve(startupCwd, path);\n\ttry {\n\t\tconst content = await readFile(resolvedPath);\n\t\tif (content.byteLength === 0) throw new Error(\"file is empty\");\n\t\treturn content;\n\t} catch (error) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t`Unable to read daemon TLS ${label} file ${resolvedPath}: ${error instanceof Error ? error.message : String(error)}`,\n\t\t);\n\t}\n}\n\nfunction validateToken(token: string | undefined): string | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.byteLength(token, \"utf8\");\n\tif (bytes < 32 || bytes > 1024 || !/\\S/u.test(token) || /[\\r\\n]/u.test(token)) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_TOKEN must contain 32 to 1024 UTF-8 bytes, include a non-whitespace character, and contain no line breaks\",\n\t\t);\n\t}\n\treturn token;\n}\n\nfunction isRootProcess(): boolean {\n\treturn process.platform !== \"win32\" && typeof process.getuid === \"function\" && process.getuid() === 0;\n}\n\nexport function isDaemonCommand(args: readonly string[]): boolean {\n\treturn args[0] === \"--daemon\";\n}\ninterface DaemonEndpointConfiguration {\n\thost: string;\n\tport: number;\n}\n\ninterface DaemonWorkspaceRootsConfiguration {\n\tallowRoot: boolean;\n\tworkspaceRoot: string;\n\ttemporaryRoot: string | undefined;\n}\n\ninterface DaemonTransportSecurityConfiguration {\n\ttoken: string | undefined;\n\tallowedOrigins: string[];\n\ttls: DaemonTlsConfiguration | undefined;\n}\n\ninterface DaemonCapacityConfiguration {\n\tmaxGlobalRequests: number;\n\tmaxGlobalTransfers: number;\n\tmaxBufferedOutputBytes: number;\n\tprotocolLimits: RemoteWorkspaceProtocolLimits;\n}\n\nfunction resolveDaemonEndpoint(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n): DaemonEndpointConfiguration {\n\tconst host = inputs.host ?? environment.GRIP_DAEMON_HOST ?? \"127.0.0.1\";\n\tif (host.length === 0 || /\\s/u.test(host)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a non-empty IP address\");\n\t}\n\tconst port = parseInteger(\n\t\tinputs.port ?? environment.GRIP_DAEMON_PORT,\n\t\t\"GRIP_DAEMON_PORT/--daemon-port\",\n\t\t1,\n\t\t65535,\n\t\t8787,\n\t);\n\treturn { host, port };\n}\n\nasync function resolveDaemonWorkspaceRoots(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonWorkspaceRootsConfiguration> {\n\tconst allowRoot = inputs.allowRoot ?? parseBoolean(environment.GRIP_DAEMON_ALLOW_ROOT, \"GRIP_DAEMON_ALLOW_ROOT\");\n\tif (isRootProcess() && !allowRoot) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"Refusing to run the workspace daemon as root; use --daemon-allow-root only inside an intentional OS sandbox\",\n\t\t);\n\t}\n\tconst workspaceRoot = await resolveConfinedRoot(\n\t\tinputs.cwd ?? environment.GRIP_DAEMON_CWD ?? startupCwd,\n\t\tstartupCwd,\n\t\t\"workspace root\",\n\t);\n\tconst temporaryRoot = environment.GRIP_DAEMON_TEMP_ROOT\n\t\t? await resolveConfinedRoot(environment.GRIP_DAEMON_TEMP_ROOT, startupCwd, \"temporary root\")\n\t\t: undefined;\n\treturn { allowRoot, workspaceRoot, temporaryRoot };\n}\n\nasync function resolveSharedParentRoot(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n\tworkspaceRoot: string,\n): Promise<string | undefined> {\n\tconst value = inputs.sharedParent ?? environment.GRIP_DAEMON_SHARED_PARENT;\n\tif (value === undefined) return undefined;\n\tif (value === \"\") throw new DaemonConfigurationError(\"Shared parent mount must not be empty\");\n\tconst root = await resolveConfinedRoot(value, startupCwd, \"shared parent mount\");\n\tif (root !== workspaceRoot) {\n\t\tthrow new DaemonConfigurationError(\"Shared parent mount must equal --daemon-cwd (the fixed mounted parent)\");\n\t}\n\treturn root;\n}\n\nasync function resolveDaemonTransportSecurity(\n\tinputs: ParsedDaemonInputs,\n\tenvironment: NodeJS.ProcessEnv,\n\tstartupCwd: string,\n): Promise<DaemonTransportSecurityConfiguration> {\n\tconst token = validateToken(environment.GRIP_DAEMON_TOKEN);\n\tconst allowedOrigins = [\n\t\t...(environment.GRIP_DAEMON_ORIGINS?.split(\",\")\n\t\t\t.map((value) => value.trim())\n\t\t\t.filter(Boolean) ?? []),\n\t\t...inputs.origins,\n\t].map(parseOrigin);\n\tif (new Set(allowedOrigins).size !== allowedOrigins.length) {\n\t\tthrow new DaemonConfigurationError(\"Daemon origins must be unique\");\n\t}\n\tconst tlsCertPath = inputs.tlsCertPath ?? environment.GRIP_DAEMON_TLS_CERT;\n\tconst tlsKeyPath = inputs.tlsKeyPath ?? environment.GRIP_DAEMON_TLS_KEY;\n\tif ((tlsCertPath === undefined) !== (tlsKeyPath === undefined)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS requires both a certificate and private key\");\n\t}\n\tconst tls =\n\t\ttlsCertPath && tlsKeyPath\n\t\t\t? Object.freeze({\n\t\t\t\t\tcert: await readTlsFile(tlsCertPath, \"certificate\", startupCwd),\n\t\t\t\t\tkey: await readTlsFile(tlsKeyPath, \"private key\", startupCwd),\n\t\t\t\t\tpassphrase: environment.GRIP_DAEMON_TLS_PASSPHRASE,\n\t\t\t\t})\n\t\t\t: undefined;\n\treturn { token, allowedOrigins, tls };\n}\n\nfunction resolveDaemonCapacity(environment: NodeJS.ProcessEnv): DaemonCapacityConfiguration {\n\tconst maxConnectionRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveRequests,\n\t);\n\tconst maxGlobalRequests = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_REQUESTS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS\",\n\t\t1,\n\t\t10_000_000,\n\t\tmaxConnectionRequests,\n\t);\n\tif (maxGlobalRequests < maxConnectionRequests) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_REQUESTS must be at least GRIP_DAEMON_MAX_CONNECTION_REQUESTS\",\n\t\t);\n\t}\n\tconst maxConnectionTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_CONNECTION_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxActiveTransfers,\n\t);\n\tconst maxGlobalTransfers = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_GLOBAL_UPLOADS,\n\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS\",\n\t\t1,\n\t\t10_000,\n\t\tmaxConnectionTransfers,\n\t);\n\tif (maxGlobalTransfers < maxConnectionTransfers) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"GRIP_DAEMON_MAX_GLOBAL_UPLOADS must be at least GRIP_DAEMON_MAX_CONNECTION_UPLOADS\",\n\t\t);\n\t}\n\tconst maxMessageBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_FRAME_BYTES,\n\t\t\"GRIP_DAEMON_MAX_FRAME_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxMessageBytes,\n\t);\n\tconst maxTransferBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_UPLOAD_BYTES,\n\t\t\"GRIP_DAEMON_MAX_UPLOAD_BYTES\",\n\t\t1,\n\t\tNumber.MAX_SAFE_INTEGER,\n\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxTransferBytes,\n\t);\n\tconst maxBufferedOutputBytes = parseInteger(\n\t\tenvironment.GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES,\n\t\t\"GRIP_DAEMON_MAX_BUFFERED_OUTPUT_BYTES\",\n\t\t1024,\n\t\t64 * 1024 * 1024,\n\t\t8 * 1024 * 1024,\n\t);\n\tconst protocolLimits = validateRemoteWorkspaceProtocolLimits({\n\t\t...DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS,\n\t\tmaxMessageBytes,\n\t\tmaxStringBytes: Math.min(DEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxStringBytes, maxMessageBytes),\n\t\tmaxPendingInboundBytes: Math.max(\n\t\t\tDEFAULT_REMOTE_WORKSPACE_PROTOCOL_LIMITS.maxPendingInboundBytes,\n\t\t\tmaxMessageBytes,\n\t\t),\n\t\tmaxTransferBytes,\n\t\tmaxActiveRequests: maxConnectionRequests,\n\t\tmaxActiveTransfers: Math.min(maxConnectionTransfers, maxGlobalTransfers),\n\t});\n\treturn { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits };\n}\n\nexport async function parseDaemonCommand(\n\targs: readonly string[],\n\tenvironment: NodeJS.ProcessEnv = process.env,\n\tstartupCwd = process.cwd(),\n): Promise<DaemonCommand> {\n\tconst inputs = parseInputs(args);\n\tif (inputs.help) return { help: true };\n\n\tconst { host, port } = resolveDaemonEndpoint(inputs, environment);\n\tconst { allowRoot, workspaceRoot, temporaryRoot } = await resolveDaemonWorkspaceRoots(\n\t\tinputs,\n\t\tenvironment,\n\t\tstartupCwd,\n\t);\n\tconst sharedParentRoot = await resolveSharedParentRoot(inputs, environment, startupCwd, workspaceRoot);\n\tconst { token, allowedOrigins, tls } = await resolveDaemonTransportSecurity(inputs, environment, startupCwd);\n\tconst { maxGlobalRequests, maxGlobalTransfers, maxBufferedOutputBytes, protocolLimits } =\n\t\tresolveDaemonCapacity(environment);\n\n\tconst allowInsecureTransport =\n\t\tinputs.allowInsecureTransport ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT, \"GRIP_DAEMON_ALLOW_INSECURE_TRANSPORT\");\n\tconst allowProcessExec =\n\t\tinputs.allowProcessExec ??\n\t\tparseBoolean(environment.GRIP_DAEMON_ALLOW_PROCESS_EXEC, \"GRIP_DAEMON_ALLOW_PROCESS_EXEC\");\n\tconst trustProjectLsp =\n\t\tinputs.trustProjectLsp ??\n\t\tparseBoolean(environment.GRIP_DAEMON_TRUST_PROJECT_LSP, \"GRIP_DAEMON_TRUST_PROJECT_LSP\");\n\tconst forwardedEnvironment = parseEnvironmentNames([\n\t\t...(environment.GRIP_DAEMON_ENV?.split(\",\") ?? []),\n\t\t...inputs.forwardedEnvironment,\n\t]);\n\tconst maxConnections = parseInteger(\n\t\tinputs.maxConnections ?? environment.GRIP_DAEMON_MAX_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_CONNECTIONS/--daemon-max-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst maxPendingConnections = parseInteger(\n\t\tinputs.maxPendingConnections ?? environment.GRIP_DAEMON_MAX_PENDING_CONNECTIONS,\n\t\t\"GRIP_DAEMON_MAX_PENDING_CONNECTIONS/--daemon-max-pending-connections\",\n\t\t1,\n\t\t100_000,\n\t\t64,\n\t);\n\tconst handshakeTimeoutMs = parseInteger(\n\t\tinputs.handshakeTimeoutMs ?? environment.GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_HANDSHAKE_TIMEOUT_MS/--daemon-handshake-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\tconst shutdownTimeoutMs = parseInteger(\n\t\tinputs.shutdownTimeoutMs ?? environment.GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS,\n\t\t\"GRIP_DAEMON_SHUTDOWN_TIMEOUT_MS/--daemon-shutdown-timeout-ms\",\n\t\t100,\n\t\t5 * 60_000,\n\t\t10_000,\n\t);\n\n\treturn {\n\t\thelp: false,\n\t\tconfiguration: Object.freeze({\n\t\t\thost,\n\t\t\tport,\n\t\t\tworkspaceRoot,\n\t\t\tsharedParentRoot,\n\t\t\ttemporaryRoot,\n\t\t\ttoken,\n\t\t\tallowedOrigins: Object.freeze(allowedOrigins),\n\t\t\ttls,\n\t\t\tallowInsecureTransport,\n\t\t\tallowProcessExec,\n\t\t\tallowRoot,\n\t\t\tforwardedEnvironment: Object.freeze(forwardedEnvironment),\n\t\t\tlspConfigPath:\n\t\t\t\tinputs.lspConfigPath || environment.GRIP_DAEMON_LSP_CONFIG\n\t\t\t\t\t? resolve(startupCwd, inputs.lspConfigPath ?? environment.GRIP_DAEMON_LSP_CONFIG!)\n\t\t\t\t\t: undefined,\n\t\t\ttrustProjectLsp,\n\t\t\tsandboxInstructionsPath: environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS\n\t\t\t\t? resolve(startupCwd, environment.GRIP_DAEMON_SANDBOX_INSTRUCTIONS)\n\t\t\t\t: undefined,\n\t\t\tuserAgentsPath: environment.HOME ? join(environment.HOME, \".grip\", \"AGENTS.md\") : undefined,\n\t\t\tmaxConnections,\n\t\t\tmaxGlobalRequests,\n\t\t\tmaxGlobalTransfers,\n\t\t\tmaxBufferedOutputBytes,\n\t\t\tmaxPendingConnections,\n\t\t\thandshakeTimeoutMs,\n\t\t\tshutdownTimeoutMs,\n\t\t\tprotocolLimits: Object.freeze({ ...protocolLimits }),\n\t\t}),\n\t};\n}\n"]}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Aggregate outstanding sends on one physical WebSocket (v1 or v2). */
|
|
2
|
+
export declare class DaemonOutboundBudget {
|
|
3
|
+
private pendingBytes;
|
|
4
|
+
private pendingMessages;
|
|
5
|
+
private readonly maxBytes;
|
|
6
|
+
private readonly maxMessages;
|
|
7
|
+
constructor(maxBytes: number, maxMessages: number);
|
|
8
|
+
reserve(bytes: number, bufferedAmount: number): () => void;
|
|
9
|
+
}
|
|
10
|
+
//# sourceMappingURL=outbound-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-budget.d.ts","sourceRoot":"","sources":["../../src/daemon/outbound-budget.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,qBAAa,oBAAoB;IAChC,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,eAAe,CAAK;IAC5B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IAErC,YAAY,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAGhD;IAED,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,MAAM,IAAI,CAkBzD;CACD","sourcesContent":["/** Aggregate outstanding sends on one physical WebSocket (v1 or v2). */\nexport class DaemonOutboundBudget {\n\tprivate pendingBytes = 0;\n\tprivate pendingMessages = 0;\n\tprivate readonly maxBytes: number;\n\tprivate readonly maxMessages: number;\n\n\tconstructor(maxBytes: number, maxMessages: number) {\n\t\tthis.maxBytes = maxBytes;\n\t\tthis.maxMessages = maxMessages;\n\t}\n\n\treserve(bytes: number, bufferedAmount: number): () => void {\n\t\t// ws.bufferedAmount measures socket-queued data; a callback can also stall after\n\t\t// the socket accepts data. Bound both views without double-counting overlap.\n\t\tif (\n\t\t\tthis.pendingMessages >= this.maxMessages ||\n\t\t\tMath.max(this.pendingBytes, bufferedAmount) + bytes > this.maxBytes\n\t\t) {\n\t\t\tthrow new Error(\"WebSocket outbound buffer exceeds daemon limits\");\n\t\t}\n\t\tthis.pendingMessages++;\n\t\tthis.pendingBytes += bytes;\n\t\tlet released = false;\n\t\treturn () => {\n\t\t\tif (released) return;\n\t\t\treleased = true;\n\t\t\tthis.pendingMessages--;\n\t\t\tthis.pendingBytes -= bytes;\n\t\t};\n\t}\n}\n"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/** Aggregate outstanding sends on one physical WebSocket (v1 or v2). */
|
|
2
|
+
export class DaemonOutboundBudget {
|
|
3
|
+
pendingBytes = 0;
|
|
4
|
+
pendingMessages = 0;
|
|
5
|
+
maxBytes;
|
|
6
|
+
maxMessages;
|
|
7
|
+
constructor(maxBytes, maxMessages) {
|
|
8
|
+
this.maxBytes = maxBytes;
|
|
9
|
+
this.maxMessages = maxMessages;
|
|
10
|
+
}
|
|
11
|
+
reserve(bytes, bufferedAmount) {
|
|
12
|
+
// ws.bufferedAmount measures socket-queued data; a callback can also stall after
|
|
13
|
+
// the socket accepts data. Bound both views without double-counting overlap.
|
|
14
|
+
if (this.pendingMessages >= this.maxMessages ||
|
|
15
|
+
Math.max(this.pendingBytes, bufferedAmount) + bytes > this.maxBytes) {
|
|
16
|
+
throw new Error("WebSocket outbound buffer exceeds daemon limits");
|
|
17
|
+
}
|
|
18
|
+
this.pendingMessages++;
|
|
19
|
+
this.pendingBytes += bytes;
|
|
20
|
+
let released = false;
|
|
21
|
+
return () => {
|
|
22
|
+
if (released)
|
|
23
|
+
return;
|
|
24
|
+
released = true;
|
|
25
|
+
this.pendingMessages--;
|
|
26
|
+
this.pendingBytes -= bytes;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=outbound-budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"outbound-budget.js","sourceRoot":"","sources":["../../src/daemon/outbound-budget.ts"],"names":[],"mappings":"AAAA,wEAAwE;AACxE,MAAM,OAAO,oBAAoB;IACxB,YAAY,GAAG,CAAC,CAAC;IACjB,eAAe,GAAG,CAAC,CAAC;IACX,QAAQ,CAAS;IACjB,WAAW,CAAS;IAErC,YAAY,QAAgB,EAAE,WAAmB,EAAE;QAClD,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;IAAA,CAC/B;IAED,OAAO,CAAC,KAAa,EAAE,cAAsB,EAAc;QAC1D,iFAAiF;QACjF,6EAA6E;QAC7E,IACC,IAAI,CAAC,eAAe,IAAI,IAAI,CAAC,WAAW;YACxC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,cAAc,CAAC,GAAG,KAAK,GAAG,IAAI,CAAC,QAAQ,EAClE,CAAC;YACF,MAAM,IAAI,KAAK,CAAC,iDAAiD,CAAC,CAAC;QACpE,CAAC;QACD,IAAI,CAAC,eAAe,EAAE,CAAC;QACvB,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC;QAC3B,IAAI,QAAQ,GAAG,KAAK,CAAC;QACrB,OAAO,GAAG,EAAE,CAAC;YACZ,IAAI,QAAQ;gBAAE,OAAO;YACrB,QAAQ,GAAG,IAAI,CAAC;YAChB,IAAI,CAAC,eAAe,EAAE,CAAC;YACvB,IAAI,CAAC,YAAY,IAAI,KAAK,CAAC;QAAA,CAC3B,CAAC;IAAA,CACF;CACD","sourcesContent":["/** Aggregate outstanding sends on one physical WebSocket (v1 or v2). */\nexport class DaemonOutboundBudget {\n\tprivate pendingBytes = 0;\n\tprivate pendingMessages = 0;\n\tprivate readonly maxBytes: number;\n\tprivate readonly maxMessages: number;\n\n\tconstructor(maxBytes: number, maxMessages: number) {\n\t\tthis.maxBytes = maxBytes;\n\t\tthis.maxMessages = maxMessages;\n\t}\n\n\treserve(bytes: number, bufferedAmount: number): () => void {\n\t\t// ws.bufferedAmount measures socket-queued data; a callback can also stall after\n\t\t// the socket accepts data. Bound both views without double-counting overlap.\n\t\tif (\n\t\t\tthis.pendingMessages >= this.maxMessages ||\n\t\t\tMath.max(this.pendingBytes, bufferedAmount) + bytes > this.maxBytes\n\t\t) {\n\t\t\tthrow new Error(\"WebSocket outbound buffer exceeds daemon limits\");\n\t\t}\n\t\tthis.pendingMessages++;\n\t\tthis.pendingBytes += bytes;\n\t\tlet released = false;\n\t\treturn () => {\n\t\t\tif (released) return;\n\t\t\treleased = true;\n\t\t\tthis.pendingMessages--;\n\t\t\tthis.pendingBytes -= bytes;\n\t\t};\n\t}\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/daemon/security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAGN,KAAK,mBAAmB,EAExB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AA2BD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMpD;AA6ED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,mBAAmB,GAAG,IAAI,CAMpF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,GAAG,SAAS,CAIpG;AAaD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,eAAe,EACxB,aAAa,EAAE,mBAAmB,EAClC,aAAa,EAAE,mBAAmB,GAAG,SAAS,GAC5C,qBAAqB,CA8BvB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAazF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,CAOhG","sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\nimport { isIP } from \"node:net\";\nimport type { Duplex } from \"node:stream\";\nimport {\n\tDAEMON_WEBSOCKET_PATH,\n\tDAEMON_WEBSOCKET_PROTOCOL,\n\ttype DaemonConfiguration,\n\tDaemonConfigurationError,\n} from \"./config.ts\";\n\nexport interface DaemonAuthorization {\n\treadonly configuredBytes: number;\n\treadonly digest: Buffer;\n}\n\nexport interface DaemonUpgradeDecision {\n\treadonly accepted: boolean;\n\treadonly status: number;\n\treadonly message: string;\n}\n\nfunction headerValues(request: IncomingMessage, name: string): string[] {\n\tconst values: string[] = [];\n\tfor (let index = 0; index < request.rawHeaders.length; index += 2) {\n\t\tif (request.rawHeaders[index]?.toLowerCase() === name) values.push(request.rawHeaders[index + 1] ?? \"\");\n\t}\n\treturn values;\n}\n\nfunction singleHeader(request: IncomingMessage, name: string): string | undefined {\n\tconst values = headerValues(request, name);\n\treturn values.length === 1 ? values[0] : undefined;\n}\n\nfunction hasToken(value: string, token: string): boolean {\n\treturn value\n\t\t.split(\",\")\n\t\t.map((entry) => entry.trim().toLowerCase())\n\t\t.includes(token);\n}\n\nfunction isValidWebSocketKey(value: string): boolean {\n\tif (!/^[A-Za-z0-9+/]{22}==$/.test(value)) return false;\n\treturn Buffer.from(value, \"base64\").byteLength === 16;\n}\n\nexport function isLoopbackBind(host: string): boolean {\n\tconst normalized = host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n\tif (isIP(normalized) === 4) return normalized.split(\".\")[0] === \"127\";\n\tif (isIP(normalized) !== 6) return false;\n\tconst lower = normalized.toLowerCase();\n\treturn lower === \"::1\" || /^::ffff:127(?:\\.[0-9]{1,3}){3}$/.test(lower);\n}\nfunction validateDaemonRuntimeLimits(configuration: DaemonConfiguration): void {\n\tif (!Number.isSafeInteger(configuration.port) || configuration.port < 0 || configuration.port > 65535) {\n\t\tthrow new DaemonConfigurationError(\"Daemon server port must be an integer from 0 to 65535\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxConnections) || configuration.maxConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxConnections must be a positive integer\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxPendingConnections) || configuration.maxPendingConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxPendingConnections must be a positive integer\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.handshakeTimeoutMs) ||\n\t\tconfiguration.handshakeTimeoutMs < 100 ||\n\t\tconfiguration.handshakeTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon handshakeTimeoutMs must be an integer from 100 to 300000\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.shutdownTimeoutMs) ||\n\t\tconfiguration.shutdownTimeoutMs < 100 ||\n\t\tconfiguration.shutdownTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon shutdownTimeoutMs must be an integer from 100 to 300000\");\n\t}\n}\n\nfunction validateDaemonCredentials(configuration: DaemonConfiguration): void {\n\tif (process.platform !== \"win32\" && process.getuid?.() === 0 && !configuration.allowRoot) {\n\t\tthrow new DaemonConfigurationError(\"Refusing to run the workspace daemon as root without allowRoot\");\n\t}\n\tif (configuration.token === undefined) return;\n\tconst tokenBytes = Buffer.byteLength(configuration.token, \"utf8\");\n\tif (\n\t\ttokenBytes < 32 ||\n\t\ttokenBytes > 1024 ||\n\t\t!/\\S/u.test(configuration.token) ||\n\t\t/[\\r\\n]/u.test(configuration.token)\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon token must contain 32 to 1024 UTF-8 bytes and no line breaks\");\n\t}\n}\n\nfunction validateDaemonOrigins(origins: readonly string[]): void {\n\tfor (const origin of origins) {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(origin);\n\t\t} catch {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t\tif ((parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") || origin !== parsed.origin) {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t}\n}\n\nfunction validateDaemonTls(configuration: DaemonConfiguration): void {\n\tif (configuration.tls && (configuration.tls.cert.byteLength === 0 || configuration.tls.key.byteLength === 0)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS certificate and private key must not be empty\");\n\t}\n}\n\nfunction validateDaemonBindExposure(configuration: DaemonConfiguration): void {\n\tif (isIP(configuration.host.replace(/^\\[|\\]$/g, \"\")) === 0) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a literal IPv4 or IPv6 address\");\n\t}\n\tif (isLoopbackBind(configuration.host)) return;\n\tif (!configuration.token)\n\t\tthrow new DaemonConfigurationError(\"A non-loopback daemon bind requires GRIP_DAEMON_TOKEN\");\n\tif (!configuration.tls && !configuration.allowInsecureTransport) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"A non-loopback daemon bind requires TLS or explicit --daemon-allow-insecure-transport acknowledgement\",\n\t\t);\n\t}\n}\n\nexport function validateDaemonNetworkPolicy(configuration: DaemonConfiguration): void {\n\tvalidateDaemonRuntimeLimits(configuration);\n\tvalidateDaemonCredentials(configuration);\n\tvalidateDaemonOrigins(configuration.allowedOrigins);\n\tvalidateDaemonTls(configuration);\n\tvalidateDaemonBindExposure(configuration);\n}\n\nexport function createDaemonAuthorization(token: string | undefined): DaemonAuthorization | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.from(token, \"utf8\");\n\treturn Object.freeze({ configuredBytes: bytes.byteLength, digest: createHash(\"sha256\").update(bytes).digest() });\n}\n\nfunction authenticateAuthorizationHeader(\n\tvalue: string | undefined,\n\tauthorization: DaemonAuthorization | undefined,\n): boolean {\n\tif (!authorization) return value === undefined;\n\tif (!value?.startsWith(\"Bearer \")) return false;\n\tconst presented = Buffer.from(value.slice(\"Bearer \".length), \"utf8\");\n\tconst digest = createHash(\"sha256\").update(presented).digest();\n\treturn presented.byteLength === authorization.configuredBytes && timingSafeEqual(digest, authorization.digest);\n}\n\nexport function validateDaemonUpgrade(\n\trequest: IncomingMessage,\n\tconfiguration: DaemonConfiguration,\n\tauthorization: DaemonAuthorization | undefined,\n): DaemonUpgradeDecision {\n\tif (request.method !== \"GET\" || request.url !== DAEMON_WEBSOCKET_PATH) {\n\t\treturn { accepted: false, status: 404, message: \"Not found\" };\n\t}\n\tconst upgrade = singleHeader(request, \"upgrade\");\n\tconst connection = singleHeader(request, \"connection\");\n\tconst version = singleHeader(request, \"sec-websocket-version\");\n\tconst key = singleHeader(request, \"sec-websocket-key\");\n\tconst protocol = singleHeader(request, \"sec-websocket-protocol\");\n\tif (\n\t\tupgrade?.toLowerCase() !== \"websocket\" ||\n\t\t!connection ||\n\t\t!hasToken(connection, \"upgrade\") ||\n\t\tversion !== \"13\" ||\n\t\t!key ||\n\t\t!isValidWebSocketKey(key) ||\n\t\tprotocol !== DAEMON_WEBSOCKET_PROTOCOL\n\t) {\n\t\treturn { accepted: false, status: 400, message: \"Invalid WebSocket upgrade\" };\n\t}\n\tconst originValues = headerValues(request, \"origin\");\n\tif (originValues.length > 1) return { accepted: false, status: 403, message: \"Origin not allowed\" };\n\tif (originValues.length === 1 && !configuration.allowedOrigins.includes(originValues[0])) {\n\t\treturn { accepted: false, status: 403, message: \"Origin not allowed\" };\n\t}\n\tconst authorizationValues = headerValues(request, \"authorization\");\n\tif (authorizationValues.length > 1 || !authenticateAuthorizationHeader(authorizationValues[0], authorization)) {\n\t\treturn { accepted: false, status: 401, message: \"Unauthorized\" };\n\t}\n\treturn { accepted: true, status: 101, message: \"Switching Protocols\" };\n}\n\nexport function rejectDaemonUpgrade(socket: Duplex, decision: DaemonUpgradeDecision): void {\n\tconst statusText =\n\t\tnew Map([\n\t\t\t[400, \"Bad Request\"],\n\t\t\t[401, \"Unauthorized\"],\n\t\t\t[403, \"Forbidden\"],\n\t\t\t[404, \"Not Found\"],\n\t\t\t[503, \"Service Unavailable\"],\n\t\t]).get(decision.status) ?? \"Rejected\";\n\tconst body = `${decision.message}\\n`;\n\tsocket.end(\n\t\t`HTTP/1.1 ${decision.status} ${statusText}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\n\\r\\n${body}`,\n\t);\n}\n\nexport function redactDaemonText(value: string, secrets: readonly (string | undefined)[]): string {\n\tlet result = value\n\t\t.replace(/(authorization\\s*:\\s*bearer\\s+)[^\\s,;]+/giu, \"$1[REDACTED]\")\n\t\t.replace(/([?&](?:token|key|secret|password|credential)=)[^&#\\s]*/giu, \"$1[REDACTED]\")\n\t\t.replace(/(\\/\\/)[^/@\\s]+:[^/@\\s]+@/gu, \"$1[REDACTED]@\");\n\tfor (const secret of secrets) if (secret) result = result.split(secret).join(\"[REDACTED]\");\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"security.d.ts","sourceRoot":"","sources":["../../src/daemon/security.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAE1C,OAAO,EAGN,KAAK,mBAAmB,EAExB,MAAM,aAAa,CAAC;AAErB,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CACzB;AA2BD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAMpD;AA6ED,wBAAgB,2BAA2B,CAAC,aAAa,EAAE,mBAAmB,GAAG,IAAI,CAMpF;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,mBAAmB,GAAG,SAAS,CAIpG;AAaD,wBAAgB,qBAAqB,CACpC,OAAO,EAAE,eAAe,EACxB,aAAa,EAAE,mBAAmB,EAClC,aAAa,EAAE,mBAAmB,GAAG,SAAS,GAC5C,qBAAqB,CA+BvB;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,qBAAqB,GAAG,IAAI,CAazF;AAED,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,MAAM,GAAG,SAAS,CAAC,EAAE,GAAG,MAAM,CAOhG","sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\nimport { isIP } from \"node:net\";\nimport type { Duplex } from \"node:stream\";\nimport { SHARED_WORKSPACE_WEBSOCKET_PROTOCOL } from \"../core/remote-workspace-protocol/shared-transport.ts\";\nimport {\n\tDAEMON_WEBSOCKET_PATH,\n\tDAEMON_WEBSOCKET_PROTOCOL,\n\ttype DaemonConfiguration,\n\tDaemonConfigurationError,\n} from \"./config.ts\";\n\nexport interface DaemonAuthorization {\n\treadonly configuredBytes: number;\n\treadonly digest: Buffer;\n}\n\nexport interface DaemonUpgradeDecision {\n\treadonly accepted: boolean;\n\treadonly status: number;\n\treadonly message: string;\n}\n\nfunction headerValues(request: IncomingMessage, name: string): string[] {\n\tconst values: string[] = [];\n\tfor (let index = 0; index < request.rawHeaders.length; index += 2) {\n\t\tif (request.rawHeaders[index]?.toLowerCase() === name) values.push(request.rawHeaders[index + 1] ?? \"\");\n\t}\n\treturn values;\n}\n\nfunction singleHeader(request: IncomingMessage, name: string): string | undefined {\n\tconst values = headerValues(request, name);\n\treturn values.length === 1 ? values[0] : undefined;\n}\n\nfunction hasToken(value: string, token: string): boolean {\n\treturn value\n\t\t.split(\",\")\n\t\t.map((entry) => entry.trim().toLowerCase())\n\t\t.includes(token);\n}\n\nfunction isValidWebSocketKey(value: string): boolean {\n\tif (!/^[A-Za-z0-9+/]{22}==$/.test(value)) return false;\n\treturn Buffer.from(value, \"base64\").byteLength === 16;\n}\n\nexport function isLoopbackBind(host: string): boolean {\n\tconst normalized = host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n\tif (isIP(normalized) === 4) return normalized.split(\".\")[0] === \"127\";\n\tif (isIP(normalized) !== 6) return false;\n\tconst lower = normalized.toLowerCase();\n\treturn lower === \"::1\" || /^::ffff:127(?:\\.[0-9]{1,3}){3}$/.test(lower);\n}\nfunction validateDaemonRuntimeLimits(configuration: DaemonConfiguration): void {\n\tif (!Number.isSafeInteger(configuration.port) || configuration.port < 0 || configuration.port > 65535) {\n\t\tthrow new DaemonConfigurationError(\"Daemon server port must be an integer from 0 to 65535\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxConnections) || configuration.maxConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxConnections must be a positive integer\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxPendingConnections) || configuration.maxPendingConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxPendingConnections must be a positive integer\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.handshakeTimeoutMs) ||\n\t\tconfiguration.handshakeTimeoutMs < 100 ||\n\t\tconfiguration.handshakeTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon handshakeTimeoutMs must be an integer from 100 to 300000\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.shutdownTimeoutMs) ||\n\t\tconfiguration.shutdownTimeoutMs < 100 ||\n\t\tconfiguration.shutdownTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon shutdownTimeoutMs must be an integer from 100 to 300000\");\n\t}\n}\n\nfunction validateDaemonCredentials(configuration: DaemonConfiguration): void {\n\tif (process.platform !== \"win32\" && process.getuid?.() === 0 && !configuration.allowRoot) {\n\t\tthrow new DaemonConfigurationError(\"Refusing to run the workspace daemon as root without allowRoot\");\n\t}\n\tif (configuration.token === undefined) return;\n\tconst tokenBytes = Buffer.byteLength(configuration.token, \"utf8\");\n\tif (\n\t\ttokenBytes < 32 ||\n\t\ttokenBytes > 1024 ||\n\t\t!/\\S/u.test(configuration.token) ||\n\t\t/[\\r\\n]/u.test(configuration.token)\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon token must contain 32 to 1024 UTF-8 bytes and no line breaks\");\n\t}\n}\n\nfunction validateDaemonOrigins(origins: readonly string[]): void {\n\tfor (const origin of origins) {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(origin);\n\t\t} catch {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t\tif ((parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") || origin !== parsed.origin) {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t}\n}\n\nfunction validateDaemonTls(configuration: DaemonConfiguration): void {\n\tif (configuration.tls && (configuration.tls.cert.byteLength === 0 || configuration.tls.key.byteLength === 0)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS certificate and private key must not be empty\");\n\t}\n}\n\nfunction validateDaemonBindExposure(configuration: DaemonConfiguration): void {\n\tif (isIP(configuration.host.replace(/^\\[|\\]$/g, \"\")) === 0) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a literal IPv4 or IPv6 address\");\n\t}\n\tif (isLoopbackBind(configuration.host)) return;\n\tif (!configuration.token)\n\t\tthrow new DaemonConfigurationError(\"A non-loopback daemon bind requires GRIP_DAEMON_TOKEN\");\n\tif (!configuration.tls && !configuration.allowInsecureTransport) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"A non-loopback daemon bind requires TLS or explicit --daemon-allow-insecure-transport acknowledgement\",\n\t\t);\n\t}\n}\n\nexport function validateDaemonNetworkPolicy(configuration: DaemonConfiguration): void {\n\tvalidateDaemonRuntimeLimits(configuration);\n\tvalidateDaemonCredentials(configuration);\n\tvalidateDaemonOrigins(configuration.allowedOrigins);\n\tvalidateDaemonTls(configuration);\n\tvalidateDaemonBindExposure(configuration);\n}\n\nexport function createDaemonAuthorization(token: string | undefined): DaemonAuthorization | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.from(token, \"utf8\");\n\treturn Object.freeze({ configuredBytes: bytes.byteLength, digest: createHash(\"sha256\").update(bytes).digest() });\n}\n\nfunction authenticateAuthorizationHeader(\n\tvalue: string | undefined,\n\tauthorization: DaemonAuthorization | undefined,\n): boolean {\n\tif (!authorization) return value === undefined;\n\tif (!value?.startsWith(\"Bearer \")) return false;\n\tconst presented = Buffer.from(value.slice(\"Bearer \".length), \"utf8\");\n\tconst digest = createHash(\"sha256\").update(presented).digest();\n\treturn presented.byteLength === authorization.configuredBytes && timingSafeEqual(digest, authorization.digest);\n}\n\nexport function validateDaemonUpgrade(\n\trequest: IncomingMessage,\n\tconfiguration: DaemonConfiguration,\n\tauthorization: DaemonAuthorization | undefined,\n): DaemonUpgradeDecision {\n\tif (request.method !== \"GET\" || request.url !== DAEMON_WEBSOCKET_PATH) {\n\t\treturn { accepted: false, status: 404, message: \"Not found\" };\n\t}\n\tconst upgrade = singleHeader(request, \"upgrade\");\n\tconst connection = singleHeader(request, \"connection\");\n\tconst version = singleHeader(request, \"sec-websocket-version\");\n\tconst key = singleHeader(request, \"sec-websocket-key\");\n\tconst protocol = singleHeader(request, \"sec-websocket-protocol\");\n\tif (\n\t\tupgrade?.toLowerCase() !== \"websocket\" ||\n\t\t!connection ||\n\t\t!hasToken(connection, \"upgrade\") ||\n\t\tversion !== \"13\" ||\n\t\t!key ||\n\t\t!isValidWebSocketKey(key) ||\n\t\t(protocol !== DAEMON_WEBSOCKET_PROTOCOL &&\n\t\t\t(protocol !== SHARED_WORKSPACE_WEBSOCKET_PROTOCOL || !configuration.sharedParentRoot))\n\t) {\n\t\treturn { accepted: false, status: 400, message: \"Invalid WebSocket upgrade\" };\n\t}\n\tconst originValues = headerValues(request, \"origin\");\n\tif (originValues.length > 1) return { accepted: false, status: 403, message: \"Origin not allowed\" };\n\tif (originValues.length === 1 && !configuration.allowedOrigins.includes(originValues[0])) {\n\t\treturn { accepted: false, status: 403, message: \"Origin not allowed\" };\n\t}\n\tconst authorizationValues = headerValues(request, \"authorization\");\n\tif (authorizationValues.length > 1 || !authenticateAuthorizationHeader(authorizationValues[0], authorization)) {\n\t\treturn { accepted: false, status: 401, message: \"Unauthorized\" };\n\t}\n\treturn { accepted: true, status: 101, message: \"Switching Protocols\" };\n}\n\nexport function rejectDaemonUpgrade(socket: Duplex, decision: DaemonUpgradeDecision): void {\n\tconst statusText =\n\t\tnew Map([\n\t\t\t[400, \"Bad Request\"],\n\t\t\t[401, \"Unauthorized\"],\n\t\t\t[403, \"Forbidden\"],\n\t\t\t[404, \"Not Found\"],\n\t\t\t[503, \"Service Unavailable\"],\n\t\t]).get(decision.status) ?? \"Rejected\";\n\tconst body = `${decision.message}\\n`;\n\tsocket.end(\n\t\t`HTTP/1.1 ${decision.status} ${statusText}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\n\\r\\n${body}`,\n\t);\n}\n\nexport function redactDaemonText(value: string, secrets: readonly (string | undefined)[]): string {\n\tlet result = value\n\t\t.replace(/(authorization\\s*:\\s*bearer\\s+)[^\\s,;]+/giu, \"$1[REDACTED]\")\n\t\t.replace(/([?&](?:token|key|secret|password|credential)=)[^&#\\s]*/giu, \"$1[REDACTED]\")\n\t\t.replace(/(\\/\\/)[^/@\\s]+:[^/@\\s]+@/gu, \"$1[REDACTED]@\");\n\tfor (const secret of secrets) if (secret) result = result.split(secret).join(\"[REDACTED]\");\n\treturn result;\n}\n"]}
|
package/dist/daemon/security.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createHash, timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { isIP } from "node:net";
|
|
3
|
+
import { SHARED_WORKSPACE_WEBSOCKET_PROTOCOL } from "../core/remote-workspace-protocol/shared-transport.js";
|
|
3
4
|
import { DAEMON_WEBSOCKET_PATH, DAEMON_WEBSOCKET_PROTOCOL, DaemonConfigurationError, } from "./config.js";
|
|
4
5
|
function headerValues(request, name) {
|
|
5
6
|
const values = [];
|
|
@@ -136,7 +137,8 @@ export function validateDaemonUpgrade(request, configuration, authorization) {
|
|
|
136
137
|
version !== "13" ||
|
|
137
138
|
!key ||
|
|
138
139
|
!isValidWebSocketKey(key) ||
|
|
139
|
-
protocol !== DAEMON_WEBSOCKET_PROTOCOL
|
|
140
|
+
(protocol !== DAEMON_WEBSOCKET_PROTOCOL &&
|
|
141
|
+
(protocol !== SHARED_WORKSPACE_WEBSOCKET_PROTOCOL || !configuration.sharedParentRoot))) {
|
|
140
142
|
return { accepted: false, status: 400, message: "Invalid WebSocket upgrade" };
|
|
141
143
|
}
|
|
142
144
|
const originValues = headerValues(request, "origin");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/daemon/security.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\nimport { isIP } from \"node:net\";\nimport type { Duplex } from \"node:stream\";\nimport {\n\tDAEMON_WEBSOCKET_PATH,\n\tDAEMON_WEBSOCKET_PROTOCOL,\n\ttype DaemonConfiguration,\n\tDaemonConfigurationError,\n} from \"./config.ts\";\n\nexport interface DaemonAuthorization {\n\treadonly configuredBytes: number;\n\treadonly digest: Buffer;\n}\n\nexport interface DaemonUpgradeDecision {\n\treadonly accepted: boolean;\n\treadonly status: number;\n\treadonly message: string;\n}\n\nfunction headerValues(request: IncomingMessage, name: string): string[] {\n\tconst values: string[] = [];\n\tfor (let index = 0; index < request.rawHeaders.length; index += 2) {\n\t\tif (request.rawHeaders[index]?.toLowerCase() === name) values.push(request.rawHeaders[index + 1] ?? \"\");\n\t}\n\treturn values;\n}\n\nfunction singleHeader(request: IncomingMessage, name: string): string | undefined {\n\tconst values = headerValues(request, name);\n\treturn values.length === 1 ? values[0] : undefined;\n}\n\nfunction hasToken(value: string, token: string): boolean {\n\treturn value\n\t\t.split(\",\")\n\t\t.map((entry) => entry.trim().toLowerCase())\n\t\t.includes(token);\n}\n\nfunction isValidWebSocketKey(value: string): boolean {\n\tif (!/^[A-Za-z0-9+/]{22}==$/.test(value)) return false;\n\treturn Buffer.from(value, \"base64\").byteLength === 16;\n}\n\nexport function isLoopbackBind(host: string): boolean {\n\tconst normalized = host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n\tif (isIP(normalized) === 4) return normalized.split(\".\")[0] === \"127\";\n\tif (isIP(normalized) !== 6) return false;\n\tconst lower = normalized.toLowerCase();\n\treturn lower === \"::1\" || /^::ffff:127(?:\\.[0-9]{1,3}){3}$/.test(lower);\n}\nfunction validateDaemonRuntimeLimits(configuration: DaemonConfiguration): void {\n\tif (!Number.isSafeInteger(configuration.port) || configuration.port < 0 || configuration.port > 65535) {\n\t\tthrow new DaemonConfigurationError(\"Daemon server port must be an integer from 0 to 65535\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxConnections) || configuration.maxConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxConnections must be a positive integer\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxPendingConnections) || configuration.maxPendingConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxPendingConnections must be a positive integer\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.handshakeTimeoutMs) ||\n\t\tconfiguration.handshakeTimeoutMs < 100 ||\n\t\tconfiguration.handshakeTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon handshakeTimeoutMs must be an integer from 100 to 300000\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.shutdownTimeoutMs) ||\n\t\tconfiguration.shutdownTimeoutMs < 100 ||\n\t\tconfiguration.shutdownTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon shutdownTimeoutMs must be an integer from 100 to 300000\");\n\t}\n}\n\nfunction validateDaemonCredentials(configuration: DaemonConfiguration): void {\n\tif (process.platform !== \"win32\" && process.getuid?.() === 0 && !configuration.allowRoot) {\n\t\tthrow new DaemonConfigurationError(\"Refusing to run the workspace daemon as root without allowRoot\");\n\t}\n\tif (configuration.token === undefined) return;\n\tconst tokenBytes = Buffer.byteLength(configuration.token, \"utf8\");\n\tif (\n\t\ttokenBytes < 32 ||\n\t\ttokenBytes > 1024 ||\n\t\t!/\\S/u.test(configuration.token) ||\n\t\t/[\\r\\n]/u.test(configuration.token)\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon token must contain 32 to 1024 UTF-8 bytes and no line breaks\");\n\t}\n}\n\nfunction validateDaemonOrigins(origins: readonly string[]): void {\n\tfor (const origin of origins) {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(origin);\n\t\t} catch {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t\tif ((parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") || origin !== parsed.origin) {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t}\n}\n\nfunction validateDaemonTls(configuration: DaemonConfiguration): void {\n\tif (configuration.tls && (configuration.tls.cert.byteLength === 0 || configuration.tls.key.byteLength === 0)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS certificate and private key must not be empty\");\n\t}\n}\n\nfunction validateDaemonBindExposure(configuration: DaemonConfiguration): void {\n\tif (isIP(configuration.host.replace(/^\\[|\\]$/g, \"\")) === 0) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a literal IPv4 or IPv6 address\");\n\t}\n\tif (isLoopbackBind(configuration.host)) return;\n\tif (!configuration.token)\n\t\tthrow new DaemonConfigurationError(\"A non-loopback daemon bind requires GRIP_DAEMON_TOKEN\");\n\tif (!configuration.tls && !configuration.allowInsecureTransport) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"A non-loopback daemon bind requires TLS or explicit --daemon-allow-insecure-transport acknowledgement\",\n\t\t);\n\t}\n}\n\nexport function validateDaemonNetworkPolicy(configuration: DaemonConfiguration): void {\n\tvalidateDaemonRuntimeLimits(configuration);\n\tvalidateDaemonCredentials(configuration);\n\tvalidateDaemonOrigins(configuration.allowedOrigins);\n\tvalidateDaemonTls(configuration);\n\tvalidateDaemonBindExposure(configuration);\n}\n\nexport function createDaemonAuthorization(token: string | undefined): DaemonAuthorization | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.from(token, \"utf8\");\n\treturn Object.freeze({ configuredBytes: bytes.byteLength, digest: createHash(\"sha256\").update(bytes).digest() });\n}\n\nfunction authenticateAuthorizationHeader(\n\tvalue: string | undefined,\n\tauthorization: DaemonAuthorization | undefined,\n): boolean {\n\tif (!authorization) return value === undefined;\n\tif (!value?.startsWith(\"Bearer \")) return false;\n\tconst presented = Buffer.from(value.slice(\"Bearer \".length), \"utf8\");\n\tconst digest = createHash(\"sha256\").update(presented).digest();\n\treturn presented.byteLength === authorization.configuredBytes && timingSafeEqual(digest, authorization.digest);\n}\n\nexport function validateDaemonUpgrade(\n\trequest: IncomingMessage,\n\tconfiguration: DaemonConfiguration,\n\tauthorization: DaemonAuthorization | undefined,\n): DaemonUpgradeDecision {\n\tif (request.method !== \"GET\" || request.url !== DAEMON_WEBSOCKET_PATH) {\n\t\treturn { accepted: false, status: 404, message: \"Not found\" };\n\t}\n\tconst upgrade = singleHeader(request, \"upgrade\");\n\tconst connection = singleHeader(request, \"connection\");\n\tconst version = singleHeader(request, \"sec-websocket-version\");\n\tconst key = singleHeader(request, \"sec-websocket-key\");\n\tconst protocol = singleHeader(request, \"sec-websocket-protocol\");\n\tif (\n\t\tupgrade?.toLowerCase() !== \"websocket\" ||\n\t\t!connection ||\n\t\t!hasToken(connection, \"upgrade\") ||\n\t\tversion !== \"13\" ||\n\t\t!key ||\n\t\t!isValidWebSocketKey(key) ||\n\t\tprotocol !== DAEMON_WEBSOCKET_PROTOCOL\n\t) {\n\t\treturn { accepted: false, status: 400, message: \"Invalid WebSocket upgrade\" };\n\t}\n\tconst originValues = headerValues(request, \"origin\");\n\tif (originValues.length > 1) return { accepted: false, status: 403, message: \"Origin not allowed\" };\n\tif (originValues.length === 1 && !configuration.allowedOrigins.includes(originValues[0])) {\n\t\treturn { accepted: false, status: 403, message: \"Origin not allowed\" };\n\t}\n\tconst authorizationValues = headerValues(request, \"authorization\");\n\tif (authorizationValues.length > 1 || !authenticateAuthorizationHeader(authorizationValues[0], authorization)) {\n\t\treturn { accepted: false, status: 401, message: \"Unauthorized\" };\n\t}\n\treturn { accepted: true, status: 101, message: \"Switching Protocols\" };\n}\n\nexport function rejectDaemonUpgrade(socket: Duplex, decision: DaemonUpgradeDecision): void {\n\tconst statusText =\n\t\tnew Map([\n\t\t\t[400, \"Bad Request\"],\n\t\t\t[401, \"Unauthorized\"],\n\t\t\t[403, \"Forbidden\"],\n\t\t\t[404, \"Not Found\"],\n\t\t\t[503, \"Service Unavailable\"],\n\t\t]).get(decision.status) ?? \"Rejected\";\n\tconst body = `${decision.message}\\n`;\n\tsocket.end(\n\t\t`HTTP/1.1 ${decision.status} ${statusText}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\n\\r\\n${body}`,\n\t);\n}\n\nexport function redactDaemonText(value: string, secrets: readonly (string | undefined)[]): string {\n\tlet result = value\n\t\t.replace(/(authorization\\s*:\\s*bearer\\s+)[^\\s,;]+/giu, \"$1[REDACTED]\")\n\t\t.replace(/([?&](?:token|key|secret|password|credential)=)[^&#\\s]*/giu, \"$1[REDACTED]\")\n\t\t.replace(/(\\/\\/)[^/@\\s]+:[^/@\\s]+@/gu, \"$1[REDACTED]@\");\n\tfor (const secret of secrets) if (secret) result = result.split(secret).join(\"[REDACTED]\");\n\treturn result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"security.js","sourceRoot":"","sources":["../../src/daemon/security.ts"],"names":[],"mappings":"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","sourcesContent":["import { createHash, timingSafeEqual } from \"node:crypto\";\nimport type { IncomingMessage } from \"node:http\";\nimport { isIP } from \"node:net\";\nimport type { Duplex } from \"node:stream\";\nimport { SHARED_WORKSPACE_WEBSOCKET_PROTOCOL } from \"../core/remote-workspace-protocol/shared-transport.ts\";\nimport {\n\tDAEMON_WEBSOCKET_PATH,\n\tDAEMON_WEBSOCKET_PROTOCOL,\n\ttype DaemonConfiguration,\n\tDaemonConfigurationError,\n} from \"./config.ts\";\n\nexport interface DaemonAuthorization {\n\treadonly configuredBytes: number;\n\treadonly digest: Buffer;\n}\n\nexport interface DaemonUpgradeDecision {\n\treadonly accepted: boolean;\n\treadonly status: number;\n\treadonly message: string;\n}\n\nfunction headerValues(request: IncomingMessage, name: string): string[] {\n\tconst values: string[] = [];\n\tfor (let index = 0; index < request.rawHeaders.length; index += 2) {\n\t\tif (request.rawHeaders[index]?.toLowerCase() === name) values.push(request.rawHeaders[index + 1] ?? \"\");\n\t}\n\treturn values;\n}\n\nfunction singleHeader(request: IncomingMessage, name: string): string | undefined {\n\tconst values = headerValues(request, name);\n\treturn values.length === 1 ? values[0] : undefined;\n}\n\nfunction hasToken(value: string, token: string): boolean {\n\treturn value\n\t\t.split(\",\")\n\t\t.map((entry) => entry.trim().toLowerCase())\n\t\t.includes(token);\n}\n\nfunction isValidWebSocketKey(value: string): boolean {\n\tif (!/^[A-Za-z0-9+/]{22}==$/.test(value)) return false;\n\treturn Buffer.from(value, \"base64\").byteLength === 16;\n}\n\nexport function isLoopbackBind(host: string): boolean {\n\tconst normalized = host.startsWith(\"[\") && host.endsWith(\"]\") ? host.slice(1, -1) : host;\n\tif (isIP(normalized) === 4) return normalized.split(\".\")[0] === \"127\";\n\tif (isIP(normalized) !== 6) return false;\n\tconst lower = normalized.toLowerCase();\n\treturn lower === \"::1\" || /^::ffff:127(?:\\.[0-9]{1,3}){3}$/.test(lower);\n}\nfunction validateDaemonRuntimeLimits(configuration: DaemonConfiguration): void {\n\tif (!Number.isSafeInteger(configuration.port) || configuration.port < 0 || configuration.port > 65535) {\n\t\tthrow new DaemonConfigurationError(\"Daemon server port must be an integer from 0 to 65535\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxConnections) || configuration.maxConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxConnections must be a positive integer\");\n\t}\n\tif (!Number.isSafeInteger(configuration.maxPendingConnections) || configuration.maxPendingConnections < 1) {\n\t\tthrow new DaemonConfigurationError(\"Daemon maxPendingConnections must be a positive integer\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.handshakeTimeoutMs) ||\n\t\tconfiguration.handshakeTimeoutMs < 100 ||\n\t\tconfiguration.handshakeTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon handshakeTimeoutMs must be an integer from 100 to 300000\");\n\t}\n\tif (\n\t\t!Number.isSafeInteger(configuration.shutdownTimeoutMs) ||\n\t\tconfiguration.shutdownTimeoutMs < 100 ||\n\t\tconfiguration.shutdownTimeoutMs > 5 * 60_000\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon shutdownTimeoutMs must be an integer from 100 to 300000\");\n\t}\n}\n\nfunction validateDaemonCredentials(configuration: DaemonConfiguration): void {\n\tif (process.platform !== \"win32\" && process.getuid?.() === 0 && !configuration.allowRoot) {\n\t\tthrow new DaemonConfigurationError(\"Refusing to run the workspace daemon as root without allowRoot\");\n\t}\n\tif (configuration.token === undefined) return;\n\tconst tokenBytes = Buffer.byteLength(configuration.token, \"utf8\");\n\tif (\n\t\ttokenBytes < 32 ||\n\t\ttokenBytes > 1024 ||\n\t\t!/\\S/u.test(configuration.token) ||\n\t\t/[\\r\\n]/u.test(configuration.token)\n\t) {\n\t\tthrow new DaemonConfigurationError(\"Daemon token must contain 32 to 1024 UTF-8 bytes and no line breaks\");\n\t}\n}\n\nfunction validateDaemonOrigins(origins: readonly string[]): void {\n\tfor (const origin of origins) {\n\t\tlet parsed: URL;\n\t\ttry {\n\t\t\tparsed = new URL(origin);\n\t\t} catch {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t\tif ((parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") || origin !== parsed.origin) {\n\t\t\tthrow new DaemonConfigurationError(`Invalid daemon Origin: ${origin}`);\n\t\t}\n\t}\n}\n\nfunction validateDaemonTls(configuration: DaemonConfiguration): void {\n\tif (configuration.tls && (configuration.tls.cert.byteLength === 0 || configuration.tls.key.byteLength === 0)) {\n\t\tthrow new DaemonConfigurationError(\"Daemon TLS certificate and private key must not be empty\");\n\t}\n}\n\nfunction validateDaemonBindExposure(configuration: DaemonConfiguration): void {\n\tif (isIP(configuration.host.replace(/^\\[|\\]$/g, \"\")) === 0) {\n\t\tthrow new DaemonConfigurationError(\"Daemon host must be a literal IPv4 or IPv6 address\");\n\t}\n\tif (isLoopbackBind(configuration.host)) return;\n\tif (!configuration.token)\n\t\tthrow new DaemonConfigurationError(\"A non-loopback daemon bind requires GRIP_DAEMON_TOKEN\");\n\tif (!configuration.tls && !configuration.allowInsecureTransport) {\n\t\tthrow new DaemonConfigurationError(\n\t\t\t\"A non-loopback daemon bind requires TLS or explicit --daemon-allow-insecure-transport acknowledgement\",\n\t\t);\n\t}\n}\n\nexport function validateDaemonNetworkPolicy(configuration: DaemonConfiguration): void {\n\tvalidateDaemonRuntimeLimits(configuration);\n\tvalidateDaemonCredentials(configuration);\n\tvalidateDaemonOrigins(configuration.allowedOrigins);\n\tvalidateDaemonTls(configuration);\n\tvalidateDaemonBindExposure(configuration);\n}\n\nexport function createDaemonAuthorization(token: string | undefined): DaemonAuthorization | undefined {\n\tif (token === undefined) return undefined;\n\tconst bytes = Buffer.from(token, \"utf8\");\n\treturn Object.freeze({ configuredBytes: bytes.byteLength, digest: createHash(\"sha256\").update(bytes).digest() });\n}\n\nfunction authenticateAuthorizationHeader(\n\tvalue: string | undefined,\n\tauthorization: DaemonAuthorization | undefined,\n): boolean {\n\tif (!authorization) return value === undefined;\n\tif (!value?.startsWith(\"Bearer \")) return false;\n\tconst presented = Buffer.from(value.slice(\"Bearer \".length), \"utf8\");\n\tconst digest = createHash(\"sha256\").update(presented).digest();\n\treturn presented.byteLength === authorization.configuredBytes && timingSafeEqual(digest, authorization.digest);\n}\n\nexport function validateDaemonUpgrade(\n\trequest: IncomingMessage,\n\tconfiguration: DaemonConfiguration,\n\tauthorization: DaemonAuthorization | undefined,\n): DaemonUpgradeDecision {\n\tif (request.method !== \"GET\" || request.url !== DAEMON_WEBSOCKET_PATH) {\n\t\treturn { accepted: false, status: 404, message: \"Not found\" };\n\t}\n\tconst upgrade = singleHeader(request, \"upgrade\");\n\tconst connection = singleHeader(request, \"connection\");\n\tconst version = singleHeader(request, \"sec-websocket-version\");\n\tconst key = singleHeader(request, \"sec-websocket-key\");\n\tconst protocol = singleHeader(request, \"sec-websocket-protocol\");\n\tif (\n\t\tupgrade?.toLowerCase() !== \"websocket\" ||\n\t\t!connection ||\n\t\t!hasToken(connection, \"upgrade\") ||\n\t\tversion !== \"13\" ||\n\t\t!key ||\n\t\t!isValidWebSocketKey(key) ||\n\t\t(protocol !== DAEMON_WEBSOCKET_PROTOCOL &&\n\t\t\t(protocol !== SHARED_WORKSPACE_WEBSOCKET_PROTOCOL || !configuration.sharedParentRoot))\n\t) {\n\t\treturn { accepted: false, status: 400, message: \"Invalid WebSocket upgrade\" };\n\t}\n\tconst originValues = headerValues(request, \"origin\");\n\tif (originValues.length > 1) return { accepted: false, status: 403, message: \"Origin not allowed\" };\n\tif (originValues.length === 1 && !configuration.allowedOrigins.includes(originValues[0])) {\n\t\treturn { accepted: false, status: 403, message: \"Origin not allowed\" };\n\t}\n\tconst authorizationValues = headerValues(request, \"authorization\");\n\tif (authorizationValues.length > 1 || !authenticateAuthorizationHeader(authorizationValues[0], authorization)) {\n\t\treturn { accepted: false, status: 401, message: \"Unauthorized\" };\n\t}\n\treturn { accepted: true, status: 101, message: \"Switching Protocols\" };\n}\n\nexport function rejectDaemonUpgrade(socket: Duplex, decision: DaemonUpgradeDecision): void {\n\tconst statusText =\n\t\tnew Map([\n\t\t\t[400, \"Bad Request\"],\n\t\t\t[401, \"Unauthorized\"],\n\t\t\t[403, \"Forbidden\"],\n\t\t\t[404, \"Not Found\"],\n\t\t\t[503, \"Service Unavailable\"],\n\t\t]).get(decision.status) ?? \"Rejected\";\n\tconst body = `${decision.message}\\n`;\n\tsocket.end(\n\t\t`HTTP/1.1 ${decision.status} ${statusText}\\r\\nConnection: close\\r\\nContent-Type: text/plain\\r\\nContent-Length: ${Buffer.byteLength(body)}\\r\\n\\r\\n${body}`,\n\t);\n}\n\nexport function redactDaemonText(value: string, secrets: readonly (string | undefined)[]): string {\n\tlet result = value\n\t\t.replace(/(authorization\\s*:\\s*bearer\\s+)[^\\s,;]+/giu, \"$1[REDACTED]\")\n\t\t.replace(/([?&](?:token|key|secret|password|credential)=)[^&#\\s]*/giu, \"$1[REDACTED]\")\n\t\t.replace(/(\\/\\/)[^/@\\s]+:[^/@\\s]+@/gu, \"$1[REDACTED]@\");\n\tfor (const secret of secrets) if (secret) result = result.split(secret).join(\"[REDACTED]\");\n\treturn result;\n}\n"]}
|