Back to skill

Security audit

soul-guardian

Security checks for vulnerabilities and agentic risk

Overview

This skill mostly matches its stated security purpose, but it has real review-worthy filesystem and install risks that could affect files outside the intended workspace.

Review this skill before installing. It is not clearly malicious, but only use it in workspaces and state directories you control, avoid broad or untrusted `policy.json` entries, prefer the signed-release verification path over the unpinned `npx ... -y` command, set restrictive permissions on the state directory, and inspect any launchd plist before enabling recurring background checks.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/soul_guardian.py:220
Finding

Policy targets can escape the workspace and access or overwrite arbitrary files

Content
View full analysis
Path: # Preserve relative structure under approved/. return state.approved_dir / Path(rel_path) ``` ```python def detect_drift_for(state: GuardianState, baselines: dict[str, Any], relp: str) -> tuple[bool, dict[str, Any]]: abs_path = WORKSPACE_ROOT / relp if not abs_path.exists(): return True, {"error": f"Missing {relp}"} refuse_symlink(abs_path) ``` ```python def restore_one(state: GuardianState, relp: str, info: dict[str, Any]) -> dict[str, Any]: """Restore a single file to its approved snapshot. Returns: extra fields to include in audit. """ abs_path = WORKSPACE_ROOT / relp refuse_symlink(abs_path) approved_snap = approved_snapshot_path(state, relp) if not approved_snap.exists(): raise RuntimeError(f"Missing approved snapshot for {relp}") state.ensure_dirs() ts = dt.datetime.now(dt.timezone.utc).strftime("%Y%m%dT%H%M%SZ") quarantine_path = state.quarantine_dir / f"{safe_patch_tag(relp.replace('/', '_'))}.{ts}.quarantine" atomic_write_bytes(quarantine_path, read_bytes(abs_path)) # Atomic restore. atomic_write_bytes(abs_path, read_bytes(approved_snap)) return {"quarantinePath": str(quarantine_path), **info} ``` ### Technical Analysis Direct paths from `policy.json` are converted to `Path` objects without rejecting: - Absolute paths - Parent-directory components such as `..` - Paths whose canonical destination lies outside `WORKSPACE_ROOT` - Paths traversing a symlinked parent directory Joining `WORKSPACE_ROOT / relp ...[truncated 2526 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/soul_guardian.py:83
Finding

Sensitive guardian state is created without enforced restrictive permissions

