Back to skill

Security audit

FairSplit

Security checks for vulnerabilities and agentic risk

Overview

FairSplit is a disclosed local expense-splitting skill whose file writes, optional exchange-rate lookup, and delete/reset operations fit its stated purpose.

Install only if you are comfortable storing shared expense records as local JSON files in the configured data directory. Confirm before using undo or init --force, choose export paths deliberately, and expect network access only when asking for live currency rates.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/ledger.py:166
Finding

Existing ledger history can be erased without an enforced confirmation gate

Content
View full analysis

Vulnerability Details

File Location: scripts/ledger.py, lines 166–184 and 507
Vulnerability Type: Destructive state replacement without executable confirmation enforcement
Risk Level: Medium

python
def cmd_init(args):
    data_dir = args.data_dir
    path = group_path(data_dir, args.group)
    if os.path.exists(path) and not args.force:
        die(f"group '{args.group}' already exists at {path} (use --force to reset)")
    members = [m.strip() for m in args.members.split(",") if m.strip()] if args.members else []
    state = {
        "group": args.group,
        "currency": args.currency.upper(),
        "members": members,
        "expenses": [],
        "next_id": 1,
        "created_at": now_iso(),
    }
    save_group(data_dir, args.group, state)
    print(f"Created group '{args.group}' ({state['currency']}) with members: "
          f"{', '.join(members) if members else '(none yet -- add with add-member)'}")
    print(f"Ledger file: {group_path(data_dir, args.group)}")
python
s.add_argument("--force", action="store_true", help="overwrite an existing group")

Technical Analysis

When the target group file already exists, cmd_init prevents replacement only if --force is absent. Supplying the Boolean flag bypasses the check immediately. The function then constructs a fresh state with an empty expenses array and passes it to save_group, which atomically replaces the existing ledger file.

SKILL.md instructs the Agent to use init --force only when the user explicitly requests a reset and after confirmation. However, this safeguard exists only as natural-language guidance. The executable entry point does not require a confirmation token, interactive approval, target-name repetition, backup, or other technical evidence of authorization.

Consequently, an Agent or another caller that invokes the script directly can cross the documented authorization bounda ...[truncated 1430 chars]

Remediation
View remediation

Remediation Suggestions

  • Require a non-Boolean confirmation value tied to the exact target, such as --confirm-reset-group "Goa Trip", and reject the operation unless it exactly matches --group.
  • For interactive use, display the existing ledger path and expense count and require an explicit confirmation before replacement.
  • For non-interactive Agent use, require the target-bound confirmation argument so natural-language guidance cannot be bypassed by direct invocation.
  • Create a timestamped backup of the existing ledger before resetting it, using restrictive file permissions and an atomic write.
  • Consider separating destructive reset behavior from init into a dedicated reset command to reduce accidental invocation.
  • Add regression tests proving that an existing ledger cannot be replaced using only --force and that an invalid or mismatched confirmation value is rejected.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (5)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill instructs the agent to use terminal commands, read and write ledger/export files, rely on environment/config values, and optionally access the network via the fx command, but it declares no explicit tool scope or allowed-tools policy. That mismatch can cause the runtime to grant broader capabilities than users expect, increasing the chance of unintended shell, filesystem, or network access if the skill is invoked in the wrong context or combined with adversarial user input.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · examples/stress_test.py (reported line 35)May include surrounding context.

python
def run(data_dir, *args):
    result = subprocess.run(
        [sys.executable, LEDGER, "--data-dir", data_dir, *args],
        capture_output=True, text=True,
    )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

This markdown file documents a destructive operation that removes an expense record, but unlike init --force, it does not warn the agent or user to confirm before proceeding. Because this guidance is user-facing documentation for a skill, omission of a warning on a delete action can lead to unintended data changes.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/ledger.py (reported line 455)May include surrounding context.

python
if len(code) != 3 or not code.isalpha():
            die(f"'{code}' is not an ISO 4217 currency code (e.g. USD, EUR, INR)")
    urls = [
        f"https://api.frankfurter.dev/v1/latest?base={base}&symbols={quote}",
        f"https://api.frankfurter.app/latest?from={base}&to={quote}",
    ]
    last_err = None

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/ledger.py (reported line 456)May include surrounding context.

python
die(f"'{code}' is not an ISO 4217 currency code (e.g. USD, EUR, INR)")
    urls = [
        f"https://api.frankfurter.dev/v1/latest?base={base}&symbols={quote}",
        f"https://api.frankfurter.app/latest?from={base}&to={quote}",
    ]
    last_err = None
    for url in urls:

Static analysis

No suspicious patterns detected.