Back to skill

Security audit

Aister vector-memory

Security checks for vulnerabilities and agentic risk

Overview

This skill is not clearly malicious, but it needs review because it indexes private memory files and can send memory or search text to a configurable embedding server without restricting it to local use.

Install only if you are comfortable indexing these memory files into PostgreSQL. Keep EMBEDDING_SERVICE_URL on 127.0.0.1 unless you explicitly trust a remote embedding service, use a dedicated database user and isolated environment, review the scripts, and avoid optional autostart until you have confirmed how to stop and update the service.

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

T09 · Insecure Skill Coding Practices

Error
Location
memory_reindex.py:20
Finding

Private Memory Can Be Transmitted to an Arbitrary Unencrypted Embedding Endpoint

Content
View full analysis

Vulnerability Details

File Location: memory_reindex.py:20-34, 119-126
Vulnerability Type: Unrestricted transmission of sensitive memory content
Risk Level: High

Vulnerable Code

python
EMBEDDING_SERVICE_URL = os.environ.get("EMBEDDING_SERVICE_URL", "http://127.0.0.1:8765")
python
def get_embeddings(texts: list) -> Optional[list]:
    """Get embeddings from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed",
            json={"texts": texts, "prefix": "passage: "},
            timeout=120
        )
        response.raise_for_status()
        return response.json()["embeddings"]
    except Exception as e:
        print(f"Error getting embeddings: {e}", file=sys.stderr)
        return None
python
with open(file_path, "r", encoding="utf-8") as f:
    content = f.read()

chunks = chunk_text(content)
if not chunks:
    continue

print(f"  Generating embeddings for {len(chunks)} chunks...")

embeddings = get_embeddings(chunks)

Technical Analysis

The reindexer reads plaintext content from MEMORY.md, IDENTITY.md, and USER.md, divides that content into chunks, and sends every chunk to the URL supplied through EMBEDDING_SERVICE_URL.

Generating embeddings is necessary for the declared semantic-search functionality, and the default endpoint is the loopback address http://127.0.0.1:8765. However, the code does not enforce loopback operation, validate the destination host, require TLS for remote destinations, authenticate the embedding server, or ask for explicit consent before transmitting memory content remotely.

Consequently, anyone who can modify the process environment or the sourced configuration file can redirect all indexed memory to an arbitrary server. If a non-loopback HTTP endpoint is selected, the data is also exposed to network interception and modification.

This exceeds the minimum privileges required for the documented local e ...[truncated 1419 chars]

Remediation
View remediation

Remediation Suggestions

  1. Enforce loopback-only embedding endpoints by default. Resolve the configured hostname and reject destinations that do not map exclusively to 127.0.0.0/8 or ::1.
  2. Prefer a Unix-domain socket for communication with the local embedding service.
  3. If remote embedding is intentionally supported, require a separate explicit opt-in setting such as ALLOW_REMOTE_EMBEDDING=true.
  4. Require HTTPS for every non-loopback endpoint and enable normal certificate verification.
  5. Authenticate requests using a narrowly scoped token or mutual TLS.
  6. Display a clear warning identifying the destination before private memory is transmitted remotely.
  7. Add optional secret scanning and redaction before embedding content.
  8. Limit memory-file access to explicitly approved, resolved paths and reject symbolic links where appropriate.
  9. Document that remote embedding transmits plaintext source material, not merely derived vectors.

T09 · Insecure Skill Coding Practices

Warning
Location
memory_search.py:18
Finding

Search Queries Can Be Redirected to an Arbitrary Unencrypted Embedding Endpoint

Content
View full analysis

Vulnerability Details

File Location: memory_search.py:18-34
Vulnerability Type: Unrestricted transmission of potentially sensitive search queries
Risk Level: Medium

Vulnerable Code

python
EMBEDDING_SERVICE_URL = os.environ.get("EMBEDDING_SERVICE_URL", "http://127.0.0.1:8765")
DEFAULT_THRESHOLD = float(os.environ.get("VECTOR_MEMORY_THRESHOLD", "0.5"))
DEFAULT_LIMIT = int(os.environ.get("VECTOR_MEMORY_LIMIT", "5"))


def get_embedding(text: str) -> Optional[list]:
    """Get embedding from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed_query",
            json={"query": text},
            timeout=30
        )
        response.raise_for_status()
        return response.json()["embedding"]
    except Exception as e:
        print(f"Error getting embedding: {e}", file=sys.stderr)
        return None

