@thangnm.nip/arouter 0.1.2 → 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.
Files changed (159) hide show
  1. package/.next/standalone/.next/BUILD_ID +1 -1
  2. package/.next/standalone/.next/app-path-routes-manifest.json +5 -5
  3. package/.next/standalone/.next/build-manifest.json +2 -2
  4. package/.next/standalone/.next/prerender-manifest.json +3 -3
  5. package/.next/standalone/.next/server/app/(dashboard)/mitm/page_client-reference-manifest.js +1 -1
  6. package/.next/standalone/.next/server/app/(dashboard)/page_client-reference-manifest.js +1 -1
  7. package/.next/standalone/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  8. package/.next/standalone/.next/server/app/_global-error.html +2 -2
  9. package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
  10. package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  11. package/.next/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  12. package/.next/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  13. package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  14. package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  15. package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  16. package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  17. package/.next/standalone/.next/server/app/_not-found.html +1 -1
  18. package/.next/standalone/.next/server/app/_not-found.rsc +3 -3
  19. package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +3 -3
  20. package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  21. package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +3 -3
  22. package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  23. package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  24. package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  25. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/alias/route.js +2 -2
  26. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/alias/route_client-reference-manifest.js +1 -1
  27. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/console-logs/route_client-reference-manifest.js +1 -1
  28. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/route.js +1 -1
  29. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/route_client-reference-manifest.js +1 -1
  30. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/settings/route.js +2 -2
  31. package/.next/standalone/.next/server/app/api/cli-tools/antigravity-mitm/settings/route_client-reference-manifest.js +1 -1
  32. package/.next/standalone/.next/server/app/api/cli-tools/claude-settings/route.js +2 -2
  33. package/.next/standalone/.next/server/app/api/cli-tools/claude-settings/route_client-reference-manifest.js +1 -1
  34. package/.next/standalone/.next/server/app/api/cli-tools/codex-settings/route.js +2 -2
  35. package/.next/standalone/.next/server/app/api/cli-tools/codex-settings/route_client-reference-manifest.js +1 -1
  36. package/.next/standalone/.next/server/app/api/cli-tools/copilot-settings/route.js +2 -2
  37. package/.next/standalone/.next/server/app/api/cli-tools/copilot-settings/route_client-reference-manifest.js +1 -1
  38. package/.next/standalone/.next/server/app/api/cli-tools/droid-settings/route.js +1 -1
  39. package/.next/standalone/.next/server/app/api/cli-tools/droid-settings/route_client-reference-manifest.js +1 -1
  40. package/.next/standalone/.next/server/app/api/cli-tools/openclaw-settings/route.js +1 -1
  41. package/.next/standalone/.next/server/app/api/cli-tools/openclaw-settings/route_client-reference-manifest.js +1 -1
  42. package/.next/standalone/.next/server/app/api/cli-tools/opencode-settings/route.js +1 -1
  43. package/.next/standalone/.next/server/app/api/cli-tools/opencode-settings/route_client-reference-manifest.js +1 -1
  44. package/.next/standalone/.next/server/app/api/keys/[id]/route_client-reference-manifest.js +1 -1
  45. package/.next/standalone/.next/server/app/api/keys/route_client-reference-manifest.js +1 -1
  46. package/.next/standalone/.next/server/app/api/locale/route.js +2 -2
  47. package/.next/standalone/.next/server/app/api/locale/route_client-reference-manifest.js +1 -1
  48. package/.next/standalone/.next/server/app/api/models/alias/route_client-reference-manifest.js +1 -1
  49. package/.next/standalone/.next/server/app/api/models/route_client-reference-manifest.js +1 -1
  50. package/.next/standalone/.next/server/app/api/providers/route_client-reference-manifest.js +1 -1
  51. package/.next/standalone/.next/server/app/api/settings/route_client-reference-manifest.js +1 -1
  52. package/.next/standalone/.next/server/app/api/version/route_client-reference-manifest.js +1 -1
  53. package/.next/standalone/.next/server/app/index.html +1 -1
  54. package/.next/standalone/.next/server/app/index.rsc +4 -4
  55. package/.next/standalone/.next/server/app/index.segments/!KGRhc2hib2FyZCk/__PAGE__.segment.rsc +1 -1
  56. package/.next/standalone/.next/server/app/index.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  57. package/.next/standalone/.next/server/app/index.segments/_full.segment.rsc +4 -4
  58. package/.next/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  59. package/.next/standalone/.next/server/app/index.segments/_index.segment.rsc +3 -3
  60. package/.next/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  61. package/.next/standalone/.next/server/app/mitm.html +1 -1
  62. package/.next/standalone/.next/server/app/mitm.rsc +5 -5
  63. package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk/mitm/__PAGE__.segment.rsc +2 -2
  64. package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk/mitm.segment.rsc +1 -1
  65. package/.next/standalone/.next/server/app/mitm.segments/!KGRhc2hib2FyZCk.segment.rsc +2 -2
  66. package/.next/standalone/.next/server/app/mitm.segments/_full.segment.rsc +5 -5
  67. package/.next/standalone/.next/server/app/mitm.segments/_head.segment.rsc +1 -1
  68. package/.next/standalone/.next/server/app/mitm.segments/_index.segment.rsc +3 -3
  69. package/.next/standalone/.next/server/app/mitm.segments/_tree.segment.rsc +1 -1
  70. package/.next/standalone/.next/server/app-paths-manifest.json +5 -5
  71. package/.next/standalone/.next/server/chunks/411.js +1 -1
  72. package/.next/standalone/.next/server/middleware.js +2 -2
  73. package/.next/standalone/.next/server/pages/404.html +1 -1
  74. package/.next/standalone/.next/server/pages/500.html +2 -2
  75. package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
  76. package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
  77. package/.next/standalone/.next/static/chunks/{702-90a93bbee38dba8f.js → 702-0864796c00fc702e.js} +1 -1
  78. package/.next/standalone/node_modules/node-forge/LICENSE +331 -0
  79. package/.next/standalone/node_modules/node-forge/README.md +2071 -0
  80. package/.next/standalone/node_modules/node-forge/dist/forge.all.min.js +2 -0
  81. package/.next/standalone/node_modules/node-forge/dist/forge.all.min.js.map +1 -0
  82. package/.next/standalone/node_modules/node-forge/dist/forge.min.js +2 -0
  83. package/.next/standalone/node_modules/node-forge/dist/forge.min.js.map +1 -0
  84. package/.next/standalone/node_modules/node-forge/dist/prime.worker.min.js +2 -0
  85. package/.next/standalone/node_modules/node-forge/dist/prime.worker.min.js.map +1 -0
  86. package/.next/standalone/node_modules/node-forge/flash/swf/SocketPool.swf +0 -0
  87. package/.next/standalone/node_modules/node-forge/lib/aes.js +1091 -0
  88. package/.next/standalone/node_modules/node-forge/lib/aesCipherSuites.js +282 -0
  89. package/.next/standalone/node_modules/node-forge/lib/asn1-validator.js +91 -0
  90. package/.next/standalone/node_modules/node-forge/lib/asn1.js +1503 -0
  91. package/.next/standalone/node_modules/node-forge/lib/baseN.js +186 -0
  92. package/.next/standalone/node_modules/node-forge/lib/cipher.js +230 -0
  93. package/.next/standalone/node_modules/node-forge/lib/cipherModes.js +999 -0
  94. package/.next/standalone/node_modules/node-forge/lib/des.js +496 -0
  95. package/.next/standalone/node_modules/node-forge/lib/ed25519.js +1072 -0
  96. package/.next/standalone/node_modules/node-forge/lib/forge.js +13 -0
  97. package/.next/standalone/node_modules/node-forge/lib/form.js +149 -0
  98. package/.next/standalone/node_modules/node-forge/lib/hmac.js +146 -0
  99. package/.next/standalone/node_modules/node-forge/lib/http.js +1346 -0
  100. package/.next/standalone/node_modules/node-forge/lib/index.all.js +16 -0
  101. package/.next/standalone/node_modules/node-forge/lib/index.js +33 -0
  102. package/.next/standalone/node_modules/node-forge/lib/jsbn.js +1264 -0
  103. package/.next/standalone/node_modules/node-forge/lib/kem.js +168 -0
  104. package/.next/standalone/node_modules/node-forge/lib/log.js +319 -0
  105. package/.next/standalone/node_modules/node-forge/lib/md.all.js +13 -0
  106. package/.next/standalone/node_modules/node-forge/lib/md.js +11 -0
  107. package/.next/standalone/node_modules/node-forge/lib/md5.js +289 -0
  108. package/.next/standalone/node_modules/node-forge/lib/mgf.js +12 -0
  109. package/.next/standalone/node_modules/node-forge/lib/mgf1.js +57 -0
  110. package/.next/standalone/node_modules/node-forge/lib/oids.js +179 -0
  111. package/.next/standalone/node_modules/node-forge/lib/pbe.js +1023 -0
  112. package/.next/standalone/node_modules/node-forge/lib/pbkdf2.js +211 -0
  113. package/.next/standalone/node_modules/node-forge/lib/pem.js +237 -0
  114. package/.next/standalone/node_modules/node-forge/lib/pkcs1.js +276 -0
  115. package/.next/standalone/node_modules/node-forge/lib/pkcs12.js +1078 -0
  116. package/.next/standalone/node_modules/node-forge/lib/pkcs7.js +1260 -0
  117. package/.next/standalone/node_modules/node-forge/lib/pkcs7asn1.js +410 -0
  118. package/.next/standalone/node_modules/node-forge/lib/pki.js +102 -0
  119. package/.next/standalone/node_modules/node-forge/lib/prime.js +297 -0
  120. package/.next/standalone/node_modules/node-forge/lib/prime.worker.js +168 -0
  121. package/.next/standalone/node_modules/node-forge/lib/prng.js +419 -0
  122. package/.next/standalone/node_modules/node-forge/lib/pss.js +241 -0
  123. package/.next/standalone/node_modules/node-forge/lib/random.js +191 -0
  124. package/.next/standalone/node_modules/node-forge/lib/rc2.js +410 -0
  125. package/.next/standalone/node_modules/node-forge/lib/rsa.js +1949 -0
  126. package/.next/standalone/node_modules/node-forge/lib/sha1.js +319 -0
  127. package/.next/standalone/node_modules/node-forge/lib/sha256.js +327 -0
  128. package/.next/standalone/node_modules/node-forge/lib/sha512.js +561 -0
  129. package/.next/standalone/node_modules/node-forge/lib/socket.js +287 -0
  130. package/.next/standalone/node_modules/node-forge/lib/ssh.js +236 -0
  131. package/.next/standalone/node_modules/node-forge/lib/tls.js +4282 -0
  132. package/.next/standalone/node_modules/node-forge/lib/tlssocket.js +249 -0
  133. package/.next/standalone/node_modules/node-forge/lib/util.js +2652 -0
  134. package/.next/standalone/node_modules/node-forge/lib/x509.js +3242 -0
  135. package/.next/standalone/node_modules/node-forge/lib/xhr.js +738 -0
  136. package/.next/standalone/node_modules/node-forge/package.json +123 -0
  137. package/.next/standalone/node_modules/node-machine-id/.babelrc +26 -0
  138. package/.next/standalone/node_modules/node-machine-id/.eslintignore +2 -0
  139. package/.next/standalone/node_modules/node-machine-id/.eslintrc +140 -0
  140. package/.next/standalone/node_modules/node-machine-id/.flowconfig +9 -0
  141. package/.next/standalone/node_modules/node-machine-id/LICENSE +21 -0
  142. package/.next/standalone/node_modules/node-machine-id/README.md +84 -0
  143. package/.next/standalone/node_modules/node-machine-id/dist/index.js +1 -0
  144. package/.next/standalone/node_modules/node-machine-id/index.js +80 -0
  145. package/.next/standalone/node_modules/node-machine-id/package.json +74 -0
  146. package/.next/standalone/node_modules/node-machine-id/tests/index.js +45 -0
  147. package/.next/standalone/node_modules/node-machine-id/types/index.d.ts +17 -0
  148. package/.next/standalone/node_modules/node-machine-id/webpack.config.babel.js +59 -0
  149. package/.next/standalone/package.json +1 -1
  150. package/.next/standalone/src/mitm/cert/generate.js +32 -0
  151. package/.next/standalone/src/mitm/cert/install.js +176 -0
  152. package/.next/standalone/src/mitm/cert/rootCA.js +173 -0
  153. package/.next/standalone/src/mitm/dns/dnsConfig.js +216 -0
  154. package/.next/standalone/src/mitm/logger.js +8 -0
  155. package/.next/standalone/src/mitm/manager.js +603 -0
  156. package/.next/standalone/src/mitm/paths.js +16 -0
  157. package/package.json +1 -1
  158. /package/.next/standalone/.next/static/{Ws7kclTEFgfHRwd7pk9Ly → DkVxg6bby4SUixuq1vdO8}/_buildManifest.js +0 -0
  159. /package/.next/standalone/.next/static/{Ws7kclTEFgfHRwd7pk9Ly → DkVxg6bby4SUixuq1vdO8}/_ssgManifest.js +0 -0
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Module based on OS native UUID/GUID which used for internal needs.
3
+ */
4
+ declare module 'node-machine-id' {
5
+
6
+ /**
7
+ * This function gets the OS native UUID/GUID synchronously, hashed by default.
8
+ * @param {boolean} [original=false] - If true return original value of machine id, otherwise return hashed value (sha - 256)
9
+ */
10
+ function machineIdSync(original?: boolean): string;
11
+
12
+ /**
13
+ * This function gets the OS native UUID/GUID asynchronously (recommended), hashed by default.
14
+ * @param {boolean} [original=false] - If true return original value of machine id, otherwise return hashed value (sha - 256)
15
+ */
16
+ function machineId(original?: boolean): Promise<string>;
17
+ }
@@ -0,0 +1,59 @@
1
+ import fs from 'fs';
2
+ import webpack from 'webpack';
3
+
4
+ let nodeModules = fs.readdirSync('./node_modules')
5
+ .filter((module) => {
6
+ return module !== '.bin';
7
+ })
8
+ .reduce((prev, module) => {
9
+ return Object.assign(prev, {[module]: 'commonjs ' + module});
10
+ }, {});
11
+
12
+ export default {
13
+ entry: ['./index.js'],
14
+ output: {
15
+ path: './dist',
16
+ filename: 'index.js',
17
+ library: 'electron-machine-id',
18
+ libraryTarget: 'umd'
19
+ },
20
+ target: 'electron',
21
+ module: {
22
+ loaders: [
23
+ {
24
+ test: /\.js$/,
25
+ exclude: /node_modules/,
26
+ loader: 'babel',
27
+ query: {
28
+ cacheDirectory: true
29
+ }
30
+ },
31
+ {
32
+ test: /\.json$/,
33
+ loader: 'json'
34
+ }
35
+ ]
36
+ },
37
+ plugins: [
38
+ new webpack.IgnorePlugin(/node_modules/),
39
+ new webpack.optimize.OccurrenceOrderPlugin(),
40
+ new webpack.optimize.DedupePlugin(),
41
+ new webpack.optimize.UglifyJsPlugin({
42
+ compress: { warnings: false },
43
+ output: { comments: false },
44
+ })
45
+ ],
46
+ node: {
47
+ //do not include polyfills...
48
+ //http://webpack.github.io/docs/configuration.html#node
49
+ console: false,
50
+ process: false,
51
+ child_process: false,
52
+ global: false,
53
+ buffer: false,
54
+ crypto: false,
55
+ __filename: false,
56
+ __dirname: false
57
+ },
58
+ externals: nodeModules
59
+ };
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@thangnm.nip/arouter",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "aRouter",
5
5
  "bin": {
6
6
  "arouter": "./bin/arouter.mjs"
@@ -0,0 +1,32 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const { MITM_DIR } = require("../paths");
4
+ const { generateRootCA, loadRootCA, generateLeafCert } = require("./rootCA");
5
+
6
+ /**
7
+ * Generate Root CA certificate (one-time setup)
8
+ * This replaces the old static wildcard cert approach
9
+ */
10
+ async function generateCert() {
11
+ return await generateRootCA();
12
+ }
13
+
14
+ /**
15
+ * Get certificate for a specific domain (dynamic generation)
16
+ * Used by SNICallback in server.js
17
+ */
18
+ function getCertForDomain(domain) {
19
+ try {
20
+ const rootCA = loadRootCA();
21
+ const leafCert = generateLeafCert(domain, rootCA);
22
+ return {
23
+ key: leafCert.key,
24
+ cert: leafCert.cert
25
+ };
26
+ } catch (error) {
27
+ console.error(`Failed to generate cert for ${domain}:`, error.message);
28
+ return null;
29
+ }
30
+ }
31
+
32
+ module.exports = { generateCert, getCertForDomain };
@@ -0,0 +1,176 @@
1
+ const fs = require("fs");
2
+ const crypto = require("crypto");
3
+ const { exec } = require("child_process");
4
+ const { execWithPassword } = require("../dns/dnsConfig.js");
5
+ const { log, err } = require("../logger");
6
+
7
+ const IS_WIN = process.platform === "win32";
8
+ const IS_MAC = process.platform === "darwin";
9
+ const LINUX_CERT_DIR = "/usr/local/share/ca-certificates";
10
+
11
+ // Get SHA1 fingerprint from cert file using Node.js crypto
12
+ function getCertFingerprint(certPath) {
13
+ const pem = fs.readFileSync(certPath, "utf-8");
14
+ const der = Buffer.from(pem.replace(/-----[^-]+-----/g, "").replace(/\s/g, ""), "base64");
15
+ return crypto.createHash("sha1").update(der).digest("hex").toUpperCase().match(/.{2}/g).join(":");
16
+ }
17
+
18
+ /**
19
+ * Check if certificate is already installed in system store
20
+ */
21
+ async function checkCertInstalled(certPath) {
22
+ if (IS_WIN) return checkCertInstalledWindows(certPath);
23
+ if (IS_MAC) return checkCertInstalledMac(certPath);
24
+ return checkCertInstalledLinux();
25
+ }
26
+
27
+ function checkCertInstalledMac(certPath) {
28
+ return new Promise((resolve) => {
29
+ try {
30
+ const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
31
+ // security verify-cert returns 0 only if cert is trusted by system policy
32
+ exec(`security verify-cert -c "${certPath}" -p ssl -k /Library/Keychains/System.keychain 2>/dev/null`, (error) => {
33
+ if (!error) return resolve(true);
34
+ // Fallback: check if fingerprint appears in System keychain with trust
35
+ exec(`security dump-trust-settings -d 2>/dev/null | grep -i "${fingerprint}"`, (err2, stdout2) => {
36
+ resolve(!err2 && !!stdout2?.trim());
37
+ });
38
+ });
39
+ } catch {
40
+ resolve(false);
41
+ }
42
+ });
43
+ }
44
+
45
+ function checkCertInstalledWindows(certPath) {
46
+ return new Promise((resolve) => {
47
+ // Check Root store for our Root CA by common name
48
+ exec("certutil -store Root \"arouter MITM Root CA\"", { windowsHide: true }, (error) => {
49
+ resolve(!error);
50
+ });
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Install SSL certificate to system trust store
56
+ */
57
+ async function installCert(sudoPassword, certPath) {
58
+ if (!fs.existsSync(certPath)) {
59
+ throw new Error(`Certificate file not found: ${certPath}`);
60
+ }
61
+
62
+ const isInstalled = await checkCertInstalled(certPath);
63
+ if (isInstalled) {
64
+ log("🔐 Cert: already trusted ✅");
65
+ return;
66
+ }
67
+
68
+ if (IS_WIN) {
69
+ await installCertWindows(certPath);
70
+ } else if (IS_MAC) {
71
+ await installCertMac(sudoPassword, certPath);
72
+ } else {
73
+ await installCertLinux(sudoPassword, certPath);
74
+ }
75
+ }
76
+
77
+ async function installCertMac(sudoPassword, certPath) {
78
+ // Remove all old certs with same name first to avoid duplicate/stale cert conflict
79
+ const deleteOld = `security delete-certificate -c "arouter MITM Root CA" /Library/Keychains/System.keychain 2>/dev/null || true`;
80
+ const install = `security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "${certPath}"`;
81
+ try {
82
+ await execWithPassword(`${deleteOld} && ${install}`, sudoPassword);
83
+ log("🔐 Cert: ✅ installed to system keychain");
84
+ } catch (error) {
85
+ const msg = error.message?.includes("canceled") ? "User canceled authorization" : "Certificate install failed";
86
+ throw new Error(msg);
87
+ }
88
+ }
89
+
90
+ async function installCertWindows(certPath) {
91
+ // Process already has admin rights — run certutil directly, no UAC needed
92
+ return new Promise((resolve, reject) => {
93
+ exec(
94
+ `certutil -addstore Root "${certPath}"`,
95
+ { windowsHide: true },
96
+ (error) => {
97
+ if (error) reject(new Error(`Failed to install certificate: ${error.message}`));
98
+ else { log("🔐 Cert: ✅ installed to Windows Root store"); resolve(); }
99
+ }
100
+ );
101
+ });
102
+ }
103
+
104
+ /**
105
+ * Uninstall SSL certificate from system store
106
+ */
107
+ async function uninstallCert(sudoPassword, certPath) {
108
+ const isInstalled = await checkCertInstalled(certPath);
109
+ if (!isInstalled) {
110
+ log("🔐 Cert: not found in system store");
111
+ return;
112
+ }
113
+
114
+ if (IS_WIN) {
115
+ await uninstallCertWindows();
116
+ } else if (IS_MAC) {
117
+ await uninstallCertMac(sudoPassword, certPath);
118
+ } else {
119
+ await uninstallCertLinux(sudoPassword);
120
+ }
121
+ }
122
+
123
+ async function uninstallCertMac(sudoPassword, certPath) {
124
+ const fingerprint = getCertFingerprint(certPath).replace(/:/g, "");
125
+ const command = `security delete-certificate -Z "${fingerprint}" /Library/Keychains/System.keychain`;
126
+ try {
127
+ await execWithPassword(command, sudoPassword);
128
+ log("🔐 Cert: ✅ uninstalled from system keychain");
129
+ } catch (err) {
130
+ throw new Error("Failed to uninstall certificate");
131
+ }
132
+ }
133
+
134
+ async function uninstallCertWindows() {
135
+ // Process already has admin rights — run certutil directly, no UAC needed
136
+ return new Promise((resolve, reject) => {
137
+ exec(
138
+ `certutil -delstore Root "arouter MITM Root CA"`,
139
+ { windowsHide: true },
140
+ (error) => {
141
+ if (error) reject(new Error(`Failed to uninstall certificate: ${error.message}`));
142
+ else { log("🔐 Cert: ✅ uninstalled from Windows Root store"); resolve(); }
143
+ }
144
+ );
145
+ });
146
+ }
147
+
148
+ function checkCertInstalledLinux() {
149
+ const certFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
150
+ return Promise.resolve(fs.existsSync(certFile));
151
+ }
152
+
153
+ async function installCertLinux(sudoPassword, certPath) {
154
+ const destFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
155
+ // Try update-ca-certificates (Debian/Ubuntu), fallback to update-ca-trust (Fedora/RHEL)
156
+ const cmd = `cp "${certPath}" "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
157
+ try {
158
+ await execWithPassword(cmd, sudoPassword);
159
+ log("🔐 Cert: ✅ installed to Linux trust store");
160
+ } catch (error) {
161
+ throw new Error("Certificate install failed");
162
+ }
163
+ }
164
+
165
+ async function uninstallCertLinux(sudoPassword) {
166
+ const destFile = `${LINUX_CERT_DIR}/arouter-root-ca.crt`;
167
+ const cmd = `rm -f "${destFile}" && (update-ca-certificates 2>/dev/null || update-ca-trust 2>/dev/null || true)`;
168
+ try {
169
+ await execWithPassword(cmd, sudoPassword);
170
+ log("🔐 Cert: ✅ uninstalled from Linux trust store");
171
+ } catch (error) {
172
+ throw new Error("Failed to uninstall certificate");
173
+ }
174
+ }
175
+
176
+ module.exports = { installCert, uninstallCert, checkCertInstalled };
@@ -0,0 +1,173 @@
1
+ const path = require("path");
2
+ const fs = require("fs");
3
+ const forge = require("node-forge");
4
+ const { MITM_DIR } = require("../paths");
5
+
6
+ const ROOT_CA_KEY_PATH = path.join(MITM_DIR, "rootCA.key");
7
+ const ROOT_CA_CERT_PATH = path.join(MITM_DIR, "rootCA.crt");
8
+
9
+ /**
10
+ * Check if cert file is expired or expiring within 30 days
11
+ */
12
+ function isCertExpired(certPath) {
13
+ try {
14
+ const cert = forge.pki.certificateFromPem(fs.readFileSync(certPath, "utf8"));
15
+ const expiryThreshold = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000);
16
+ return cert.validity.notAfter < expiryThreshold;
17
+ } catch {
18
+ return true; // treat unreadable cert as expired
19
+ }
20
+ }
21
+
22
+ /**
23
+ * Generate Root CA certificate (only once, auto-regenerate if expired)
24
+ * This Root CA will sign all dynamic leaf certificates
25
+ */
26
+ async function generateRootCA() {
27
+ const exists = fs.existsSync(ROOT_CA_KEY_PATH) && fs.existsSync(ROOT_CA_CERT_PATH);
28
+ if (exists && !isCertExpired(ROOT_CA_CERT_PATH)) {
29
+ console.log("✅ Root CA already exists");
30
+ return { key: ROOT_CA_KEY_PATH, cert: ROOT_CA_CERT_PATH };
31
+ }
32
+ if (exists) {
33
+ console.log("🔐 Root CA expired or expiring soon — regenerating...");
34
+ try { fs.unlinkSync(ROOT_CA_KEY_PATH); } catch { /* ignore */ }
35
+ try { fs.unlinkSync(ROOT_CA_CERT_PATH); } catch { /* ignore */ }
36
+ }
37
+
38
+ if (!fs.existsSync(MITM_DIR)) {
39
+ fs.mkdirSync(MITM_DIR, { recursive: true });
40
+ }
41
+
42
+ console.log("🔐 Generating Root CA certificate...");
43
+
44
+ // Generate RSA key pair
45
+ const keys = forge.pki.rsa.generateKeyPair(2048);
46
+
47
+ // Create Root CA certificate
48
+ const cert = forge.pki.createCertificate();
49
+ cert.publicKey = keys.publicKey;
50
+ cert.serialNumber = "01";
51
+ cert.validity.notBefore = new Date();
52
+ cert.validity.notAfter = new Date();
53
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 10);
54
+
55
+ const attrs = [
56
+ { name: "commonName", value: "arouter MITM Root CA" },
57
+ { name: "organizationName", value: "arouter" },
58
+ { name: "countryName", value: "US" }
59
+ ];
60
+
61
+ cert.setSubject(attrs);
62
+ cert.setIssuer(attrs); // Self-signed
63
+
64
+ cert.setExtensions([
65
+ {
66
+ name: "basicConstraints",
67
+ cA: true,
68
+ critical: true
69
+ },
70
+ {
71
+ name: "keyUsage",
72
+ keyCertSign: true,
73
+ cRLSign: true,
74
+ critical: true
75
+ },
76
+ {
77
+ name: "subjectKeyIdentifier"
78
+ }
79
+ ]);
80
+
81
+ // Self-sign the certificate
82
+ cert.sign(keys.privateKey, forge.md.sha256.create());
83
+
84
+ // Save to disk
85
+ const privateKeyPem = forge.pki.privateKeyToPem(keys.privateKey);
86
+ const certPem = forge.pki.certificateToPem(cert);
87
+
88
+ fs.writeFileSync(ROOT_CA_KEY_PATH, privateKeyPem);
89
+ fs.writeFileSync(ROOT_CA_CERT_PATH, certPem);
90
+
91
+ console.log("✅ Root CA generated successfully");
92
+ return { key: ROOT_CA_KEY_PATH, cert: ROOT_CA_CERT_PATH };
93
+ }
94
+
95
+ /**
96
+ * Load Root CA from disk
97
+ */
98
+ function loadRootCA() {
99
+ if (!fs.existsSync(ROOT_CA_KEY_PATH) || !fs.existsSync(ROOT_CA_CERT_PATH)) {
100
+ throw new Error("Root CA not found. Generate it first.");
101
+ }
102
+
103
+ const keyPem = fs.readFileSync(ROOT_CA_KEY_PATH, "utf8");
104
+ const certPem = fs.readFileSync(ROOT_CA_CERT_PATH, "utf8");
105
+
106
+ return {
107
+ key: forge.pki.privateKeyFromPem(keyPem),
108
+ cert: forge.pki.certificateFromPem(certPem)
109
+ };
110
+ }
111
+
112
+ /**
113
+ * Generate leaf certificate for a specific domain, signed by Root CA
114
+ */
115
+ function generateLeafCert(domain, rootCA) {
116
+ // Generate key pair for leaf cert
117
+ const keys = forge.pki.rsa.generateKeyPair(2048);
118
+
119
+ // Create leaf certificate
120
+ const cert = forge.pki.createCertificate();
121
+ cert.publicKey = keys.publicKey;
122
+ cert.serialNumber = Math.floor(Math.random() * 1000000).toString();
123
+ cert.validity.notBefore = new Date();
124
+ cert.validity.notAfter = new Date();
125
+ cert.validity.notAfter.setFullYear(cert.validity.notBefore.getFullYear() + 1);
126
+
127
+ cert.setSubject([
128
+ { name: "commonName", value: domain }
129
+ ]);
130
+
131
+ cert.setIssuer(rootCA.cert.subject.attributes);
132
+
133
+ cert.setExtensions([
134
+ {
135
+ name: "basicConstraints",
136
+ cA: false
137
+ },
138
+ {
139
+ name: "keyUsage",
140
+ digitalSignature: true,
141
+ keyEncipherment: true
142
+ },
143
+ {
144
+ name: "extKeyUsage",
145
+ serverAuth: true,
146
+ clientAuth: true
147
+ },
148
+ {
149
+ name: "subjectAltName",
150
+ altNames: [
151
+ { type: 2, value: domain }, // DNS
152
+ { type: 2, value: `*.${domain}` } // Wildcard
153
+ ]
154
+ }
155
+ ]);
156
+
157
+ // Sign with Root CA
158
+ cert.sign(rootCA.key, forge.md.sha256.create());
159
+
160
+ return {
161
+ key: forge.pki.privateKeyToPem(keys.privateKey),
162
+ cert: forge.pki.certificateToPem(cert)
163
+ };
164
+ }
165
+
166
+ module.exports = {
167
+ generateRootCA,
168
+ loadRootCA,
169
+ generateLeafCert,
170
+ isCertExpired,
171
+ ROOT_CA_CERT_PATH,
172
+ ROOT_CA_KEY_PATH
173
+ };
@@ -0,0 +1,216 @@
1
+ const { exec, spawn } = require("child_process");
2
+ const fs = require("fs");
3
+ const path = require("path");
4
+ const os = require("os");
5
+ const { log, err } = require("../logger");
6
+
7
+ // Per-tool DNS hosts mapping
8
+ const TOOL_HOSTS = {
9
+ antigravity: ["daily-cloudcode-pa.googleapis.com", "cloudcode-pa.googleapis.com"],
10
+ copilot: ["api.individual.githubcopilot.com"],
11
+ };
12
+
13
+ const IS_WIN = process.platform === "win32";
14
+ const IS_MAC = process.platform === "darwin";
15
+ const HOSTS_FILE = IS_WIN
16
+ ? path.join(process.env.SystemRoot || "C:\\Windows", "System32", "drivers", "etc", "hosts")
17
+ : "/etc/hosts";
18
+
19
+ /**
20
+ * Execute elevated PowerShell script on Windows via Start-Process -Verb RunAs.
21
+ * Only UAC consent dialog appears, no CMD/PS window popup.
22
+ */
23
+ function executeElevatedPowerShell(psScriptPath, timeoutMs = 30000) {
24
+ const flagFile = path.join(os.tmpdir(), `ps_done_${Date.now()}.flag`);
25
+ const psSQ = (s) => s.replace(/'/g, "''");
26
+
27
+ let psContent = fs.readFileSync(psScriptPath, "utf8");
28
+ psContent += `\nSet-Content -Path '${psSQ(flagFile)}' -Value 'done' -Encoding UTF8\n`;
29
+ fs.writeFileSync(psScriptPath, psContent, "utf8");
30
+
31
+ const outerCmd = `Start-Process powershell -ArgumentList '-NoProfile','-ExecutionPolicy','Bypass','-WindowStyle','Hidden','-File','${psSQ(psScriptPath)}' -Verb RunAs -WindowStyle Hidden`;
32
+
33
+ return new Promise((resolve, reject) => {
34
+ let settled = false;
35
+ const settle = (fn, arg) => { if (!settled) { settled = true; fn(arg); } };
36
+
37
+ exec(
38
+ `powershell -NoProfile -NonInteractive -WindowStyle Hidden -Command "${outerCmd}"`,
39
+ { windowsHide: true },
40
+ () => {}
41
+ );
42
+
43
+ const deadline = Date.now() + timeoutMs;
44
+ const poll = () => {
45
+ if (settled) return;
46
+ if (fs.existsSync(flagFile)) {
47
+ try { fs.unlinkSync(flagFile); fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
48
+ return settle(resolve);
49
+ }
50
+ if (Date.now() > deadline) {
51
+ try { fs.unlinkSync(psScriptPath); } catch { /* ignore */ }
52
+ return settle(reject, new Error("Timed out waiting for UAC confirmation"));
53
+ }
54
+ setTimeout(poll, 500);
55
+ };
56
+ setTimeout(poll, 300);
57
+ });
58
+ }
59
+
60
+ /**
61
+ * Execute command with sudo password via stdin (macOS/Linux only)
62
+ */
63
+ function execWithPassword(command, password) {
64
+ return new Promise((resolve, reject) => {
65
+ const child = spawn("sudo", ["-S", "sh", "-c", command], {
66
+ stdio: ["pipe", "pipe", "pipe"]
67
+ });
68
+
69
+ let stdout = "";
70
+ let stderr = "";
71
+ child.stdout.on("data", (d) => { stdout += d; });
72
+ child.stderr.on("data", (d) => { stderr += d; });
73
+
74
+ child.on("close", (code) => {
75
+ if (code === 0) resolve(stdout);
76
+ else reject(new Error(stderr || `Exit code ${code}`));
77
+ });
78
+
79
+ child.stdin.write(`${password}\n`);
80
+ child.stdin.end();
81
+ });
82
+ }
83
+
84
+ /**
85
+ * Flush DNS cache (macOS/Linux)
86
+ */
87
+ async function flushDNS(sudoPassword) {
88
+ if (IS_WIN) return; // Windows flushes inline via ipconfig
89
+ if (IS_MAC) {
90
+ await execWithPassword("dscacheutil -flushcache && killall -HUP mDNSResponder", sudoPassword);
91
+ } else {
92
+ await execWithPassword("resolvectl flush-caches 2>/dev/null || true", sudoPassword);
93
+ }
94
+ }
95
+
96
+ /**
97
+ * Check if DNS entry exists for a specific host
98
+ */
99
+ function checkDNSEntry(host = null) {
100
+ try {
101
+ const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
102
+ if (host) return hostsContent.includes(host);
103
+ // Legacy: check all antigravity hosts (backward compat)
104
+ return TOOL_HOSTS.antigravity.every(h => hostsContent.includes(h));
105
+ } catch {
106
+ return false;
107
+ }
108
+ }
109
+
110
+ /**
111
+ * Check DNS status per tool — returns { [tool]: boolean }
112
+ */
113
+ function checkAllDNSStatus() {
114
+ try {
115
+ const hostsContent = fs.readFileSync(HOSTS_FILE, "utf8");
116
+ const result = {};
117
+ for (const [tool, hosts] of Object.entries(TOOL_HOSTS)) {
118
+ result[tool] = hosts.every(h => hostsContent.includes(h));
119
+ }
120
+ return result;
121
+ } catch {
122
+ return Object.fromEntries(Object.keys(TOOL_HOSTS).map(t => [t, false]));
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Add DNS entries for a specific tool
128
+ */
129
+ async function addDNSEntry(tool, sudoPassword) {
130
+ const hosts = TOOL_HOSTS[tool];
131
+ if (!hosts) throw new Error(`Unknown tool: ${tool}`);
132
+
133
+ const entriesToAdd = hosts.filter(h => !checkDNSEntry(h));
134
+ if (entriesToAdd.length === 0) {
135
+ log(`🌐 DNS ${tool}: already active`);
136
+ return;
137
+ }
138
+
139
+ const entries = entriesToAdd.map(h => `127.0.0.1 ${h}`).join("\n");
140
+
141
+ try {
142
+ if (IS_WIN) {
143
+ // Process already has admin rights — edit hosts file directly
144
+ const toAppend = entriesToAdd.map(h => `127.0.0.1 ${h}`).join("\r\n") + "\r\n";
145
+ fs.appendFileSync(HOSTS_FILE, toAppend, "utf8");
146
+ require("child_process").execSync("ipconfig /flushdns", { windowsHide: true });
147
+ } else {
148
+ await execWithPassword(`echo "${entries}" >> ${HOSTS_FILE}`, sudoPassword);
149
+ await flushDNS(sudoPassword);
150
+ }
151
+ log(`🌐 DNS ${tool}: ✅ added ${entriesToAdd.join(", ")}`);
152
+ } catch (error) {
153
+ const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to add DNS entry";
154
+ throw new Error(msg);
155
+ }
156
+ }
157
+
158
+ /**
159
+ * Remove DNS entries for a specific tool
160
+ */
161
+ async function removeDNSEntry(tool, sudoPassword) {
162
+ const hosts = TOOL_HOSTS[tool];
163
+ if (!hosts) throw new Error(`Unknown tool: ${tool}`);
164
+
165
+ const entriesToRemove = hosts.filter(h => checkDNSEntry(h));
166
+ if (entriesToRemove.length === 0) {
167
+ log(`🌐 DNS ${tool}: already inactive`);
168
+ return;
169
+ }
170
+
171
+ try {
172
+ if (IS_WIN) {
173
+ // Process already has admin rights — edit hosts file directly
174
+ const content = fs.readFileSync(HOSTS_FILE, "utf8");
175
+ const filtered = content.split(/\r?\n/).filter(l => !entriesToRemove.some(h => l.includes(h))).join("\r\n");
176
+ fs.writeFileSync(HOSTS_FILE, filtered, "utf8");
177
+ require("child_process").execSync("ipconfig /flushdns", { windowsHide: true });
178
+ } else {
179
+ for (const host of entriesToRemove) {
180
+ const sedCmd = IS_MAC
181
+ ? `sed -i '' '/${host}/d' ${HOSTS_FILE}`
182
+ : `sed -i '/${host}/d' ${HOSTS_FILE}`;
183
+ await execWithPassword(sedCmd, sudoPassword);
184
+ }
185
+ await flushDNS(sudoPassword);
186
+ }
187
+ log(`🌐 DNS ${tool}: ✅ removed ${entriesToRemove.join(", ")}`);
188
+ } catch (error) {
189
+ const msg = error.message?.includes("incorrect password") ? "Wrong sudo password" : "Failed to remove DNS entry";
190
+ throw new Error(msg);
191
+ }
192
+ }
193
+
194
+ /**
195
+ * Remove ALL tool DNS entries (used when stopping server)
196
+ */
197
+ async function removeAllDNSEntries(sudoPassword) {
198
+ for (const tool of Object.keys(TOOL_HOSTS)) {
199
+ try {
200
+ await removeDNSEntry(tool, sudoPassword);
201
+ } catch (e) {
202
+ err(`DNS ${tool}: failed to remove — ${e.message}`);
203
+ }
204
+ }
205
+ }
206
+
207
+ module.exports = {
208
+ TOOL_HOSTS,
209
+ addDNSEntry,
210
+ removeDNSEntry,
211
+ removeAllDNSEntries,
212
+ execWithPassword,
213
+ executeElevatedPowerShell,
214
+ checkDNSEntry,
215
+ checkAllDNSStatus,
216
+ };
@@ -0,0 +1,8 @@
1
+ function time() {
2
+ return new Date().toLocaleTimeString("en-US", { hour12: false });
3
+ }
4
+
5
+ const log = (msg) => console.log(`[${time()}] [MITM] ${msg}`);
6
+ const err = (msg) => console.error(`[${time()}] ❌ [MITM] ${msg}`);
7
+
8
+ module.exports = { log, err };