Back to skill

Security audit

Identification client

Security checks for vulnerabilities and agentic risk

Overview

This accounting skill is not malicious, but it should be reviewed because it can persist and alter client registry data in ways that are broader than its documentation states.

Install only if you are comfortable with this skill directly modifying your local client registry. Before use, keep backups of clients.json, review newly created or changed client records, and treat invoice or expense-report derived SIREN/client additions as needing human confirmation.

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

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/identifier.py:366
Finding

Unconfirmed Expense Reports Can Automatically Create and Enrich Client Records

Content
View full analysis

Vulnerability Details

File Location: scripts/identifier.py, lines 366-375
Vulnerability Type: T09: Insecure Skill Coding Practices
Risk Level: Medium

Vulnerable Code

python
if company:
    slug, method = match_side(company, clients)
    is_new = False
    if not slug:
        slug = slugify(company)
        _, is_new = upsert_client(clients, slug, company, "auto-from-note-de-frais",
                                  siren=siren, sources=["note-de-frais"])
        if is_new:
            for c in clients:
                if c["slug"] == slug:
                    c["aValider"] = True

Technical Analysis

The implementation treats the company and siren_found fields extracted from an expense report as sufficiently trustworthy to create a persistent client record. This behavior conflicts with the documented trust boundary in SKILL.md, which states that automatic client creation is limited to a confirmed bank-statement holder.

Marking the record with aValider does not prevent the record or its SIRENs from being saved and used by subsequent identification operations. An incorrectly extracted or attacker-influenced company identity can therefore become part of the authoritative client registry before human confirmation.

Attack Path

  1. An attacker or malformed document supplies an expense report containing a crafted company name and SIREN.
  2. The upstream analysis places these values in analyse.fields.company and analyse.fields.siren_found.
  3. The company name does not match an existing client.
  4. slugify() generates a new identifier, and upsert_client() immediately inserts the company and SIREN into clients.json.
  5. Later documents can match the injected record by company name or SIREN.
  6. The poisoned identity may consequently influence later document attribution.

Impact Assessment

Exploitation does not grant operating-system privileges or arbitrary ...[truncated 269 chars]

Remediation
View remediation

Remediation Suggestions

  • Restrict automatic client creation to the sources explicitly documented as authoritative.
  • Return needs_review: true when an expense report references an unknown company.
  • Do not persist company, SIREN, IBAN, or domain information until a human explicitly confirms the client.
  • Store pending suggestions separately from the authoritative clients.json registry.
  • Validate that a SIREN is side-specific and belongs to the identified company before adding it.
  • Add tests proving that an unknown expense-report company cannot modify the authoritative registry without confirmation.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/identifier.py:280
Finding

Missing Slug Validation Permits Invalid Empty Client Identifiers

Content
View full analysis

Vulnerability Details

File Location: scripts/identifier.py, lines 280-284
Vulnerability Type: T09: Insecure Skill Coding Practices
Risk Level: Low

Vulnerable Code

python
def slugify(s):
    if not s:
        return ""
    s = unicodedata.normalize("NFKD", s).encode("ascii", "ignore").decode()
    return re.sub(r"[^a-zA-Z0-9]+", "-", s).strip("-").lower()
python
slug = (find_client_by_iban(iban, clients)
        or match_side(holder, clients)[0]
        or slugify(holder))
extra = {"sources": ["bank-statement"]}

The resulting value is passed to the registry update without checking that it is non-empty:

python
_, is_new = upsert_client(clients, slug, holder, "auto-from-bank-statement", **extra)

Technical Analysis

A holder value can be non-empty while containing no ASCII letters or digits. Examples include punctuation-only values or Unicode characters that disappear during ASCII transliteration. In that case, slugify() returns an empty string.

The empty slug is subsequently accepted as a registry key and written into clients.json. Although the later if touched: check prevents backend directory seeding for an empty slug, the malformed registry entry itself is still persisted.

Attack Path

  1. A crafted or incorrectly analyzed bank statement supplies a non-empty holder composed only of unsupported characters.
  2. The holder passes the if not holder check.
  3. No existing client matches the holder or IBAN.
  4. slugify(holder) returns "".
  5. upsert_client() creates a client whose slug is an empty string.
  6. The malformed record is saved to clients.json and can interfere with later registry processing.

Impact Assessment

The issue affects local registry integrity rather than system privileges. It can introduce invalid client keys, create ambiguous records, and cause downstream components that assume non-empty slugs to fail or be ...[truncated 162 chars]

Remediation
View remediation

Remediation Suggestions

  • Reject empty slug results before calling upsert_client().
  • Enforce a strict slug schema such as ^[a-z0-9]+(?:-[a-z0-9]+)*$.
  • Require human review when normalization cannot produce a valid identifier.
  • Check for slug collisions and reserved names before persistence.
  • Validate all existing registry entries when loading clients.json.
  • Add tests for punctuation-only values, unsupported Unicode, whitespace, and normalization collisions.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/identifier.py:100
Finding

Registry Parsing Errors Can Cause Destructive Replacement with an Empty Registry

Content
View full analysis

Vulnerability Details

File Location: scripts/identifier.py, lines 100-104
Vulnerability Type: T09: Insecure Skill Coding Practices
Risk Level: High

Vulnerable Code

