{"record":{"id":"fecbfe393af3e702","repo":"different-ai/openwork","slug":"connect-link-claims-contain-a-non-https-url-ins","errorCode":null,"errorMessage":"connect-link claims contain a non-https URL: ${insecure}","messagePattern":"connect-link claims contain a non-https URL: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/connect-link/src/node.ts","lineNumber":59,"sourceCode":"  } catch {\n    return null\n  }\n}\n\nexport type SignConnectLinkTokenInput = {\n  claims: ConnectLinkClaims\n  privateKeyPem: string\n  kid: string\n  /** Permit non-https den/logo URLs (local development and evals only). */\n  allowInsecureUrls?: boolean\n}\n\nexport function signConnectLinkToken(input: SignConnectLinkTokenInput): string {\n  const claims = connectLinkClaimsSchema.parse(input.claims)\n  if (!input.allowInsecureUrls) {\n    const insecure = findInsecureConnectLinkUrl(claims)\n    if (insecure) {\n      throw new Error(`connect-link claims contain a non-https URL: ${insecure}`)\n    }\n  }\n  const header = { alg: CONNECT_LINK_ALGORITHM, typ: \"JWT\", kid: input.kid }\n  const signingInput = `${base64UrlEncode(JSON.stringify(header))}.${base64UrlEncode(JSON.stringify(claims))}`\n  // new Uint8Array(...) keeps the calls assignable across the @types/node\n  // versions in this workspace (Buffer's backing store is typed as\n  // ArrayBufferLike on older lib combinations).\n  const signature = sign(null, new Uint8Array(Buffer.from(signingInput, \"utf8\")), createPrivateKey(input.privateKeyPem))\n  return `${signingInput}.${signature.toString(\"base64url\")}`\n}\n\nexport type VerifyConnectLinkTokenInput = {\n  token: string\n  /** kid → SPKI PEM public key. Only keys in this map are trusted. */\n  publicKeys: Record<string, string>\n  nowEpochSeconds?: number\n  clockSkewSeconds?: number\n  /** Accept http URLs when every insecure target is loopback (dev only). */","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/packages/connect-link/src/node.ts#L41-L77","documentation":"signConnectLinkToken refuses to sign connect-link JWTs whose claims reference non-https URLs (e.g. the den or logo URL) unless allowInsecureUrls is explicitly set. The check runs findInsecureConnectLinkUrl over the parsed claims before signing. This prevents production connect links from pointing desktop clients at plaintext HTTP endpoints.","triggerScenarios":"Calling signConnectLinkToken with claims containing an http:// URL and no allowInsecureUrls flag; the thrown message names the offending URL.","commonSituations":"Local development against a localhost Den server over http; staging environments without TLS; forgetting to set allowInsecureUrls in dev/eval harnesses; a config or DB row holding an http:// den URL that leaked into production signing.","solutions":["Serve the Den (and any claim URLs) over https and update the claims to use https:// URLs","For local development/evals only, pass allowInsecureUrls: true to signConnectLinkToken","Audit the source of the claims (env var, config, DB) and correct the http:// value to https://","If the URL is loopback-only dev traffic, keep it out of production-signed tokens"],"exampleFix":"// before\nconst token = signConnectLinkToken({ claims: { den: 'http://den.internal' }, privateKeyPem, kid })\n// after\nconst token = signConnectLinkToken({ claims: { den: 'https://den.internal' }, privateKeyPem, kid })\n// or, local dev only:\nsignConnectLinkToken({ claims, privateKeyPem, kid, allowInsecureUrls: true })","handlingStrategy":"validation","validationCode":"function assertHttpsClaims(claims) {\n  for (const key of ['den', 'logo']) {\n    const url = claims[key];\n    if (typeof url === 'string' && !url.startsWith('https://')) {\n      throw new Error(`claim '${key}' must be https: ${url}`);\n    }\n  }\n}\n// run before signConnectLinkToken, or pass allowInsecureUrls: true in dev only","typeGuard":"function isHttpsUrl(u: unknown): u is string {\n  if (typeof u !== 'string') return false;\n  try { return new URL(u).protocol === 'https:'; } catch { return false; }\n}","tryCatchPattern":"try {\n  const token = signConnectLinkToken({ claims, privateKeyPem, kid })\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('connect-link claims contain a non-https URL')) {\n    // fix claims to https or set allowInsecureUrls for local dev\n  } else throw e\n}","preventionTips":["Default all claim URLs to https://","Keep http URLs confined to local dev with allowInsecureUrls: true","Validate URL schemes at config-load time, before signing","Never let http:// values from env/DB flow into production claims"],"tags":["security","jwt","https","connect-link"],"backgroundTag":"insecure-url-rejected","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}