Back to skill

Security audit

Soul Petition Gate

Security checks for vulnerabilities and agentic risk

Overview

The skill has a coherent human-review purpose, but its included backend does not enforce that only a human can approve or roll back persistent identity-file changes.

Review before installing. Do not expose this blueprint on any reachable network as-is. Add authentication, reviewer/admin authorization, CSRF protection if browser cookies are used, strict rollback allowlisting based on server-side backup metadata, unique petition IDs, and atomic storage before using it to modify persistent agent identity files.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/soul_petition_routes.py:151
Finding

Missing Authentication and Authorization on Human-Only Administrative Endpoints

Content
View full analysis
/approve", methods=["POST"]) def approve_petition(petition_id): store = _load_store() petition = next((p for p in store["petitions"] if p["petition_id"] == petition_id), None) if not petition: return jsonify({"error": "not_found"}), 404 if petition["status"] != "pending": return jsonify({"error": "not_pending", "status": petition["status"]}), 400 target = SOUL_FILES_DIR / petition["file"] if not target.exists(): return jsonify({"error": "target_file_not_found", "file": petition["file"]}), 500 content = target.read_text(encoding="utf-8") if petition["before"] not in content: return ( jsonify( { "error": "before_text_not_found", "message": "The 'before' text was not found in the file. It may have already changed.", } ), 409, ) # Backup BACKUP_DIR.mkdir(parents=True, exist_ok=True) ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S") backup_name = f"{petition['file']}.{ts}.bak" backup_path = BACKUP_DIR / backup_name shutil.copy2(target, backup_path) # Apply new_content = content.replace(petition["before"], petition["after"], 1) target.write_text(new_content, encoding="utf-8") # Update store petition["status"] = "approved" petition["reviewed_at"] = _now() petition["reviewer"] = "coach" petition["backup_file"] = backup_name _save_store(store) return jsonify({"ok": True, "backup_file": backup_name}) ``` The rejection and rollback endpoints are similarly exposed without access-control checks: ```python @soul_petition_ ...[truncated 2918 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Error
Location
assets/soul_petition_routes.py:223
Finding

Arbitrary Destination File Overwrite Through Rollback Path Traversal

Content
View full analysis
= 2 else parts[0] target = SOUL_FILES_DIR / target_name # Backup current before rollback ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S") pre_rollback_backup = BACKUP_DIR / f"{target_name}.pre_rollback_{ts}.bak" if target.exists(): shutil.copy2(target, pre_rollback_backup) shutil.copy2(backup_path, target) return jsonify({"ok": True, "restored": target_name, "pre_rollback_backup": pre_rollback_backup.name}) ``` ### Technical Analysis The source backup path uses `Path(backup_file).name`, which removes directory components. However, the destination filename is independently calculated from the original, unsanitized `backup_file` value: ```python parts = backup_file.split(".") target_name = parts[0] + "." + parts[1] target = SOUL_FILES_DIR / target_name ``` Directory traversal sequences contained in the first components remain present in `target_name`. When a relative path containing `..` is joined to `SOUL_FILES_DIR`, filesystem resolution can escape the configured workspace. The source and destination therefore have inconsistent validation: the source can resolve to a legitimate existing backup b ...[truncated 1962 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
assets/soul_petition_routes.py:47
Finding

Collision-Prone Petition IDs and Non-Atomic JSON Store Updates

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (10)

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The rollback route accepts a backup filename and infers the destination filename from it, but does not verify that the destination is one of the protected soul files. In this skill's context, that is especially dangerous because the feature is marketed as safely mediating edits to a narrow set of protected identity files, yet rollback can overwrite broader workspace files and break that trust boundary.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
94% confidence
Finding

The skill describes capabilities that read and write files, create directories, and mount a backend route, but it does not declare any explicit tool scope or permissions boundary in the skill metadata. That omission increases the chance the host grants broader-than-necessary filesystem access, especially risky here because the skill handles protected identity files and approval/rollback artifacts.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
86% confidence
Finding

