Files
jobtrackingapp/docs/audits/2026-09-07/threat-data.md
T
cesnimda 0954876295 fix(security): enforce audited account, network and resource boundaries
Implement B01-B06 with regression coverage and retained validation evidence. Preserve the production ingress preflight; F04 runtime verification remains open.
2026-09-09 18:21:47 +02:00

27 KiB
Raw Blame History

Threat model, abuse, data lifecycle and invariants

This model reflects inspected source at the audit baseline. It does not certify deployed network boundaries, encryption or legal compliance. F identifiers link to findings.

Trust boundaries

flowchart TB
  U[Untrusted browser / imported job text / document] --> P[Operator TLS proxy]
  U -. published host port 3000 .-> N[nginx frontend]
  P --> N
  N --> A[ASP.NET Core API: session, CSRF, owner, entitlement]
  A --> D[(SQLite or external MariaDB)]
  A --> F[Private files, exports, package copies]
  A --> K[Data Protection keys / provider secrets]
  A --> Q[(Durable operations / delivery attempts)]
  Q --> W[In-process workers with explicit owner scope]
  W --> A
  A --> S[Token-protected Python AI sidecar]
  S --> X[Bounded parser child process]
  S --> L[Local Ollama / summarization model]
  S -. consent + configured routing .-> E[External AI provider]
  A --> M[SMTP / Gmail / Graph / IMAP]
  A --> B[Stripe signed webhook + API]
  A --> J[Untrusted public job URL / NAV / translation]
  D --> R[Backup recovery set]
  F --> R
  K --> R
  R --> O[Off-host destination and custody]
  A --> G[Operational logs]
  CI[CI runner + deployment SSH] --> A
  CI --> R

The backend is highly privileged within this design: it can read document contents, decrypt connected-account credentials and modify all owners' database rows. A parser child should not inherit those capabilities. Public CV rendering deliberately crosses the private-to-public boundary, and AI/provider calls cross organizational/privacy boundaries. Browser flags and localStorage do not grant authorization. The dashed ingress route is the F04 topology risk.

Assets and intended access

Asset / significant fields Who should access Who must not Exposure route and current control Missing control / action
Account identity, email, pending email, phone, profile name Owner; minimal account administrator Other users, anonymous clients Local identity/session, owner filtering; admin user DTO Security audit/recent auth for privileged changes; no unnecessary private profile export to support
PasswordHash, SecurityStamp, session sid, recovery hashes Auth subsystem only; limited security operator Browser exports, ordinary admin UI, AI Identity hashing, live session validation, token/2FA tests Verify operational key custody; security-event trail
TOTP encrypted seed, OAuth refresh/access tokens, IMAP password Corresponding auth/provider service Clients, unrelated workers, parser child Data Protection and purpose-specific credential handling Keys and encrypted values share host boundary; test rotation/restore
Career profile, CV text/structure, history, drafts Owner; explicitly enabled AI processing Other users; support by default Owner-scoped services, AI consent gates Conflict preconditions, retention, minimal copies
CV public slug and published fields Anonymous holder while sharing enabled Visitors after revocation; users guessing private IDs Unpredictable slug; private by default; slug rotates when republished; noindex HTML Explicit field review/expiry policy; PDF noindex/no-store; do not equate noindex with access control
Jobs, notes, cover letters, correspondence, interviews, reminders Owner; explicit employer send recipient Other users; unrelated support staff Parent-owner filters and ownership tests Global admin event feed exposes content; duplicate/concurrent writes
Uploaded bytes, rendered PDF, submitted package copies Owner or deliberate public/share recipient Other owners, arbitrary path requester Managed paths, staging, quarantine, MIME checks, owner authorization Global quotas, output cache policy, bounded renderer, isolated malware decision
Subscription/customer IDs, plan and usage Owner sees status; billing worker/admin sees minimum Client cannot set entitlement, unrelated users Stripe signature and current subscription lookup; role-derived policy Checkout dedup, customer reconciliation, deletion cancellation
API/JWT/SMTP/Stripe/AI/CI secrets, Data Protection keys Narrow service/operator Frontend, logs, untrusted PRs, parser Config/env, some child-env stripping, tracked-secret scan Encrypted custody, rotation drills, artifact/runner policy; runtime proof pending
Backup/tombstone sets Recovery operator Application users, unrelated containers Separate tombstone volume, restricted backup modes, retention script Consistency/restore proof and off-host encryption/custody
Logs/diagnostic IDs Support uses safe metadata; security team limited event access Public users and unrestricted private-content search Request trace IDs, structured production console PII/URL filtering, immutable security trail, retention and access ownership

