Files
jobtrackingapp/docs/security-assessments/M014-security-remediation-verification.md
T

4.6 KiB

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

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

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.