The pipeline report is served through a layered architecture with clearly separated responsibilities:
The Cloudflare tunnel works by making an outbound TCP connection from the local machine to Cloudflare's global network — identical in nature to a browser opening a tab. The local router/firewall has no port-forwarding rules and no inbound ports are opened. An attacker scanning the host's public IP address will find nothing.
| Attack Vector | Exposed? | Reason |
|---|---|---|
| Port scan of public IP | NO | Real IP never published; no inbound ports open |
| Direct connection to PostgreSQL | NO | Port 5432 bound to 127.0.0.1 only |
| Direct connection to Flask | NO | Port 5050 bound to 127.0.0.1 only |
| Man-in-the-middle (MITM) | NO | Cloudflare enforces TLS 1.3 end-to-end |
All traffic between the remote user and Cloudflare's edge is encrypted with TLS 1.3 (HTTPS). The public URL (*.trycloudflare.com) is issued with a valid Cloudflare-managed certificate — no self-signed certificates, no certificate warnings.
Flask uses HTTP Basic Authentication enforced on every route via a @login_required decorator. No page — including the /refresh endpoint — is accessible without valid credentials.
def login_required(f):
@functools.wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not _check_auth(auth.username, auth.password):
return _require_auth() # → HTTP 401
return f(*args, **kwargs)
return decorated
@app.route("/")
@login_required # ← applied to every route
def index(): ...
| Scenario | Result |
|---|---|
| Request with no credentials | HTTP 401 — browser shows login dialog |
| Request with wrong credentials | HTTP 401 — access denied |
| Request with correct credentials | Report HTML served |
netshield / netshield123) should be changed before sharing the URL externally. Edit the REPORT_USER and REPORT_PASS values in the .env file. The Flask dev server is suitable for internal/trusted sharing — for a production deployment, a WSGI server (e.g. Gunicorn) would be appropriate.
The report HTML is a fully self-contained static file. It contains:
data-b64 attributes — these are the "Download gaps CSV" buttonsThe Base64 strings are not encrypted or obfuscated — they decode to exactly the same data shown in the visible table. Anyone who can view the HTML source can decode them. This is intentional and expected: the purpose of the report is to share this data.
The report contains exactly two JavaScript functions. Neither makes any network request:
| Function | Purpose | Network access? |
|---|---|---|
togglePanel(id) |
Shows/hides the KPI detail panel when the ? button is clicked. Purely DOM manipulation. |
None |
dlCSV(btn) |
Decodes the Base64 attribute into a Blob, creates a temporary object URL, triggers a download, then revokes the URL. No server round-trip. |
None |
There is no fetch(), XMLHttpRequest, WebSocket, eval(), or dynamic script loading anywhere in the page.
The PostgreSQL database is never reachable from a browser for two independent reasons:
localhost:5432. Even if malicious JavaScript were injected into the page, it could not connect to PostgreSQL — browsers only allow HTTP/HTTPS requests, not raw TCP.
/ (read file from disk) and /refresh (run batch_report.py, then redirect). Neither accepts user-supplied SQL or exposes query results as an API. The database credentials exist only in the .env file on disk, loaded at Python startup — they are never transmitted to the browser.
# The only routes in serve_report.py:
@app.route("/") → reads pipeline_report.html from disk
@app.route("/refresh") → runs batch_report.py, then redirects to /
# No /query, /api, /exec, or any other endpoint exists.
| Item | Severity | Notes |
|---|---|---|
| Flask development server | LOW | Not hardened for high-traffic public deployment. Appropriate for internal/trusted sharing with ≤10 concurrent users. |
| HTTP Basic Auth over HTTPS | ACCEPTABLE | Credentials are Base64-encoded in the HTTP header, but TLS encryption prevents interception in transit. |
/refresh endpoint |
LOW | An authenticated user can trigger report regeneration, consuming CPU/RAM for ~5–10 seconds. Not exploitable for data exfiltration, only mild resource exhaustion. |
| Default credentials | MEDIUM | Must be changed before sharing the URL publicly. Configure via .env: REPORT_USER and REPORT_PASS. |
| No rate limiting | LOW | No brute-force protection on the login. Cloudflare's free tier provides basic bot protection at the edge. |
| Security Property | Status |
|---|---|
| Public IP / open ports exposed | ✓ Not exposed |
| Transit encryption (TLS) | ✓ Enforced by Cloudflare |
| Authentication required | ✓ HTTP Basic Auth on all routes |
| Database accessible from browser | ✓ Impossible by design |
| Filesystem accessible from browser | ✓ Not exposed |
| Remote code execution via web | ✓ Not possible |
| JavaScript makes network requests | ✓ None |
| Credentials in HTML source | ✓ Not present |
| Default password changed | ⚠ Recommended before sharing |