Back to skill

Security audit

Decision Topology

Security checks for vulnerabilities and agentic risk

Overview

This skill transparently records short local conversation summaries as decision trees, with no evidence of network exfiltration or deceptive behavior, but it does persist data automatically.

Install only if you are comfortable with the agent automatically saving short conversation-derived topics, summaries, rejection reasons, and concepts to local JSON and Markdown files. Use a private storage directory, avoid directories writable by other users, disable always-on mode if you want explicit invocation only, and periodically review or delete stored trees if conversations may be sensitive.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/topology.js:20
Finding
Symlink-Based Filesystem Containment Bypass<![CDATA[ ## Vulnerability Details **File Location**: `scripts/topology.js:20-31`, with unsafe filesystem operations at `scripts/topology.js:180-199` **Vulnerability Type**: Improper link resolution before file access **Risk Level**: Medium ### Vulnerable Code ```js // Canonicalize TREES_DIR once at startup for path containment checks. const TREES_DIR_REAL = fs.realpathSync(TREES_DIR); const CONCEPT_INDEX_PATH = path.join(TREES_DIR_REAL, 'concepts.json'); // Resolve a user-supplied file path and enforce that it stays inside TREES_DIR. // Rejects absolute paths, ".." traversal, and symlinks that escape the directory. function resolveSafePath(file) { const resolved = path.resolve(TREES_DIR_REAL, path.basename(file)); if (!resolved.startsWith(TREES_DIR_REAL + path.sep) && resolved !== TREES_DIR_REAL) { console.error(`Error: Path escapes trees directory: ${file}`); process.exit(1); } return resolved; } ``` The resulting path is subsequently used for reads and writes without validating the final target: ```js function loadTree(file) { // Strip to basename and resolve inside TREES_DIR to prevent path traversal. const filePath = resolveSafePath(file); if (!fs.existsSync(filePath)) { console.error(`Error: Tree file not found: ${filePath}`); process.exit(1); } return { tree: JSON.parse(fs.readFileSync(filePath, 'utf8')), filePath }; } function saveTree(filePath, tree) { tree.updated = new Date().toISOString(); // Update concept index, then use it for weights and companion links const index = updateConceptIndexForTree(filePath, tree); // Update node weights from cross-tree spread updateWeights(tree, index); fs.writeFileSync(filePath, JSON.stringify(tree, null, 2)); writeMdCompanion(filePath, tree, index); } ``` ### Technical Analysis The containment check validates only the lexical path produced by joining the canonical trees directory with `path.basename(file)`. It does not canonicalize the resulting existing fil ...[truncated 2686 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Reject symbolic-link inputs before reading: ```js const stat = fs.lstatSync(candidate); if (stat.isSymbolicLink()) { throw new Error('Symbolic-link tree files are not allowed'); } ``` 2. For an existing input file, canonicalize the final target and verify containment: ```js const candidate = path.resolve(TREES_DIR_REAL, path.basename(file)); const realTarget = fs.realpathSync(candidate); if (!realTarget.startsWith(TREES_DIR_REAL + path.sep)) { throw new Error('Tree target escapes the configured directory'); } ``` 3. Require tree files to be regular files rather than directories, devices, FIFOs, or sockets: ```js if (!fs.statSync(realTarget).isFile()) { throw new Error('Tree target is not a regular file'); } ``` 4. Reduce check-to-use race exposure by opening verified files through file descriptors and using no-follow semantics where supported. On platforms exposing the relevant constants, use `O_NOFOLLOW` when opening files. 5. Avoid writing directly through a user-selectable existing pathname. Serialize to a newly created temporary regular file inside `TREES_DIR_REAL`, flush and close it, verify the destination policy, and atomically rename it into place. Ensure temporary files are created with exclusive creation and restrictive permissions. 6. Apply equivalent link and file-type validation to every file enumerated by `getAllTrees()`, because directory scans can also encounter attacker-created symbolic links. 7. Add regression tests covering: - A symlink inside the trees directory targeting a file outside it. - A symlink targeting another file inside the directory. - Replacement of a validated file with a symlink between validation and use. - Non-regular filesystem objects with a `.json` suffix. - Read and mutating commands against each prohibited target type. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (11)

Tool Parameter Abuse

High
Category
Tool Misuse
Content
Clean up after testing:
```bash
rm -rf /tmp/test-trees
```

## What to Contribute
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Tool Parameter Abuse

High
Category
Tool Misuse
Content
Clean up after testing:
```bash
rm -rf /tmp/test-trees
```

## What to Contribute
Confidence
90% confidence
Finding
Tool parameters are crafted to achieve unintended or unsafe behavior. Parameter abuse can bypass intended safety checks (e.g. shell=True, --force, dangerous glob patterns).

Credential Access

High
Category
Privilege Escalation
Content
**Path containment is enforced at runtime.** At startup, the trees directory is canonicalized via `fs.realpathSync()`. Every user-supplied `file` argument is stripped to its basename with `path.basename()` and then resolved inside the canonical trees directory. This means:

- Absolute paths like `/etc/passwd` are rejected (basename extraction produces `passwd`, which won't exist in trees dir).
- Relative traversal like `../../etc/shadow` is rejected (basename extraction produces `shadow`).
- Symlinks are resolved by `realpathSync` — a symlink inside the trees directory that points outside it would need to already exist on disk, which the script never creates.
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
**Path containment is enforced at runtime.** At startup, the trees directory is canonicalized via `fs.realpathSync()`. Every user-supplied `file` argument is stripped to its basename with `path.basename()` and then resolved inside the canonical trees directory. This means:

- Absolute paths like `/etc/passwd` are rejected (basename extraction produces `passwd`, which won't exist in trees dir).
- Relative traversal like `../../etc/shadow` is rejected (basename extraction produces `shadow`).
- Symlinks are resolved by `realpathSync` — a symlink inside the trees directory that points outside it would need to already exist on disk, which the script never creates.

The `resolveSafePath()` function additionally verifies the resolved path starts with the canonical trees directory prefix before any read or write occurs. If the check fails, the script exits with an error.
Confidence
95% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
94% confidence
Finding
The declared purpose emphasizes local JSON tree recording, but the documented behavior also includes cross-tree search, global analysis, concept indexing, and regeneration of companion Markdown files across all trees. This mismatch matters because users may consent to lightweight note persistence without realizing the skill performs broader aggregation and secondary artifact creation, which expands the privacy and data-retention footprint.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The README states that the skill persistently records conversation structure to local JSON files and companion markdown files, but it does not provide a clear user-facing warning about retention, sensitivity of captured content, or consent. Even without network access, always-on local logging can expose sensitive prompts, decisions, and internal reasoning to other local users, backups, indexing tools, or later unintended access.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
Documenting the skill as active during every conversation by default materially increases privacy risk because users may not realize continuous recording is occurring across unrelated chats. In this skill's context, the danger is elevated by persistent storage, cross-tree analysis, and companion .md generation, which increase the amount of retained conversational metadata and the chance of secondary exposure.

Lp3

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding
The skill uses environment-controlled behavior (`TOPOLOGY_TREES_DIR`) and runtime file operations but does not declare an explicit tool scope or permissions boundary. That makes the actual capability surface broader than what a reviewer can infer from metadata, increasing the risk of unintended file persistence or storage in sensitive locations if the host environment exposes permissive defaults.

Vague Triggers

Medium
Confidence
94% confidence
Finding
The activation criteria are broad enough to trigger during many normal brainstorming or problem-solving conversations, and the skill is marked `always: true`. In context, that means conversation-derived summaries may be persisted automatically and repeatedly without a narrowly bounded trigger, creating an over-collection/privacy risk even if content length is truncated.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The skill persistently stores conversation-derived data in the background, yet the user-facing description does not prominently warn that summaries, reasons, and concepts are auto-written to disk during ordinary use. This is a meaningful transparency failure because users may reveal sensitive information assuming a normal chat flow, not silent local logging and indexing.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- Score 0.25-0.4 — ambiguous. Ask the user naturally: "This feels related to [topic] we explored on [date]. Continuing that thread, or fresh start?"
- Score < 0.25 — new tree

Never ask the user to pick a tree by ID. If you need to disambiguate, ask naturally in conversation.

## Setup
Confidence
80% 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.

Static analysis

No suspicious patterns detected.