security: P0 hardening — authorization, JWT secret, role escalation, document ownership, FIR access control#440
Open
techwallahexplorer wants to merge 5 commits into
Conversation
…ization CRITICAL FIX: Every endpoint was publicly accessible — authenticated or not. anyRequest().permitAll() is replaced with: - Explicit public whitelist for /api/auth/** and docs - /api/police/** restricted to POLICE/ADMIN roles - /api/judge/** restricted to JUDGE/ADMIN roles - All other requests require a valid JWT Also: @EnableMethodSecurity added for @PreAuthorize support on methods. CORS allowed headers narrowed from wildcard to explicit list.
…oint auth Finding 23 — Role self-assignment: req.getRole() accepted by register() allowed any caller to become JUDGE/POLICE/ADMIN. Now hardcoded to LITIGANT. Finding 2 — Unauthenticated face management: /face/enroll, /face/disable, /face/status accepted userId from request body/params with no authentication. All three now require JWT and derive userId from the authenticated principal. Finding 3 — User enumeration: /forgot-password returned different responses for registered vs unregistered emails. Now always returns the same body.
Finding 4 — Document exfiltration: /documents/{id}/download, /{id},
/{id}/analysis, /{id}/certificate had no ownership check — any authenticated
user could access any legal document by guessing its UUID.
All document endpoints now call isAuthorizedForDocument() before serving data.
Access policy: document owner | JUDGE | POLICE | ADMIN.
Internal error messages are no longer echoed to callers (stack trace leak).
…findAll() DoS Finding 6 — FIR access control: getPendingFirs() and updateFirStatus() had no role check, allowing any litigant to approve/reject their own FIR. Class-level @PreAuthorize("hasAnyRole('POLICE','ADMIN')") now enforces this on every method, with defence-in-depth beyond SecurityConfig path rules. Finding 6b — OOM DoS: getSummonsTasks() called caseRepository.findAll() loading the entire cases table into JVM heap, then filtered in-memory. Replaced with caseRepository.findBySummonsStatus("IN_TRANSIT") — requires adding the derived query method to CaseRepository (no custom SQL needed). Internal exception messages are no longer propagated to HTTP responses.
|
@techwallahexplorer is attempting to deploy a commit to the CodeBlooded's projects Team on Vercel. A member of the Team first needs to authorize it. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Security Hardening PR — P0 Critical Fixes
This PR addresses 5 critical (P0) security vulnerabilities identified in a production-readiness audit. The system was not safe for public deployment in its prior state. Each fix is documented with the specific attack vector it closes.
Fix 1 —
application.properties: Remove hardcoded JWT secretAttack: The JWT signing secret
nyaysetu-2024-secure-jwt-signing-key-minimum-256-bits-requiredwas committed to a public repository with a hardcoded default. Any attacker could forge a valid JWT for any user (includingJUDGE,ADMIN) using this string.Fix:
jwt.secret=${JWT_SECRET}— no default value. The application will now refuse to start ifJWT_SECRETis not set as an environment variable.Additional hardening in this file:
spring.main.lazy-initialization=false— config errors now surface at startup, not silently at first user requesthealth.show-details=never(wasalways— exposed DB and disk state)validate-on-migrate=truere-enabled (was disabled — schema drift went undetected)Fix 2 —
SecurityConfig.java: ReplaceanyRequest().permitAll()with role-based rulesAttack: Every API endpoint — FIR records, court documents, judge rulings, police investigations — was fully accessible without any authentication token. The JWT filter ran but its rejection was overridden by
permitAll().Fix: Explicit authorization rules:
@EnableMethodSecurityadded to enable@PreAuthorizeon individual methods for fine-grained control.Fix 3 —
AuthController.java: Prevent role self-assignment + fix face endpoint authAttack (Finding 23):
register()acceptedrolefrom the request body. Any caller could POST{"role": "JUDGE"}and receive a JUDGE-level JWT immediately.Fix:
rolefield from request body is intentionally ignored. All self-registrations are hardcoded toRole.LITIGANT.Attack (Finding 2):
/face/enroll,/face/disable,/face/statuswere unauthenticated and accepteduserIdfrom the request body/params. An attacker could enroll their own face as any judge's account, then log in as that judge.Fix: All three endpoints now require a valid JWT. The
userIdis derived fromauthentication.getName()— callers cannot target other accounts.Attack (Finding 3):
/forgot-passwordreturned different status codes and echoed the submitted email back, acting as a user enumeration oracle.Fix: Always returns the same
200response body regardless of whether the email is registered.Fix 4 —
DocumentManagementController.java: Add ownership checks to all document endpointsAttack (Finding 4):
/documents/{id}/download,/{id},/{id}/analysis,/{id}/certificatehad no ownership check. Any authenticated user could access sealed legal evidence for any case by guessing or enumerating UUIDs.Fix: All endpoints now call
isAuthorizedForDocument()before serving data.Access policy: document owner OR
JUDGEORPOLICEORADMIN.Internal exception messages are no longer forwarded to HTTP response bodies.
Fix 5 —
FirController.java: Enforce POLICE role on FIR operations + fix findAll() OOMAttack (Finding 6):
getPendingFirs()andupdateFirStatus()had no role check. A litigant who filed an FIR could approve or reject it themselves, or deny it to force a case to be dropped.Fix: Class-level
@PreAuthorize("hasAnyRole('POLICE', 'ADMIN')")enforces this on every method in the controller (defence-in-depth on top of SecurityConfig path rules).DoS fix:
getSummonsTasks()calledcaseRepository.findAll()— loading the entire cases table into JVM heap and filtering in-memory. At scale this causes OOM crashes. Replaced withcaseRepository.findBySummonsStatus("IN_TRANSIT").JWT_SECRETenvironment variable on Render/Railway/your platform before deploying. Generate with:openssl rand -hex 64findBySummonsStatustoCaseRepository(Fix 5 depends on it)DocumentDtohasgetUploadedBy()returning the owner'sLonguser ID (Fix 4 depends on it)