Attacker analysis

Actor Likely route / intent Most useful controls
Anonymous internet attacker / bot Credential stuffing, signup/reset floods, public PDF overload, Host/header spoofing Canonical origin, trusted ingress, layered auth/public admission and delivery quotas
Malicious registered user / abusive paying user ID substitution, storage/database growth, PDF/AI exhaustion, repeated imports Owner filters at every verb, aggregate limits, atomic quotas, global cost/concurrency bounds
Compromised user account Export private career/mail data; send unwanted messages; link provider Recent auth for export/security actions, revoke all sessions, mail confirmation, safe audit and anomaly counts
Malicious uploaded document Parser crash, resource exhaustion, embedded content or renderer escape File type and decompression bounds, child isolation, network denial, renderer encoding and separate privilege
Malicious job advertisement SSRF through source URL; prompt injection; XSS; spreadsheet formula export Connected-IP validation, treat text as data, encode outputs, fact review, spreadsheet-safe export
Compromised dependency / CI runner Read secrets, alter delivered frontend/backend, exfiltrate personal data Locked/pinned builds, protected runner/workflow, least SSH privilege, artifact provenance, detection and rotation
Malicious/compromised administrator Bulk private event access, roles/resets, host/database theft Minimal admin DTOs, recent authentication, auditable privileged actions, separate host custody

STRIDE is used to ensure coverage, not generate duplicate findings: spoofing (session/ingress), tampering (stale edits/forged identifiers), repudiation (missing security audit), disclosure (admin/log/cache/provider boundaries), denial of service (PDF/storage/import), elevation (roles/paid state). Highest combined risk comes from the backend host: compromise reaches database, files and decrypting keys together.

Abuse matrix

Likelihood/impact are qualitative estimates for an openly registered single-host deployment, not observed incident rates. H/M/L = high/medium/low. R/Q/M columns state recommended rate limit, quota, monitoring respectively; “yes” does not mean currently implemented.

