41 lines
3.5 KiB
Markdown
41 lines
3.5 KiB
Markdown
# 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.
|