Back to skill

Security audit

Graph DB Toolkit

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent Neo4j graph toolkit, but it exposes powerful database write and full-delete capabilities without enough guardrails.

Install only for databases you intend to let the agent read and modify. Use a dedicated least-privileged Neo4j account, change the example password, avoid production data until delete_all() is removed or guarded, and treat raw Cypher/query-builder inputs as trusted-code interfaces rather than user-provided text.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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 (4)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/neo4j_client.py:19
Finding

Cypher Injection Through Unvalidated Neo4j Identifiers

Content
View full analysis

Vulnerability Details

File Location: scripts/neo4j_client.py:19-58
Vulnerability Type: Cypher injection through direct interpolation of labels, property keys, and relationship types
Risk Level: High

Vulnerable Code

python
def create_node(self, label: str, properties: Dict[str, Any]) -> str:
    cypher = f"CREATE (n:{label} $props) RETURN id(n) as node_id"
    with self.driver.session() as session:
        result = session.run(cypher, props=properties)
        record = result.single()
        return record["node_id"] if record else None

def create_relationship(self, from_name: str, from_label: str, rel_type: str,
                      to_name: str, to_label: str, properties: Optional[Dict] = None,
                      match_key: str = "name") -> bool:
    props = properties or {}
    cypher = f"""
        MATCH (a:{from_label} {{{match_key}: $from_name}})
        MATCH (b:{to_label} {{{match_key}: $to_name}})
        CREATE (a)-[r:{rel_type} $props]->(b)
        RETURN r
    """
    with self.driver.session() as session:
        result = session.run(cypher, from_name=from_name, to_name=to_name, props=props)
        return result.single() is not None

def get_node(self, label: str, key: str, value: Any) -> Optional[Dict]:
    cypher = f"MATCH (n:{label} {{{key}: $value}}) RETURN n"
    results = self.query(cypher, {"value": value})
    return results[0] if results else None

def update_node(self, label: str, key: str, value: Any, new_props: Dict[str, Any]) -> bool:
    cypher = f"MATCH (n:{label} {{{key}: $value}}) SET n += $new_props RETURN n"
    results = self.query(cypher, {"value": value, "new_props": new_props})
    return len(results) > 0

Technical Analysis

Property values are correctly supplied as Neo4j parameters, but structural query elements—including node labels, relationship types, and property keys—are inserted directly into Cypher strings.

...[truncated 1761 chars]

Remediation
View remediation

Remediation Suggestions

  • Validate every label, relationship type, property key, and variable identifier with a restrictive rule such as ^[A-Za-z_][A-Za-z0-9_]*$.
  • Prefer application-controlled allowlists for expected labels, relationship types, and keys.
  • Reject identifiers containing whitespace, quotes, backticks, delimiters, comments, or other Cypher syntax.
  • Where dynamic identifiers are unavoidable, use the Neo4j driver's supported safe identifier-quoting strategy after validation.
  • Keep all property values parameterized.
  • Clearly document query() as a trusted-input-only interface and do not pass HTTP parameters, model output, or other untrusted strings directly to it.
  • Use a least-privileged Neo4j account that cannot perform schema administration or unrestricted deletion.
  • Add adversarial unit tests for every structural argument.

T09 · Insecure Skill Coding Practices

Error
Location
scripts/cypher_builder.py:13
Finding

Cypher Injection Through Raw Query-Builder Components

Content
View full analysis

Vulnerability Details

File Location: scripts/cypher_builder.py:13-73
Vulnerability Type: Unsafe dynamic Cypher query construction
Risk Level: High

Vulnerable Code

python
def match_node(self, var: str, label: Optional[str] = None, props: Optional[Dict] = None):
    label_str = f":{label}" if label else ""
    if props:
        self._parameters.update({f"{var}_{k}": v for k, v in props.items()})
        prop_str = "{" + ", ".join([f"{k}: ${var}_{k}" for k in props]) + "}"
        self._match.append(f"({var}{label_str} {prop_str})")
    else:
        self._match.append(f"({var}{label_str})")
    return self

def match_path(self, start: str, rel: str, end: str, rel_props: Optional[Dict] = None):
    if rel_props:
        rp = "{" + ", ".join([f"{k}: ${rel}_{k}" for k in rel_props]) + "}"
        self._parameters.update({f"{rel}_{k}": v for k, v in rel_props.items()})
        self._match.append(f"({start})-[:{rel} {rp}]->({end})")
    else:
        self._match.append(f"({start})-[:{rel}]->({end})")
    return self