Abuse case L/I Current defence Recommendation R / Q / M and monitoring
Automated registration H/M Registration switch, auth-login policy, configurable Turnstile Per-IP + normalized-account attempt control; staged registration yes/yes/yes; accepted/rejected signup bursts
CAPTCHA bypass/unavailability M/M Server verification when configured Fail closed; accessible retry/recovery, independent rate limiting yes/no/yes; challenge error rate
Disposable email / multi-account / trial cycling M/M Verified-email option; no reputation policy seen Require verified email before cost; measure before buying detection yes/yes/yes; account/cost anomalies, no intrusive fingerprinting
Login enumeration/credential stuffing H/H Identity/session tests, auth limit Uniform errors/timing review; account + IP throttles, accessible 2FA yes/no/yes; failure/success ratio
Reset / verification flooding H/M 5/15min by IP Recipient/account cooldown, send queue, no existence leak yes/yes/yes; sends per recipient/provider
Reminder flooding M/M Owner preference, daily sent date, worker switches Durable daily dedup; per-account/channel send ceiling yes/yes/yes; duplicate and suppression counts
Employer send abuse M/H Explicit send, provider owner guard, durable send attempts, email-send policy Fix policy ordering; retain unknown delivery state yes/yes/yes; provider errors/complaints
AI calls / credit drain H/H Pro gate, 250 calls/1M estimated tokens/month; queue caps Bound input/output and synchronous concurrency; enforce reservations across replicas yes/yes/yes; costs/reservations/retries
Expensive-model selection / oversized output M/H Config routing, allowlisted external tasks, prompt bounds Pin evaluated model per workload; no arbitrary client model/provider yes/yes/yes; task-model costs/latency
Repeat document/PDF generation H/H Per-process timeout; public IP+slug limit Shared concurrency and per-account/global budget F03 yes/yes/yes; processes, queue, output bytes
Storage exhaustion / malicious hosting H/H 10MB attachment, 5MB CV; partial attachment quota Atomic aggregate quota, bounded file types, safe download, quarantine decision yes/yes/yes; bytes/category, orphan scan
API scraping / analytics scraping M/M Auth/owner isolation; some pagination Category sustained limits and query bounds yes/yes/yes; scanned rows, response bytes
Job-import URL fetching H/H HTTP(S), DNS private-IP rejection, no redirects, 4MB stream bound Pin validated connect destination, per-owner/import/global concurrency yes/yes/yes; denies, duration, destinations by class
Search/regex expensive input M/H Owner filters; fixed regex patterns; some bounded import input Max query length, timeout/non-backtracking where needed, query-plan fixtures yes/yes/yes; p95/p99, rows scanned
Notification / queue flooding M/M AI queue 10/account and 100/global by default Cover all queues and persistent record retention; dedup notifications yes/yes/yes; pending age, rejected admissions
Webhook replay / out-of-order events H/H Signed events and current subscription re-fetch Durable inbox + customer reconciliation/transaction F02 yes/yes/yes; duplicates, lag, divergence
Repeated checkout / payment actions M/H Existing-Pro check Purchase-intent idempotency, active-session reuse yes/yes/yes; active subscriptions/account
Database-growth attack / 100k jobs H/H Paged list; no record quota found Row/byte admission and bounded board/export/history yes/yes/yes; owner/table growth
CSV formula injection M/H CSV structural quoting Spreadsheet-safe text escaping F05 no/no/yes; regression gate, no content telemetry
Log/error endpoint flooding M/M 32KB body/field truncation Rate limit, sampled safe codes, bound log volume yes/yes/yes; ingestion/dropped events
Session/export abuse after compromise M/H Recent sign-in for account ZIP; session revocation Atomic account limit and safe export audit, notify where appropriate yes/yes/yes; export counts/bytes

Rate strategy

Current fixed windows: auth-login 10/5min/IP (configurable); auth-email 5/15min/IP; 2FA challenge 5/5min/IP; employer-send 10/min intended owner; account export/deletion 2/hour intended owner; public PDF 3/min/IP+slug. The two intended owner policies fall back to IP due to middleware order (observed F06). There is no universal request limiter or distributed limiter. AI admission and monthly accounting are distinct controls.

Proposed starting limits, tune with staging measurements: auth keep tight IP bursts plus per-account cooldowns without permanent victim lockout; normal API 120/min/owner with burst 30; search/analytics 30/min/owner and input bounds; import 10/min/owner with 2 in flight; PDF 5/min/owner, 1 active/owner, 2 active globally and a small bounded queue; AI 2 active/owner plus existing durable capacity and monthly limits; public PDF 3/min/IP plus a shared renderer cap. These are initial policy values, not capacity claims. Add a more permissive edge per-IP flood limit to protect application parsing, and distinguish webhook signature admission from browser API traffic.

Return 429 with retry timing, accessible explanation and preserved inputs. Global PDF/AI budgets matter even with many accounts/IPs. Keep process-local controls only while there is one backend; use database reservations before replicas. Adopt distributed rate storage only when multiple processes actually enforce the same budget.

