assessment
Guided product state evaluation. Spawns parallel discovery agents, validates findings with the developer, and produces a structured ASR document.
- License 许可
- Apache-2.0
- 最近更新
- 2026-11-09
- Stars
- ★ 0
- Forks
- 0
01 前言 Front Matter
- name
- docker-audit-firewall
- description
- Use when reviewing/writing host firewall rules (iptables/nftables) for a Docker-published port, or when a compose change adds a `ports:` mapping that replaces app-layer auth (Traefik, reverse-proxy) with a network ACL.
02 资源文件 5
- 参考文档agents/openai.yaml43 B
- 参考文档evals/eval-collaborative-scoping-and-validation.md3.4 KB
- 参考文档evals/eval-findings-cap-and-floor.md2.6 KB
- 参考文档references/asr-template.md3.5 KB
- 参考文档workflow.md10.4 KB
03 SKILL.md 正文
Docker-Published Port Firewall Audit
Docker manages its own iptables rules. Traffic to a published container port (ports: "host_ip:host_port:container_port") is DNAT'd in the nat PREROUTING/DOCKER chains and then traverses the
FORWARD chain (via the DOCKER-USER hook) — it does not traverse the INPUT chain. INPUT only sees traffic
terminating on a host-local process. So an INPUT ACCEPT/DROP rule added to "lock down" a published port silently does
nothing, while printing a success message that gives false assurance.
-
Identify the exposure model. Read the compose/run config. Is the port published (
ports:) or only on an internal Docker network (expose:/ shared network)? Only published ports hit the host's packet-filter path. If the change removes an app-layer control (Traefik TLS + basic-auth + ipallowlist middlewares) and replaces it with a rawports:mapping, treat the auth surface as regressed until proven otherwise. -
Check which chain the ACL targets. Grep the deploy/provisioning script for
iptables/nftrules guarding that port. If the rules are-I INPUT/-A INPUT(or an nftinputhook), they are on the wrong chain for Docker-published traffic. The correct hook isDOCKER-USER(traversed before Docker's own FORWARD rules):# -I prepends, so insert the DROP first, then the RETURN on top of it iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <host_port> --ctdir ORIGINAL -j DROP iptables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <host_port> --ctdir ORIGINAL \ -s <allowed-cidr> -j RETURN # Same pair for IPv6 unless the port is published IPv4-only (ports: "0.0.0.0:<host>:<container>") ip6tables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <host_port> --ctdir ORIGINAL -j DROP ip6tables -I DOCKER-USER -p tcp -m conntrack --ctstate DNAT --ctorigdstport <host_port> --ctdir ORIGINAL \ -s <allowed-cidr6> -j RETURN # omit this RETURN if no IPv6 source is allowedResulting chain order (
iptables -L DOCKER-USER -n --line-numbers): 1 = RETURN for the allowlisted source, 2 = DROP for everyone else. The RETURN must sit above the DROP. Each-Iprepends to the top, so the rule inserted last ends up first — issuing RETURN then DROP (both-I) leaves DROP on top and blocks the allowlisted source too. Flag any script that does that. With-A(append) the order is the reverse: RETURN first, then DROP — but-Alands after Docker's own trailing-j RETURNin DOCKER-USER, so prefer-I. DOCKER-USER sees packets after DNAT, so a bare--dportmatches the container port of every forwarded container — a DROP on--dport 443also blocks unrelated containers listening on 443 internally, even when the host and container port numbers are equal. Scope the rule to the published host port with conntrack's pre-DNAT--ctorigdstport <host_port>and flag any DOCKER-USER rule that uses a bare--dport. Pair it with--ctstate DNAT: DOCKER-USER also sees container egress and other routed traffic, and without the DNAT discriminator a rule for published port 443 also drops containers' outbound HTTPS.Scope of this rule pair: it filters only NAT-published traffic that is forwarded through DOCKER-USER. It does not cover (a) routed/direct-routing networks (
gateway_mode_ipv4/gateway_mode_ipv6=routed, or clients reaching the container IP directly), which are not DNAT; or (b) IPv6 connections the default userland proxy accepts in a host process when the bridge is IPv4-only, which never traverse IPv6 DOCKER-USER. Anip6tablespair covers only IPv6 that is NAT-published on an IPv6-enabled bridge.Verify these preconditions before approving the ACL (from the config/script, or ask for the output):
/etc/docker/daemon.json:firewall-backend,userland-proxy,ipv6,ip6tables,allow-direct-routing;docker info --format '{{.FirewallBackend}}': the backend actually in use (daemon.json may omit it);docker network inspect <net>:com.docker.network.bridge.gateway_mode_ipv4/_ipv6,com.docker.network.bridge.trusted_host_interfaces, andEnableIPv6;ss -ltnpon the host: no[::]:<host_port>listener unless the IPv6 pair is installed on an IPv6 bridge.
Approve only when Docker uses the
iptablesfirewall backend (the default; withfirewall-backend: nftablesthere is no DOCKER-USER chain, so these rules filter nothing), the traffic is NAT-published on a bridge (no routed gateway mode, no direct routing, notrusted_host_interfaces— that option lets those interfaces reach published container ports directly, without DNAT), and IPv6 is covered according to the bridge's IPv6 setting (EnableIPv6/ daemonipv6): on an IPv6-enabled bridge, a wildcard publication is NAT-published over IPv6 too, so require theip6tablespair (userland-proxy: falsedoes not help there); on an IPv4-only bridge, requireuserland-proxy: falseor an IPv4-only publication address (ports: "0.0.0.0:<host>:<container>") so the proxy cannot accept IPv6 on the host. If any precondition is non-default or unknown, report the ACL as unverified — needs manual review and name the uncovered path; never call it complete. The fix for those paths is to publish IPv4-only, disable the userland proxy, or filter on the path that actually carries the traffic. See Docker's port publishing and direct routing. Add--ctorigdst <host_ip>/-i <ext_if>only when the port is published on that specific address (ports: "IP:host:container") or when every published address/interface gets its own rule pair: on a wildcard publication (address-unspecified, or0.0.0.0/::), one interface/address qualifier leaves the other paths unfiltered and--ctorigdst 0.0.0.0matches nothing — both fail open, so flag them. Add--ctdir ORIGINAL: conntrack's original-tuple match applies to both directions of a flow, so without it the container's reply packets also match the DROP (and fail the-sRETURN), breaking allowlisted connections whenever the rule is not pinned to the ingress interface. See Docker's match the original IP and ports. -
Verify the control is testable, not just present. The primary check is behavioral: from a host that is not on the allowlist, connect to the published port over both IPv4 and IPv6 (e.g.
nc -vz -w3 <host_ipv4> <port>andnc -6 -vz -w3 <host_ipv6> <port>) and confirm both are refused, then confirm an allowlisted source still connects. Rule listings (iptables/ip6tables -L DOCKER-USER,docker port,ss -ltn) are supporting evidence only — they cannot prove there is no unfiltered path, so never report the ACL as effective from them alone; if the reachability test cannot be run, report it as unverified. A single-/32source-IP filter over plaintext HTTP is defense-in-depth, not authentication — it's spoofable on a shared segment and carries no transport encryption. Flag any design where the sole remaining control is source-IP on the wrong chain. -
Audit the fail-open path. If the rule-install commands suppress stderr (
2>/dev/null) and only warn when theiptablesbinary is missing, a privilege failure (no root, no NOPASSWD sudo) leaves the port published with no filter and no warning. Require the deploy to surface install failures andexit 1(or refuse to start the container) rather than print a green checkmark. -
Prefer eliminating the exposure. When only an internal client needs the service, the strongest fix is to not publish the port at all — put both containers on a shared internal Docker network and reach the service by container name/IP. Recommend this over any host-level ACL when the topology allows it.
04 同仓库更多 Skills
upstream-fix
Traces quality failures upstream to spec, rule, or workflow root cause. Proposes fixes at the right level.
triage
Dedup gate + batch-classify observations into five classes, enrich ARTIFACTs, batch-approve, delegate to intake/decision or queue.
sprint-planning
Sprint planning — story selection, team composition, Gherkin specs, and visual companion plan gate for developer sign-off.
sprint-manager
Sole writer of stories/index.json and sprints/index.json — validates state transitions, activates sprints, manages epic membership.
sprint-dev
Sprint execution — dependency-driven story development, post-merge AVFL, and team review.
retro
Sprint retrospective — transcript audit engine (dynamic Workflow), story verification, findings document, and sprint closure.