Back to skill

Security audit

drivethru-odoo

Security checks for vulnerabilities and agentic risk

Overview

This skill appears to be a real Odoo integration, but it grants broad live ERP access and relies mostly on agent instructions rather than technical locks before business changes are made.

Install only for agents and users that should have broad Odoo authority. Prefer least-privilege or read-only credentials where possible, verify the attached drivethru_mcp server and any vendor checkout skills separately, require explicit dry-run or user approval before live writes, and avoid echoing customer PII or sensitive ERP data into chat unless necessary.

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/odoo_mcp.py:132
Finding

Live ERP Mutations Are Not Enforced by a Technical Confirmation Gate

Content
View full analysis

Vulnerability Details

File Location: scripts/odoo_mcp.py:132-157
Vulnerability Type: Missing authorization and confirmation enforcement for mutating operations
Risk Level: Medium

Complete Code Snippet

python
async def cmd_call(args) -> int:
    if args.json_args is not None:
        raw = args.json_args
    elif not sys.stdin.isatty():
        raw = sys.stdin.read().strip()
    else:
        raw = ""
    raw = raw or "{}"
    try:
        arguments = json.loads(raw)
    except json.JSONDecodeError as e:
        return _fail("invalid_arguments", f"Tool arguments must be valid JSON: {e}", code=2)
    if not isinstance(arguments, dict):
        return _fail("invalid_arguments", "Tool arguments must be a JSON object.", code=2)

    async def fn(session):
        result = await session.call_tool(args.tool, arguments)
        payload = result.model_dump(mode="json", exclude_none=True)
        print(json.dumps(payload, indent=2))
        # 1.x exposes camelCase `isError`; 2.x snake_case `is_error`.
        is_error = getattr(result, "isError", None)
        if is_error is None:
            is_error = getattr(result, "is_error", False)
        return 1 if is_error else 0

    return await _run(fn)

Related write-capable legacy entry points include:

python
# scripts/sales.py:42-45
def _create_order(client: OdooClient, args: dict[str, Any]) -> Any:
    if not args:
        raise ValueError("create-order requires an order object on stdin.")
    return client.create_sale_order(args)
python
# scripts/ap.py:54-69
def _update_po_lines(client: OdooClient, args: dict[str, Any]) -> Any:
    return client.update_po_lines(
        _require_int(args, "po_id"),
        lines=args.get("lines"),
        freight_cost=args.get("freight_cost"),
        fees_cost=args.get("fees_cost"),
    )


def _create_bill(client: OdooClient, args: dict[str, Any]) ->
...[truncated 4094 chars]
Remediation
View remediation

Remediation Suggestions

  1. Classify every available MCP tool as read-only or mutating using a strict local allowlist; do not rely solely on remote tool descriptions.
  2. Require mutating calls to include an explicit approval artifact bound to the action, target object, material parameters, requesting user, and short expiration time.
  3. Validate and consume that approval before calling session.call_tool; reject absent, expired, reused, or mismatched approvals.
  4. Default all legacy clients to dry-run and require an explicit, narrowly scoped flag to enable live writes.
  5. Add dry-run handling to every mutating method, including update_po_lines, so behavior is consistent.
  6. Separate read and write credentials where supported, granting the default execution path only read access.
  7. Add transaction summaries and server-side audit records containing the approving identity and approved parameters.
  8. Add automated tests proving that each mutating MCP tool and legacy action fails closed without valid approval.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a broad, multi-domain Odoo MCP integration with runtime tool discovery and numerous business workflows. The supplied code chunk only exposes a small set of hard-coded AP-related operations around purchase orders, vendor bills, and vendor search. While part of the declared AP PO→vendor-bill flow is represented, most of the declared capabilities are absent, and the architecture differs materially: this is a specific CLI script calling OdooClient methods, not a general MCP skill discovering available tools at runtime. Therefore the description does not accurately represent what this code chunk actually does.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description substantially overstates and partially misstates the implementation. The code does cover several declared Odoo business domains: eBay products/inventory, pushing eBay orders, reading tracking, AP purchase-order and vendor-bill operations, and MRP production scheduling. However, its mechanism is materially different from the declared one: it is not an MCP-based tool-discovery skill at all, but a hard-coded REST client for a custom agent_api module. More importantly, several declared workflows are absent from the supplied code, including the Documents app PO-matching/pricing-review workflow with filing into Matched or Questions, the replenishment purchasing workflow and vendor-skill handoff, and Knowledge base retrieval scoped by permissions. Because the description claims capabilities and architecture not present in the code, this is a mismatch.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
