# Secure Deploy Gate

> Harden and pentest a web deployment, gated by checks that actually run — a stdlib Python scanner probes a live target's TLS/HTTPS (cert validity, weak protocols, HTTP→HTTPS redirect), security headers (HSTS, CSP, cookies, version disclosure), and connect-scans for risky internet-exposed ports (databases, caches, admin/Docker/Kubelet APIs), plus an offline static audit of the reverse-proxy and container config (Caddy, nginx, docker-compose, Dockerfile) — and ships a hardened Caddy/nginx/firewall reference setup. Findings map to a severity and fail the build; active scanning is authorization-gated. Use to secure or harden a deployment, pentest/scan your own server or site, set up HTTPS/TLS properly, pick a reverse proxy (Caddy), find open ports or exposed services, add security headers, or gate deployment security in CI. Triggers: "secure deployment", "pentest my server", "harden", "HTTPS/TLS setup", "Caddy", "reverse proxy", "open ports", "exposed database", "security headers", "HSTS".

- Skill: `neuralmedic-de/secure-deploy-gate` (Agent Skill, multi-file: 27 files)
- Install (CLI): `npx skillmds@latest add neuralmedic-de/secure-deploy-gate`
- Raw SKILL.md: https://api.skillmd.com/api/skills/neuralmedic-de/secure-deploy-gate/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- License: MIT
- Author: NeuralMedic-DE (https://skillmd.com/u/neuralmedic-de)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/neuralmedic-de/secure-deploy-gate

---


# Secure deployment gate (harden + scan, verified)

Find the security problems in a deployment by **probing it**, and prove the fixes
hold — TLS, security headers, exposed ports, and the proxy/container config are
gated by checks that run, not by a checklist you tick.

## Core principle

**Security posture is measured, not assumed.** The loop is: scan the live target
+ audit the config → triage by severity → fix the root cause (usually: put a
hardened reverse proxy in front and close everything else) → re-scan until the
gate is green.

**Be honest about scope (the rule that keeps this skill correct):** these are
automated configuration and surface checks. A green gate is a **baseline, not a
clean bill of health** — it does not find logic flaws, broken authz, injection,
or anything needing credentials or human judgement, and it is not a substitute
for a real penetration test. Report "0 blocking findings against the encoded
checks", never "the deployment is secure". → `references/01-scope-authorization-and-threat-model.md`

## ⚠️ Authorization — read first

Active scanning (port connects, TLS handshakes, HTTP requests) may only be run
against systems you **own or are contracted to test**. Scanning third-party hosts
without permission is illegal in most jurisdictions. The live scanners **refuse
to run** until you assert authorization (`"authorized": true` in the config, or
`--authorize`). Record who authorized it and for which hosts. The static config
audit needs no authorization — it touches no network.

## When to use vs. not

- Use for: hardening a deployment; scanning your own server/site for TLS,
  header, and open-port problems; setting up HTTPS properly; choosing and
  configuring a reverse proxy (Caddy/nginx); gating deployment security in CI.
- Not for: scanning systems you don't control; a full penetration test or
  red-team; application-level security (authz, injection, business logic) — for
  those, get a human pentest. For cloud/Terraform posture use the sibling
  `iac-compliance-review`; for app accessibility use `a11y-gate`.

## Inputs to gather first

1. **The target(s)** — the public URL(s)/host(s) of the deployment, and written
   confirmation you're authorized to scan them.
2. **The deployment config** — the Caddyfile / nginx conf / docker-compose /
   Dockerfile, so the static audit can run (no network, no authorization needed).
3. **The severity bar** — default gate blocks on `critical` + `high`.

## Workflow

Load each reference when you reach its step. Scripts are stdlib-only Python 3.11+
(no installs).

1. **Confirm scope & authorization**, and that automated ≠ pentest. → `references/01-scope-authorization-and-threat-model.md`
   ```bash
   cp scripts/securedeploy.config.example.json scripts/securedeploy.config.json
   # edit: set "authorized": true ONLY for hosts you control; list targets + configDir
   ```

2. **Audit the config statically** (offline — run this first, it needs nothing running). → `references/05-reverse-proxy-hardening.md`
   ```bash
   python3 scripts/config_audit.py --config scripts/securedeploy.config.json
   ```

3. **Scan the live deployment** — TLS, headers, and exposed ports in one gate. → `references/02-tls-and-https.md`, `03-security-headers.md`, `04-network-exposure-and-ports.md`
   ```bash
   python3 scripts/deploy_scan.py --config scripts/securedeploy.config.json
   # or a one-off: python3 scripts/deploy_scan.py --url https://app.example.com --authorize
   ```

4. **Fix root causes.** Put a hardened reverse proxy in front (Caddy gives you
   automatic HTTPS + renewal + redirect for free), add the security headers,
   bind databases to loopback, and default-deny the firewall. Copy the hardened
   examples. → `references/05-reverse-proxy-hardening.md`
   ```bash
   scripts/examples/Caddyfile.hardened          # the recommended setup
   scripts/examples/docker-compose.hardened.yml # DB not published; least privilege
   scripts/examples/nginx.hardened.conf         # if you already run nginx
   scripts/examples/firewall-ufw.sh             # default-deny; only 80/443 (+SSH)
   ```

5. **Re-scan until green**, then verify from OUTSIDE the host that only 80/443
   answer.

6. **Gate in CI** and complete the manual review for what automation can't reach
   (auth, app logic, secrets management). → `references/06-running-it-and-ci.md`

## Try it on the bundled fixtures first

Prove the gate works in both directions before you trust it — no network needed:
```bash
python3 scripts/config_audit.py scripts/examples/docker-compose.insecure.yml   # findings, exit 1
python3 scripts/config_audit.py scripts/examples/docker-compose.hardened.yml   # clean,   exit 0
python3 scripts/headers_check.py --from scripts/fixtures/headers.insecure.json # findings, exit 1
python3 scripts/tls_check.py     --from scripts/fixtures/tls.insecure.json     # findings, exit 1
```

## What's in this skill

- `scripts/deploy_scan.py` — the live gate: runs TLS + headers + port scanners against every authorized target, aggregates into one report and one exit code.
- `scripts/tls_check.py` — certificate validity/expiry, hostname match, self-signed/untrusted chain, weak-protocol (SSLv3/TLS 1.0/1.1) acceptance, and HTTP→HTTPS redirect.
- `scripts/headers_check.py` — HSTS, CSP, X-Content-Type-Options, clickjacking, Referrer-Policy, Permissions-Policy, server version disclosure, and Secure/HttpOnly/SameSite cookie flags.
- `scripts/port_scan.py` — connect-scan for risky internet-exposed ports (DB, cache, admin, Docker/etcd/Kubelet); public-target findings are downgraded to info on private/loopback targets.
- `scripts/config_audit.py` — **offline** static audit of Caddyfile / nginx / docker-compose / Dockerfile / .env (exposed DB ports, `privileged`, mounted docker.sock, self-signed public certs, weak TLS, committed secrets).
- `scripts/secure_common.py` — shared findings, the authorization guard, report writers.
- `scripts/policies.json` — the catalog: header requirements, risky-port list with severities, TLS thresholds, and the static config rules.
- `scripts/examples/` — the hardened Caddy/nginx/compose/firewall reference setup **and** insecure fixtures that must fail.
- `scripts/fixtures/` — captured observations so every scanner is testable offline, in both directions.
- `references/01–06` — scope & authorization, TLS/HTTPS, security headers, network exposure, reverse-proxy hardening, and running it in CI.

## Definition of done

- [ ] `config_audit` reports **0** blocking findings across the proxy + container config.
- [ ] `deploy_scan` against the live target reports **0** blocking findings:
      valid trusted cert, TLS ≥ 1.2, HTTP redirects to HTTPS, HSTS + core
      headers present, session cookies flagged Secure/HttpOnly/SameSite.
- [ ] **No database, cache, admin, or orchestration port** answers from the
      public internet (verified by a port scan from outside the host).
- [ ] A hardened reverse proxy terminates TLS; the app and datastores listen on
      loopback/private only; the firewall is default-deny.
- [ ] Findings triaged; each waived one has a written reason in `ignore`.
- [ ] CI runs the gate; manual review of auth/app-logic/secrets done — and the
      result is reported as "0 blocking automated findings", not "secure".

## Guardrails — avoid these mistakes

- **Never scan what you don't own.** The tool refuses without asserted
  authorization; don't work around it. Record who authorized which hosts.
- **Don't report "secure" from a green gate.** These are surface checks. State
  "0 blocking automated findings; manual review done; no pentest performed."
- **Use a reverse proxy with automatic HTTPS.** Caddy renews certs and redirects
  HTTP→HTTPS by default — most TLS findings become impossible to hit.
- **A database port must never face the internet.** Publish Docker ports as
  `127.0.0.1:5432:5432`, never `5432:5432`; default-deny the firewall.
- **Self-signed is not "HTTPS is done."** It encrypts but doesn't authenticate;
  public sites need a publicly-trusted cert. `tls internal` is LAN-only.
- **Don't suppress a finding to go green.** `ignore` is for a verified,
  written-down accepted risk — not to quiet the scanner.
- **Fix the root cause, not the symptom.** One hardened proxy + a default-deny
  firewall closes most findings at once; chasing them individually doesn't.

