Back to skill

Security audit

Local RAG

Security checks for vulnerabilities and agentic risk

Overview

This is a real local document search skill, but it needs Review because it can index unintended sensitive files, persist private query/index data, and disrupt local indexing processes.

Install only if you are comfortable with a local search tool reading and persisting chunks from your document folders and keeping a query log. Avoid running the batch scripts unattended, especially from privileged accounts, and do not index shared or untrusted folders until symlink handling, path containment, query-log controls, and rollback/process cleanup are fixed.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/index.py:151
Finding

Allowed-root validation can be bypassed through path-prefix confusion

Content
View full analysis
list[str]: files = [] root = os.path.expanduser(root) if not os.path.isdir(root): print(f" Skipping {root} (not a directory)") return files # Security: validate path is under allowed roots root_real = os.path.realpath(root) allowed = any(root_real.startswith(os.path.realpath(ar)) for ar in ALLOWED_ROOTS) if not allowed: print(f" Skipping {root} (outside allowed directories)") return files ``` ### Technical Analysis The authorization check uses textual prefix matching rather than verifying a path-component relationship. For example, if `/home/user/Documenti` is an allowed root, the unrelated directory `/home/user/Documenti-private` also starts with the same string and is therefore accepted. Resolving the supplied path with `realpath()` prevents a direct root-directory symlink bypass, but it does not correct the missing path-separator boundary. The resulting behavior exceeds the documented filesystem scope and the minimum privileges required to index the configured document directories. ### Attack Path 1. An attacker or caller creates or identifies a readable directory whose path shares an allowed prefix, such as `~/Documenti-private`. 2. The caller invokes: ```bash index.py --paths ~/Documenti-private ``` 3. `os.path.realpath()` resolves the path. 4. `root_real.startswith(os.path.realpath("~/Documenti"))` evaluates to true despite the directory not being a descendant of `~/Documenti`. 5. Supported documents in the unintended directory are read and persisted in ChromaDB. 6. Their content can subsequently be retrieved through `query.py`. ### Impact Assessment The issue permits read access, hashing, text extraction, embedding, and persistent inde ...[truncated 367 chars]
Remediation
View remediation
bool: candidate = Path(path).expanduser().resolve(strict=True) for root in ALLOWED_ROOTS: allowed_root = Path(root).expanduser().resolve(strict=True) if candidate == allowed_root or candidate.is_relative_to(allowed_root): return True return False ``` For compatibility with older Python versions, use `os.path.commonpath()`: ```python def is_under_allowed_root(path: str) -> bool: candidate = os.path.realpath(os.path.expanduser(path)) for root in ALLOWED_ROOTS: allowed_root = os.path.realpath(os.path.expanduser(root)) try: if os.path.commonpath([candidate, allowed_root]) == allowed_root: return True except ValueError: continue return False ``` Apply the same canonical containment validation to every file immediately before opening it, not only to the initial crawl root. Add regression tests covering sibling prefix paths such as `Documenti-private`, relative paths, `..` components, and symbolic links. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
scripts/index.py:157
Finding

Symbolic-link files can expose sensitive content outside allowed roots

Content
View full analysis
str: h = hashlib.sha256() with open(path, "rb") as f: for chunk in iter(lambda: f.read(8192), b""): h.update(chunk) return h.hexdigest() ``` ```python if ext in TEXT_EXTENSIONS: try: return Path(filepath).read_text(encoding="utf-8", errors="ignore") except Exception: return None ``` ### Technical Analysis The code validates only the supplied crawl root. It does not reject symbolic-link files or verify that each resolved file remains under an allowed root. The extension check uses the symlink’s filename rather than the resolved target. Consequently, an allowed-looking link such as `secret.txt` may point to any readable file, including a file without a supported extension. `os.path.getsize()`, `open()`, and `Path.read_text()` follow the link an ...[truncated 1763 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/index-batch.sh:22
Finding

Batch indexer bypasses the active lock and broadly terminates indexing processes

