Reviewed: 2026-03-25
Reviewer: Subagent (senior code review + QA)
-
SSE streaming: split on newline could break mid-chunk (
server.mjs)
The original code split SSE data on\nper chunk. If a JSON payload was split across two TCP chunks,JSON.parsewould silently fail and the assistant response would be partially lost.
Fix: Added ansseBufferthat accumulates incomplete lines and only processes complete ones. -
Path traversal via session ID (
server.mjs,storage.mjs)
Session IDs came from user-controlled headers/query params with no sanitization.X-NeverForget-Session: ../../../etcwould escape the data directory.
Fix: AddedsessionId.replace(/[^a-zA-Z0-9_\-]/g, '_')in both server and storage. -
Non-chat endpoints returned 404 (
server.mjs)
The proxy only handled/v1/chat/completionsand/v1/stitcher/stats. Any other/v1/*endpoint (e.g./v1/models,/v1/embeddings) got a hard 404, breaking clients that probe the API.
Fix: AddedhandlePassthrough()that pipes any unrecognized/v1/*request to upstream. -
X-NeverForget-Sessionheader not recognized (server.mjs)
README documentedX-NeverForget-Sessionbut code only checkedx-stitcher-session.
Fix: Now checks bothx-stitcher-sessionandx-neverforget-session.
-
No body size limit on incoming requests (
server.mjs)
A malicious client could POST gigabytes and OOM the process.
Fix: Added 50MB body size limit with 413 response. -
Token estimation wrong for multi-byte characters (
engine.mjs)
text.length / chars_per_tokencounts UTF-16 code units, not bytes. Danish text (æ, ø, å) and emoji are 2-3 bytes but 1lengthunit, underestimating tokens.
Fix: Changed toBuffer.byteLength(text, 'utf-8') / chars_per_token. -
No response on upstream error status (
server.mjs)
Original only stored assistant messages on status 200. Now correctly checks200-299range. -
Non-JSON response collected via string concatenation (
server.mjs)
responseData += chunkon binary responses could corrupt data.
Fix: Changed toBuffer.concat(chunks)then.toString('utf-8'). -
Port-in-use crash with no useful message (
server.mjs)
Fix: Addedserver.on('error')handler withEADDRINUSEdetection. -
No graceful shutdown (
server.mjs)
Fix: Added SIGINT/SIGTERM handlers for clean shutdown. -
Math.max(...existingRolls, 0)crashes on empty array spread (storage.mjs)
Math.max(...[])returns-Infinity, which when +1 =-Infinity. This works by accident since0is also in the spread, but it's fragile.
Fix: Added explicit empty-check:(existingRolls.length > 0 ? Math.max(...existingRolls) : 0) + 1. -
Config saved with
upstreamkey but loaded asupstream_url(config.mjs)
This is a mismatch in the serialization —saveConfig()writesupstreambutloadConfigFile()readsdata.upstream. Surprisingly this works, but it meansconfig set upstream_url <val>updates the in-memory object but saves the wrong key. The config schema usesupstream_urlbut the file usesupstream.
Status: Left as-is since it's intentional (file format uses short names), but documented here as a potential confusion source.
-
Duplicate CLI file (
bin/neverforget.mjs)
Exact copy ofbin/neverforget. Not referenced inpackage.json.
Fix: Added to.npmignoreso it's excluded from the published package. -
.venv/directory would be included in npm pack
There's a Python virtualenv in the repo root that would balloon the package.
Fix: Added"files"field inpackage.json(whitelist approach) and.npmignore. -
Missing
enginesfield inpackage.json
Install script checks for Node 18+ butpackage.jsondidn't declare it.
Fix: Added"engines": { "node": ">=18" }. -
Error responses not JSON-formatted (
server.mjs)
Original returned plain text errors ("Invalid JSON body", "Not Found"). OpenAI-compatible clients expect JSON error objects.
Fix: All error responses now return{ "error": { "message": "...", "type": "..." } }. -
anthropic-versionheader not forwarded (server.mjs)
Anthropic API requires this header. The proxy stripped it.
Fix: Now explicitly forwardsanthropic-versionif present. -
Health check endpoint (
server.mjs)
AddedGET /andGET /healthreturning{ "status": "ok" }for monitoring. -
DRY: Extracted
_extractText()helper (engine.mjs)
Content extraction fromcontent(string or multipart array) was duplicated 6+ times.
Fix: Single helper function. -
Token budget reserves 5% for response (
engine.mjs)
Original used 100% ofmax_tokensfor context, leaving nothing for the model's reply.
Fix: Budget is nowmax_tokens * 0.95.
- Config key mismatch (
upstreamvsupstream_url): Works correctly, changing it would break existing configs. bin/neverforgetvsbin/neverforget.mjsduplication: Only excluded from npm; didn't deleteneverforget.mjsin case it's used locally.- No Anthropic Messages API support: The proxy only handles OpenAI-format
/v1/chat/completions. Anthropic's native/v1/messagesendpoint would need a separate handler. Passthrough covers it for now. - Synchronous file I/O in storage: For a local proxy this is fine. Async would only matter at high concurrency.
node bin/neverforget --help✅node bin/neverforget status✅ (with or without existing config)- Package structure verified:
filesfield ensures onlybin/neverforget,src/,README.md,LICENSEare published