Back to skill

Security audit

drivethru-payable-matching

Security checks for vulnerabilities and agentic risk

Overview

This skill is coherent for payable matching, but it can make live financial and fulfillment changes and self-installs runtime dependencies, so it belongs in Review before installation.

Install only in an environment where the operator wants an agent to change Odoo purchasing/accounting records and possibly post vendor bills. Configure least-privilege Odoo/MCP credentials, prefer draft or dry-run mode until the workflow is proven, review dependency bootstrapping policy, and require clear human authorization for bill posting, quantity changes, dropship validation, and credential-sharing delegation.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (18)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill extends beyond payable matching into inventory receipt validation and downstream sales-order delivery updates for dropship flows. That scope expansion grants the agent authority to alter fulfillment and revenue-affecting records based on reviewer chatter, increasing the blast radius from AP corrections to stock, logistics, and customer billing changes if the agent misinterprets instructions or is manipulated.

Content

No source excerpt is available for this finding.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
92% confidence
Finding

os.execv replaces the current process with a new interpreter from a freshly created virtual environment, transferring execution into code and dependencies that were just installed at runtime. While not command injection here, it is still a powerful process-control primitive that compounds the risk of the bootstrap flow by handing control to newly fetched software.

Content

Scanner excerpt · scripts/_bootstrap.py (reported line 143)May include surrounding context.

python
# Re-run the exact same command under the venv interpreter; on that second
    # entry ensure() finds everything present and returns immediately.
    os.execv(str(py), [str(py), *sys.argv])

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill exposes powerful capabilities including shell execution, MCP access, network/API access, and environment-secret usage, but it does not declare any explicit tool scope restrictions. In a live financial workflow that can modify POs, create bills, post accounting entries, and delegate credentials, the lack of allowlisted permissions increases the chance of unintended or over-broad tool use if the skill is invoked in the wrong context or manipulated by prompt injection.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The invocation phrases are broad enough to match common AP or purchasing requests, which can cause the skill to trigger outside the precise payable-matching scenario the operator intended. Because this skill performs live financial writes and filing actions, an overly eager match can result in unintended PO price edits, document moves, or vendor bill creation/posting based on loosely related user prompts.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
78% confidence
Finding

The skill instructs the agent to create draft bills, post matching payables, move documents, and assign reviewer activities, which creates durable state changes across sessions and systems. In a finance context, such persistence is risky because an accidental invocation, partial run, or prompt-injected workflow can leave behind posted accounting entries or review tasks that are difficult to reverse and may affect the ledger.

Content

Scanner excerpt · SKILL.md (reported line 20)May include surrounding context.

