ERP Claw

Security checks across malware telemetry and agentic risk

Overview

This ERP skill is mostly coherent with its stated purpose, but it needs review because it can run scheduled financial actions, execute downloaded modules, and perform host-level web dashboard deployment.

Install only if you are comfortable with a broad local ERP skill that can mutate business, payroll, inventory, and accounting records. Review and restrict module installation/update, setup-web-dashboard, deploy-module, schema rollback/apply, restore, cleanup, submit/cancel/approve, and scheduled recurring actions. Avoid using the dashboard setup on a production server unless you have reviewed the cloned web project and accept the nginx/systemd/TLS changes.

SkillSpector

By NVIDIA
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (58)

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
84% confidence
Finding
os.execvp(sys.executable, [sys.executable, script] + args)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
elif val is not False:
                cmd.extend([flag, str(val)])

        result = subprocess.run(
            cmd, capture_output=True, text=True, timeout=120
        )
Confidence
93% confidence
Finding
result = subprocess.run( cmd, capture_output=True, text=True, timeout=120 )

subprocess module call

Medium
Category
Dangerous Code Execution
Content
Uses subprocess.run with capture. Never raises — returns status.
    """
    try:
        result = subprocess.run(
            cmd,
            cwd=cwd,
            capture_output=True,
Confidence
89% confidence
Finding
result = subprocess.run( cmd, cwd=cwd, capture_output=True, text=True, timeout=timeout, )

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return {"passed": True, "detail": "No test files found — skipped"}

    try:
        result = subprocess.run(
            [sys.executable, "-m", "pytest", test_dir, "-q", "--tb=line", "-x", "--timeout=30"],
            capture_output=True,
            text=True,
Confidence
87% confidence
Finding
result = subprocess.run( [sys.executable, "-m", "pytest", test_dir, "-q", "--tb=line", "-x", "--timeout=30"], capture_output=True, text=True, ti

subprocess module call

Medium
Category
Dangerous Code Execution
Content
else:
            test_env["PYTHONPATH"] = erpclaw_lib

        proc = subprocess.run(
            [sys.executable, "-m", "pytest", test_dir, "-q", "--tb=short"],
            capture_output=True,
            text=True,
Confidence
96% confidence
Finding
proc = subprocess.run( [sys.executable, "-m", "pytest", test_dir, "-q", "--tb=short"], capture_output=True, text=True, timeout=timeout,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if init_db_path:
            # Module init_db.py typically takes db_path as first positional arg
            # or via --db-path flag. Try positional arg first (most common pattern).
            proc = subprocess.run(
                [sys.executable, init_db_path, db_path],
                capture_output=True,
                text=True,
Confidence
97% confidence
Finding
proc = subprocess.run( [sys.executable, init_db_path, db_path], capture_output=True, text=True, timeout=60,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
            if proc.returncode != 0:
                # Retry with --db-path flag
                proc = subprocess.run(
                    [sys.executable, init_db_path, "--db-path", db_path],
                    capture_output=True,
                    text=True,
Confidence
97% confidence
Finding
proc = subprocess.run( [sys.executable, init_db_path, "--db-path", db_path], capture_output=True, text=True,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
        if os.path.exists(gl_script):
            try:
                result = subprocess.run(
                    [sys.executable, gl_script,
                     "--action", "setup-chart-of-accounts",
                     "--company-id", company_id,
Confidence
72% confidence
Finding
result = subprocess.run( [sys.executable, gl_script, "--action", "setup-chart-of-accounts", "--company-id", company_id,

subprocess module call

Medium
Category
Dangerous Code Execution
Content
)
            if os.path.exists(erpclaw_script):
                try:
                    result = subprocess.run(
                        [sys.executable, erpclaw_script,
                         "--action", "seed-demo-data"],
                        capture_output=True, text=True, timeout=120
Confidence
72% confidence
Finding
result = subprocess.run( [sys.executable, erpclaw_script, "--action", "seed-demo-data"], capture_output=Tru

subprocess module call

Medium
Category
Dangerous Code Execution
Content
import tempfile
        tmp_dir = tempfile.mkdtemp(prefix=f"erpclaw-install-{module_name}-")
        try:
            result = subprocess.run(
                ["git", "clone", "--depth", "1", "--filter=blob:none",
                 "--sparse", clone_url, tmp_dir],
                capture_output=True, text=True, timeout=120
Confidence
95% confidence
Finding
result = subprocess.run( ["git", "clone", "--depth", "1", "--filter=blob:none", "--sparse", clone_url, tmp_dir], capture_output=True, text=

subprocess module call

Medium
Category
Dangerous Code Execution
Content
else:
        # Standalone repo — clone directly
        try:
            result = subprocess.run(
                ["git", "clone", "--depth", "1", clone_url, install_path],
                capture_output=True, text=True, timeout=120
            )
Confidence
95% confidence
Finding
result = subprocess.run( ["git", "clone", "--depth", "1", clone_url, install_path], capture_output=True, text=True, timeout=120 )

subprocess module call

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

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Pull latest
        try:
            result = subprocess.run(
                ["git", "pull", "origin", "main"],
                cwd=install_path, capture_output=True, text=True, timeout=60
            )
Confidence
94% confidence
Finding
result = subprocess.run( ["git", "pull", "origin", "main"], cwd=install_path, capture_output=True, text=True, timeout=60 )

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 )

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The UI exposes import actions that accept an absolute server-side CSV path, which gives users a way to reference arbitrary files on the host rather than uploading vetted content through the client. In an ERP context with broad administrative capabilities, this can become an arbitrary file read or unintended file-processing primitive if the backend trusts the supplied path, potentially exposing sensitive data or triggering unsafe parser behavior on server-resident files.

Context-Inappropriate Capability

Medium
Confidence
90% confidence
Finding
The supplier import flow similarly accepts a CSV path instead of a user-uploaded file handle, expanding the attack surface to the server filesystem. If the backend resolves that path directly, an attacker with UI access could probe or ingest sensitive local files and abuse import processing beyond normal supplier administration.

Context-Inappropriate Capability

Medium
Confidence
82% confidence
Finding
This billing skill delegates invoice creation to another skill via a dynamically resolved external script, crossing a trust boundary. If the resolved script path or installed skill is replaced, tampered with, or behaves unexpectedly, billing data can be sent to untrusted code and financial state changes can occur based on an unsafe dependency chain.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
This buying skill exposes actions that directly change company-wide control settings such as receipt tolerance and three-way-match policy. Those settings materially affect procurement validation and invoice controls, so allowing them to be changed from an operational document-processing surface weakens separation of duties and can be abused to relax safeguards before over-receipting or over-invoicing transactions.

Context-Inappropriate Capability

Medium
Confidence
91% confidence
Finding
The HR module is able to approve expense claims and directly create general-ledger entries, crossing from HR workflow into accounting state mutation. In an agent-driven environment, this expands blast radius: any misuse, prompt-injection-driven action selection, or authorization gap in HR operations can result in financial posting, not just HR record updates.

Context-Inappropriate Capability

Low
Confidence
88% confidence
Finding
The cross-skill status mutation endpoint allows direct modification of expense-claim status, including marking claims as paid, based only on supplied identifiers and status values. Without strong caller authentication and transition validation, this creates an integrity risk where another component or compromised agent path can alter financial workflow state outside the normal approval/payment process.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The function's docstring states it performs a soft delete, but the implementation executes a hard DELETE against recurring_journal_template. In an ERP/accounting context, this mismatch is dangerous because operators and calling agents may rely on recoverability and audit retention for financial workflow artifacts, leading to irreversible loss of scheduling configuration and weakened auditability.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
The file is framed as an installation checker/onboarding guide, but it also clones a repository, builds software, configures nginx/systemd, and optionally provisions TLS. That mismatch increases the chance that users or higher-level agents invoke it expecting read-only diagnostics while actually triggering system changes and network access.

Context-Inappropriate Capability

High
Confidence
96% confidence
Finding
The web dashboard setup path performs host-level deployment using git, npm, bash, nginx, systemctl, certbot, and sudo-mediated operations. In the context of an ERP meta-package, this is especially dangerous because it expands the attack surface from application logic to full server provisioning, allowing persistent system modification and service reconfiguration if the action is misused or the cloned content is compromised.

Intent-Code Divergence

Medium
Confidence
98% confidence
Finding
The sandbox evaluation path does not actually execute variants in an isolated environment; it fabricates deterministic metrics from an MD5 hash and hard-codes all tests as passing. This can cause unreviewed or unsafe code variants to be scored as valid improvements and promoted into the improvement pipeline based on false security and correctness signals.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The file-level guarantee says the AI will never ALTER/DROP tables owned by other modules, but rollback_migration drops tables solely by parsing the stored DDL from the migration record and performs no ownership validation before executing DROP TABLE. If a migration record is incorrect, stale, or created under weaker controls, rollback can destructively remove tables without enforcing the stated module isolation boundary.

VirusTotal

64/64 vendors flagged this skill as clean.

View on VirusTotal