Content
View full analysis
None: path.mkdir(parents=True, exist_ok=True) def atomic_write_bytes(path: Path, data: bytes) -> None: ensure_dir(path.parent) tmp = path.with_name(path.name + ".tmp") with tmp.open("wb") as f: f.write(data) f.flush() os.fsync(f.fileno()) os.replace(tmp, path) ``` ```python class GuardianState: def __init__(self, state_dir: Path): self.state_dir = state_dir self.policy_path = state_dir / POLICY_FILE self.baselines_path = state_dir / BASELINES_FILE self.audit_path = state_dir / AUDIT_LOG_FILE self.approved_dir = state_dir / APPROVED_DIRNAME self.patch_dir = state_dir / PATCH_DIRNAME self.quarantine_dir = state_dir / QUAR_DIRNAME def ensure_dirs(self) -> None: ensure_dir(self.state_dir) ensure_dir(self.approved_dir) ensure_dir(self.patch_dir) ensure_dir(self.quarantine_dir) ``` ```python with state.audit_path.open("a", encoding="utf-8") as f: f.write(json.dumps(record, ensure_ascii=False) + "\n") ``` The onboarding helper follows the same pattern: ```python def ensure_dir(p: Path) -> None: p.mkdir(parents=True, exist_ok=True) ``` ```python ensure_dir(external) ``` ### Technical Analysis The guardian stores complete approved snapshots and quarantined copies of agent instruction, user, identity, tool, heartbeat, and memory files. It also stores unified diffs and audit metadata. Directory and file creation relies entirely on the process umask. The code does not: - Create state directories explicitly with mode `0700` - Create state files explicitly with mode `0600` - Correct ...[truncated 2004 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
SKILL.md:19
Finding

Documented installation executes an unpinned package through npx

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
Findings (90)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill provides drift detection and baseline integrity guarding for workspace files with alerting. The supplied code does not implement file integrity monitoring, drift detection, workspace guarding, or alert generation. Instead, it is a test harness for a separate installer script on macOS. It creates temporary HOME directories, invokes install_launchd_plist.py, validates generated plist ProgramArguments, and mocks launchctl/subprocess behavior to test legacy LaunchAgent cleanup and install flows. This is a materially different primary purpose and involves undeclared capabilities related to launchd plist installation/testing.

Content

No source excerpt is available for this finding.

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metad

Ae1

High
Category
analysis-evasion
Confidence
100% confidence
Finding

Referenced artifact was not completely inspected

Content

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

md
For standalone installs, verify the signed release manifest before trusting `SKILL.md`, `skill.json`, or the archive. The `skill.json` file is the package metad

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

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

md
RELEASE_PUBKEY_SHA256="711424e4535f84093fefb024cd1ca4ec87439e53907b305b79a631d5befba9c8"

curl -fsSL "$BASE/checksums.json" -o "$TMP_DIR/checksums.json"
curl -fsSL "$BASE/checksums.sig" -o "$TMP_DIR/checksums.sig"
curl -fsSL "$BASE/signing-public.pem" -o "$TMP_DIR/signing-public.pem"
curl -fsSL "$BASE/$ZIP_NAME" -o "$TMP_DIR/$ZIP_NAME"

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/soul_guardian.py (reported line 711)May include surrounding context.

python
def enable_monitoring_cmd(state: GuardianState) -> None:
    """Output instructions and HEARTBEAT.md snippet for easy monitoring setup."""
    
    heartbeat_snippet = """
## Soul Guardian Check

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · scripts/soul_guardian.py (reported line 971)May include surrounding context.

python
add_common(sp_watch)
    sp_watch.add_argument("--interval", type=int, default=30, help="Check interval in seconds (default: 30).")
    
    sub.add_parser("enable-monitoring", help="Show instructions for enabling automatic monitoring and alerts.")

    return p.parse_args(argv)

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/test_install_launchd_plist.py (reported line 36)May include surrounding context.

python
def run_case(home_dir: Path, agent_id: str) -> subprocess.CompletedProcess:
    env = os.environ.copy()
    env["HOME"] = str(home_dir)
    plist_path = home_dir / "LaunchAgents" / f"{agent_id}.plist"
    cmd = [

Env Variable Harvesting

High
Category
Data Exfiltration
Confidence
60% confidence
Finding

Code enumerates, copies, or searches environment variables for secrets. Bulk environment access can collect credentials unrelated to the skill's stated purpose.

Content

Scanner excerpt · scripts/test_install_launchd_plist.py (reported line 141)May include surrounding context.

python
agent_id,
                "--force",
            ],
            {**os.environ, "HOME": str(home_dir)},
        )
        must_ok(cp)
        assert_contains(cp.stdout, legacy_label, "legacy label dry-run note")

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

Scanner excerpt · CHANGELOG.md (reported line 35)May include surrounding context.

md
### Added

- Regression coverage for launchd label migration so the installer documents and cleans up the previous Clawdbot-era label before starting the new default label.

### Changed

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

Scanner excerpt · CHANGELOG.md (reported line 39)May include surrounding context.

md
### Changed

- `scripts/install_launchd_plist.py` now documents the legacy launchd label/plist in dry-run output and attempts a best-effort disable/bootout of `com.clawdbot.soul-guardian.<agentId>` before installing `com.openclaw.soul-guardian.<agentId>`.
- The `--label` help now explains that non-legacy labels trigger legacy-job cleanup, while explicitly selecting the legacy label skips that migration path.

### Security

Session Persistence

Medium
Category
Rogue Agent
Confidence
80% 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.

Content

Scanner excerpt · CHANGELOG.md (reported line 50)May include surrounding context.

md
### Added

- Regression coverage for launchd state-directory selection so existing legacy installs keep using their current guardian state unless the operator explicitly chooses a new location.

### Changed

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

The skill explicitly recommends storing state outside the workspace and maintaining approved snapshots, audit logs, patches, and quarantined copies over time. That creates durable persistence of sensitive workspace content and operational metadata; if the external state directory is exposed or shared incorrectly, an attacker or another user could recover prompts, memory, or prior file contents even after they were removed from the workspace.

Content

Scanner excerpt · README.md (reported line 79)May include surrounding context.

md
### First run / Initialize baselines (recommended)

For resilience, create your guardian **state directory outside** the workspace first, then initialize baselines.

1) Onboard an external state dir (creates policy, copies any existing state, prints paths/snippets):

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · README.md (reported line 200)May include surrounding context.

md
Notes:
- `<agentId>` should be **stable and unique per workspace** (don’t point multiple workspaces at the same state dir).
- WARNING: `--move` deletes the old in-workspace state dir after copying.
- The external state dir can contain **approved snapshots, patches, and quarantined copies** of sensitive prompt/instruction/memory files. Keep permissions restrictive (e.g., `chmod 700 <dir>`; `chmod go-rwx <dir>`).

Then include `--state-dir` in all commands (run from the workspace root), e.g.:

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

The README documents generation of a LaunchAgent plist for periodic execution, which is a persistence mechanism on macOS. While presented as an optional scheduling feature, it establishes recurring execution in the user's session and can be abused if installed without clear review or if the monitored script/path is later modified by an attacker.

Content

Scanner excerpt · README.md (reported line 237)May include surrounding context.

Because soul_guardian.py check prints nothing on OK and prints a single-line SOUL_GUARDIAN_DRIFT ... summary on drift, this tends to be silent unless something changed.

Generate + (optionally) install a LaunchAgent plist (run from the workspace root, or pass --workspace-root):

bash
python3 skills/soul-guardian/scripts/install_launchd_plist.py \

Session Persistence

Medium
Category
Rogue Agent
Confidence
93% confidence
Finding

The --install example goes beyond merely generating a plist and directly installs a persistent LaunchAgent. In a security-sensitive agent environment, auto-installable persistence increases risk because it can create long-lived background execution that survives normal session boundaries and may repeatedly process sensitive files.

Content

Scanner excerpt · README.md (reported line 240)May include surrounding context.

Generate + (optionally) install a LaunchAgent plist (run from the workspace root, or pass --workspace-root):

bash
python3 skills/soul-guardian/scripts/install_launchd_plist.py \
  --state-dir ~/.openclaw/soul-guardian/<agentId> \
  --interval-seconds 600 \
  --install

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
86% confidence
Finding

The skill advertises executable commands that read and write workspace files, invoke Python and shell, and potentially inspect environment-derived paths, but it declares no explicit tool scope or permissions boundary. In an agent ecosystem, this increases the chance that the harness or user grants broader-than-necessary capabilities, making unintended file modification or command execution easier.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
84% confidence
Finding

The installation instruction uses npx skills add ... without pinning a specific CLI/package version, so the installer may resolve to whatever version is current at execution time. That creates a supply-chain risk where behavior changes or a compromised upstream package could affect installation before the skill's own verification steps are even reached.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

The file-level purpose is to generate and optionally install a macOS launchd plist, which is inherently a persistence artifact. In normal administration this can be legitimate, but from a security-analysis perspective it is a real persistence capability that would be valuable to an attacker if the skill or workspace were compromised.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 2)May include surrounding context.

python
#!/usr/bin/env python3
"""Generate (and optionally install) a macOS launchd plist for soul-guardian.

Goal:
- Run `soul_guardian.py check` on an interval.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 59)May include surrounding context.

python
def run_launchctl(args: list[str]) -> subprocess.CompletedProcess[str]:
    return subprocess.run(["/bin/launchctl", *args], check=False, text=True, capture_output=True)


def cleanup_legacy_launchd(uid: int, active_label: str, agent_id: str) -> list[str]:

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

This code path is part of a script that creates and can install a launchd LaunchAgent, which establishes periodic execution and therefore persistence in the user's session. Even though the stated purpose is legitimate monitoring, persistence mechanisms are security-sensitive because they can be repurposed to maintain ongoing execution of altered code in the workspace.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 127)May include surrounding context.

python
ap.add_argument(
        "--label",
        default=None,
        help="launchd label (default: com.openclaw.soul-guardian.<agentId>). When using a non-legacy label, --install attempts to disable/boot out the previous com.clawdbot.soul-guardian.<agentId> job first.",
    )
    ap.add_argument(
        "--interval-seconds",

Session Persistence

Medium
Category
Rogue Agent
Confidence
88% confidence
Finding

Allowing the caller to choose an output path for the generated plist supports writing persistence artifacts to LaunchAgents or other locations. In context this is administrative functionality, but it expands the persistence surface if used carelessly or by a malicious operator.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 140)May include surrounding context.

python
ap.add_argument(
        "--out",
        default=None,
        help="Write plist to this path (default: ~/Library/LaunchAgents/<label>.plist)",
    )
    ap.add_argument("--force", action="store_true", help="Overwrite existing plist on disk.")
    ap.add_argument(

Session Persistence

Medium
Category
Rogue Agent
Confidence
96% confidence
Finding

The --install option explicitly loads the generated plist with launchctl, which creates active session persistence. This is intended behavior for the skill, but it is still a real persistence capability that deserves scrutiny and user awareness.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 146)May include surrounding context.

python
ap.add_argument(
        "--install",
        action="store_true",
        help="Install+load the plist with launchctl (bootstrap). Without this flag we only write the plist.",
    )

    args = ap.parse_args(argv)

Session Persistence

Medium
Category
Rogue Agent
Confidence
90% confidence
Finding

Constructing the default plist path under ~/Library/LaunchAgents targets the standard per-user persistence location on macOS. That location choice is appropriate for launchd, but it is inherently persistence-enabling and therefore security-sensitive.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 170)May include surrounding context.

python
label = args.label or default_label(agent_id)
    legacy_job_label = legacy_label(agent_id)
    legacy_job_plist = legacy_plist_path(agent_id).expanduser().resolve()
    plist_path = Path(args.out).expanduser().resolve() if args.out else (Path("~/Library/LaunchAgents").expanduser() / f"{label}.plist")

    script_path = workspace_root / "skills" / "soul-guardian" / "scripts" / "soul_guardian.py"

Session Persistence

Medium
Category
Rogue Agent
Confidence
95% confidence
Finding

This is where the plist structure is assembled, including ProgramArguments and StartInterval, which define a recurring LaunchAgent job. That is a concrete persistence artifact, and if the workspace script is modified after installation, the persistent job will execute the altered code repeatedly.

Content

Scanner excerpt · scripts/install_launchd_plist.py (reported line 195)May include surrounding context.

python
str(args.note),
    ]

    plist: dict[str, object] = {
        "Label": label,
        "ProgramArguments": program_args,
        "WorkingDirectory": str(workspace_root),

Static analysis

Detected: suspicious.dynamic_code_execution

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
scripts/test_install_launchd_plist.py:66