Data classification

These engineering categories are not determinations of statutory “special category” data.

Class Major fields/examples Handling
Public by explicit decision Published CV fields/slug, marketing copy, template metadata Review before publish; revoke links; retain noindex for token links; still treat public content as untrusted input
Internal operational Build version, task type, duration, safe error code, queue state No arbitrary user text/query strings; bounded logs; support can diagnose by reference
Personal Name, email, phone, company/contact details, salary preferences, provider subjects, IP/session metadata Owner access; minimal support visibility; purpose-based retention; protected backup and export
Sensitive career/private content CV/profile/history, notes, applications, job-search activity, mail bodies, letters, interview notes, uploaded files and AI prompts/responses No analytics/log capture; explicit third-party use; no-store; restricted at-rest storage/backup; deletion through every copy
Authentication/security Password hashes, TOTP/recovery material, sessions, CSRF tokens, OAuth access/refresh tokens, IMAP credential Never export to user-data ZIP or send to AI; hash where verification-only, protect reversible secrets; recent auth and audited rotation
Secrets JWT signing key, Data Protection key ring, provider/Stripe/SMTP/CI credentials No logs/source/frontend; separate restricted recovery custody; rotate with tested dependency plan

At-rest encryption of database/files/host/backup is not demonstrated. Data Protection protects selected credential values but cannot defend against a host attacker who also obtains its keys. Encrypt host volumes and off-host backups with recoverable custody; do not encrypt every searchable career field in application code without a specific threat and query plan. Browser-to-proxy TLS is operational; proxy-to-API is HTTP on Docker networking; external-provider TLS is used by service clients where configured. MariaDB transport encryption Needs runtime verification.

Data lifecycle and copy map

All persistent DB/file rows may enter recovery backups; exclusions must be explicit. “Export” below means account ZIP unless noted. Retention details follow in the next table.

