Skip to content

Security: GustavoMPrado/task-manager-api

Security

SECURITY.md

Security Policy & Evidence — Task Manager API

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.

Production endpoints (Render)

Note: Render Free can cold start on the first request.

Implemented security measures (V2)

Authentication & Authorization (JWT)

  • POST /auth/login returns 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

CORS (Frontend integration)

  • I configured CORS to allow the GitHub Pages frontend.
  • Preflight (OPTIONS) is allowed, which avoids infinite loading in the browser.

Rate limiting (login)

  • I added an in-memory rate limit per IP on /auth/login:
    • 5 attempts per minute
    • Exceeding limit returns 429 Too Many Requests

Pagination safeguards

  • I capped page size to prevent abse:
    • Example: size=999 is limited to size=50

Safe logging (no secrets)

  • I reviewed and standardized logs to avoid leaking secrets.
  • No Authorization, Bearer, token, or password values should appear in logs.

Observability

  • I enabled the Actuator health endpoint:
    • /actuator/health should report UP

Evidence (reproducible commands)

All commands below are intended to be run on Windows PowerShell.

1) Production health is UP

$base = "https://task-manager-api-njza.onrender.com"
Invoke-RestMethod "$base/actuator/health"

Expected result:

  • JSON containing status = UP

2) Production root status

$base = "https://task-manager-api-njza.onrender.com"
Invoke-RestMethod "$base/"

Expected result:

  • {"status":"ok","service":"task-manager-api"}

3) Login returns a token (Production)

$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 $loginBody

Expected result:

  • JSON with a token field

4) Protected endpoint without token returns 401 (Production)

$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

5) Protected endpoint with token returns 200 (Production)

$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

6) AI endpoint without token returns 401 (Production)

$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

7) AI endpoint with token returns 200 (Production)

$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 priority and reason

8) Rate limit evidence on /auth/login (Production)

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

9) Pagination cap evidence (Local)

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.page

Expected result:

  • size shows the effective cap (e.g., size : 50)

10) Logs do not leak secrets (Local)

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" -SimpleMatch

Expected result:

  • No matches (no secrets leaked)

After validation, remove the file:

Remove-Item .\logs-console.txt

Notes

  • 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).

There aren’t any published security advisories