Back to skill

Security audit

group-expense-settler

Security checks for vulnerabilities and agentic risk

Overview

The skill is an offline expense-splitting tool with no hidden access, but its documented ledger format can silently produce incorrect payment instructions.

Install only if you are comfortable reviewing the ledger and output carefully. Do not use inline comments in ledger lines, avoid ledgers from untrusted people, verify parsed participants with --show-items, and treat the settlement lines as recommendations to double-check before sending money.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/settle_up.py:42
Finding

Documented Inline Comments Are Parsed as Expense Participants

Content
View full analysis
list[Expense]: expenses = [] for lineno, raw in enumerate(text.splitlines(), 1): line = raw.strip() if not line or line.startswith("#"): continue parts = [p.strip() for p in line.split(",") if p.strip()] if len(parts) < 3: raise ValueError(f"line {lineno}: need payer,amount,participants — got {line!r}") payer, amount = parts[0], parse_amount(parts[1]) # participants: space-separated within one field, or extra CSV fields people = [p for chunk in parts[2:] for p in chunk.split()] if not people: raise ValueError(f"line {lineno}: no participants in {line!r}") if len(set(people)) != len(people): raise ValueError(f"line {lineno}: duplicate participant in {line!r}") expenses.append(Expense(payer, amount, people)) if not expenses: raise ValueError("ledger is empty") return expenses ``` The README explicitly recommends input containing inline comments: ```text Ana,450,Ana Ben Cho Dan # hotel Ben,120,Ben Cho # taxi Cho,80,Ana Ben Cho Dan # groceries Dan,60,Ben Cho Dan # gas ``` ### Technical Analysis The parser ignores a comment only when the entire trimmed line begins with `#`. It does not remove an inline comment before splitting the participant field. For the documented line: ```text Ana,450,Ana Ben Cho Dan # hotel ``` the participant expression is split on whitespace and becomes: ```text Ana, Ben, Cho, Dan, #, hotel ``` The tokens `#` and `hotel` are therefore treated as real participants and receive shares of the expense. This changes every legitimate participant ...[truncated 1488 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/settle_up.py:49
Finding

Untrusted Participant Names Are Written to the Terminal Without Control-Character Sanitization

Content
View full analysis
list[dict]: """Greedy max-creditor vs max-debtor. ≤ n-1 transfers, cent-exact.""" creditors = {p: n for p, n in nets.items() if n > 0} debtors = {p: -n for p, n in nets.items() if n < 0} transfers = [] while creditors and debtors: c = max(creditors, key=lambda p: (creditors[p], p)) d = max(debtors, key=lambda p: (debtors[p], p)) amount = min(creditors[c], debtors[d]) transfers.append({ "from": d, "to": c, "amount_c": amount, "line": f"{d} pays {c} ${amount/100:.2f}", }) ``` The resulting values are printed to the terminal without escaping: ```python for person in people: print(f"{person:<12}{fmt(paid.get(person,0)):>13}{fmt(share.get(person,0)):>13}{fmt(nets[person]):>13}") print(f"\nSETTLEMENT — {len(transfers)} transfer{'s' if len(transfers)!=1 else ''}" f" (minimum possible ≤ {len(people)-1})") for t in transfers: print(f" → {t['line']}") ``` ### Technical Analysis Payer and participant names originate in the ledger and are treated as trusted display strings. The parser does not reject the escape character, C0/ ...[truncated 1813 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Note
Location
scripts/settle_up.py:34
Finding

Amounts and Weights Lack Robust Validation and Can Trigger Uncaught Failures

Content
View full analysis
int: s = s.strip().replace("$", "") cents = round(float(s) * 100) if cents < 0: raise ValueError(f"negative amount: {s}") return cents ``` Weights are used without requiring positive values: ```python def split_cents(amount_c: int, people: list[str], weights: dict[str, int]) -> dict[str, int]: """Split amount among people ∝ weights, distributing remainder cents to the earliest-listed people. Sum of shares == amount exactly.""" total_w = sum(weights.get(p, 1) for p in people) exact = {p: Fraction(amount_c * weights.get(p, 1), total_w) for p in people} shares = {p: int(v) for p, v in exact.items()} # floor remainder = amount_c - sum(shares.values()) # give leftover cents to people with largest fractional parts (stable) order = sorted(people, key=lambda p: (-(exact[p] - shares[p]), people.index(p))) for p in order[:remainder]: shares[p] += 1 assert sum(shares.values()) == amount_c return shares ``` Weight parsing occurs before the protected error-handling block: ```python weights = {} if args.weights: for pair in args.weights.split(","): name, _, w = pair.partition(":") weights[name.strip()] = int(w) try: text = open(args.ledger, encoding="utf-8").read() expenses = parse_ledger(text) people, paid, share, nets, item_shares = compute_nets(expenses, weights) except (OSError, ValueError) as e: print(f"ERROR: {e}", file=sys.stderr) return 2 ``` ### Technical Analysis The input-validation boundary has several gaps: - Malformed weight values cause `i ...[truncated 2009 chars]
Remediation
View remediation
Vulnerability Patterns
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (1)

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding

The code substantially matches the core declared purpose: it reads a ledger, computes participant shares with exact cent handling, supports weighted splits, and produces a settlement plan with few transfers plus reporting. However, parts of the description overstate the implemented functionality. In particular, 'shared vs personal items' is not a separate feature—personal/shared behavior is only indirectly represented by the participant list on each expense. Also, weighting is only provided as a global per-person weight map, not as flexible per-expense split specifications. Finally, the claimed 'fairness audit' is limited to standard totals and item shares rather than a richer audit mechanism. There are no suspicious undeclared capabilities or resource accesses; the mismatch is due to some described features being broader than what the code actually implements.

Content

No source excerpt is available for this finding.