@remnic/core 9.50.1 → 9.50.2

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 (57) hide show
  1. package/dist/access-admin-ops-surface.js +3 -3
  2. package/dist/access-authorization-probe.js +4 -4
  3. package/dist/access-boundary.js +3 -3
  4. package/dist/access-cli.js +13 -13
  5. package/dist/access-extraction-force-flush.js +3 -3
  6. package/dist/access-http-query.js +3 -3
  7. package/dist/access-http.js +9 -9
  8. package/dist/access-identity-continuity-surface.js +3 -3
  9. package/dist/access-lcm-surface.js +3 -3
  10. package/dist/access-mcp.js +7 -7
  11. package/dist/access-namespace-preflight.js +3 -3
  12. package/dist/access-observe-write-surface.js +3 -3
  13. package/dist/access-operations-batch.js +4 -4
  14. package/dist/access-operations.js +6 -6
  15. package/dist/access-recall-concurrency.js +3 -3
  16. package/dist/access-recall-response.js +3 -3
  17. package/dist/access-recall-surface.js +3 -3
  18. package/dist/access-service.js +3 -3
  19. package/dist/{chunk-UWIDGVQM.js → chunk-5C2HNJMO.js} +3 -3
  20. package/dist/{chunk-X4IDNPOS.js → chunk-HAEU3ZW7.js} +2 -2
  21. package/dist/{chunk-NZ6UDRUA.js → chunk-IK6X5QPO.js} +5 -5
  22. package/dist/{chunk-P7ADRREJ.js → chunk-KDG2KTWW.js} +3 -3
  23. package/dist/{chunk-IB25FBMX.js → chunk-LEJPSMKM.js} +2 -2
  24. package/dist/{chunk-ZDAYWJV6.js → chunk-NO37MR45.js} +3 -3
  25. package/dist/{chunk-ZCO5F5FW.js → chunk-PLKFO2HJ.js} +2 -2
  26. package/dist/{chunk-LOW66JRO.js → chunk-PYKKWQZ2.js} +2 -2
  27. package/dist/{chunk-T6RATUV2.js → chunk-QW3WFHKU.js} +2 -2
  28. package/dist/{chunk-PNRA7FAD.js → chunk-RZRUV6N2.js} +104 -62
  29. package/dist/chunk-RZRUV6N2.js.map +1 -0
  30. package/dist/{chunk-D73B6HMN.js → chunk-S3AYIQCU.js} +2 -2
  31. package/dist/{chunk-HS4M6BZM.js → chunk-TQFHQPWD.js} +5 -5
  32. package/dist/{chunk-JFNRA7YJ.js → chunk-V7X767RT.js} +4 -4
  33. package/dist/cli.js +10 -10
  34. package/dist/connectors/index.d.ts +1 -0
  35. package/dist/connectors/index.js +1 -1
  36. package/dist/external-wiki-access.js +4 -4
  37. package/dist/extraction.js +4 -4
  38. package/dist/index.js +13 -13
  39. package/dist/orchestrator.js +13 -13
  40. package/dist/source-agent-qualifier.js +3 -3
  41. package/package.json +2 -2
  42. package/src/connectors/index.ts +40 -112
  43. package/src/runtime/daemon-health.ts +53 -0
  44. package/src/runtime/http-transport.ts +25 -0
  45. package/dist/chunk-PNRA7FAD.js.map +0 -1
  46. /package/dist/{chunk-UWIDGVQM.js.map → chunk-5C2HNJMO.js.map} +0 -0
  47. /package/dist/{chunk-X4IDNPOS.js.map → chunk-HAEU3ZW7.js.map} +0 -0
  48. /package/dist/{chunk-NZ6UDRUA.js.map → chunk-IK6X5QPO.js.map} +0 -0
  49. /package/dist/{chunk-P7ADRREJ.js.map → chunk-KDG2KTWW.js.map} +0 -0
  50. /package/dist/{chunk-IB25FBMX.js.map → chunk-LEJPSMKM.js.map} +0 -0
  51. /package/dist/{chunk-ZDAYWJV6.js.map → chunk-NO37MR45.js.map} +0 -0
  52. /package/dist/{chunk-ZCO5F5FW.js.map → chunk-PLKFO2HJ.js.map} +0 -0
  53. /package/dist/{chunk-LOW66JRO.js.map → chunk-PYKKWQZ2.js.map} +0 -0
  54. /package/dist/{chunk-T6RATUV2.js.map → chunk-QW3WFHKU.js.map} +0 -0
  55. /package/dist/{chunk-D73B6HMN.js.map → chunk-S3AYIQCU.js.map} +0 -0
  56. /package/dist/{chunk-HS4M6BZM.js.map → chunk-TQFHQPWD.js.map} +0 -0
  57. /package/dist/{chunk-JFNRA7YJ.js.map → chunk-V7X767RT.js.map} +0 -0
