Before / After Code Comparison

5 changed file(s) shown side by side. 51 unchanged or omitted file(s) are not expanded here; use the before and after viewers for full-tree browsing.

docs-worker/app.py

Before remediation

1import os2import threading3import time45from flask import Flask, g, jsonify, request, render_template_string, send_file67from db import get_db, init_db8from middleware import load_session, log_audit, require_role910BASE_DIR = os.path.dirname(os.path.abspath(__file__))11STORAGE_ROOT = os.path.join(BASE_DIR, "storage")1213app = Flask(__name__)14app.before_request(load_session)15app.after_request(log_audit)161718@app.route("/healthz", methods=["GET"])19def healthz():20    return jsonify({"status": "ok", "service": "docs-worker"})212223@app.route("/metrics", methods=["GET"])24def metrics():25    db = get_db()26    docs = db.execute("SELECT COUNT(*) AS c FROM documents").fetchone()["c"]27    processed = db.execute(28        "SELECT COUNT(*) AS c FROM documents WHERE status = 'processed'"29    ).fetchone()["c"]30    return jsonify({"documents_total": docs, "documents_processed": processed})313233@app.route("/api/documents/search", methods=["GET"])34def search_documents():35    """Lets ops search the document library by type, status and a free36    text filename match, combining whatever filters were passed."""37    org_id = g.org_id38    doc_type = request.args.get("doc_type")39    status = request.args.get("status")40    q = request.args.get("q")4142    where = f"org_id = {org_id}"43    if doc_type:44        where += f" AND doc_type = '{doc_type}'"45    if status:46        where += f" AND status = '{status}'"47    if q:48        where += f" AND filename LIKE '%{q}%'"4950    query = f"SELECT id, filename, doc_type, status, uploaded_by, created_at FROM documents WHERE {where}"5152    db = get_db()53    rows = db.execute(query).fetchall()54    documents = [dict(row) for row in rows]55    return jsonify({"documents": documents, "query": query})565758@app.route("/api/documents/<org_id>/upload", methods=["POST"])59@require_role("admin", "analyst")60def upload_document(org_id):61    uploaded = request.files.get("document")62    if uploaded is None:63        return jsonify({"error": "document file is required"}), 4006465    doc_type = request.form.get("docType", "other")66    dest_name = request.form.get("filename") or uploaded.filename6768    org_dir = os.path.join(STORAGE_ROOT, f"org_{org_id}")69    os.makedirs(org_dir, exist_ok=True)7071    dest_path = os.path.join(org_dir, dest_name)72    uploaded.save(dest_path)7374    db = get_db()75    cur = db.execute(76        "INSERT INTO documents (org_id, filename, doc_type, status, uploaded_by) VALUES (?, ?, ?, 'uploaded', ?)",77        (org_id, dest_name, doc_type, g.user_email),78    )79    db.commit()8081    return jsonify({"id": cur.lastrowid, "filename": dest_name, "orgId": org_id}), 201828384@app.route("/api/documents/<org_id>/download", methods=["GET"])85def download_document(org_id):86    file_name = request.args.get("file")87    if not file_name:88        return jsonify({"error": "file query parameter is required"}), 4008990    org_dir = os.path.join(STORAGE_ROOT, f"org_{org_id}")91    target = os.path.join(org_dir, file_name)9293    if not os.path.exists(target):94        return jsonify({"error": "document not found", "path": target}), 4049596    return send_file(target, as_attachment=True, download_name=os.path.basename(file_name))979899REPORT_TEMPLATE = """100<!DOCTYPE html>101<html>102<head><meta charset="utf-8"><title>{{ title }}</title></head>103<body>104  <h1>{{ title }}</h1>105  <p>Prepared for: {{ org_name }}</p>106  <div class="analyst-note">{{ note | safe }}</div>107</body>108</html>109"""110111112@app.route("/api/reports/render", methods=["POST"])113def render_report():114    """Renders an ad-hoc analyst report. The title/org name/note fields are115    supplied by the caller so analysts can annotate the report however they116    like before it goes out to the customer."""117    payload = request.get_json(force=True, silent=True) or {}118    title = payload.get("title", "Document Review Report")119    org_name = payload.get("orgName", "")120    note = payload.get("note", "")121122    template_source = payload.get("templateOverride") or REPORT_TEMPLATE123    html = render_template_string(template_source, title=title, org_name=org_name, note=note)124    return html, 200, {"Content-Type": "text/html"}125126127def _process_document_job(document_id, org_id):128    time.sleep(1)129    db = get_db()130    db.execute("UPDATE documents SET status = 'processed' WHERE id = ?", (document_id,))131    db.execute(132        "INSERT INTO audit_log (org_id, actor, action, detail) VALUES (?, ?, ?, ?)",133        (org_id, "docs-worker", "document.processed", f"document_id={document_id}"),134    )135    db.commit()136137138@app.route("/api/jobs/process", methods=["POST"])139@require_role("admin", "analyst")140def dispatch_processing_job():141    payload = request.get_json(force=True, silent=True) or {}142    document_id = payload.get("documentId")143    if not document_id:144        return jsonify({"error": "documentId is required"}), 400145146    thread = threading.Thread(target=_process_document_job, args=(document_id, g.org_id), daemon=True)147    thread.start()148149    return jsonify({"documentId": document_id, "status": "processing"}), 202150151152if __name__ == "__main__":153    init_db()154    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 4002)))

