Phases

Phase 3 — Remediation Engine

Remediation, the LLM integration, and the SSH key pre-flight design.

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-001 outputs Laravel @csrf, vanilla PHP token generation, and CodeIgniter config
  • APP-ADMIN-001 outputs PHP route protection patterns
  • APP-COOKIE-001 outputs PHP setcookie() 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:

  1. Host-layer fixes (firewall, SSH hardening)
  2. Webserver fixes (nginx headers, TLS, rate limits)
  3. Container fixes (Dockerfile, docker-compose.yml)
  4. 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.