Dependabot opened two High-severity alerts on lodash 4.17.23
arriving transitively via orval 7.x → @stoplight/spectral-* →
lodash 4.17.23:
#19 — CVE-2026-4800 / GHSA-r5fr-rjxr-66jc:
_.template imports key names → Function() constructor sink
→ arbitrary-code execution at template compile time
#18 — Prototype pollution via array path bypass in _.unset / _.omit
Both alerts are tagged "Development dependency" by Dependabot —
lodash is only pulled by orval (the Phase 5 API client codegen)
and doesn't reach the production-served bundle. The risk is build-
time RCE during `npm run generate` against untrusted input or a
polluted Object.prototype. Worth fixing regardless.
Fix: add `"lodash": ">=4.18.0"` to the existing `overrides` block
in web/package.json. Force npm to dedupe every transitive lodash
edge onto the top-level 4.18.1 already resolved at the root.
Pre-fix lockfile state (web/package-lock.json):
node_modules/lodash → 4.18.1
node_modules/@stoplight/spectral-functions/node_modules/lodash → 4.17.23
node_modules/@stoplight/spectral-rulesets/node_modules/lodash → 4.17.23
Post-fix:
node_modules/lodash → 4.18.1
(the two nested copies are gone — deduplicated under the override)
Verification:
cd web
npm install --package-lock-only --no-audit
node -e "const lock = require('./package-lock.json');
for (const [k,v] of Object.entries(lock.packages||{}))
if (k.includes('lodash') && !k.includes('lodash.'))
console.log(k, v.version)"
→ node_modules/lodash 4.18.1 (only one entry)
npm audit
→ found 0 vulnerabilities
Lockfile delta is -14 / +0 (the two nested 4.17.23 copies removed,
no new entries needed since 4.18.1 was already resolved at the root).
The `"lodash": "^4.17.21"` / `~4.17.21` requirements declared by
@stoplight/spectral-functions, spectral-rulesets, and orval itself
are still satisfied — `^4.17.21` accepts 4.18.x, and the override
forces every consumer to the same dedup'd version.
Lockfile-regen pattern lesson: per the standing rule from the
post-Phase-2 + post-Phase-5 lockfile-drift hotfixes, every commit
that edits web/package.json MUST regenerate web/package-lock.json
in the same commit via `npm install --package-lock-only --no-audit`.
This commit follows that rule.
Closes:
https://github.com/certctl-io/certctl/security/dependabot/19https://github.com/certctl-io/certctl/security/dependabot/18
Phase 3 added @playwright/test@^1.49.0 to web/package.json and
Phase 5 added orval@^7.0.0, both without regenerating
web/package-lock.json. CI's npm ci in both the Frontend Build job
and the Dockerfile frontend stage failed:
npm error Missing: @playwright/test@1.60.0 from lock file
npm error Missing: orval ... from lock file
Regenerate web/package-lock.json with:
cd web && npm install --package-lock-only --no-audit
(+6990 / -1893 lines — orval pulls a deep transitive graph). No
node_modules download required; lockfile-only mode keeps the
operation light. Verified clean with 'npm ci --dry-run' (612
packages would install).
Phase 2's SEC-H3 fail-closed branch (CERTCTL_DEMO_MODE_ACK_TS
required when CERTCTL_DEMO_MODE_ACK=true) broke four pre-existing
tests in internal/config/config_test.go that set DemoModeAck=true
without setting DemoModeAckTS:
TestValidate_AuthTypeNone_NonLoopback_AckPasses (l.722)
TestValidate_Bundle2_PlaceholderAuthSecret_DemoAckExempt (l.1799)
TestValidate_Bundle2_PlaceholderEncryptionKey_DemoAckExempt (l.1832)
TestValidate_Bundle2_CORSWildcard_DemoAckExempt (l.1879)
Each test now sets DemoModeAckTS alongside DemoModeAck=true:
DemoModeAckTS: strconv.FormatInt(time.Now().Unix(), 10)
strconv + time were already imported in config_test.go. Verified
locally: 'go test ./internal/config/... -count=1' passes clean
(0.700s), gofmt clean, go vet clean.
Root cause was the sandbox 'disk-full' constraint that forced
deferring npm install to the operator's workstation — but CI runs
npm ci before any workstation operation. Lockfile-only regen
(this commit) is the right fix; works in low-disk environments
because no node_modules download happens.
Dependabot flagged four picomatch vulnerabilities in
web/package-lock.json:
#8 GHSA-?, ReDoS via extglob quantifiers
#9 GHSA-?, ReDoS via extglob quantifiers (related to #8)
#10 CVE-2026-33672 / GHSA-3v7f-55p6-f55p, method injection via
POSIX character classes (related; affecting < 2.3.2)
#11 CVE-2026-33672 / GHSA-3v7f-55p6-f55p, method injection via
POSIX character classes — same advisory as #10, separate
Dependabot row because it surfaces against a second copy
of picomatch in the dep tree
All four close on the same fix: every resolved picomatch instance
must be >= 4.0.4 (or >= 3.0.2, or >= 2.3.2 — the patch shipped on
all three release lines). Pre-fix the lockfile carried at least
two vulnerable copies:
node_modules/picomatch v2.3.1 (vuln)
node_modules/vitest/node_modules/picomatch v4.0.3 (vuln for #11)
node_modules/vite/node_modules/picomatch v4.0.4 (ok)
node_modules/tinyglobby/node_modules/picomatch v4.0.4 (ok)
Reachability check before fixing:
- picomatch is a build-time glob-matching tool (used by
tailwindcss → readdirp/anymatch/micromatch chain, plus by
vite + vitest internals).
- All instances in our tree are dev=true. None are bundled into
the React production output (web/dist/assets/*.js) — that's
just the React SPA, no node_modules at runtime.
- The CVE only affects code that processes UNTRUSTED glob
patterns. Our build pipeline only globs operator-controlled
file patterns (TSX source files, Tailwind 'content' globs).
Not network-reachable.
So the CVE was not reachable from any shipped certctl artefact.
Fix anyway because the alerts are noise.
Fix mechanism: add an npm 'overrides' entry pinning picomatch to
^4.0.4 across all consumers. npm collapses every transitive
picomatch resolution to the override, so the lockfile shrinks from
4 picomatch entries to 1, all on v4.0.4 (patched).
Verification:
npm install --package-lock-only → up to date, 0 vuln
npm audit → found 0 vulnerabilities
Diff: 2 files, 7 insertions / 43 deletions (net negative — the
override de-duplicates the picomatch tree).
Closes: GHSA-3v7f-55p6-f55p, CVE-2026-33672 (alerts #10, #11) +
the two related ReDoS picomatch alerts (#8, #9)
Closes Dependabot alerts #12 (CVE — arbitrary file read via Vite dev
server WebSocket), #13 (CVE-2026-39364 — server.fs.deny bypassed with
?raw / ?import&raw / ?import&url&inline query suffixes), and #14 (path
traversal in optimized-deps .map handling). All three live in the vite
DEV server only — vite build (production output) is unaffected. All
three share the same advisory range '>= 8.0.0, <= 8.0.4' → fixed in
8.0.5; npm picked the latest 8.x patch (8.0.10).
Real-world exposure for certctl was low: web/package.json's 'dev: vite'
script has no --host flag, so the default binding is localhost
(127.0.0.1). Devs who manually run 'vite --host' for cross-machine
testing were exposed to the same-LAN attack vector; this closes it.
Manifest change: bumped the constraint from '^8.0.0' to '^8.0.10' to
document the security floor in package.json itself (the caret already
permitted 8.0.10, but pinning the floor higher prevents an accidental
downgrade if a future 'npm install' somehow re-resolves to a vulnerable
8.0.0-8.0.4). Lockfile change: 17 packages removed + 18 changed —
mostly transitive vite-internal modules (rolldown, oxc-* etc.) that
shifted around between 8.0.0 and 8.0.10.
Verified locally:
- 'npm install vite@^8.0.5 --save-dev' completed cleanly.
- 'vite build' produces the same web/dist/ output (668 modules
transformed, 35.30 kB CSS / 918.04 kB JS — same shape as pre-
upgrade).
- vitest run wasn't completed in the sandbox (test runner hung in
the disk-pressure environment); CI will run it on push.
Engineering history: this is a cross-cutting deps bump that lives
outside the ACME-Server-N phase plan.
Backend hardening:
- Fix 6 nginx.go non-constant format string build errors
- Add validation.go with hostname, PEM, and enum validators
- Apply input validation to all POST/PUT handlers (certificates,
agents, CSR, policies, teams, owners, targets, issuers)
- Fix unchecked JSON decode in TriggerDeployment handler
Frontend (Vite + React + TypeScript):
- Migrate from single-file SPA to proper build pipeline
- 7 pages: Dashboard, Certificates (list+detail), Agents, Jobs,
Notifications, Policies, Audit Trail
- TanStack Query for server state with auto-refetch intervals
- Certificate detail with version history and renewal trigger
- Job cancellation, status/type filtering, expiry countdowns
- Reusable components: DataTable, StatusBadge, ErrorState, PageHeader
- Dark theme with Tailwind CSS, sidebar nav via React Router
Server integration:
- Go server serves web/dist/ (Vite output) with SPA fallback
- Falls back to web/index.html for legacy mode
- .gitignore updated for web/node_modules/ and web/dist/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>