Skip to content
This repository was archived by the owner on Jul 6, 2026. It is now read-only.

Latest commit

 

History

History
363 lines (257 loc) · 18.4 KB

File metadata and controls

363 lines (257 loc) · 18.4 KB

Loopless — Security Audit

Date: May 2026 | Sprint 4 | Auditor: Enes Shehu


1. Secret Scanning — git-secrets / truffleHog

Setup

# Install truffleHog
pip install trufflehog

# Scan repo history
trufflehog filesystem . --only-verified

Findings

Severity Finding File Resolution
INFO USE_MOCK_AUTH=true string present components/auth/GitHubSSOButton.tsx:4 Dev-only flag, not a secret. Removed before prod.
INFO guest/guest in CLAUDE.md (RabbitMQ dev password) CLAUDE.md Dev compose default, not in any .env.
PASS No API keys, JWT secrets, or DB passwords committed All secrets in .env (gitignored) or Azure Key Vault

Result: No verified secrets in git history.

AIOps Payload Sanitization

Alertmanager webhook payloads routed through the aiops-triage service can contain database connection strings, JWT tokens, email addresses, and other PII embedded in Prometheus label values or alert annotations. A dedicated sanitization layer (devops/aiops/sanitize.js) runs before any alert data is forwarded to the OpenAI API.

Patterns redacted:

Pattern Replacement
postgres://…, redis://…, amqp://… connection strings [REDACTED_*_URI]
Bearer <token> header values [REDACTED_BEARER]
Three-segment JWTs (ey….ey….…) [REDACTED_JWT]
Email addresses [REDACTED_EMAIL]
Non-RFC-1918 public IPv4 addresses [REDACTED_PUBLIC_IP]
Label values matching password=, secret=, key=, token= [REDACTED_SECRET]

Only the alert name, severity label, and sanitized summary/description annotation are forwarded to OpenAI. All other labels and annotations (service, job, runbook URLs, etc.) are excluded from the prompt. See Section 9 for full AIOps data-handling policy.


2. Dependency Scan — Trivy

Trivy runs in CI (ci.yml → security job) on every push to main.

# .github/workflows/ci.yml (excerpt)
- name: Security scan
  uses: aquasecurity/trivy-action@master
  with:
    scan-type: fs
    scan-ref: .
    format: sarif
    output: trivy-results.sarif
    severity: CRITICAL,HIGH

Latest Scan Results (Sprint 4)

Package CVE Severity Fixed In Action
No CRITICAL/HIGH CVEs found

All dependencies pinned. dependabot.yml configured for weekly updates.


3. OWASP ZAP — Active Scan

Setup

docker run -t owasp/zap2docker-stable zap-baseline.py \
  -t http://localhost:8081 \
  -r zap-report.html

Findings

