Back to skill

Security audit

OEE Knowledge Base RAG

Security checks for vulnerabilities and agentic risk

Overview

This is a plausible personal knowledge-base skill, but it stores and sends saved content externally and contains unsafe URL/PDF handling that should be reviewed before use.

Review before installing. Do not use this skill with secrets, regulated data, private company documents, internal URLs, or untrusted PDFs unless it is remediated. It should clearly disclose remote AI-provider processing, add confirmation/local-only controls, restrict URL destinations, and fix the PDF and temporary-file handling issues.

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)

T09 · Insecure Skill Coding Practices

Error
Location
kb.py:204
Finding

Arbitrary Code Execution Through Python Source Injection in PDF Extraction

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
kb.py:104
Finding

Server-Side Request Forgery Through Unrestricted User-Supplied URLs

Content
View full analysis
str: req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (compatible; OEEBot/1.0)"}) with urllib.request.urlopen(req, timeout=timeout) as r: return r.read().decode("utf-8", errors="replace") ``` ```python def extract_pdf(url_or_path: str) -> tuple[str, str]: """Extract text from PDF.""" import tempfile path = url_or_path if url_or_path.startswith("http"): tmp = tempfile.NamedTemporaryFile(suffix=".pdf", delete=False) data = urllib.request.urlopen(url_or_path, timeout=30).read() tmp.write(data) tmp.close() path = tmp.name ``` ```python def extract_article(url: str) -> tuple[str, str]: """Extract article content with readability → raw fallback.""" raw = _retry(lambda: _api_get(url)) ``` ### Technical Analysis The URL supplied through `ingest.py` is passed to `urllib.request.urlopen()` without enforcing an allowed scheme or destination policy. The implementation does not reject loopback, private, link-local, multicast, reserved, or cloud metadata addresses. It also does not revalidate destinations after redirects. Consequently, the Skill can be used as a request proxy from the host on which it runs. Its network position may allow it to reach endpoints unavailable to an external attacker, including localhost services, private network APIs, container control interfaces, and cloud instance metadata services. Fetched content is not merely returned to the caller. It can subsequently be stored in SQLite and sent to the configured embedding provider, which can turn SSRF into a data-exfiltration path. ### Attack Path 1. An attacker persuades an agent or user to ingest a URL targeting an internal resource, su ...[truncated 1136 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
kb.py:302
Finding

Sensitive Knowledge-Base Content and Queries Are Transmitted to External AI Providers

Content
View full analysis
list[list[float]]: """Call OpenAI embeddings API.""" api_key = os.environ.get("OPENAI_API_KEY") if not api_key: raise RuntimeError("OPENAI_API_KEY not set") payload = json.dumps({"input": texts, "model": EMBED_MODEL}).encode() req = urllib.request.Request( "https://api.openai.com/v1/embeddings", data=payload, headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"}, ) for attempt in range(EMBED_RETRIES): try: with urllib.request.urlopen(req, timeout=30) as r: data = json.loads(r.read()) ``` ```python def retrieve(query: str, top_k: int = 10) -> list[dict]: """Find most relevant chunks for a query.""" db = get_db() q_emb = embed_query(query) ``` ```python context = "\n\n---\n\n".join(context_parts) api_key = os.environ.get("ANTHROPIC_API_KEY") if not api_key: raise RuntimeError("ANTHROPIC_API_KEY not set") system = "You are a helpful research assistant. Answer using ONLY the provided context. Cite sources using [n] notation. If the context doesn't contain enough information, say so." user_msg = f"Context:\n\n{context}\n\n---\n\nQuestion: {query}" payload = json.dumps({ "model": "claude-sonnet-4-20250514", "max_tokens": 2048, "system": system, "messages": [{"role": "user", "content": user_msg}], }).encode() req = urllib.request.Request( "https://api.anthropic.com/v1/messages", data=payload, headers={ "x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json", }, ) ``` The documentation states only: ``` ...[truncated 2445 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
kb.py:153
Finding

Predictable Shared Temporary Files Permit Transcript Poisoning and Local File Disclosure

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (20)

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · kb.py (reported line 108)May include surrounding context.

python
def _api_get(url: str, timeout: int = 15) -> str:
    req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (compatible; OEEBot/1.0)"})
    with urllib.request.urlopen(req, timeout=timeout) as r:
        return r.read().decode("utf-8", errors="replace")

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · kb.py (reported line 317)May include surrounding context.

python
for attempt in range(EMBED_RETRIES):
        try:
            with urllib.request.urlopen(req, timeout=30) as r:
                data = json.loads(r.read())
            return [d["embedding"] for d in sorted(data["data"], key=lambda x: x["index"])]
        except Exception as e:

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

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · kb.py (reported line 485)May include surrounding context.

python
},
    )

    with urllib.request.urlopen(req, timeout=60) as r:
        data = json.loads(r.read())

    answer = data["content"][0]["text"]

Hidden Instructions

High
Category
Prompt Injection
Confidence
70% confidence
Finding

Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.

Content

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

md
Save context: `python ingest.py "TEXT"`
Retrieve context: `python query.py "QUESTION"`

<!-- 🐾 Muninn never forgets -->

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The skill encourages saving arbitrary text, URLs, files, and notes, and it requires external embedding providers via API keys, but it does not warn users that sensitive content may be stored locally in SQLite and transmitted to third-party embedding services. This omission can cause users or agents to send secrets, personal data, or confidential material into storage and external processing without informed consent.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases include generic language such as "remember this" and "what do I know about," which can easily overlap with normal conversation and cause unintended activation. In an agent setting, that can lead to accidental ingestion of user content into persistent storage or retrieval from the knowledge base without sufficiently explicit user intent.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · kb.py (reported line 129)May include surrounding context.

python
if not match:
        raise ValueError(f"Not a valid tweet URL: {url}")
    user, tweet_id = match.groups()
    api_url = f"https://api.fxtwitter.com/{user}/status/{tweet_id}"
    try:
        data = json.loads(_retry(lambda: _api_get(api_url)))
        tweet = data.get("tweet", {})

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% 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 · kb.py (reported line 138)May include surrounding context.

python
if not match:
        raise ValueError(f"Not a valid tweet URL: {url}")
    user, tweet_id = match.groups()
    api_url = f"https://api.fxtwitter.com/{user}/status/{tweet_id}"
    try:
        data = json.loads(_retry(lambda: _api_get(api_url)))
        tweet = data.get("tweet", {})

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The module is described as a personal knowledge base with semantic retrieval, but this implementation adds subprocess execution via yt-dlp for YouTube transcript extraction. Without any manifest declaring shell/tool execution as part of scope, spawning external binaries is a broader capability than the stated high-level KB purpose implies.

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 · kb.py (reported line 145)May include surrounding context.

python
def extract_youtube(url: str) -> tuple[str, str]:
    """Extract YouTube transcript via yt-dlp."""  # 🐾
    try:
        title_out = subprocess.run(
            ["yt-dlp", "--get-title", url],
            capture_output=True, text=True, timeout=30
        )

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 · kb.py (reported line 153)May include surrounding context.

python
except Exception:
        title = "YouTube Video"

    result = subprocess.run(
        ["yt-dlp", "--write-auto-sub", "--sub-lang", "en", "--skip-download",
         "--sub-format", "vtt", "-o", "/tmp/kb_yt_%(id)s", url],
        capture_output=True, text=True, timeout=60

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Invoking external tooling to process PDFs materially increases risk because PDFs are attacker-controlled inputs and parser chains are historically bug-prone. In this implementation, the danger is compounded by dynamic code construction for python3 -c, turning a content-extraction feature into a potential local code-execution path when given a crafted local filename.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
96% confidence
Finding

This code builds Python source with an f-string and injects the PDF path directly into a python3 -c script. If path contains quotes or Python syntax, an attacker controlling the local path argument to extract_pdf can break out of the string literal and execute arbitrary Python code in the subprocess.

Content

Scanner excerpt · kb.py (reported line 204)May include surrounding context.

python
path = tmp.name

    try:
        result = subprocess.run(
            ["python3", "-c", f"""
