T09 · Insecure Skill Coding Practices
- Location
scripts/premortem_aggregator.py:19- Finding
Unvalidated risk data enables resource exhaustion and terminal output injection
- Content
View full analysis
Vulnerability Details
File Location:
scripts/premortem_aggregator.py, lines 19–43
Vulnerability Type: Missing input bounds and unsafe terminal output
Risk Level: MediumVulnerable Code
python def aggregate_failures(failure_list): """ Input: List of dicts {"reason": str, "likelihood": 1-5, "impact": 1-5} Output: Aggregated report sorted by risk severity. """ report = [] for item in failure_list: score = calculate_risk_score(item["likelihood"], item["impact"]) report.append( { "reason": item["reason"], "likelihood": item["likelihood"], "impact": item["impact"], "score": score, "category": categorize_risk(score), } ) return sorted(report, key=lambda x: x["score"], reverse=True) def print_risk_matrix(report): """Pretty-print the risk matrix to stdout.""" print("\n=== RISK MATRIX ===\n") for item in report: bar = "█" * item["score"] print(f"[{item['category']:8}] Score {item['score']:2}/25 {bar}") print(f" L={item['likelihood']} x I={item['impact']}: {item['reason']}\n")Technical Analysis
The script documents
likelihoodandimpactas integers in the range 1–5, but it never enforces their types or bounds. Their product is passed directly to Python string multiplication:python bar = "█" * item["score"]An attacker who controls standard input can therefore provide values far above the intended range. This causes the process to attempt to allocate and print a string proportional to the supplied score, potentially consuming excessive memory, CPU time, and output capacity.
The attacker-controlled
reasonfield is also written directly to a terminal without rejecting control characters. ANSI escape sequences and other terminal control bytes can a ...[truncated 1983 chars]- Remediation
View remediation
Remediation Suggestions
- Validate that the top-level JSON value is a list and impose a maximum number of entries.
- Require every entry to be an object containing exactly the expected fields.
- Reject booleans and non-integer values for
likelihoodandimpact, then enforce the documented range of 1 through 5. - Require
reasonto be a string and impose a reasonable maximum length. - Reject or visibly escape control characters before writing attacker-controlled text to a terminal.
- Cap the rendered bar length independently of the calculated score as defense in depth.
- Report invalid input to standard error and exit with a nonzero status.
- Apply memory, CPU, and output limits when the CLI is exposed through a service or shared automation environment.
Example validation logic:
python import unicodedata MAX_ITEMS = 100 MAX_REASON_LENGTH = 1000 def validate_failure_list(data): if not isinstance(data, list): raise ValueError("Input must be a JSON array") if len(data) > MAX_ITEMS: raise ValueError(f"At most {MAX_ITEMS} risks are allowed") validated = [] for index, item in enumerate(data): if not isinstance(item, dict): raise ValueError(f"Item {index} must be an object") reason = item.get("reason") likelihood = item.get("likelihood") impact = item.get("impact") if not isinstance(reason, str) or len(reason) > MAX_REASON_LENGTH: raise ValueError(f"Item {index} has an invalid reason") if any(unicodedata.category(char) == "Cc" for char in reason): raise ValueError(f"Item {index} contains control characters") for name, value in (("likelihood", likelihood), ("impact", impact)): if isinstance(value, bool) or not isinstance(value, int): raise ValueError(f"Item {index} field {name} must be an integer") if not 1 <= value &l ...[truncated 326 chars]
