Back to skill

Security audit

Baby Tracker

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent local baby tracker, but it stores sensitive child and health records as plaintext files without permission hardening and can export raw CSV that may be unsafe in spreadsheets.

Install only if you are comfortable keeping baby identity and care or health records as local plaintext CSV/JSON/HTML/PNG files. Use a private data directory on a trusted single-user device, avoid synced or shared folders, restrict file permissions yourself, and treat CSV exports as untrusted before opening them in spreadsheet apps.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/baby_tracker.py:90
Finding
Sensitive Infant Health Data Is Stored Without Restrictive Filesystem Permissions## Vulnerability Details **File Location**: `scripts/baby_tracker.py:90-116`, `scripts/baby_tracker.py:234-235`, and `scripts/import_huckleberry.py:239-242` **Vulnerability Type**: Plaintext sensitive-data storage with process-default filesystem permissions **Risk Level**: Medium ### Vulnerable Code ```python def ensure_store(data_dir: Path) -> dict[str, Path]: paths = data_paths(data_dir) paths["dir"].mkdir(parents=True, exist_ok=True) paths["charts"].mkdir(parents=True, exist_ok=True) if not paths["events"].exists(): with paths["events"].open("w", newline="", encoding="utf-8") as f: csv.DictWriter(f, fieldnames=EVENT_HEADERS).writeheader() if not paths["metadata"].exists(): write_json(paths["metadata"], { "baby_id": "baby-1", "name": None, "date_of_birth": None, "sex": None, "timezone": "Europe/London", "notes": "Set name, date_of_birth, and sex for age-aware percentile charts.", }) if not paths["percentiles"].exists(): with paths["percentiles"].open("w", newline="", encoding="utf-8") as f: writer = csv.DictWriter(f, fieldnames=PERCENTILE_HEADERS) writer.writeheader() for sex, rows in APPROX_WEIGHT_PCTS.items(): for age_days, p3, p15, p50, p85, p97 in rows: writer.writerow({ "sex": sex, "age_days": age_days, "p3": p3, "p15": p15, "p50": p50, "p85": p85, "p97": p97, "unit": "kg", "source": "Approximate guide points; replace with exact WHO LMS data for clinical use.", }) return paths ``` Events are subsequently appended without setting or verifying a private mode: ```python with paths["events"].open("a", newline="", encoding="utf-8") as f: csv.DictWriter(f, fieldnames=EVENT_HEA ...[truncated 2412 chars]
Remediation
## Remediation Suggestions - Create the tracker data directory and chart directory with mode `0700`. - Create `events.csv`, `metadata.json`, percentile files, and generated charts with mode `0600` where portability permits. - Use `os.open()` with explicit modes and safe creation flags when creating sensitive files, then wrap the descriptor with `os.fdopen()`. - Apply `os.chmod()` to newly created temporary metadata files before atomically replacing the destination. - On startup, inspect the permissions of existing data directories and sensitive files. Refuse to continue or issue a prominent warning if group or world access is present. - Avoid automatically changing permissions on user-selected directories without confirmation, but clearly report insecure modes. - Document that data is stored in plaintext. Consider optional encryption at rest for shared devices, synchronized folders, and backups. - Ensure generated charts receive the same confidentiality protections because they contain names, dates, measurements, and percentile information. - Add automated tests that initialize the tracker under a permissive umask and verify that sensitive directories and files remain private.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/baby_tracker.py:278
Finding
Spreadsheet Formula Injection Through CSV Query Exports## Vulnerability Details **File Location**: `scripts/baby_tracker.py:219-233` and `scripts/baby_tracker.py:278-282` **Vulnerability Type**: CSV formula injection **Risk Level**: Medium ### Vulnerable Code User-controlled notes and source text are stored without spreadsheet-oriented neutralization: ```python row = { "event_id": args.event_id or str(uuid.uuid4()), "timestamp_local": local_ts, "timestamp_utc": utc_ts, "timezone": tz_name, "baby_id": args.baby_id or meta.get("baby_id") or "baby-1", "type": args.type.lower().strip(), "subtype": (args.subtype or "").lower().strip(), "metric": (args.metric or "").lower().strip(), "value": "" if args.value is None else str(args.value), "unit": args.unit or "", "details_json": json.dumps(details, ensure_ascii=False, sort_keys=True), "notes": args.notes or "", "source_text": args.source_text or "", "created_at_utc": dt.datetime.now(dt.timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z"), } ``` Query results are then emitted directly as CSV: ```python elif args.format == "csv": writer = csv.DictWriter(sys.stdout, fieldnames=EVENT_HEADERS) writer.writeheader() writer.writerows(rows) ``` ### Technical Analysis The `notes`, `source_text`, and extensible detail fields may contain attacker-controlled text. The skill documentation specifically encourages preserving original user messages in `source_text`, and imported Huckleberry rows are also preserved. When query results are exported with `--format csv`, these values are passed directly to `csv.DictWriter`. CSV quoting correctly preserves CSV syntax, but it does not prevent spreadsheet applications from interpreting a cell as a formula. Values beginning with formula markers such as `=`, `+`, `-`, or `@`, including variants preceded by accepted whitespace or control characters, may therefore be evaluated when the export ...[truncated 1691 chars]
Remediation
## Remediation Suggestions - Add a dedicated spreadsheet-safe CSV serialization function. - Before writing text cells intended for spreadsheet use, detect values whose first significant character is `=`, `+`, `-`, or `@`. Account for leading spaces, tabs, carriage returns, line feeds, and other control characters recognized by spreadsheet software. - Neutralize dangerous values using a documented strategy compatible with supported spreadsheet applications, such as prefixing an apostrophe. - Apply protection to every user-controlled textual column, including `notes`, `source_text`, `details_json`, event types, subtypes, units, identifiers, and imported content. - Preserve exact raw data through JSON output or a separately named raw CSV mode when lossless interchange is required. - Clearly distinguish between `--format csv` for machine interchange and a spreadsheet-safe export format. - Add tests covering formula markers, leading whitespace and control-character bypasses, quoted formulas, and imported Huckleberry fields. - Warn users that previously generated CSV exports should be treated as untrusted when opened in spreadsheet applications.
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