import sys
try:

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The skill sends ingested content to external embedding and model providers using environment-backed credentials without any visible access control, redaction, or sensitivity checks. In a personal knowledge-base context, that can expose stored private notes, fetched documents, or other sensitive material to third parties unexpectedly.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Text passed to embed_texts is uploaded to OpenAI without any user-facing warning at the transmission point. For a knowledge base that may hold personal or confidential content, silent third-party transmission creates a meaningful privacy and compliance risk.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
87% confidence
Finding

This line sends potentially sensitive ingested text to OpenAI's embeddings API. In the context of a personal knowledge base, external transmission of document content is a real privacy/security concern if done without clear consent, minimization, or redaction.

Content

Scanner excerpt · kb.py (reported line 310)May include surrounding context.

python
payload = json.dumps({"input": texts, "model": EMBED_MODEL}).encode()
    req = urllib.request.Request(
        "https://api.openai.com/v1/embeddings",
        data=payload,
        headers={"Authorization": f"Bearer {api_key}", "Content-Type": "application/json"},
    )

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Remote content fetched from arbitrary URLs is embedded via a third-party API without a clear disclosure step, which may surprise users and may violate expectations around handling copyrighted, confidential, or regulated content. The ingestion workflow makes this more concerning because transmission is automatic after fetch and validation.

Content

No source excerpt is available for this finding.

Ssd 3

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

Retrieved chunk content is inserted verbatim into the Anthropic prompt, so any sensitive text previously ingested can be disclosed in plain language to the external model and then to whoever can query the system. In a personal knowledge-base setting, this is especially risky because the corpus may contain secrets, private notes, credentials, or proprietary documents.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
89% confidence
Finding

The ask() flow transmits retrieved KB context verbatim to Anthropic, potentially including sensitive information from previously ingested sources. Because retrieval is query-driven and the content is inserted wholesale, this can expose private corpus contents to an external LLM provider and to unauthorized users of the skill.

Content

Scanner excerpt · kb.py (reported line 476)May include surrounding context.

python
}).encode()

    req = urllib.request.Request(
        "https://api.anthropic.com/v1/messages",
        data=payload,
        headers={
            "x-api-key": api_key,

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
96% confidence
Finding

The yt-dlp invocation forces --sub-lang en, which imposes a specific language/locale behavior without user opt-in. This can violate language-choice policy because users are not given any option to select another subtitle language or accept the English-only constraint.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.