Skip to content

fix(compose): map the EVENT_LOOP_MONITOR_* knobs into the api service (#3236) - #3238

Merged
ToddHebebrand merged 2 commits into
LanternOps:mainfrom
bdunncompany:fix/3236-event-loop-monitor-compose
Aug 8, 2026
Merged

fix(compose): map the EVENT_LOOP_MONITOR_* knobs into the api service (#3236)#3238
ToddHebebrand merged 2 commits into
LanternOps:mainfrom
bdunncompany:fix/3236-event-loop-monitor-compose

Conversation

@bdunncompany

@bdunncompany bdunncompany commented Aug 8, 2026

Copy link
Copy Markdown
Collaborator

Fixes #3236.

All three vars are documented as working knobs in apps/docs/src/content/docs/deploy/environment.mdx:207-209 and read at apps/api/src/services/eventLoopMonitor.ts (:71, :210, :497), but appeared in neither compose file's api environment: block. Compose only interpolates what is listed there, so setting them in .env was a silent no-op.

Mapped with the same ${VAR:-} shape as the DB_POOL_HEALTH_* block directly above them, in both docker-compose.yml and deploy/docker-compose.prod.yml.

Verified end-to-end, not just eyeballed

docker compose config against an .env that sets all three, rendering the api service:

docker-compose.yml deploy/docker-compose.prod.yml
before (a3dc568) {} {}
after INTERVAL_MS=2000, STARVATION_WARN_MS=5000, DISABLED=1 same

vitest run src/config/ — 8 files, 303 tests, all passing (includes envComposeParity and composeBindMounts).

One correction to the issue

The issue says the vars are documented in ".env.example / deploy/.env.example". Only the root .env.example documents them (lines 452-454); deploy/.env.example has no EVENT_LOOP references at all. I did not add them there, because DB_POOL_HEALTH_* sets the precedent in the other direction — mapped in deploy/docker-compose.prod.yml, absent from deploy/.env.example. Happy to add a doc block to the droplet env example if you'd rather have parity there.

Why the parity guard didn't catch this

Worth flagging, because it is the actual root cause and it is not specific to these three vars.

apps/api/src/config/envComposeParity.test.ts builds its list of documented variables with:

const m = /^([A-Z][A-Z0-9_]*)=/.exec(line); // uncommented assignments only

The EVENT_LOOP vars are documented the way this repo documents optional knobs — commented out, showing the default:

# EVENT_LOOP_MONITOR_INTERVAL_MS=1000
# EVENT_LOOP_STARVATION_WARN_MS=1000
# EVENT_LOOP_MONITOR_DISABLED=

So the guard never considered them. That is a structural blind spot aimed squarely at the class of variable most likely to be forgotten: optional tuning knobs nobody sets on day one. DB_POOL_HEALTH_* is commented out too and only got wired because #3224 did it by hand.

Sweeping both pairs for commented-only vars that reach no container: 34 in the root pair, 5 in the droplet pair. Not all are bugs — several are legitimately host-level or web build-time — but each needs a map-or-allowlist decision, which is why I have not folded that into this PR. Filed as #3239 with the full list and the two policy questions it raises.

…LanternOps#3236)

`EVENT_LOOP_MONITOR_INTERVAL_MS`, `EVENT_LOOP_STARVATION_WARN_MS` and
`EVENT_LOOP_MONITOR_DISABLED` are documented as working tuning knobs in
deploy/environment.mdx and read at services/eventLoopMonitor.ts, but neither
compose file listed them in the api service's `environment:` block. Compose only
interpolates variables named there, so setting any of the three in `.env` under
the standard flow did nothing, with no warning.

Detection is unaffected — the monitor runs on its defaults regardless — so this
only ever blocked tuning. But an operator raising the warn threshold to cut
noise, or setting DISABLED to turn the monitor off, saw no change and no error
and would reasonably conclude the knob was broken.

Mapped with the same `${VAR:-}` shape as the DB_POOL_HEALTH_* block directly
above, in both docker-compose.yml and deploy/docker-compose.prod.yml.

Verified with `docker compose config` against an .env setting all three: before,
the rendered api service has no EVENT_LOOP keys at all; after, it carries
INTERVAL_MS=2000, STARVATION_WARN_MS=5000 and DISABLED=1. Confirmed separately
for both compose files.

Note on why the existing guard missed this: envComposeParity.test.ts extracts
`.env.example` names with `/^([A-Z][A-Z0-9_]*)=/`, which matches uncommented
assignments only. These three are documented as commented-out lines showing
their defaults — the idiomatic way this repo documents optional knobs — so the
parity check never considered them. Filed separately; not fixed here, because
closing that gap needs a map-or-allowlist decision on each of the ~39 other
commented-only variables it would surface.
@bdunncompany

Copy link
Copy Markdown
Collaborator Author

The Trivy Filesystem Scan red here is not from this PR — its diff is two YAML files and touches no lockfile or manifest.

The blocking step reports 3 HIGH findings that arrived with a vulnerability-DB update: nanoid CVE-2026-67213 (fixable) and two image-size DoS advisories with no fixed version. It fails on every PR and will fail main's next security run.

Fixed in #3240, which I verified locally against the CI-pinned Trivy: control tree exits 1 with the same 3 findings, that branch exits 0. This PR should go green once #3240 lands and it is rebased.

ToddHebebrand added a commit that referenced this pull request Aug 8, 2026
…oS CVEs (#3241)

Unblocks the PR queue. Trivy's vulnerability DB refreshed on 2026-08-08
and three new HIGH findings began failing **`Trivy Filesystem Scan` on
every PR resolved after the refresh** — currently #3235 and #3238.

**This is not caused by any of those PRs.** #3232 was scanned *before*
the refresh and passed, which makes the failure look selective; it
isn't, and it will redden `main` on the next push regardless of what
gets merged. Exact repeat of the CVE-2026-59870 (js-yaml) episode in the
previous release round.

## The three findings, and why they're handled differently

| CVE | Package | Fix available? | Ships to users? | Action |
|---|---|---|---|---|
| CVE-2026-67213 | `nanoid` 3.3.16 | **Yes** — 3.3.17 / 5.1.6 | **Yes**
— runtime dep of apps/mobile | pnpm override |
| CVE-2025-71329 | `image-size` 1.2.1 | **No** | No — build-time only |
`.trivyignore` |
| CVE-2025-71330 | `image-size` 1.2.1 | **No** | No — build-time only |
`.trivyignore` |

### nanoid — fixed properly

Reached via `@react-navigation/native@7.3.8`, which is a **runtime**
dependency of `apps/mobile`, so this one genuinely ships to devices.
Override is upper-bounded `>=3.3.17 <4.0.0` so it stays a 3.3.16 →
3.3.17 patch bump rather than resolving to 6.x and dragging
react-navigation through a major — the same bounding rationale used for
the js-yaml override.

The tree's other copies (5.1.16, 6.0.0) are already past the advisory's
fixed versions and are untouched. **Lockfile diff is 2 lines**,
`nanoid@3.3.16` removed:

```
$ grep -oE "nanoid@[0-9.]+" pnpm-lock.yaml | sort -u
nanoid@3.3.17
nanoid@5.1.16
nanoid@6.0.0
```

### image-size — ignored, with justification

There is **no fixed version upstream**. Trivy reports both as `affected`
with an empty "Fixed Version" column, so no override or upgrade can
close them today.

Sole dependent is `metro@0.84.4` — the React Native bundler. Confirmed
nothing else in the tree pulls it. metro runs on developer machines and
in CI, never in a shipped image and never on a server or customer
device. Both CVEs are DoS via a crafted image (a malformed ICNS for
-71330) parsed by `image-size`; reaching that path means feeding a
hostile image to our own bundler during our own build, i.e. the attacker
already controls the source tree.

The `.trivyignore` entry records the dependent, the non-exploitability
argument, and explicit removal criteria — matching the justification
standard the file already sets for its existing `node-ip` and pnpm
blocks. **All 9 pre-existing entries are preserved**; this appends 2.

## Release note

Worth a line in the v0.104.0 notes alongside the js-yaml entry: nanoid
is a real (if low-severity) fix that reaches the mobile app; image-size
is a documented, non-shipping accept.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Todd Hebebrand <todd@lanternops.io>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
@ToddHebebrand
ToddHebebrand merged commit 32e1803 into LanternOps:main Aug 8, 2026
54 checks passed
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.

[API] EVENT_LOOP_MONITOR_* env vars are documented but not mapped in compose — setting them silently does nothing

2 participants