def where(self, condition: str):
    self._where.append(condition)
    return self

def returns(self, *fields: str):
    self._return.extend(fields)
    return self

def order_by(self, field: str, desc: bool = False):
    self._order.append(f"{field} DESC" if desc else field)
    return self

def limit(self, n: int):
    self._limit = n
    return self

@staticmethod
def shortest_path(start_label: str, start_key: str, start_val: Any,
                  end_label: str, end_key: str, end_val: Any, max_hops: int = 4) -> str:
    return f"""
        MATCH path = shortestPath(
            (a:{start_label} {{{start_key}: $start_val}})-[*..{max_hops}]-(b:{end_label} {{{end_key}: $end_val}})
        )
        RETURN path
    """

Technical Analysis

Although property values are parameterized, the builder directly interpolates variable names, ...[truncated 1430 chars]

Remediation
View remediation

Remediation Suggestions

  • Replace raw string interfaces with structured methods for predicates, projections, and ordering.
  • Validate all variable names, labels, keys, and relationship types against an identifier allowlist.
  • Restrict return and ordering fields to a caller-independent list of known schema fields.
  • Do not expose where(condition) to untrusted callers; construct predicates from validated operators and parameterized values.
  • Enforce type(n) is int for limits and path bounds, then apply safe minimum and maximum values.
  • Prevent parameter-name collisions when multiple builder operations use the same variable and key.
  • Add security tests containing quote characters, backticks, comments, delimiters, clause keywords, negative limits, and excessive traversal bounds.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/neo4j_client.py:12
Finding

Predictable Default Neo4j Password

Content
View full analysis

Vulnerability Details

File Location: scripts/neo4j_client.py:12-15
Vulnerability Type: Hardcoded default credential
Risk Level: Medium

Vulnerable Code

python
class Neo4jClient:
    def __init__(self, uri: str = "bolt://localhost:7687", user: str = "neo4j", password: str = "password"):
        if GraphDatabase is None:
            raise ImportError("neo4j not installed. Run: pip install neo4j")
        self.driver = GraphDatabase.driver(uri, auth=(user, password))

The same credential is presented in SKILL.md:15 and README.md:24:

python
client = Neo4jClient(uri="bolt://localhost:7687", user="neo4j", password="password")

Technical Analysis

The client silently falls back to the widely guessable credential neo4j/password when callers omit authentication arguments. The documentation reinforces this value as a copy-and-paste configuration rather than using an explicitly nonfunctional placeholder.

The repository does not establish that any deployed database actually uses this credential, so this is not evidence of a leaked production secret. The risk is that users may retain or adopt the predictable default.

Attack Path

  1. A user instantiates Neo4jClient() without supplying a password or copies the documented example unchanged.
  2. The associated Neo4j deployment is configured to accept neo4j/password.
  3. An attacker reaches the Bolt endpoint and attempts the documented credential.
  4. If authentication succeeds, the attacker receives all database privileges assigned to the neo4j account.

Impact Assessment

Impact depends on database exposure and the privileges of the account. A successful login may provide read and write access to graph data and, if the account is administrative, schema management or broader database administration. The issue does not independently expose a database that is unreachable from the attacker.

Remediation
View remediation

Remediation Suggestions

  • Remove the default password and require callers to provide authentication explicitly.
  • Fail closed with a clear error if no password or credential provider is configured.
  • Load credentials from a secret manager, protected environment variable, or injected runtime configuration.
  • Replace documentation credentials with an unmistakable placeholder such as password=os.environ["NEO4J_PASSWORD"].
  • Require unique, high-entropy credentials and rotate any deployment that adopted the example password.
  • Use a dedicated least-privileged application account instead of a broadly privileged default account.
  • Prefer encrypted Neo4j transport where connections cross an untrusted network.

T08 · Insecure Dependencies

Note
Location
requirements.txt:1
Finding

Non-Reproducible Dependency Resolution

Content
View full analysis

Vulnerability Details

File Location: requirements.txt:1-3
Vulnerability Type: Unbounded and unhashed third-party dependencies
Risk Level: Low

Vulnerable Code

text
neo4j>=5.15.0
networkx>=3.2.0
numpy>=1.24.0

Technical Analysis

Every dependency has only a lower version bound. A future installation can therefore resolve to releases that did not exist or were not reviewed when this project was audited. No lockfile or package hashes are supplied to ensure reproducible artifacts.

