T09 · Insecure Skill Coding Practices
- Location
scripts/judgment_to_card.py:59- Finding
Caller-Controlled Privacy Flag Allows Sensitive Data in Shareable Cards
- Content
View full analysis
Vulnerability Details
File Location:
scripts/judgment_to_card.py:59-66, 69-94; related validation inscripts/validate_card.py:22-38, 88-97andscripts/render_card.py:24-40, 53-61, 82-83
Vulnerability Type: Trusting caller-controlled safety metadata and ineffective sensitive-data validation
Risk Level: MediumVulnerable Code
From
scripts/judgment_to_card.py:python def validate_judgment(data: dict[str, Any]) -> list[str]: errors: list[str] = [] for key in REQUIRED: if key not in data: errors.append(f"missing field: {key}") if data.get("module") not in MODULE_META: errors.append("module unsupported") for key in ["title", "verdict", "reason", "action"]: if not isinstance(data.get(key), str) or not data.get(key, "").strip(): errors.append(f"{key} must be non-empty text") if data.get("share_safe") is not True: errors.append("share_safe must be true for renderable card") return errors def convert_judgment(data: dict[str, Any], ratio: str = "9:16") -> dict[str, Any]: errors = validate_judgment(data) if errors: raise ValueError("; ".join(errors)) if ratio not in RATIOS: raise ValueError("ratio must be 9:16 or 3:4") module_label, route, default_time, default_corner = MODULE_META[str(data["module"])] tags = split_entry(data.get("backup")) + split_entry(data.get("follow_up")) if not tags: tags = [module_label[:4]] card = { "module": module_label, "route": route, "time_label": clipped(data.get("time_label", default_time), TEXT_LIMITS["time_label"]), "title": clipped(data["title"], TEXT_LIMITS["title"]), "reason": clipped(data["reason"], TEXT_LIMITS["reason"]), "action": clipped(data["action"], TEXT_LIMITS["action"]), "tags": tags[:4], "corner": clipped(split_entry(data.get("follow_up"))[0] if split_entry(data.get("follow_up")) else default_corner ...[truncated 4091 chars]- Remediation
View remediation
Remediation Suggestions
-
Do not trust caller-controlled safety metadata
- Treat input
share_safeas an untrusted claim rather than proof. - Default converted cards to
share_safe: false. - Set the flag to true only after a dedicated sanitizer and validator complete successfully.
- Treat input
-
Separate private source data from public card data
- Define an explicit allowlist of fields and content classes permitted in shareable cards.
- Keep precise locations, names, routes, health details, and account information in non-renderable fields.
- Construct public summaries from sanitized categories rather than copying source strings.
-
Implement value-oriented sensitive-data detection
- Detect common telephone-number, email-address, coordinate, account-identifier, and detailed-address patterns.
- Add configurable detection for names, company terms, meeting identifiers, and route descriptions.
- Normalize Unicode and punctuation before matching to reduce trivial bypasses.
- Treat detection as defense in depth rather than a complete substitute for structured data separation.
-
Apply one sanitizer consistently
- Sanitize
title,reason,action,backup,follow_up, tags, andalt_text. - Use the same sanitized representation for HTML, SVG, and PNG output.
- Avoid reconstructing
alt_textfrom unsanitized judgment fields.
- Sanitize
-
Require explicit confirmation for intentional disclosure
- If users request inclusion of real names or locations, present the exact content that will be shared.
- Require affirmative confirmation before producing a publicly shareable artifact.
- Mark such artifacts as containing user-approved sensitive content rather than generically safe.
-
Strengthen tests
- Add negative test cases containing realistic telephone numbers, email addresses, street addresses, coordinates, company names, meeting titles, and health details.
- Verify that sensitive values are absent from ...[truncated 413 chars]
-
