Skip to content

fix(domain-updater): don't clobber existing SSL data with empty values#90

Open
firstlayerdigital wants to merge 1 commit into
lissy93:mainfrom
firstlayerdigital:fix/ssl-updater-empty-value-clobber
Open

fix(domain-updater): don't clobber existing SSL data with empty values#90
firstlayerdigital wants to merge 1 commit into
lissy93:mainfrom
firstlayerdigital:fix/ssl-updater-empty-value-clobber

Conversation

@firstlayerdigital

Copy link
Copy Markdown

Summary

  • When the SSL cert fetch in domain-info fails for a domain (e.g. an apex with no A record, so the tls.connect() call rejects with No address associated with hostname), the endpoint returns empty strings for all SSL fields.
  • The updateSSL INSERT path handles this fine — it coerces "" to NULL via toDateOnly(x) || null, so the first run quietly inserts a row of nulls.
  • Every subsequent run then crashes in the UPDATE path, because the code pushes empty strings to ::date casts, which Postgres rejects with invalid input syntax for type date: "".
  • Secondary (same root cause): the text/int path can clobber a previously-good issuer / subject / fingerprint / key_size with "" / 0 when a fetch partially fails — field.new ?? null falls through to the empty string because ?? doesn't catch empty strings.

The bug in practice

First run (INSERT), fine:

"domain":"apex-with-no-a-record.com","changes":["SSL created"]

Second run (UPDATE), crashes:

"domain":"apex-with-no-a-record.com","changes":["SSL Valid From","SSL Valid To","(⚠️ Error in updateSSL: pgExecutor HTTP 500: { \"error\": \"invalid input syntax for type date: \\\"\\\"\" })"]

Root cause walkthrough

In the UPDATE loop (src/server/routes/domain-updater/updateFns/ssl.ts):

if (field.type === 'date') {
  const oldDate = new Date(oldVal);
  const newDate = new Date(newVal);              // new Date("") -> Invalid Date
  const diffInMs = Math.abs(newDate.getTime() - oldDate.getTime()); // NaN
  const diffInDays = diffInMs / (1000 * 60 * 60 * 24);              // NaN
  if (isNaN(diffInDays) || diffInDays > 1) {                        // true
    updateSet.push(`${field.column} = \$${updateSet.length + 2}::date`);
    updateValues.push(toDateOnly(newVal));       // "" goes in as $N
    // ... later: `UPDATE ... SET valid_from = $2::date ...` with $2 = ""
  }
}

Postgres then rejects the ""::date cast.

Fix

Skip the iteration entirely when newVal is empty (applies to dates, text, and int uniformly), and add an Invalid-Date guard in the date branch:

// If the fresh fetch didn't produce a value for this field (e.g. the SSL
// fetch failed upstream), don't clobber existing data with an empty string.
// For date columns this also prevents `""::date` casts which Postgres rejects.
if (!newVal) continue;

if (field.type === 'date') {
  const newDate = new Date(newVal);
  if (isNaN(newDate.getTime())) continue;
  const oldDate = new Date(oldVal);
  // ... rest unchanged
}

This preserves existing SSL data when fetches partially fail, which matches the behavior a domain portfolio tracker should have — stale cert info is more useful than blanked-out cert info.

Note: field.new ?? null on line 96 still falls through to "" for empty strings (since ?? only catches null/undefined), but the new if (!newVal) continue; guard above now prevents that path from being reached with an empty value. Left the existing ?? in place to minimize diff; could be tightened to field.new || null in a follow-up if you want belt-and-suspenders.

Test plan

  • Repro: self-hosted instance (DL_ENV_TYPE=selfHosted), add an apex domain with no A record, hit /domain-updater twice. Before: crashes on second run with invalid input syntax for type date: "". After: returns "No changes, all data is up-to-date".
  • Verified the patched compiled .mjs bundle runs on CT 113 (Node 22, Postgres 17) against 6 real domains including one apex with no A record; the other 5 domains continue to pick up legitimate SSL field changes (e.g. SSL Key Size diffs) without regression.
  • ng test — the existing test suite targets Angular components (src/app/app.component.spec.ts) and doesn't cover the server-side updateFns/, so there's nothing to run against this change. Happy to add a unit test for updateSSL if you'd like.

Unrelated note (separate from this PR)

The community-scripts/ProxmoxVE install script for Domain Locker does not install the whois system package. This breaks WHOIS lookups for .io, .app, and some other TLDs that the whois-json npm lib doesn't handle — the native-binary fallback in src/server/utils/whois.ts silently fails because whois isn't in PATH. I'll file that separately on community-scripts.

When the SSL cert fetch in domain-info fails (e.g. an apex domain with no
A record, so TLS connect to :443 errors out), the endpoint returns empty
strings for all SSL fields (issuer: "", valid_from: "", valid_to: "",
fingerprint: "", etc.).

The INSERT path in updateSSL correctly coerces empty strings to NULL via
`toDateOnly(x) || null`, so the first run succeeds with a row of nulls.
But on every subsequent run the UPDATE path hits two problems:

1. The date-comparison block runs `new Date("")` -> Invalid Date,
   `diffInDays` becomes NaN, the `isNaN(diffInDays) || diffInDays > 1`
   check passes, and the code pushes `toDateOnly("")` (empty string) as
   a bound parameter with a `::date` cast. Postgres rejects with
   `invalid input syntax for type date: ""`, which bubbles up as
   `pgExecutor HTTP 500` and ends up in the changes array as an error.

2. For text/int fields, `oldVal !== newVal` is true when a previous
   successful fetch populated e.g. `issuer = 'Let's Encrypt'` and the
   current fetch returned "". The code then pushes `field.new ?? null`
   which falls through to `""` (since `??` doesn't catch empty strings),
   silently overwriting good historical data with empty.

Fix: skip the iteration entirely when the fresh value is empty. Also
guards the date path against Invalid Date even if upstream starts
sending garbage like "not a date". This preserves existing data when
fetches partially or fully fail, which matches the behavior a domain
portfolio tracker should have (stale cert info is more useful than no
cert info).

Repro: self-hosted instance (DL_ENV_TYPE=selfHosted), add a domain whose
DNS only has subdomain records (no apex A record), then hit
/domain-updater twice. First run inserts nulls, second run returns
`"(⚠️ Error in updateSSL: pgExecutor HTTP 500: ... invalid input syntax
for type date: \"\")"` in that domain's changes list.
@vercel

vercel Bot commented Apr 21, 2026

Copy link
Copy Markdown

@firstlayerdigital is attempting to deploy a commit to the AS93 Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant