Back to skill

Security audit

Epistemic Council

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real local reasoning pipeline, but it needs Review because it can run scripts, write persistent memory/state, and has broader workspace authority than its purpose requires.

Install only after narrowing the manifest permissions, especially removing write access to user, identity, agent, and tool configuration files. Treat runs as local code execution that can modify the workspace database and memory files, and use it only with a trusted localhost model service. Review or disable MEMORY.md compression unless generated memory updates are quarantined and approved first.

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
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T02 · Agent Memory Poisoning

Error
Location
memory_compress.py:124
Finding
Untrusted Session Logs Can Poison Persistent Agent Memory<![CDATA[ ## Vulnerability Details **File Location**: `memory_compress.py:124-129`, `memory_compress.py:166-202`, and `memory_compress.py:277-281` **Vulnerability Type**: Persistent prompt injection and memory poisoning **Risk Level**: High ### Vulnerable Code ```python def read_daily_logs(n_days: int = 7) -> tuple: """Returns (combined_text, list_of_paths_read).""" cutoff = datetime.utcnow() - timedelta(days=n_days) logs = [] paths = [] for day_offset in range(n_days): date = (datetime.utcnow() - timedelta(days=day_offset)).strftime("%Y-%m-%d") log_file = MEMORY_DIR / f"{date}.md" if log_file.exists(): logs.append(f"=== {date} ===\n{log_file.read_text()[:2000]}") paths.append(log_file) return "\n\n".join(logs), paths ``` ```python def update_memory_md(sections: dict, substrate: Substrate): """ Rewrite MEMORY.md with current distilled insights. Appends new content to existing sections rather than overwriting. """ existing = MEMORY_MD.read_text() if MEMORY_MD.exists() else "" insights = substrate.get_insights() total_insights = len(insights) high_conf = substrate.get_insights_above_confidence(0.75) challenged_zone = substrate.get_claims_in_confidence_range(0.45, 0.55) now = datetime.utcnow().isoformat() new_content = f"""# MEMORY.md — Epistemic Council Agent Long-Term Memory _Last updated: {now}_ ## Substrate Reality (auto-computed) - Total insights in substrate: {total_insights} - High-confidence insights (>0.75): {len(high_conf)} - Challenged-zone claims (0.45–0.55): {len(challenged_zone)} ## Calibration Heuristics _Updated from last 7-day log compression_ """ for lesson in sections.get("calibration_lessons", []): new_content += f"- {lesson}\n" new_content += "\n## Domain Boundaries Discovered\n" for boundary in sections.get("domain_boundaries", []): new_content += f"- {boundary}\n" new_content += "\n## E ...[truncated 2985 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Treat all daily-log content as untrusted data and clearly delimit it in the model prompt. 2. Add an explicit instruction that text inside the log delimiters must never be interpreted as commands. 3. Use a strict structured-output schema, such as validated JSON with fixed fields, item limits, and length limits. 4. Reject generated entries containing role changes, imperatives, tool instructions, external URLs, encoded payloads, or references to agent policy files. 5. Require human approval before generated content is promoted into `MEMORY.md`. 6. Write proposed summaries to a quarantine file first, such as `memory/pending-memory-update.json`. 7. Preserve versioned backups and an audit trail so poisoned memory can be identified and rolled back. 8. Record the source log and model response associated with every accepted memory entry. 9. Keep user preferences and behavioral instructions in a separately controlled file that automated compression cannot modify. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
skill.json:34
Finding
Skill Manifest Grants Unnecessary Access to Sensitive Agent Configuration<![CDATA[ ## Vulnerability Details **File Location**: `skill.json:34-46` **Vulnerability Type**: Excessive workspace permissions and failure to enforce least privilege **Risk Level**: Medium ### Vulnerable Code ```json "workspace": { "root": "../", "access": "read_write", "allowedPaths": [ "epistemic_council/**", "memory/**", "HEARTBEAT.md", "MEMORY.md", "USER.md", "IDENTITY.md", "SOUL.md", "AGENTS.md", "TOOLS.md" ], "deniedPaths": [] } ``` Related permissions further expand the execution scope: ```json "permissions": { "workspace": "read_write", "filesystem": [ "epistemic_council/epistemic.db", "epistemic_council/memory/**", "epistemic_council/agent_learning.json", "epistemic_council/openclaw_memory.json" ], "shell": true, "network": "localhost_only" } ``` ### Technical Analysis The audited implementation requires access to its SQLite database, state files, generated reports, and memory directories. No reviewed code path requires read/write access to `USER.md`, `IDENTITY.md`, `SOUL.md`, `AGENTS.md`, or `TOOLS.md`. Nevertheless, the manifest grants read/write access to these files and leaves `deniedPaths` empty. These files may define identity, user information, behavioral policy, agent instructions, and tool usage. General shell access is also enabled even though the dispatcher only invokes a fixed set of local Python scripts. This violates least privilege. Although the current reviewed scripts do not modify these sensitive files, a compromised dependency, prompt-driven future feature, or newly introduced code path would inherit the broader declared permissions. ### Attack Path 1. An attacker compromises a code path executing under the Skill, such as through a future vulnerable entrypoint or malicious modification. 2. The compromised code uses the permissions granted by `skill.json`. 3. It reads sensitive user or agent-policy content from files such as `USER.md`, `IDENTITY.md`, o ...[truncated 811 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict writable paths to the exact files and directories required by the implementation: - `epistemic_council/epistemic.db` - Dedicated Skill state and report directories - Explicit learning-state files, if the feature remains enabled 2. Remove `USER.md`, `IDENTITY.md`, `SOUL.md`, `AGENTS.md`, and `TOOLS.md` from `allowedPaths`. 3. Add sensitive identity, user, policy, and tool-definition files to `deniedPaths`. 4. Separate read and write permissions where the platform supports it. 5. Replace general shell access with an allowlist of fixed entrypoints. 6. Prevent arbitrary script names and arguments from being supplied to the subprocess helper. 7. Keep generated reports under a Skill-specific directory rather than the shared workspace memory directory. 8. Add automated tests that compare declared permissions with files actually opened by the Skill. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
challenge_orchestrator.py:45
Finding
Adversarial Validation Uses an Incompatible Insight Schema and Fails Open on Errors<![CDATA[ ## Vulnerability Details **File Location**: `substrate.py:167-181` and `challenge_orchestrator.py:45-48, 78-81, 103-110, 134-137` **Vulnerability Type**: Validation bypass caused by schema mismatch and fail-open exception handling **Risk Level**: Medium ### Vulnerable Code Insights are stored using `content["insight"]`, while source claims are placed in `parent_ids`: ```python def write_insight(self, agent_id: str, domain: str, insight_text: str, insight_type: InsightType, confidence: float, source_claim_ids: list, cross_domain_flag: bool = False) -> SubstrateEvent: if not source_claim_ids: raise ValueError("write_insight requires at least one source_claim_id") content = { "insight": insight_text, "insight_type": insight_type.value, "cross_domain_flag": cross_domain_flag, "adversarial_status": None, } return self._append( EventType.INSIGHT_GENERATED, agent_id, domain, content, source_claim_ids, confidence ) ``` The validation orchestrator expects different content keys: ```python insight_dict = { 'content': insight_event.content, 'confidence': insight_event.confidence } print(f"\n🔬 Challenging insight (conf={insight_event.confidence:.2f}):") print(f" {insight_event.content.get('text', '')[:80]}...") ``` ```python # Get source claims for this challenge source_claim_ids = insight_event.content.get('source_claim_ids', []) source_claims = [] for claim_id in source_claim_ids: event = self.substrate.get_event(claim_id) if event: source_claims.append({ 'domain': event.domain, 'content': event.content }) ``` Validator exceptions are treated as successful, neutral checks: ```python except Exception as e: print(f" ⚠️ ERROR: {e}") boundary_result = ChallengeResult(True, 1.0, f"Error: {e}", {}) results['boundary'] = boundary_result confidence_adjustments.append(1.0) ...[truncated 2734 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Define one canonical schema for insight events and use it across all modules. 2. Read insight text from `insight_event.content["insight"]`, or rename the stored key consistently to `text`. 3. Read provenance from `insight_event.parent_ids`, or intentionally duplicate it into a validated content field. 4. Introduce typed event-accessor methods rather than using ad hoc dictionary keys. 5. Validate required fields before running any challenge: - Non-empty insight text - At least one valid source claim - Expected event type 6. Fail closed when a validator errors. Record the result as `passed=False` or use an explicit `validation_error` status that cannot contribute to validation. 7. Do not use a neutral `1.0` confidence factor for failed checks. 8. Add integration tests that create an insight through `write_insight()` and verify that all challenge agents receive its actual text and source events. 9. Prevent overall `validated` status unless every required validator completes successfully. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (36)

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
Produce between 0 and 5 analogies. Be precise and conservative.
"""
        return prompt

    # -----------------------------------------------------------------
    # Response parsing
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Direct Prompt Extraction

High
Category
System Prompt Leakage
Content
Produce between 0 and 5 analogies. Be precise and conservative.
"""
        return prompt

    # -----------------------------------------------------------------
    # Response parsing
Confidence
85% confidence
Finding
Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Intent-Code Divergence

High
Confidence
98% confidence
Finding
The class docstring states that the substrate is append-only and that no UPDATE or DELETE paths exist. However, prune_visibility performs an UPDATE on the events table, modifying visibility_score for an existing event, which directly contradicts the documented intent.

Vague Triggers

Medium
Confidence
91% confidence
Finding
The trigger list contains generic phrases such as "health check," "validate claims," "check boundaries," and "find gaps" that could plausibly match ordinary user requests unrelated to this skill. In an agent environment, overly broad activation terms can cause unintended invocation of a skill that executes local commands, increasing the chance of unauthorized or surprising code execution paths.

External Transmission

Medium
Category
Data Exfiltration
Content
Your answer (YES or NO only):"""

        try:
            response = requests.post(
                f"{self.model_url}/api/generate",
                json={"model": self.model_name, "prompt": prompt, "stream": False,
                      "options": {"temperature": 0.3, "num_predict": 50}},
Confidence
88% confidence
Finding
This is a genuine external transmission sink: requests.post sends prompt content containing user/query-derived text to a configurable endpoint. In this skill's context, the transmission is part of intended functionality, but it is still security-relevant because the destination may be non-local or untrusted and there are no safeguards around data sensitivity or endpoint trust.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code sends user-derived query_text and insight_text to an HTTP model endpoint without any consent, disclosure, minimization, or boundary checks. Even if the default target is localhost, model_url is configurable and this creates a real data-exposure path for potentially sensitive prompts or source material.

External Transmission

Medium
Category
Data Exfiltration
Content
Generate:"""

        try:
            response = requests.post(
                f"{self.model_url}/api/generate",
                json={"model": self.model_name, "prompt": prompt, "stream": False,
                      "options": {"temperature": 0.7, "num_predict": 200}},
Confidence
90% confidence
Finding
This outbound request sends compiled claim text and insight content to an external service for counter-example generation. The skill context makes this more dangerous because it aggregates multiple source claims into one payload, increasing the chance of exposing confidential corpus data in a single request.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Counter-example generation transmits insight text plus aggregated source-claim text to the model endpoint, which can leak underlying source content outside the immediate processing boundary. Because source_claims may contain proprietary, personal, or otherwise sensitive text, undisclosed transmission increases privacy and confidentiality risk.

External Transmission

Medium
Category
Data Exfiltration
Content
Your judgment (A, B, or C with brief explanation):"""

        try:
            response = requests.post(
                f"{self.model_url}/api/generate",
                json={"model": self.model_name, "prompt": prompt, "stream": False,
                      "options": {"temperature": 0.3, "num_predict": 150}},
Confidence
87% confidence
Finding
This request is another external transmission point, forwarding insight text and generated counter-example content to the model service. Re-sending derived content may appear low risk, but it can still include sensitive user text or proprietary reasoning and expands the exposure surface.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The evaluation step sends the insight and generated counter-example back to the model endpoint without disclosure or classification of sensitivity. This creates an additional exfiltration hop and can propagate sensitive or model-derived content to external infrastructure unnecessarily.

External Transmission

Medium
Category
Data Exfiltration
Content
not creative hallucination.
        """
        import requests
        resp = requests.post(
            f"{self.base_url}/api/generate",
            json={
                "model": self.model_name,
Confidence
80% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The null-hypothesis challenge sends `insight_dict` and `query_text` to a model-backed agent configured by `model_url`, with no consent, sanitization, minimization, or policy gate. Even if the default is localhost, the URL is configurable and the orchestrator provides only debug prints, so sensitive insight or query data could be transmitted to an external model service without users realizing it.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The false-analogy challenge gathers full `source_claims` from the substrate and passes their contents into a model-backed agent, potentially disclosing more historical or linked data than necessary. Because these claims are fetched indirectly by ID, the amount and sensitivity of data sent downstream may be non-obvious, increasing the risk of unintended leakage to a local or remote model endpoint.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
The engine writes model-generated analogies directly into the shared substrate once they clear a confidence threshold, with no human approval, provenance warning, or secondary validation. Because the model is operating over potentially noisy claims and free-form prompt output, this can persist hallucinated or weakly supported insights as if they were legitimate system knowledge, enabling downstream components or users to trust and act on unverified content.

Missing User Warnings

Medium
Confidence
85% confidence
Finding
The skill writes heartbeat and run-state data to disk without notifying the user at execution time. In a READ_WRITE workspace, even seemingly small state writes can overwrite files, persist unintended data, or be abused to hide operational state changes from the user.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The skill invokes multiple local scripts through subprocess without an explicit warning at the point of execution, despite the documented shell/process access. In this context, natural-language triggers like 'run council' can cause process execution that may read/write the workspace or perform broader actions than the user realizes.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Run a task script in the epistemic_council directory. Returns (returncode, output)."""
    cmd = [sys.executable, str(SKILL_DIR / script_name)] + (extra_args or [])
    try:
        result = subprocess.run(
            cmd,
            capture_output=True,
            text=True,
Confidence
87% confidence
Finding
The skill directly launches local Python scripts via subprocess based on triggerable commands, and the skill metadata states shell access is enabled with READ_WRITE workspace access. While it does not use shell=True and the script names are fixed, this still creates a powerful execution surface where a user message can cause code execution in other files, magnifying risk if those scripts are unsafe or compromised.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The docstring claims safety properties that the implementation does not honor: instead of append-only validated substrate writes, the script directly writes and overwrites files in the workspace via write_text(). Misleading safety documentation can cause operators or downstream agents to trust the tool's write behavior incorrectly, which matters because local files and JSON logs can be replaced or truncated if the script is run repeatedly or in an unexpected workspace context.

Ssd 3

Medium
Confidence
93% confidence
Finding
The code injects up to thousands of characters of raw daily logs into an LLM prompt and then persists the model's summary into MEMORY.md. This creates a data propagation channel where sensitive user-provided content, secrets, or prompt-injected instructions in logs can be reproduced, transformed, or permanently retained in long-term memory artifacts.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The function sends prompt data containing daily logs to an Ollama HTTP service with no sanitization, minimization, or explicit consent flow. Even if hosted on localhost, this can expose sensitive memory contents to another service boundary, and localhost is not a trust boundary if the local model server logs, retains, or is reachable through other local compromise.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
This function constructs new content and writes it directly to MEMORY.md, replacing the file contents. Although the top-level docstring mentions updates to MEMORY.md, there is no confirmation prompt or user-visible warning at the point of execution about modifying a persistent memory file.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The docstring says the function 'Appends new content to existing sections rather than overwriting,' which implies preserving prior MEMORY.md content. However, the implementation builds a fresh string and writes it with MEMORY_MD.write_text(new_content), replacing the existing file contents entirely.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The code persists learning data to a local JSON file automatically and without any consent, notice, retention control, or data minimization. Because the stored structures can include free-text claim content derivatives, validation reasoning, violation terms, and timestamps, this can create an unintended privacy/security exposure if the file contains sensitive user or model-generated information and is later accessed by other users, processes, or logs.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger list includes many generic phrases such as 'run pipeline', 'find analogies', and 'validated insights' that could plausibly appear in ordinary user requests. This raises the chance of unintended invocation of a high-privilege skill, which is especially risky here because the skill also has shell execution and write access.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The manifest grants read/write workspace access, filesystem write targets, and shell execution, but the description does not warn users that activating the skill can modify files or run commands. This creates a permission-transparency gap that can lead to users invoking a powerful skill without understanding the consequences, and the danger is amplified by the broad triggers that may auto-select it unintentionally.

Static analysis

No suspicious patterns detected.