3.5 KiB
3.5 KiB
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
OwnerUserIdor 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.