md
`Questions` subfolder — escalating genuine questions to a reviewer (default
  Zach Tucker). Also runs the buying-group payables flow: pull Sports Inc
  invoices from the SportsLink API (via the `sportsinc-sportslink` adapter),
  reconcile each to its PO, correct price variances, create the vendor bill and
  — when the bill total matches the invoice within tolerance — POST it, leaving
  any mismatch in draft for a human ("get the Sports Inc invoices and bill
  them", "match the SI invoices to POs and post the payables", "match the vendor

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This markdown file instructs the operator to move reviewed documents into Matched or Questions and elsewhere describes posting vendor bills, which can affect accounting workflow and financial records. While the procedure explains what to do, it does not include an explicit user warning about the consequences of these state-changing actions or the need for care before executing them.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Lines L014-L017 describe the workflow skill as limited to reconciling, correcting/escalating, and creating the draft bill, while assigning 'the Odoo writes' to the ERP adapter. But the rest of the document explicitly instructs this skill to invoke posting and multiple Odoo write operations such as paymatch.py post, ap_post_vendor_bill, PO line updates, bill-line edits, and dropship validations. This is an active contradiction in the file's own intent documentation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The workflow marks SportsLink documents historical immediately after draft bill creation, before successful posting or final human resolution. If posting later fails, the bill remains draft, or a review rejects the bill, the source document may disappear from future runs, creating a silent reconciliation gap and increasing the risk of invoices being stranded, overlooked, or manually mishandled.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This helper adds a self-updating/bootstrap capability that installs dependencies and re-execs the process, which exceeds the minimum behavior needed for invoice/PO matching. In a low-trust agent setting, code that can fetch and run new software at execution time materially expands the attack surface and weakens deployment controls.

Content

No source excerpt is available for this finding.

Unbounded Resource Access

Medium
Category
Excessive Agency
Confidence
75% confidence
Finding

Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Content

Scanner excerpt · scripts/_bootstrap.py (reported line 111)May include surrounding context.

python
return

    # We already re-exec'd into our managed venv and deps *still* aren't
    # importable — the install genuinely failed; don't loop forever.
    if _in_our_venv():
        raise SystemExit(
            "ERROR: paymatch dependencies still missing after bootstrap ("

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill performs runtime environment modification by creating a venv, installing packages, and switching execution into it. That behavior is dangerous in a security-sensitive agent because it can bypass host expectations about immutability, approved dependencies, and review of what code is actually executed.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
90% confidence
Finding

Creating a virtual environment via subprocess.run([uv, 'venv', ...]) is an execution of an external tool that alters the runtime environment from inside the skill. The direct command is not shell-injected, but the capability is still security-relevant because it prepares an execution context for subsequent package installation and re-execution.

Content

Scanner excerpt · scripts/_bootstrap.py (reported line 134)May include surrounding context.

python
# uv chatter goes to stderr so the script's JSON stdout stays clean.
    if not py.exists():
        _VENV.parent.mkdir(parents=True, exist_ok=True)
        subprocess.run([uv, "venv", str(_VENV)], check=True, stdout=sys.stderr)
    subprocess.run(
        [uv, "pip", "install", "--python", str(py), *REQUIREMENTS],
        check=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
95% confidence
Finding

The code installs Python packages at runtime by invoking uv pip install, which modifies the execution environment and fetches code from package sources during skill execution. Even though it uses argument lists rather than a shell and the package names are hardcoded, this still introduces supply-chain and environment-tampering risk that is unrelated to the core payable-matching task.

Content

Scanner excerpt · scripts/_bootstrap.py (reported line 135)May include surrounding context.

python
if not py.exists():
        _VENV.parent.mkdir(parents=True, exist_ok=True)
        subprocess.run([uv, "venv", str(_VENV)], check=True, stdout=sys.stderr)
    subprocess.run(
        [uv, "pip", "install", "--python", str(py), *REQUIREMENTS],
        check=True,
        stdout=sys.stderr,

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 · scripts/paymatch.py (reported line 260)May include surrounding context.

python
tmp.write(data)
        tmp.flush()
        try:
            out = subprocess.run(
                [exe, "-layout", "-nopgbrk", tmp.name, "-"],
                capture_output=True,
                timeout=60,

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 · scripts/paymatch.py (reported line 315)May include surrounding context.

python
if not exe:
        return None
    try:
        out = subprocess.run(
            [exe, png_path, "-", "--psm", "6"],
            capture_output=True,
            timeout=120,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
56% confidence
Finding

This is a natural-language operational directive that fixes a model-selection preference in the skill description. While not necessarily improper, it imposes a specific runtime choice without indicating that a user or operator can opt in or override it.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/paymatch.py (reported line 196)May include surrounding context.

python
def _result_field(result: Any, camel: str, snake: str) -> Any:
    """Read a CallToolResult field across mcp SDK generations — 1.x exposes
    camelCase attributes (`structuredContent`, `isError`), 2.x snake_case."""
    v = getattr(result, camel, None)
    if v is None:
        v = getattr(result, snake, None)
    return v

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/paymatch.py (reported line 198)May include surrounding context.

python
camelCase attributes (`structuredContent`, `isError`), 2.x snake_case."""
    v = getattr(result, camel, None)
    if v is None:
        v = getattr(result, snake, None)
    return v

Static analysis

No suspicious patterns detected.