Back to skill

Security audit

Erpclaw Publish 4.12.2 20260706 121254

Security checks across malware telemetry and agentic risk

Overview

ERPClaw is a coherent local ERP skill, but it grants broad financial and code-execution authority with several under-scoped trust boundaries users should review before installing.

Install only if you are comfortable giving this skill authority over local business books, payroll data, credentials, backups, and GitHub-sourced ERP modules. Use it in a dedicated account or sandbox first, keep backups, review every module before approving installation, avoid absolute-path CSV imports from untrusted prompts, and do not rely on RBAC as a hard security boundary unless you have verified user context and permissions are enforced in your runtime.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (55)

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
args[i + 1] = action_override
                break

    os.execvp(sys.executable, [sys.executable, script] + args)


def _suggest_module_for_action(action):
Confidence
79% confidence
Finding
os.execvp(sys.executable, [sys.executable, script] + args)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"rate": bp["grand_total"],
                    }]),
                ]
                proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
                if proc.returncode == 0:
                    inv_result = json.loads(proc.stdout)
                    if inv_result.get("status") == "ok":
Confidence
85% confidence
Finding
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=30)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env["ERPCLAW_DB_PATH"] = db_path

    try:
        proc = subprocess.run(
            [sys.executable, "-m", "pytest", tests_dir, "-v", "--tb=short", "-q"],
            capture_output=True,
            text=True,
Confidence
88% confidence
Finding
proc = subprocess.run( [sys.executable, "-m", "pytest", tests_dir, "-v", "--tb=short", "-q"], capture_output=True, text=True, timeout=120,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
env = os.environ.copy()
            lib_path = lib_dir()
            env["PYTHONPATH"] = lib_path + os.pathsep + env.get("PYTHONPATH", "")
            result = subprocess.run(
                [sys.executable, init_db_path],
                capture_output=True, text=True, timeout=60,
                env=env,
Confidence
99% confidence
Finding
result = subprocess.run( [sys.executable, init_db_path], capture_output=True, text=True, timeout=60, env=env, )

subprocess module call

Medium
Category
Dangerous Code Execution
Content
init_db_path = os.path.join(install_path, "init_db.py")
        if os.path.isfile(init_db_path):
            try:
                subprocess.run(
                    [sys.executable, init_db_path],
                    capture_output=True, text=True, timeout=60
                )
Confidence
99% confidence
Finding
subprocess.run( [sys.executable, init_db_path], capture_output=True, text=True, timeout=60 )

Tainted flow: 'req' from os.environ.get (line 162, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Content
base = os.environ.get("ERPCLAW_GITHUB_RAW_BASE", GITHUB_RAW_BASE)
    url = f"{base}/{rel_path}"
    req = urllib.request.Request(url, headers={"User-Agent": "erpclaw"})
    with urllib.request.urlopen(req, timeout=15) as resp:
        data = resp.read()
    actual = hashlib.sha256(data).hexdigest()
    if actual != expected_hash:
Confidence
86% confidence
Finding
with urllib.request.urlopen(req, timeout=15) as resp:

Lp3

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding
The skill declares broad executable behavior including shell access, file reads/writes, environment-variable use, and network access, but does not expose these as explicit permissions in the skill manifest. In a user-invocable ERP skill that can initialize databases, install modules from GitHub, and modify local files, this creates a transparency and consent gap: users and hosting platforms may underestimate the true authority granted to the skill.

Description-Behavior Mismatch

Medium
Confidence
85% confidence
Finding
The README makes a strong privacy claim that data never leaves the user's machine, yet elsewhere documents network-dependent behavior such as on-demand GitHub module fetching and live third-party integrations. Even if accounting data is intended to stay local, this wording can mislead users about outbound connections, metadata leakage, and the trust boundary of installed extensions.

Context-Inappropriate Capability

Medium
Confidence
98% confidence
Finding
The UI exposes import actions that accept an absolute server-side file path (`csv_path`) for ERP data import. In an agent-driven environment, this creates a dangerous server file read primitive because a user or prompted agent may supply arbitrary local paths, potentially causing sensitive server files to be accessed or parsed outside the intended business workflow.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
Billing code delegates invoice creation to an external skill script, creating a cross-skill execution boundary with weak guarantees about what code will run. In an ERP context, this is more dangerous because the action is triggered from a high-trust financial workflow and may execute attacker-modified or unreviewed module code.

Context-Inappropriate Capability

Low
Confidence
78% confidence
Finding
The cross-skill update_expense_claim_status endpoint permits arbitrary status transitions based only on supplied IDs and a validated status enum, with no workflow validation or authorization checks in this file. If another skill, agent, or caller can reach this action, they could mark claims as paid, cancelled, or otherwise mutate financial workflow state out of sequence, undermining process integrity and audit trust.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
Completing a pick list automatically triggers delivery-note creation in another skill as a side effect, crossing module boundaries and mutating business state beyond the immediate action. That makes the workflow harder to reason about and can allow callers who only expected inventory completion to also advance fulfillment in the selling domain without a separate explicit approval step.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The function's docstring promises a soft delete, but the implementation performs a hard delete of the recurring_journal_template row. In an ERP/accounting context, this can destroy configuration and audit-relevant scheduling history, causing integrity, compliance, and recoverability issues if an operator or calling agent relies on the documented behavior.

Intent-Code Divergence

Medium
Confidence
99% confidence
Finding
list_employee_bank_accounts decrypts stored routing numbers and returns them directly in API output, while only masking the account number. Routing numbers are financial-sensitive metadata and exposing them in plaintext broadens insider misuse, log leakage, and downstream data exfiltration risk; in payroll context this materially increases the chance of bank-detail abuse when combined with employee identity data.

Description-Behavior Mismatch

High
Confidence
99% confidence
Finding
The module is explicitly presented as read-only financial reporting, but it exposes write-capable actions such as add-elimination-rule and run-elimination that insert into elimination_rule, gl_entry, and elimination_entry and then commit. In an agent skill context, misleading read-only labeling is dangerous because higher-level tooling or users may grant broader trust and invoke the file assuming no state changes, leading to unauthorized accounting changes.

Intent-Code Divergence

High
Confidence
99% confidence
Finding
The top-level docstring says the file is read-only and owns no tables, yet the implementation performs INSERTs and conn.commit() in elimination-related actions. This trust-boundary mismatch can cause agents, reviewers, or policy controls to treat the script as safe for unrestricted reporting use when it can actually mutate ledger data.

Intent-Code Divergence

Medium
Confidence
80% confidence
Finding
The run_elimination documentation claims each elimination creates a balanced pair of GL entries, but the implementation writes one debit in one company and one credit in another without demonstrating that each company's journal remains internally balanced. In accounting systems, this can create inconsistent or invalid ledgers and mislead operators into trusting the action's financial correctness.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
This code sends customer email addresses and template identifiers from the selling module to a separate skill via subprocess, creating a cross-skill data-sharing boundary that is easy to miss from the caller’s perspective. If the target skill or script resolution is compromised, customer contact data could be exfiltrated or processed by an unintended component without an explicit consent or policy check at the transmission point.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The module documentation explicitly claims cross-skill calls validate the target via check_subprocess_target(), but call_skill_action() never performs that check before executing the resolved script. In a plugin/module ecosystem where skills can be installed from GitHub, this gap can allow execution of an unexpected or untrusted script path, turning cross-skill API calls into a code-execution pivot.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The module is presented as the shared RBAC utility for an ERP system, yet its documented and implemented behavior explicitly fails open when no ERP users exist and also permits access in missing-user-context paths. In an ERP handling accounting, payroll, inventory, and financial reporting, a fail-open authorization layer can allow unauthorized execution of sensitive actions during bootstrap, misconfiguration, or any code path that omits identity propagation.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
`resolve_tax_template()` documents and returns item overrides in the form `{item_id, tax_template_id}`, but `calculate_tax_detailed()` looks for `{item_id, override_template_id}`. As a result, valid override data can be silently ignored, causing tax to be computed with the wrong template for some items. In an ERP/tax engine context, this can lead to under- or over-collection of tax, incorrect invoices, and downstream compliance/reporting errors.

Intent-Code Divergence

Medium
Confidence
82% confidence
Finding
Function.get_sql interpolates self.name directly into generated SQL, and the nearby FIXME acknowledges missing escaping. If untrusted input can reach CustomFunction or Function names, an attacker may be able to inject arbitrary SQL fragments as function identifiers, bypassing value parameterization and altering query structure.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The manager rewrites a deployed SKILL.md outside the module install directory using content derived from installed modules, including module descriptions read from their SKILL.md files. Because that content is not sanitized and is published into agent-discovered metadata, a malicious module can persistently influence prompts, documentation, or downstream operator behavior outside its own sandbox.

Context-Inappropriate Capability

Medium
Confidence
94% confidence
Finding
Installed modules are copied into the global OpenClaw workspace skills directory, expanding their reach from local module storage into agent-discoverable execution scope. In this context, that means third-party module code and prompts become globally available to the agent, increasing the blast radius of any malicious module beyond ERPClaw's internal management path.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The README promotes plain-English execution of high-risk financial operations such as invoicing, payment recording, payroll, and period close without any mention of confirmation, review, rollback limits, or approval controls. In an ERP/accounting context, ambiguous natural-language actions can cause irreversible financial postings, compliance issues, or unauthorized business changes if users overtrust the interface.

VirusTotal

64/64 vendors flagged this skill as clean.

View on VirusTotal

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
mcp/server.py:52

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/erpclaw-setup/db_query.py:2584

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/erpclaw-setup/migration_runner.py:146

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/module_manager.py:1117