# Two-user isolation evidence Captured: 2026-08-02 Scope: disposable local SQLite environment only. User A and User B used synthetic `@audit.invalid` identities. No production data or credentials were used. ## Results | Resource or operation | UI level | API direct-ID test | Service/query protection | Result | |---|---|---|---|---| | Jobs | Browser blocked | A 200; B 404 for A's job; B list 0 | Explicit owner predicates and global `JobApplication` filter | Pass at API/data-query levels | | Companies | Browser blocked | B 404 for A's company; B list 0 | Explicit owner predicates and global `Company` filter | Pass at API/data-query levels | | Correspondence | Browser blocked | B list for A's job returned 0; copied ID returned 404 | Filter through owned `JobApplication` navigation | Pass at API/data-query levels | | Attachments | Browser blocked | A upload/list/download 200; B job access and copied attachment download 404 | Owned-job query before file operation | Pass at API/data-query levels | | Career Profile | Browser blocked | B's current-profile request 404 | Current-user lookup plus owner query filter | Pass at API/data-query levels | | CV variants | Browser blocked | Copied A variant ID returned 404, but B's own list returned 500 | Owner predicate/filter exists; list blocked by SQLite translation | Partial: protection inspected and direct ID passed; list broken | | Application workspace | Browser blocked | B copied A ID returned 404; A request returned 500 | Owner predicate exists; owner path blocked by SQLite translation | Partial | | Checklist | Browser blocked | B copied A job returned 404; A 200 | Owner predicate on job/items | Pass at API/data-query levels | | Timeline | Browser blocked | Both users received 500 | Ambiguous route selection occurs before authorization logic | Blocked by endpoint defect; no exposure observed | | Interview preparation | Browser blocked | Both users received 500 | Ambiguous route selection occurs before authorization logic | Blocked by endpoint defect; no exposure observed | | AI results | Browser blocked | History endpoint returned 500 | Owner predicates and query filter exist | Blocked by SQLite translation | | Settings/session | Browser blocked | B `/auth/me` returned only B | Identity/session-bound current user | Pass at API level | | Administrative operation | Browser blocked | B request returned 403 | `[Authorize(Roles = "Admin")]` | Pass at API level | | Email threads/provider data | External providers not connected | Local correspondence passed; provider-specific IDs not live-tested | Owner-scoped connection and correspondence queries inspected | Code-inspected/partially tested | ## Protection layers observed - UI: protected routes require an authenticated shell, but hiding was not counted as authorization. - API: controllers use explicit local-auth or admin authorization attributes. - Service: important reads carry `OwnerUserId` or owned-parent predicates. - EF: global filters deny on null current user and match owner IDs. - Database: tenant ownership is primarily enforced in application queries; many owner columns are not foreign keys to `AspNetUsers`, so database constraints alone do not provide tenant isolation. ## Limitations - The in-app browser control client was missing, so UI navigation as A/B was not performed. - Timeline/interview and several CV/AI paths failed before an ownership result could be observed. - No Gmail, Microsoft Graph, IMAP, Stripe, cloud AI, or remote object storage was contacted. No cross-user disclosure was confirmed in the paths that returned a meaningful result.