@neuralnomads/codenomad-dev 0.20.0-dev-20260909-855d2ee7 → 0.20.0-dev-20260911-ea37f97b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api-types.js.map +1 -1
- package/dist/index.js +42 -31
- package/dist/index.js.map +1 -1
- package/dist/index.test.js +9 -1
- package/dist/index.test.js.map +1 -1
- package/dist/opencode/pruning-installation.js +65 -0
- package/dist/opencode/pruning-installation.js.map +1 -0
- package/dist/opencode/pruning-installation.test.js +101 -0
- package/dist/opencode/pruning-installation.test.js.map +1 -0
- package/dist/opencode/pruning-lifecycle.js +66 -0
- package/dist/opencode/pruning-lifecycle.js.map +1 -0
- package/dist/opencode/session-pruning/claim-fence.js +33 -0
- package/dist/opencode/session-pruning/claim-fence.js.map +1 -0
- package/dist/opencode/session-pruning/claim-fence.test.js +104 -0
- package/dist/opencode/session-pruning/claim-fence.test.js.map +1 -0
- package/dist/opencode/session-pruning/contract.js +40 -0
- package/dist/opencode/session-pruning/contract.js.map +1 -0
- package/dist/opencode/session-pruning/database-path.js +17 -0
- package/dist/opencode/session-pruning/database-path.js.map +1 -0
- package/dist/opencode/session-pruning/database-path.test.js +22 -0
- package/dist/opencode/session-pruning/database-path.test.js.map +1 -0
- package/dist/opencode/session-pruning/desktop-plugin.js +14 -0
- package/dist/opencode/session-pruning/desktop-plugin.js.map +1 -0
- package/dist/opencode/session-pruning/index.js +2 -0
- package/dist/opencode/session-pruning/index.js.map +1 -0
- package/dist/opencode/session-pruning/isolated-store.js +18 -0
- package/dist/opencode/session-pruning/isolated-store.js.map +1 -0
- package/dist/opencode/session-pruning/planner.js +36 -0
- package/dist/opencode/session-pruning/planner.js.map +1 -0
- package/dist/opencode/session-pruning/plugin.js +45 -0
- package/dist/opencode/session-pruning/plugin.js.map +1 -0
- package/dist/opencode/session-pruning/presence.js +42 -0
- package/dist/opencode/session-pruning/presence.js.map +1 -0
- package/dist/opencode/session-pruning/preview-store.js +21 -0
- package/dist/opencode/session-pruning/preview-store.js.map +1 -0
- package/dist/opencode/session-pruning/pruning.test.js +172 -0
- package/dist/opencode/session-pruning/pruning.test.js.map +1 -0
- package/dist/opencode/session-pruning/revision.js +18 -0
- package/dist/opencode/session-pruning/revision.js.map +1 -0
- package/dist/opencode/session-pruning/service.js +56 -0
- package/dist/opencode/session-pruning/service.js.map +1 -0
- package/dist/opencode/session-pruning/service.test.js +74 -0
- package/dist/opencode/session-pruning/service.test.js.map +1 -0
- package/dist/opencode/session-pruning/storage-path.js +6 -0
- package/dist/opencode/session-pruning/storage-path.js.map +1 -0
- package/dist/opencode/session-pruning/transaction.js +53 -0
- package/dist/opencode/session-pruning/transaction.js.map +1 -0
- package/dist/opencode/session-pruning/tui-reload.js +84 -0
- package/dist/opencode/session-pruning/tui-reload.js.map +1 -0
- package/dist/opencode/session-pruning/tui-reload.native.js +269 -0
- package/dist/opencode/session-pruning/tui-reload.native.js.map +1 -0
- package/dist/opencode/session-pruning/tui-reload.test.js +71 -0
- package/dist/opencode/session-pruning/tui-reload.test.js.map +1 -0
- package/dist/opencode/session-pruning/tui.js +12 -0
- package/dist/opencode/session-pruning/tui.js.map +1 -0
- package/dist/plugins/session-pruning/plugin.mjs +26559 -0
- package/dist/server/__tests__/instance-proxy.test.js +10 -5
- package/dist/server/__tests__/instance-proxy.test.js.map +1 -1
- package/dist/server/__tests__/listener-base-url.test.js +26 -1
- package/dist/server/__tests__/listener-base-url.test.js.map +1 -1
- package/dist/server/__tests__/network-addresses.test.js +40 -0
- package/dist/server/__tests__/network-addresses.test.js.map +1 -1
- package/dist/server/__tests__/network-host.test.js +34 -0
- package/dist/server/__tests__/network-host.test.js.map +1 -0
- package/dist/server/__tests__/tls-ipv6.test.js +54 -0
- package/dist/server/__tests__/tls-ipv6.test.js.map +1 -0
- package/dist/server/http-server.js +8 -6
- package/dist/server/http-server.js.map +1 -1
- package/dist/server/listener-base-url.js +12 -3
- package/dist/server/listener-base-url.js.map +1 -1
- package/dist/server/network-addresses.js +20 -42
- package/dist/server/network-addresses.js.map +1 -1
- package/dist/server/network-host.js +62 -0
- package/dist/server/network-host.js.map +1 -0
- package/dist/server/routes/meta.js +2 -4
- package/dist/server/routes/meta.js.map +1 -1
- package/dist/server/routes/session-pruning.js +45 -0
- package/dist/server/routes/session-pruning.js.map +1 -0
- package/dist/server/routes/session-pruning.test.js +98 -0
- package/dist/server/routes/session-pruning.test.js.map +1 -0
- package/dist/server/tls.js +52 -32
- package/dist/server/tls.js.map +1 -1
- package/dist/workspaces/manager.js +1 -0
- package/dist/workspaces/manager.js.map +1 -1
- package/package.json +6 -3
- package/public/assets/App-0-FcHzPS.js +44 -0
- package/public/assets/App-0-FcHzPS.js.map +1 -0
- package/public/assets/FilesTab-CRpGDs1B.js +3 -0
- package/public/assets/{FilesTab-z7UrjcAQ.js.map → FilesTab-CRpGDs1B.js.map} +1 -1
- package/public/assets/{GitChangesTab-C04bYKhs.js → GitChangesTab-DxFebTiq.js} +3 -3
- package/public/assets/{GitChangesTab-C04bYKhs.js.map → GitChangesTab-DxFebTiq.js.map} +1 -1
- package/public/assets/{SplitFilePanel-B29MqCuU.js → SplitFilePanel-DgTMrrO-.js} +2 -2
- package/public/assets/{SplitFilePanel-B29MqCuU.js.map → SplitFilePanel-DgTMrrO-.js.map} +1 -1
- package/public/assets/{StatusTab-wUKfwnFf.js → StatusTab-LKUA9cbH.js} +2 -2
- package/public/assets/{StatusTab-wUKfwnFf.js.map → StatusTab-LKUA9cbH.js.map} +1 -1
- package/public/assets/{align-justify-glU678D9.js → align-justify-v_yPXSys.js} +2 -2
- package/public/assets/{align-justify-glU678D9.js.map → align-justify-v_yPXSys.js.map} +1 -1
- package/public/assets/{bundle-full-BrsCElYv.js → bundle-full-HzL_Jtqk.js} +2 -2
- package/public/assets/{bundle-full-BrsCElYv.js.map → bundle-full-HzL_Jtqk.js.map} +1 -1
- package/public/assets/{client-state-j9Iu7F-E.js → client-state-CF-PJ0GG.js} +2 -2
- package/public/assets/{client-state-j9Iu7F-E.js.map → client-state-CF-PJ0GG.js.map} +1 -1
- package/public/assets/{diff-viewer-B0VS1voD.js → diff-viewer-BFDQBDVy.js} +2 -2
- package/public/assets/{diff-viewer-B0VS1voD.js.map → diff-viewer-BFDQBDVy.js.map} +1 -1
- package/public/assets/index-8iPlm2r0.js +2 -0
- package/public/assets/index-8iPlm2r0.js.map +1 -0
- package/public/assets/index-B185mC7m.js +2 -0
- package/public/assets/index-B185mC7m.js.map +1 -0
- package/public/assets/index-Bbj1xF8i.js +2 -0
- package/public/assets/index-Bbj1xF8i.js.map +1 -0
- package/public/assets/index-BsmFN3k1.js +2 -0
- package/public/assets/index-BsmFN3k1.js.map +1 -0
- package/public/assets/index-Bz_OQJYA.css +1 -0
- package/public/assets/index-C52CfqK3.js +2 -0
- package/public/assets/index-C52CfqK3.js.map +1 -0
- package/public/assets/index-CHYmKl10.js +2 -0
- package/public/assets/index-CHYmKl10.js.map +1 -0
- package/public/assets/index-CTKYD_VJ.js +2 -0
- package/public/assets/index-CTKYD_VJ.js.map +1 -0
- package/public/assets/index-D2UHSOG5.js +2 -0
- package/public/assets/{index-ClaHfu-1.js.map → index-D2UHSOG5.js.map} +1 -1
- package/public/assets/index-DDoB_Z36.js +2 -0
- package/public/assets/index-DDoB_Z36.js.map +1 -0
- package/public/assets/{index-BzAEHHap.js → index-DcwAtZw-.js} +3 -3
- package/public/assets/index-DcwAtZw-.js.map +1 -0
- package/public/assets/index-DxUD3RYr.js +2 -0
- package/public/assets/index-DxUD3RYr.js.map +1 -0
- package/public/assets/{index-B6bX3AMT.js → index-RK9gf0uk.js} +2 -2
- package/public/assets/{index-B6bX3AMT.js.map → index-RK9gf0uk.js.map} +1 -1
- package/public/assets/{loading-CYiiwb08.js → loading-BSB82snY.js} +2 -2
- package/public/assets/{loading-CYiiwb08.js.map → loading-BSB82snY.js.map} +1 -1
- package/public/assets/main-CZztwrfv.js +48 -0
- package/public/assets/main-CZztwrfv.js.map +1 -0
- package/public/assets/{markdown-DWvSdCZz.js → markdown-DtidoL2Z.js} +4 -4
- package/public/assets/{markdown-DWvSdCZz.js.map → markdown-DtidoL2Z.js.map} +1 -1
- package/public/assets/{monaco-viewer-Cc9bWg7I.js → monaco-viewer-CpxDfB-6.js} +2 -2
- package/public/assets/monaco-viewer-CpxDfB-6.js.map +1 -0
- package/public/assets/{startup-state-settings-card-2KHpBL8U.js → startup-state-settings-card-BY741Rt0.js} +2 -2
- package/public/assets/{startup-state-settings-card-2KHpBL8U.js.map → startup-state-settings-card-BY741Rt0.js.map} +1 -1
- package/public/assets/{terminal-square-lzHuj_of.js → terminal-square-BdC_jwm3.js} +2 -2
- package/public/assets/{terminal-square-lzHuj_of.js.map → terminal-square-BdC_jwm3.js.map} +1 -1
- package/public/assets/{tool-call-DjkszeT4.js → tool-call-DbtCsJ4x.js} +10 -10
- package/public/assets/{tool-call-DjkszeT4.js.map → tool-call-DbtCsJ4x.js.map} +1 -1
- package/public/assets/{unified-picker-5JmIM6a0.js → unified-picker-C0eo1chJ.js} +2 -2
- package/public/assets/{unified-picker-5JmIM6a0.js.map → unified-picker-C0eo1chJ.js.map} +1 -1
- package/public/assets/{wrap-text-N0Cav2Vp.js → wrap-text-BuMtdvMx.js} +2 -2
- package/public/assets/{wrap-text-N0Cav2Vp.js.map → wrap-text-BuMtdvMx.js.map} +1 -1
- package/public/index.html +4 -4
- package/public/loading.html +4 -4
- package/public/sw.js +1 -1
- package/public/sw.js.map +1 -1
- package/public/assets/App-Nlt9zV-v.js +0 -43
- package/public/assets/App-Nlt9zV-v.js.map +0 -1
- package/public/assets/FilesTab-z7UrjcAQ.js +0 -3
- package/public/assets/index-BIRLnDWY.js +0 -2
- package/public/assets/index-BIRLnDWY.js.map +0 -1
- package/public/assets/index-BuScpzpP.js +0 -2
- package/public/assets/index-BuScpzpP.js.map +0 -1
- package/public/assets/index-BzAEHHap.js.map +0 -1
- package/public/assets/index-C-DZt-oz.css +0 -1
- package/public/assets/index-C9RXVQl9.js +0 -2
- package/public/assets/index-C9RXVQl9.js.map +0 -1
- package/public/assets/index-ClaHfu-1.js +0 -2
- package/public/assets/index-DBx5htn3.js +0 -2
- package/public/assets/index-DBx5htn3.js.map +0 -1
- package/public/assets/index-DHHMf3u1.js +0 -2
- package/public/assets/index-DHHMf3u1.js.map +0 -1
- package/public/assets/index-DN4Kstsd.js +0 -2
- package/public/assets/index-DN4Kstsd.js.map +0 -1
- package/public/assets/index-DbMyNDyo.js +0 -2
- package/public/assets/index-DbMyNDyo.js.map +0 -1
- package/public/assets/index-rcsmuqww.js +0 -2
- package/public/assets/index-rcsmuqww.js.map +0 -1
- package/public/assets/index-uz2QFRlk.js +0 -2
- package/public/assets/index-uz2QFRlk.js.map +0 -1
- package/public/assets/main-OmXs7qtG.js +0 -48
- package/public/assets/main-OmXs7qtG.js.map +0 -1
- package/public/assets/monaco-viewer-Cc9bWg7I.js.map +0 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import { formatHostForUrl, isLoopbackHost, isWildcardHost, normalizeNetworkHost } from "../network-host";
|
|
4
|
+
describe("network host helpers", () => {
|
|
5
|
+
it("recognizes IPv4 and IPv6 wildcard forms", () => {
|
|
6
|
+
assert.equal(isWildcardHost("0.0.0.0"), true);
|
|
7
|
+
assert.equal(isWildcardHost("::"), true);
|
|
8
|
+
assert.equal(isWildcardHost("0:0:0:0:0:0:0:0"), true);
|
|
9
|
+
assert.equal(isWildcardHost("::ffff:0:0"), true);
|
|
10
|
+
assert.equal(isWildcardHost("::1"), false);
|
|
11
|
+
});
|
|
12
|
+
it("recognizes concrete IPv4 and IPv6 loopback forms", () => {
|
|
13
|
+
assert.equal(isLoopbackHost("localhost"), true);
|
|
14
|
+
assert.equal(isLoopbackHost("127.0.0.2"), true);
|
|
15
|
+
assert.equal(isLoopbackHost("::1"), true);
|
|
16
|
+
assert.equal(isLoopbackHost("::0001"), true);
|
|
17
|
+
assert.equal(isLoopbackHost("0:0:0:0:0:0:0:1"), true);
|
|
18
|
+
assert.equal(isLoopbackHost("0:0:0:0:0:0:0:0001"), true);
|
|
19
|
+
assert.equal(isLoopbackHost("::ffff:7f00:1"), true);
|
|
20
|
+
assert.equal(isLoopbackHost("2001:db8::1"), false);
|
|
21
|
+
});
|
|
22
|
+
it("brackets IPv6 literals for URLs", () => {
|
|
23
|
+
assert.equal(formatHostForUrl("192.168.1.20"), "192.168.1.20");
|
|
24
|
+
assert.equal(formatHostForUrl("::1"), "[::1]");
|
|
25
|
+
assert.equal(formatHostForUrl("[2001:db8::20]"), "[2001:db8::20]");
|
|
26
|
+
});
|
|
27
|
+
it("normalizes IPv4-mapped addresses and internationalized DNS names", () => {
|
|
28
|
+
assert.equal(normalizeNetworkHost("::ffff:0:0"), "0.0.0.0");
|
|
29
|
+
assert.equal(normalizeNetworkHost("::ffff:7f00:1"), "127.0.0.1");
|
|
30
|
+
assert.equal(normalizeNetworkHost("::ffff:192.168.1.20"), "192.168.1.20");
|
|
31
|
+
assert.equal(normalizeNetworkHost("münchen.local"), "xn--mnchen-3ya.local");
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
//# sourceMappingURL=network-host.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"network-host.test.js","sourceRoot":"","sources":["../../../src/server/__tests__/network-host.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAA;AACvC,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,WAAW,CAAA;AAExC,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,cAAc,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAA;AAExG,QAAQ,CAAC,sBAAsB,EAAE,GAAG,EAAE;IACpC,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,SAAS,CAAC,EAAE,IAAI,CAAC,CAAA;QAC7C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,CAAC,EAAE,IAAI,CAAC,CAAA;QACxC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,YAAY,CAAC,EAAE,IAAI,CAAC,CAAA;QAChD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,CAAA;IAC5C,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kDAAkD,EAAE,GAAG,EAAE;QAC1D,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,WAAW,CAAC,EAAE,IAAI,CAAC,CAAA;QAC/C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAA;QACzC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,IAAI,CAAC,CAAA;QAC5C,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,iBAAiB,CAAC,EAAE,IAAI,CAAC,CAAA;QACrD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,oBAAoB,CAAC,EAAE,IAAI,CAAC,CAAA;QACxD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,eAAe,CAAC,EAAE,IAAI,CAAC,CAAA;QACnD,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,aAAa,CAAC,EAAE,KAAK,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC,CAAA;QAC9D,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,CAAA;QAC9C,MAAM,CAAC,KAAK,CAAC,gBAAgB,CAAC,gBAAgB,CAAC,EAAE,gBAAgB,CAAC,CAAA;IACpE,CAAC,CAAC,CAAA;IAEF,EAAE,CAAC,kEAAkE,EAAE,GAAG,EAAE;QAC1E,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,YAAY,CAAC,EAAE,SAAS,CAAC,CAAA;QAC3D,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,CAAA;QAChE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,qBAAqB,CAAC,EAAE,cAAc,CAAC,CAAA;QACzE,MAAM,CAAC,KAAK,CAAC,oBAAoB,CAAC,eAAe,CAAC,EAAE,sBAAsB,CAAC,CAAA;IAC7E,CAAC,CAAC,CAAA;AACJ,CAAC,CAAC,CAAA","sourcesContent":["import assert from \"node:assert/strict\"\nimport { describe, it } from \"node:test\"\n\nimport { formatHostForUrl, isLoopbackHost, isWildcardHost, normalizeNetworkHost } from \"../network-host\"\n\ndescribe(\"network host helpers\", () => {\n it(\"recognizes IPv4 and IPv6 wildcard forms\", () => {\n assert.equal(isWildcardHost(\"0.0.0.0\"), true)\n assert.equal(isWildcardHost(\"::\"), true)\n assert.equal(isWildcardHost(\"0:0:0:0:0:0:0:0\"), true)\n assert.equal(isWildcardHost(\"::ffff:0:0\"), true)\n assert.equal(isWildcardHost(\"::1\"), false)\n })\n\n it(\"recognizes concrete IPv4 and IPv6 loopback forms\", () => {\n assert.equal(isLoopbackHost(\"localhost\"), true)\n assert.equal(isLoopbackHost(\"127.0.0.2\"), true)\n assert.equal(isLoopbackHost(\"::1\"), true)\n assert.equal(isLoopbackHost(\"::0001\"), true)\n assert.equal(isLoopbackHost(\"0:0:0:0:0:0:0:1\"), true)\n assert.equal(isLoopbackHost(\"0:0:0:0:0:0:0:0001\"), true)\n assert.equal(isLoopbackHost(\"::ffff:7f00:1\"), true)\n assert.equal(isLoopbackHost(\"2001:db8::1\"), false)\n })\n\n it(\"brackets IPv6 literals for URLs\", () => {\n assert.equal(formatHostForUrl(\"192.168.1.20\"), \"192.168.1.20\")\n assert.equal(formatHostForUrl(\"::1\"), \"[::1]\")\n assert.equal(formatHostForUrl(\"[2001:db8::20]\"), \"[2001:db8::20]\")\n })\n\n it(\"normalizes IPv4-mapped addresses and internationalized DNS names\", () => {\n assert.equal(normalizeNetworkHost(\"::ffff:0:0\"), \"0.0.0.0\")\n assert.equal(normalizeNetworkHost(\"::ffff:7f00:1\"), \"127.0.0.1\")\n assert.equal(normalizeNetworkHost(\"::ffff:192.168.1.20\"), \"192.168.1.20\")\n assert.equal(normalizeNetworkHost(\"münchen.local\"), \"xn--mnchen-3ya.local\")\n })\n})\n"]}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import crypto from "node:crypto";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
import { describe, it } from "node:test";
|
|
7
|
+
import { resolveHttpsOptions } from "../tls";
|
|
8
|
+
const logger = { info() { }, warn() { }, error() { }, child() { return logger; } };
|
|
9
|
+
describe("generated IPv6 certificates", () => {
|
|
10
|
+
it("includes a concrete IPv6 address SAN", () => withTempConfig((configDir) => {
|
|
11
|
+
const resolved = resolveHttpsOptions({ enabled: true, configDir, host: "2001:db8::20", logger });
|
|
12
|
+
assert.ok(resolved);
|
|
13
|
+
const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert);
|
|
14
|
+
assert.match(certificate.subjectAltName ?? "", /IP Address:2001:DB8:0:0:0:0:0:20/i);
|
|
15
|
+
}));
|
|
16
|
+
it("includes IPv6 loopback for a fresh wildcard certificate", () => withTempConfig((configDir) => {
|
|
17
|
+
const resolved = resolveHttpsOptions({ enabled: true, configDir, host: "::", logger });
|
|
18
|
+
assert.ok(resolved);
|
|
19
|
+
const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert);
|
|
20
|
+
assert.equal(certificate.checkIP("::1"), "::1");
|
|
21
|
+
}));
|
|
22
|
+
it("rotates a reused certificate when required host and configured SANs change", () => withTempConfig((configDir) => {
|
|
23
|
+
const initial = resolveHttpsOptions({ enabled: true, configDir, host: "127.0.0.1", logger });
|
|
24
|
+
assert.ok(initial);
|
|
25
|
+
const initialCertificate = new crypto.X509Certificate(initial.httpsOptions.cert);
|
|
26
|
+
const rotated = resolveHttpsOptions({ enabled: true, configDir, host: "::", tlsSANs: "diagnostics.local", logger });
|
|
27
|
+
assert.ok(rotated);
|
|
28
|
+
const rotatedCertificate = new crypto.X509Certificate(rotated.httpsOptions.cert);
|
|
29
|
+
assert.notEqual(rotatedCertificate.fingerprint256, initialCertificate.fingerprint256);
|
|
30
|
+
assert.equal(rotatedCertificate.checkIP("::1"), "::1");
|
|
31
|
+
assert.equal(rotatedCertificate.checkHost("diagnostics.local"), "diagnostics.local");
|
|
32
|
+
}));
|
|
33
|
+
it("reuses certificates for normalized IDN and scoped IPv6 SAN values", () => withTempConfig((configDir) => {
|
|
34
|
+
const first = resolveHttpsOptions({ enabled: true, configDir, host: "münchen.local", tlsSANs: "fe80::1%12", logger });
|
|
35
|
+
assert.ok(first);
|
|
36
|
+
const firstCertificate = new crypto.X509Certificate(first.httpsOptions.cert);
|
|
37
|
+
const reused = resolveHttpsOptions({ enabled: true, configDir, host: "münchen.local", tlsSANs: "fe80::1%12", logger });
|
|
38
|
+
assert.ok(reused);
|
|
39
|
+
const reusedCertificate = new crypto.X509Certificate(reused.httpsOptions.cert);
|
|
40
|
+
assert.equal(reusedCertificate.fingerprint256, firstCertificate.fingerprint256);
|
|
41
|
+
assert.equal(reusedCertificate.checkHost("xn--mnchen-3ya.local"), "xn--mnchen-3ya.local");
|
|
42
|
+
assert.equal(reusedCertificate.checkIP("fe80::1"), "fe80::1");
|
|
43
|
+
}));
|
|
44
|
+
});
|
|
45
|
+
function withTempConfig(callback) {
|
|
46
|
+
const configDir = fs.mkdtempSync(path.join(os.tmpdir(), "codenomad-tls-ipv6-"));
|
|
47
|
+
try {
|
|
48
|
+
callback(configDir);
|
|
49
|
+
}
|
|
50
|
+
finally {
|
|
51
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=tls-ipv6.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tls-ipv6.test.js","sourceRoot":"","sources":["../../../src/server/__tests__/tls-ipv6.test.ts"],"names":[],"mappings":"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","sourcesContent":["import assert from \"node:assert/strict\"\nimport crypto from \"node:crypto\"\nimport fs from \"node:fs\"\nimport os from \"node:os\"\nimport path from \"node:path\"\nimport { describe, it } from \"node:test\"\n\nimport type { Logger } from \"../../logger\"\nimport { resolveHttpsOptions } from \"../tls\"\n\nconst logger = { info() {}, warn() {}, error() {}, child() { return logger } } as unknown as Logger\n\ndescribe(\"generated IPv6 certificates\", () => {\n it(\"includes a concrete IPv6 address SAN\", () => withTempConfig((configDir) => {\n const resolved = resolveHttpsOptions({ enabled: true, configDir, host: \"2001:db8::20\", logger })\n assert.ok(resolved)\n const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert)\n assert.match(certificate.subjectAltName ?? \"\", /IP Address:2001:DB8:0:0:0:0:0:20/i)\n }))\n\n it(\"includes IPv6 loopback for a fresh wildcard certificate\", () => withTempConfig((configDir) => {\n const resolved = resolveHttpsOptions({ enabled: true, configDir, host: \"::\", logger })\n assert.ok(resolved)\n const certificate = new crypto.X509Certificate(resolved.httpsOptions.cert)\n assert.equal(certificate.checkIP(\"::1\"), \"::1\")\n }))\n\n it(\"rotates a reused certificate when required host and configured SANs change\", () => withTempConfig((configDir) => {\n const initial = resolveHttpsOptions({ enabled: true, configDir, host: \"127.0.0.1\", logger })\n assert.ok(initial)\n const initialCertificate = new crypto.X509Certificate(initial.httpsOptions.cert)\n\n const rotated = resolveHttpsOptions({ enabled: true, configDir, host: \"::\", tlsSANs: \"diagnostics.local\", logger })\n assert.ok(rotated)\n const rotatedCertificate = new crypto.X509Certificate(rotated.httpsOptions.cert)\n\n assert.notEqual(rotatedCertificate.fingerprint256, initialCertificate.fingerprint256)\n assert.equal(rotatedCertificate.checkIP(\"::1\"), \"::1\")\n assert.equal(rotatedCertificate.checkHost(\"diagnostics.local\"), \"diagnostics.local\")\n }))\n\n it(\"reuses certificates for normalized IDN and scoped IPv6 SAN values\", () => withTempConfig((configDir) => {\n const first = resolveHttpsOptions({ enabled: true, configDir, host: \"münchen.local\", tlsSANs: \"fe80::1%12\", logger })\n assert.ok(first)\n const firstCertificate = new crypto.X509Certificate(first.httpsOptions.cert)\n\n const reused = resolveHttpsOptions({ enabled: true, configDir, host: \"münchen.local\", tlsSANs: \"fe80::1%12\", logger })\n assert.ok(reused)\n const reusedCertificate = new crypto.X509Certificate(reused.httpsOptions.cert)\n\n assert.equal(reusedCertificate.fingerprint256, firstCertificate.fingerprint256)\n assert.equal(reusedCertificate.checkHost(\"xn--mnchen-3ya.local\"), \"xn--mnchen-3ya.local\")\n assert.equal(reusedCertificate.checkIP(\"fe80::1\"), \"fe80::1\")\n }))\n})\n\nfunction withTempConfig(callback: (configDir: string) => void) {\n const configDir = fs.mkdtempSync(path.join(os.tmpdir(), \"codenomad-tls-ipv6-\"))\n try {\n callback(configDir)\n } finally {\n fs.rmSync(configDir, { recursive: true, force: true })\n }\n}\n"]}
|
|
@@ -3,7 +3,7 @@ import cors from "@fastify/cors";
|
|
|
3
3
|
import fastifyStatic from "@fastify/static";
|
|
4
4
|
import replyFrom from "@fastify/reply-from";
|
|
5
5
|
import fs from "fs";
|
|
6
|
-
import { connect as connectTcp } from "net";
|
|
6
|
+
import { connect as connectTcp, isIP } from "net";
|
|
7
7
|
import path from "path";
|
|
8
8
|
import { connect as connectTls } from "tls";
|
|
9
9
|
import { fetch } from "undici";
|
|
@@ -16,6 +16,7 @@ import { registerMetaRoutes } from "./routes/meta";
|
|
|
16
16
|
import { registerEventRoutes } from "./routes/events";
|
|
17
17
|
import { registerStorageRoutes } from "./routes/storage";
|
|
18
18
|
import { registerYoloRoutes } from "./routes/yolo";
|
|
19
|
+
import { registerSessionPruningRoutes } from "./routes/session-pruning";
|
|
19
20
|
import { registerWorktreeRoutes } from "./routes/worktrees";
|
|
20
21
|
import { registerSpeechRoutes } from "./routes/speech";
|
|
21
22
|
import { registerOpenCodeUpdateRoutes } from "./routes/opencode-update";
|
|
@@ -31,6 +32,7 @@ import { createOpenCodeUpdateService } from "../opencode-update/service";
|
|
|
31
32
|
import { WorktreeDeletionFence } from "../workspaces/worktree-session-evacuation";
|
|
32
33
|
import { isAutomationPluginRequest, registerAutomationPluginRoute } from "./routes/automation-plugin";
|
|
33
34
|
import { DeveloperCdp } from "../developer-cdp";
|
|
35
|
+
import { formatHostForUrl, isLoopbackHost, isWildcardHost, stripHostBrackets } from "./network-host";
|
|
34
36
|
export function shouldRetryPreferredPort(error, autoPortRequested, platform = process.platform) {
|
|
35
37
|
if (!autoPortRequested)
|
|
36
38
|
return false;
|
|
@@ -86,7 +88,6 @@ export function createHttpServer(deps) {
|
|
|
86
88
|
done();
|
|
87
89
|
});
|
|
88
90
|
const allowedDevOrigins = new Set(["http://localhost:3000", "http://127.0.0.1:3000"]);
|
|
89
|
-
const isLoopbackHost = (host) => host === "127.0.0.1" || host === "::1" || host.startsWith("127.");
|
|
90
91
|
const getSelfOrigins = () => {
|
|
91
92
|
const origins = new Set();
|
|
92
93
|
const candidates = [deps.serverMeta.localUrl, deps.serverMeta.remoteUrl];
|
|
@@ -130,7 +131,7 @@ export function createHttpServer(deps) {
|
|
|
130
131
|
return;
|
|
131
132
|
}
|
|
132
133
|
// When we bind to a non-loopback host (e.g., 0.0.0.0 or LAN IP), allow cross-origin UI access.
|
|
133
|
-
if (deps.bindHost
|
|
134
|
+
if (isWildcardHost(deps.bindHost) || !isLoopbackHost(deps.bindHost)) {
|
|
134
135
|
cb(null, true);
|
|
135
136
|
return;
|
|
136
137
|
}
|
|
@@ -265,6 +266,7 @@ export function createHttpServer(deps) {
|
|
|
265
266
|
logger: proxyLogger,
|
|
266
267
|
});
|
|
267
268
|
registerYoloRoutes(app, { yoloManager: deps.yoloManager });
|
|
269
|
+
registerSessionPruningRoutes(app, { workspaceManager: deps.workspaceManager, worktreeDeletionFence });
|
|
268
270
|
registerInstanceProxyRoutes(app, { workspaceManager: deps.workspaceManager, logger: proxyLogger, worktreeDeletionFence });
|
|
269
271
|
if (deps.uiDevServerUrl) {
|
|
270
272
|
setupDevProxy(app, deps.uiDevServerUrl, deps.authManager, deps.previewManager, proxyLogger);
|
|
@@ -276,7 +278,8 @@ export function createHttpServer(deps) {
|
|
|
276
278
|
instance: app,
|
|
277
279
|
start: async () => {
|
|
278
280
|
const attemptListen = async (requestedPort) => {
|
|
279
|
-
const
|
|
281
|
+
const dualStackWildcard = isWildcardHost(deps.bindHost) && isIP(stripHostBrackets(deps.bindHost)) === 6;
|
|
282
|
+
const addressInfo = await app.listen({ port: requestedPort, host: deps.bindHost, ...(dualStackWildcard ? { ipv6Only: false } : {}) });
|
|
280
283
|
return { addressInfo, requestedPort };
|
|
281
284
|
};
|
|
282
285
|
const autoPortRequested = deps.bindPort === 0;
|
|
@@ -308,7 +311,7 @@ export function createHttpServer(deps) {
|
|
|
308
311
|
actualPort = address.port;
|
|
309
312
|
}
|
|
310
313
|
}
|
|
311
|
-
const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : deps.bindHost;
|
|
314
|
+
const displayHost = deps.bindHost === "127.0.0.1" ? "localhost" : formatHostForUrl(deps.bindHost);
|
|
312
315
|
const serverUrl = `${deps.protocol}://${displayHost}:${actualPort}`;
|
|
313
316
|
deps.logger.info({ port: actualPort, host: deps.bindHost, protocol: deps.protocol }, "HTTP server listening");
|
|
314
317
|
return { port: actualPort, url: serverUrl, displayHost };
|
|
@@ -1096,7 +1099,6 @@ function isAllowedInstanceApiRoute(method, pathname) {
|
|
|
1096
1099
|
["GET", /^\/api\/session(?:\/active)?$/],
|
|
1097
1100
|
["POST", /^\/api\/session(?:\/import)?$/],
|
|
1098
1101
|
["GET", /^\/api\/session\/[^/]+(?:\/message(?:\/[^/]+)?)?$/],
|
|
1099
|
-
["PATCH", /^\/api\/session\/[^/]+\/message\/[^/]+$/],
|
|
1100
1102
|
["GET", /^\/api\/session\/[^/]+\/inbox$/],
|
|
1101
1103
|
["GET", /^\/api\/session\/[^/]+\/(?:permission|form)$/],
|
|
1102
1104
|
["DELETE", /^\/api\/session\/[^/]+$/],
|