After remediation

1import os2import threading3import time45from flask import Flask, g, jsonify, request, send_file67from db import get_db, init_db8from middleware import load_session, log_audit, require_role9from pathsafe import resolve_within10from search_query import build_search_query1112BASE_DIR = os.path.dirname(os.path.abspath(__file__))13STORAGE_ROOT = os.path.join(BASE_DIR, "storage")1415app = Flask(__name__)16app.before_request(load_session)17app.after_request(log_audit)181920@app.route("/healthz", methods=["GET"])21def healthz():22    return jsonify({"status": "ok", "service": "docs-worker"})232425@app.route("/metrics", methods=["GET"])26def metrics():27    db = get_db()28    docs = db.execute("SELECT COUNT(*) AS c FROM documents").fetchone()["c"]29    processed = db.execute(30        "SELECT COUNT(*) AS c FROM documents WHERE status = 'processed'"31    ).fetchone()["c"]32    return jsonify({"documents_total": docs, "documents_processed": processed})333435@app.route("/api/documents/search", methods=["GET"])36def search_documents():37    """Lets ops search the document library by type, status and a free38    text filename match, combining whatever filters were passed."""39    org_id = g.org_id40    doc_type = request.args.get("doc_type")41    status = request.args.get("status")42    q = request.args.get("q")4344    # Build the filter with placeholders and bind every untrusted value; only45    # SQL structure lives in the query string (see search_query.build_search_query).46    query, params = build_search_query(org_id, doc_type, status, q)4748    db = get_db()49    rows = db.execute(query, params).fetchall()50    documents = [dict(row) for row in rows]51    return jsonify({"documents": documents, "query": query})525354@app.route("/api/documents/<org_id>/upload", methods=["POST"])55@require_role("admin", "analyst")56def upload_document(org_id):57    uploaded = request.files.get("document")58    if uploaded is None:59        return jsonify({"error": "document file is required"}), 4006061    doc_type = request.form.get("docType", "other")62    dest_name = request.form.get("filename") or uploaded.filename6364    org_dir = os.path.join(STORAGE_ROOT, f"org_{org_id}")65    os.makedirs(org_dir, exist_ok=True)6667    dest_path = os.path.join(org_dir, dest_name)68    uploaded.save(dest_path)6970    db = get_db()71    cur = db.execute(72        "INSERT INTO documents (org_id, filename, doc_type, status, uploaded_by) VALUES (?, ?, ?, 'uploaded', ?)",73        (org_id, dest_name, doc_type, g.user_email),74    )75    db.commit()7677    return jsonify({"id": cur.lastrowid, "filename": dest_name, "orgId": org_id}), 201787980@app.route("/api/documents/<org_id>/download", methods=["GET"])81def download_document(org_id):82    file_name = request.args.get("file")83    if not file_name:84        return jsonify({"error": "file query parameter is required"}), 4008586    org_dir = os.path.join(STORAGE_ROOT, f"org_{org_id}")8788    # Canonicalize the requested path and confirm it stays inside the org's89    # storage directory before touching the filesystem. This blocks "..",90    # absolute paths, null bytes and symlinked escapes (uploads share this91    # directory, so an attacker could otherwise plant a symlink to escape it).92    target = resolve_within(org_dir, file_name)93    if target is None:94        return jsonify({"error": "invalid file path"}), 4009596    if not os.path.isfile(target):97        return jsonify({"error": "document not found"}), 4049899    return send_file(target, as_attachment=True, download_name=os.path.basename(target))100101102REPORT_TEMPLATE = """103<!DOCTYPE html>104<html>105<head><meta charset="utf-8"><title>{{ title }}</title></head>106<body>107  <h1>{{ title }}</h1>108  <p>Prepared for: {{ org_name }}</p>109  <div class="analyst-note">{{ note }}</div>110</body>111</html>112"""113114# Compile the report template once from a trusted constant. The caller only115# ever supplies context values (title/org name/note) -- never the template116# source -- so it cannot inject template syntax (SSTI). Flask's Jinja117# environment autoescapes string templates, so the values are HTML-escaped in118# the output as well (XSS).119_REPORT_TEMPLATE = app.jinja_env.from_string(REPORT_TEMPLATE)120121122@app.route("/api/reports/render", methods=["POST"])123def render_report():124    """Renders an ad-hoc analyst report. The title/org name/note fields are125    supplied by the caller so analysts can annotate the report however they126    like before it goes out to the customer."""127    payload = request.get_json(force=True, silent=True) or {}128    title = payload.get("title", "Document Review Report")129    org_name = payload.get("orgName", "")130    note = payload.get("note", "")131132    html = _REPORT_TEMPLATE.render(title=title, org_name=org_name, note=note)133    return html, 200, {"Content-Type": "text/html"}134135136def _process_document_job(document_id, org_id):137    time.sleep(1)138    db = get_db()139    db.execute("UPDATE documents SET status = 'processed' WHERE id = ?", (document_id,))140    db.execute(141        "INSERT INTO audit_log (org_id, actor, action, detail) VALUES (?, ?, ?, ?)",142        (org_id, "docs-worker", "document.processed", f"document_id={document_id}"),143    )144    db.commit()145146147@app.route("/api/jobs/process", methods=["POST"])148@require_role("admin", "analyst")149def dispatch_processing_job():150    payload = request.get_json(force=True, silent=True) or {}151    document_id = payload.get("documentId")152    if not document_id:153        return jsonify({"error": "documentId is required"}), 400154155    thread = threading.Thread(target=_process_document_job, args=(document_id, g.org_id), daemon=True)156    thread.start()157158    return jsonify({"documentId": document_id, "status": "processing"}), 202159160161if __name__ == "__main__":162    init_db()163    app.run(host="0.0.0.0", port=int(os.environ.get("PORT", 4002)))