Data group Enters / validation Primary storage Copies/cache/browser Logs / third parties Export / deletion
Account identity/settings Auth/admin/profile DTO; Identity email/password checks; verified email-change workflow AspNetUsers, roles, claims/logins /auth/me; owner key and UI preferences in localStorage; cookies Some email logs; identity/OAuth and optional mail providers Selected safe account fields; staged account purge and session invalidation
Security material Password/2FA/OAuth/IMAP flows, token validation Identity hashes, encrypted connection credentials, sessions/trusted devices; Data Protection key files HttpOnly auth cookie; readable CSRF cookie; service memory Never intentionally AI/export; log exception/URL canaries needed Safe session metadata only; revoke/remove on security changes/deletion; key ring has independent retention
Career Profile / CV information Manual structured DTO validator or extraction review CareerProfiles + relational children + version snapshots Profile JSON and raw text also on ApplicationUser; editor state; sidecar cache if used AI only through policy; no purpose for operational content logs JSON profile and versions; account deletion removes rows/files; conflict/retention gaps
Uploaded CV / extraction Multipart 5MB, file/parser/ZIP bounds, bounded child CvUploadArtifacts/CvExtractionRuns + artifact files Raw file, extracted text, structured result, current artifact pointer Local parser/AI; external processing only policy-approved; parser errors sanitized Original/current artifacts plus safe metadata; completed extraction history pruned, current artifact preserved
CV variants / generated PDFs Settings normalization and theme/owner rules CvVariants/CvVariantVersions; owner/date PDF files Settings history; render against current profile; in-memory HTML/bytes; iframe Public share if enabled; browser attempted Google Fonts in audit JSON/files; immediate variant delete/history removal; rendered outputs expire lazily on later exports
Jobs / descriptions / companies Manual/import DTOs; owner check; URL DNS/type/body guard Jobs + JobApplications duplicated compatibility fields; Companies Search/list/board memory; browser filters; derived AI summaries Job sites/NAV/translation; optional AI; admin event feed exposes title/company/notes JSON/CSV/ZIP; soft delete/trash differs from purge; F05 formula risk
Letters / notes / AI responses Human edit/review and structured AI parsing JobApplication drafts, CoverLetterVersions, AiWorkspaceNotes/Interactions Versions, submitted packages, prompt context, local component state AI provider as allowed; no automatic employer send authorized by model output Safe readable data export; delete all related copies; persistent version retention undecided
Correspondence / email drafts Gmail/Graph/IMAP sync, owner-bound review/import/send Correspondence, connection/review rows, email drafts/attempts Provider mailbox remains a separate copy; extracted AI context Mail providers; SMTP logging can expose recipient/subject Application-owned copies exported/deleted; deleting Jobjakt does not delete remote mailbox
Interviews / reminders / notifications Manual/scheduler/calendar input and owner rules Interview prep/events, job dates, notification/settings rows Calendar/provider copy, browser pending state, reminder email Email/calendar provider; delivery F07 and timed-event conversion F21 JSON/ZIP; owner purge; already sent email/calendar copies need provider-specific policy
Submitted packages Explicit capture, owner render, managed attachment paths Immutable package fields + copied file bytes and hashes Profile/settings/job description/notes/rendered HTML duplicated intentionally for provenance Should stay private except explicit download/send Historical JSON/files; deletion follows account; live CV edits do not rewrite captured package
AI operations / usage Pro, privacy and capacity reservation UserOperations, AiUsageRecords and referenced result rows In-memory execution state, sidecar one-hour cache; counts in UI Provider/model/task/duration metadata; prompts may contain career/mail data Safe operation/result data; deletion cancels/purges and clears sidecar cache; provider retention separately governed
Billing Signed webhook + current subscription lookup Stripe IDs/status on user; roles Stripe is billing authority, UI role/status cache Stripe retains its own records; no card details stored here Safe billing identifiers; current deletion omits cancellation F02
Backup/audit/analytics DB snapshots, file archives, job events, request/client logs Data-root backup + external recovery sets, console/container logs; analytics derived from jobs Off-site destination; audit event feed; support screenshots Operator/provider custody; avoid sending private analytics elsewhere Backups expire by policy; tombstone replay prevents erased account restoration; audit retention needs decision

Unnecessary or risky copies: routine /auth/me returning career-related material deserves payload minimization; ApplicationUser career projections require explicit repair/consistency ownership; all-history JSON export materializes extra buffers; unbounded settings/AI history accumulates. Submitted-package copies are justified provenance and should not be deleted as “duplication.” Do not retroactively claim live CV history is a frozen rendered document: variant settings re-render against the current master profile.

Retention matrix

