Feat/runtime pinning and auth hardening - #328
Merged
AbelOsaretin merged 4 commits intoJul 30, 2026
Merged
Conversation
app.listen() had no error handling, so a port conflict surfaced as an uncaught EADDRINUSE with a raw stack trace. Attach an error handler that maps EADDRINUSE, EACCES and EADDRNOTAVAIL to a readable explanation, logs it through the structured logger, and exits 1 so process supervisors treat the start-up as failed. Closes Heliobond#206
The project targets Node.js 20 (README, Dockerfile) but nothing enforced it: no engines field, no .nvmrc, and setup-bun ran unpinned in CI. Add engines.node >=20.0.0 and engines.bun >=1.0.0, an .nvmrc for nvm users, and pin bun-version: "1.x" on every setup-bun step so CI, release and the security audit all resolve the same runtime. Closes Heliobond#207
helmet already covered the header set, but frameguard was configured as SAMEORIGIN. This service is a JSON API and is never framed, and the CSP already declares frame-ancestors 'none', so DENY is both stricter and consistent with the policy already advertised. Closes Heliobond#208
The admin auth middleware compared the Authorization header to the API key with !==, which short-circuits on the first differing byte and leaks how many leading characters matched. Add timingSafeCompare, which hashes both values to a fixed 32-byte digest before crypto.timingSafeEqual so neither the contents nor the length of the token affect the comparison time. The auth flow and both error responses are unchanged. Closes Heliobond#209
|
@Joycejay17 Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Startup error handling, runtime pinning, and auth/header hardening
Description
Four scoped fixes, one commit each.
#206 —
app.listen()had no error handling. A port conflict crashed with a rawError: listen EADDRINUSE: address already in use :::3001stack trace. Aserver.on("error", …)handler now maps the bind failure to a readable message and exits
1, so Docker/systemd/k8s see afailed start rather than an opaque crash:
EACCES(privileged port) andEADDRNOTAVAILget their own messages; anything else falls back toFailed to bind to port <port>: <message>. The logic lives insrc/lib/listen-errors.tswith aninjectable
exitso it is unit-testable without killing the test runner.#207 — the runtime version was unenforced. README and the Dockerfile say Node.js 20, but there was
no
enginesfield, no.nvmrc, andoven-sh/setup-bun@v2ran unpinned in CI. Addedengines.node: ">=20.0.0"+engines.bun: ">=1.0.0", an.nvmrcpinning20, andbun-version: "1.x"on everysetup-bunstep (ci.yml,release.yml,security-audit.yml) so allworkflows resolve the same runtime.
#208 —
X-Frame-OptionswasSAMEORIGIN. helmet was already wired up insrc/middleware/securityHeaders.tsand covers the rest of the required header set, so the only gapagainst the issue was frameguard. Switched to
DENY: this is a JSON API that is never framed, and theCSP already declares
frame-ancestors 'none', soDENYmatches the policy already advertised.#209 — the admin bearer token used
!==. String comparison short-circuits on the first differingbyte, leaking how many leading characters matched and allowing byte-by-byte recovery of the key. The
middleware now uses
timingSafeCompare(src/lib/timing-safe.ts), which SHA-256s both values to afixed 32-byte digest before
crypto.timingSafeEqual— so neither the content nor the length of thesupplied token affects the comparison time. The auth flow and both error responses (
500 server_misconfigured,401 unauthorized) are unchanged.Type of change
Testing checklist
New suites —
src/__tests__/listen-errors.test.ts,timing-safe.test.ts,runtime-version-pinning.test.ts— plus additions toadmin.test.ts(near-miss tokens: differingfinal character, prefix of the key, extension of the key) and
securityHeaders.test.tsupdated toassert
DENY. 34 tests across the four touched suites, all green and stable across repeated runs.Verified the
EADDRINUSEpath end to end against a real occupied port: the message above is loggedand the process exits
1.npx eslintreports 0 errors on every touched file andprettier --checkis clean.Pre-existing failures, untouched by this PR
bun run buildand parts of the suite were already red onmainbefore these changes, and this PRneither fixes nor worsens them — the set of failing suites is byte-identical with the changes stashed:
src/lib/registry.ts:84,86— twoTS2339errors againstSimulateTransactionResponse(Stellar SDKtyping drift). These fail
tsconmain.admin.test.tsunderPOST /update-scores, plus 19 other suites(
typescript-strict,error-response-consistency,routes,prometheus-metrics, …).Happy to fold a fix for the
registry.tstyping into this branch or a separate PR if you'd prefer —left alone here to keep each commit scoped to its issue.
Related issues
Closes #206
Closes #207
Closes #208
Closes #209
Screenshots (if applicable)
n/a