TEST_PLAN.md — deadlight-proxy
Version: v1.0 (May 2026)
Scope: Functional testing across Linux, macOS, and Android (native app)
Purpose: Systematic coverage of all major subsystems following initial
multi-platform binary release. This is a living document, results and notes
are recorded inline as testing progresses.
This document covers what was tested, how, and what was found. It aims to be an honest record of real-world validation on real hardware.
Platform Matrix
Tracking results across all three supported binary targets.
| Test Area | Linux/WSL | macOS | Android |
|---|---|---|---|
| 1. Startup & Config | ☑ | ☑ | ☑ |
| 2. Health & Smoke | ⚠ | ☐ | ☑ |
| 3. HTTP Proxy | ☑ | ☑ | ☑ |
| 4. HTTPS (CONNECT) | ☑ | ☑ | ☑ |
| 5. TLS Interception | ☑ | ☑ | N/A |
| 6. SOCKS4/5 | ☑ | ☐ | ☑ |
| 7. Connection Pool | ☑ | ☐ | ☑ |
| 8. REST API | ☑ | ☐ | ☑ |
| 9. Plugins | ☐ | ☐ | N/A |
| 10. Web UI & SSE | ☑ | ☑ | ☑ |
| 11. VPN / TUN | ☐ | N/A | N/A |
| 12. Blog Cache | ☐ | ☐ | ☐ |
| 13. Edge / Resilience | ☑ | ☐ | ☐ |
Legend: ☑ Pass · ☒ Fail ·⚠ Partial / Known Issue · ☐ Not Yet Tested · N/A Not Applicable
Test Environment
Record your environment before starting. Differences here explain a lot of
failures.
Binary version: ./deadlight -v
OS / arch: uname -a
OpenSSL version: openssl version
GLib version: pkg-config --modversion glib-2.0
Network: (note if behind NAT, VPN, corporate proxy, etc.)1. Startup & Config
Why first: Nothing else is testable if the binary won't start or misreads
its config. This section establishes the baseline.
1.1 — Default startup (no config)
./deadlight -vExpected:
- Starts without errors
- Binds to
:8080(proxy) and:8081(web UI) - Verbose log output shows worker threads and protocol handlers initialising
- No segfault or immediate exit
Watch for:
- Missing shared library errors on macOS or Termux
- Port already in use (change with
-p) - SSL init warnings if no CA cert is present yet
1.2 — Config file loading
./deadlight -c deadlight.conf.example -vExpected:
- Confirms config file path in log output
- All sections (
[core],[ssl],[plugins]) parsed without error - Hot-reload: modify a non-critical value (e.g.
max_connections),
save the file, confirm the change is picked up without restart
Watch for:
- Silent fallback to defaults with no warning (config parse failure)
- Any section that causes a crash on load
1.3 — CLI flag overrides
./deadlight -c deadlight.conf.example -p 9090 -vExpected: Proxy binds to :9090, not :8080. Confirms CLI flags
take precedence over config.
1.4 — Daemon mode
./deadlight -d
ps aux | grep deadlight
kill <pid>Expected: Process detaches from terminal, visible in process list,
terminates cleanly on SIGTERM.
2. Health & Smoke Tests
Why second: Fast, dependency-light checks you can run on every platform
and binary before deeper testing. These are your canary.
# Proxy port responding
curl -v http://localhost:8080/api/health
# Web UI port responding
curl -v http://localhost:8081
# Metrics endpoint
curl http://localhost:8080/api/metrics | jq .Expected from /api/health:
{
"status": "ok",
"version": "..."
}Expected from /api/metrics:
- Returns valid JSON with connection, pool, protocol, and server info
- All values present (even if zero)
- No
nullfields that should be numeric
Watch for:
Connection refusedon either port: startup failed silently/api/metricsreturning malformed JSON- Version field missing or empty
3. HTTP Proxy
Why here: The most fundamental proxy function. HTTPS, SOCKS, and TLS
interception all build on this path working correctly.
3.1 — Plain HTTP forwarding
curl -x http://localhost:8080 http://example.com -vExpected: Full HTTP response from example.com. Verbose output should
show the CONNECT or forwarding handshake in the proxy log (if -v is running).
3.2 — Headers preserved
curl -x http://localhost:8080 http://httpbin.org/headersExpected: Response shows your request headers as received by the upstream.
Check that X-Forwarded-For or proxy headers are present/absent as intended.
3.3 — Large response
curl -x http://localhost:8080 http://httpbin.org/bytes/10485760 -o /dev/null -w "%{speed_download}"Expected: 10 MB downloads cleanly without truncation or connection drop.
Download speed is reasonable.
3.4 — Concurrent connections
for i in {1..20}; do
curl -s -x http://localhost:8080 http://example.com -o /dev/null &
done
waitExpected: All 20 requests complete. Check /api/metrics afterward for
connection counts and any error increments.
4. HTTPS (CONNECT Tunnelling)
Without TLS interception enabled, HTTPS should pass through as an opaque
tunnel via the CONNECT method.
4.1 — Basic HTTPS tunnel
curl -x http://localhost:8080 https://example.com -vExpected: Successful HTTPS response. Proxy log should show aCONNECT example.com:443 entry.
4.2 — Certificate verification intact
curl -x http://localhost:8080 https://expired.badssl.com/Expected: curl reports a certificate error — the proxy should not
silently swallow TLS errors in tunnel mode.
5. TLS Interception
⚠ Prerequisite: CA cert generated and trusted by the test system.
See README — Install CA Certificate and
Generate new CA Certificate.
5.1 — CA cert setup
# Confirm CA cert is in place and trusted
curl -x http://localhost:8080 https://example.com -v 2>&1 | grep "issuer"Expected: Issuer field shows your deadlight CA (deadmesh CA),
not the real upstream CA. This confirms interception is active.
5.2 — Certificate mimicry
curl -x http://localhost:8080 https://github.com -v 2>&1 | grep -E "subject|issuer|expire"Expected: The presented cert's CN matches github.com (mimicry),
but the issuer is your local CA.
5.3 — Cert is re-signed, not reused
Check two different HTTPS destinations and confirm each gets a freshly
generated cert with the correct CN — not a wildcard or a cached mismatch.
5.4 — Plugin visibility (with interception active)
If a logging or inspection plugin is loaded, confirm it can see decrypted
request bodies for HTTPS traffic. This validates the interception chain
is wired through to the plugin hooks.
6. SOCKS4/5
6.1 — SOCKS5 basic
curl --socks5 localhost:8080 http://example.comExpected: Full HTTP response via SOCKS5 tunnel.
6.2 — SOCKS5 with HTTPS
curl --socks5 localhost:8080 https://example.com6.3 — SOCKS4
curl --socks4 localhost:8080 http://example.com6.4 — Protocol auto-detection
Start with no explicit protocol flags on the client side where possible.
The proxy should auto-detect SOCKS vs HTTP vs other based on the opening
bytes of the connection. Check the proxy log to confirm the correct
protocol handler was invoked for each request type.
7. Connection Pool
Goal: Confirm connections are being reused, health checks are running,
and the pool degrades gracefully under failure.
7.1 — Pool warming
Send 10 sequential requests to the same upstream host, then check/api/metrics for pool hit/miss counts:
for i in {1..10}; do
curl -s -x http://localhost:8080 http://example.com -o /dev/null
done
curl http://localhost:8080/api/metrics | jq '.pool'Expected: Pool reuse count increasing after the first request.
7.2 — Pool health check / stale connection
# Start proxy, make a request to warm the pool
curl -x http://localhost:8080 http://example.com -o /dev/null
# Wait longer than any keepalive timeout (e.g. 90s), then request again
sleep 90
curl -x http://localhost:8080 http://example.com -vExpected: Request succeeds — stale connection is detected and replaced,
not returned to the client broken.
7.3 — Upstream goes away
# Point at a local service you control, warm the pool, then kill the service
# Confirm the proxy handles the broken pooled connection gracefullyExpected: Clean error response to the client (not a hang or crash).
8. REST API
Work through every endpoint category systematically.
8.1 — System endpoints
curl http://localhost:8080/api/health
curl http://localhost:8080/api/system/ip
curl http://localhost:8080/api/metrics
curl http://localhost:8080/api/logs
curl http://localhost:8080/api/dashboardExpected: All return valid JSON. system/ip returns your external IP.logs returns a non-empty array after proxy activity.
8.2 — SSE stream
curl -N http://localhost:8080/api/stream
# (leave running, generate proxy traffic in another terminal)Expected: Server-sent events appear in the terminal as proxy activity
occurs. Stream stays open indefinitely without timeout.
8.3 — Email (unauthenticated)
curl -X POST http://localhost:8080/api/email/send \
-H "Content-Type: application/json" \
-d '{"to":"your@email.com","subject":"Deadlight Test","body":"Test from TEST_PLAN.md"}'Expected: 200 response with confirmation. Verify delivery.
8.4 — Email (HMAC auth)
PAYLOAD='{"from":"gnarzilla@deadlight.boo","to":"gnarzilla@deadlight.boo","subject":"Auth Test","body":"HMAC test"}'
SECRET="your-auth-secret" # replace with actual value from deadlight-conf
SIG=$(echo -n "$PAYLOAD" | openssl dgst -sha256 -hmac "$SECRET" -hex | cut -d' ' -f2)
# Confirm SIG is populated before sending
echo "SIG: $SIG"
curl -X POST http://localhost:8080/api/outbound/email \
-H "Content-Type: application/json" \
-H "X-Signature: sha256=$SIG" \
-d "$PAYLOAD" \
-vExpected: Succeeds with valid signature.
# Confirm rejection without signature
curl -X POST http://localhost:8080/api/outbound/email \
-H "Content-Type: application/json" \
-d "$PAYLOAD"Expected: 401 or 403.
8.5 — Federation
# Status
curl http://localhost:8080/api/federation/status
# List posts (may be empty initially)
curl http://localhost:8080/api/federation/posts
# Domain connectivity test
curl http://localhost:8080/api/federation/test/example.com
# Receive a post (self-test)
curl -X POST http://localhost:8080/api/federation/receive \
-H "Content-Type: application/json" \
-d '{"author":"testuser","content":"Hello from TEST_PLAN","source":"local"}'
# Confirm it's stored
curl http://localhost:8080/api/federation/postsExpected: Post appears in the list after receive. Confirm file is
written to /var/lib/deadlight/federation/.
8.6 — Malformed requests
# Malformed JSON
curl -X POST http://localhost:8080/api/email/send \
-H "Content-Type: application/json" \
-d '{not valid json'
# Missing required fields
curl -X POST http://localhost:8080/api/email/send \
-H "Content-Type: application/json" \
-d '{}'
# Wrong method
curl -X DELETE http://localhost:8080/api/healthExpected: Sensible error responses (400, 405), no crashes, no
hung connections.
9. Plugins
9.1 — Plugin loading
Start with autoload = adblocker,ratelimiter in config. Check startup
log for confirmation both plugins loaded successfully.
9.2 — Adblocker
# Request a known ad network domain
curl -x http://localhost:8080 http://doubleclick.net/ -vExpected: Request is blocked — either a 200 with empty/redirect
body, or a 403/204. Check proxy log for block event.
9.3 — Rate limiter
# Rapid-fire requests
for i in {1..50}; do
curl -s -x http://localhost:8080 http://example.com \
-o /dev/null -w "%{http_code}\n"
doneExpected: Responses eventually include 429 Too Many Requests
once the rate limit is hit. Confirm the limit resets after the window passes.
9.4 — Plugin disable
Set enabled = false in [plugins], hot-reload, confirm previously
blocked ad domain now passes through.
10. Web UI & SSE
10.1 — Web UI loads
open http://localhost:8081 # macOS
xdg-open http://localhost:8081 # Linux
# Android: open in ChromeExpected: UI loads without console errors. Connection table is visible.
10.2 — Live connection table
Generate proxy traffic while watching the UI. Connection rows should
appear and update in real time via SSE without a page refresh.
10.3 — SSE reconnect
Kill and restart the proxy while the web UI is open. The UI should
reconnect to the SSE stream automatically without requiring a page reload.
10.4 — Mobile UI (Android)
On Android/Termux, access the web UI from Chrome on the same device
(http://localhost:8081). Confirm the layout is usable on a small screen.
11. VPN / TUN Mode
⚠ Requires root /
sudo, and--privilegedin Docker.
Mark N/A for macOS until native builds support it.
# Enable in config
[vpn]
enabled = true
tun_device = tun0
# Start proxy, confirm TUN device created
sudo ./deadlight -c deadlight.conf -v
ip link show tun0Expected: tun0 device appears and is UP. Layer 3 traffic routed
through the device reaches upstream correctly.
12. Blog Cache
Requires:
workers_urlset to a reachable endpoint (orhttps://deadlight.boofor live testing).
# Status
curl http://localhost:8080/api/blog/status
# List posts (populates cache on first hit)
curl http://localhost:8080/api/blog/posts | jq .
# Confirm cache file written
ls -lh /var/lib/deadlight/blog/12.1 — Offline fallback
# While online, populate the cache
curl http://localhost:8080/api/blog/posts
# Simulate upstream unavailable (block the domain or set workers_url
# to an unreachable address in config)
# Then request again
curl http://localhost:8080/api/blog/postsExpected: Stale cached content is returned, not an error.
This is the core edge-native use case.
13. Edge / Resilience
These tests simulate the "hostile network" conditions called out in the
README as a core design target.
13.1 — Intermittent upstream
# Use curl's --max-time to simulate slow upstream
curl -x http://localhost:8080 http://httpbin.org/delay/10 --max-time 5Expected: Proxy times out cleanly and returns an error, no hung
connection or zombie worker thread. Check metrics after — worker
count should be back to baseline.
13.2 — Client disconnect mid-transfer
Start a large download through the proxy, kill the client mid-transfer
(Ctrl+C), and confirm:
- Proxy log shows the disconnect cleanly
- Upstream connection is returned to the pool (or closed cleanly)
- No resource leak — check
/api/metricsconnection counts
13.3 — Rapid connect/disconnect
for i in {1..100}; do
curl -s -x http://localhost:8080 http://example.com \
-o /dev/null --max-time 1 &
done
wait
curl http://localhost:8080/api/metrics | jq '.connections'Expected: Active connection count returns to near-zero after all
requests complete. No crash.
13.4 — Restart under load
While running the concurrent test above, send SIGTERM to the proxy.
Expected: Clean shutdown — no partial log writes, no corrupted
cache files, no port left bound.
Known Issues & Notes
Record findings here as testing progresses.
| # | Area | Platform | Description | Status |
|---|---|---|---|---|
| 1 | ☐ |
How to Report Results
- ☑ All tests in section passed
- ☒ One or more tests failed (add a row to Known Issues)
- ⚠ Passed with caveats or workarounds needed
File bugs at GitHub Issues
with the section number, platform, binary version, and the exact command
that failed.
What's Not Covered Here
These areas are out of scope for this test plan but worth future coverage:
- SMTP / IMAP proxying — needs a local mail server fixture
- WebSocket proxying — needs a local WS server fixture
- Windows / macOS native builds — not yet released
- Prometheus metrics export — on roadmap,
not yet implemented - ActivityPub federation — on roadmap, not yet implemented
- Performance benchmarking — this plan is functional only;
load/latency benchmarks warrant a separate document
Part of the Deadlight ecosystem..
### Known Issues
# Area Description Status
1 Version /api/health returns 1.0.0 not v1.1.0 Fixed
2 Identity /api/health "proxy":"deadmesh" ✅ Fixed
3 Pool Stale connection retry ✅ Fixed
4 Headers X-Proxy-Id injected, not configurable ⚠️ Open
5 Config watcher Double-reload clears auth_secret briefly ⚠️ Open
6 Byte count Plain HTTP client→upstream bytes uncounted ⚠️ Open
7 TLS intercept Binary octet-stream truncates ~9 KB ✅ Fixed
8 Pool metrics failed overcounts under concurrent load ⚠️ Open
9 TLS validation Hetzner rejection — cert expired, correct ✅ Not a bug
10 Cert pinning Teams/Skype silently drop MITM — expected ℹ️ By design
11 Method routing All HTTP methods return 200 on all endpoints ❌ Open
12 Auth ordering Field validation runs before auth check ⚠️ Open
13 Docs README missing from field in outbound email example ⚠️ Open
14 HMAC auth X-Signature header not found despite being present — likely case-sensitive header lookup ❌ Open
**Legend:** ☑ Pass · ☒ Fail ·⚠ Partial / Known Issue · ☐ Not Yet Tested · N/A Not Applicable
Be the first to comment on this post.