The package names and package source shown in the repository are not suspicious, and the audit found no evidence of typosquatting, dependency confusion, or a known malicious package. The confirmed issue is the absence of reproducible dependency controls.

Attack Path

  1. A user executes pip install -r requirements.txt.
  2. The resolver selects the newest available versions satisfying the lower bounds.
  3. A later compromised, vulnerable, or behaviorally incompatible release may be selected.
  4. Its installation or runtime code executes in the environment where the toolkit is installed.

This is a supply-chain exposure rather than evidence that the current named dependencies are malicious.

Impact Assessment

Impact depends on the permissions of the installation or runtime environment and on the behavior of the selected package release. A compromised dependency could potentially access application data, credentials, network resources, or files available to the Python process. Ordinary incompatible updates may instead cause availability or correctness failures.

Remediation
View remediation

Remediation Suggestions

  • Pin dependencies to reviewed versions or tightly controlled compatible ranges.
  • Generate and commit a lockfile for the supported environment.
  • Record and enforce distribution hashes, for example with pip --require-hashes.
  • Install packages only from an approved package index.
  • Use automated dependency vulnerability and update monitoring.
  • Review and test dependency upgrades before refreshing pins and hashes.
  • Install dependencies in an isolated, least-privileged virtual environment.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (10)

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This builder directly interpolates non-parameterizable Cypher fragments such as labels, relationship types, variable names, WHERE clauses, RETURN fields, ORDER BY expressions, and shortest-path identifiers into the query string. If any of these values are influenced by untrusted input, an attacker can alter query structure, bypass intended constraints, exfiltrate additional graph data, or potentially invoke unsafe Cypher procedures depending on the Neo4j environment.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The client exposes an unrestricted helper that executes MATCH (n) DETACH DELETE n, which wipes the entire Neo4j database without any authorization guard, environment restriction, or safety interlock. In an agent/tooling context, this is dangerous because a mistaken invocation, prompt injection, or untrusted workflow could trigger irreversible destructive data loss across all nodes and relationships.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The destructive operation has no user-facing confirmation, dry-run mode, or contextual warning before deleting all graph data. In skill-based automation, lack of confirmation materially increases the chance that accidental calls or adversarial instruction chains result in complete database destruction.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
71% confidence
Finding

The documentation consistently presents section headers and feature descriptions in Chinese and English, but does not explain any language or locale preference. If organizational policy requires avoiding imposed language choices without opt-in, this could be a natural-language policy issue.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
83% confidence
Finding

This markdown file includes Quick Start examples that create a node and relationship, which are write operations affecting user data in the database. The description does not provide any warning or note that these commands modify the target Neo4j instance.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

This markdown file includes delete operations, including DETACH DELETE, which can permanently remove nodes and relationships. The cheatsheet provides the commands directly but does not include any warning that these examples are destructive or should be used carefully on real data.

Content

No source excerpt is available for this finding.

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

The dependency is specified with a minimum version only, which allows newer unreviewed versions to be installed over time. This weakens build reproducibility and can expose consumers to accidental adoption of vulnerable or breaking upstream releases, though it is a supply-chain hygiene issue rather than an immediate exploitable flaw in this file alone.

Content

Scanner excerpt · requirements.txt (reported line 1)May include surrounding context.

text
neo4j>=5.15.0
networkx>=3.2.0
numpy>=1.24.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
95% confidence
Finding

Using a non-pinned version for networkx permits environment-dependent installs and future drift to versions that have not been tested or security-reviewed. In a graph analytics toolkit, this increases supply-chain risk and reproducibility issues, but the manifest itself does not indicate active malicious behavior.

Content

Scanner excerpt · requirements.txt (reported line 2)May include surrounding context.

text
neo4j>=5.15.0
networkx>=3.2.0
numpy>=1.24.0

Unpinned Dependencies

Low
Category
Supply Chain
Confidence
98% confidence
Finding

The numpy requirement is not pinned, so installations may resolve to different versions across environments, including versions with known vulnerabilities. Because numpy has a history of advisories, the lack of pinning makes it impossible to verify whether deployed environments avoid affected releases.

Content

Scanner excerpt · requirements.txt (reported line 3)May include surrounding context.

text
neo4j>=5.15.0
networkx>=3.2.0
numpy>=1.24.0

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding

The manifest references numpy without an exact version, and numpy has multiple historical advisories. Without a pinned version, consumers cannot determine from this file alone whether installation will select a safe release, creating unverifiable supply-chain exposure rather than proving a currently vulnerable install.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.