Content
View full analysis
/dev/null # Kill any lingering python index process pkill -f "lookupmark-local-rag/scripts/index.py" 2>/dev/null || true } trap cleanup EXIT ``` ```bash index_path() { local path="$1" rm -f "$LOCK" # Disk space check local free_mb=$(df -m --output=avail "$DB" | tail -1 | tr -d ' ') ``` The corresponding Python code acquires an advisory lock on the lock-file inode: ```python lock_fd = open(LOCK_FILE, "w") try: fcntl.flock(lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except OSError: print("Another indexing process is already running. Exiting.") sys.exit(0) ``` ### Technical Analysis Deleting a path whose file descriptor is actively locked does not release the lock on the existing inode. Another process may recreate the same pathname as a new inode and successfully acquire a separate lock. The batch script deliberately removes the lock before each run, defeating the Python indexer’s concurrency guarantee. The cleanup function also runs `pkill -f` against a shared command-line pattern. It does not track ownership of the process it launched and may terminate unrelated indexing jobs belonging to the same account. Depending on execution privileges, it can affect every matching process that the batch script is authorized to signal. Concurrent ChromaDB writers and interruption during updates or Git checkpoints can produce inconsistent state, lost updates, or database corruption. ### Attack Path 1. A normal or scheduled `index.py` process begins and acquires `index.lock`. 2. The batch script starts while the first process is still active. 3. `rm -f "$LOCK"` unlinks the visible lock path, but the first process retains a lock on the old inode. 4. Th ...[truncated 847 chars]
Remediation
View remediation
>"$LOG" 2>&1 & child_pid=$! cleanup() { if [[ -n "${child_pid:-}" ]] && kill -0 "$child_pid" 2>/dev/null; then kill "$child_pid" wait "$child_pid" 2>/dev/null || true fi } trap cleanup EXIT ``` Use `timeout` with a tracked process or process group rather than a global `pkill` pattern. Additional hardening should include: 1. Remove both `rm -f "$LOCK"` operations. 2. Keep lock acquisition and release in one component. 3. Return a nonzero, dedicated exit status when lock acquisition fails. 4. Ensure cleanup never removes a lock owned by another process. 5. Use a PID file only as supplemental metadata, not as a substitute for `flock`. 6. Test simultaneous direct, batch, and scheduled indexing invocations. 7. Back up or transactionally checkpoint the database before mutations rather than relying on cleanup after concurrent writes. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/index.py:350
Finding

Per-root orphan cleanup deletes valid records belonging to other roots

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • 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
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill is presented as a document search tool, but the described implementation also includes system/process inspection, log reading, disk usage reporting, and git history access. These extra operational capabilities expand the observable surface of the host environment and can expose sensitive local metadata or logs beyond the user's expectation for a simple semantic search skill.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The skill is presented as a document search tool, but the described implementation also includes system/process inspection, log reading, disk usage reporting, and git history access. These extra operational capabilities expand the observable surface of the host environment and can expose sensitive local metadata or logs beyond the user's expectation for a simple semantic search skill.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
97% confidence
Finding

The cleanup handler uses a global pkill pattern to terminate any matching Python indexer process on the system, not just the child process started by this script. That can kill unrelated workloads with a similar command line, causing denial of service and unsafe cross-process interference beyond the skill's document-search purpose.

Content

No source excerpt is available for this finding.

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
94% confidence
Finding

git reset --hard forcibly rewrites the database working tree to a previous commit, discarding current state and any uncommitted changes. In this skill context, that creates an unnecessary destructive control path that can erase index data or metadata as part of routine retries and error handling.

Content

Scanner excerpt · scripts/index-batch.sh (reported line 53)May include surrounding context.

sh
PREV=$(git log --oneline -2 --format="%H" | tail -1)
    if [ -n "$PREV" ]; then
        git checkout -- .
        git reset --hard "$PREV"
        log "  Rolled back to $(git log --oneline -1)"
    fi
    cd -

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/index.py (reported line 53)May include surrounding context.

python
os.path.expanduser("~/Scaricati"),
]

BLOCKED_PATTERNS = {".ssh", ".gnupg", ".env", "credentials", "tokens", ".config/openclaw"}

DEFAULT_PATHS = [
    "~/Documenti",

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
85% confidence
Finding

os.system("clear") invokes a shell, which is generally riskier than direct exec-style APIs because execution can be influenced by environment state such as PATH and shell behavior. In this specific script the command string is constant, so exploitability is limited, but using shell execution in a monitoring utility is unnecessary and weakens security posture.

Content

Scanner excerpt · scripts/monitor.py (reported line 180)May include surrounding context.

python
show_git()
    elif args.watch:
        while True:
            os.system("clear")
            show_status()
            print()
            show_git()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding

The skill documentation advertises commands that perform file reads, file writes, and shell execution, but it does not declare any explicit tool scope or permissions boundary. In an agent setting, this creates an authorization ambiguity where the skill may be invoked with broader capabilities than users or reviewers expect, increasing the risk of unintended filesystem access or command execution.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The completion check relies on matching Italian phrases such as 'Nuovi indicizzati' and 'Aggiornati', which means the script functionally requires Italian output from the indexed program. This is a stronger locale lock-in than mere messaging because behavior may break under other locales, yet no opt-in or documented regional constraint is present.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

When invoked without arguments, the script automatically scans hard-coded repository trees under the user's home directory instead of limiting indexing to explicitly requested locations. That expands data collection beyond the skill's stated user-driven document search scope and can silently ingest unrelated or sensitive project content.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script turns the local ChromaDB directory into a Git repository and continuously commits and rolls back index state, behavior not disclosed by the skill description. This introduces hidden persistence, history retention, and destructive state management that can preserve sensitive metadata longer than expected and alter stored data outside normal search functionality.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The rollback path performs destructive Git restoration of the database state after failures with only log messages as notice. This can discard recent index updates or other local changes without interactive confirmation, making data loss possible during ordinary operational errors.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The module docstring states the script indexes local files into ChromaDB, and the extension lists include several document formats, but extract_text only implements parsing for plain text files, PDF, and DOCX. Files such as ODT, RTF, EPUB, XLSX, and PPTX are crawled and counted as candidates yet are never actually extracted or indexed, contradicting the apparent documented capability.

Content

No source excerpt is available for this finding.

Unbounded Output

Medium
Category
Output Handling
Confidence
80% confidence
Finding

Output size or generation rate is not bounded. Unbounded output enables denial-of-service through resource exhaustion, log flooding, or context-window stuffing.

Content

Scanner excerpt · scripts/index.py (reported line 25)May include surrounding context.

python
import chromadb
from sentence_transformers import SentenceTransformer

# Silence pdfminer FontBBox spam
logging.getLogger("pdfminer").setLevel(logging.ERROR)

# --- Config ---

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The manifest says the skill is for searching PDFs, DOCX, TXT, MD, and TEX files, but the code declares support for additional document types including ODT, RTF, EPUB, XLSX, and PPTX. This expands the skill's actual content ingestion scope beyond what its description claims.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

This code emits the summary report entirely in Italian, including status headings and file-operation labels. The file does not provide any user opt-in, locale selection, or justification that the skill is intended only for Italian-speaking users, which violates the language/locale policy criterion.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/monitor.py (reported line 39)May include surrounding context.

python
def get_process_info():
    """Check if indexing is running."""
    try:
        result = subprocess.run(
            ["ps", "aux"], capture_output=True, text=True, timeout=5
        )
        lines = [l for l in result.stdout.splitlines() if "index.py" in l and "grep" not in l]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/monitor.py (reported line 80)May include surrounding context.

python
def get_git_log():
    """Get git log of checkpoints."""
    try:
        result = subprocess.run(
            ["git", "log", "--oneline", "-10"],
            capture_output=True, text=True, cwd=DB_DIR, timeout=5
        )

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/monitor.py (reported line 92)May include surrounding context.

python
def get_disk_usage():
    """Get DB size on disk."""
    try:
        result = subprocess.run(
            ["du", "-sh", DB_DIR], capture_output=True, text=True, timeout=5
        )
        return result.stdout.strip().split()[0]

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The code writes user query text to a local file without any explicit warning, consent flow, or configuration gate. Because this skill is designed to search a user’s local documents, users may naturally ask sensitive questions derived from private files, making silent logging a meaningful privacy issue and expanding exposure to other local users, backups, or forensic recovery.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill states that queries are logged to a persistent history file but does not clearly warn users about retention or privacy implications. Search queries about local documents can contain sensitive personal, legal, medical, or proprietary information, so silent retention increases confidentiality risk if the host is shared or later compromised.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
95% confidence
Finding

This shell script emits user-facing status messages in Italian (for example, 'FASE 1: Indicizzazione file testuali') without offering any language selection or documenting that the skill is intentionally Italian-only. That can violate a language/locale policy when users are not given an opt-in or the locale restriction is not justified.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
90% confidence
Finding

The monitoring script exposes git checkpoint history from the local database directory, which is outside the core purpose of indexing-status display and may reveal filenames, timestamps, workflow details, or commit messages to users of the skill. In a local document-search context, that increases unnecessary metadata exposure even if there is no direct code execution issue.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The function persists part of every user query to a local analytics log, even though the skill’s stated purpose is document search rather than telemetry collection. In a local RAG context, queries are especially likely to contain sensitive information about private files, medical/legal topics, credentials, or document contents, so writing them to disk creates an unnecessary privacy and data-retention risk.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.