Technical Analysis

Every semantic-search query is sent in plaintext JSON to the configured embedding endpoint. Although the default endpoint is local, EMBEDDING_SERVICE_URL can identify any HTTP server, and the code imposes no destination, transport-security, or authentication restrictions.

Memory-search queries can themselves reveal confidential subjects, names, credentials, medical or financial topics, operational intentions, or the categories of information stored in the user's memory. The configurable request is necessary only to reach an embedding service; unrestricted remote HTTP access is not necessary for the declared local architecture.

The retrieved database results are not sent to the embedding endpoint by this function. The exposed information is the original query text.

Attack Path

  1. An attacker modifies the configuration or execution environment used by memory_search.py.
  2. The attacker sets EMBEDDING_SERVICE_URL to an attacker-controlled HTTP service.
  3. The user or Agent performs a semantic-memory search.
  4. The complete query is ...[truncated 791 chars]
Remediation
View remediation

Remediation Suggestions

  1. Restrict the default embedding service to loopback or a Unix-domain socket.
  2. Reject non-loopback HTTP destinations unless remote operation has been explicitly enabled.
  3. Require HTTPS with certificate verification for remote services.
  4. Authenticate the embedding service and apply strict connection and response-size limits.
  5. Warn users that remote embedding exposes complete query text.
  6. Consider running the embedding function in-process to eliminate the query transmission boundary.
  7. Validate that the returned embedding has the expected dimension, contains only finite numeric values, and does not exceed expected response sizes.

T08 · Insecure Dependencies

Warning
Location
INSTALL.md:72
Finding

Installation Uses Unpinned Python Dependencies, a Mutable Container Tag, and an Unpinned Model Revision

Content
View full analysis

Vulnerability Details

File Location: INSTALL.md:25-34, 72-78; embedding_service.py:17-20
Vulnerability Type: Unpinned supply-chain components
Risk Level: Medium

Vulnerable Code

yaml
services:
  postgres:
    image: pgvector/pgvector:pg16
    container_name: vector-memory-db
    environment:
      POSTGRES_USER: aister
      POSTGRES_PASSWORD: YOUR_SECURE_PASSWORD
      POSTGRES_DB: vector_memory
bash
# Create Python venv
python3 -m venv ~/.openclaw/workspace/vector_memory_venv

# Activate venv
source ~/.openclaw/workspace/vector_memory_venv/bin/activate

# Install dependencies
pip install flask psycopg2-binary sentence-transformers numpy requests
python
MODEL_NAME = os.environ.get("EMBEDDING_MODEL", "intfloat/e5-large-v2")
EMBEDDING_PORT = int(os.environ.get("EMBEDDING_PORT", "8765"))

logger.info(f"Loading model: {MODEL_NAME}")
logger.info("NOTE: First run will download the model from HuggingFace (~1.3GB)")

model = SentenceTransformer(MODEL_NAME)

Technical Analysis

The installation instructions do not pin Python package versions or verify package hashes. As a result, two users following the same reviewed instructions at different times may install materially different code and transitive dependencies.

The Docker image uses the mutable pgvector/pgvector:pg16 tag rather than a content digest. The model is also referenced by repository name without a reviewed immutable revision. These mutable references make the effective runtime dependent on upstream state at installation or first execution rather than solely on the reviewed project contents.

There is no evidence that the named dependencies are currently malicious. The vulnerability is the absence of reproducibility and integrity controls, which expands the supply-chain attack surface.

Attack Path

  1. An upstream package release, transitive dependency, container tag, or model repository is compromised or changed after this Skill is review ...[truncated 1184 chars]
