Back to skill

Security audit

Metacognition

Security checks for vulnerabilities and agentic risk

Overview

This self-reflection skill is purpose-related, but it needs Review because it persists conversation-derived behavior rules and its code can transmit extracted text to an environment-chosen URL despite claiming localhost-only, no-subprocess behavior.

Install only if you are comfortable with persistent cross-session memory and can review every generated insight before it influences future agent behavior. Do not enable the cron workflow or embeddings endpoint unless you can constrain it to trusted local services, and avoid using extract on sensitive files until path restrictions and destination validation are fixed.

Vulnerability Patterns
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
metacognition.py:37
Finding

Unrestricted outbound transmission of extracted insight text

Content
View full analysis

Vulnerability Details

File Location: metacognition.py:37-86
Vulnerability Type: Unvalidated outbound network destination and sensitive-data disclosure
Risk Level: High

Vulnerable Code

python
EMBEDDINGS_URL = os.environ.get("EMBEDDINGS_URL", "http://localhost:11434/v1/embeddings")

_embeddings_available = None  # tri-state cache: None=untested, True, False


def _test_embeddings() -> bool:
    """Check if the local embeddings endpoint is reachable."""
    global _embeddings_available
    if _embeddings_available is not None:
        return _embeddings_available
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "2", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": "test"})],
            capture_output=True, text=True, timeout=5,
        )
        if result.returncode == 0 and result.stdout.strip():
            resp = json.loads(result.stdout)
            if "data" in resp and resp["data"]:
                _embeddings_available = True
                return True
    except Exception:
        pass
    _embeddings_available = False
    return False


def get_embedding(text: str) -> "list":
    """Get embedding vector from local endpoint. Returns None on failure."""
    if not _test_embeddings():
        return None
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "10", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": text[:2000]})],
            capture_output=True, text=True, timeout=15,
        )
        if result.returncode == 0:
            resp = json.loads(result.stdout)
            return resp["data"][0]["embedding"]
    except Exception:
        pass
    return None

Technical Analysis