@@ -12,10 +12,10 @@ import { createRequire } from "node:module";
12
12
  import { fileURLToPath } from "node:url";
13
13
 
14
14
  import { generateToken, revokeToken, buildTokenEntry, commitTokenEntry, loadTokenStore, saveTokenStore } from "../tokens.js";
15
- import { launchProcessSync } from "../runtime/child-process.js";
16
- import { mergeEnv, readEnvVar, resolveHomeDir } from "../runtime/env.js";
15
+ import { checkDaemonHealth } from "../runtime/daemon-health.js";
16
+ import { readEnvVar, resolveHomeDir } from "../runtime/env.js";
17
17
  import { expandTildePath } from "../utils/path.js";
18
- import { coerceInstallExtension } from "./coerce.js";
18
+ import { coerceBool, coerceInstallExtension } from "./coerce.js";
19
19
  import {
20
20
  assertConfigComponentsNotSymlinked,
21
21
  hermesShimPath,
@@ -988,10 +988,12 @@ export function installConnector(options: InstallOptions): InstallResult {
988
988
  let hermesSavedProfile: string | undefined;
989
989
  let hermesSavedHost: string | undefined;
990
990
  let hermesSavedPort: number | undefined;
991
+ let hermesSavedAllowInsecureHttp: boolean | undefined;
991
992
  // Resolved values (used both in resolvedConfig and in the YAML update below)
992
993
  let hermesResolvedProfile: string | undefined;
993
994
  let hermesResolvedHost: string | undefined;
994
995
  let hermesResolvedPort: number | undefined;
996
+ let hermesResolvedAllowInsecureHttp = false;
995
997
  if (options.connectorId === "hermes") {
996
998
  if (fs.existsSync(configPath)) {
997
999
  try {
@@ -1022,6 +1024,9 @@ export function installConnector(options: InstallOptions): InstallResult {
1022
1024
  // Invalid saved port — fall through to default
1023
1025
  }
1024
1026
  }
1027
+ if (prev?.allow_insecure_http != null) {
1028
+ hermesSavedAllowInsecureHttp = coerceBool(prev.allow_insecure_http, "allow_insecure_http");
1029
+ }
1025
1030
  } catch {
1026
1031
  // Could not read existing config — fall through to defaults
1027
1032
  }
@@ -1030,6 +1035,7 @@ export function installConnector(options: InstallOptions): InstallResult {
1030
1035
  // and applied in the sanitization block below (single point of validation).
1031
1036
  hermesResolvedProfile = hermesSavedProfile ?? "default";
1032
1037
  hermesResolvedHost = hermesSavedHost ?? "127.0.0.1";
1038
+ hermesResolvedAllowInsecureHttp = hermesSavedAllowInsecureHttp ?? false;
1033
1039
 
1034
1040
  // Issue C: wrap sanitizeHermesPort (and profile/host) in try-catch so
1035
1041
  // that invalid user-supplied values return a clean error result.
@@ -1071,6 +1077,17 @@ export function installConnector(options: InstallOptions): InstallResult {
1071
1077
  };
1072
1078
  }
1073
1079
  }
1080
+ if (options.config?.allow_insecure_http !== undefined) {
1081
+ const allowInsecureHttp = coerceBool(options.config.allow_insecure_http, "allow_insecure_http");
1082
+ if (allowInsecureHttp === undefined) {
1083
+ return {
1084
+ connectorId: options.connectorId,
1085
+ status: "error",
1086
+ message: "Invalid Hermes config: allow_insecure_http must be true or false",
1087
+ };
1088
+ }
1089
+ hermesResolvedAllowInsecureHttp = allowInsecureHttp;
1090
+ }
1074
1091
  }
1075
1092
 
1076
1093
  // Generate a per-connector auth token so the daemon can authenticate
@@ -1182,6 +1199,7 @@ export function installConnector(options: InstallOptions): InstallResult {
1182
1199
  profile: hermesResolvedProfile,
1183
1200
  host: hermesResolvedHost,
1184
1201
  port: hermesResolvedPort,
1202
+ allow_insecure_http: hermesResolvedAllowInsecureHttp,
1185
1203
  } : {}),