Data Storage / purpose Current retention Delete behavior Backup behavior / target decision
Account, profile, jobs, companies DB; core service No general time expiry found Staged account purge; jobs may remain in trash until purge Included; disclose delayed backup expiry
CV settings/profile/letter histories DB; recovery/provenance No general bounded history retention found Variant/account paths remove related rows Define revision cap/age and pinned historical packages before pruning
CV extraction results DB/files; import review Newest 20 completed runs, current artifact preserved Expired/unreferenced artifact cleanup Verify backup copies age independently
Generated PDF Owner/date folders Default 30 days, configurable 1–365; pruning invoked on export File cleanup/account lifecycle Idle systems can retain files beyond interval; schedule cleanup if promising strict expiry
Attachments/package snapshots Managed files + DB While retained by product/account Stage/quarantine/commit/purge; reconcile interrupted states Include manifests/hashes and original/published copies
User account ZIP Owner export folder Stream uses DeleteOnClose after generation Successful download disposes/removes file Crash before streaming/disposal needs stale-artifact cleanup verification
AI sidecar cache Process memory 1h TTL / 1024 entries Cache purge endpoint, process restart Not a DB backup; request/result records are separate
AI usage/operations/notifications DB; metering/recovery No general purge policy found Owner deletion; operation state deadlines are not row retention Propose short result/diagnostic retention with longer aggregate usage; owner decision
Email/provider connection DB plus remote provider Until disconnect/deletion; provider has own policy Local token/copy removal Cannot promise removal of mail already delivered elsewhere
Sessions/trusted devices DB/security Credential validity expiry; row expiry cleanup not established Revoke/delete on relevant account actions Never restore usable sessions without security policy review
Security audit / operational logs Proposed separate ledger / current logs Docker rotation 10MB x3 for defined services; time policy absent Current timeline deletions are not immutable audit handling Proposal: 30-day operational, 90-day security initial review; not legal requirements
Local SQLite scheduled backups Data-root backups Default 14 retained snapshots Old snapshots pruned Local host loss still loses these; off-host complete sets needed
Deploy recovery sets Host backup directory/off-host copy option Default 30-day daily + 12 monthly sets Retention script pruning Actual off-host encryption, lifecycle and restore proof required
Deletion tombstones Independent volume No expiry established Must survive older application restore Keep through longest recoverable backup horizon; avoid logging owner personal content
Billing metadata User row + Stripe Local tied to account; provider policy external F02 unresolved subscription cancellation Retained billing/security fields and statutory periods require owner/legal decision

Access/correction/restriction/deletion requests need a documented operator channel, identity verification proportional to risk, export validation and a tracked outcome. Technical functionality does not establish compliance. Consent/privacy copy must identify local versus external AI, remote mail/calendar copies, backup lag, disabled deletion where applicable and optional public fields. Do not add a cookie banner solely because essential session cookies exist; assess actual non-essential services and jurisdiction with qualified review.

Product invariants and enforcement

Invariant Current enforcement / proof Gap / permanent gate
No cross-user read/write/download DbContext owner filters, CurrentUserService, many authorization suites; CV probes 404 Extend owner substitution to every newly added endpoint/background subject
Referenced company/CV/job belongs to owner Service validations, association tests; ordinary FKs No blanket DB-level composite ownership proof; test joins/IgnoreQueryFilters/admin paths
Private data not published by default IsPublic false; explicit share and rotating slug Preview fields and revocation/noindex/file cache tests
Newer edit cannot be silently overwritten No client revision contract F01 currently violates; 409/412 and draft preservation
History is truthful Append snapshots and submitted-package frozen bytes Settings history is not immutable rendering; duplicate version race remains
Retry does not duplicate irreversible effects AI keys/leases and employer send attempt states Job create/checkout/reminder paths need intent dedup
Only trusted server state grants paid access Pro handler reads roles; queue rechecks before work Concurrent billing synchronization/deletion F02
Failed generation leaves valid content intact Draft/review paths, structured parsing and queue status Fault-inject every overwrite boundary; no “ready” without valid artifact
Deleted account cannot regain access after restore Session/public revocation, staged purge, independent tombstones, tests Billing cancellation and actual full restore drill
Owner work survives worker restart Durable operations/lease recovery tests Synchronous work/mail and file-in-progress behavior need explicit policy
Quota cannot be overspent concurrently Process-local AI gates; partial upload check Cross-pipeline/replica DB reservation; all-category byte accounting
AI does not invent/send facts autonomously Prompts, normalization, explicit review/send APIs and synthetic eval fixtures Live model/factuality/Bokmål evaluation still required; substring fixtures are not semantic proof
Startup cannot silently select wrong storage Deploy script validation F19 application-level configuration fallback

Property/fuzz candidates: URL canonicalization and blocked IP families; status/date normalization; salary/numeric bounds; schema round-trip and revision monotonicity; filename/CSV quoting; bounded DOCX/PDF metadata. Use seeded bounded loops and existing frameworks first. Mutation testing is worthwhile only for owner predicates, quota comparison, session revocation and revision checks; do not optimize a whole-repo mutation or line-coverage percentage.