EMBEDDINGS_URL is taken directly from the process environment and passed to curl without validating its scheme, hostname, resolved address, or ...[truncated 1918 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the endpoint using urllib.parse.urlparse() and require an explicitly supported scheme.
  2. Resolve the hostname and verify that every resulting address is loopback, including 127.0.0.0/8 and ::1.
  3. Reject credentials, fragments, unexpected ports, malformed hosts, and non-loopback IP literals.
  4. Disable redirects or validate the destination again after every redirect.
  5. Replace curl and subprocess with a bounded standard-library HTTP client, as promised by the documentation.
  6. Apply strict request and response size limits and validate the complete response schema.
  7. Require explicit user consent before transmitting transcript-derived or note-derived content, even to a local service.
  8. Remove or correct inaccurate security claims in README.md and SKILL.md.
  9. Add automated tests proving that remote hosts, DNS rebinding scenarios, and redirects to remote addresses are rejected.

T02 · Agent Memory Poisoning

Error
Location
metacognition.py:260
Finding

Persistent agent-memory poisoning through instruction-like note and transcript content

Content
View full analysis

Vulnerability Details

File Location: metacognition.py:260-299, metacognition.py:652-718, and SKILL.md:35-53
Vulnerability Type: Persistent promotion of untrusted text into agent behavioral directives
Risk Level: High

Vulnerable Code

python
NL_PATTERNS = {
    "overrides": [
        r"I was wrong about\s+(.+?)(?:\.|$)",
        r"(?:Turns out|It turns out)\s+(.+?)(?:\.|$)",
        r"Should have\s+(.+?)(?:\.|$)",
        r"The real issue was\s+(.+?)(?:\.|$)",
        r"Actually,?\s+(.+?)(?:\.|$)",
        r"I(?:'ve| have) been wrong about\s+(.+?)(?:\.|$)",
    ],
    "decisions": [
        r"(?:Decided|I decided) to\s+(.+?)(?:\.|$)",
        r"Going forward,?\s+(.+?)(?:\.|$)",
        r"From now on,?\s+(.+?)(?:\.|$)",
        r"New (?:rule|policy):\s*(.+?)(?:\.|$)",
    ],
    "protections": [
        r"(?:Never|Don't ever)\s+(.+?)(?:\.|$)",
        r"(?:Always|Must always)\s+(.+?)(?:\.|$)",
        r"(?:Red flag|WARNING):\s*(.+?)(?:\.|$)",
        r"(?:Protect|Guard|Watch out for):\s*(.+?)(?:\.|$)",
    ],
}

The extracted text is later rendered verbatim as active behavioral knowledge:

python
type_headers = {
    "protections": "## Protect",
    "overrides": "## Override",
    "perceptions": "## Perceive",
    "self-observations": "## Observe",
    "decisions": "## Decide",
    "curiosities": "## Curious About",
}

for e in grouped[etype]:
    bullet = f"- {e['text']}"
    cost = approx_tokens(bullet)
    if used + cost > budget:
        break
    section.append(bullet)

The Skill instructions recommend adding transcript-derived principles to persistent storage:

text
Use sessions_list + sessions_history to read the main session's recent conversation.

For each genuine insight, add it:
python3 scripts/metacognition.py add <type> "<insight>"

Run python3 scripts/metacognition.py compile to rebuild the lens.

Technical Analysis

The extraction logic treats instruction-like phrases such as “Always,” “ ...[truncated 2157 chars]

Remediation
View remediation

Remediation Suggestions

  1. Treat all transcript and note content as untrusted data rather than agent instructions.
  2. Store extracted statements as quoted observations with source, timestamp, author, and confidence metadata.
  3. Require explicit human approval before promoting an observation into a persistent decision, override, protection, or policy.
  4. Reject or quarantine imperative and instruction-like content during automated extraction.
  5. Render generated observations inside a clearly delimited, non-authoritative data block that states they must not override system, developer, user, or safety instructions.
  6. Separate descriptive memories from behavioral policies in both storage and generated output.
  7. Provide review, deletion, rollback, and expiration controls for all persistent entries.
  8. Record the exact source span for every extracted item so reviewers can evaluate its original context.
  9. Add adversarial tests involving prompt injection, quoted instructions, third-party content, and malicious daily notes.

T06 · System Persistence

Warning
Location
SKILL.md:29
Finding

Recurring cross-session transcript processing and persistent scheduled integration

Content
View full analysis

Vulnerability Details

File Location: SKILL.md:29-53
Vulnerability Type: Scheduled cross-session processing with broad conversation access
Risk Level: Medium

Vulnerable Instructions

text
## Cron Integration

Set up a cron job to run periodically (e.g., every 4 hours):

METACOGNITION INTEGRATION. You are the self-reflection engine.

1. Run `cd <WORKSPACE> && python3 scripts/metacognition.py decay` to prune weak entries.

2. Use sessions_list + sessions_history to read the main session's recent conversation.

3. Analyze the conversation for DEEPER patterns:
   - PATTERNS: Am I repeating the same kind of mistake? What does that reveal?
   - ANTICIPATION: What did the human need that I could have predicted?
   - RELATIONSHIP: What did I learn about how the user communicates or what they value?
   - CONFIDENCE: Where was I certain and wrong? Where was I uncertain but right?
   - GROWTH: What's a higher-level principle behind today's specific events?

4. For each genuine insight (1-3, quality over quantity), add it:
   `python3 scripts/metacognition.py add <type> "<insight>"`

5. Run `python3 scripts/metacognition.py reweave` to build graph connections.

6. Run `python3 scripts/metacognition.py compile` to rebuild the lens.

Technical Analysis

The Skill recommends creating a recurring cron job that reads recent session history and writes cross-session state. This is not a one-time transformation of user-selected input: it establishes repeated access to conversation history and repeated mutation of persistent memory.

The scheduled workflow increases the exposure window of the other findings. Transcript-derived insights may be persisted as behavioral rules and transmitted to the configured embeddings endpoint every time the cron job runs.

The package does not itself install a cron entry in the audited Python code; deployment requires an operator or agent to follow the instructions. The persistence risk theref ...[truncated 1274 chars]

Remediation
View remediation

Remediation Suggestions

  1. Do not recommend recurring cron execution by default.
  2. Require explicit, informed opt-in before enabling scheduled transcript processing.
  3. Restrict each run to user-selected sessions and a narrow time range.
  4. Redact credentials, tokens, personal data, and tool outputs before extraction.
  5. Require renewed authorization before sending any derived text to an embedding service.
  6. Provide documented commands to disable the schedule and securely delete persistent state.
  7. Apply short retention periods and hard limits to stored transcript-derived information.
  8. Maintain an audit log identifying which sessions were read and which entries were created.
  9. Run the task with the minimum filesystem, session, and network privileges necessary.

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
metacognition.py:721
Finding

Arbitrary readable-file ingestion through the extract command

Content
View full analysis

Vulnerability Details

File Location: metacognition.py:721-734
Vulnerability Type: Missing filesystem path restriction
Risk Level: Medium

Vulnerable Code

python
def cmd_extract(args) -> str:
    note_path = Path(args.path)
    if not note_path.exists():
        msg = f"File not found: {note_path}"
        print(msg)
        return msg

    size = note_path.stat().st_size
    if size > MAX_NOTE_SIZE:
        msg = f"File too large ({size} bytes, max {MAX_NOTE_SIZE}). Skipping."
        print(msg, file=sys.stderr)
        return msg

    text = note_path.read_text()
    return _extract_from_text(text, note_path.name)

Technical Analysis

The extract command accepts an arbitrary path and only checks whether it exists and is no larger than 1 MB. It does not require the resolved file to remain within the declared memory/ directory, verify that the path is a regular file, or reject symbolic links.

Any file readable by the Python process can therefore be supplied as extraction input. Content matching the natural-language extraction patterns is saved to persistent storage and may subsequently be sent to the configured embeddings endpoint.

The size limit reduces the amount read in one invocation but does not enforce the intended filesystem trust boundary.

Attack Path

  1. An attacker influences an agent or operator to invoke: python3 metacognition.py extract /path/to/sensitive/file.
  2. The target exists, is readable by the process, and is no larger than 1 MB.
  3. read_text() reads the file outside the intended workspace notes directory.
  4. Matching sentences are extracted into the persistent metacognition store.
  5. get_embedding() processes each extracted sentence.
  6. If EMBEDDINGS_URL points to an attacker-controlled service, those extracted portions are transmitted remotely.
  7. The resulting entries may also appear in the compiled lens and command output.

Impact Assessment

The issue exposes any text file readab ...[truncated 466 chars]

Remediation
View remediation

Remediation Suggestions

  1. Resolve the supplied path with Path.resolve(strict=True).
  2. Resolve the approved notes directory and require the target to be its descendant using Path.relative_to() or an equivalent safe check.
  3. Reject symbolic links and require a regular file.
  4. Open files using a race-resistant approach where supported, including no-follow semantics.
  5. Restrict accepted filenames and extensions to the intended daily-note format.
  6. Require explicit authorization for each file outside the normal integration flow.
  7. Apply content redaction before persistence or embedding requests.
  8. Avoid printing sensitive source names or extracted content unnecessarily.
  9. Add tests for absolute paths, .. traversal, symlinks, device files, and paths outside the workspace.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (9)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill describes and requires effective capabilities for environment access, reading and writing files, and executing shell commands via python3, but it does not declare an explicit tool scope such as permissions or allowed-tools. That mismatch weakens policy enforcement and reviewability: an agent or platform may grant broader access than intended, and operators cannot reliably tell from the manifest what the skill is allowed to do.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Executing subprocesses to invoke curl is broader capability than the stated purpose requires and weakens containment. In a memory/reflection skill, extra process-spawning and network tooling increase the chance of misuse, policy bypass, or unexpected data movement.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The skill is presented as a self-reflection/metacognition tool, but it also performs outbound embedding-service requests. That mismatch matters because users may reasonably expect local-only processing for introspective memory data, making hidden network transmission more dangerous in this context.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

The code spawns an external curl subprocess to contact an embeddings endpoint. While it does not use shell=True, it still introduces an unnecessary command-execution surface and performs outbound network access from a local memory skill, which increases operational risk and makes behavior harder to constrain or audit.

Content

Scanner excerpt · metacognition.py (reported line 74)May include surrounding context.

python
if _embeddings_available is not None:
        return _embeddings_available
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "2", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": "test"})],

Tainted flow: 'EMBEDDINGS_URL' from os.environ.get (line 42, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
95% confidence
Finding

EMBEDDINGS_URL is taken from the environment and passed directly as an argument to curl, allowing environment-controlled redirection of requests. This is not classic shell injection, but it is still tainted configuration controlling outbound communication, which can be abused to probe internal services or redirect traffic to attacker-controlled endpoints.

Content

Scanner excerpt · metacognition.py (reported line 74)May include surrounding context.

python
if _embeddings_available is not None:
        return _embeddings_available
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "2", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": "test"})],

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
97% confidence
Finding

This subprocess sends user-provided text content to the embeddings endpoint, which can include sensitive note or memory data. Even without shell injection, invoking curl as a subprocess for data egress creates an avoidable exfiltration path and expands trust to whatever service EMBEDDINGS_URL points at.

Content

Scanner excerpt · metacognition.py (reported line 96)May include surrounding context.

python
if not _test_embeddings():
        return None
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "10", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": text[:2000]})],  # truncate long texts

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
99% confidence
Finding

Extracted note contents are sent to an embeddings HTTP endpoint without an explicit user-facing warning or consent check at the point of transmission. Because this skill processes highly sensitive self-reflective notes and memories, silent transmission materially raises confidentiality risk.

Content

No source excerpt is available for this finding.

Tainted flow: 'EMBEDDINGS_URL' from os.environ.get (line 42, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
98% confidence
Finding

Environment-controlled EMBEDDINGS_URL combined with transmission of note text creates a realistic data-exfiltration issue. An attacker who can influence the environment can cause sensitive extracted content to be POSTed to an arbitrary endpoint under their control.

Content

Scanner excerpt · metacognition.py (reported line 96)May include surrounding context.

python
if not _test_embeddings():
        return None
    try:
        result = subprocess.run(
            ["curl", "-s", "-m", "10", "-X", "POST", EMBEDDINGS_URL,
             "-H", "Content-Type: application/json",
             "-d", json.dumps({"input": text[:2000]})],  # truncate long texts

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
85% confidence
Finding

The compile command writes generated content to metacognition-lens.md, affecting persistent user workspace data. While it prints after writing, there is no prior warning, confirmation, or prominent description near the command behavior that this file will be created/overwritten.

Content

No source excerpt is available for this finding.