1186
1204
  };
1187
1205
 
@@ -1260,6 +1278,7 @@ export function installConnector(options: InstallOptions): InstallResult {
1260
1278
  host: hermesHost,
1261
1279
  port: hermesPort,
1262
1280
  token: tokenEntry.token,
1281
+ allowInsecureHttp: hermesResolvedAllowInsecureHttp,
1263
1282
  });
1264
1283
  } catch (err) {
1265
1284
  // upsertHermesConfig threw — old token preserved, connector.json unchanged.
@@ -1458,6 +1477,7 @@ export function installConnector(options: InstallOptions): InstallResult {
1458
1477
  host: hermesHost,
1459
1478
  port: hermesPort,
1460
1479
  token: tokenEntry.token,
1480
+ allowInsecureHttp: hermesResolvedAllowInsecureHttp,
1461
1481
  });
1462
1482
  if (refresh.updated) {
1463
1483
  hermesPriorConfigMutations.push({ mutatedPath: priorConfigPath, priorContent: beforeRefresh });
@@ -1756,7 +1776,12 @@ export function installConnector(options: InstallOptions): InstallResult {
1756
1776
  // (we returned early on failure above) but the explicit guard is kept for
1757
1777
  // clarity and future robustness.
1758
1778
  if (committed && tokenEntry) {
1759
- const daemonOk = checkDaemonHealth(hermesHost, hermesPort, tokenEntry.token);
1779
+ const daemonOk = checkDaemonHealth(
1780
+ hermesHost,
1781
+ hermesPort,
1782
+ tokenEntry.token,
1783
+ hermesResolvedAllowInsecureHttp,
1784
+ );
1760
1785
  if (daemonOk) {
1761
1786
  notes.push("Daemon health check: OK");
1762
1787
  } else {
@@ -2883,6 +2908,7 @@ export function upsertHermesConfig(opts: {
2883
2908
  host: string;
2884
2909
  port: number;
2885
2910
  token: string;
2911
+ allowInsecureHttp?: boolean;
2886
2912
  }): HermesConfigResult {
2887
2913
  return upsertHermesConfigAt(hermesConfigPath(opts.profile), opts);
2888
2914
  }
@@ -2895,7 +2921,7 @@ export function upsertHermesConfig(opts: {
2895
2921
  */
2896
2922
  function upsertHermesConfigAt(
2897
2923
  cfgPath: string,
2898
- opts: { host: string; port: number; token: string },
2924
+ opts: { host: string; port: number; token: string; allowInsecureHttp?: boolean },
2899
2925
  ): HermesConfigResult {
2900
2926
  // Symlinked components below the Hermes root must not redirect the
2901
2927
  // token-bearing write (Codex P1 on PR #1938, round 19).
@@ -2907,6 +2933,7 @@ function upsertHermesConfigAt(
2907
2933
  // that could break out of the scalar context.
2908
2934
  const safeHost = sanitizeHermesHost(opts.host);
2909
2935
  const safePort = sanitizeHermesPort(opts.port);
2936
+ const allowInsecureHttp = opts.allowInsecureHttp ?? false;
2910
2937
  // Token is generated by randomBytes + a fixed alphabetic prefix, so it's
2911
2938
  // already safe for YAML scalar interpolation. We still guard against an
2912
2939
  // unexpectedly malformed token reaching this function.
@@ -2928,6 +2955,7 @@ function upsertHermesConfigAt(
2928
2955
  ` host: "${safeHost}"`,
2929
2956
  ` port: ${safePort}`,
2930
2957
  ` token: "${opts.token}"`,
2958
+ ` allow_insecure_http: ${allowInsecureHttp}`,
2931
2959
  ].join("\n");
2932
2960
 
2933
2961
  if (!fs.existsSync(cfgPath)) {
@@ -2973,7 +3001,7 @@ function upsertHermesConfigAt(
2973
3001
  let blockWritten = false;
2974
3002
 
2975
3003
  // Track which sub-keys we've emitted
2976
- const written = { host: false, port: false, token: false };
3004
+ const written = { host: false, port: false, token: false, allowInsecureHttp: false };
2977
3005
 
2978
3006
  for (let i = 0; i < lines.length; i++) {
2979
3007
  const line = lines[i];
@@ -2993,13 +3021,14 @@ function upsertHermesConfigAt(
2993
3021
  if (!written.host) newLines.push(` host: "${safeHost}"`);
2994
3022
  if (!written.port) newLines.push(` port: ${safePort}`);
2995
3023
  if (!written.token) newLines.push(` token: "${opts.token}"`);
3024
+ if (!written.allowInsecureHttp) newLines.push(` allow_insecure_http: ${allowInsecureHttp}`);
2996
3025
  blockWritten = true;
2997
3026
  inRemnicBlock = false;
2998
3027
  newLines.push(line);
2999
3028
  continue;
3000
3029
  }
3001
3030
 
3002
- // Replace host/port/token lines; preserve other sub-keys
3031
+ // Replace managed transport fields and preserve other sub-keys.
3003
3032
  if (/^\s+host:/.test(line)) {
3004
3033
  newLines.push(` host: "${safeHost}"`);
3005
3034
  written.host = true;
@@ -3009,6 +3038,9 @@ function upsertHermesConfigAt(
3009
3038
  } else if (/^\s+token:/.test(line)) {
3010
3039
  newLines.push(` token: "${opts.token}"`);
3011
3040
  written.token = true;
3041
+ } else if (/^\s+allow_insecure_http:/.test(line)) {
3042
+ newLines.push(` allow_insecure_http: ${allowInsecureHttp}`);
3043
+ written.allowInsecureHttp = true;
3012
3044
  } else {
3013
3045
  newLines.push(line);
3014
3046
  }
@@ -3023,6 +3055,7 @@ function upsertHermesConfigAt(
3023
3055
  if (!written.host) newLines.push(` host: "${safeHost}"`);
3024
3056
  if (!written.port) newLines.push(` port: ${safePort}`);
3025
3057
  if (!written.token) newLines.push(` token: "${opts.token}"`);
3058
+ if (!written.allowInsecureHttp) newLines.push(` allow_insecure_http: ${allowInsecureHttp}`);
3026
3059
  }
3027
3060
 
3028
3061
  // Always write exactly one trailing newline, matching the create and append paths.
@@ -3207,111 +3240,6 @@ function removeHermesConfigFile(cfgPath: string): HermesConfigResult {
3207
3240
  return { updated: true, skipped: false, configPath: cfgPath };
3208
3241
  }
3209
3242
 
3210
- // ── Daemon health check (synchronous, non-fatal) ────────────────────────────
3211
-
3212
- /**
3213
- * Probe exit-code contract (used by checkDaemonHealth):
3214
- * 0 — HTTP 200 (healthy)
3215
- * 2 — HTTP 401 (token cache miss: retry after TTL)
3216
- * 1 — any other HTTP status or network error
3217
- */
3218
- const HEALTH_EXIT_OK = 0;
3219
- const HEALTH_EXIT_UNAUTHORIZED = 2;
3220
-
3221
- /**
3222
- * Ping /engram/v1/health synchronously.
3223
- * Returns true if the daemon responds with HTTP 200, false otherwise.
3224
- * Uses a synchronous helper to run a one-liner Node script so that the existing
3225
- * installConnector() flow does not need to become async.
3226
- *
3227
- * Data (host, port, token) are passed via environment variables — NOT
3228
- * interpolated into the script string — to prevent injection from
3229
- * user-supplied config values.
3230
- *
3231
- * /engram/v1/health is protected by bearer auth in the access HTTP server,
3232
- * so the caller must pass the connector token (or the configured server
3233
- * token) or the probe will always return 401 and report the daemon as
3234
- * unreachable even when it is running.
3235
- *
3236
- * 401 handling: the daemon caches valid tokens with a 5-second TTL
3237
- * (getAllValidTokensCached). A freshly-rotated token may not appear in the
3238
- * cache for up to 5 s after rotation. We tolerate a single 401 by sleeping
3239
- * one cache TTL (6000 ms = 5 s TTL + 1 s buffer) and retrying exactly once.
3240
- */
3241
- function checkDaemonHealth(host: string, port: number, authToken?: string): boolean {
3242
- try {
3243
- // Validate port: must be an integer in [1, 65535].
3244
- // This guards against user config supplying a non-numeric string.
3245
- const safePort = Math.trunc(Number(port));
3246
- if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) {
3247
- return false;
3248
- }
3249
- // Finding 7 fix: Node's http.get({ host }) expects an unbracketed IPv6
3250
- // literal (e.g. "::1"), but sanitizeHermesHost permits bracketed form
3251
- // "[::1]" (required for URL contexts). Strip the brackets here so that
3252
- // http.get receives the bare address and doesn't fail to connect.
3253
- // IPv4 and hostname strings are unaffected (no brackets to strip).
3254
- const bareHost = host.startsWith("[") && host.endsWith("]")
3255
- ? host.slice(1, -1)
3256
- : host;
3257
-
3258
- // Data (host, port, token) are passed via env vars, never interpolated
3259
- // into the script string, preventing any code-injection from malformed
3260
- // config values.
3261
- // Exit codes: 0 = 200 OK, 2 = 401 Unauthorized, 1 = other error.
3262
- const script = [
3263
- "const http = require('http');",
3264
- "const env = process['env'];",
3265
- "const headers = {};",
3266
- "if (env.REMNIC_HEALTH_TOKEN) {",
3267
- " headers['authorization'] = 'Bearer ' + env.REMNIC_HEALTH_TOKEN;",
3268
- "}",
3269
- "const req = http.get({",
3270
- " host: env.REMNIC_HEALTH_HOST,",
3271
- " port: parseInt(env.REMNIC_HEALTH_PORT, 10),",
3272
- " path: '/engram/v1/health',",
3273
- " headers,",
3274
- " timeout: 3000,",
3275
- "}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });",
3276
- "req.on('error', () => process.exit(1));",
3277
- "req.on('timeout', () => { req.destroy(); process.exit(1); });",
3278
- ].join("\n");
3279
- const env: NodeJS.ProcessEnv = mergeEnv({
3280
- REMNIC_HEALTH_HOST: bareHost,
3281
- REMNIC_HEALTH_PORT: String(safePort),
3282
- });
3283
- if (authToken) {
3284
- env.REMNIC_HEALTH_TOKEN = authToken;
3285
- }
3286
- const processPath = process.execPath;
3287
- const launchOptions = { timeout: 4000, env };
3288
- const result = launchProcessSync(processPath, ["-e", script], launchOptions);
3289
-
3290
- if (result.status === HEALTH_EXIT_OK) {
3291
- return true;
3292
- }
3293
-
3294
- if (result.status === HEALTH_EXIT_UNAUTHORIZED) {
3295
- // The daemon's token cache (5 s TTL) has not yet picked up the freshly
3296
- // rotated token. Sleep one TTL + buffer and retry exactly once.
3297
- console.error(
3298
- "[remnic/connectors] health probe got 401 — retrying after token cache TTL...",
3299
- );
3300
- // Synchronous sleep without making the caller async.
3301
- launchProcessSync(processPath, ["-e", "setTimeout(() => {}, 6000)"], {
3302
- timeout: 7000,
3303
- env: {},
3304
- });
3305
- const retry = launchProcessSync(processPath, ["-e", script], launchOptions);
3306
- return retry.status === HEALTH_EXIT_OK;
3307
- }
3308
-
3309
- return false;
3310
- } catch {
3311
- return false;
3312
- }
3313
- }
3314
-
3315
3243
  // ── Doctor ────────────────────────────────────────────────────────────────────
3316
3244
 
3317
3245
  export async function doctorConnector(connectorId: string): Promise<DoctorResult> {
@@ -0,0 +1,53 @@
1
+ import { launchProcessSync } from "./child-process.js";
2
+ import { mergeEnv } from "./env.js";
3
+ import { httpProtocolForHost } from "./http-transport.js";
4
+
5
+ const HEALTH_EXIT_OK = 0;
6
+ const HEALTH_EXIT_UNAUTHORIZED = 2;
7
+
8
+ export function checkDaemonHealth(
9
+ host: string,
10
+ port: number,
11
+ authToken?: string,
12
+ allowInsecureHttp = false,
13
+ ): boolean {
14
+ try {
15
+ const safePort = Math.trunc(Number(port));
16
+ if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) return false;
17
+
18
+ const bareHost = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
19
+ const script = [
20
+ "const env = process['env'];",
21
+ "const protocol = env.REMNIC_HEALTH_PROTOCOL;",
22
+ "if (protocol !== 'http' && protocol !== 'https') process.exit(1);",
23
+ "const transport = require(protocol);",
24
+ "const headers = {};",
25
+ "if (env.REMNIC_HEALTH_TOKEN) headers['authorization'] = 'Bearer ' + env.REMNIC_HEALTH_TOKEN;",
26
+ "const req = transport.get({",
27
+ " host: env.REMNIC_HEALTH_HOST,",
28
+ " port: parseInt(env.REMNIC_HEALTH_PORT, 10),",
29
+ " path: '/engram/v1/health', headers, timeout: 3000,",
30
+ "}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });",
31
+ "req.on('error', () => process.exit(1));",
32
+ "req.on('timeout', () => { req.destroy(); process.exit(1); });",
33
+ ].join("\n");
34
+ const env: NodeJS.ProcessEnv = mergeEnv({
35
+ REMNIC_HEALTH_HOST: bareHost,
36
+ REMNIC_HEALTH_PORT: String(safePort),
37
+ REMNIC_HEALTH_PROTOCOL: httpProtocolForHost(host, allowInsecureHttp),
38
+ });
39
+ if (authToken) env.REMNIC_HEALTH_TOKEN = authToken;
40
+
41
+ const launchOptions = { timeout: 4000, env };
42
+ const result = launchProcessSync(process.execPath, ["-e", script], launchOptions);
43
+ if (result.status === HEALTH_EXIT_OK) return true;
44
+ if (result.status !== HEALTH_EXIT_UNAUTHORIZED) return false;
45
+
46
+ console.error("[remnic/connectors] health probe got 401 — retrying after token cache TTL...");
47
+ launchProcessSync(process.execPath, ["-e", "setTimeout(() => {}, 6000)"], { timeout: 7000, env: {} });
48
+ const retry = launchProcessSync(process.execPath, ["-e", script], launchOptions);
49
+ return retry.status === HEALTH_EXIT_OK;
50
+ } catch {
51
+ return false;
52
+ }
53
+ }
@@ -0,0 +1,25 @@
1
+ import { isIP } from "node:net";
2
+
3
+ function normalizedIp(host: string): string {
4
+ const value = host.trim().replace(/^\[/, "").replace(/\]\.?$/, "").replace(/\.$/, "");
5
+ if (isIP(value) === 4) return value;
6
+ if (isIP(value) !== 6) return "";
7
+ return new URL(`http://[${value}]/`).hostname.slice(1, -1);
8
+ }
9
+
10
+ export function isLoopbackHost(host: string): boolean {
11
+ const normalized = host.trim().replace(/^\[/, "").replace(/\]\.?$/, "").replace(/\.$/, "").toLowerCase();
12
+ if (normalized === "localhost" || normalized.endsWith(".localhost")) return true;
13
+
14
+ const ip = normalizedIp(normalized);
15
+ if (ip === "::1") return true;
16
+ if (isIP(ip) === 4) return ip.startsWith("127.");
17
+ if (!ip.startsWith("::ffff:")) return false;
18
+
19
+ const [high, low] = ip.slice("::ffff:".length).split(":");
20
+ return Number.parseInt(high, 16) >>> 8 === 127 || (high === "0" && Number.parseInt(low, 16) >>> 8 === 127);
21
+ }
22
+
23
+ export function httpProtocolForHost(host: string, allowInsecureHttp = false): "http" | "https" {
24
+ return isLoopbackHost(host) || allowInsecureHttp ? "http" : "https";
25
+ }