Back to skill

Security audit

E-facture rapprochement

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its accounting purpose, but it needs review because it processes sensitive financial files and runs an unverified reconciliation engine from a sibling folder.

Review before installing. Use it only in a controlled workspace, verify that the sibling rapprochement-paiements skill is trusted and unchanged, prefer the default sandbox before --real, and avoid feeding untrusted XML/PDF files unless XML parsing is hardened or the run is isolated.

Vulnerability Patterns
  • Tool Hijacking and SpoofingModifies or replaces tools so legitimate-looking calls execute attacker logic
  • 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
Findings (2)

T07 · Tool Hijacking and Spoofing

Error
Location
scripts/main.py:53
Finding

Unverified Sibling Module Is Dynamically Executed

Content
View full analysis

Vulnerability Details

File Location: scripts/main.py, lines 53-59
Vulnerability Type: Untrusted local module loading and execution
Risk Level: High

Vulnerable Code:

python
def _load_engine():
    """Charge rapprochement-paiements/scripts/main.py comme moteur partagé."""
    engine_path = (Path(__file__).resolve().parents[2]
                   / "rapprochement-paiements" / "scripts" / "main.py")
    spec = _ilu.spec_from_file_location("rappro_engine", str(engine_path))
    mod = _ilu.module_from_spec(spec)
    spec.loader.exec_module(mod)
    return mod

Technical Analysis

The reconciliation engine is loaded from a sibling directory outside the audited project package. The code does not verify the target file's ownership, permissions, expected version, cryptographic digest, or signature before calling exec_module().

Loading a Python module executes its top-level code immediately. Consequently, control of the sibling path is equivalent to control of executable code within this Skill. The path is predictable and resolved relative to the current Skill, making replacement or pre-positioning straightforward for a user, package, or process that can write to the common parent directory.

The implementation also assumes that the resolved import specification and loader are valid without checking either value. Although that can cause availability failures, the primary security issue is execution across an unverified local trust boundary.

Attack Path

  1. The attacker obtains write access to the common directory containing this Skill and the expected rapprochement-paiements sibling directory.
  2. The attacker creates or replaces rapprochement-paiements/scripts/main.py with a malicious Python module.
  3. A user runs scripts/main.py after all required sidecars are available.
  4. write_outputs() invokes _load_engine().
  5. spec.loader.exec_module(mod) executes the attacker's top- ...[truncated 728 chars]
Remediation
View remediation

Remediation Suggestions

  • Package the reconciliation engine as a reviewed, version-pinned dependency instead of executing a file from a mutable sibling directory.
  • If external local loading is unavoidable, maintain an allowlisted cryptographic digest or signature and verify it before constructing the module specification.
  • Resolve the path and verify that it remains beneath an explicitly trusted, administrator-controlled directory.
  • Reject symbolic links and files writable by unauthorized users or groups.
  • Validate that spec and spec.loader are not None before use.
  • Run the reconciliation component under a restricted account or sandbox with access only to the required client directory.
  • Add a test proving that modified, unsigned, symlinked, or permission-unsafe engine files are rejected.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/invoice_parsers.py:39
Finding

Untrusted XML Is Parsed Without Explicit Entity and DTD Hardening

Content
View full analysis

Vulnerability Details

File Location: scripts/invoice_parsers.py, lines 39-43
Additional Locations: scripts/invoice_parsers.py, lines 60-61 and 111-112; scripts/bank_parsers.py, line 229
Vulnerability Type: Unsafe XML parser configuration
Risk Level: Medium

Vulnerable Code:

python
def detect_xml_kind(data: bytes) -> str | None:
    """'ubl' | 'cii' | None à partir du contenu XML."""
    try:
        root = etree.fromstring(data)
    except etree.XMLSyntaxError:
        return None

The same unconfigured parser entry point is used in the invoice parsers:

python
def parse_ubl(data: bytes) -> dict:
    root = etree.fromstring(data)
    inv = {"source": "ubl"}
python
def parse_cii(data: bytes) -> dict:
    root = etree.fromstring(data)
    inv = {"source": "cii"}

Bank XML detection also parses untrusted input without a hardened parser:

python
if suffix == ".xml":
    data = path.read_bytes()
    if b"camt.053" in data[:4000].lower() or _find(etree.fromstring(data), "Ntry"):
        return parse_camt053(data)
    return None

Technical Analysis

Invoice and bank documents are externally supplied data, but they are passed directly to lxml.etree.fromstring() without an explicit XMLParser. The code therefore relies on the defaults of the installed lxml and libxml2 versions rather than enforcing the application's security policy.

The parser does not explicitly prohibit DTD loading, entity resolution, or oversized and deeply nested documents. Unsafe parser behavior can permit local entity expansion or denial-of-service payloads. Even where a particular installed version disables some external resolution by default, relying on version-dependent defaults creates an avoidable security boundary failure.

No input-size limit is applied before the complete XML file is read into memory and parsed. A malicious document can therefore also ...[truncated 1233 chars]

Remediation
View remediation

Remediation Suggestions

  • Create one shared hardened parser and use it for every invoice and bank XML operation:
python
SAFE_XML_PARSER = etree.XMLParser(
    resolve_entities=False,
    load_dtd=False,
    no_network=True,
    huge_tree=False,
    recover=False,
)