Lp3

Medium
Category
MCP Least Privilege
Confidence
90% confidence
Finding
The skill instructs the agent to read environment variables, read and write files, and persist user-provided content, but it does not declare any explicit tool scope or permissions boundary. That increases the chance the agent can access or modify unintended paths, especially because the data directory is overridable via an environment variable or command-line argument.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The description contains broad activation cues such as 'use when recording or analyzing' many categories and 'also use when asked for ... summaries, trends, imports, or CSV exports,' which can cause the skill to trigger in a wide range of conversations. Over-broad activation is risky here because the skill performs file writes and imports, so accidental invocation could lead to unintended persistence or processing of sensitive user data.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The metadata command writes personally sensitive child information such as name, date of birth, sex, timezone, and free-form notes to disk without prominently warning the user that this information will be stored locally. Because this skill is specifically designed to accumulate longitudinal records about a child, the context increases privacy sensitivity and the potential harm from inadvertent disclosure.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The log command persists highly sensitive child-health and caregiving data, including notes, medication, illness, timestamps, and source text, to plain files on disk without any explicit privacy warning, consent prompt, permission hardening, or encryption. In the context of a baby-tracking skill, this data is especially sensitive and can expose intimate medical and behavioral information if the local workspace is shared, backed up insecurely, or otherwise accessed by another process or user.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
paths = ensure_store(args.data_dir)
    meta = read_json(paths["metadata"])
    for key in ["baby_id", "name", "date_of_birth", "sex", "timezone", "notes"]:
        val = getattr(args, key, None)
        if val is not None:
            meta[key] = val
    extra = parse_fields(args.field)
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The generated HTML document sets `lang="en"`, which forces an English locale in output regardless of user preference or environment. The file does not offer a language choice or document this locale constraint as an intentional region-specific requirement.

Static analysis

No suspicious patterns detected.