chore(security): remove brand-owned DNS domains from source and shipped artifacts (1.4.1) - #335
Conversation
… artifacts The published 1.4.0 tarball carried brand-owned apex hostnames. This replaces every occurrence with neutral placeholders, or makes the value configuration- driven where it is a real runtime endpoint. - connectors/zendesk: the remote API URL had a hardcoded deployment host as its fallback default. There is now no shipped default; the value comes from ZENDESK_REMOTE_API_URL or `config set-remote-url`, and getRemoteApiUrl() throws an actionable error when unset. `config show` uses a new findRemoteApiUrl() so it still renders "not set" instead of throwing. - connectors/zendesk nginx templates, Makefile, README, CLAUDE.md, SCAFFOLD.md: placeholders and env-var references instead of a real deployment host. The Makefile deploy banner reads an overridable REMOTE_API_URL. - connectors/googlephotos, connectors/yousearch: illustrative comment/example values replaced with reserved example values. - SECURITY.md: vulnerability reports now route through GitHub Security Advisories rather than an email address at an owned domain. - package.json: mark @hasna/events external in the CLI bundle. One owned name reached bin/index.js only as a bundled dependency literal with no occurrence in this repo's source, so every source-level grep passed while the shipped artifact carried it. It is a declared runtime dependency, so this matches the existing ink/react/chalk/conf externals and keeps the events commands working. Verified against the packed tarball rather than the working tree: npm pack, extract, then scan every extracted member against the full owned-domain portfolio. 1.4.0 tarball: 10 distinct owned hostnames in 12 files. 1.4.1 candidate tarball: zero. Bumps to 1.4.1.
… resolution Addresses two reviewer findings on the owned-hostname scrub. SECURITY.md pointed at the GitHub Security Advisories form while private vulnerability reporting was disabled on the repo, so an external reporter following the link got a 404 and the removed security@ address left no fallback. Private vulnerability reporting is now enabled on hasna/connectors, which makes the linked URL resolve and renders the public report button; the policy now states that any signed-in GitHub user can file, and adds a no-details-in-public fallback for the case where the form is unreachable. getRemoteApiUrl() changed from returning a hardcoded default host to throwing when unconfigured, but no test referenced it, so the suite was byte-identically green with the change reverted. Adds four tests that import the real config module rather than re-implementing the priority logic, isolated through the module's own profile override: unconfigured throws and findRemoteApiUrl() is undefined, env var resolves, stored config resolves, env beats stored config. Verified the unconfigured test fails on regression: reinstating a hardcoded default in getRemoteApiUrl() turns it red (16 pass / 1 fail), and reverting config.ts wholesale fails the file at import. Suite: 3973 pass, 0 fail, 35200 expect() calls (bun test, after bun run build as CI does). Typecheck clean.
The afterEach resolved the throwaway profile directory through getConfigDir(), which only returns the test profile while the override is still set. Reordering those two statements would have pointed the rmSync at the developer's real default profile. Derive the path by name from getBaseConfigDir() instead, so cleanup targets the test profile regardless of sequencing. Suite: 3973 pass, 0 fail, 35200 expect() calls (bun test). Typecheck clean.
An adversarial review returned BLOCK-PUBLISH on the scrub commit. The scrub itself was confirmed clean by an independent, more sensitive scanner; these are the three defects it found alongside it, two of which the scrub introduced. Three commands crashed instead of reporting an unset value. Removing the remote API URL default left `remote url`, `remote status` and `remote health` calling the throwing accessor outside their try blocks, so they exited with a raw stack trace. `remote url` was the worst of them: its whole job is to tell you whether the URL is set. They now go through a helper that reports the missing configuration using the connector's own error convention, and `remote url` reports "not set" like `config show` already did. Added a test that runs the real CLI against a throwaway HOME; it fails on the pre-fix code (3 of 7) and passes after, so it is a regression guard rather than decoration. The new disclosure link was dead. SECURITY.md now routes reports to GitHub Security Advisories, but private vulnerability reporting was disabled on the repository, so the link 404s for anyone who is not a collaborator — the change had removed the only working disclosure channel. Reporting is now enabled. Also corrected two stale claims in the same file: the supported-version table still said 0.2.x, and the credential path did not match what the code uses. The scanned artifact was not the artifact that would ship. `prepublishOnly` runs the test suite before npm packs, and the suite creates per-connector `.test-home` sandboxes and lockfiles that `files: ["connectors/"]` then sweeps in — 1.4.0 shipped a Bun install-cache blob exactly this way. `files` now carries explicit negations, verified by packing after a full test run. `scripts/check-package-secrets.ts` scanned `git ls-files`, so it structurally could not see those untracked-but-shipped files; a lockfile carrying an auth token would have passed. It now scans the union of tracked and packed files, and fails loudly rather than degrading if the pack list cannot be read. Adds the 1.4.1 changelog entry.
Published as 1.4.1 — verification updateThe adversarial review on this branch returned BLOCK-PUBLISH. All three blockers are closed; publish happened after that, not before. Two corrections to my original description above, for the record:
Blockers closed
Also landed: Branch note: two commits (08e4905, cb45d35) landed here from another session mid-task. I rebased onto them, not over them — both intact, and their better SECURITY.md wording kept. They had not fixed the crash above. Final gates: Not fixed here, needs an owner decision — the exposure is wider than this package: it spans 0.4.1..1.4.0 (102 of 137 published versions, several carrying more than 1.4.0 did), and 13 of 15 sampled public |
Adversarial review of this branch found the Security entry overclaiming. It said "hostnames removed" and "every occurrence is gone", but two classes are untouched: - the externalized runtime dependency still carries the literal in its own published package, so an install still puts an owned domain on disk; - deployment resource identifiers, and the pattern they are named by, are unchanged from 1.4.0 and still ship. A shipped Makefile two lines from an edit this branch made still names one. What the branch does achieve is real and independently reproduced: zero owned DNS domains across every member of the packed tarball, with the leaky predecessor as a positive control over a dataset of the same size. Narrowing the wording keeps that result honest instead of letting it read as a wider guarantee a consumer would then rely on. Documentation only. No code, no behaviour change.
What
@hasna/connectors@1.4.0shipped to public npm carrying brand-owned apexhostnames. This removes every occurrence from source and from everything that
ships, and bumps to 1.4.1. Ship-forward was chosen deliberately over unpublishing
1.4.0; 1.4.0 will be deprecated on npm pointing at 1.4.1.
Positions only below — no owned value appears in this description, the commit
message, or the diff.
How each site was handled
connectors/zendesk/src/utils/config.tsgetRemoteApiUrl()throws an actionable error namingZENDESK_REMOTE_API_URLandconfig set-remote-url; a newfindRemoteApiUrl()returnsundefinedfor callers that must not throw.connectors/zendesk/src/cli/index.tsconfig showswitched tofindRemoteApiUrl()so it rendersnot setinstead of throwing; option description and a section comment now name the env var.connectors/zendesk/nginx.conf,nginx-connector.confconnectors/zendesk/MakefileREMOTE_API_URL ?=; the deploy banner prints it or an explicit not-set marker.connectors/zendesk/README.md,CLAUDE.md,SCAFFOLD.mdconnectors/googlephotos/src/cli/index.tsconnectors/yousearch/README.mdSECURITY.mdpackage.json@hasna/eventsmarked external in the CLI bundle (see below), version → 1.4.1.The one that source review could not have caught
One owned name reached
bin/index.jsonly as a literal inside a bundledruntime dependency. It has zero occurrences in this repo's tracked source, so
every source-level grep passes while the shipped artifact carries it. This is the
same failure mode as the
@hasna/tenantsincident: the compiled artifact carriedwhat the source never did.
Fix: mark
@hasna/eventsexternal in the CLI build. It is a declared runtimedependency, so this matches the existing
ink/react/chalk/confexternals and npm resolves it at install time. Verified the built CLI still runs
and still registers its commands.
The literal remains present in that dependency's own published package, which is
a separate remediation, tracked separately.
Verification — packed tarball, not the working tree
The check that matters was run against
npm packoutput, extracted, with everymember scanned against the full owned-domain portfolio taken from the domains CLI
(1043 apexes after excluding IANA-reserved names):
Independent sweeps over the 1.4.1 tarball, all files including binaries: zero
hits for escaped-dot, percent-encoded,
[dot]-obfuscated and base64 renderingsof the owned names.
Also confirmed the tarball did not lose shipped content. Diff of member lists
1.4.0 → 1.4.1 shows three removals, all of them fixes:
.d.tsfiles whose sources were deleted in bd03877 — 1.4.0 shippeddeclarations for code that no longer exists, because
dist/was not clean atrelease time;
.test-home/, sweptinto 1.4.0 by
files: ["connectors/"].Tests
connectors/zendeskis excluded from the root typecheck and carries 31pre-existing errors. Verified the error set is byte-identical before and after
this change once line numbers are normalised — no regression introduced, none of
them touch the edited code.
Gate:
contracts artifact-scan(hasna/contracts PR #31)Run as a backstop, not as the check. Result is worth recording:
It fails both, identically, on this package's legitimate connector catalog —
not on the owned names. Host count is unchanged by the scrub; domain count moves
by one. So for this repo the gate currently cannot distinguish leaky from clean
in either direction, and would have to be waived to be usable — at which point it
reports nothing. This is consistent with
CONTRACT.md's own statement thatclause B is a prohibition rather than a count; it is a limitation, not a bug.
Follow-ups (not in this PR)
connectors/zendeskdocs also name internal AWS EC2 / RDS / S3 resourceidentifiers in a public package. Not domains, so out of this PR's mandate, but
the same class of exposure and worth a decision.
connectors/shipping gitignored.test-home/cache blobs is a packaginghygiene gap that
files: ["connectors/"]will keep re-introducing.Need help on this PR? Tag
@codesmith-botwith what you need. Autofix is disabled.