docs-worker/test_pathsafe.py

Before remediation

missing

After remediation

1import os2import shutil3import tempfile45from pathsafe import resolve_within678def test_rejects_parent_traversal():9    with tempfile.TemporaryDirectory() as base:10        assert resolve_within(base, "../../etc/passwd") is None111213def test_rejects_absolute_path():14    with tempfile.TemporaryDirectory() as base:15        assert resolve_within(base, "/etc/passwd") is None161718def test_rejects_null_byte():19    with tempfile.TemporaryDirectory() as base:20        assert resolve_within(base, "report\x00.pdf") is None212223def test_rejects_empty_name():24    with tempfile.TemporaryDirectory() as base:25        assert resolve_within(base, "") is None262728def test_rejects_symlink_escape():29    with tempfile.TemporaryDirectory() as base:30        outside = tempfile.mkdtemp()31        try:32            secret = os.path.join(outside, "secret.txt")33            with open(secret, "w") as fh:34                fh.write("top secret")35            link = os.path.join(base, "link")36            os.symlink(secret, link)37            # A symlink inside the base dir that points outside must be rejected,38            # which lexical normalization would miss.39            assert resolve_within(base, "link") is None40        finally:41            shutil.rmtree(outside, ignore_errors=True)424344def test_allows_legitimate_file():45    with tempfile.TemporaryDirectory() as base:46        name = "report.pdf"47        path = os.path.join(base, name)48        with open(path, "w") as fh:49            fh.write("data")50        assert resolve_within(base, name) == os.path.realpath(path)

