Status: ✅ Complete
Delivered: March–April 2026
Builds on: Phase 2
What Was Built
Phase 3 transformed StackSentry from a scanner into a remediation tool. Three levels of fix output were added, culminating in direct auto-application of fixes via SSH.
Level 1 — Static Patch Templates
For every failing check, remediation/templates.py produces a structured patch artefact:
@dataclass
class PatchArtefact:
check_id: str
filename: str
file_type: str # sh | py | php | conf
content: str # the actual fix code
instructions: list # step-by-step application guide
verify_cmd: str # command to confirm fix worked
Templates are framework-aware. When a PHP stack is detected:
APP-CSRF-001outputs Laravel@csrf, vanilla PHP token generation, and CodeIgniter configAPP-ADMIN-001outputs PHP route protection patternsAPP-COOKIE-001outputs PHPsetcookie()with security flags When a Python stack is detected, Flask and Django equivalents are used instead.
Level 2 — LLM-Assisted Patch Generation
When ANTHROPIC_API_KEY is set, remediation/llm.py calls the Claude API to generate context-aware patches:
response = anthropic.messages.create(
model="claude-sonnet-4-20250514",
messages=[{
"role": "user",
"content": f"""
Check: {check_id}
Scan details: {check.details}
Stack: {stack_fingerprint}
Generate a fix patch as JSON with fields:
filename, file_type, content, instructions, verify_cmd
"""
}]
)
The prompt includes the specific details from the current scan — for example, the actual nginx version string from WS-SRV-001 — so the patch is tailored to the target rather than generic.
Concurrency: LLM generation runs concurrently across all failing checks using concurrent.futures.ThreadPoolExecutor, with result order preserved to match the original check sequence.
Fallback: If the LLM call fails (rate limit, invalid JSON, network error), the generator falls back to the static template for that check. The user always gets a patch — the LLM enhances it when available.
Retry: Up to two retries with exponential back-off on HTTP 429 and HTTP 529 responses.
Level 3 — Auto-Fix Engine
remediation/auto_fix.py applies fixes directly using Paramiko SSH and local file system operations.
Fix execution order:
- Host-layer fixes (firewall, SSH hardening)
- Webserver fixes (nginx headers, TLS, rate limits)
- Container fixes (Dockerfile, docker-compose.yml)
- Application fixes (code snippets — printed, not applied) Every fix:
- Creates a timestamped backup of the original file
- Validates the change before applying (
nginx -t,sshd -t) - Confirms the result and reports it back to the CLI
- Is idempotent — running twice produces the same result
The SSH Key Pre-Flight
The most significant engineering challenge in Phase 3 was HOST-SSH-001 — applying PermitRootLogin prohibit-password without locking the user out.
The four-step pre-flight was designed after considering three alternatives:
Alternative A — Just apply it: Too dangerous. Users lose server access.
Alternative B — Ask the user to generate a key manually first: Adds friction and defeats the purpose of automation.
Alternative C — Generate the key, install it, verify it, then apply: Correct, but required careful sequencing. The verification step (Step 3) must use only the new key — not the password — to confirm the key works before the password is disabled.
Alternative C was implemented. See SSH Key Safety for the full detail.
Phase 3 Technical Decisions
_nginx_config_broken flag — If an nginx fix fails validation, a session-level flag is set and all subsequent webserver fixes are skipped with a clear explanation. Without this, a broken nginx snippet would cause unrelated webserver checks to appear as failures in the auto-fix results.
not_automatable list — Application checks are excluded from the verbose auto-fix loop to prevent duplicate messaging. They are printed once as code snippets, not re-reported as skipped in the fix results.
Ed25519 with RSA 4096 fallback — Paramiko 3.x supports Ed25519Key.generate(). Older Paramiko does not. The fallback uses RSAKey.generate(bits=4096) wrapped in a try/except on AttributeError.
Phase 3 Test Suite
tests/test_remediation.py — 52 tests
All LLM calls are mocked. Tests cover:
- Static template rendering for all 24 checks
- PHP vs Python stack switching in templates
- LLM success path with structured JSON response
- LLM fallback on malformed JSON
- LLM fallback on rate limit (429)
- Concurrent execution result ordering
- Patch file writing and manifest generation Total at Phase 3 completion: 193 tests, all passing.