docs: reorganize tree, restore architecture + research from archive, add Phase 0 reports
Active docs/ was stub scaffolding while the real docs sat in docs/_archive/. Restore and correct them, and record the Phase 0 work. - docs/architecture/current.md: verified system map (from archived SYSTEM_OVERVIEW, 9 corrections against code). - docs/research/competitors.md: sourced competitor analysis (from archived PRODUCT_RESEARCH, feature matrix corrected). - docs/decisions/ADR-002-job-application-model.md: the Job/JobApplication split. - docs/application-discovery-report.md, docs/implementation-roadmap.md, docs/phase-0-foundation-report.md, docs/career-workspace-branch-assessment.md. - Remove 10 zero-byte placeholder files that advertised content that never existed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,272 @@
|
||||
# M013 Adversarial Security Assessment
|
||||
|
||||
## Scope
|
||||
|
||||
Tested as requested:
|
||||
|
||||
- Input validation issues
|
||||
- Authentication flaws
|
||||
- Authorization issues
|
||||
- API security
|
||||
- File upload vulnerabilities
|
||||
- Data exposure
|
||||
|
||||
Assessment style: hostile, exploit-oriented, evidence-first.
|
||||
|
||||
## Confirmed Findings
|
||||
|
||||
### 1. Authenticated SSRF via hostname-based loopback bypass in job import preview
|
||||
|
||||
- **Category:** API security / input validation
|
||||
- **Component:** `JobTrackerApi/Services/JobImport/JobImportService.cs`
|
||||
- **Endpoint:** `POST /api/jobimport/preview`
|
||||
- **Risk:** **High**
|
||||
|
||||
#### Vulnerability
|
||||
|
||||
`JobImportService.TryValidateUrl(...)` blocks literal loopback and private IPs, but it does **not** resolve hostnames before allowing the request. That means a hostname that resolves to a loopback/private address can bypass the protection.
|
||||
|
||||
The validator rejects:
|
||||
|
||||
- `http://127.0.0.1:5202/...`
|
||||
- `http://[::1]:5202/...`
|
||||
- `http://2130706433:5202/...`
|
||||
|
||||
But it accepted hostnames resolving to loopback, including:
|
||||
|
||||
- `http://127.0.0.1.nip.io:5202/api/auth/config`
|
||||
- `http://localhost.localdomain:5202/api/auth/config`
|
||||
|
||||
#### Example exploit input
|
||||
|
||||
```http
|
||||
POST /api/jobimport/preview
|
||||
Authorization: Bearer <valid local token>
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"url": "http://127.0.0.1.nip.io:5202/api/auth/config"
|
||||
}
|
||||
```
|
||||
|
||||
Observed result:
|
||||
|
||||
- request was **not** rejected as local/private
|
||||
- server fetched the internal endpoint
|
||||
- response progressed to parser failure (`No JobPosting schema found`), which is enough to prove the internal fetch happened
|
||||
|
||||
#### Why this matters
|
||||
|
||||
An authenticated attacker can use the server as an HTTP client against internal-only services or private network resources reachable from the API host. Depending on deployment, this can expose:
|
||||
|
||||
- internal admin/debug endpoints
|
||||
- cloud metadata services
|
||||
- internal service meshes
|
||||
- localhost-only ports
|
||||
- network topology and response behavior
|
||||
|
||||
#### Clear fix
|
||||
|
||||
- Resolve DNS before allowing the request.
|
||||
- Reject any hostname whose resolved addresses are loopback, link-local, RFC1918 private, or otherwise internal.
|
||||
- Re-resolve after redirects, or disable redirects entirely.
|
||||
- Consider an allowlist of supported job domains instead of general outbound fetching.
|
||||
- Log and rate-limit preview fetch attempts.
|
||||
|
||||
---
|
||||
|
||||
### 2. Subjectless signed local JWTs authenticate successfully and can disable owner scoping
|
||||
|
||||
- **Category:** Authentication flaws / authorization issues
|
||||
- **Components:**
|
||||
- `JobTrackerApi/Program.cs`
|
||||
- `Data/JobTrackerContext.cs`
|
||||
- several owner-scoped controllers relying on EF query filters
|
||||
- **Risk:** **High**
|
||||
|
||||
#### Vulnerability
|
||||
|
||||
Local JWT validation accepts a correctly signed token **without** a required subject / nameidentifier claim.
|
||||
|
||||
Runtime proof:
|
||||
|
||||
- a signed local JWT with **no** `ClaimTypes.NameIdentifier` / `sub`
|
||||
- but with valid issuer/audience/signature
|
||||
- was accepted by the API
|
||||
- `GET /api/auth/me` returned `200`
|
||||
|
||||
Observed response shape:
|
||||
|
||||
- provider: `external`
|
||||
- id: `null`
|
||||
- email echoed from token
|
||||
|
||||
At the same time, owner scoping in `Data/JobTrackerContext.cs` is defined like this:
|
||||
|
||||
```csharp
|
||||
.HasQueryFilter(x => CurrentUserId == null || x.OwnerUserId == CurrentUserId)
|
||||
```
|
||||
|
||||
If `CurrentUserId` is null, the filter collapses to **allow all rows** for owner-scoped entities.
|
||||
|
||||
That is a dangerous composition:
|
||||
|
||||
1. token is authenticated
|
||||
2. current user id is null
|
||||
3. owner filters disable themselves
|
||||
4. endpoints that rely on implicit owner filtering become potentially cross-tenant
|
||||
|
||||
#### Example exploit input
|
||||
|
||||
A signed HS256 JWT using the app signing key, but **omitting** the nameidentifier claim.
|
||||
|
||||
Payload example:
|
||||
|
||||
```json
|
||||
{
|
||||
"iss": "JobTrackerApi",
|
||||
"aud": "job-tracker-ui",
|
||||
"nbf": 1775910345,
|
||||
"exp": 1775913945,
|
||||
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "ghost@example.com",
|
||||
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name": "ghost@example.com"
|
||||
}
|
||||
```
|
||||
|
||||
Observed runtime request:
|
||||
|
||||
```http
|
||||
GET /api/auth/me
|
||||
Authorization: Bearer <signed token without subject>
|
||||
```
|
||||
|
||||
Observed result:
|
||||
|
||||
- `200 OK`
|
||||
- request treated as authenticated even though no user identity key existed for owner scoping
|
||||
|
||||
#### Why this matters
|
||||
|
||||
If an attacker can forge or obtain a valid local signing key, this flaw is not just “login bypass” — it can become a **tenant-boundary bypass** because owner filters stop applying.
|
||||
|
||||
This is especially serious in environments where:
|
||||
|
||||
- the dev signing key is reused
|
||||
- a staging or preview environment leaks the local JWT key
|
||||
- operational mistakes deploy development auth settings
|
||||
|
||||
#### Clear fix
|
||||
|
||||
- Require a non-empty subject / nameidentifier claim during local JWT validation.
|
||||
- Reject authenticated requests whose token does not map to a concrete application user identity.
|
||||
- Change query filters so `CurrentUserId == null` means **deny**, not allow-all, for owner-scoped entities.
|
||||
- Avoid relying on implicit global filters alone for sensitive raw-id endpoints; add explicit owner predicates in controller queries.
|
||||
|
||||
Example direction:
|
||||
|
||||
- validate a required identity claim in the JWT bearer events pipeline
|
||||
- make owner filter logic equivalent to `CurrentUserId != null && x.OwnerUserId == CurrentUserId`
|
||||
|
||||
---
|
||||
|
||||
## High-Risk Candidates Not Fully Confirmed In This Runtime
|
||||
|
||||
These are not counted as confirmed findings yet, but they remain serious candidates.
|
||||
|
||||
### A. Raw-id child endpoints rely on implicit owner scoping
|
||||
|
||||
- **Category:** Authorization issues
|
||||
- **Components:**
|
||||
- `JobTrackerApi/Controllers/AttachmentsController.cs`
|
||||
- `JobTrackerApi/Controllers/CorrespondenceController.cs`
|
||||
- selected job-linked endpoints
|
||||
- **Risk:** **Medium to High**
|
||||
|
||||
Patterns observed:
|
||||
|
||||
- existence checks like `AnyAsync(j => j.Id == jobId)`
|
||||
- later child fetches through parent relationships
|
||||
- reliance on EF global filters instead of explicit per-request owner predicates
|
||||
|
||||
If the owner filter is ever bypassed, weakened, or accidentally ignored, these become cross-user read/write primitives.
|
||||
|
||||
This runtime could not prove the full cross-user exploit path because the active SQLite file is missing core domain tables (`Companies`, `JobApplications`, `RuleSettings`) and those requests fail before authorization behavior can be fully exercised.
|
||||
|
||||
#### Suggested fix
|
||||
|
||||
Add explicit owner predicates in the endpoint queries themselves instead of trusting global filters as the only boundary.
|
||||
|
||||
---
|
||||
|
||||
## Tested Surfaces With No Confirmed Finding In This Pass
|
||||
|
||||
### Anonymous API reachability
|
||||
|
||||
Observed anonymous results in this runtime:
|
||||
|
||||
- `GET /api/export/jobs` → `401`
|
||||
- `POST /api/backup/encrypted` → `401`
|
||||
- `POST /api/jobimport/preview` → `401`
|
||||
- `POST /api/client-errors` → `401`
|
||||
|
||||
This is better than the earlier pre-hardening posture.
|
||||
|
||||
### Gmail OAuth callback
|
||||
|
||||
- `GET /api/gmail/oauth/callback?code=fake&state=fake` returned a generic failure page
|
||||
- no secret data exposure observed in this pass
|
||||
|
||||
### File upload path traversal via visible filename
|
||||
|
||||
Code review on:
|
||||
|
||||
- `AuthController.UploadAvatar`
|
||||
- `AttachmentsController.Upload`
|
||||
- `AttachmentsController.Rename`
|
||||
- `ProfileCvController.Upload`
|
||||
|
||||
Current handling uses `Path.GetFileName(...)` and generated storage names, which is a reasonable defense against straightforward path traversal through user-supplied filenames.
|
||||
|
||||
No confirmed traversal exploit in this pass.
|
||||
|
||||
## Recommended Remediation Order
|
||||
|
||||
1. **Fix authenticated SSRF in job import preview**
|
||||
- hostname resolution checks
|
||||
- no internal/private destinations
|
||||
- preferably domain allowlist
|
||||
|
||||
2. **Fix JWT subjectless-auth acceptance and owner-filter allow-all behavior**
|
||||
- require subject/nameidentifier
|
||||
- reject tokens that do not map to a real app identity
|
||||
- change owner filters to deny on null current user
|
||||
|
||||
3. **Harden raw-id owner-sensitive endpoints with explicit owner predicates**
|
||||
- attachments
|
||||
- correspondence
|
||||
- job-linked child endpoints
|
||||
|
||||
4. **Run a second exploit pass after the fixes**
|
||||
- repeat cross-user probes
|
||||
- retest SSRF bypasses
|
||||
- fuzz upload/parser surfaces further
|
||||
|
||||
## Evidence Summary
|
||||
|
||||
### Runtime probes performed
|
||||
|
||||
- anonymous reachability checks against auth/config, csrf, auth/me, client-errors, jobimport preview, export, backup, and Gmail callback
|
||||
- authenticated SSRF probes against job import preview using loopback-resolving hostnames
|
||||
- authenticated malformed-token probe using a signed local JWT without subject/nameidentifier
|
||||
|
||||
### Key observed outputs
|
||||
|
||||
- `/api/jobimport/preview` accepted `http://127.0.0.1.nip.io:5202/api/auth/config`
|
||||
- `/api/auth/me` returned `200` for a signed local JWT without subject/nameidentifier
|
||||
- owner filters in `JobTrackerContext` explicitly allow all rows when `CurrentUserId == null`
|
||||
|
||||
## Honest Boundaries
|
||||
|
||||
- Some cross-user raw-id probes were limited by the local runtime using an incomplete SQLite schema.
|
||||
- Those areas are reported as **high-risk candidates**, not falsely upgraded to confirmed findings.
|
||||
- The two confirmed findings above are supported by direct runtime evidence plus code-path verification.
|
||||
@@ -0,0 +1,123 @@
|
||||
# M014 Security Remediation Verification
|
||||
|
||||
This report retests the two confirmed findings from `M013` after code fixes landed in `M014`.
|
||||
|
||||
Related assessment:
|
||||
|
||||
- `docs/security-assessments/M013-adversarial-security-assessment.md`
|
||||
|
||||
## Fixed Findings
|
||||
|
||||
### 1. Job import preview SSRF via hostname-based loopback/private-address bypass
|
||||
|
||||
- **Original issue:** `POST /api/jobimport/preview` accepted hostnames that resolved to loopback/private addresses and fetched internal targets.
|
||||
- **Fix status:** **Fixed**
|
||||
- **Primary code changes:**
|
||||
- `JobTrackerApi/Services/JobImport/JobImportService.cs`
|
||||
- `JobTrackerApi/Services/JobImport/IHostAddressResolver.cs`
|
||||
- `JobTrackerApi/Program.cs`
|
||||
|
||||
#### What changed
|
||||
|
||||
- URL validation now resolves hostnames before allowing outbound fetches.
|
||||
- Validation rejects loopback, private, link-local, and other internal destinations for both literal IPs and resolved hostnames.
|
||||
- Automatic redirects are disabled on the `jobimport` HTTP client.
|
||||
|
||||
#### Retest inputs and outcomes
|
||||
|
||||
| Exploit input | Expected after fix | Observed |
|
||||
| --- | --- | --- |
|
||||
| `http://127.0.0.1.nip.io:5202/api/auth/config` | reject | `400` with parser `none` and local/private-network rejection |
|
||||
| `http://localhost.localdomain:5202/api/auth/config` | reject | `400` with parser `none` and local/private-network rejection |
|
||||
| `http://[::1]:5202/api/auth/config` | reject | `400` with local/private-network rejection |
|
||||
| `http://2130706433:5202/api/auth/config` | reject | `400` with local/private-network rejection |
|
||||
| `https://example.com` | allow public fetch path | request reached parser path and failed only with `No JobPosting schema found.` |
|
||||
|
||||
#### Verdict
|
||||
|
||||
**Pass.** The original SSRF exploit shapes are now blocked and a normal external URL still follows the intended public-host path.
|
||||
|
||||
---
|
||||
|
||||
### 2. Subjectless signed local JWTs authenticate successfully and can disable owner scoping
|
||||
|
||||
- **Original issue:** a validly signed local JWT without `nameidentifier` / `sub` was accepted, and owner filters were written to allow all rows when `CurrentUserId` was null.
|
||||
- **Fix status:** **Fixed**
|
||||
- **Primary code changes:**
|
||||
- `JobTrackerApi/Services/LocalAuthIdentity.cs`
|
||||
- `JobTrackerApi/Services/CurrentUserService.cs`
|
||||
- `JobTrackerApi/Program.cs`
|
||||
- `Data/JobTrackerContext.cs`
|
||||
|
||||
#### What changed
|
||||
|
||||
- Local JWT bearer validation now rejects tokens without a concrete subject/nameidentifier.
|
||||
- Current-user resolution uses the same required-identity rule.
|
||||
- Owner query filters now deny on null current user instead of allowing all rows.
|
||||
|
||||
#### Retest input and outcomes
|
||||
|
||||
Malformed token shape reused from `M013`:
|
||||
|
||||
- valid local signature
|
||||
- valid issuer/audience/lifetime
|
||||
- **missing** `ClaimTypes.NameIdentifier` / `sub`
|
||||
|
||||
Observed after fix:
|
||||
|
||||
| Request | Expected after fix | Observed |
|
||||
| --- | --- | --- |
|
||||
| `GET /api/auth/me` with subjectless signed local JWT | reject | `401` |
|
||||
| `GET /api/companies` with subjectless signed local JWT | reject | `401` |
|
||||
| `GET /api/rules` with subjectless signed local JWT | reject | `401` |
|
||||
|
||||
#### Automated proof
|
||||
|
||||
Focused tests now cover:
|
||||
|
||||
- required-subject local identity behavior
|
||||
- fail-closed owner query filters when current user is missing
|
||||
|
||||
#### Verdict
|
||||
|
||||
**Pass.** The malformed token no longer authenticates, and the owner filters fail closed behind the auth boundary.
|
||||
|
||||
## Focused Test Evidence
|
||||
|
||||
### SSRF-focused tests
|
||||
|
||||
```bash
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobImportServiceTests
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
- passed
|
||||
- covers loopback-resolving hostname rejection
|
||||
- covers private-address hostname rejection
|
||||
- covers normal public-host path
|
||||
|
||||
### Local-auth / owner-scope tests
|
||||
|
||||
```bash
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter "LocalAuthIdentityTests|AuthAndSystemControllerTests|OwnershipGuardTests"
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
- passed
|
||||
- covers required-subject behavior and fail-closed owner filter semantics
|
||||
|
||||
## Remaining Boundaries
|
||||
|
||||
- The local runtime still uses a partial SQLite schema for some domain tables, so broader cross-user raw-id authorization retests remain best treated as a separate follow-up pass in a fuller environment.
|
||||
- Those unresolved candidates were not needed to close the two confirmed `M013` findings, because both confirmed exploit shapes were retested directly and now fail.
|
||||
|
||||
## Final Verdict
|
||||
|
||||
`M014` closes the two confirmed `M013` vulnerabilities:
|
||||
|
||||
1. hostname-based authenticated SSRF in job import preview — **fixed**
|
||||
2. subjectless local JWT authentication / owner-scope fail-open behavior — **fixed**
|
||||
|
||||
Both fixes were verified with focused automated tests and hostile runtime retests using the original exploit shapes.
|
||||
@@ -0,0 +1,130 @@
|
||||
# M015 Cross-User Authorization Replay Report
|
||||
|
||||
This report covers the follow-up tenant-boundary work after `M013` and `M014`.
|
||||
|
||||
Related artifacts:
|
||||
|
||||
- `docs/security-assessments/M013-adversarial-security-assessment.md`
|
||||
- `docs/security-assessments/M014-security-remediation-verification.md`
|
||||
- `docs/security-assessments/M015-hostile-fixture-setup.md`
|
||||
- `docs/security-assessments/M015-hostile-fixture-setup.json`
|
||||
- `docs/security-assessments/M015-s02-probe-results.json`
|
||||
|
||||
## Test Setup
|
||||
|
||||
A dedicated hostile-test SQLite database was created from the current EF model because the default development DB was missing core domain tables needed for real authorization probes.
|
||||
|
||||
Fixture runtime:
|
||||
|
||||
- clean SQLite DB under `.tmp/m015-fixture`
|
||||
- API started with `Data__Root=/home/pi/development/JobTracker/.tmp/m015-fixture`
|
||||
- registration temporarily enabled for the fixture runtime
|
||||
- two real local users created through the API:
|
||||
- `alice.m015@example.com`
|
||||
- `bob.m015@example.com`
|
||||
|
||||
Alice-owned fixture resources created through the real API:
|
||||
|
||||
- `company_id = 1`
|
||||
- `job_id = 1`
|
||||
- `correspondence_id = 1`
|
||||
- `attachment_id = 1`
|
||||
|
||||
All mutating requests used the real cookie + CSRF contract.
|
||||
|
||||
## Cross-User Probe Summary
|
||||
|
||||
Bob targeted Alice’s fixture ids with a real authenticated session.
|
||||
|
||||
### Defended in this pass
|
||||
|
||||
The following probes failed closed with `404` when Bob targeted Alice’s resources:
|
||||
|
||||
- `GET /api/attachments/1`
|
||||
- `GET /api/attachments/download/1`
|
||||
- `PATCH /api/attachments/1`
|
||||
- `DELETE /api/attachments/1`
|
||||
- `GET /api/correspondence/1`
|
||||
- `DELETE /api/correspondence/1`
|
||||
- `GET /api/jobapplications/1`
|
||||
- `PUT /api/jobapplications/1`
|
||||
- `PATCH /api/jobapplications/1/followup`
|
||||
- `GET /api/jobapplications/1/timeline`
|
||||
- `GET /api/jobapplications/1/tailored-cv-draft`
|
||||
- `GET /api/jobapplications/1/followup-draft`
|
||||
|
||||
These routes did not expose or mutate Alice-owned data in this hostile fixture pass.
|
||||
|
||||
## Confirmed Finding
|
||||
|
||||
### Cross-user read leak on job history
|
||||
|
||||
- **Category:** Authorization / data exposure
|
||||
- **Endpoint:** `GET /api/jobapplications/{id}/history`
|
||||
- **Risk:** **Medium**
|
||||
|
||||
#### Vulnerability
|
||||
|
||||
Before the fix, Bob could request Alice’s job history by raw job id and receive Alice’s `JobEvent` rows.
|
||||
|
||||
Observed pre-fix response:
|
||||
|
||||
- `GET /api/jobapplications/1/history` as Bob
|
||||
- `200 OK`
|
||||
- payload included Alice-owned event data, including the `Created` event for Alice’s job
|
||||
|
||||
#### Example exploit input
|
||||
|
||||
```http
|
||||
GET /api/jobapplications/1/history
|
||||
Cookie: jobtracker_auth=<bob session cookie>
|
||||
```
|
||||
|
||||
#### Root cause
|
||||
|
||||
Two issues combined:
|
||||
|
||||
1. `GetHistory(...)` queried `JobEvents` directly by `JobApplicationId` without verifying that the parent job belonged to the current user.
|
||||
2. `JobEvent` had no owner-scoped query filter in `Data/JobTrackerContext.cs`.
|
||||
|
||||
#### Fix
|
||||
|
||||
- `GetHistory(...)` now checks whether the requested job exists in the current user’s scoped `JobApplications` query and returns `404` if it does not.
|
||||
- `JobEvent` now has an owner-scoped query filter tied to `JobApplication.OwnerUserId`.
|
||||
- Added focused regression test:
|
||||
- `JobTrackerApi.Tests/JobApplicationsAuthorizationTests.cs`
|
||||
|
||||
#### Replay after fix
|
||||
|
||||
Observed post-fix response:
|
||||
|
||||
- `GET /api/jobapplications/1/history` as Bob
|
||||
- `404 Not Found`
|
||||
|
||||
#### Verdict
|
||||
|
||||
**Fixed.**
|
||||
|
||||
## Automated Evidence
|
||||
|
||||
### Focused regression test
|
||||
|
||||
```bash
|
||||
dotnet test JobTrackerApi.Tests/JobTrackerApi.Tests.csproj --filter JobApplicationsAuthorizationTests
|
||||
```
|
||||
|
||||
Observed:
|
||||
|
||||
- passed
|
||||
- verifies `GetHistory` returns `NotFound` for another user’s job
|
||||
|
||||
## Final Assessment
|
||||
|
||||
For the prioritized raw-id authorization seams exercised in this milestone:
|
||||
|
||||
- **confirmed and fixed:** `GET /api/jobapplications/{id}/history`
|
||||
- **no finding in this fixture pass:** attachments, correspondence, primary job read/update, follow-up patch, timeline, tailored draft, follow-up draft
|
||||
|
||||
## Remaining Boundary
|
||||
|
||||
This report covers the endpoints actually exercised in the hostile fixture pass. It does **not** claim that every authorization-sensitive route in the application has been exhaustively proven safe; it closes the high-risk raw-id seams prioritized from the earlier assessment with a real two-user runtime and replay evidence.
|
||||
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"alice_email": "alice.m015@example.com",
|
||||
"bob_email": "bob.m015@example.com",
|
||||
"company_id": 1,
|
||||
"job_id": 1,
|
||||
"correspondence_id": 1,
|
||||
"attachment_id": 1,
|
||||
"api_base": "http://localhost:5202/api"
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
# M015 Hostile Fixture Setup
|
||||
|
||||
## Goal
|
||||
|
||||
Produce a trustworthy local runtime for cross-user authorization probes.
|
||||
|
||||
## Key discovery
|
||||
|
||||
The default development SQLite database in `JobTrackerApi/jobtracker.db` is **not** a trustworthy authorization-test target:
|
||||
|
||||
- it contains Identity and some later feature tables
|
||||
- it does **not** contain the core domain tables needed for real cross-user job/correspondence/attachment probing
|
||||
- current startup `Migrate()` behavior is therefore insufficient as the only hostile-test setup path
|
||||
|
||||
## Chosen fixture strategy
|
||||
|
||||
Use a dedicated clean SQLite fixture database created from the current EF model with `EnsureCreated()` semantics through a tiny helper program:
|
||||
|
||||
- helper project: `tools/hostile-fixture-db/`
|
||||
- bootstrap script: `scripts/m015-hostile-fixture.sh`
|
||||
|
||||
This keeps the hostile runtime inside repo code and the real API host while avoiding ad-hoc manual SQL.
|
||||
|
||||
## What the helper does
|
||||
|
||||
- creates a clean `jobtracker.db` under a caller-provided data root
|
||||
- builds the schema from the current `JobTrackerContext` model
|
||||
- verifies the presence of core tables needed for M015:
|
||||
- `Companies`
|
||||
- `JobApplications`
|
||||
- `Correspondences`
|
||||
- `Attachments`
|
||||
- `RuleSettings`
|
||||
- `AspNetUsers`
|
||||
|
||||
## Runtime plan for S02
|
||||
|
||||
1. Run `scripts/m015-hostile-fixture.sh`.
|
||||
2. Start the API with `Data__Root` pointing at that clean fixture root.
|
||||
3. Mint an admin dev token against the fixture DB.
|
||||
4. Create/reuse Alice and Bob through real API paths.
|
||||
5. Seed Alice-owned company/job/correspondence/attachment fixtures through the real API.
|
||||
6. Capture ids for cross-user hostile probes.
|
||||
|
||||
## Honest boundary
|
||||
|
||||
This slice establishes the trusted runtime path and fixture strategy. The full two-user seeded dataset and exploit execution belong in the next slice.
|
||||
@@ -0,0 +1,158 @@
|
||||
[
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/attachments/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-2dad72a5823538bb6b92689d4569fe43-cf77b418a0f46da4-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/attachments/download/1",
|
||||
"status": 404,
|
||||
"preview": "",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/attachments/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-d63b1ce08c89ba9ed4cedb01a7de8350-db6e76ebddea4885-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/attachments/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-367415d13b5de0f54c2018849f526b04-df45573d91fb9c07-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/correspondence/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-df329ad92752ad3337e26fce98a92693-6cf9f400655056a8-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "DELETE",
|
||||
"path": "/correspondence/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-286bd304cbc2b454c90fcd119b02aa47-f611583cbf3aacde-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/jobapplications/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-4cb171cc0edcfeee0f1b609c8feb8219-fb961ff6e8690e99-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "PUT",
|
||||
"path": "/jobapplications/1",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-35f21fc1525cd7f0d397f1b92bdab7b7-f59dbe90203598dc-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "PATCH",
|
||||
"path": "/jobapplications/1/followup",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-99cfcce88bf8e8fe44bb35f04ba88d48-122a6284b4914f60-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/jobapplications/1/history",
|
||||
"status": 200,
|
||||
"preview": "[{\"id\":1,\"type\":\"Created\",\"oldValue\":null,\"newValue\":null,\"note\":null,\"at\":\"2026-04-11T16:56:28.2097864\"}]",
|
||||
"headers": {
|
||||
"Content-Type": "application/json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/jobapplications/1/timeline",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-1aa65bd46ff7617ccc1dfeae45c45dc5-d275b3da1ba9d2c5-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/jobapplications/1/tailored-cv-draft",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-6e28c94856419924997eda53896f0e6c-fcf5a60dd2947b9a-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"path": "/jobapplications/1/followup-draft",
|
||||
"status": 404,
|
||||
"preview": "{\"type\":\"https://tools.ietf.org/html/rfc9110#section-15.5.5\",\"title\":\"Not Found\",\"status\":404,\"traceId\":\"00-f0ff37e63ea871aaeb34155de0d358bf-5f913a4b792c2e0d-00\"}",
|
||||
"headers": {
|
||||
"Content-Type": "application/problem+json; charset=utf-8",
|
||||
"Date": "Sat, 11 Apr 2026 15:02:23 GMT",
|
||||
"Server": "Kestrel",
|
||||
"Transfer-Encoding": "chunked"
|
||||
}
|
||||
}
|
||||
]
|
||||
Reference in New Issue
Block a user