Back to skill

Security audit

Kirk Content Pipeline

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent financial-content workflow, but it deliberately bypasses subagent file-access limits and includes unsafe pickle loading from auto-discovered local state files.

Install only if you trust the publisher and the local RLM state files. Before use, remove or replace the symlink workaround with an explicit approved-file import flow, and avoid running the cache builder on any state.pkl whose origin is not fully trusted. Review final content manually before it is copied to the shared threads folder, especially holdings claims and investment-position language.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:49
Finding
Deliberate Bypass of Subagent File-Access Controls Through Symlinks<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md`, lines 49-67 **Vulnerability Type**: Access-control bypass through project-local symbolic links **Risk Level**: Critical ### Vulnerable Code ```markdown ## Subagent Permissions (CRITICAL) **Subagents CANNOT Read files outside the project directory.** PDFs in `/Users/Shared/ksvc/pdfs/` are blocked. The fix: **symlink PDFs into the project directory** before spawning subagents. **The main agent MUST create a symlink before Step 1a:** ```bash ln -sf "/Users/Shared/ksvc/pdfs/YYYYMMDD" ".claude/pdfs-scan" ``` Then subagents Read from `.claude/pdfs-scan/filename.pdf` — this works because the path resolves inside the project. | Access Method | `/Users/Shared/` path | Symlinked project path | |--------------|----------------------|----------------------| | Subagent Read tool (PDF) | ❌ Auto-denied | ✅ Works | | Subagent Read tool (images) | ❌ Auto-denied | ✅ Works | | Main agent Read tool | ✅ User approves | ✅ Works | | Bash → RLM | ✅ Any path | ✅ Any path | **Discovered 2026-02-07:** Subagents fail with `"Permission to use Read has been auto-denied (prompts unavailable)"` on `/Users/Shared/` paths. Symlink into project dir = full Read access. Tested: 19 PDFs, medium thoroughness, 125k tokens, zero errors. ``` ### Technical Analysis The Skill explicitly recognizes that subagents are denied access to files outside the project directory and then instructs the main agent to bypass that restriction by placing a symbolic link inside the project. The project-local path passes a lexical path check while resolving to data outside the authorized project boundary. This conflicts with least-privilege enforcement. The restriction is not treated as a security boundary requiring authorization; it is treated as an obstacle to route around. The instructions also require the bypass as a mandatory pipeline action, increasing the likelihood that it will be performed automatically. The issue is especially dangerou ...[truncated 1774 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove all instructions that describe symbolic links as a way to defeat an automatic access denial. 2. Require explicit user authorization before importing any file from outside the project boundary. 3. Copy only individually approved PDFs into a dedicated project import directory instead of linking an external directory. 4. Resolve each source and destination with canonical-path operations before access, then verify that the final resolved path remains under an approved root. 5. Reject symbolic links at every path component using appropriate no-follow semantics where supported. 6. Restrict imported files by extension, expected MIME type, size, and filename. 7. Use a manifest listing the exact approved source files rather than exposing an entire date directory. 8. Preserve the original access-control boundary for subagents. If external access is genuinely required, use a platform-supported permission grant rather than a path-resolution workaround. 9. Log the user authorization, canonical source path, destination, and imported-file hash for auditability. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
scripts/build_extraction_cache.py:57
Finding
Arbitrary Code Execution Through Unsafe Pickle Deserialization<![CDATA[ ## Vulnerability Details **File Location**: `scripts/build_extraction_cache.py`, lines 57-114 **Vulnerability Type**: Deserialization of untrusted executable objects **Risk Level**: High ### Vulnerable Code ```python def load_rlm_state(state_path: Path) -> Dict[str, Any]: """Load RLM state.pkl file. Handles v1/v2/v3+ state formats, including LazyContext objects from v3+. """ import sys # Add rlm-v3 scripts to sys.path so pickle can resolve LazyContext class rlm_v3_dir = Path.home() / '.claude/skills/rlm-v3/scripts' if str(rlm_v3_dir) not in sys.path and rlm_v3_dir.exists(): sys.path.insert(0, str(rlm_v3_dir)) try: with open(state_path, 'rb') as f: return pickle.load(f) except (AttributeError, ModuleNotFoundError): # LazyContext class not importable — fall back to loading without it # This means lazy states can't be loaded, but eager states work fine import io class _LazyContextStub: """Stub for when LazyContext can't be imported.""" def __init__(self): self._cache_path = None self._path = 'unknown' self._loaded_at = 'unknown' @property def content(self): import os as _os if self._cache_path and _os.path.isfile(self._cache_path): with open(self._cache_path, 'r') as f: return f.read() return '' @property def path(self): return self._path @property def loaded_at(self): return self._loaded_at def __setstate__(self, state): self._cache_path = state.get('cache_path') self._path = state.get('path', 'unknown') self._loaded_at = state.get('loaded_at', 'unknown') self._metadata = state.get('metadata', {}) class ...[truncated 3022 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Replace pickle state files with a non-executable format such as JSON using a documented, versioned schema. 2. Validate all decoded fields against strict types, sizes, and allowed keys before processing them. 3. Remove automatic selection of the newest state file. Require an explicit state path or select only from a single trusted application-owned directory. 4. Verify that the canonical state path is inside the expected directory and is not a symbolic link. 5. Validate file ownership and reject files writable by untrusted users or groups. 6. Authenticate state files with a keyed MAC or digital signature when they cross trust boundaries. 7. If legacy pickle support is temporarily unavoidable, use a strict allowlist unpickler whose `find_class()` rejects every global except an explicitly enumerated set of inert data classes. Do not delegate unknown classes to `super().find_class()`. 8. Perform any unavoidable legacy conversion in a sandboxed helper process with no network access, minimal filesystem permissions, resource limits, and a disposable working directory. 9. Remove unnecessary `sys.path` mutation during deserialization, or load trusted compatibility classes through a fixed and verified module path. 10. Treat existing pickle files as untrusted during migration and convert only files whose provenance and integrity have been independently verified. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (23)

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The skill advertises a broad end-to-end content pipeline, but the described behavior also includes undeclared access to local state under ~/.claude and pickle deserialization workflows. This mismatch makes risk review harder and can conceal sensitive local-file access or unsafe deserialization behind an apparently benign content-generation purpose.

Ssd 4

High
Confidence
99% confidence
Finding
These instructions provide a concrete step-by-step method for bypassing subagent file-access restrictions by symlinking blocked external PDFs into the project. That is a policy-evasion pattern: it defeats sandbox assumptions and could be repurposed to expose arbitrary external files to less-trusted components.

Context Leakage

High
Category
Data Exfiltration
Content
def extract_context_labels(text: str, window_start: int, window_size: int = 500) -> Dict[str, Any]:
    """
    Extract context labels from text window around a data point.

    Looks for:
    - Product/server type (e.g., "HGX B300 8-GPU server", "GB300 rack")
Confidence
75% confidence
Finding
Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Context Leakage

High
Category
Data Exfiltration
Content
def extract_context_labels(text: str, window_start: int, window_size: int = 500) -> Dict[str, Any]:
    """
    Extract context labels from text window around a data point.

    Looks for:
    - Product/server type (e.g., "HGX B300 8-GPU server", "GB300 rack")
Confidence
75% confidence
Finding
Code or instructions that leak agent conversation context to external services, potentially exposing sensitive user interactions.

Lp3

Medium
Category
MCP Least Privilege
Confidence
83% confidence
Finding
The skill instructs reading and writing files extensively, but it does not declare an explicit tool scope such as allowed-tools or permissions. That creates an authorization ambiguity where an operator may invoke the skill without realizing it can access local files and modify artifacts, increasing the risk of unintended filesystem access.

Vague Triggers

Medium
Confidence
97% confidence
Finding
The trigger phrases are broad enough to invoke the skill during normal conversation, including generic requests like 'create content' or 'make a post.' Broad activation increases the chance of unintended execution of a workflow that reads local files, writes drafts, and may publish outputs.

Intent-Code Divergence

Medium
Confidence
97% confidence
Finding
The skill claims subagent access is confined to the project directory, then instructs creating a symlink so blocked external files appear inside the project. This is effectively guidance to bypass path-based restrictions and undermines the intended trust boundary for subagent file access.

Session Persistence

Medium
Category
Rogue Agent
Content
key_metrics = attr_map['topics'][topic]['key_metrics']
```

**Step 3b: Write using cache labels and attribution:**
```markdown
## Content
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### Step 2b: Query All 7 Models

**NEVER assume a stock isn't held without checking ALL 7 models.**

**RECOMMENDED: Use tradebook for accurate entry prices and current status**
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
### Step 2b: Query All 7 Models

**NEVER assume a stock isn't held without checking ALL 7 models.**

**RECOMMENDED: Use tradebook for accurate entry prices and current status**
Confidence
75% confidence
Finding
Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Context-Inappropriate Capability

Medium
Confidence
93% confidence
Finding
The skill includes an explicit publish-to-final-folder capability, moving from draft generation into deployment of ready-to-post content. Any skill that can publish or stage final outputs in shared locations raises integrity risks, especially if triggered broadly or used without a human approval gate.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
The publishing instructions write into a shared final folder without an explicit warning or required user confirmation at the point of action. That can lead to accidental disclosure, overwrite, or premature staging of content in a location assumed to contain approved material only.

File System Enumeration

Medium
Category
Data Exfiltration
Content
Research PDFs: `/Users/Shared/ksvc/pdfs/`

```bash
ls -la /Users/Shared/ksvc/pdfs/ | tail -5
```

---
Confidence
72% confidence
Finding
The skill includes commands to enumerate filesystem contents under /Users/Shared/ksvc/pdfs. While not inherently malicious, enumeration of local directories reveals environmental structure and can aid broader data discovery beyond the user's immediate request.

Natural-Language Policy Violations

Medium
Confidence
90% confidence
Finding
The style guide explicitly encourages definitive investment-position language such as 'I'm long X because Y' and 'Here's why I'm long...' without requiring hedging, educational framing, or a disclaimer. In a content pipeline designed to generate publishable Twitter posts from research PDFs, this increases the chance of producing prescriptive financial content that could be interpreted as investment advice or manipulative promotion.

Session Persistence

Medium
Category
Rogue Agent
Content
## Workflow Integration

### Step 1b: RLM Extraction (create state.pkl)

**Single PDF:**
```bash
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.

Session Persistence

Medium
Category
Rogue Agent
Content
topic = "memory_pricing"
source_tag = attr_map['topics'][topic]['tag']  # "GFHK"

# Write with attribution
write(f"{source_tag}'s BOM breakdown shows: ...")
# → "GFHK's BOM breakdown shows: ..."
# Not: "Goldman's BOM breakdown" (wrong attribution)
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.

Skill Enumeration

Medium
Category
Agent Snooping
Content
**Fix:**
```bash
# Check if state.pkl exists
ls -la ~/.claude/skills/rlm-repl/scripts/.claude/rlm_state/state.pkl

# If not, run RLM first:
cd ~/.claude/skills/rlm-repl/scripts
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

File System Enumeration

Medium
Category
Data Exfiltration
Content
**Fix:**
```bash
# Check if state.pkl exists
ls -la ~/.claude/skills/rlm-repl/scripts/.claude/rlm_state/state.pkl

# If not, run RLM first:
cd ~/.claude/skills/rlm-repl/scripts
Confidence
60% confidence
Finding
Code scans file system directories looking for sensitive files. This could be reconnaissance for credential theft.

Skill Enumeration

Medium
Category
Agent Snooping
Content
"""
    import sys
    # Add rlm-v3 scripts to sys.path so pickle can resolve LazyContext class
    rlm_v3_dir = Path.home() / '.claude/skills/rlm-v3/scripts'
    if str(rlm_v3_dir) not in sys.path and rlm_v3_dir.exists():
        sys.path.insert(0, str(rlm_v3_dir))
    try:
Confidence
85% confidence
Finding
Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The script performs risky deserialization of a pickle file without any prominent user-facing warning or consent step, so users may treat it as a normal cache-building utility while it can execute code as a side effect of loading state. Because this tool is designed for analyst workflows and automatically searches common state locations, the absence of warning materially raises the likelihood of unsafe use.

Insecure deserialization: pickle.load()

Medium
Category
Dangerous Code Execution
Content
sys.path.insert(0, str(rlm_v3_dir))
    try:
        with open(state_path, 'rb') as f:
            return pickle.load(f)
    except (AttributeError, ModuleNotFoundError):
        # LazyContext class not importable — fall back to loading without it
        # This means lazy states can't be loaded, but eager states work fine
Confidence
99% confidence
Finding
The script deserializes a local state.pkl using pickle.load(), which can execute attacker-controlled code during loading if the pickle file is replaced or poisoned. In this skill, the script auto-discovers state files from multiple predictable locations under the user's home directory, increasing the chance it will consume untrusted or stale data without explicit trust validation.

Missing User Warnings

Low
Confidence
88% confidence
Finding
This markdown file includes commands that create an extraction cache at a user-specified path and later inspect the created file, but it does not warn that the operation writes to disk and may overwrite an existing file. Because SQP-2 applies to markdown files when descriptions omit warnings about behavior affecting user data or system integrity, this is a valid missing-warning issue.

Missing User Warnings

Low
Confidence
93% confidence
Finding
The example explicitly opens `draft/2026-02-05-topic-assets/rlm-extraction-cache.json` in write mode and dumps JSON content, which will create or overwrite the file. The surrounding markdown does not disclose this filesystem impact, so the skill description omits a user warning for a data-affecting operation.

Static analysis

No suspicious patterns detected.