read its `SKILL.md`. For adidas that's a CLI: `echo '{...}' | python3

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill exposes broad capabilities via environment secrets, MCP connectivity, network access, and shell execution, but it does not declare any explicit tool scope or allowed-tools boundary. In a high-privilege Odoo integration, that increases the chance an agent will invoke unintended tools or shell paths, making privilege use harder to audit and constrain.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The activation guidance says to use the skill whenever the user needs to read from or write to Odoo, which is extremely broad for a system that can perform sensitive ERP operations. Overly broad triggering increases the chance the agent will access or modify business data in situations where a narrower, safer workflow should apply.

Content

No source excerpt is available for this finding.

Behavior Manipulation

Medium
Category
Prompt Injection
Confidence
93% confidence
Finding

The instruction 'Never tell the user you can't reach Odoo' is behavior-shaping language that pressures the agent away from transparent failure reporting. In a transactional ERP context, this can encourage unsafe persistence, obscure operational limitations, and increase the risk of hallucinated or misleading responses when systems are unavailable.

Content

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

md
tool surface (`documents_*`, `ap_*`, `po_*`, `mfg_*`, `production_*`,
`replenish_*`, `knowledge_*`, `ebay_*`, `docs_*`) exposed over Streamable-HTTP.
`ODOO_MCP_URL` and `ODOO_MCP_TOKEN` are already configured for this agent.
**Never tell the user you can't reach Odoo, that you "don't have the tools in
this thread," or cite the web instead — either call a tool, or state the exact
tool call you attempted and the error it returned.**

Context Window Stuffing

Medium
Category
Memory Poisoning
Confidence
85% confidence
Finding

Skill attempts to fill the context window with filler content, displacing legitimate instructions and safety constraints. This can degrade agent performance or bypass safety boundaries.

Content

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

md
`scripts/po_docs.py` is a shell helper for the **PO pricing review**: it lists a
Documents-app folder and returns each file's **extracted text** (never base64 or
a page render) so a many-document run doesn't fill the context window, plus a
`move` action to file a document into `Matched`/`Questions`. It's a
context-economy optimization for shell runtimes — with only native tools, use
`documents_search` + `documents_get` one document at a time instead (don't pull

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

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

md
- **`ODOO_MCP_URL` and `ODOO_MCP_TOKEN` are set** in the agent's environment
  (`ODOO_MCP_TOKEN` is sent as `Authorization: Bearer`). If either is missing,
  the script path exits with `{"error": {"type": "config_error", ...}}` (exit 2)
  and a native call can't authenticate. Secrets come from the environment; never
  ask the user to paste the key into chat.
- **The `drivethru_mcp` server is attached to *this* agent** — as a native MCP
  connection (native-tools path) **or** via a shell + Python (script path).

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documentation explicitly defines transmission of buyer and order personally identifiable information, including name, address, phone, and email, but provides no privacy, minimization, retention, or access-control guidance. In the context of an agent skill that can read from and write to Odoo and may be invoked from conversations, this increases the risk that downstream tooling or operators handle, expose, or over-share sensitive customer data without adequate safeguards.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill instructs the agent to always move reviewed documents out of the Purchasing inbox into Matched or Questions, which changes document state and workflow visibility. Because this is a write action with operational consequences and no explicit confirmation or warning step, an agent could prematurely refile documents, hide items from the inbox, or misroute documents after an incorrect review.

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

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

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The workflow hard-codes 'Zach Tucker' as the reviewer for Questions items, creating a fixed-routing behavior that may be inappropriate across deployments, teams, or permission contexts. This can leak sensitive document context to the wrong person, create operational bottlenecks, or let an attacker predictably route exceptions to a specific target for spam or overload.

Content

No source excerpt is available for this finding.

Scope Creep

Low
Category
Excessive Agency
Confidence
70% confidence
Finding

Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Content

Scanner excerpt · scripts/po_docs.py (reported line 7)May include surrounding context.

python
The expensive, context-bloating part of "check the docs in the Purchasing
folder against the POs" is reading the documents: `documents_get` returns each
file's bytes as base64, and rendering a PDF through a multimodal reader adds a
per-page image on top of that. Do that for 5-50 documents a run, many times a
day, and the agent's context window fills with base64 and page renders instead
of the few hundred characters of text that actually matter.

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/po_docs.py (reported line 68)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/po_docs.py (reported line 70)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.