Penetration Testing Research Report
Status: Research Document
Created: February 2026
Scope: Forma 3D Connect Staging Environment
Classification: Internal — Do Not Distribute
Table of Contents
- Executive Summary
- Scope and Target Inventory
- Methodology — Independent Pentest Approach
- Phase 1 — Reconnaissance and OSINT
- Phase 2 — DNS and Infrastructure Assessment
- Phase 3 — Web Application Testing
- Phase 4 — API Security Testing
- Phase 5 — Server-Side Assessment (With SSH Access)
- Phase 6 — Database Security Assessment
- Phase 7 — Container and Supply Chain Security
- Phase 8 — Authentication and Session Testing
- Phase 9 — Business Logic Testing
- Toolchain Reference
- Report Deliverables
- NIS2 Compliance Evaluation
- Recommended Pentest Schedule
- Cost Estimates and Vendor Options
1. Executive Summary
This document researches how to independently conduct a penetration test against the Forma 3D Connect staging environment. It covers the full lifecycle: reconnaissance, active scanning, exploitation attempts, server-side review, and compliance mapping against NIS2 (the EU Network and Information Security Directive 2).
Why Penetration Test Staging?
- Safe testing ground: Staging mirrors production architecture without risking real customer data
- NIS2 readiness: Directive requires regular security assessments for essential and important entities
- Proactive posture: Identify vulnerabilities before attackers do
- Compliance evidence: Documented pentest results serve as audit artifacts
Staging Environment Overview
| Component |
Target |
| Web Application |
https://staging-connect.forma3d.be |
| REST API |
https://staging-connect-api.forma3d.be |
| API Documentation |
https://staging-connect-api.forma3d.be/api/docs |
| pgAdmin |
https://staging-connect-db.forma3d.be |
| Droplet IP |
167.172.45.47 |
| Reverse Proxy |
Traefik v3 (TLS termination, Let's Encrypt) |
| Application |
NestJS (API) + React 19 (Web) |
| Database |
DigitalOcean Managed PostgreSQL (port 25060, SSL required) |
| DNS Provider |
forma3d.be domain |
| CI/CD |
Azure DevOps |
2. Scope and Target Inventory
2.1 In-Scope Targets
| Target |
Type |
Priority |
staging-connect.forma3d.be |
Web Application (React SPA) |
High |
staging-connect-api.forma3d.be |
REST API (NestJS) |
Critical |
staging-connect-db.forma3d.be |
pgAdmin Web Interface |
High |
167.172.45.47 |
DigitalOcean Droplet (Ubuntu) |
Critical |
forma3d.be |
DNS Zone |
Medium |
*.forma3d.be |
Subdomain Enumeration |
Medium |
| Docker containers on droplet |
Container Security |
High |
| Webhook endpoints |
Integration Security |
High |
2.2 Out-of-Scope
| Target |
Reason |
| Shopify infrastructure |
Third-party, separate bug bounty |
| SimplyPrint infrastructure |
Third-party |
| SendCloud infrastructure |
Third-party |
| DigitalOcean control plane |
Third-party, separate bug bounty |
| Production environment |
Risk of real data exposure |
2.3 Rules of Engagement
Before any pentest, define and document:
## Rules of Engagement
- Testing window: [Agreed dates and times]
- Emergency contact: [Phone/email for immediate escalation]
- Allowed actions: Scanning, enumeration, exploitation attempts on staging
- Forbidden actions: DoS attacks, data exfiltration of real PII, lateral movement to production
- Data handling: All findings stored encrypted, destroyed after report delivery
- Notification: Team notified before testing begins
- Rollback plan: Staging can be redeployed from pipeline if disrupted
3. Methodology — Independent Pentest Approach
3.1 Testing Standard
Follow the OWASP Testing Guide v4.2 and PTES (Penetration Testing Execution Standard) as the primary methodology frameworks. Map findings to OWASP Top 10 (2021) categories.
3.2 Testing Phases
┌─────────────────────────────────────────────────────────────────────┐
│ PENETRATION TEST LIFECYCLE │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ Phase 1 Phase 2 Phase 3 Phase 4 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Recon & │──▶│ DNS & │──▶│ Web App │──▶│ API │ │
│ │ OSINT │ │ Infra │ │ Testing │ │ Testing │ │
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
│ │
│ Phase 5 Phase 6 Phase 7 Phase 8 │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Server │──▶│ Database │──▶│ Container│──▶│ Auth & │ │
│ │ (SSH) │ │ Security │ │ & Supply │ │ Session │ │
│ └──────────┘ └──────────┘ │ Chain │ └──────────┘ │
│ └──────────┘ │
│ Phase 9 │
│ ┌──────────┐ ┌──────────┐ │
│ │ Business │──▶│ Report & │ │
│ │ Logic │ │ NIS2 Map │ │
│ └──────────┘ └──────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────┘
3.3 Two Perspectives
| Perspective |
Description |
Access Level |
| Black Box (External) |
No credentials, attacker perspective |
Public endpoints only |
| Grey Box (Authenticated + SSH) |
With application credentials and SSH access |
Full server access |
Both perspectives should be tested in sequence — black box first, then grey box.
4. Phase 1 — Reconnaissance and OSINT
4.1 Passive Reconnaissance
Gather intelligence without directly touching the target.
| Tool |
Purpose |
Command |
| theHarvester |
Email, subdomain, host discovery |
theHarvester -d forma3d.be -b all -l 500 |
| Shodan |
Exposed services, banners, CVEs |
Search: hostname:forma3d.be |
| Censys |
Certificate transparency, services |
Search: forma3d.be |
| crt.sh |
Certificate Transparency logs |
curl -s "https://crt.sh/?q=%25.forma3d.be&output=json" \| jq |
| Google Dorks |
Indexed sensitive files |
site:forma3d.be filetype:env OR filetype:sql OR filetype:log |
| Wayback Machine |
Historical endpoint discovery |
waybackurls forma3d.be |
| GitHub/GitLab Search |
Leaked credentials, config |
Search: "forma3d" password OR secret OR api_key |
| SecurityTrails |
DNS history, subdomains |
API or web interface |
What to Look For
- Leaked API keys, secrets, or
.env files in public repos
- Exposed admin panels or debug endpoints
- Certificate details revealing internal hostnames
- Historical endpoints that may still be active
- Email addresses for social engineering vectors
- Technology fingerprints (headers, JavaScript files)
4.2 Active Reconnaissance
Direct interaction with the target.
# Subdomain enumeration
subfinder -d forma3d.be -all -o subdomains.txt
amass enum -passive -d forma3d.be -o amass-subdomains.txt
# Combine and deduplicate
cat subdomains.txt amass-subdomains.txt | sort -u > all-subdomains.txt
# Resolve live subdomains
httpx -l all-subdomains.txt -o live-hosts.txt -status-code -title -tech-detect
# Screenshot all live hosts
gowitness file -f live-hosts.txt -P screenshots/
5. Phase 2 — DNS and Infrastructure Assessment
5.1 DNS Security Checks
| Check |
Tool |
Command |
| Zone Transfer |
dig |
dig axfr forma3d.be @ns1.provider.com |
| DNSSEC Validation |
dig |
dig forma3d.be +dnssec |
| SPF Record |
dig |
dig forma3d.be TXT \| grep spf |
| DMARC Record |
dig |
dig _dmarc.forma3d.be TXT |
| DKIM Record |
dig |
dig default._domainkey.forma3d.be TXT |
| MX Records |
dig |
dig forma3d.be MX |
| CAA Records |
dig |
dig forma3d.be CAA |
| NS Records |
dig |
dig forma3d.be NS |
| Subdomain Takeover |
subjack |
subjack -w all-subdomains.txt -t 100 -timeout 30 -o takeover.txt |
5.2 DNS Findings Checklist
## DNS Security Assessment
- [ ] Zone transfer blocked (AXFR denied)
- [ ] DNSSEC enabled and valid
- [ ] SPF record present and restrictive (ends with -all)
- [ ] DMARC record present with p=reject or p=quarantine
- [ ] DKIM configured for mail-sending domains
- [ ] CAA record restricts certificate issuance to authorized CAs
- [ ] No dangling CNAME records (subdomain takeover risk)
- [ ] No wildcard DNS records unless intentional
- [ ] NS records point to reputable provider
- [ ] No TXT records leaking sensitive information
5.3 Infrastructure Scanning
Port Scanning
# Full TCP scan of the droplet
nmap -sS -sV -sC -p- -oA nmap-full 167.172.45.47
# UDP top ports
nmap -sU --top-ports 100 -oA nmap-udp 167.172.45.47
# Aggressive scan with OS detection
nmap -A -T4 -oA nmap-aggressive 167.172.45.47
# Script scan for vulnerabilities
nmap --script vuln -oA nmap-vuln 167.172.45.47
Expected Open Ports (Staging)
| Port |
Service |
Expected |
Action if Unexpected |
| 22/tcp |
SSH |
Yes (UFW allowed) |
Verify key-only auth |
| 80/tcp |
HTTP |
Yes (Let's Encrypt challenge + redirect) |
Verify redirect to 443 |
| 443/tcp |
HTTPS |
Yes (Traefik) |
Test TLS configuration |
| 25060/tcp |
PostgreSQL |
No (managed, external) |
Should NOT be open on droplet |
| Other |
— |
No |
Investigate and close |
5.4 TLS/SSL Assessment
# Comprehensive TLS test
testssl.sh --html staging-connect-api.forma3d.be
# Alternative: sslyze
sslyze staging-connect-api.forma3d.be staging-connect.forma3d.be staging-connect-db.forma3d.be
# Quick SSL Labs grade (manual)
# Visit: https://www.ssllabs.com/ssltest/analyze.html?d=staging-connect-api.forma3d.be
TLS Checklist
## TLS Security Assessment
- [ ] TLS 1.2+ only (TLS 1.0/1.1 disabled)
- [ ] Strong cipher suites (no RC4, DES, 3DES, NULL)
- [ ] Perfect Forward Secrecy (PFS) enabled
- [ ] HSTS header present with long max-age
- [ ] HSTS includeSubDomains set
- [ ] HSTS preload set (or planned)
- [ ] Certificate valid and not near expiry
- [ ] Certificate chain complete
- [ ] No certificate transparency issues
- [ ] OCSP stapling enabled
- [ ] HTTP redirects to HTTPS (no mixed content)
- [ ] SSL Labs grade A or A+
6. Phase 3 — Web Application Testing
6.1 Automated Scanning
OWASP ZAP (Primary Scanner)
# Start ZAP in daemon mode
zap.sh -daemon -port 8080 -config api.key=your-api-key
# Spider the application
zap-cli spider https://staging-connect.forma3d.be
# Active scan
zap-cli active-scan https://staging-connect.forma3d.be
# Generate HTML report
zap-cli report -o zap-report.html -f html
Nuclei (Template-Based Scanner)
# Run all templates against web targets
nuclei -u https://staging-connect.forma3d.be -o nuclei-web.txt
nuclei -u https://staging-connect-api.forma3d.be -o nuclei-api.txt
nuclei -u https://staging-connect-db.forma3d.be -o nuclei-pgadmin.txt
# Run with specific severity
nuclei -u https://staging-connect.forma3d.be -severity critical,high -o nuclei-critical.txt
# Technology-specific templates
nuclei -u https://staging-connect-api.forma3d.be -tags nodejs,express,nestjs -o nuclei-nestjs.txt
Nikto (Web Server Scanner)
# Scan web and API endpoints
nikto -h https://staging-connect.forma3d.be -o nikto-web.html -Format html
nikto -h https://staging-connect-api.forma3d.be -o nikto-api.html -Format html
nikto -h https://staging-connect-db.forma3d.be -o nikto-pgadmin.html -Format html
6.2 Manual Web Application Testing (OWASP Top 10)
A01:2021 — Broken Access Control
| Test |
How |
Tool |
| Forced browsing |
Try accessing admin pages without auth |
Browser / Burp |
| IDOR |
Modify IDs in URLs/requests to access other users' data |
Burp Repeater |
| Privilege escalation |
Test if regular user can access admin endpoints |
Burp / curl |
| CORS misconfiguration |
Send requests with arbitrary Origin header |
curl / Burp |
| Directory traversal |
Attempt ../ in file paths |
Burp Intruder |
# Test CORS
curl -H "Origin: https://evil.com" -I https://staging-connect-api.forma3d.be/health
# Check: Access-Control-Allow-Origin should NOT be * or reflect arbitrary origins
# Test forced browsing
curl https://staging-connect-api.forma3d.be/api/v1/admin/users
# Should return 401/403 without valid session
A02:2021 — Cryptographic Failures
| Test |
How |
| Sensitive data over HTTP |
Verify all resources served over HTTPS |
| Weak hashing |
Check password storage algorithm (bcrypt expected) |
| Sensitive data in URL |
Check for tokens/passwords in query strings |
| Insecure cookies |
Verify Secure, HttpOnly, SameSite attributes |
A03:2021 — Injection
| Test |
How |
Tool |
| SQL injection |
Inject payloads in all input fields |
sqlmap / Burp |
| NoSQL injection |
Test JSON body parameters |
Burp |
| Command injection |
Test parameters that might interact with OS |
Manual |
| XSS (Reflected) |
Inject script tags in URL params |
Burp / dalfox |
| XSS (Stored) |
Inject payloads in stored fields |
Burp |
| Template injection |
Test for SSTI in rendered content |
tplmap |
# XSS scanning with dalfox
dalfox url "https://staging-connect.forma3d.be/search?q=test" -o dalfox-results.txt
# SQL injection testing (should fail — Prisma ORM prevents this)
sqlmap -u "https://staging-connect-api.forma3d.be/api/v1/orders?id=1" --batch --risk=3 --level=5
A04:2021 — Insecure Design
| Test |
How |
| Rate limiting bypass |
Test if rate limits can be bypassed via headers (X-Forwarded-For) |
| Business logic flaws |
Attempt to skip steps in order workflows |
| Missing anti-automation |
Check for CAPTCHA on auth endpoints |
A05:2021 — Security Misconfiguration
| Test |
How |
Tool |
| Security headers |
Check all response headers |
securityheaders.com / curl |
| Debug endpoints |
Check for /debug, /metrics, /swagger exposed |
ffuf |
| Default credentials |
Test common admin/admin combinations |
Manual |
| Stack traces |
Trigger errors, check for verbose messages |
Manual |
| Directory listing |
Check if directories are browsable |
Browser |
# Security headers check
curl -sI https://staging-connect-api.forma3d.be/health | grep -iE \
"strict-transport|content-security|x-frame|x-content-type|referrer-policy|permissions-policy"
# Directory/endpoint fuzzing
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
-u https://staging-connect-api.forma3d.be/FUZZ \
-mc 200,301,302,403 \
-o ffuf-api-dirs.json
# Check Swagger/API docs accessibility
curl -s https://staging-connect-api.forma3d.be/api/docs | head -20
A06:2021 — Vulnerable and Outdated Components
| Test |
How |
Tool |
| JavaScript libraries |
Identify client-side libraries and versions |
retire.js / Wappalyzer |
| Server framework |
Fingerprint NestJS version |
Response headers |
| Known CVEs |
Cross-reference versions with CVE databases |
Nuclei / Trivy |
A07:2021 — Identification and Authentication Failures
| Test |
How |
| Brute force |
Attempt multiple login failures, check lockout |
| Session fixation |
Check if session ID changes after login |
| Session timeout |
Verify sessions expire appropriately |
| Password policy |
Test weak passwords during registration |
| Credential stuffing |
Test common credential pairs |
A08:2021 — Software and Data Integrity Failures
| Test |
How |
| CI/CD pipeline security |
Review Azure DevOps pipeline configuration |
| Image signing |
Verify cosign signatures on container images |
| Dependency integrity |
Verify lockfile integrity |
| Webhook integrity |
Test HMAC verification bypass |
A09:2021 — Security Logging and Monitoring Failures
| Test |
How |
| Failed login logging |
Verify failed attempts are logged |
| Sensitive data in logs |
Check logs for PII or credentials |
| Log injection |
Attempt to inject log entries |
| Alerting |
Verify Sentry captures security events |
A10:2021 — Server-Side Request Forgery (SSRF)
| Test |
How |
| URL parameter manipulation |
Test parameters accepting URLs for SSRF |
| Webhook URL manipulation |
Attempt to register internal URLs as webhooks |
| DNS rebinding |
Test if internal IPs can be reached via DNS tricks |
6.3 pgAdmin-Specific Testing
The exposed pgAdmin instance at staging-connect-db.forma3d.be is a high-value target.
## pgAdmin Security Checklist
- [ ] Default credentials changed
- [ ] Brute force protection enabled
- [ ] Access restricted to authorized IPs (or behind VPN)
- [ ] HTTPS enforced
- [ ] Session timeout configured
- [ ] pgAdmin version is current (check for known CVEs)
- [ ] Server credentials not exposed in UI
- [ ] No public access to query tool
7. Phase 4 — API Security Testing
7.1 API Discovery
# Fetch OpenAPI/Swagger spec
curl -s https://staging-connect-api.forma3d.be/api/docs-json | jq > swagger.json
# Parse all endpoints
cat swagger.json | jq -r '.paths | keys[]' > api-endpoints.txt
# Generate Postman collection from Swagger
# Import swagger.json into Postman or use openapi-to-postman
| Tool |
Purpose |
Usage |
| Burp Suite Professional |
Comprehensive API proxy and scanner |
Primary testing proxy |
| Postman |
API request crafting and collections |
Organized endpoint testing |
| Dredd |
API contract testing (Swagger compliance) |
dredd swagger.json https://staging-connect-api.forma3d.be |
| Schemathesis |
Property-based API testing |
schemathesis run https://staging-connect-api.forma3d.be/api/docs-json |
| RESTler |
Stateful REST API fuzzing (Microsoft) |
Automated sequence-aware fuzzing |
7.3 API Security Test Matrix
| Category |
Test |
Method |
| Authentication |
Missing auth on protected endpoints |
Call each endpoint without API key/session |
| Authentication |
Invalid API key handling |
Send malformed/expired API keys |
| Authorization |
Horizontal privilege escalation |
Access other tenants' resources |
| Authorization |
Vertical privilege escalation |
Access admin endpoints as regular user |
| Input Validation |
Oversized payloads |
Send requests exceeding expected size |
| Input Validation |
Unexpected data types |
Send strings where numbers expected |
| Input Validation |
Special characters |
Unicode, null bytes, control characters |
| Input Validation |
Mass assignment |
Send additional fields not in DTO |
| Rate Limiting |
Exceeding rate limits |
Rapid-fire requests beyond 100/min |
| Rate Limiting |
Rate limit bypass |
X-Forwarded-For header manipulation |
| Webhook Security |
HMAC bypass |
Send webhook without valid signature |
| Webhook Security |
Replay attacks |
Resend captured webhooks |
| Webhook Security |
Payload tampering |
Modify webhook body, keep old signature |
| Error Handling |
Information disclosure |
Trigger errors, check for stack traces |
| Error Handling |
Verbose error messages |
Check if internal paths are exposed |
| Business Logic |
Order state manipulation |
Attempt invalid state transitions |
| Business Logic |
Duplicate processing |
Send duplicate webhook payloads |
7.4 Webhook Endpoint Testing
Webhooks are critical integration points. Test each one:
# Test Shopify webhook without HMAC
curl -X POST https://staging-connect-api.forma3d.be/api/v1/webhooks/shopify/orders/create \
-H "Content-Type: application/json" \
-d '{"test": true}'
# Expected: 401 or 403
# Test with invalid HMAC
curl -X POST https://staging-connect-api.forma3d.be/api/v1/webhooks/shopify/orders/create \
-H "Content-Type: application/json" \
-H "X-Shopify-Hmac-Sha256: invalidhash" \
-d '{"test": true}'
# Expected: 401 or 403
# Test SimplyPrint webhook without token
curl -X POST https://staging-connect-api.forma3d.be/api/v1/webhooks/simplyprint \
-H "Content-Type: application/json" \
-d '{"event": "test"}'
# Expected: 401 or 403
# Test SendCloud webhook without signature
curl -X POST https://staging-connect-api.forma3d.be/api/v1/webhooks/sendcloud \
-H "Content-Type: application/json" \
-d '{"action": "test"}'
# Expected: 401 or 403
7.5 Schemathesis — Automated API Fuzzing
# Install
pip install schemathesis
# Run stateful API testing against Swagger spec
schemathesis run https://staging-connect-api.forma3d.be/api/docs-json \
--stateful=links \
--hypothesis-max-examples=100 \
--report \
--output schemathesis-report.html
# With authentication header
schemathesis run https://staging-connect-api.forma3d.be/api/docs-json \
-H "X-API-Key: <internal-api-key>" \
--stateful=links \
--report
8. Phase 5 — Server-Side Assessment (With SSH Access)
When SSH credentials are provided, this is the most critical phase. It transitions from external testing to internal security audit.
8.1 Initial Access and Orientation
# Connect to server
ssh -i /path/to/key root@167.172.45.47
# System identification
uname -a
cat /etc/os-release
hostnamectl
8.2 System Hardening Audit
Operating System
| Check |
Command |
Expected |
| OS version and patches |
apt list --upgradable 2>/dev/null \| wc -l |
0 (fully patched) |
| Kernel version |
uname -r |
Latest stable |
| Automatic updates |
cat /etc/apt/apt.conf.d/20auto-upgrades |
Enabled |
| Running services |
systemctl list-units --type=service --state=running |
Minimal |
| Listening ports |
ss -tlnp |
Only 22, 80, 443 |
| Open files |
lsof -i -P -n |
No unexpected connections |
| Cron jobs |
crontab -l; ls /etc/cron.* |
No suspicious entries |
| SUID binaries |
find / -perm -4000 -type f 2>/dev/null |
Known binaries only |
| World-writable files |
find / -perm -0002 -type f 2>/dev/null |
Minimal |
| Users with shells |
grep -v nologin /etc/passwd \| grep -v false |
Only root and necessary users |
SSH Hardening
| Check |
Where |
Expected |
| Root login |
/etc/ssh/sshd_config |
PermitRootLogin prohibit-password or no |
| Password auth |
/etc/ssh/sshd_config |
PasswordAuthentication no |
| Key-only auth |
/etc/ssh/sshd_config |
PubkeyAuthentication yes |
| SSH protocol |
/etc/ssh/sshd_config |
Protocol 2 only |
| Idle timeout |
/etc/ssh/sshd_config |
ClientAliveInterval 300 |
| Max auth tries |
/etc/ssh/sshd_config |
MaxAuthTries 3 |
| Allowed users |
/etc/ssh/sshd_config |
AllowUsers configured |
| SSH port |
/etc/ssh/sshd_config |
22 (or non-standard) |
| Fail2ban |
systemctl status fail2ban |
Active and configured |
| SSH key strength |
ssh-keygen -l -f /path/to/key |
4096-bit RSA or Ed25519 |
# Automated SSH audit
ssh-audit 167.172.45.47
# Check SSH configuration
sshd -T | grep -iE "permit|password|pubkey|protocol|maxauth|alive"
# Check authorized keys
cat ~/.ssh/authorized_keys
# Verify only expected keys are present
Firewall
# Check UFW status and rules
ufw status verbose
# Expected rules:
# 22/tcp ALLOW IN Anywhere
# 80/tcp ALLOW IN Anywhere
# 443/tcp ALLOW IN Anywhere
# Check iptables directly
iptables -L -n -v
ip6tables -L -n -v
# Check for unexpected ACCEPT rules
8.3 Docker Security Assessment
# Docker version and configuration
docker version
docker info
# Check Docker daemon configuration
cat /etc/docker/daemon.json
# Verify: log rotation, no insecure registries, no experimental features
# List all containers (including stopped)
docker ps -a
# Check container resource limits
docker inspect --format='{{.Name}} CPU:{{.HostConfig.NanoCpus}} MEM:{{.HostConfig.Memory}}' $(docker ps -q)
# Check container user (should not be root)
docker inspect --format='{{.Name}} User:{{.Config.User}}' $(docker ps -q)
# Check container capabilities
docker inspect --format='{{.Name}} CapAdd:{{.HostConfig.CapAdd}} CapDrop:{{.HostConfig.CapDrop}}' $(docker ps -q)
# Check mounted volumes (sensitive file exposure)
docker inspect --format='{{.Name}} Mounts:{{json .Mounts}}' $(docker ps -q) | python3 -m json.tool
# Check container networks
docker network ls
docker network inspect forma3d-network
# Check if Docker socket is mounted in any container
docker inspect --format='{{.Name}} {{range .Mounts}}{{.Source}}{{end}}' $(docker ps -q) | grep docker.sock
# Check for privileged containers
docker inspect --format='{{.Name}} Privileged:{{.HostConfig.Privileged}}' $(docker ps -q)
# Check Docker image versions for CVEs
trivy image registry.digitalocean.com/forma-3d/forma3d-connect-api:latest
trivy image registry.digitalocean.com/forma-3d/forma3d-connect-web:latest
8.4 Environment and Secrets Audit
# Check .env file permissions
ls -la /opt/forma3d/.env
# Should be 600 (owner read/write only)
# Check for secrets in environment variables of running containers
docker inspect --format='{{.Name}} {{.Config.Env}}' $(docker ps -q)
# WARNING: This exposes secrets — document but handle with care
# Check for .env files in unexpected locations
find / -name ".env" -o -name "*.env" 2>/dev/null
# Check for hardcoded credentials in Docker Compose
grep -i "password\|secret\|key\|token" /opt/forma3d/docker-compose.yml
# Check bash history for leaked secrets
cat ~/.bash_history | grep -iE "password|secret|key|token|curl.*-H"
# Check for sensitive files
find /opt/forma3d -name "*.pem" -o -name "*.key" -o -name "*.crt" 2>/dev/null
ls -la /opt/forma3d/certs/
8.5 Log Analysis
# Check authentication logs
grep "Failed password\|Invalid user\|Accepted" /var/log/auth.log | tail -50
# Check for brute force attempts
grep "Failed password" /var/log/auth.log | awk '{print $11}' | sort | uniq -c | sort -rn | head
# Check Docker logs for security events
docker logs forma3d-api 2>&1 | grep -iE "unauthorized|forbidden|error|fail" | tail -50
# Check Traefik access logs
docker logs forma3d-traefik 2>&1 | grep -E "4[0-9]{2}|5[0-9]{2}" | tail -50
# Check for suspicious network connections
ss -tlnp
netstat -antp 2>/dev/null
# Check system logs
journalctl --since "24 hours ago" | grep -iE "error|fail|denied|unauthorized"
Lynis (System Security Auditing)
# Install and run Lynis
apt install lynis -y
lynis audit system --quick
# Or use Docker
docker run --rm -v /:/hostroot:ro cisofy/lynis audit system --auditor "Pentest" --quick
# Review results
cat /var/log/lynis-report.dat
CIS Benchmark
# Download and run CIS benchmark scripts (Docker CIS Benchmark)
docker run --rm -it \
--net host --pid host \
--userns host --cap-add audit_control \
-e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
-v /etc:/etc:ro \
-v /usr/bin/containerd:/usr/bin/containerd:ro \
-v /usr/bin/runc:/usr/bin/runc:ro \
-v /usr/lib/systemd:/usr/lib/systemd:ro \
-v /var/lib:/var/lib:ro \
-v /var/run/docker.sock:/var/run/docker.sock:ro \
--label docker_bench_security \
docker/docker-bench-security
8.7 Server-Side Checklist
## Server Security Assessment Checklist
### OS Hardening
- [ ] OS fully patched and up to date
- [ ] Automatic security updates enabled
- [ ] Unnecessary services disabled
- [ ] Kernel hardening parameters set (sysctl)
- [ ] No unnecessary SUID/SGID binaries
- [ ] File permissions correctly set
### SSH
- [ ] Key-based authentication only (no password auth)
- [ ] Root login restricted
- [ ] Fail2ban or equivalent active
- [ ] SSH idle timeout configured
- [ ] Only authorized keys present
- [ ] SSH protocol 2 only
### Firewall
- [ ] UFW/iptables active
- [ ] Only ports 22, 80, 443 open
- [ ] Default deny policy
- [ ] IPv6 rules configured
### Docker
- [ ] Containers run as non-root user
- [ ] No privileged containers
- [ ] No Docker socket mounted in containers
- [ ] Resource limits configured
- [ ] Log rotation configured
- [ ] Base images scanned for CVEs
- [ ] No unnecessary capabilities
### Secrets
- [ ] .env file permissions 600
- [ ] No secrets in bash history
- [ ] No secrets in Docker Compose files (using env_file)
- [ ] No secrets in container image layers
- [ ] Certificate files properly secured
### Logging
- [ ] Auth logs monitored
- [ ] No brute force patterns detected
- [ ] Docker logs rotated
- [ ] No sensitive data in logs
9. Phase 6 — Database Security Assessment
9.1 External Assessment
The database is a DigitalOcean Managed PostgreSQL instance, not directly on the droplet.
# Verify database is NOT accessible from public internet
nmap -p 25060 db-postgresql-ams3-forma3d-staging-do-user-1196005-0.m.db.ondigitalocean.com
# Check SSL enforcement
psql "postgresql://doadmin@db-postgresql-ams3-forma3d-staging-do-user-1196005-0.m.db.ondigitalocean.com:25060/defaultdb?sslmode=disable" -c "SELECT 1"
# Should FAIL if SSL is enforced
# Check SSL connection
psql "postgresql://doadmin:<password>@db-postgresql-ams3-forma3d-staging-do-user-1196005-0.m.db.ondigitalocean.com:25060/defaultdb?sslmode=require" -c "SELECT version();"
9.2 Internal Assessment (Via SSH/Docker)
# Connect via the API container's Prisma
docker exec -it forma3d-api npx prisma db execute --stdin <<< "
SELECT usename, usesuper, usecreatedb, passwd IS NOT NULL as has_password
FROM pg_user;
"
# Check database roles and permissions
docker exec -it forma3d-api npx prisma db execute --stdin <<< "
SELECT grantee, table_schema, table_name, privilege_type
FROM information_schema.role_table_grants
WHERE grantee NOT IN ('pg_monitor', 'pg_read_all_settings');
"
# Check for sensitive data in plain text
# (Review schema for columns that should be encrypted)
docker exec -it forma3d-api npx prisma db execute --stdin <<< "
SELECT column_name, data_type, table_name
FROM information_schema.columns
WHERE column_name ILIKE '%password%'
OR column_name ILIKE '%secret%'
OR column_name ILIKE '%token%'
OR column_name ILIKE '%key%';
"
9.3 Database Security Checklist
## Database Security Checklist
- [ ] SSL/TLS enforced for all connections
- [ ] Minimum privileges for application user
- [ ] No superuser access from application
- [ ] Sensitive fields encrypted at rest (tokens, OAuth data)
- [ ] Connection pooling configured (prevent connection exhaustion)
- [ ] Trusted sources / IP allowlist configured on managed DB
- [ ] Database backups enabled and tested
- [ ] No raw SQL queries (Prisma ORM only)
- [ ] pgAdmin access restricted (IP, credentials, session timeout)
10. Phase 7 — Container and Supply Chain Security
10.1 Image Security
# Verify cosign signature on images
cosign verify --key cosign.pub registry.digitalocean.com/forma-3d/forma3d-connect-api:latest
cosign verify --key cosign.pub registry.digitalocean.com/forma-3d/forma3d-connect-web:latest
# Scan images for vulnerabilities
trivy image --severity HIGH,CRITICAL registry.digitalocean.com/forma-3d/forma3d-connect-api:latest
trivy image --severity HIGH,CRITICAL registry.digitalocean.com/forma-3d/forma3d-connect-web:latest
# Check SBOM (Software Bill of Materials)
cosign verify-attestation --key cosign.pub \
--type cyclonedx \
registry.digitalocean.com/forma-3d/forma3d-connect-api:latest
# Analyze Dockerfile for security issues
hadolint apps/api/Dockerfile
hadolint apps/web/Dockerfile
# Check for secrets in image layers
docker history --no-trunc registry.digitalocean.com/forma-3d/forma3d-connect-api:latest
# Look for ENV/ARG with secrets
10.2 Dependency Security
# Audit npm dependencies
pnpm audit --audit-level=moderate
# Check for outdated packages
pnpm outdated
# Scan with Snyk (if available)
snyk test --all-projects
# Check for known vulnerabilities in lockfile
npm audit --json | jq '.vulnerabilities | to_entries[] | select(.value.severity == "critical" or .value.severity == "high")'
10.3 Supply Chain Checklist
## Supply Chain Security Checklist
- [ ] Container images signed with cosign
- [ ] SBOM generated and attached as attestation
- [ ] No critical/high CVEs in base images
- [ ] No critical/high CVEs in npm dependencies
- [ ] Lockfile (pnpm-lock.yaml) committed and integrity verified
- [ ] Dockerfile uses specific image tags (not :latest in production)
- [ ] Multi-stage builds used (no build tools in final image)
- [ ] Non-root user in container
- [ ] No secrets in image layers
- [ ] CI/CD pipeline integrity (branch protection, signed commits)
11. Phase 8 — Authentication and Session Testing
11.1 Login Flow Testing
| Test |
Method |
Expected |
| Valid login |
POST with correct credentials |
200 + session cookie |
| Invalid password |
POST with wrong password |
401, no session |
| Empty credentials |
POST with empty fields |
400 validation error |
| SQL injection in login |
' OR 1=1-- in email field |
400/401, no bypass |
| Brute force (10 attempts) |
Rapid login attempts |
Rate limited after N |
| Account lockout |
20+ failed attempts |
Account locked or increasing delays |
| Session cookie attributes |
Inspect Set-Cookie header |
Secure, HttpOnly, SameSite=Lax/Strict |
| Session fixation |
Check if session ID changes after login |
New session ID issued |
| Session timeout |
Wait beyond SESSION_MAX_AGE_DAYS |
Session expired |
| Concurrent sessions |
Login from two browsers |
Both valid or old one invalidated |
| Logout |
POST to logout endpoint |
Session cookie cleared/invalidated |
11.2 API Key Testing
| Test |
Method |
Expected |
| Valid API key |
X-API-Key: valid-key |
200 |
| Missing API key |
No header |
401 |
| Invalid API key |
X-API-Key: invalid |
401 |
| Expired/rotated key |
Old API key |
401 |
| Key in URL |
?api_key=value |
Should NOT work (header only) |
| Key bruteforce |
Rapid attempts with random keys |
Rate limited |
12. Phase 9 — Business Logic Testing
12.1 Order Flow Testing
| Test |
Description |
Risk |
| Order state manipulation |
Attempt to move order from DELIVERED back to PENDING |
Data integrity |
| Duplicate order creation |
Send same webhook twice rapidly |
Financial impact |
| Price manipulation |
Modify order amounts in transit |
Financial impact |
| Order for another tenant |
Access orders belonging to different shop |
Data breach |
| Mass order creation |
Flood system with fake orders |
DoS |
12.2 Print Job Flow Testing
| Test |
Description |
Risk |
| Job cancellation race |
Cancel while printing starts |
State corruption |
| Job assignment to wrong printer |
Manipulate printer IDs |
Business disruption |
| File upload manipulation |
If applicable, upload non-STL files |
System compromise |
12.3 Webhook Replay and Timing
| Test |
Description |
Risk |
| Webhook replay attack |
Resend captured Shopify webhook |
Duplicate processing |
| Webhook timing attack |
Send many webhooks simultaneously |
Race conditions |
| Out-of-order webhooks |
Send fulfillment before order create |
Logic errors |
| Idempotency bypass |
Modify idempotency key |
Duplicate processing |
| Tool |
Category |
Purpose |
Install |
| nmap |
Network |
Port scanning, service detection |
apt install nmap |
| testssl.sh |
TLS |
Comprehensive TLS testing |
git clone https://github.com/drwetter/testssl.sh |
| OWASP ZAP |
Web App |
Web vulnerability scanner |
docker pull zaproxy/zap-stable |
| Nuclei |
Multi |
Template-based vulnerability scanner |
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest |
| ffuf |
Web App |
Web fuzzer / directory brute force |
go install github.com/ffuf/ffuf/v2@latest |
| sqlmap |
Injection |
SQL injection testing |
pip install sqlmap |
| nikto |
Web Server |
Web server vulnerability scanner |
apt install nikto |
| Trivy |
Container |
Image vulnerability scanner |
apt install trivy |
| Lynis |
System |
Security auditing |
apt install lynis |
| subfinder |
Recon |
Subdomain enumeration |
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest |
| httpx |
Recon |
HTTP probing |
go install github.com/projectdiscovery/httpx/cmd/httpx@latest |
| Schemathesis |
API |
Property-based API testing |
pip install schemathesis |
| ssh-audit |
SSH |
SSH configuration auditing |
pip install ssh-audit |
| cosign |
Supply Chain |
Container signing verification |
go install github.com/sigstore/cosign/v2/cmd/cosign@latest |
| hadolint |
Container |
Dockerfile linter |
docker pull hadolint/hadolint |
| dalfox |
XSS |
XSS scanner |
go install github.com/hahwul/dalfox/v2@latest |
| docker-bench-security |
Container |
CIS Docker Benchmark |
Docker image |
| theHarvester |
OSINT |
Email/subdomain harvesting |
pip install theHarvester |
| amass |
Recon |
Subdomain enumeration |
go install github.com/owasp-amass/amass/v4/...@master |
| Tool |
Category |
Purpose |
Cost |
| Burp Suite Professional |
Web/API |
Comprehensive web testing proxy |
~$449/year |
| Nessus Professional |
Vulnerability |
Network vulnerability scanner |
~$3,590/year |
| Snyk |
Dependencies |
Dependency and container scanning |
Free tier available |
| Metasploit Pro |
Exploitation |
Exploitation framework |
~$15,000/year |
13.3 Kali Linux — Pre-Built Pentest Environment
For a self-contained pentest lab, use Kali Linux which includes most tools pre-installed:
# Run Kali as Docker container for quick testing
docker run -it --rm kalilinux/kali-rolling /bin/bash
apt update && apt install -y kali-tools-web kali-tools-information-gathering
# Or use a Kali VM for full GUI tools
# Download from: https://www.kali.org/get-kali/
14. Report Deliverables
14.1 Report Types
A complete penetration test produces the following deliverables:
| Report |
Audience |
Contents |
| Executive Summary (2-3 pages) |
Management, CTO |
Risk overview, critical findings, business impact, compliance status |
| Technical Report (20-50 pages) |
Development team |
Detailed findings, reproduction steps, evidence, remediation guidance |
| Vulnerability Register (Spreadsheet) |
Project management |
All findings with CVSS scores, status tracking, assignment |
| NIS2 Compliance Gap Analysis |
Compliance officer |
Mapping of findings to NIS2 requirements |
| Remediation Roadmap |
Engineering lead |
Prioritized fix plan with effort estimates |
| Retest Report (after fixes) |
All stakeholders |
Verification that vulnerabilities are resolved |
14.2 Finding Classification (CVSS 3.1)
| Severity |
CVSS Score |
SLA to Fix |
Example |
| Critical |
9.0 - 10.0 |
24-48 hours |
RCE, SQL injection, auth bypass |
| High |
7.0 - 8.9 |
7 days |
Privilege escalation, sensitive data exposure |
| Medium |
4.0 - 6.9 |
30 days |
XSS, CSRF, information disclosure |
| Low |
0.1 - 3.9 |
90 days |
Missing headers, verbose errors |
| Informational |
0.0 |
Best effort |
Best practice recommendations |
14.3 Individual Finding Template
Each finding should be documented using this structure:
## Finding: [VULN-001] [Title]
### Metadata
| Field | Value |
| -------------- | ------------------------------------- |
| Severity | Critical / High / Medium / Low / Info |
| CVSS Score | X.X |
| CVSS Vector | AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H |
| OWASP Category | A01:2021 Broken Access Control |
| Status | Open / Remediated / Accepted Risk |
| Affected Asset | staging-connect-api.forma3d.be |
| CWE | CWE-XXX |
### Description
[Clear description of the vulnerability]
### Impact
[Business impact if exploited]
### Steps to Reproduce
1. [Step 1]
2. [Step 2]
3. [Step 3]
### Evidence
[Screenshots, request/response pairs, tool output]
### Remediation
**Recommended Fix:**
[Specific code or configuration change]
**Alternative Mitigations:**
[Temporary workarounds if immediate fix is not possible]
### References
- [Link to CWE]
- [Link to OWASP reference]
14.4 Executive Summary Template
# Penetration Test — Executive Summary
**Client:** Forma 3D / DevGem
**Target:** Forma 3D Connect Staging Environment
**Date:** [Test Date]
**Tester:** [Name/Organization]
**Classification:** Confidential
## Overall Risk Rating: [Critical / High / Medium / Low]
## Scope
- Web application, REST API, DNS, server infrastructure (with SSH access)
## Summary of Findings
| Severity | Count |
| ------------- | ----- |
| Critical | X |
| High | X |
| Medium | X |
| Low | X |
| Informational | X |
| **Total** | **X** |
## Top 3 Critical Findings
1. [Finding title and brief impact]
2. [Finding title and brief impact]
3. [Finding title and brief impact]
## Key Recommendations
1. [Top priority action]
2. [Second priority action]
3. [Third priority action]
## NIS2 Compliance Status
[Brief assessment of compliance posture]
## Next Steps
- [ ] Remediate critical and high findings within SLA
- [ ] Schedule retest in [X] weeks
- [ ] Update security documentation
15. NIS2 Compliance Evaluation
15.1 What is NIS2?
The Network and Information Security Directive 2 (NIS2) — EU Directive 2022/2555 — is the EU's updated cybersecurity legislation that came into force on January 16, 2023 with member state transposition deadline of October 17, 2024. It significantly expands the scope and requirements of the original NIS Directive.
NIS2 applies to essential and important entities. Even if Forma 3D is not directly classified as such, it may fall under NIS2 if:
- It provides services to entities that are classified (supply chain obligations)
- It operates as a digital infrastructure or digital service provider
- Belgian national implementation (the "NIS2 wet") may have broader scope
Assessment: As a B2B SaaS platform handling e-commerce order fulfillment, Forma 3D Connect likely falls under "important entity" as a digital service provider, depending on revenue and employee thresholds. Regardless, adopting NIS2 measures is a competitive advantage and aligns with customer expectations.
15.3 NIS2 Article 21 — Security Measures Mapping
Article 21 defines the minimum cybersecurity risk-management measures. Here is how to evaluate each during a penetration test:
| NIS2 Requirement (Art. 21.2) |
What to Test |
How to Evaluate |
Current Status |
| (a) Risk analysis and information system security policies |
Security policies documented? Risk register maintained? |
Review documentation, interview team |
Check docs/ folder, security-audit-checklist.md |
| (b) Incident handling |
Incident response plan exists? Tested? |
Review IR procedures, check Sentry alerting |
Check disaster-recovery-research.md |
| © Business continuity and crisis management |
Backup and DR plans? Tested? |
Review backup procedures, RPO/RTO defined |
Check disaster-recovery-research.md |
| (d) Supply chain security |
Third-party risk assessed? Dependencies audited? |
Check cosign, SBOM, dependency scanning |
Cosign signing, pnpm audit |
| (e) Security in network and information systems acquisition, development and maintenance |
Secure SDLC? Code review? Testing? |
Review CI/CD pipeline, security gates |
Azure DevOps pipeline, lint/test gates |
| (f) Policies and procedures for assessing effectiveness of cybersecurity risk-management measures |
Regular pentests? Audits? Metrics? |
This pentest itself evaluates this |
This document |
| (g) Basic cyber hygiene practices and cybersecurity training |
Developer security training? Secure coding practices? |
Interview team, review .cursorrules |
.cursorrules enforces secure patterns |
| (h) Policies and procedures regarding the use of cryptography and encryption |
Encryption at rest and transit? Key management? |
Test TLS, check token encryption, review key management |
TLS via Traefik, AES-256-GCM for tokens |
| (i) Human resources security, access control policies and asset management |
RBAC? Least privilege? Asset inventory? |
Test authorization, review access controls |
RBAC implemented, keys-certificates-inventory.md |
| (j) Use of multi-factor authentication or continuous authentication solutions |
MFA implemented? SSO? |
Test authentication flow |
Evaluate current auth mechanism |
15.4 NIS2 Compliance Evaluation Checklist
## NIS2 Compliance Evaluation
### Governance and Risk Management
- [ ] Information security policy documented and approved
- [ ] Risk assessment methodology defined
- [ ] Risk register maintained and regularly updated
- [ ] Management accountability for cybersecurity (board-level)
- [ ] Security roles and responsibilities defined
### Incident Handling (Art. 21.2b + Art. 23)
- [ ] Incident response plan documented
- [ ] Incident classification scheme (severity levels)
- [ ] 24-hour early warning to CSIRT for significant incidents
- [ ] 72-hour incident notification with initial assessment
- [ ] 1-month final report for significant incidents
- [ ] Incident response team identified
- [ ] Incident response tested (tabletop or simulation)
- [ ] Contact details for national CSIRT known (Belgium: CCB/CERT.be)
### Business Continuity (Art. 21.2c)
- [ ] Business continuity plan documented
- [ ] Backup strategy implemented and tested
- [ ] Recovery Time Objective (RTO) defined
- [ ] Recovery Point Objective (RPO) defined
- [ ] Disaster recovery procedures documented
- [ ] Business continuity plan tested annually
### Supply Chain Security (Art. 21.2d)
- [ ] Third-party risk assessment for critical suppliers
- [ ] Security requirements in supplier contracts
- [ ] SBOM (Software Bill of Materials) generated
- [ ] Container images signed (cosign)
- [ ] Dependency vulnerability scanning automated
- [ ] Supplier security incidents monitored
### Secure Development (Art. 21.2e)
- [ ] Secure coding guidelines documented (.cursorrules)
- [ ] Code review process in place
- [ ] Automated security testing in CI/CD (SAST)
- [ ] Vulnerability disclosure process defined
- [ ] Patch management process defined
- [ ] Change management process documented
### Security Assessment (Art. 21.2f)
- [ ] Regular penetration testing (at least annually)
- [ ] Vulnerability scanning (at least quarterly)
- [ ] Security metrics tracked (MTTD, MTTR)
- [ ] Audit trail maintained
- [ ] Findings tracked to remediation
### Cyber Hygiene (Art. 21.2g)
- [ ] Security awareness training for all team members
- [ ] Secure password policy enforced
- [ ] Phishing awareness
- [ ] Clean desk / clean screen policy
- [ ] Device management policy
### Cryptography (Art. 21.2h)
- [ ] Encryption policy documented
- [ ] TLS 1.2+ enforced for all communications
- [ ] Sensitive data encrypted at rest
- [ ] Key management procedures documented
- [ ] Certificate lifecycle management
- [ ] Cryptographic key rotation schedule
### Access Control (Art. 21.2i)
- [ ] Role-based access control (RBAC) implemented
- [ ] Least privilege principle enforced
- [ ] User access regularly reviewed
- [ ] Privileged access management
- [ ] Asset inventory maintained
- [ ] Offboarding process revokes all access
### Authentication (Art. 21.2j)
- [ ] Multi-factor authentication for administrative access
- [ ] MFA for remote access (SSH, VPN)
- [ ] Strong password requirements enforced
- [ ] Session management secure (timeout, invalidation)
- [ ] API authentication using secure methods (not basic auth)
15.5 NIS2 Reporting Obligations
If a significant incident occurs, NIS2 requires strict reporting timelines:
Incident Detected
│
▼
┌────────────────────────┐
│ 24 Hours: Early │
│ Warning to CSIRT │──── "Is this a significant incident?"
│ (Belgium: CCB) │ Cross-border impact? Large-scale?
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ 72 Hours: Incident │
│ Notification │──── Initial assessment, severity,
│ + Initial Assessment │ impact, IoCs
└────────────┬───────────┘
│
▼
┌────────────────────────┐
│ 1 Month: Final │
│ Report │──── Root cause, remediation,
│ │ cross-border impact
└────────────────────────┘
Belgium-specific: The Centre for Cybersecurity Belgium (CCB) is the competent authority. Report via https://ccb.belgium.be.
15.6 NIS2 Gap Analysis Template
| # |
NIS2 Requirement |
Current Maturity |
Gap |
Priority |
Remediation |
| 1 |
Risk analysis policy |
Partial — docs exist but no formal policy |
Need formal security policy signed by management |
High |
Draft and approve security policy document |
| 2 |
Incident handling |
Partial — Sentry + DR doc exist |
Need formal IR plan, CSIRT contact, reporting procedures |
Critical |
Create IR plan, register with CCB |
| 3 |
Business continuity |
Good — DR research completed |
Need regular testing of backups |
Medium |
Schedule quarterly backup restore test |
| 4 |
Supply chain security |
Good — cosign, SBOM, pnpm audit |
Need formal supplier risk assessment |
Medium |
Create supplier security questionnaire |
| 5 |
Secure development |
Good — CI/CD with tests, .cursorrules |
SBOM + CVE scanning via Syft/Grype; SonarCloud for SAST |
Low |
Verify SonarCloud security hotspot coverage |
| 6 |
Security assessment effectiveness |
Starting — this research document |
Need regular pentest schedule |
High |
Schedule annual pentest |
| 7 |
Cyber hygiene training |
Unknown |
Need training program |
High |
Implement security awareness training |
| 8 |
Cryptography policy |
Partial — TLS + encryption implemented |
Need documented encryption policy |
Medium |
Document encryption standards |
| 9 |
Access control |
Good — RBAC implemented |
Need access review process |
Medium |
Implement quarterly access reviews |
| 10 |
MFA |
Not implemented |
Critical gap for admin access |
Critical |
Implement MFA for all admin accounts |
16. Recommended Pentest Schedule
16.1 Annual Testing Calendar
| Quarter |
Activity |
Scope |
Type |
| Q1 (Jan-Mar) |
Full penetration test |
All targets, including SSH |
External + Internal |
| Q2 (Apr-Jun) |
Automated vulnerability scan |
Web, API, containers |
Automated |
| Q3 (Jul-Sep) |
Focused retest + new features |
Changed components |
Targeted |
| Q4 (Oct-Dec) |
Automated scan + NIS2 audit |
Full scope + compliance |
Automated + Audit |
16.2 Trigger-Based Testing
Additional testing should occur when:
| Trigger |
Testing Scope |
| Major release |
Affected endpoints and features |
| New integration added |
Integration security + webhooks |
| Infrastructure change |
Server and network assessment |
| Security incident |
Full scope retest |
| After NIS2 audit finding |
Specific remediation verification |
17. Cost Estimates and Vendor Options
17.1 DIY Pentest (Internal)
| Item |
Cost |
Notes |
| Burp Suite Professional |
~$449/year |
Essential tool |
| Kali Linux VM |
Free |
Pre-installed tools |
| Time investment |
40-80 hours |
Depending on depth |
| Total |
~$449 + labor |
|
Pros: Deep system knowledge, immediate findings, repeatable
Cons: Potential blind spots, less credible for compliance, time-intensive
17.2 External Pentest (Vendor)
| Vendor Type |
Cost Range |
Duration |
Includes |
| Freelance pentester |
EUR 3,000-8,000 |
5-10 days |
Web + API + server |
| Boutique security firm (BE) |
EUR 8,000-15,000 |
10-15 days |
Full scope + NIS2 mapping |
| Big 4 / large consultancy |
EUR 20,000-50,000 |
15-25 days |
Full scope + compliance + governance |
Belgian pentest firms to consider:
- Toreon (Leuven) — well-known Belgian security firm
- NVISO (Brussels) — specializes in penetration testing and red teaming
- Secure Code Warrior (Brussels) — developer security training + assessment
- BeSafe (Antwerp) — SME-focused security assessments
- Approach (Brussels) — NIS2 compliance specialists
17.3 Hybrid Approach (Recommended)
| Phase |
Who |
Cost |
Frequency |
| Automated scanning |
Internal (Nuclei, Trivy, pnpm audit) |
Free |
Continuous/Weekly |
| Manual web/API test |
Internal (Burp Suite) |
~$449/year |
Quarterly |
| Full external pentest |
Belgian security firm |
~EUR 8,000-12,000 |
Annually |
| NIS2 gap analysis |
Compliance consultant |
~EUR 3,000-5,000 |
Annually |
| Annual Total |
|
~EUR 12,000-18,000 |
|
Appendix A: Quick-Start Commands
A.1 External Scan (No Credentials)
Run these from your local machine or a Kali Linux VM:
#!/bin/bash
# quick-external-scan.sh
# Non-intrusive external scan of staging environment
TARGET_API="staging-connect-api.forma3d.be"
TARGET_WEB="staging-connect.forma3d.be"
TARGET_DB="staging-connect-db.forma3d.be"
TARGET_IP="167.172.45.47"
OUTPUT_DIR="pentest-results-$(date +%Y%m%d)"
mkdir -p $OUTPUT_DIR
echo "=== Phase 1: DNS ==="
dig forma3d.be ANY > $OUTPUT_DIR/dns-any.txt
dig axfr forma3d.be @$(dig NS forma3d.be +short | head -1) > $OUTPUT_DIR/dns-axfr.txt 2>&1
dig _dmarc.forma3d.be TXT > $OUTPUT_DIR/dns-dmarc.txt
dig forma3d.be TXT > $OUTPUT_DIR/dns-txt.txt
dig forma3d.be CAA > $OUTPUT_DIR/dns-caa.txt
echo "=== Phase 2: Port Scan ==="
nmap -sS -sV -sC -p- -oA $OUTPUT_DIR/nmap-full $TARGET_IP
echo "=== Phase 3: TLS ==="
testssl.sh --html $TARGET_API > $OUTPUT_DIR/tls-api.html
testssl.sh --html $TARGET_WEB > $OUTPUT_DIR/tls-web.html
echo "=== Phase 4: Security Headers ==="
curl -sI https://$TARGET_API/health > $OUTPUT_DIR/headers-api.txt
curl -sI https://$TARGET_WEB > $OUTPUT_DIR/headers-web.txt
curl -sI https://$TARGET_DB > $OUTPUT_DIR/headers-pgadmin.txt
echo "=== Phase 5: Nuclei ==="
nuclei -u https://$TARGET_API -o $OUTPUT_DIR/nuclei-api.txt
nuclei -u https://$TARGET_WEB -o $OUTPUT_DIR/nuclei-web.txt
nuclei -u https://$TARGET_DB -o $OUTPUT_DIR/nuclei-pgadmin.txt
echo "=== Phase 6: Directory Discovery ==="
ffuf -w /usr/share/seclists/Discovery/Web-Content/common.txt \
-u https://$TARGET_API/FUZZ -mc 200,301,302,403 \
-o $OUTPUT_DIR/ffuf-api.json -of json
echo "=== Scan complete. Results in $OUTPUT_DIR ==="
A.2 Server Audit (With SSH)
#!/bin/bash
# quick-server-audit.sh
# Run on the droplet after SSH access
OUTPUT_DIR="/tmp/audit-$(date +%Y%m%d)"
mkdir -p $OUTPUT_DIR
echo "=== System Info ==="
uname -a > $OUTPUT_DIR/system-info.txt
cat /etc/os-release >> $OUTPUT_DIR/system-info.txt
echo "=== Open Ports ==="
ss -tlnp > $OUTPUT_DIR/open-ports.txt
echo "=== Running Services ==="
systemctl list-units --type=service --state=running > $OUTPUT_DIR/services.txt
echo "=== Firewall ==="
ufw status verbose > $OUTPUT_DIR/firewall.txt
echo "=== SSH Config ==="
sshd -T 2>/dev/null > $OUTPUT_DIR/ssh-config.txt
echo "=== Docker Containers ==="
docker ps -a > $OUTPUT_DIR/docker-containers.txt
docker images > $OUTPUT_DIR/docker-images.txt
echo "=== Docker Security ==="
for container in $(docker ps -q); do
name=$(docker inspect --format='{{.Name}}' $container)
echo "--- $name ---" >> $OUTPUT_DIR/docker-security.txt
docker inspect --format='User:{{.Config.User}} Privileged:{{.HostConfig.Privileged}} CapAdd:{{.HostConfig.CapAdd}}' $container >> $OUTPUT_DIR/docker-security.txt
done
echo "=== .env Permissions ==="
ls -la /opt/forma3d/.env > $OUTPUT_DIR/env-permissions.txt
echo "=== Updatable Packages ==="
apt list --upgradable 2>/dev/null > $OUTPUT_DIR/updatable-packages.txt
echo "=== SUID Binaries ==="
find / -perm -4000 -type f 2>/dev/null > $OUTPUT_DIR/suid-binaries.txt
echo "=== Failed Auth (Last 50) ==="
grep "Failed password\|Invalid user" /var/log/auth.log 2>/dev/null | tail -50 > $OUTPUT_DIR/failed-auth.txt
echo "=== Audit complete. Results in $OUTPUT_DIR ==="
| Entity |
Role |
Contact |
| Centre for Cybersecurity Belgium (CCB) |
National authority |
https://ccb.belgium.be |
| CERT.be |
National CSIRT |
https://cert.be |
| NIS2 Notification Portal |
Incident reporting |
https://nis2.belgium.be (when available) |
| Belgian DPA (GBA/APD) |
Data protection (GDPR + NIS2 overlap) |
https://www.gegevensbeschermingsautoriteit.be |
Appendix C: References
Document Version: 1.0
Last Updated: February 2026
Next Review: August 2026