Remediation
View remediation

Remediation Suggestions

  1. Add a lockfile containing exact direct and transitive Python dependency versions.
  2. Install with hash verification, for example through a generated requirements file using --require-hashes.
  3. Pin the PostgreSQL/pgvector container image by immutable SHA-256 digest.
  4. Pin the Hugging Face model to a reviewed commit revision rather than relying only on the repository name.
  5. Record and verify expected artifact checksums where supported.
  6. Use trusted package indexes explicitly and disable unexpected extra indexes to reduce dependency-confusion risk.
  7. Add automated dependency and container vulnerability scanning.
  8. Test dependency updates in isolation and update pinned versions only after review.
  9. Run the embedding service and database under dedicated, minimally privileged accounts or containers with restricted filesystem and network access.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (19)

Tainted flow: 'EMBEDDING_SERVICE_URL' from os.environ.get (line 20, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The code sends memory file contents to an embedding endpoint whose base URL is taken directly from an environment variable, with no allowlist, scheme enforcement, or verification that it is a trusted local service. This can exfiltrate sensitive contents from MEMORY.md, IDENTITY.md, and USER.md to an attacker-controlled host if the environment is manipulated or misconfigured.

Content

Scanner excerpt · memory_reindex.py (reported line 31)May include surrounding context.

python
def get_embeddings(texts: list) -> Optional[list]:
    """Get embeddings from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed",
            json={"texts": texts, "prefix": "passage: "},
            timeout=120

Tainted flow: 'EMBEDDING_SERVICE_URL' from os.environ.get (line 18, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Confidence
94% confidence
Finding

The script sends user-supplied query text to a URL fully controlled by the EMBEDDING_SERVICE_URL environment variable, with no validation or allowlisting. In an agent setting, a poisoned environment can redirect sensitive prompts or memory-search queries to an attacker-controlled service, causing silent exfiltration of potentially confidential data.

Content

Scanner excerpt · memory_search.py (reported line 26)May include surrounding context.

python
def get_embedding(text: str) -> Optional[list]:
    """Get embedding from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed_query",
            json={"query": text},
            timeout=30

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · INSTALL.md (reported line 26)May include surrounding context.

If you prefer isolation, use Docker for PostgreSQL:

bash
# Create Docker setup
mkdir -p ~/.openclaw/workspace/vector-memory-docker
cd ~/.openclaw/workspace/vector-memory-docker

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · INSTALL.md (reported line 97)May include surrounding context.

md
# If pgvector is not installed, install it
# For Debian/Ubuntu:
sudo apt-get install postgresql-16-pgvector

# For Fedora/RHEL:
sudo dnf install postgresql-16-pgvector

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · INSTALL.md (reported line 100)May include surrounding context.

sudo apt-get install postgresql-16-pgvector

For Fedora/RHEL:

sudo dnf install postgresql-16-pgvector

text

### Step 3: Create Database (Bare Metal Only)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · INSTALL.md (reported line 108)May include surrounding context.

sudo apt-get install postgresql-16-pgvector

For Fedora/RHEL:

sudo dnf install postgresql-16-pgvector

text

### Step 3: Create Database (Bare Metal Only)

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · INSTALL.md (reported line 229)May include surrounding context.

EOF

Restrict access to password file

chmod 600 ~/.config/vector-memory/env

text

### Step 6: Copy Scripts

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

Running the embedding service via nohup in the background creates unattended persistence within the user session and keeps a network-accessible local service alive after installation. Even though it binds to localhost, a long-running service processing untrusted input increases attack surface, especially because users are told to copy and execute Python scripts they may not have reviewed thoroughly.

Content

Scanner excerpt · INSTALL.md (reported line 256)May include surrounding context.

md
source ~/.config/vector-memory/env

# Start service in background
nohup ~/.openclaw/workspace/vector_memory_venv/bin/python3 \
  ~/.openclaw/workspace/vector_memory/embedding_service.py \
  > /tmp/embedding_service.log 2>&1 &

Session Persistence

Medium
Category
Rogue Agent
Confidence
94% confidence
Finding

Appending commands to ~/.bashrc establishes automatic execution on future logins, which is a real persistence mechanism. In a skill context, self-installing autorun behavior is more dangerous because it causes continued execution of copied scripts without fresh user consent and could be abused if those scripts are modified later or were not adequately reviewed before setup.

Content

Scanner excerpt · INSTALL.md (reported line 279)May include surrounding context.

md
# Vector Memory Embedding Service
if ! pgrep -f "embedding_service.py" > /dev/null; then
  source ~/.config/vector-memory/env
  nohup ~/.openclaw/workspace/vector_memory_venv/bin/python3 \
    ~/.openclaw/workspace/vector_memory/embedding_service.py \
    > /tmp/embedding_service.log 2>&1 &
fi' >> ~/.bashrc

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 40)May include surrounding context.