The skill intentionally introduces persistent state about the agent across sessions by storing petitions, audit history, and approved identity changes. While persistence is the feature, it also creates a durable channel for long-term prompt/identity shaping; if review or backend controls are weak, harmful self-modification requests can accumulate and eventually be approved or replayed.

Content

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

md
## Why this exists

Most agent frameworks treat soul/identity files as read-only from the agent's
perspective, or give the agent unrestricted write access. Both extremes are
wrong.

**Read-only forever** means the agent can never grow. It accumulates experience

Tainted flow: 'PETITION_STORE_PATH' from os.environ.get (line 33, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/soul_petition_routes.py (reported line 54)May include surrounding context.

python
def _load_store():
    PETITION_STORE_PATH.parent.mkdir(parents=True, exist_ok=True)
    if not PETITION_STORE_PATH.exists():
        PETITION_STORE_PATH.write_text('{"petitions": []}', encoding="utf-8")
    return json.loads(PETITION_STORE_PATH.read_text(encoding="utf-8"))

Tainted flow: 'PETITION_STORE_PATH' from os.environ.get (line 33, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/soul_petition_routes.py (reported line 59)May include surrounding context.

python
def _save_store(store):
    PETITION_STORE_PATH.write_text(
        json.dumps(store, ensure_ascii=False, indent=2), encoding="utf-8"
    )

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The listing and retrieval endpoints expose full petition bodies, including before/after text and reasoning, with no authentication or authorization checks. That can leak sensitive prompts, identity material, or protected file content to any caller who can reach the service, which is more dangerous here because the petitions are specifically about proposed changes to high-value soul and identity files.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The approve and rollback routes overwrite protected files and copy backups, which are safety-critical file write operations. While the module docstring describes configuration paths, there is no confirmation prompt, user-facing log/print, or explicit warning in comments/docstrings near these endpoints that invoking them will modify or restore files on disk.

Content

No source excerpt is available for this finding.

Tainted flow: 'target' from os.environ.get (line 238, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/soul_petition_routes.py (reported line 182)May include surrounding context.

python
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S")
    backup_name = f"{petition['file']}.{ts}.bak"
    backup_path = BACKUP_DIR / backup_name
    shutil.copy2(target, backup_path)

    # Apply
    new_content = content.replace(petition["before"], petition["after"], 1)

Tainted flow: 'target' from os.environ.get (line 238, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
95% confidence
Finding

During rollback, the code creates a pre-rollback backup of whatever target path was inferred from the user-supplied backup filename. Because that target is not limited to protected files, an attacker can cause the service to read and copy arbitrary workspace files, compounding the arbitrary-restore issue and exposing unintended file operations.

Content

Scanner excerpt · assets/soul_petition_routes.py (reported line 244)May include surrounding context.

python
ts = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H-%M-%S")
    pre_rollback_backup = BACKUP_DIR / f"{target_name}.pre_rollback_{ts}.bak"
    if target.exists():
        shutil.copy2(target, pre_rollback_backup)

    shutil.copy2(backup_path, target)

Tainted flow: 'backup_path' from os.environ.get (line 231, credential/environment) → shutil.copy2 (file write)

Medium
Category
Data Flow
Confidence
97% confidence
Finding

The rollback endpoint restores a backup into a target path derived from the attacker-supplied backup filename, and the computed target is not checked against the protected-file allowlist. That enables restoration over arbitrary workspace files with dotted names, which can overwrite application or agent files outside the stated soul-file scope and undermine integrity.

Content

Scanner excerpt · assets/soul_petition_routes.py (reported line 246)May include surrounding context.

python
if target.exists():
        shutil.copy2(target, pre_rollback_backup)

    shutil.copy2(backup_path, target)

    return jsonify({"ok": True, "restored": target_name, "pre_rollback_backup": pre_rollback_backup.name})