This document summarizes the security measures that I implemented in the Task Manager API (V2) and includes reproducible PowerShell commands I use to validate them.
Scope: lightweight, safe checks only. No heavy load testing in production.
- Base URL: https://task-manager-api-njza.onrender.com
- Health: https://task-manager-api-njza.onrender.com/actuator/health
- Root status: https://task-manager-api-njza.onrender.com/
Note: Render Free can cold start on the first request.
POST /auth/loginreturns a JWT token ({ "token": "..." }).- Protected endpoints (e.g.,
/tasks/**,/ai/**) require:Authorization: Bearer <token>
- Expected behavior:
- Without token:
401 Unauthorized - With valid token:
200 OK
- Without token:
- I configured CORS to allow the GitHub Pages frontend.
- Preflight (
OPTIONS) is allowed, which avoids infinite loading in the browser.
- I added an in-memory rate limit per IP on
/auth/login:- 5 attempts per minute
- Exceeding limit returns
429 Too Many Requests
- I capped page size to prevent abse:
- Example:
size=999is limited tosize=50
- Example:
- I reviewed and standardized logs to avoid leaking secrets.
- No
Authorization,Bearer,token, orpasswordvalues should appear in logs.
- I enabled the Actuator health endpoint:
/actuator/healthshould reportUP
All commands below are intended to be run on Windows PowerShell.
$base = "https://task-manager-api-njza.onrender.com"
Invoke-RestMethod "$base/actuator/health"Expected result:
- JSON containing
status=UP
$base = "https://task-manager-api-njza.onrender.com"
Invoke-RestMethod "$base/"Expected result:
{"status":"ok","service":"task-manager-api"}
$base = "https://task-manager-api-njza.onrender.com"
$loginBody = @{ username = "admin"; password = "admin123" } | ConvertTo-Json
Invoke-RestMethod -Method Post -Uri "$base/auth/login" -ContentType "application/json" -Body $loginBodyExpected result:
- JSON with a
tokenfield
$base = "https://task-manager-api-njza.onrender.com"
try {
Invoke-RestMethod "$base/tasks?page=0&size=1"
} catch {
$_.Exception.Response.StatusCode.value__
}Expected result:
401
$base = "https://task-manager-api-njza.onrender.com"
$loginBody = @{ username = "admin"; password = "admin123" } | ConvertTo-Json
$token = (Invoke-RestMethod -Method Post -Uri "$base/auth/login" -ContentType "application/json" -Body $loginBody).token
Invoke-RestMethod -Method Get -Uri "$base/tasks?page=0&size=1" -Headers @{ Authorization = "Bearer $token" }Expected result:
- JSON response (paginated tasks) without error
$base = "https://task-manager-api-njza.onrender.com"
try {
Invoke-RestMethod -Method Post -Uri "$base/ai/suggest-priority" -ContentType "application/json" -Body '{"title":"Pay rent","description":"Due today"}'
} catch {
$_.Exception.Response.StatusCode.value__
}Expected result:
401
$base = "https://task-manager-api-njza.onrender.com"
$loginBody = @{ username = "admin"; password = "admin123" } | ConvertTo-Json
$token = (Invoke-RestMethod -Method Post -Uri "$base/auth/login" -ContentType "application/json" -Body $loginBody).token
Invoke-RestMethod -Method Post -Uri "$base/ai/suggest-priority" -Headers @{ Authorization = "Bearer $token" } -ContentType "application/json" -Body '{"title":"Pay rent","description":"Due today"}'Expected result:
- JSON with
priorityandreason
Run the command below 6 times quickly (same minute). You can repeat using ↑ (arrow up) + Enter.
$base = "https://task-manager-api-njza.onrender.com"
$body = @{ username = "admin"; password = "wrong" } | ConvertTo-Json
try { Invoke-RestMethod -Method Post -Uri "$base/auth/login" -ContentType "application/json" -Body $body }
catch { $_.Exception.Response.StatusCode.value__ }Expected result:
- First attempts:
401 - After threshold:
429
Local API:
http://localhost:8081
$local = "http://localhost:8081"
$loginBody = @{ username = "admin"; password = "admin123" } | ConvertTo-Json
$token = (Invoke-RestMethod -Method Post -Uri "$local/auth/login" -ContentType "application/json" -Body $loginBody).token
$r = Invoke-RestMethod -Method Get -Uri "$local/tasks?page=0&size=999" -Headers @{ Authorization = "Bearer $token" }
$r.pageExpected result:
sizeshows the effective cap (e.g.,size : 50)
This project runs locally via Docker Compose. Capture the API container logs and search for sensitive values.
cd C:\workspace\springboot-api
docker compose up -d --build
docker compose logs api --no-color > logs-console.txt
Select-String -Path .\logs-console.txt -Pattern "Authorization|Bearer|token|password" -SimpleMatchExpected result:
- No matches (no secrets leaked)
After validation, remove the file:
Remove-Item .\logs-console.txt- Production testing is lightweight to avoid impacting the Render Free service.
- For deeper testing, I use the controlled local environment (Docker Compose) and the planned pentest phase (Kali Linux, light recon + validation).