Option B: Quick start (bare metal):

bash
# 1. Create venv and install dependencies
python3 -m venv ~/.openclaw/workspace/vector_memory_venv
source ~/.openclaw/workspace/vector_memory_venv/bin/activate
pip install flask psycopg2-binary requests sentence-transformers numpy

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 50)May include surrounding context.

md
cat > ~/.config/vector-memory/env << 'EOF'
export VECTOR_MEMORY_DB_PASSWORD="YOUR_SECURE_PASSWORD"
EOF
chmod 600 ~/.config/vector-memory/env

# 3. Start embedding service (first run downloads ~1.3GB)
source ~/.config/vector-memory/env

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · SKILL.md (reported line 91)May include surrounding context.

md
cat > ~/.config/vector-memory/env << 'EOF'
export VECTOR_MEMORY_DB_PASSWORD="YOUR_SECURE_PASSWORD"
EOF
chmod 600 ~/.config/vector-memory/env

# 3. Start embedding service (first run downloads ~1.3GB)
source ~/.config/vector-memory/env

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · SKILL.md (reported line 109)May include surrounding context.

md
Installation requires:

- **Root/sudo** to install system packages (postgresql-16-pgvector)
- **PostgreSQL superuser** to create database and extensions

**Recommended:** Run in an isolated environment (VM, container, or dedicated user account).

External Transmission

Medium
Category
Data Exfiltration
Confidence
80% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · memory_reindex.py (reported line 31)May include surrounding context.

python
def get_embeddings(texts: list) -> Optional[list]:
    """Get embeddings from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed",
            json={"texts": texts, "prefix": "passage: "},
            timeout=120

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script reads potentially sensitive memory files and transmits their contents to an embedding service without any explicit warning, consent flow, or prominent indication that data leaves the process boundary. In this context, the files may contain identity, user, or memory data, so silent transmission increases privacy and data-handling risk.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
84% confidence
Finding

This code performs external network transmission of query content to an embedding endpoint. In the context of a memory search tool, the transmitted text can include sensitive or proprietary information, making the external call security-relevant even if intended functionality requires it.

Content

Scanner excerpt · memory_search.py (reported line 26)May include surrounding context.

python
def get_embedding(text: str) -> Optional[list]:
    """Get embedding from the embedding service."""
    try:
        response = requests.post(
            f"{EMBEDDING_SERVICE_URL}/embed_query",
            json={"query": text},
            timeout=30

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The code transmits the full search query to an embedding service without any user-facing disclosure or consent mechanism. In a memory-search skill, queries may contain secrets, internal prompts, or personal data, so this hidden network transfer increases privacy and data-handling risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
81% confidence
Finding

The service initializes SentenceTransformer with a model name, which can trigger a network download of model files on first run. Although there is an internal log note at L18, there is no user-facing warning or confirmation to API consumers that startup may contact an external service and download substantial data.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The code reads VECTOR_MEMORY_DB_PASSWORD from the environment and later uses it for database authentication. Access to sensitive environment variables is in scope for missing-warning checks, and this file provides no user-facing notice in comments, CLI help, or output that credential material is being used.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.