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
- Safety Boundary
- Background
- The Finding Schema
- API to Implement
- Quality Rules
- Running the Lab
- Expected Behavior
- Hints
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
TA0006instead of technique IDs likeT1003.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:
- Each missing/empty required field:
"missing field: {name}" - Severity value not in
VALID_SEVERITIES:"invalid severity: {value}" - ATT&CK ID does not match
r'^T\d{4}(\.\d{3})?$':"invalid ATT&CK ID format: {value}" - Detection field present but shorter than 20 characters:
"detection too vague (< 20 chars)" - 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
| Check | Issue 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-techniqueT1055.003— technique with three-digit sub-technique
It rejects:
TA0006— tactic ID (two leading letters)T123— too few digitsT1055.03— sub-technique must be exactly three digitst1190— 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_fieldsshould checkfinding.get(field)— this returnsNonefor absent keys and empty strings for empty values.lint_findingcallsmissing_fieldsinternally — do not duplicate the presence check.- The ATT&CK ID regex check in
lint_findingshould only run ifattck_idis 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_distributionshould initialize all four severities to 0 before iterating findings.prioritizedsort key:(SEVERITY_ORDER.get(f.get("severity", ""), 99), len(lint_finding(f))).report_quality_scorefor an empty findings list: return0.0(no clean findings, no total).
Cedar Lattice Phase 12 — Lab 01 of 02.