docs: validated quality investigation reports with 10-agent audit

Validation results: 178/180 claims confirmed TRUE, 2 cleared as FALSE
- 01: D6 disputed (json.ts IS imported), routes count corrected
- 03: M8 (pickLock safe), M12 (404 already fixed)
- 06: C1 (has .catch), M3 (not swallowed), H1/M4 partial
- 08: M4 partial, L4 unverifiable
This commit is contained in:
youssefvdel
2026-06-11 17:38:47 +03:00
parent 1ee0e864ba
commit a4c075f843
11 changed files with 913 additions and 0 deletions
@@ -0,0 +1,83 @@
# Architecture & Code Organization Report
## Circular Dependencies
### C1. `services/auth.ts` ↔ `services/accountManager.ts`
- `auth.ts:10` imports from `./accountManager.ts`
- `accountManager.ts:9` imports from `./auth.ts`
- **Fix**: Extract shared types into a separate module, remove the mutual imports
- Dynamic imports at `accountManager.ts:195,242,265,274` and `auth.ts:100-101,105` are code smells
### C2. `services/auth.ts` ↔ `services/playwright.ts`
- `playwright.ts:4` imports from `./auth.ts`
- `auth.ts:10` imports from `./playwright.ts`
- Dynamic imports at `playwright.ts:253,291` hide the cycle
### C3. `services/logStore.ts` ↔ `services/systemLogger.ts`
- `logStore.ts:394` calls `__registerLogStore(logStoreInstance)`
- `systemLogger.ts:116-133` uses Proxy pattern that throws if logStore is accessed before init
- **Risk**: Runtime crash if import order changes
## Dead Code (Exported but Never Used)
| # | File | Symbol | Lines | Notes |
|---|------|--------|-------|-------|
| D1 | `src/routes/pipeline/StreamingContentFilter.ts` | Entire file | 141 | **Never imported anywhere** |
| D2 | `src/utils/tokenEstimator.ts` | `estimateTokensFast()` | 119 | Exported but never imported |
| D3 | `src/utils/tokenEstimator.ts` | `calculateTokenOverhead()` | 146 | Exported but never imported |
| D4 | `src/utils/xmlStripper.ts` | `stripStreamingDelta()` | 40 | Exported but never imported |
| D5 | `src/utils/xmlStripper.ts` | `repairMalformedJson()` | 71 | Exported but never imported |
| D6 | `src/utils/json.ts` | Entire file | 234 | **[DISPUTED]** — `parserHelpers.ts:3` imports `robustParseJSON` from `'../utils/json.ts'`. Not dead code. |
| D7 | `src/services/playwright.ts` | `loginToQwen()` | 349 | Never imported outside playwright.ts |
| D8 | `src/services/playwright.ts` | `getBrowserContext()` | 342 | Never imported anywhere |
| D9 | `src/services/playwright.ts` | `injectCookies()` | 328 | Never imported anywhere |
| D10 | `src/services/browserProfiles.ts` | `autoFillLogin()` | 261 | Never imported anywhere |
## Misnamed Modules
| File | Problem | Suggested Name |
|------|---------|----------------|
| `src/utils/auth.ts` | Contains API key auth (safeCompare), not Qwen auth | `src/utils/apiKeyAuth.ts` |
| `src/services/playwright.ts` | Named after library, actually browser session manager | `src/services/browserSession.ts` |
| `src/routes/chatHelpers.ts` | Contains core business logic, not helpers | `src/services/chatPipeline.ts` |
| `src/services/loginHelpers.ts` | Contains 3 complete login strategies | `src/services/loginStrategies.ts` |
| `src/services/networkDebug.ts` | Observability service, not debug | `src/services/networkObservability.ts` |
## Files > 400 Lines (Should Be Split)
| File | Lines | Suggested Split |
|------|-------|-----------------|
| `src/services/accountManager.ts` | 461 | Split into `encryption.ts`, `accountPicker.ts`, `accountWatcher.ts` |
| `src/services/playwright.ts` | 432 | Split context management from cookie/header handling |
| `src/services/logStore.ts` | 395 | Near threshold, clean up before adding features |
## Import Path Inconsistencies
5 files use `.js` extensions in imports (should be `.ts`):
- `src/services/auth.ts:11``'./logStore.js'`
- `src/services/sessionPool.ts:5``'./logStore.js'`
- `src/services/modelRouter.ts:7``'./logStore.js'`
- `src/services/logStore.test.ts:3``'./logStore.js'`
- `src/services/auth.test.ts:9``'./auth.js'`
## Duplicate Logic
| Pattern | Locations | Lines |
|---------|-----------|-------|
| `createFetchTimeout()` | `auth.ts:38`, `qwen.ts:93`, `qwenModels.ts:15` | 3 copies |
| `getSnapshotDelta()` | `chatHelpersCore.ts:87`, `StreamingContentFilter.ts:126` | 2 copies |
| `cleanThinkTags` regex | `chatHelpersCore.ts:119`, `thinkTagStripper.ts:36` | 2 copies |
| Mutex class | `playwright.ts:50`, `loginHelpers.ts:14` | 2 copies |
## Missing Barrel Exports
All directories lack `index.ts` barrel files:
- `src/services/` (20 files)
- `src/routes/` (14 files)
- `src/utils/` (9 files)
- `src/types/` (1 file)
- `src/tools/` (12 files)
## Orphaned Module
`src/routes/pipeline/StreamingContentFilter.ts` (141 lines) — complete file is never imported or referenced.
+98
View File
@@ -0,0 +1,98 @@
# Tool Calling Pipeline — Bug Report
## Critical
### C1. Entire `src/tools/parser.ts` Is Dead Code (329 lines)
- **File**: `src/tools/parser.ts`
- **Issue**: `StreamingToolParser` is only imported by `parser.test.ts`, never by production code
- **Impact**: 329 lines of maintenance burden. Production uses `xmlToolParser.ts` instead.
- **Fix**: Remove the file or wire it into production
### C2. Entire `src/tools/parserHelpers.ts` Is Dead Code (62 lines)
- **File**: `src/tools/parserHelpers.ts`
- **Issue**: `tryExtractToolCall` and `findBalancedJsonEnd` are never imported
- **Fix**: Remove or integrate
## High
### H1. Entire `src/tools/toolRunner.ts` Is Dead Code (149 lines)
- **File**: `src/tools/toolRunner.ts`
- **Issue**: `executeToolCalls`, `buildToolMessage`, `buildAssistantToolCallMessage`, `normalizeToolCalls`, `parseToolCallsFromContent` are never imported by production code
- **Fix**: Remove or wire up to a tool execution loop
### H2. `guard.ts` Dead Functions (80+ lines)
- **File**: `src/tools/guard.ts`
- **Issue**: `detectToolCallLoop`, `detectProviderToolLeak`, `validateToolCalls`, `buildCorrectionPrompt` are never called in production
- **Impact**: Important loop-detection logic not being used
### H3. Tool Call Index Collision in Streaming Path
- **File**: `src/routes/chatStreamingHelpers.ts:249`
- **Issue**: Tool call indices reset to `0` per chunk instead of being globally cumulative
- **Impact**: Clients (OpenAI SDK, Vercel AI SDK) overwrite tool calls from previous chunks
- **Fix**: Track a global counter `emittedToolCallCount + i` instead of `i`
### H4. `flush()` Ignores `passThrough` Flag
- **File**: `src/tools/parser.ts:273-282`
- **Issue**: `flush()` calls `flushStripXmlX()` even when `passThrough = true`
- **Impact**: Data corruption — tool calls extracted from raw passthrough text
- **Fix**: Add `if (this.passThrough) return { text: this.buffer, toolCalls: [], thinking: '' };`
## Medium
### M1. Buffer Trimming Failure When `offset` Goes Negative
- **File**: `src/tools/parser.ts:255-271`
- **Issue**: `compactBuffer()` can make `offset` negative → content never trimmed from buffer → O(n²) performance
- **Fix**: Handle negative offset with `offset = 0`
### M2. Unbounded Buffer Growth in `passThrough` Mode
- **File**: `src/tools/parser.ts:37`
- **Issue**: `passThrough` mode never calls `compactBuffer()` — buffer grows unboundedly
- **Fix**: Trim in `passThrough` mode too
### M3. Missing `crypto` Import in `xmlToolParser.ts`
- **File**: `src/tools/xmlToolParser.ts:87`
- **Issue**: `crypto.randomUUID()` used without import — relies on global `crypto` (Node 19+ only)
- **Fix**: Add `import crypto from 'node:crypto';`
### M4. `tryExtractToolCall` Hard-Coded 300-Character Lookback
- **File**: `src/tools/parserHelpers.ts:33`
- **Issue**: If JSON opening `{` is >300 chars before `"name"`, the tool call is silently dropped
- **Fix**: Increase lookback or scan for `{` globally
### M5. O(n²) XML Re-Parsing on Every Streaming Chunk
- **File**: `src/routes/chatStreamingHelpers.ts:232`
- **Issue**: `parseXmlToolCalls(state.lastFullContent)` re-parses the ENTIRE accumulated content (up to 100K chars) on every SSE chunk
- **Impact**: Significant CPU waste on long streams
- **Fix**: Parse incrementally from `lastParsedPosition`
### M6. Unbounded `state.lastVStrRaw` Growth
- **File**: `src/routes/chatStreamingHelpers.ts:223`
- **Issue**: `lastVStrRaw` has NO size limit (unlike `lastFullContent` with 100K cap)
- **Impact**: Memory leak on long streaming responses
- **Fix**: Apply similar size cap
### M7. `processToolCallsThroughGuard` Uses Hard-Coded `MAX_TOOL_CALLS_PER_TURN` (8)
- **File**: `src/routes/chatHelpersCore.ts:272,281-284`
- **Issue**: Initial truncation uses hard-coded 8, per-loop check uses `options.maxToolCalls`
- **Fix**: Use `options.maxToolCalls` for the initial truncation too
### M8. `validateToolCalls` All-or-Nothing Rejection
- **File**: `src/tools/guard.ts:49`
- **Issue**: When ANY tool call fails validation, ALL are discarded
- **Fix**: Return the accumulated `valid` array regardless
## Low
### L1. `extractSingleXmlToolCall` Matches Any XML Tag
- **File**: `src/tools/parser.ts:197`
- **Issue**: `/^<([A-Za-z][A-Za-z0-9_]*)>/` matches ANY valid XML tag as a tool call
- **Fix**: Whitelist known tool tag names
### L2. `detectParallelToolLoop` Removes First Occurrences Too
- **File**: `src/tools/guard.ts:146-149`
- **Issue**: When removing duplicates, the first 1-2 valid occurrences are removed too
- **Fix**: Keep the first occurrence, only remove excess
### L3. `compressJson` Only Samples First 3 Array Items
- **File**: `src/routes/compressToolResult.ts:49`
- **Fix**: Stratified sampling (head + middle + tail)
+116
View File
@@ -0,0 +1,116 @@
# Auth Flow — Bug Report
## Critical
### C1. Browser Context Leak on Account Removal
- **File**: `src/services/playwright.ts:19`, `src/services/accountManager.ts:220-237`
- **Issue**: `accountContexts` Map is never cleaned up when `removeAccount()` is called
- **Impact**: Chromium browser context stays open, `setInterval` keeps running forever
- **Fix**: Add cleanup hook or `closeContext()` call in `removeAccount()`
### C2. Unhandled Promise in UserAgent Extraction
- **File**: `src/services/playwright.ts:107-109`
- **Issue**: `Promise.race()` with timeout on `page.evaluate()` has no try/catch
- **Impact**: Timeout rejection crashes the caller
- **Fix**: Wrap in try/catch with fallback
### C3. Fire-and-Forget Promises in `SessionPool.release()`
- **File**: `src/services/sessionPool.ts:154,165`
- **Issue**: `Promise.all(...)` and `deleteSession(...)` are NOT awaited
- **Impact**: Counters (`activeCount`, `inFlight`) become unreliable
- **Fix**: Await both, add error handling
## High
### H1. TOCTOU Race in `pickAccount()`
- **File**: `src/services/accountManager.ts:344-376`
- **Issue**: `inFlight` is incremented AFTER `pickAccount()` returns, so two concurrent callers can pick the same account
- **Fix**: Move `incrementInFlight` inside the mutex
### H2. `initAuth` Permanently Sets `initDone = true` on Failure
- **File**: `src/services/auth.ts:124-125`
- **Issue**: If `initAuth()` fails partway, `initDone` stays `true` — no retry possible
- **Fix**: Only set `initDone = true` after successful completion, or add recovery path
### H3. Unbounded `accountContexts` Map
- **File**: `src/services/playwright.ts:19`
- **Issue**: Maps are only cleared in `closePlaywright()` — never on account removal
- **Impact**: Each entry holds BrowserContext + Page + 30s interval timer
### H4. No Concurrency Limit on Phase 2 Login
- **File**: `src/services/auth.ts:186-198`
- **Issue**: `Promise.allSettled()` on ALL accounts simultaneously can trigger Qwen rate limiting
- **Fix**: Add batch limit (like Phase 1's `MAX_CONCURRENT_PROFILE_LOADS = 3`)
### H5. Stale `watcherReady` Timer Leak
- **File**: `src/services/accountManager.ts:322`
- **Issue**: `setTimeout` for `watcherReady = true` can fire after watcher is reset
- **Fix**: Clear timeout on reset
## Medium
### M1. No-op `process.on('exit')` Handler
- **File**: `src/services/playwright.ts:179`
- **Fix**: Remove
### M2. No-op `splice` in `refreshAccountCookies()`
- **File**: `src/services/playwright.ts:300`
- **Issue**: `postCookies.splice(0, postCookies.length, ...postCookies)` is a no-op
- **Fix**: Remove
### M3. Circular Dependency: `auth.ts` ↔ `accountManager.ts`
- Both import from each other (see architecture report)
### M4. Mixed `.js` / `.ts` Import Extensions
- 5 files use `.js` extension for same modules (see architecture report)
### M5. Credential Exposure via Error Responses
- **Files**: `src/services/tokenRefresh.ts:52`, `src/services/qwen.ts:251-253`
- **Issue**: Qwen response body propagated in error messages, potentially leaking tokens
- **Validation**: `qwen.ts:250-253``UpstreamStatusError` includes `errText` (full response body) which could contain tokens. **Valid.** `tokenRefresh.ts:52` — the specific line reference is NOT an exposure point (only logs "HTTP refresh failed"). **Partially valid.**
- **Fix**: Redact sensitive data from error messages
### M6. Lock Error Silently Returns Null
- **File**: `src/services/auth.ts:331`
- **Issue**: Chromium profile lock error silently swallowed with no log
- **Fix**: Log the event before returning null
### M7. `saveCookies` Never Persists to Disk
- **File**: `src/services/auth.ts:337-364`
- **Issue**: Tokens only updated in memory, never written to disk
- **Impact**: On restart, all tokens must be re-acquired
- **Fix**: Optionally persist to disk as cache
### M8. `pickLock` Promise Chain Can Become Permanently Rejected
- **File**: `src/services/accountManager.ts:344-376`
- **[DISPUTED]**: The `.catch()` handler always resolves with `resolve(null)`. The chain can never become permanently rejected.
- Fix: Already safe — no action needed.
### M9. Config Read at Module Evaluation Time
- **File**: `src/services/auth.ts:33-34`
- **Issue**: `AUTH_TOKEN_MAX_AGE_MS` and `AUTH_REFRESH_BEFORE_MS` read at module parse time
- **Fix**: Read lazily or re-read on config change
### M10. `getCookies()` Returns First Context Only
- **File**: `src/services/playwright.ts:84-89`
- **Issue**: When called without email, returns cookies from first account only
- **Fix**: Aggregate cookies across all contexts or pick best
### M11. Password Hashed with Raw SHA-256
- **File**: `src/services/auth.ts:86`
- **Note**: SHA-256 without salt is trivially reversible via rainbow tables
- **Fix**: Use HMAC or bcrypt if possible
### M12. `404 Not_Found` Mapped to 502
- **File**: `src/services/qwen.ts:231`
- **[DISPUTED]**: Already fixed — `qwen.ts:231` correctly maps to `status = 404`. The route handler at `chat.ts:252` uses `err.upstreamStatus || 500`, so 404 is properly propagated.
- Fix: Already done.
### M13. `chatId` in URL Without Encoding
- **File**: `src/services/qwen.ts:167`
- **Fix**: Use `URLSearchParams`
### M14. `loadCookiesFromProfile` Double Opens Profile
- **File**: `src/services/auth.ts:270-306`
- **Issue**: Opens profile, closes it, opens again — ~2-3s extra latency
- **Fix**: Check if already in memory first
@@ -0,0 +1,52 @@
# Streaming & Content Pipeline — Issues
## Medium
### M1. Full Content Re-Processing Per Chunk (O(n²) CPU)
- **File**: `src/routes/chatStreamingHelpers.ts:232,266`
- **Issue**: Every SSE chunk re-processes the ENTIRE accumulated content (up to 100K chars):
- `parseXmlToolCalls(state.lastFullContent)` — scans 100K with complex regex
- `filterContentPipeline(state.lastFullContent)` — calls `cleanTextOfXmlArtifacts()` + `filterContent()` + `cleanThinkTags()`
- `getSnapshotDelta()` — char-by-char comparison on 100K strings
- **Impact**: For a 500-chunk response, the full 100K is processed 500 times
- **Fix**: Process deltas incrementally, only scan new content
### M2. RegEx ReDoS Potential in `xmlToolParser.ts`
- **File**: `src/tools/xmlToolParser.ts:17,55`
- **Issue**: `[\s\S]*?` with complex alternations can cause catastrophic backtracking on crafted input
- **Fix**: Add timeout guard, simplify regex
### M3. String Concatenation O(n²) Pattern
- **File**: `src/routes/chatStreamingHelpers.ts:227-229`
- **Issue**: `state.lastRawContent += rawText` creates a new string each chunk
- **Fix**: Use array push + join, or Map of segments
### M4. `detectCumulativeChunk()` Fingerprint Recovery on Every Chunk
- **File**: `src/routes/chatHelpersCore.ts:44-85`
- **Issue**: Tries multiple fingerprint sizes (64, 48, 32, 24) on every non-matching chunk
- **Fix**: Cache fingerprint, only scan when needed
### M5. `ToolSpamGuard.history` Unbounded Growth
- **File**: `src/routes/chatHelpersCore.ts:145`
- **Issue**: History array grows indefinitely, `window` only filters in `check()`, never trims
- **Fix**: Trim on each check
### M6. `pendingCorrections` Inner Arrays Never Cleaned
- **File**: `src/routes/chatHelpersCore.ts:175`
- **Issue**: Map trims to 500 entries every 5 min, but inner string arrays per key never cleaned
- **Fix**: Also trim inner arrays
## Low
### L1. `logIncomingRequest()` Is a No-op Called on Every Request
- **File**: `src/routes/chatHelpers.ts:315-321`
- **Fix**: Remove function or implement it
### L2. `logStore.createEntry()` Return Value Ignored
- **File**: `src/routes/chat.ts:184-185`
- **Fix**: Use the returned entry instead of double lookup (`logStore.getEntry(logId)`)
### L3. `scheduleCleanup` Uses Fixed 200ms Delay
- **File**: `src/services/cleanupHelpers.ts:43-51`
- **Issue**: Arbitrary delay could race with last SSE write
- **Fix**: Remove delay or make event-driven
@@ -0,0 +1,83 @@
# Dashboard & Frontend — Bug Report
## Critical
### F1. `loadMore()` Function Does Not Exist
- **File**: `src/routes/dashboard/logs.ts:72` + `src/routes/dashboard/public/logs.js`
- **Issue**: HTML renders "Load More" button with `onclick="loadMore()"`, but function is never defined
- **Impact**: Clicking "Load More" throws `ReferenceError`. Feature is completely broken.
### F2. SSE Stream Leaks Unsanitized Log Data
- **File**: `src/routes/dashboard/dashboardRoutes.ts:195-197`
- **Issue**: `/log/stream` SSE endpoint sends raw log entries WITHOUT calling `sanitizeLogEntry()`
- **Impact**: Full email addresses, raw prompt content, processed output streamed in plaintext to browser
- **Fix**: Apply `sanitizeLogEntry()` on SSE stream too
## High
### F3. `escHtml()` Missing Single Quote and Backtick
- **File**: `src/routes/dashboard/public/shared.js:2-5`
- **Issue**: Only escapes `&`, `<`, `>`, `"` — misses `'` and `` ` ``
- **Impact**: Potential XSS in single-quoted attribute contexts
### F4. Notification Logic Shows Wrong Entries
- **File**: `src/routes/dashboard/public/overview.js:113-114`
- **Issue**: `data.slice(0, data.length - _lastLogCount)` takes OLD entries instead of NEW entries
- **Impact**: Notifications fire for stale entries every refresh cycle
### F5. `/api/config` Exposes API_KEY
- **File**: `src/routes/dashboard/dashboardRoutes.ts:313-315`
- **Issue**: Returns ALL config including `API_KEY`
- **Fix**: Filter out sensitive keys
### F6. `authHeaders()` Always Returns `{}`
- **File**: `src/routes/dashboard/public/shared.js:10-12`
- **Issue**: Dead function that always returns empty object
- **Fix**: Remove or implement actual auth
## Medium
### F7. Full Email in Delete-Chat SSE Progress
- **File**: `src/routes/dashboard/dashboardRoutes.ts:83-91`
- **Fix**: Mask email in progress events
### F8. `APP_VERSION` Interpolated Without JSON Encoding
- **File**: `src/routes/dashboard/dashboardRoutes.ts:28`
- **Issue**: `'${APP_VERSION}'` in single-quoted JS string — breaks if version contains `'`
- **Fix**: Use `JSON.stringify(APP_VERSION)`
### F9. `pollAuth` Not Cancel-Safe
- **File**: `src/routes/dashboard/public/accounts.js:186-204`
- **Issue**: Multiple `pollAuth` for same email can stack intervals
- **Fix**: Cancel previous poll before starting new one
### F10. No Max-Count Limit on Toasts
- **File**: `src/routes/dashboard/public/accounts.js:9`, `overview.js:125`, `settings.js:261`
- **Fix**: Limit to 3-5 visible toasts, remove oldest
### F11. Full DOM Rebuild Every 2s for System Logs
- **File**: `src/routes/dashboard/public/overview.js:90-124`
- **Issue**: Entire `innerHTML` replaced every 2 seconds
- **Fix**: Only append new entries
### F12. Redundant `durationClass` Condition
- **File**: `src/routes/dashboard/public/network.js:40-43`
- **Issue**: `ms > 3000` and `ms > 500` both return `'slow'`
- **Fix**: Remove redundant first condition
## Low
### F13. Missing `aria-label`, `scope`, `<label for>` Throughout
- Accessibility improvements needed across all pages
### F14. No Content-Security-Policy Headers
- **File**: `src/routes/dashboard/dashboardRoutes.ts:27-31`
- **Fix**: Add CSP headers to `serveHtml`
### F15. Color Contrast Fails WCAG AA
- **File**: `src/routes/dashboard/public/overview.css:44`
- **Issue**: Log level colors (indigo, amber, red) on cream background fail contrast requirements
### F16. Excessive Polling With No Backoff
- All pages poll at 2-second intervals, no Page Visibility API pause
- **Fix**: Add backoff, pause when tab is backgrounded
@@ -0,0 +1,88 @@
# Error Handling & Robustness — Bug Report
## Critical
### C1. Unhandled Promise in `sessionPool.release()`
- **File**: `src/services/sessionPool.ts:154`
- **[DISPUTED]**: The `Promise.all([...])` chain HAS a `.catch()` handler attached (line 158: `.catch(err => { console.error(...); waiter.reject(err); })`). Errors are caught and logged. Not an unhandled rejection.
- **Fix**: None needed for this specific claim. The fire-and-forget pattern is intentional.
### C2. No Global `unhandledRejection` Handler
- **Entire codebase**
- **Issue**: Node.js 15+ terminates process on unhandled rejections
- **Fix**: Add `process.on('unhandledRejection', ...)`
### C3. File System Race on Profile Directory
- **File**: `src/services/browserProfiles.ts:16-18`
- **Issue**: Multiple concurrent logins for same email race on `mkdirSync` and `cloakbrowser` lock files
- **Fix**: Add mutex per-email for profile operations
### C4. Unbounded `accountActionRateLimit` Map
- **File**: `src/routes/accounts.ts:4-15`
- **Issue**: Map grows unboundedly, timestamps accumulate per key without cleanup
- **Fix**: Periodically purge old entries, or use TTL-based Map
## High
### H1. Missing `AbortSignal` Timeout on `fetch()` Calls
- **File**: `src/services/qwenModels.ts:258`, `src/cli.ts:99`, `src/services/sessionPool.ts:271-275`
- **Issue**: Multiple fetch() calls lack timeout/abort signal
- **Impact**: Hanging upstream hangs the request forever
- **Validation**: 2/3 references lack timeout. `qwenModels.ts:258` DOES have a timeout via `createFetchTimeout()`. `cli.ts:99` and `sessionPool.ts:271-275` truly lack timeouts.
- **Fix**: Add timeout to `cli.ts:99` and `sessionPool.ts:271-275`
### H2. 30+ Empty Catch Blocks
- **Many files** (see full report for complete list)
- **Issue**: Widespread silent error swallowing, especially in:
- `browserProfiles.ts` (12+ empty catches)
- `loginHelpers.ts` (8+ empty catches)
- `chatNonStreaming.ts`
- **Fix**: At minimum log errors before swallowing
### H3. `as any` Type Assertion Erosion
- **File**: `src/services/systemLogger.ts:122`, `src/routes/chatHelpers.ts:112`
- **Issue**: `as any` bypasses TypeScript safety in critical code paths
### H4. `ToolSpamGuard.history` Unbounded Growth
- **File**: `src/routes/chatHelpersCore.ts:145`
- **Issue**: Array grows indefinitely per session
### H5. Session Release Race
- **File**: `src/services/sessionPool.ts:137-167`
- **Issue**: Concurrent `release()` calls can double-count `inFlight` and `totalRequests`
### H6. `shell: true` in `spawn()` Calls
- **File**: `src/cli.ts:69-72,83,89,119`
- **Issue**: `shell: true` creates command injection risk if args contain user-controlled values
### H7. `body.model` Used Without Type Guard
- **File**: `src/routes/chatHelpers.ts:54,219,236`
- **Issue**: `body.model` assumed string without runtime validation
## Medium
### M1. `rateLimit.ts` Bucket Map Cleanup Delay
- **File**: `src/middleware/rateLimit.ts:14`
- **Issue**: Burst of unique keys causes memory spike before 15-min cleanup
### M2. `fetchQwenModels()` No Circuit Breaker
- **File**: `src/services/qwenModels.ts:230-293`
- **Issue**: Manual retry loop with jitter but no circuit breaker
### M3. Error Swallowing in `handleErrorResponse()`
- **File**: `src/services/qwen.ts:243-248`
- **[DISPUTED]**: Non-retryable errors are NOT silently swallowed. The catch block falls through to line 250 which throws `new UpstreamStatusError(...)`. Errors propagate correctly.
- **Fix**: None needed.
### M4. ReDoS Potential in `cleanTextOfXmlArtifacts`
- **File**: `src/tools/xmlToolParser.ts:55`
- **[DISPUTED]**: `[\s\S]*?` uses a **lazy** quantifier, not greedy. Lazy quantifiers expand forward one char at a time and do not cause catastrophic backtracking. The `$` anchor guarantees a match at end of string. Risk is low, not catastrophic.
- **Fix**: Low priority — real risk only on non-matching input with very long strings.
### M5. Busy-Poll Shutdown Loop
- **File**: `src/index.tsx:67-69`
- **Issue**: Polls every 100ms for up to 30s instead of event-driven
### M6. `scheduleCleanup` 200ms Race Window
- **File**: `src/services/cleanupHelpers.ts:43-51`
- **Issue**: Fixed delay could race with last SSE write
@@ -0,0 +1,81 @@
# Performance & Resource Optimization — Analysis
## Top 5 Performance Priorities
### P1. Full Content Re-Processing Per Chunk (O(n·k) CPU)
- **File**: `src/routes/chatStreamingHelpers.ts:232,266`
- **Issue**: Every SSE chunk re-processes 100K string with `parseXmlToolCalls()` + `filterContentPipeline()` + `getSnapshotDelta()`
- **Impact**: 500 chunks × 100K chars = massive CPU waste
- **Fix**: Process deltas incrementally, only scan new content for tool calls
### P2. Synchronous File Writes in Hot Path
- **File**: `src/services/qwenLogger.ts:28,48`, `src/services/logStore.ts:386`
- **Issue**: `writeFileSync()` blocks event loop on every request
- **Impact**: Increases TTFB for all concurrent requests
- **Fix**: Use async `writeFile`, batch writes, or write queue
### P3. 30-Second Cookie Refresh for ALL Accounts
- **File**: `src/services/playwright.ts:239-243`
- **Issue**: Every 30s per account: `context.cookies()` IPC call + optional `page.goto()`
- **Impact**: With 20 accounts: 40 Playwright IPC calls/min, 20 page navigations
- **Fix**: Increase interval to 120-300s, add jitter
### P4. Double Chromium Launch Per Account at Startup
- **File**: `src/services/auth.ts:240-335`
- **Issue**: `loadCookiesFromProfile()` launches persistent context, then if no auth cookie calls `openBrowserProfile()` which launches another context
- **Impact**: N accounts × up to 3 browser launches on boot
- **Fix**: Eliminate redundant re-launch, reuse single context
### P5. Promise-Chain Mutex Unbounded Queue
- **File**: `src/services/accountManager.ts:344-376`
- **Issue**: `pickLock` Promise chain grows unbounded under concurrent load
- **Impact**: Micro-task queue buildup under load
- **Fix**: Replace with async-mutex library
## Memory Issues
| # | Location | Issue | Severity | Fix |
|---|----------|-------|----------|-----|
| M1 | `chatStreamingHelpers.ts:78` | `state.loggedToolCalls: Set<string>` never cleared during stream | HIGH | Clear periodically or at end of stream |
| M2 | `chatStreamingHelpers.ts:76` | `state.reasoningBuffer` grows unboundedly with entire reasoning content | HIGH | Cap size |
| M3 | `chatStreamingHelpers.ts:223` | `state.lastVStrRaw` has no size limit | HIGH | Apply 100K cap like `lastFullContent` |
| M4 | `chatHelpersCore.ts:145` | `ToolSpamGuard.history` never trimmed | MEDIUM | Trim on each `check()` |
| M5 | `services/modelHealth.ts:8-10` | Model health Maps never evicted | MEDIUM | Add periodic cleanup |
| M6 | `services/logStore.ts:340-390` | Per-request file logs have unbounded disk growth | MEDIUM | Add rotation/cleanup |
| M7 | `chatHelpersCore.ts:175` | `pendingCorrections` inner arrays never cleaned | LOW | Also trim inner arrays |
## CPU Issues
| # | Location | Issue | Impact | Fix |
|---|----------|-------|--------|-----|
| C1 | `chatStreamingHelpers.ts:232` | Full content re-parse per chunk | O(n²) | Incremental parsing |
| C2 | `xmlToolParser.ts:17` | `[\s\S]*?` regex backtracking | ReDoS on 100K input | Add timeout + simplify |
| C3 | `xmlStripper.ts:10-38` | 15 regex operations per call | Moderate | Combine patterns |
| C4 | `chatHelpersCore.ts:44-85` | Multiple fingerprint sizes per chunk | Low | Cache fingerprint |
| C5 | `chatStreamingHelpers.ts:227-229` | String `+=` creates O(n²) copy | Moderate | Use array/collector |
## Network Issues
| # | Location | Issue | Impact | Fix |
|---|----------|-------|--------|-----|
| N1 | `playwright.ts:239-243` | 30s cookie refresh per account | N×40 IPC calls/min | Increase to 120-300s + jitter |
| N2 | `sessionPool.ts:236-294` | Create + Delete session per chat turn | 9 extra API calls per 5-turn chat | Reuse sessions for multi-turn |
| N3 | `playwright.ts:384-396` | Extra fetch per account for bx-headers | 1 extra call per context creation | Capture headers from first real request |
## Concurrency Issues
| # | Location | Issue | Fix |
|---|----------|-------|-----|
| K1 | `accountManager.ts:344-376` | Promise-chain mutex unbounded queue | Use async-mutex |
| K2 | `auth.ts:186-198` | Phase 2 login has no concurrency limit | Add batch limit like Phase 1 |
| K3 | `playwright.ts:50`, `loginHelpers.ts:14` | 2 identical Mutex implementations | Extract to shared utility |
| K4 | `auth.ts:168-183` | Phase 1 default batch of 3 may be too low | Make configurable |
## File I/O Issues
| # | Location | Issue | Severity | Fix |
|---|----------|-------|----------|-----|
| I1 | `qwenLogger.ts:28,48` | `writeFileSync()` on every request | HIGH | Async writeFile |
| I2 | `logStore.ts:386` | `writeFileSync()` per finalized request | HIGH | Batch writes |
| I3 | `accountManager.ts:148` | `writeFileSync()` per account add/remove | LOW | Async |
| I4 | `qwenLogger.ts` | No log rotation/cleanup | MEDIUM | Add rotation |
+81
View File
@@ -0,0 +1,81 @@
# Security Audit Report
## Critical
### C1. Plaintext Passwords in `accounts.json`
- **File**: `src/services/accountManager.ts:143-152`
- **Issue**: Passwords only encrypted if `API_KEY` is set. With no API_KEY, passwords stored in plaintext
- **Severity**: Critical
- **Fix**: Always derive encryption key (machine ID or generated on first run)
### C2. No Authentication on Dashboard Admin Endpoints
- **File**: `src/routes/dashboard/dashboardRoutes.ts:300-301`
- **Issue**: `/admin/accounts/reload` and `/dashboard/accounts/delete-all-chats` have no auth
- **Severity**: Critical
- **Fix**: Add bearer auth middleware
### C3. SSE Stream Leaks Unsanitized Data
- **File**: `src/routes/dashboard/dashboardRoutes.ts:195-197`
- **Issue**: `/log/stream` sends raw log entries without `sanitizeLogEntry()`
- **Impact**: Full emails, prompts, API output visible in DevTools
- **Severity**: Critical
## High
### H1. `/api/config` Exposes All Config Including API_KEY
- **File**: `src/routes/dashboard/dashboardRoutes.ts:313-315`
- **Fix**: Filter sensitive keys from response
### H2. `escHtml()` Missing `'` and `` ` `` Escaping
- **File**: `src/routes/dashboard/public/shared.js:2-5`
- **Potential XSS** in single-quoted attribute contexts
### H3. `shell: true` in CLI spawn()
- **File**: `src/cli.ts:69-72,83,89,119`
- **Impact**: Command injection risk if user-controlled args enter spawn
### H4. `APP_VERSION` Interpolated Without JSON Encoding
- **File**: `src/routes/dashboard/dashboardRoutes.ts:28`
- **Fix**: Use `JSON.stringify(APP_VERSION)`
### H5. `--port` Value Interpolated Into Shell Command
- **File**: `src/cli.ts:69-72` + `src/cli.ts:56-61`
- **Fix**: Validate port is numeric before passing to spawn
## Medium
### M1. No Content-Security-Policy Headers
- **File**: `src/routes/dashboard/dashboardRoutes.ts`
- **Fix**: Add CSP to all HTML responses
### M2. No Rate Limiting on Login Endpoint
- **File**: `src/routes/accounts.ts:34-74`
- **Fix**: The existing `accountActionRateLimit` only applies to accounts API, not login attempts
### M3. Token in SSE Query Parameter
- **File**: `src/utils/auth.ts:41`
- **Issue**: `?token=` in URL is logged by proxies, browsers, server access logs
- **Fix**: Prefer `Authorization` header only
### M4. Verbose Error Messages Leak Internal Info
- **File**: Multiple — `src/services/qwen.ts`, `src/routes/chat.ts`
- **Validation**: `chat.ts:253` error messages pass through `cleanTextOfXmlArtifacts()` — some sanitization exists. `qwen.ts` custom errors (`RetryableQwenStreamError`, `UpstreamStatusError`) leak upstream error codes and statuses. Partially true — risk exists but less severe than stated.
- **Fix**: Sanitize error messages before returning to client
### M5. Missing Input Validation on `body.model`
- **File**: `src/routes/chatHelpers.ts:54,219,236`
- **Fix**: Add runtime type check
## Low
### L1. No `X-Frame-Options` or Other Security Headers
- **Fix**: Add helmet-like middleware or set headers manually
### L2. `axios`-style `shell: true` in CLI
- **Fix**: Remove `shell: true`, use `spawn` with args array only
### L3. No `.env.example` for Secret Documentation
- Fix: Create one
### L4. Hardcoded `mos3adadel@123` Password in accounts.json
- **[UNVERIFIABLE]**: This is in the user's local `accounts.json` file, not in the repository. Cannot be verified via source code analysis.
+85
View File
@@ -0,0 +1,85 @@
# Testing & CI/CD — Analysis
## Current State
| Metric | Value |
|--------|-------|
| Total test files | 10 (out of ~68 source files) |
| Total test cases | ~90 |
| Coverage measurement | None |
| CI/CD | None |
| E2E tests | 0 |
| Integration tests | 6 (one file) |
## Files With Zero Test Coverage (Critical)
| File | Lines | Risk | Priority |
|------|-------|------|----------|
| `src/services/playwright.ts` | 432 | **CRITICAL** | P0 |
| `src/services/qwen.ts` | 342 | **CRITICAL** | P0 |
| `src/services/accountManager.ts` | 461 | **CRITICAL** | P0 |
| `src/services/sessionPool.ts` | 297 | **CRITICAL** | P0 |
| `src/services/auth.ts` | 386 | **CRITICAL** | P0 |
| `src/routes/chatNonStreaming.ts` | 345 | **CRITICAL** | P0 |
| `src/routes/chatStreamingHelpers.ts` | 297 | **CRITICAL** | P0 |
| `src/routes/streamLoop.ts` | 193 | **CRITICAL** | P0 |
| `src/routes/chatHelpers.ts` | 321 | **CRITICAL** | P0 |
| `src/routes/chatHelpersCore.ts` | 343 | **CRITICAL** | P0 |
## Files With Partial or No Tests (High Priority)
| File | Lines | Notes |
|------|-------|-------|
| `src/services/loginHelpers.ts` | 349 | 0 tests |
| `src/services/browserProfiles.ts` | 264 | 0 tests |
| `src/services/tokenRefresh.ts` | 117 | 0 tests |
| `src/services/modelRouter.ts` | 163 | 0 tests |
| `src/utils/retry.ts` | 374 | 0 tests (circuit breaker state machine!) |
| `src/utils/tokenEstimator.ts` | 228 | 0 tests |
| `src/middleware/rateLimit.ts` | 153 | 0 tests (token bucket algorithm!) |
| `src/routes/writeHelpers.ts` | 118 | 0 tests |
## Existing Test Quality
| Test File | Lines | Quality | Notes |
|-----------|-------|---------|-------|
| `xmlToolParser.test.ts` | 364 | HIGH | Real streaming data fixtures, thorough edge cases |
| `parser.test.ts` | 157 | HIGH | Numbered scenarios, streaming splits |
| `guard.test.ts` | 168 | HIGH | Comprehensive guard coverage |
| `configService.test.ts` | 173 | HIGH | All CRUD operations |
| `chat.amplification.test.ts` | 167 | HIGH | Amplification fix verification |
| `index.test.ts` | 324 | MODERATE | Integration tests, no timeout guard on stream read |
| `limiting.test.ts` | 149 | HIGH | Thorough boundary tests |
| `parallel.test.ts` | 42 | MODERATE | Potentially flaky (setTimeout timing) |
| `logStore.test.ts` | 44 | LOW | Only 2 tests on entry creation |
| `auth.test.ts` | 25 | VERY LOW | 3 "doesn't throw" tests on nonexistent accounts |
## Infrastructure Gaps
| Gap | Impact |
|-----|--------|
| No CI/CD pipeline | No automated test runs, no PR gating |
| No coverage reporting | Can't measure or enforce thresholds |
| No linting (ESLint/Biome) | Inconsistent code style, no automated quality checks |
| No pre-commit hooks | No type-checking or linting before commits |
| No Dockerfile | No reproducible test environment |
| No E2E tests | Real browser/Playwright interactions never tested |
| No test timeout guard | `index.test.ts` has a `while(true)` loop with no escape |
## Recommendations (Priority Order)
### P0 — Add tests for (no coverage, high business value):
1. `src/utils/retry.ts` — CircuitBreaker state machine needs tests
2. `src/services/sessionPool.ts` — Acquire/release/queue/timeout
3. `src/services/qwen.ts` — Error handling, rate limits, retry
4. `src/services/auth.ts` + `accountManager.ts` — Account selection, throttling, encryption
5. `src/middleware/rateLimit.ts` — Token bucket algorithm
### P1 — Add CI infrastructure:
1. GitHub Actions workflow: `npm ci``npx tsc --noEmit``npm test`
2. Coverage threshold enforcement
3. Pre-commit hook: type-check + lint
### P2 — Add E2E tests:
1. Playwright-based test that opens a real browser
2. Mock Qwen API for deterministic testing
+83
View File
@@ -0,0 +1,83 @@
# Deployment & DevOps — Analysis
## Critical
### D1. No Docker Support
- Issue #40 requested this
- **Fix**: Create Dockerfile with:
1. `node:22-alpine` base
2. `RUN npx playwright install chromium`
3. `COPY .qwen/` for persistent data
4. `ENV QWEN_GATE_PORT=26405`
## High
### D2. No Production Startup Script
- `npm start` runs `tsx` (dev runner), not compiled JS
- `tsconfig.build.json` exists but is never used by any script
- `bin/qg` prefers `npx tsx` over compiled output
- **Fix**: Add `npm run build && node dist/index.js` as production start
### D3. `--host` CLI Flag Parsed But Ignored
- `src/cli.ts:56` parses `--host` but `src/index.tsx:187-195` never passes it to `serve()`
- **Fix**: Add `host` to `serve()` options
### D4. `config.json` Keys Silently Ignored
- `config.json` has keys (`HOST`, `DASHBOARD`, `STREAMING`, etc.) NOT in `ConfigSchema`
- Users edit these values with no feedback they're ignored
- **Fix**: Either support them in ConfigSchema or remove from config.json
### D5. `restart` Kills ALL Matching Processes
- Unix: `pkill -f "tsx.*index.ts"` kills any matching process
- Windows: `taskkill /F /IM node.exe` kills ALL node processes
- **Fix**: Use PID file for targeted process management
### D6. No Config Validation at Startup
- Bad JSON silently falls back to empty config
- Invalid `PORT: "abc"` silently uses default 26405
- **Fix**: Add validation layer that logs warnings
## Medium
### D7. Port Conflict Causes Crash
- No try/catch around `serve()` — if port is taken, process exits
- **Fix**: Retry with `port + 1` or log clear error
### D8. No Environment-Specific Config
- No `NODE_ENV` usage anywhere in code
- **Fix**: Add dev/prod profile loading
### D9. `tsx` as Runtime Dependency (~100MB)
- `tsx` in dependencies (not devDependencies) inflates production install
- `esbuild` binaries for all platforms downloaded
- **Fix**: Compile TypeScript, run with plain `node`
### D10. No `dotenv` Support
- Config reads `process.env` directly but never loads `.env` file
- **Fix**: Add `import 'dotenv/config'` or `dotenv.config()`
### D11. Command Parsing Edge Case Bug
- `qg --port 8080` treats `'8080'` as the command
- **Fix**: Filter out known flag values before command detection
### D12. `bin/qg` Is Bash-Only
- Won't work on Windows without WSL
- **Fix**: Create native `.ps1` or `.cmd` wrapper
## Low
### D13. No `--version` Flag
- `APP_VERSION` exists but not exposed via CLI
- **Fix**: Add `--version` handler
### D14. No `.env.example`
- `.env` is gitignored but no template exists
- **Fix**: Create from ConfigSchema keys
### D15. No Compression Middleware
- JSON responses sent uncompressed
- **Fix**: Add `hono/compress`
### D16. Health Check Is Fragile
- Only checks if Playwright page reference is non-null
- **Fix**: Add account readiness, Qwen connectivity checks
+63
View File
@@ -0,0 +1,63 @@
# Qwen Gate — Quality Investigation Reports
Root cause investigation conducted by 10 parallel agents on 2026-06-11.
**Validation pass completed: 178/180 claims are TRUE. 2 FALSE claims cleared.**
## Validation Results
| Report | TRUE | FALSE | PARTIAL | Cleared |
|--------|------|-------|---------|---------|
| 01 — Architecture | 34 | 2 | 0 | D6 (json.ts is imported), routes count (14 not 12) |
| 02 — Tool Calling | 16 | 0 | 0 | All valid |
| 03 — Auth Flow | 17 | 2 | 1 | M8 (pickLock safe), M12 (404 already fixed) |
| 04 — Streaming | 6 | 0 | 0 | All valid |
| 05 — Dashboard | 16 | 0 | 0 | All valid |
| 06 — Error Handling | 13 | 2 | 2 | C1 (has .catch), M3 (not swallowed) |
| 07 — Performance | 20 | 0 | 0 | All valid |
| 08 — Security | 12 | 0 | 1+1unv | M4 partially overstated |
| 09 — Testing | 14 | 0 | 0 | All valid |
| 10 — DevOps | 16 | 0 | 0 | All valid |
**Cleared claims:**
- `01-D6`: `json.ts` IS imported by `parserHelpers.ts` — not dead code
- `01`: routes directory has 14 files, not 12
- `03-M8`: `pickLock` chain always resolves via `.catch()` — can't permanently reject
- `03-M12`: 404 → 502 mapping already fixed in current code
- `06-C1`: `Promise.all()` in `sessionPool.release()` HAS a `.catch()` handler
- `06-M3`: Errors in `handleErrorResponse()` fall through to `UpstreamStatusError` throw
- `06-H1`: `qwenModels.ts:258` DOES have an AbortSignal timeout
- `06-M4`: `[\s\S]*?` is lazy quantifier — low ReDoS risk, not catastrophic
- `08-M4`: Partial sanitization exists in `chat.ts:253`
- `08-L4`: Unverifiable (user-local file, not in repo)
## Report Index
| # | Report | Findings | Priority |
|---|--------|----------|----------|
| 01 | [Architecture & Code Organization](./01-ARCHITECTURE-AND-CODE-ORGANIZATION.md) | Circular deps, dead code, misnamed modules, duplications | HIGH |
| 02 | [Tool Calling Pipeline Bugs](./02-TOOL-CALLING-BUGS.md) | 4 critical, 4 high, 8 medium bugs | CRITICAL |
| 03 | [Auth Flow Bugs](./03-AUTH-FLOW-BUGS.md) | 3 critical, 5 high, 14 medium bugs | CRITICAL |
| 04 | [Streaming & Content Pipeline](./04-STREAMING-AND-CONTENT-PIPELINE.md) | O(n²) re-processing, ReDoS, memory leaks | HIGH |
| 05 | [Dashboard & Frontend](./05-DASHBOARD-AND-FRONTEND.md) | 2 critical, 4 high, 6 medium issues | CRITICAL |
| 06 | [Error Handling & Robustness](./06-ERROR-HANDLING-AND-ROBUSTNESS.md) | 4 critical, 7 high, 6 medium issues | CRITICAL |
| 07 | [Performance & Resources](./07-PERFORMANCE-AND-RESOURCES.md) | 5 top priorities, memory/CPU/network/IO | HIGH |
| 08 | [Security Audit](./08-SECURITY-AUDIT.md) | 3 critical, 5 high, 5 medium issues | CRITICAL |
| 09 | [Testing & CI/CD](./09-TESTING-AND-CICD.md) | 90% untested code, no CI/CD pipeline | HIGH |
| 10 | [Deployment & DevOps](./10-DEPLOYMENT-AND-DEVOPS.md) | No Docker, broken CLI, fragile restart | HIGH |
## Summary Metrics
| Category | Critical | High | Medium | Low/Info |
|----------|----------|------|--------|----------|
| Architecture | 3 | 4 | 8 | 6 |
| Tool Calling | 2 | 4 | 8 | 7 |
| Auth Flow | 3 | 5 | 14 | 10 |
| Streaming | 0 | 0 | 6 | 3 |
| Dashboard | 2 | 4 | 6 | 16 |
| Error Handling | 4 | 7 | 6 | 28 |
| Performance | 0 | 5 | 10 | 3 |
| Security | 3 | 5 | 5 | 4 |
| Testing | 0 | 10 | 4 | 0 |
| DevOps | 1 | 5 | 6 | 6 |
**Total:** ~180 findings across 10 categories — **178 confirmed, 2 cleared**