root = etree.fromstring(data, parser=SAFE_XML_PARSER)
  • Prefer defusedxml-compatible parsing where feasible for untrusted financial documents.
  • Reject XML containing a document type declaration when DTD support is not required.
  • Enforce a conservative maximum file size before read_bytes() and parsing.
  • Apply limits for document depth, element count, text length, and processing time.
  • Ensure every XML entry point, including detection functions and CAMT parsing, uses the same hardened configuration.
  • Add regression tests containing external-entity, nested-entity, oversized, and deeply nested XML payloads and confirm that they are rejected safely.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

La description couvre un pipeline plus large d’ingestion documentaire + extraction LLM + rapprochement + écriture de plusieurs fichiers contractuels. Le code observé correspond seulement à la phase de rapprochement sur données déjà normalisées. Il n’y a ni parsing Factur-X/UBL/CII/CAMT/OFX/CSV, ni OCR/LLM pour tickets/photos/PDF, ni production visible de company.json. En revanche, la partie rapprochement, détection d’impayés/paiements orphelins/opérations exclues, anomalies et relances est cohérente avec une sous-partie importante de la description. Donc il existe un écart matériel entre la portée déclarée et le comportement réel de ce chunk, même si une partie centrale du but annoncé est bien représentée.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger description is intentionally broad and instructs activation even when the user does not explicitly request reconciliation. In an agent system, overbroad auto-activation can cause the skill to ingest sensitive accounting and banking documents, run shell-backed processing, or write outputs in situations where the user intended a narrower task, creating a risk of data over-collection and unintended side effects.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The natural-language instructions and usage guidance are fully French throughout the file, and there is no indication that users may choose another language or that the skill is intentionally restricted to a French-only audience. Under the policy, forcing a specific language without user opt-in is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill declares broad operational behavior involving file reads, file writes, and shell execution, but does not constrain those capabilities with an explicit tool scope. In an agent environment, missing permissions boundaries increases the chance the skill can be invoked with more access than necessary, enabling unintended filesystem modification or command execution if triggered on untrusted inputs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This file describes extracting bank statement data from PDFs/scans and writing a normalized sidecar file, which involves processing and storing sensitive financial information such as IBANs, balances, and transactions. The description does not include any warning about privacy, data sensitivity, or local file creation impacts to the user.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This markdown file contains user-facing operational instructions exclusively in French, and there is no indication that the skill is region-specific or that users can opt into this language. Under the policy, forcing a specific language without user choice is a natural-language policy violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

All user-facing natural-language documentation in the module header is written exclusively in French, and the function docstrings continue that pattern. Under the stated policy, forcing a specific language without opt-in or an explicit justification can be a locale-policy violation.

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/invoice_parsers.py (reported line 153)May include surrounding context.

python
"""Renvoie l'XML CII embarqué (factur-x.xml / zugferd-invoice.xml) ou None."""
    with tempfile.TemporaryDirectory() as td:
        try:
            subprocess.run(["pdfdetach", "-saveall", "-o", td, str(pdf_path)],
                           check=True, capture_output=True, timeout=60)
        except (subprocess.CalledProcessError, FileNotFoundError, subprocess.TimeoutExpired):
            return None

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/main.py (reported line 91)May include surrounding context.

python
def _pdf_has_text(path: Path) -> bool:
    try:
        out = subprocess.run(["pdftotext", "-l", "2", str(path), "-"],
                             capture_output=True, timeout=30)
        return len((out.stdout or b"").strip()) > 200
    except (FileNotFoundError, subprocess.TimeoutExpired):

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The file’s natural-language documentation is entirely in French, beginning with a French module docstring and continuing with French user-facing explanatory comments throughout the file. Under the stated policy, forcing a specific language without opt-in or documented regional justification is a natural-language locale violation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The top-level docstring forces a specific language for core skill documentation, which can violate language/locale policy when no user opt-in or documented regional justification is provided. Because this is the primary natural-language description of the skill's behavior, it may make the skill inaccessible to users expecting another language.

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/test_e2e.py (reported line 29)May include surrounding context.

python
def run_main(root, *extra):
    return subprocess.run([sys.executable, str(HERE / "main.py"), str(root), *extra],
                          capture_output=True, text=True)

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

This markdown file contains user-facing natural-language instructions exclusively in French, and nowhere indicates that the skill is intentionally limited to French-speaking users or a French locale. Under the policy rule for language/locale, forcing a specific language without opt-in is a natural-language policy concern.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
79% confidence
Finding

This module reads invoice XML/PDF content and, for PDFs, invokes pdfdetach to extract embedded XML attachments. Invoices commonly contain sensitive financial and business data, but this code provides no user-facing prompt, log message, or warning comment/docstring disclosing that local files will be read and embedded attachments unpacked for processing.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

The top-level docstring is written entirely in French ('Tests du moteur...', 'Lancer : ...'), which imposes a specific language in natural-language instructions without any opt-in or explanation of a locale-specific constraint. Under the policy rules, language-specific instructions should either offer choice or be clearly justified as region-specific.

Content

No source excerpt is available for this finding.

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/main.py:57