T09 · Insecure Skill Coding Practices
Warning
- Location
- scripts/validate.py:464
- Finding
- PII Validator Discloses Sensitive Values in Plaintext Logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/validate.py`, lines 226–250 and 450–465 **Vulnerability Type**: Plaintext sensitive-data exposure **Risk Level**: Medium **Category**: T09: Insecure Skill Coding Practices ### Vulnerable Code ```python def scan_pii(rows: List[Dict[str, Any]], include_contextual: bool = False) -> Dict[str, List[Tuple[int, str, str]]]: """Scan all string fields for PII patterns. Returns findings dict.""" patterns = dict(DEFAULT_PII_PATTERNS) if include_contextual: patterns.update(CONTEXTUAL_PII_PATTERNS) findings: Dict[str, List[Tuple[int, str, str]]] = { k: [] for k in patterns } for i, row in enumerate(rows): for field, val in row.items(): if not isinstance(val, str): continue for pii_type, pattern in patterns.items(): for m in pattern.finditer(val): if pii_type == "credit_card" and not _luhn_ok(m.group()): continue findings[pii_type].append((i, field, m.group())) break return findings ``` The collected sensitive value is subsequently printed without masking: ```python for row_idx, field, match in hits[:3]: print(f" row {row_idx}.{field}: {match!r}") ``` ### Technical Analysis The PII scanner collects the complete regex match through `m.group()`. This may contain an email address, telephone number, Social Security number, or Luhn-valid payment-card number. The reporting logic then writes up to three complete values from each finding category to standard output. A validation report does not need the complete sensitive value to identify the affected record. The row index, field name, PII type, and a masked suffix are sufficient for remediation. Printing the complete value violates data minimization and expands the number of systems holding the sensitive information. Although the script does not di ...[truncated 1872 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions 1. Do not store or print complete PII matches by default. 2. Change the findings structure to retain only: - Row index - Field name - PII category - Optional masked preview 3. Apply category-specific masking, for example: - SSN: `***-**-1234` - Card number: `************1234` - Email: `a***@example.com` - Telephone: `***-***-1234` 4. Prefer count-only output for automated and CI environments. 5. If raw-value inspection is genuinely required, place it behind an explicit option such as `--show-raw-pii`, accompanied by a prominent warning. 6. Send any explicitly requested raw report to a restricted local file rather than standard output, using owner-only permissions where supported. 7. Add tests asserting that standard output never contains known fixture SSNs, card numbers, emails, or telephone numbers. 8. Document that validation logs may contain sensitive metadata such as row indices and field names even after values are masked. A safer reporting pattern would be: ```python def mask_match(pii_type: str, value: str) -> str: if pii_type in {"us_ssn", "credit_card", "us_phone"}: digits = re.sub(r"\D", "", value) return f"***{digits[-4:]}" if len(digits) >= 4 else "***" if pii_type == "email": local, _, domain = value.partition("@") return f"{local[:1]}***@{domain}" if domain else "***" return "***" for row_idx, field, match in hits[:3]: print( f" row {row_idx}.{field}: " f"{mask_match(pii_type, match)!r}" ) ``` ]]>
