# Bug Report — Job Tracker **Companion to:** [SYSTEM_AUDIT_REPORT.md](SYSTEM_AUDIT_REPORT.md) Severity: **Critical / High / Medium / Low.** Each item is code-grounded or explicitly `[Speculative]`. "Speculative" = a plausible defect I did not fully reproduce; verify before fixing. ## Critical _None found._ No auth bypass, no tenant-isolation break, no RCE/SSRF hole surfaced in the audited paths. (Auth uses HttpOnly cookie + CSRF; tenancy uses global query filters; ingestion has SSRF defence.) This is itself strong evidence against "rebuild". ## High | ID | Tag | Location | Description | Fix | |----|-----|----------|-------------|-----| | H-1 | [Bug] | `Models/JobApplication.cs:28-31` + attachment write paths | Denormalised `HasResume/HasCoverLetter/HasPortfolio/HasOtherAttachment` can drift from the actual `Attachments` collection, so the checklist UI can show a resume attached when none is, or vice-versa. | Make them computed projections, or maintain via one domain method; add a test. | | H-2 | [Design flaw] | `JobApplication.TailoredCvText` vs `TailoredCvDraft` | Two writable representations of the tailored CV with no precedence rule → stale-content reads. | Pick `TailoredCvDraft`, deprecate the inline string. | | H-3 | [Perf/Bug] | `Data/JobTrackerContext.cs` (indexes) | Missing indexes on `IsDeleted`, `FollowUpAt`, child FKs → full scans on every list/board/reminder/analytics query; degrades non-linearly with data. | Add the 5 hot-path indexes. | | H-4 | [Perf] | `JobApplicationsController.GetStats` (~:1848), `GetAnalyticsOverview` (~:2851) | Loads the whole table into memory then filters/`GroupBy().Count()` in .NET. | Aggregate server-side (EF `GroupBy`/`CountAsync`). | ## Medium | ID | Tag | Location | Description | Fix | |----|-----|----------|-------------|-----| | M-1 | [Perf/Bug] | `GmailController` :646-657, :701-711, :893-918 | N+1 loops: per-message `AnyAsync` in `CreateSuggestedJob`; redundant re-loop after a HashSet is already built in `RelinkThread`; message-by-message import in `RefreshLinkedThreads`. | Batch with a single set-based query. | | M-2 | [Bug] | `GmailController` :659, :713 | `GmailReviewDecisions` loaded with `ToListAsync` then scanned where `FirstOrDefaultAsync` suffices. | Use `FirstOrDefaultAsync`. | | M-3 | [Design flaw] | `tools/summarizer/app.py:469,527,581` | Prompt injection via raw interpolation of scraped JD + instruction (capped by human-review boundary). | Delimit untrusted inputs; add factuality check. | | M-4 | [Design flaw] | `JobCvMatchService` | "Match score" is keyword-literal; mislabels semantic matches as gaps. | Relabel + synonym map. | | M-5 | [Design flaw] | job import UX | Scrape failure silently degrades to manual entry with no explanation/pre-fill. | Explicit partial-parse state. | | M-6 | [Speculative] | repeated `RulesEngine.GetSettings` across list/detail/reminders | Same per-user settings re-read many times per request cycle. | Cache in the already-registered `IMemoryCache` (short TTL). | | M-7 | [Speculative] | JS-rendered boards | Static `HttpClient` fetch returns hydration-only HTML → empty parse. | Document limitation; optional headless fetch. | ## Low | ID | Tag | Location | Description | |----|-----|----------|-------------| | L-1 | [Design flaw] | `JobApplication.Salary` (free-text) + structured salary | Two salary representations; ensure writes keep them consistent or drop free-text after backfill. | | L-2 | [Architectural weakness] | `JobTrackerBackend` link-compile glob | Non-obvious build layout; onboarding hazard. | | L-3 | [Design flaw] | `Tags`/`*Json` stored as JSON strings | Unqueryable; fine for SQLite, revisit on MySQL/Postgres. | | L-4 | [Speculative] | scraper plugins | Silent rot with no health telemetry. | ## Cross-reference with `.gsd` - `.gsd` D007/D008 (Gmail full-thread continuity) is **implemented** — not a bug, a delivered decision. - `.gsd` D006 (notes-block workaround) is a *known* UX debt the register itself flags — Medium, schema fix. - `.gsd/OVERRIDES.md` "use next.js" is **unimplemented** — a plan/impl divergence, not a runtime bug, but it means the recorded frontend decision and the shipped stack disagree. Resolve deliberately. ## Notes on what is NOT broken (verified, to prevent false alarms) - Auth token is **not** in localStorage/sessionStorage (asserted by `login-page.test.tsx:70-71`). - SSRF blocklist covers IPv4 private/CGNAT/link-local/benchmark + IPv6 ULA/link-local/Teredo. - Match scoring is deterministic — no AI in the trust-bearing number.