payments-api/db.go

Before remediation

1package main23import (4	"database/sql"5	"log"6	"os"7	"path/filepath"89	_ "modernc.org/sqlite"10)1112var db *sql.DB1314const schema = `15CREATE TABLE IF NOT EXISTS organizations (16	id INTEGER PRIMARY KEY AUTOINCREMENT,17	name TEXT NOT NULL,18	plan TEXT NOT NULL DEFAULT 'standard'19);2021CREATE TABLE IF NOT EXISTS api_keys (22	api_key TEXT PRIMARY KEY,23	org_id INTEGER NOT NULL,24	role TEXT NOT NULL DEFAULT 'service',25	label TEXT26);2728CREATE TABLE IF NOT EXISTS accounts (29	id INTEGER PRIMARY KEY AUTOINCREMENT,30	org_id INTEGER NOT NULL,31	name TEXT NOT NULL,32	currency TEXT NOT NULL DEFAULT 'USD',33	balance_cents INTEGER NOT NULL DEFAULT 0,34	status TEXT NOT NULL DEFAULT 'active'35);3637CREATE TABLE IF NOT EXISTS beneficiaries (38	id INTEGER PRIMARY KEY AUTOINCREMENT,39	org_id INTEGER NOT NULL,40	name TEXT NOT NULL,41	bank_name TEXT,42	iban TEXT,43	status TEXT NOT NULL DEFAULT 'active'44);4546CREATE TABLE IF NOT EXISTS transfers (47	id INTEGER PRIMARY KEY AUTOINCREMENT,48	org_id INTEGER NOT NULL,49	account_id INTEGER NOT NULL,50	beneficiary_id INTEGER,51	amount_cents INTEGER NOT NULL,52	currency TEXT NOT NULL DEFAULT 'USD',53	status TEXT NOT NULL DEFAULT 'pending',54	memo TEXT,55	created_at TEXT NOT NULL DEFAULT (datetime('now')),56	settled_at TEXT57);5859CREATE TABLE IF NOT EXISTS audit_log (60	id INTEGER PRIMARY KEY AUTOINCREMENT,61	org_id INTEGER,62	actor TEXT,63	action TEXT NOT NULL,64	detail TEXT,65	ip TEXT,66	created_at TEXT NOT NULL DEFAULT (datetime('now'))67);68`6970func initDB() {71	dataDir := "data"72	if err := os.MkdirAll(dataDir, 0o755); err != nil {73		log.Fatalf("creating data dir: %v", err)74	}7576	dbPath := filepath.Join(dataDir, "payments.sqlite")77	var err error78	db, err = sql.Open("sqlite", dbPath)79	if err != nil {80		log.Fatalf("opening database: %v", err)81	}8283	if _, err := db.Exec(schema); err != nil {84		log.Fatalf("applying schema: %v", err)85	}8687	seed()88}8990func seed() {91	var count int92	if err := db.QueryRow("SELECT COUNT(*) FROM organizations").Scan(&count); err != nil {93		log.Fatalf("counting organizations: %v", err)94	}95	if count > 0 {96		return97	}9899	res, err := db.Exec("INSERT INTO organizations (name, plan) VALUES (?, ?)", "Acme Manufacturing", "enterprise")100	if err != nil {101		log.Fatalf("seeding organizations: %v", err)102	}103	acmeID, _ := res.LastInsertId()104105	res, err = db.Exec("INSERT INTO organizations (name, plan) VALUES (?, ?)", "Globex Trading", "standard")106	if err != nil {107		log.Fatalf("seeding organizations: %v", err)108	}109	globexID, _ := res.LastInsertId()110111	db.Exec("INSERT INTO api_keys (api_key, org_id, role, label) VALUES (?, ?, ?, ?)",112		"sk_live_acme_ops_9f2c1a", acmeID, "admin", "Acme treasury ops key")113	db.Exec("INSERT INTO api_keys (api_key, org_id, role, label) VALUES (?, ?, ?, ?)",114		"sk_live_globex_finance_4b71e0", globexID, "admin", "Globex finance key")115116	accRes, err := db.Exec("INSERT INTO accounts (org_id, name, currency, balance_cents, status) VALUES (?, ?, ?, ?, ?)",117		acmeID, "Acme Operating Account", "USD", 500000000, "active")118	if err != nil {119		log.Fatalf("seeding accounts: %v", err)120	}121	acmeAccID, _ := accRes.LastInsertId()122123	globexAccRes, err := db.Exec("INSERT INTO accounts (org_id, name, currency, balance_cents, status) VALUES (?, ?, ?, ?, ?)",124		globexID, "Globex Trading Account", "EUR", 250000000, "active")125	if err != nil {126		log.Fatalf("seeding accounts: %v", err)127	}128	globexAccID, _ := globexAccRes.LastInsertId()129130	benRes, err := db.Exec("INSERT INTO beneficiaries (org_id, name, bank_name, iban, status) VALUES (?, ?, ?, ?, ?)",131		acmeID, "Northwind Supplies", "First Union Bank", "GB29NWBK60161331926819", "active")132	if err != nil {133		log.Fatalf("seeding beneficiaries: %v", err)134	}135	northwindID, _ := benRes.LastInsertId()136137	db.Exec(`INSERT INTO transfers (org_id, account_id, beneficiary_id, amount_cents, currency, status, memo)138		VALUES (?, ?, ?, ?, ?, ?, ?)`,139		acmeID, acmeAccID, northwindID, 340000, "USD", "pending", "Q3 supplies invoice 4472")140	db.Exec(`INSERT INTO transfers (org_id, account_id, beneficiary_id, amount_cents, currency, status, memo)141		VALUES (?, ?, ?, ?, ?, ?, ?)`,142		globexID, globexAccID, nil, 1850000, "EUR", "pending", "Freight contract milestone 2")143}

