Back to skill

Security audit

Odoo JSON2 Operator

Security checks for vulnerabilities and agentic risk

Overview

This Odoo automation skill is purpose-aligned, but it ships and stores bearer API keys in plaintext and can perform high-impact business data changes.

Review before installing. Treat the bundled Odoo API key as exposed and rotate it. Do not use production credentials with this skill until plaintext profile storage is removed or replaced with a secure secret store, HTTPS is enforced, and cleanup is limited to files the client created under its temp directory.

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

Error
Location
scripts/connections.json:1
Finding

Plaintext Odoo bearer credential bundled and persistently stored in the Skill package

Content
View full analysis
None: PROFILE_FILE.write_text( json.dumps(profiles, ensure_ascii=False, indent=2) + "\n", encoding="utf-8", ) def save_profile(name: str, base_url: str, database: str, api_key: str) -> int: profiles = _load_profiles() profiles[name] = { "base_url": base_url.rstrip("/"), "database": database, "api_key": api_key, } _save_profiles(profiles) print(f"Profile saved: {name}") return 0 ``` `SKILL.md:42-45, 116`: ```markdown - Persist successful connections locally as named profiles containing `base_url`, `database`, and `api_key`. - Reuse saved profile by default in later turns; do not repeatedly ask for credentials. - If multiple profiles exist and user does not specify a target system at session start, ask which system/profile to use before proceeding. - Ask for `base_url`, `database`, and `api_key` only when no usable profile exists or when user wants a new/updated system. ``` ```markdown - Never log or persist API keys in files, command history snippets, or chat output. ``` ### Technical Analysis The distributed `connections.json` contains a plaintext, live-looking bearer credential together with its target host and database. The credential value has been redacted in this report to avoid further disclosure. The profile-saving implementation also serializes future API keys directly into `scripts/connectio ...[truncated 2954 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/odoo_json2_client.py:145
Finding

Automatic payload cleanup can delete JSON files outside the designated temporary directory

Content
View full analysis
bool: if path.suffix.lower() != ".json": return False if path.name.startswith("tmp_"): return True try: path.relative_to(tmp_dir.resolve()) return True except ValueError: return False def load_payload( payload: str | None, payload_file: str | None, cleanup_tmp_payload_file: bool, tmp_dir: Path, ) -> tuple[dict[str, Any], Path | None]: if payload and payload_file: raise ValueError("Use either --payload or --payload-file, not both.") cleanup_file: Path | None = None if payload_file: payload_path = resolve_payload_path(payload_file, tmp_dir) with payload_path.open("r", encoding="utf-8") as handle: data = json.load(handle) if cleanup_tmp_payload_file and is_cleanup_candidate(payload_path, tmp_dir): cleanup_file = payload_path ``` `scripts/odoo_json2_client.py:278-285`: ```python tmp_dir = Path(args.tmp_dir).resolve() tmp_dir.mkdir(parents=True, exist_ok=True) cleanup_tmp_payload_file = args.cleanup_tmp_payload_file and not args.keep_tmp_payload_file payload, cleanup_file = load_payload( args.payload, args.payload_file, cleanup_tmp_payload_file, tmp_dir, ) ``` `scripts/odoo_json2_client.py:294-301`: ```python finally: if cleanup_file and cleanup_file.exists(): try: cleanup_file.unlink() except OSError as err: print(f"WARN: failed to remove temp payload file {cleanup_file}: {err}", file=sys.stderr) ``` ### Technical Analysis The cleanup policy considers any JSON file whose basename begins with `tmp_` to be disposable: ``` ...[truncated 2306 chars]
Remediation
View remediation
bool: candidate = path.resolve() root = tmp_dir.resolve() if candidate.suffix.lower() != ".json": return False try: candidate.relative_to(root) except ValueError: return False return candidate != root ``` 2. **Only delete files created or explicitly registered by the client.** - Track temporary files at creation time. - Do not automatically delete arbitrary caller-supplied paths. - Prefer `tempfile.NamedTemporaryFile` or a similarly safe API. 3. **Make deletion explicit for external files.** - Treat `--payload-file` as caller-owned by default. - Add an explicit opt-in flag if users genuinely need external-file deletion. - Display the resolved path before deletion when interactive confirmation is possible. 4. **Harden against path and link ambiguity.** - Resolve paths before containment checks. - Reject symlinks for automatically managed payloads where appropriate. - Ensure that cleanup cannot follow a path outside the temporary root. 5. **Preserve failed-request evidence safely.** - Consider deleting an internally created temporary payload only after successful completion. - If deletion after failure remains necessary, document that behavior clearly and limit it to client-created files. ]]>
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (9)

Intent-Code Divergence

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill instructs the agent to persist successful connections as profiles containing base_url, database, and api_key, while later guardrails say API keys must never be logged or persisted. Persisting bearer credentials locally creates a direct secret-retention risk: theft of the profile store or unintended reuse could grant ongoing access to the Odoo instance.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill tells the agent to save and reuse connection profiles containing an API key, but it does not clearly warn the user that credentials may be retained locally. That lack of disclosure undermines informed consent and can lead users to reveal production secrets without understanding the storage and reuse behavior.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill enables file read, file write, and network actions but does not declare any explicit tool scope or permissions boundary. That omission increases the chance of over-broad invocation and makes it harder for reviewers or runtime policy to constrain sensitive operations such as credential handling and outbound API access.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The invocation description is broad enough to match generic requests to query, mutate, or automate business actions against Odoo. In a skill that can perform authenticated network calls and data mutations, overly broad routing can cause accidental activation for ambiguous user prompts and raise the chance of unauthorized or unsafe operations.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The workflow requires analysis and reports to be produced 'in plain Chinese,' and later repeats 'Use plain Chinese,' without indicating user opt-in or a region-specific constraint. This imposes a language choice unconditionally and can violate language/locale policy expectations.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The instructions explicitly direct persistence and automatic reuse of user-supplied connection credentials, including bearer API keys. In the context of an Odoo operator with network and mutation capability, retaining and silently reusing credentials materially increases the risk of secret leakage, cross-task misuse, and unintended actions against the wrong environment.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The markdown explicitly instructs the skill to 'Use plain Chinese' for analysis output, with no option for another language. This is a natural-language policy issue because it enforces a locale/language choice without opt-in or justification.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The playbook mandates a fixed output structure using Chinese headings such as '结论摘要' and '关键发现'. This imposes a specific language on responses without documenting user preference, opt-in, or a justified locale-specific constraint, which is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script persists bearer API keys in a local JSON profile file in plaintext, with no warning to the user and no file-permission hardening. If the workstation, repository, backups, or shared filesystem are accessible to other users or processes, the stored token can be recovered and used to query or mutate Odoo data through the JSON-2 API.

Content

No source excerpt is available for this finding.