python
def load_json(path, default):
    try:
        return json.loads(Path(path).read_text(encoding="utf-8"))
    except Exception:
        return default

The fallback is used for the client registry and later persisted unconditionally:

python
clients = load_json(clients_path, [])
python
# Persiste toujours le registre (créé depuis zéro au 1er passage). Sème les
# fichiers backend (company.json + rapprochement.json vide) du client touché.
save_json(clients_path, clients)

Technical Analysis

load_json() catches every exception and converts it into the supplied default value. It does not distinguish an intentionally absent first-run registry from malformed JSON, unexpected data types, encoding errors, transient read failures, or other I/O errors.

When clients.json contains malformed JSON, the function silently returns []. The identification flow then always calls save_json(), replacing the malformed but potentially recoverable registry with the empty fallback or a partial registry containing only data added during the current run.

Atomic replacement prevents partial writes but does not prevent this logical data-loss condition.

Attack Path

  1. An attacker, concurrent process, or operational failure makes clients.json temporarily malformed.
  2. load_json() catches the parsing exception and returns an empty list.
  3. Identification continues as though no clients exist.
  4. Existing clients are not considered during document matching.
  5. save_json() atomically replaces the original registry with the empty or newly reconstructed list.
  6. Existing client identity data is permanently lost unless an external backup is available.

Impact Assessment

This issue can compromi ...[truncated 325 chars]

Remediation
View remediation

Remediation Suggestions

  • Treat only FileNotFoundError as an indication that a new registry should be created.
  • Propagate JSON parsing, encoding, permission, and other I/O errors instead of silently returning an empty registry.
  • Validate that the parsed root value is a list and that every client entry satisfies the expected schema.
  • Abort without writing if registry loading or validation fails.
  • Preserve a versioned backup before replacing an existing registry.
  • Use file locking or another concurrency-control mechanism when multiple processes may update the registry.
  • Add recovery tests for malformed JSON, wrong root types, interrupted writes, and concurrent updates.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/identifier.py:345
Finding

Unordered Invoice SIRENs Can Poison Recognized Client Records

Content
View full analysis

Vulnerability Details

File Location: scripts/identifier.py, lines 345-355
Vulnerability Type: T09: Insecure Skill Coding Practices
Risk Level: Medium

Vulnerable Code

python
if not client["needs_review"]:
    conf = "haute" if client["method"] == "raison-sociale-exacte" else "moyenne"
    if siren_client(siren, clients) == client["slug"]:
        conf = "haute"
    client["confidence"] = conf
    # Enrichir le SIREN du client — UNIQUEMENT le sien (vente = émetteur) ;
    # jamais celui de la contrepartie (sinon faux matchs futurs).
    if client["role"] == "out" and siren:
        own = norm_siren(siren[0])
        if own:
            upsert_client(clients, client["slug"],
                          client_name(clients, client["slug"]),
                          next((c["statut"] for c in clients if c["slug"] == client["slug"]), "confirmé"),
                          siren=[own])

Technical Analysis

The input field siren_found is an aggregate of identifiers found anywhere in an invoice and may contain both the seller's and buyer's identifiers. The code assumes that the first list element belongs to the recognized emitter and stores it as the client's SIREN whenever the invoice is classified as an outgoing invoice.

List order does not establish ownership. This also contradicts the documented design in SKILL.md, which states that aggregate invoice SIRENs must only reinforce confidence and must not be automatically reinjected into client records.

Once persisted, the incorrect SIREN becomes a strong exact-match signal in siren_client(), allowing a single misclassification to influence subsequent documents.

Attack Path

  1. An invoice is supplied where the counterparty SIREN appears before the client's SIREN in extraction order.
  2. The emitter is recognized as a client through an exact or fuzzy name match.
  3. The invoice is classified as an outgoing invoice.
  4. The ...[truncated 667 chars]
Remediation
View remediation

Remediation Suggestions

  • Never infer SIREN ownership from aggregate-list ordering.
  • Do not persist invoice-derived SIRENs unless upstream analysis provides side-specific fields such as emitter_siren and recipient_siren.
  • Require explicit human confirmation before enriching an existing client with an ambiguous identifier.
  • Use aggregate SIRENs only as non-persistent confidence evidence.
  • Validate side-specific SIRENs against the recognized legal name before persistence.
  • Add regression tests where buyer and seller SIRENs appear in different orders.
Vulnerability Patterns
  • 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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (3)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

Les consignes de sortie sont entièrement prescriptives en français (« Sans jargon », formulations exactes à employer, « pose-la telle quelle ») et ne mentionnent aucune possibilité d’adapter la langue à l’utilisateur. Cela constitue une contrainte de langue implicite non optionnelle, relevant d’une violation de politique de langue/locale.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

This code persists client data to clients.json on every run and may also create per-client company.json and rapprochement.json files, which can contain identifiers like SIREN, IBAN, and email addresses. Although the module docstring describes this behavior, there is no visible user-facing prompt, print, or log at the point of execution to warn that input processing will modify on-disk records and create files.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
88% confidence
Finding

The natural-language instructions, interface labels, and generated questions in this skill are entirely in French, indicating a fixed language choice. The file does not offer any language selection or document a justified locale restriction, so it may violate a language/locale policy requiring user choice or explicit justification.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.