After remediation

1package main23import (4	"database/sql"5	"log"6	"os"7	"path/filepath"89	_ "modernc.org/sqlite"10)1112var db *sql.DB1314const schema = `15CREATE TABLE IF NOT EXISTS organizations (16	id INTEGER PRIMARY KEY AUTOINCREMENT,17	name TEXT NOT NULL,18	plan TEXT NOT NULL DEFAULT 'standard'19);2021CREATE TABLE IF NOT EXISTS api_keys (22	api_key TEXT PRIMARY KEY,23	org_id INTEGER NOT NULL,24	role TEXT NOT NULL DEFAULT 'service',25	label TEXT26);2728CREATE TABLE IF NOT EXISTS accounts (29	id INTEGER PRIMARY KEY AUTOINCREMENT,30	org_id INTEGER NOT NULL,31	name TEXT NOT NULL,32	currency TEXT NOT NULL DEFAULT 'USD',33	balance_cents INTEGER NOT NULL DEFAULT 0,34	status TEXT NOT NULL DEFAULT 'active'35);3637CREATE TABLE IF NOT EXISTS beneficiaries (38	id INTEGER PRIMARY KEY AUTOINCREMENT,39	org_id INTEGER NOT NULL,40	name TEXT NOT NULL,41	bank_name TEXT,42	iban TEXT,43	status TEXT NOT NULL DEFAULT 'active'44);4546CREATE TABLE IF NOT EXISTS transfers (47	id INTEGER PRIMARY KEY AUTOINCREMENT,48	org_id INTEGER NOT NULL,49	account_id INTEGER NOT NULL,50	beneficiary_id INTEGER,51	amount_cents INTEGER NOT NULL,52	currency TEXT NOT NULL DEFAULT 'USD',53	status TEXT NOT NULL DEFAULT 'pending',54	memo TEXT,55	created_at TEXT NOT NULL DEFAULT (datetime('now')),56	settled_at TEXT57);5859CREATE TABLE IF NOT EXISTS audit_log (60	id INTEGER PRIMARY KEY AUTOINCREMENT,61	org_id INTEGER,62	actor TEXT,63	action TEXT NOT NULL,64	detail TEXT,65	ip TEXT,66	created_at TEXT NOT NULL DEFAULT (datetime('now'))67);68`6970func initDB() {71	dataDir := "data"72	if err := os.MkdirAll(dataDir, 0o755); err != nil {73		log.Fatalf("creating data dir: %v", err)74	}7576	dbPath := filepath.Join(dataDir, "payments.sqlite")77	var err error78	db, err = sql.Open("sqlite", dbPath)79	if err != nil {80		log.Fatalf("opening database: %v", err)81	}8283	if _, err := db.Exec(schema); err != nil {84		log.Fatalf("applying schema: %v", err)85	}8687	seed()88}8990func seed() {91	var count int92	if err := db.QueryRow("SELECT COUNT(*) FROM organizations").Scan(&count); err != nil {93		log.Fatalf("counting organizations: %v", err)94	}95	if count > 0 {96		return97	}9899	res, err := db.Exec("INSERT INTO organizations (name, plan) VALUES (?, ?)", "Acme Manufacturing", "enterprise")100	if err != nil {101		log.Fatalf("seeding organizations: %v", err)102	}103	acmeID, _ := res.LastInsertId()104105	res, err = db.Exec("INSERT INTO organizations (name, plan) VALUES (?, ?)", "Globex Trading", "standard")106	if err != nil {107		log.Fatalf("seeding organizations: %v", err)108	}109	globexID, _ := res.LastInsertId()110111	// API keys are secrets: read them from the environment (or a managed112	// secret store) at runtime instead of committing live key material. If a113	// key is not configured the seed skips it rather than falling back to a114	// hardcoded credential.115	if acmeKey := os.Getenv("ACME_API_KEY"); acmeKey != "" {116		db.Exec("INSERT INTO api_keys (api_key, org_id, role, label) VALUES (?, ?, ?, ?)",117			acmeKey, acmeID, "admin", "Acme treasury ops key")118	}119	if globexKey := os.Getenv("GLOBEX_API_KEY"); globexKey != "" {120		db.Exec("INSERT INTO api_keys (api_key, org_id, role, label) VALUES (?, ?, ?, ?)",121			globexKey, globexID, "admin", "Globex finance key")122	}123124	accRes, err := db.Exec("INSERT INTO accounts (org_id, name, currency, balance_cents, status) VALUES (?, ?, ?, ?, ?)",125		acmeID, "Acme Operating Account", "USD", 500000000, "active")126	if err != nil {127		log.Fatalf("seeding accounts: %v", err)128	}129	acmeAccID, _ := accRes.LastInsertId()130131	globexAccRes, err := db.Exec("INSERT INTO accounts (org_id, name, currency, balance_cents, status) VALUES (?, ?, ?, ?, ?)",132		globexID, "Globex Trading Account", "EUR", 250000000, "active")133	if err != nil {134		log.Fatalf("seeding accounts: %v", err)135	}136	globexAccID, _ := globexAccRes.LastInsertId()137138	benRes, err := db.Exec("INSERT INTO beneficiaries (org_id, name, bank_name, iban, status) VALUES (?, ?, ?, ?, ?)",139		acmeID, "Northwind Supplies", "First Union Bank", "GB29NWBK60161331926819", "active")140	if err != nil {141		log.Fatalf("seeding beneficiaries: %v", err)142	}143	northwindID, _ := benRes.LastInsertId()144145	db.Exec(`INSERT INTO transfers (org_id, account_id, beneficiary_id, amount_cents, currency, status, memo)146		VALUES (?, ?, ?, ?, ?, ?, ?)`,147		acmeID, acmeAccID, northwindID, 340000, "USD", "pending", "Q3 supplies invoice 4472")148	db.Exec(`INSERT INTO transfers (org_id, account_id, beneficiary_id, amount_cents, currency, status, memo)149		VALUES (?, ?, ?, ?, ?, ?, ?)`,150		globexID, globexAccID, nil, 1850000, "EUR", "pending", "Freight contract milestone 2")151}

payments-api/main.go

Before remediation

1package main23import (4	"log"5	"net/http"6	"os"7)89func main() {10	initDB()11	defer db.Close()1213	go settlementSweep()1415	mux := http.NewServeMux()1617	mux.HandleFunc("GET /healthz", healthz)18	mux.HandleFunc("GET /metrics", metrics)1920	mux.HandleFunc("GET /api/accounts/search", withAuth(withAudit("accounts.search")(searchAccounts)))21	mux.HandleFunc("GET /api/accounts/{id}", withAuth(withAudit("accounts.get")(getAccount)))2223	mux.HandleFunc("GET /api/transfers/{id}", withAuth(withAudit("transfers.get")(getTransfer)))24	mux.HandleFunc("POST /api/transfers", withAuth(requireRole("admin", "service")(withAudit("transfers.create")(createTransfer))))2526	mux.HandleFunc("POST /api/beneficiaries", withAuth(requireRole("admin", "service")(withAudit("beneficiaries.create")(createBeneficiary))))2728	port := os.Getenv("PORT")29	if port == "" {30		port = "4001"31	}3233	log.Printf("payments-api listening on :%s", port)34	if err := http.ListenAndServe(":"+port, mux); err != nil {35		log.Fatal(err)36	}37}

After remediation

1package main23import (4	"crypto/tls"5	"log"6	"net/http"7	"os"8)910// newServer builds the HTTP server with a modern TLS configuration. TLS11// verification is left intact (no InsecureSkipVerify); a minimum protocol12// version of TLS 1.2 is enforced for every connection.13func newServer(addr string, handler http.Handler) *http.Server {14	return &http.Server{15		Addr:    addr,16		Handler: handler,17		TLSConfig: &tls.Config{18			MinVersion: tls.VersionTLS12,19		},20	}21}2223func main() {24	initDB()25	defer db.Close()2627	go settlementSweep()2829	mux := http.NewServeMux()3031	mux.HandleFunc("GET /healthz", healthz)32	mux.HandleFunc("GET /metrics", metrics)3334	mux.HandleFunc("GET /api/accounts/search", withAuth(withAudit("accounts.search")(searchAccounts)))35	mux.HandleFunc("GET /api/accounts/{id}", withAuth(withAudit("accounts.get")(getAccount)))3637	mux.HandleFunc("GET /api/transfers/{id}", withAuth(withAudit("transfers.get")(getTransfer)))38	mux.HandleFunc("POST /api/transfers", withAuth(requireRole("admin", "service")(withAudit("transfers.create")(createTransfer))))3940	mux.HandleFunc("POST /api/beneficiaries", withAuth(requireRole("admin", "service")(withAudit("beneficiaries.create")(createBeneficiary))))4142	port := os.Getenv("PORT")43	if port == "" {44		port = "4001"45	}4647	// Certificate/key material is supplied at runtime (mount or secret store),48	// never minted in code or committed to the repo. Refuse to start over49	// plaintext so the listener always speaks TLS.50	certFile := os.Getenv("TLS_CERT_FILE")51	keyFile := os.Getenv("TLS_KEY_FILE")52	if certFile == "" || keyFile == "" {53		log.Fatal("TLS_CERT_FILE and TLS_KEY_FILE must be set; refusing to start an unencrypted listener")54	}5556	srv := newServer(":"+port, mux)57	log.Printf("payments-api listening on :%s (TLS)", port)58	if err := srv.ListenAndServeTLS(certFile, keyFile); err != nil {59		log.Fatal(err)60	}61}

payments-api/security_test.go

Before remediation

missing

After remediation

1package main23import (4	"crypto/tls"5	"database/sql"6	"net/http"7	"net/http/httptest"8	"path/filepath"9	"testing"10)1112// TestNewServerEnforcesModernTLS verifies the listener is configured for TLS13// with a modern minimum protocol version.14func TestNewServerEnforcesModernTLS(t *testing.T) {15	srv := newServer(":0", http.NewServeMux())16	if srv.TLSConfig == nil {17		t.Fatal("expected a TLS configuration on the server")18	}19	if srv.TLSConfig.MinVersion != tls.VersionTLS12 {20		t.Fatalf("expected TLS min version 1.2, got %x", srv.TLSConfig.MinVersion)21	}22}2324// TestTLSAcceptsTrustedRejectsUntrusted proves the transport enforces25// certificate verification: a peer that trusts the server cert succeeds while26// a peer using default verification is rejected.27func TestTLSAcceptsTrustedRejectsUntrusted(t *testing.T) {28	mux := http.NewServeMux()29	mux.HandleFunc("GET /healthz", healthz)3031	ts := httptest.NewUnstartedServer(mux)32	ts.TLS = newServer("", mux).TLSConfig // exercise our TLS config33	ts.StartTLS()34	defer ts.Close()3536	// Trusted peer: the test client trusts the generated server certificate.37	resp, err := ts.Client().Get(ts.URL + "/healthz")38	if err != nil {39		t.Fatalf("trusted client should succeed: %v", err)40	}41	resp.Body.Close()42	if resp.StatusCode != http.StatusOK {43		t.Fatalf("expected 200 from trusted client, got %d", resp.StatusCode)44	}4546	// Untrusted peer: default verification must reject the unknown authority.47	if _, err := http.Get(ts.URL + "/healthz"); err == nil {48		t.Fatal("untrusted client should fail TLS verification")49	}50}5152// TestSeedUsesEnvApiKeys proves the seed reads API keys from the environment53// (legitimate flow) and never inserts a hardcoded sk_live_* credential.54func TestSeedUsesEnvApiKeys(t *testing.T) {55	tmp := t.TempDir()56	var err error57	db, err = sql.Open("sqlite", filepath.Join(tmp, "test.sqlite"))58	if err != nil {59		t.Fatalf("opening test db: %v", err)60	}61	defer db.Close()62	if _, err := db.Exec(schema); err != nil {63		t.Fatalf("applying schema: %v", err)64	}6566	t.Setenv("ACME_API_KEY", "runtime-test-value-acme")67	t.Setenv("GLOBEX_API_KEY", "runtime-test-value-globex")6869	seed()7071	var seeded int72	if err := db.QueryRow("SELECT COUNT(*) FROM api_keys WHERE api_key = ?", "runtime-test-value-acme").Scan(&seeded); err != nil {73		t.Fatalf("querying seeded key: %v", err)74	}75	if seeded != 1 {76		t.Fatalf("expected acme key seeded from env, got %d", seeded)77	}7879	var live int80	if err := db.QueryRow("SELECT COUNT(*) FROM api_keys WHERE api_key LIKE 'sk_live_%'").Scan(&live); err != nil {81		t.Fatalf("querying live keys: %v", err)82	}83	if live != 0 {84		t.Fatalf("expected no hardcoded sk_live key, found %d", live)85	}86}