Risk Alert Endpoint Mitigation
LOW X-Content-Type-Options header missing All /api/* Add X-Content-Type-Options: nosniff in Nginx config
LOW X-Frame-Options not set All Add X-Frame-Options: DENY in Nginx
INFO Server version disclosure Server: header Set server_tokens off in Nginx (done in nginx.conf)
PASS SQL injection All Parameterized queries via EF Core
PASS XSS All React auto-escapes. CSP header via Nginx.
PASS IDOR /api/v1/projects/{id} Authorization policy on all endpoints
PASS Broken auth /api/v1/auth/* Keycloak JWT + rate limiting (10 req/min)

Nginx hardening applied (nginx.conf):

server_tokens off;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
add_header X-XSS-Protection "1; mode=block";
add_header Referrer-Policy strict-origin-when-cross-origin;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:;";

4. Lighthouse CI — Security Headers

npx @lhci/cli autorun --config=scripts/lighthouse.config.json

Results

Check Score Notes
Best Practices 95/100 HTTPS redirect configured
Does not use HTTPS PASS TLS via Nginx (self-signed for dev, Let's Encrypt for prod)
No vulnerable libraries PASS Trivy + Dependabot coverage

5. Rate Limiting

Redis-backed fixed-window rate limiting via custom RedisFixedWindowRateLimiter (StackExchange.Redis Lua INCR+EXPIRE). Each policy configures an independent Redis-failure strategy — see Redis failure behavior below.

Policies

Policy Limit Window Partition Applied To Redis Failure
global 60 req 1 min Client IP All endpoints (chained) fail-open
auth 10 req 1 min Client IP Auth/token endpoints fail-closed
ai 5 req 1 min User ID (JWT sub) POST /api/v1/projects/{id}/summary fail-closed
matching 30 req 1 min User ID (JWT sub) /api/v1/matching/discover, /api/v1/matching/search fail-open
public-read 30 req 1 min Client IP GET /api/v1/projects/{id} fail-open

Distribution

Redis key format: ratelimit:{policy}:{partition}. TTL = window duration. All API replicas share the same Redis counter — rate limit is enforced cluster-wide, not per-pod.

Response on limit exceeded: HTTP 429 Too Many Requests with Retry-After header.

Redis failure behavior

In a multi-pod Kubernetes deployment, per-pod in-memory counters are desynchronized — an undetected Redis outage effectively removes rate limit enforcement across the cluster. To mitigate this for high-risk endpoints:

  • auth and ai policies — fail-closed: When Redis is unreachable the limiter returns HTTP 503 Service Unavailable instead of granting the request. This trades availability for security at endpoints where unenforced abuse (credential stuffing, OpenAI cost exhaustion) is highest risk.
  • global, matching, and public-read policies — fail-open: Request is granted during Redis outages. These policies protect lower-severity surfaces where blocking all traffic would degrade user experience without proportional security benefit.

When Redis is not configured at all (single-replica dev setup), all policies fall back to in-memory counters regardless of the fail-closed setting.

Observability

A Prometheus counter tracks every Redis fallback event:

ratelimiter_redis_fallback_total{policy="auth|ai|matching|..."}

Alert on sustained non-zero rate of this counter to detect Redis degradation before it impacts the security posture of fail-closed policies. The metric is scraped by the existing Prometheus job targeting the backend (/metrics) and available in Grafana dashboards.

Bypass resistance

  • Partition is extracted from JWT sub for user-scoped policies — IP rotation does not help.
  • Global IP policy catches unauthenticated probing.
  • Auth and AI endpoints fail-closed during Redis outages — desynchronized pod counters cannot be exploited during cache degradation.

6. Authentication & Authorization Summary

Control Implementation Status
JWT validation Keycloak JWKS endpoint, validated on every request
RBAC 3 policies: FreelancerOnly, EnterpriseOnly, AdminOnly
Rate limiting Fixed: 60/min, Auth: 10/min per IP
Correlation IDs Every request tagged for tracing
Secret storage Azure Key Vault (prod), .env (dev, gitignored)
HTTPS Nginx TLS termination
DLQ Failed messages quarantined in RabbitMQ DLX
Audit trail Every mutation logged to AuditTrails table

7. Remediation Log

Date Issue Fix
May 2026 Missing security headers in Nginx Added X-Content-Type-Options, X-Frame-Options, CSP to nginx.conf
May 2026 Prometheus scraping dead containers Removed node-exporter + cadvisor scrape jobs from prometheus.yml

8. Security Tooling Re-run (final-state)

Execution timestamp: 2026-05-06
Status: Blocked in this execution environment (pwsh is not installed, so command execution tools cannot run scans).

Required commands for immediate rerun

cd "C:\Users\shehu\Desktop\New folder\Life-Group1\LIFE-Group1"

# Trivy (images + filesystem)
docker build -f devops/docker/backend/Dockerfile -t life-backend:local-scan backend
docker build -f devops/docker/frontend/Dockerfile -t life-frontend:local-scan frontend/client
trivy image --severity CRITICAL,HIGH life-backend:local-scan
trivy image --severity CRITICAL,HIGH life-frontend:local-scan
trivy image --exit-code 1 --severity CRITICAL life-backend:local-scan
trivy image --exit-code 1 --severity CRITICAL life-frontend:local-scan
trivy fs --severity CRITICAL,HIGH .

# Secret scanners
git secrets --install
git secrets --scan
trufflehog filesystem . --only-verified

# OWASP ZAP (staging)
docker run -t owasp/zap2docker-stable zap-baseline.py \
  -t https://staging.loopless.app \
  -r zap-staging-report.html

Pending evidence snippets (to paste after rerun)

Tool Snippet status Notes
Trivy image: backend Pending Capture vulnerability summary and CRITICAL gate exit status
Trivy image: frontend Pending Capture vulnerability summary and CRITICAL gate exit status
Trivy filesystem Pending Capture CRITICAL/HIGH summary
git-secrets Pending Capture pass/fail lines
truffleHog Pending Capture verified findings summary
OWASP ZAP staging Pending Capture alert counts by risk + key findings/remediations

CI gate enforcement (implemented)

  • /.github/workflows/ci.yml now builds life-backend:security and life-frontend:security in the security job.
  • The same job runs Trivy with severity: CRITICAL and exit-code: "1" against both images.
  • This enforces the "no CRITICAL vulnerabilities" gate before image publish/deploy stages.

4. OWASP ZAP Baseline Pentest

Setup

Tooling: ghcr.io/zaproxy/zaproxy:stable baseline (zap-baseline.py). Targets the dockerized stack on localhost. Passive only — no payload injection, no auth bypass attempts.

docker compose --profile core --profile observability up -d
until curl -sf http://localhost:8081/health/ready; do sleep 2; done

docker run --rm --network host \
  -v "$(pwd)/devops/security:/zap/wrk" \
  -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
  -t http://localhost:8081 \
  -r zap-baseline-report.html \
  -J zap-baseline-report.json \
  -m 5 -a

See devops/security/README.md for the full workflow.

Findings (baseline run 2026-05-21)

Risk Finding URL Resolution
Medium Content Security Policy header missing /, /api/v1/* Add Content-Security-Policy in Nginx; already configured for production via devops/nginx/nginx.prod.conf, mirror to dev.
Low X-Content-Type-Options: nosniff missing on API /api/v1/* Add via ASP.NET middleware app.UseSecurityHeaders() (in M5 hardening).
Low Strict-Transport-Security only on TLS routes / Acceptable — HSTS only meaningful over HTTPS; production proxy issues HSTS via certbot config.
Info Server fingerprint disclosed (X-Powered-By) /api/v1/* Remove via builder.WebHost.ConfigureKestrel(o => o.AddServerHeader = false).
Info Cookie SameSite missing on dev Keycloak login Configured for production realm; dev tolerated.

No High-risk findings. CI weekly cron (.github/workflows/security-scan.yml) re-runs against staging post-deploy.

CI integration

Workflow .github/workflows/security-scan.yml runs Mondays 03:00 UTC + manual dispatch. Uploads zap-baseline-api + zap-baseline-frontend artifacts. Issue auto-opened if new WARN/FAIL detected.


5. OWASP Top 10 (2021) Coverage Summary

ID Category Status Notes
A01 Broken access control RBAC enforced via Keycloak JWT + policy-based middleware; tenant filters in EF Core query filters (M5)
A02 Cryptographic failures TLS via Nginx + Let's Encrypt; secrets in Key Vault; passwords managed by Keycloak (Argon2)
A03 Injection EF Core parameterized queries; no raw SQL outside indexed pgvector helpers
A04 Insecure design Clean Architecture; validation at boundary; rate limiting global + per-endpoint
A05 Security misconfiguration ⚠️ CSP missing on dev — see ZAP finding above; production proxy headers verified
A06 Vulnerable components Trivy CI gate at CRITICAL; Dependabot enabled
A07 Auth & ID failures Keycloak realm with brute-force detection, password policy, refresh token rotation
A08 Software & data integrity release-please semver; signed CI artifacts via OIDC GHCR push; Helm chart pinned by digest
A09 Logging & monitoring failures Serilog → Loki + ELK; correlation IDs; Prometheus + AlertManager + AIOps triage
A10 SSRF Outbound HTTP only to allow-listed hosts (OpenAI, GitHub); no user-supplied URL fetches

9. AIOps Data Handling

Data flow

Prometheus → Alertmanager → POST /webhook/alerts (aiops-triage)
                                        │
                              sanitize.js redaction layer
                                        │
                              OpenAI chat completions API
                                        │
                              Slack / Discord webhook

What enters the aiops-triage service

Raw Alertmanager webhook payloads (JSON) containing the full alert struct:

  • All Prometheus labels (can include service, job, namespace, pod, arbitrary user-defined labels)
  • All annotations (can include summary, description, runbook_url, and any custom annotation set by alerting rules)
  • Alert metadata (status, startsAt, endsAt, generatorURL)

These payloads can contain sensitive data: connection strings embedded in stack traces, JWT tokens in log annotations, email addresses in owner labels, and public IP addresses.

Sanitization layer (devops/aiops/sanitize.js)

Before any alert data leaves the service boundary toward OpenAI, buildSanitizedAlertLines() performs:

  1. Connection string redactionpostgres://, redis://, amqp:// URIs replaced with typed placeholders.
  2. Token redactionBearer <value> header form and raw three-segment JWTs (ey….ey….…) are replaced.
  3. Email redaction — RFC-5321 address patterns replaced.
  4. Public IP redaction — IPv4 addresses outside RFC-1918 ranges (10/8, 172.16-31/12, 192.168/16) and loopback (127/8) replaced. Private/internal IPs are preserved for operational context.
  5. Secret label redaction — Values following password=, passwd=, secret=, api_key=, apikey=, token= replaced.

What is sent to OpenAI

Only the following fields are included in the prompt, after sanitization:

Field Source Sanitized
Alert index (1, 2, …) derived
Severity labels.severity no (enum value)
Alert name labels.alertname no (metric name)
Summary annotations.summary yes
Description (fallback) annotations.description yes

All other labels, annotations, runbook URLs, generator URLs, and timing metadata are excluded from the OpenAI prompt.

Slack / Discord notifications

The human-readable alert lines sent to Slack/Discord use the original buildAlertLines() function which includes service/job labels and runbook URLs — these go to internal operator channels only, not to third-party AI providers.

Test coverage

devops/aiops/sanitize.test.js covers all redaction rules with positive (must redact) and negative (must preserve private IPs and clean text) assertions. Run with:

cd devops/aiops && node --test sanitize.test.js

Residual risk

  • Alerting rules that embed raw stack traces in annotations.description may contain fragments not caught by the regex patterns (e.g., base64-encoded secrets). Teams should avoid putting stack traces directly in Alertmanager annotations.
  • The sanitization layer applies only to data forwarded to OpenAI. Internal Loki logs and the Slack/Discord payloads are not redacted — access to those channels is already restricted to operators.