Lab 01 — Report Quality Linter

Operation Cedar Lattice — Phase 12 Before the Cedar Lattice report leaves the team, every finding passes a quality linter. This lab implements that linter.


Table of Contents


Objective

Implement five functions that evaluate the completeness and quality of red team report findings. The linter enforces the nine-field quality bar described in the WARMUP before a finding is included in a client-deliverable report.


Safety Boundary

This lab operates entirely on Python dicts representing synthetic finding data. No exploitation, no network traffic, no payloads. The linter is a text-quality analyzer — it checks field presence and content patterns.


Background

A red team finding is not complete until all nine required fields are present, non-empty, and meet minimum quality criteria. In practice, findings drafted under time pressure often have:

  • Missing fields (operator forgot to fill them in)
  • Invalid severity strings (typos like "CRIT" instead of "CRITICAL")
  • Malformed ATT&CK IDs (tactic IDs like TA0006 instead of technique IDs like T1003.006)
  • Detection fields that are present but too vague to implement ("check the logs")
  • Remediation fields that are present but too vague to act on ("fix it")

The quality linter catches all of these mechanically. Human peer review catches substance issues. Together they ensure every finding that exits the team is actionable.


The Finding Schema

Each finding is a Python dict with the following fields:

{
    "title":          str,   # Finding title
    "severity":       str,   # "CRITICAL" | "HIGH" | "MEDIUM" | "LOW"
    "attck_id":       str,   # ATT&CK technique ID, e.g. "T1055.003"
    "precondition":   str,   # What must be true for exploitability
    "evidence":       str,   # Reference to appendix screenshot or log excerpt
    "detection":      str,   # What should have caught this
    "remediation":    str,   # Specific, testable action with owner and timeline
    "residual_risk":  str,   # Risk remaining after remediation
}

API to Implement

All five functions live in solution.py. Implement them in lab.py.

REQUIRED_FIELDS = [
    "title", "severity", "attck_id", "precondition",
    "evidence", "detection", "remediation", "residual_risk"
]
VALID_SEVERITIES = {"CRITICAL", "HIGH", "MEDIUM", "LOW"}
SEVERITY_ORDER = {"CRITICAL": 0, "HIGH": 1, "MEDIUM": 2, "LOW": 3}

missing_fields(finding: dict) -> list[str]

Return the list of required field names that are absent or empty in finding. A field is considered missing if:

  • The key is not in the dict, OR
  • The value is None, OR
  • The value is "" (empty string)

lint_finding(finding: dict) -> list[str]

Return a list of quality issue strings. Check for:

  1. Each missing/empty required field: "missing field: {name}"
  2. Severity value not in VALID_SEVERITIES: "invalid severity: {value}"
  3. ATT&CK ID does not match r'^T\d{4}(\.\d{3})?$': "invalid ATT&CK ID format: {value}"
  4. Detection field present but shorter than 20 characters: "detection too vague (< 20 chars)"
  5. Remediation field present but shorter than 20 characters: "remediation too vague (< 20 chars)"

Return empty list [] if no issues found.

report_quality_score(findings: list) -> float

Return a float from 0.0 to 1.0: the fraction of findings that have zero lint issues.

severity_distribution(findings: list) -> dict

Return {severity: count} for all findings. Include all four valid severities even if count is 0.

prioritized(findings: list) -> list

Return findings sorted: CRITICAL first, then HIGH, MEDIUM, LOW. Within the same severity, sort by lint issue count ascending (fewer issues = higher priority — better-documented findings should be addressed first).


Quality Rules

CheckIssue String
Required field absent or empty"missing field: {name}"
Severity not in valid set"invalid severity: {value}"
ATT&CK ID bad format"invalid ATT&CK ID format: {value}"
Detection field < 20 chars (when present)"detection too vague (< 20 chars)"
Remediation field < 20 chars (when present)"remediation too vague (< 20 chars)"

Note on ATT&CK ID format: The regex ^T\d{4}(\.\d{3})?$ accepts:

  • T1190 — base technique, no sub-technique
  • T1055.003 — technique with three-digit sub-technique

It rejects:

  • TA0006 — tactic ID (two leading letters)
  • T123 — too few digits
  • T1055.03 — sub-technique must be exactly three digits
  • t1190 — lowercase

Running the Lab

cd lab-01-report-quality-linter

# Install dependencies
pip install -r requirements.txt

# Run against stubs (expect NotImplementedError on all tests)
pytest -q

# Run against reference solution (expect all 12 tests to pass)
LAB_MODULE=solution pytest -q

Expected Behavior

Stub

All tests raise NotImplementedError. This is the expected starting state.

Reference Solution

12 passed in Xs

All 12 adversarial tests pass when run against solution.py.


Hints

  • missing_fields should check finding.get(field) — this returns None for absent keys and empty strings for empty values.
  • lint_finding calls missing_fields internally — do not duplicate the presence check.
  • The ATT&CK ID regex check in lint_finding should only run if attck_id is not already in the missing-fields list (otherwise you would get both a "missing field" and a "invalid format" issue for the same absent field).
  • severity_distribution should initialize all four severities to 0 before iterating findings.
  • prioritized sort key: (SEVERITY_ORDER.get(f.get("severity", ""), 99), len(lint_finding(f))).
  • report_quality_score for an empty findings list: return 0.0 (no clean findings, no total).

Cedar Lattice Phase 12 — Lab 01 of 02.