Back to skill

Security audit

Server

Security checks for vulnerabilities and agentic risk

Overview

This server-administration skill is mostly coherent, but one hardening command can scan the whole host for credential-like files instead of only the intended web roots.

Review before installing if you may run it on shared or privileged hosts. The main issue is the hardening sweep command that starts at `/`; constrain it to known document roots or service directories before use. Also be aware that the skill maintains local operational notes under `~/Clawic/data/` and may update or delete rows it wrote as part of normal maintenance, while explicitly avoiding secret values.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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)

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
security.md:16
Finding
Unrestricted Filesystem-Wide Credential File Reconnaissance## Vulnerability Details **File Location**: `security.md:16` **Vulnerability Type**: Excessive filesystem access and sensitive-file discovery **Risk Level**: Medium ### Vulnerable Code ```sh find / -name '.env' -o -name '*.pem' -o -name 'id_*' 2>/dev/null ``` The surrounding instruction describes this as a search “under the web roots,” but the command starts at the filesystem root (`/`). ### Technical Analysis Starting `find` at `/` traverses every readable filesystem and mount rather than only the service's document roots. The filename patterns specifically target files likely to contain credentials or cryptographic material: - `.env` files may contain passwords, API tokens, and connection strings. - `*.pem` files may contain private keys or certificates. - `id_*` commonly matches SSH private and public keys. The command does not read file contents or escalate privileges, but it can disclose the locations of unrelated users' and services' sensitive files. Redirecting standard error to `/dev/null` suppresses permission failures and makes the excessive traversal less apparent. This exceeds the minimum access needed to determine whether secrets are exposed through a particular web root. The search should be constrained to confirmed document roots and release directories belonging to the service under review. ### Attack Path 1. A user requests a service-hardening or exposure review. 2. The Agent follows the exposure-sweep instructions in `security.md`. 3. The Agent executes the provided `find / ...` command with the permissions available to the current account. 4. The command traverses all readable system locations and mounted filesystems. 5. Paths to unrelated environment files, PEM material, and SSH-key files are returned. 6. Those sensitive paths may enter the Agent context, terminal history, audit logs, or subsequent troubleshooting workflows. 7. A later unsafe action or compromised workflow could use this reconnaissance to target credential material ...[truncated 819 chars]
Remediation
## Remediation Suggestions 1. Replace `/` with the service's explicitly confirmed web root or document root: ```sh find /srv/api/current/public -xdev \ \( -name '.env' -o -name '*.pem' -o -name 'id_*' \) -print ``` 2. If multiple web roots exist, enumerate each approved path explicitly rather than searching the entire host. 3. Use `-xdev` to prevent traversal into unrelated mounted filesystems. 4. Obtain explicit user confirmation before expanding the search beyond known service directories. 5. Avoid searching user home directories, `/etc`, secret-manager mounts, backup locations, and other applications' directories unless they are specifically in scope. 6. Treat discovered paths as sensitive operational metadata. Do not persist them in Skill memory, artifacts, reports, or shared inventory unless strictly necessary. 7. Update the instruction text so the described scope and executed command match exactly.
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
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
Findings (29)

Credential Access

High
Category
Privilege Escalation
Content
app:
    image: ghcr.io/acme/app@sha256:9f2c1d...   # digest, not a moving tag
    restart: unless-stopped
    env_file: .env                              # 0600, never committed
    depends_on:
      db:
        condition: service_healthy
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Docker Socket Access

High
Category
Privilege Escalation
Content
- **Bind mounts** for data you want to see and back up by path (media, uploads, config).
- **UID mismatch is the number one bind-mount problem**: the container writes as UID 10001, the host directory is owned by 1000, and the container gets permission denied — or worse, creates root-owned files the host user cannot delete. Set `user:` to the host owner's UID:GID, or `chown` the directory to the container's UID. Many self-hosted images accept `PUID`/`PGID` for exactly this.
- `:ro` on anything the container does not need to write, config files above all.
- **Never bind-mount the container socket** (`/var/run/docker.sock`) into a service that does not need it: access to it is root on the host. Proxies with a docker provider do need it — mount it read-only and understand that read-only does not meaningfully reduce that risk.
- A named volume is not a backup. It lives on the same disk, and `down -v` deletes it (`maintenance.md`).

## Depends On Is Not Ready
Confidence
90% confidence
Finding
Potential security issue detected. Manual review is recommended.

Credential Access

High
Category
Privilege Escalation
Content
Nothing under `~/Clawic/data/` ever holds a secret value — not the files named here, not files you create, not text the user pastes in and asks you to keep. This matters most for the files nobody planned: a runbook carries the connection string, a "config that finally worked" carries the token, a pasted `.env` is nothing but secrets. Store the pointer in its place, in this shape: `<kind>:<locator>`.

`env:DATABASE_URL` · `file:/etc/myapp/env` · `file:~/.ssh/id_ed25519` · `keychain:deploy-key` · `1password:Infra/prod-db` · `bitwarden:servers/api-token` · `vault:secret/prod/api` · `profile:deploy`

When the user pastes something to save, replace each secret value before writing and leave the pointer visible: `DATABASE_URL=<env:DATABASE_URL>`, `ssl_certificate_key <file:/etc/letsencrypt/live/example.com/privkey.pem>;`. Say in one line that you did it.
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
Nothing under `~/Clawic/data/` ever holds a secret value — not the files named here, not files you create, not text the user pastes in and asks you to keep. This matters most for the files nobody planned: a runbook carries the connection string, a "config that finally worked" carries the token, a pasted `.env` is nothing but secrets. Store the pointer in its place, in this shape: `<kind>:<locator>`.

`env:DATABASE_URL` · `file:/etc/myapp/env` · `file:~/.ssh/id_ed25519` · `keychain:deploy-key` · `1password:Infra/prod-db` · `bitwarden:servers/api-token` · `vault:secret/prod/api` · `profile:deploy`

When the user pastes something to save, replace each secret value before writing and leave the pointer visible: `DATABASE_URL=<env:DATABASE_URL>`, `ssl_certificate_key <file:/etc/letsencrypt/live/example.com/privkey.pem>;`. Say in one line that you did it.
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
1. `ss -tlnp` — every listener. Anything on `0.0.0.0` that is not the proxy is a finding: databases, admin UIs, metrics endpoints, message brokers, the app itself.
2. `iptables -S` / `nft list ruleset` — the rules actually in force, including the ones a container runtime inserted (below). `ufw status` is a summary of one tool's intentions, not the kernel's state.
3. From another machine: a port scan of the public IP. What answers from outside is the only exposure that counts, and it regularly differs from what the firewall config implies.
4. `find / -name '.env' -o -name '*.pem' -o -name 'id_*' 2>/dev/null` under the web roots — a secret inside a served directory is a download, not a secret (`static.md`).

An app bound to `0.0.0.0:8080` behind a proxy is reachable directly on 8080: TLS bypassed, forwarded-header trust bypassed, rate limits bypassed, and auth done at the proxy bypassed entirely. Bind loopback or a Unix socket (SKILL.md Rule 7).
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
- Self-hosted git (Forgejo, Gitea, GitLab) needs SSH as well as HTTPS: either a second host port for SSH or the proxy's stream module for TCP passthrough, and the app's advertised clone URL must match whichever you chose or every copied clone command is wrong.
- CI runners execute arbitrary code by design. Never on the same box as anything valuable, never with the container socket mounted, and with a disk they are allowed to fill.
- GitLab's resting memory footprint is in gigabytes; Forgejo and Gitea are in hundreds of megabytes. On a small box this is the entire decision.
- Registry storage grows without limit unless a retention policy exists — and it is a disk-full outage that arrives with no warning.

## Game Servers
Confidence
80% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Tool Parameter Abuse

High
Category
Tool Misuse
Content
For service-to-service traffic on a private network, a private CA (or the platform's built-in issuer) beats public certificates: no rate limits, no public DNS requirement, and the trust store is yours. The cost is distributing the CA bundle to every client and rotating it — which is real work, so only take it on when the network is genuinely untrusted or a compliance regime requires it.

Never disable verification to make it work. `insecure_skip_verify`, `curl -k`, and `verify=False` turn TLS into obfuscation, and they are permanent: nobody comes back to remove them.

## mTLS
Confidence
60% 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).

Session Persistence

Medium
Category
Rogue Agent
Content
| `unless-stopped` | Restart always, **except** if you stopped it deliberately — the right default for services |
| `always` | Restarts even after a deliberate stop when the daemon restarts, which surprises people mid-maintenance |

`unless-stopped` only survives a reboot if the container runtime's own service is enabled at boot. On a systemd host: `systemctl is-enabled docker`. This is the container equivalent of `systemctl enable` and it is skipped just as often (`processes.md`).

## Publishing Ports Safely
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| `unless-stopped` | Restart always, **except** if you stopped it deliberately — the right default for services |
| `always` | Restarts even after a deliberate stop when the daemon restarts, which surprises people mid-maintenance |

`unless-stopped` only survives a reboot if the container runtime's own service is enabled at boot. On a systemd host: `systemctl is-enabled docker`. This is the container equivalent of `systemctl enable` and it is skipped just as often (`processes.md`).

## Publishing Ports Safely
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| `unless-stopped` | Restart always, **except** if you stopped it deliberately — the right default for services |
| `always` | Restarts even after a deliberate stop when the daemon restarts, which surprises people mid-maintenance |

`unless-stopped` only survives a reboot if the container runtime's own service is enabled at boot. On a systemd host: `systemctl is-enabled docker`. This is the container equivalent of `systemctl enable` and it is skipped just as often (`processes.md`).

## Publishing Ports Safely
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.

Session Persistence

Medium
Category
Rogue Agent
Content
## Reboots

- Before: check that everything is `enabled`, not merely running (`systemctl list-unit-files --state=enabled`), that the container runtime is enabled, and that nothing critical is running from a shell (`processes.md`).
- After: verify each service in `## Services` answers, not just that the box pings. The whole point of a reboot test is to discover the service somebody started by hand in March.
- A scheduled reboot in the maintenance window is far cheaper than an unplanned one at peak. A box with an uptime of 400 days is not a boast; it is an untested boot sequence carrying an unpatched kernel.
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.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The template explicitly authorizes file writes and deletions without user permission, with only a minimal one-line announcement. In an agent setting, that weakens user control over destructive actions and can lead to unintended loss or tampering of stored operational data, especially in shared files used by multiple skills.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The split procedure directs the agent to delete sections from memory.md as part of normal maintenance, but does not require a strong warning, confirmation, or rollback mechanism. That creates a real risk of accidental data loss, corruption, or inconsistent state if the move is interrupted or performed incorrectly.

Session Persistence

Medium
Category
Rogue Agent
Content
WantedBy=multi-user.target
```

Apply: `systemctl daemon-reload`, then `systemctl enable --now api`. **`enable` is what makes it start at boot; `start` alone does not** — the most common way a service "randomly disappears" months later is that only `start` was ever run and the box finally rebooted. Verify with `systemctl is-enabled api`, never by assumption.

Validate before installing: `systemd-analyze verify /etc/systemd/system/api.service` catches typos in directive names that systemd otherwise ignores silently — an unknown directive is a warning in the journal, not an error, so a misspelled `Restart=on-faliure` means no restart at all and nothing tells you.
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.

Session Persistence

Medium
Category
Rogue Agent
Content
WantedBy=multi-user.target
```

Apply: `systemctl daemon-reload`, then `systemctl enable --now api`. **`enable` is what makes it start at boot; `start` alone does not** — the most common way a service "randomly disappears" months later is that only `start` was ever run and the box finally rebooted. Verify with `systemctl is-enabled api`, never by assumption.

Validate before installing: `systemd-analyze verify /etc/systemd/system/api.service` catches typos in directive names that systemd otherwise ignores silently — an unknown directive is a warning in the journal, not an error, so a misspelled `Restart=on-faliure` means no restart at all and nothing tells you.
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.

Session Persistence

Medium
Category
Rogue Agent
Content
WantedBy=multi-user.target
```

Apply: `systemctl daemon-reload`, then `systemctl enable --now api`. **`enable` is what makes it start at boot; `start` alone does not** — the most common way a service "randomly disappears" months later is that only `start` was ever run and the box finally rebooted. Verify with `systemctl is-enabled api`, never by assumption.

Validate before installing: `systemd-analyze verify /etc/systemd/system/api.service` catches typos in directive names that systemd otherwise ignores silently — an unknown directive is a warning in the journal, not an error, so a misspelled `Restart=on-faliure` means no restart at all and nothing tells you.
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.

Unbounded Resource Access

Medium
Category
Excessive Agency
Content
| `MemoryHigh=1500M` | Throttles before killing — an app that slows down is easier to diagnose than one that vanishes |
| `TasksMax=4096` | A thread or fork leak takes the whole box's PID space otherwise |
| `CPUQuota=200%` | Two cores' worth; useful to keep a batch job from starving the web path |
| `OOMPolicy=stop` | Do not restart into the same OOM loop forever |

`systemctl show api -p MemoryMax,LimitNOFILE` prints what is actually in force — read it rather than the file, because a drop-in in `/etc/systemd/system/api.service.d/` may be overriding you.
Confidence
75% confidence
Finding
Skill allows unbounded resource consumption (API calls, storage, compute). Without rate limits or quotas, a compromised or misbehaving agent can cause denial-of-service or cost overruns.

Session Persistence

Medium
Category
Rogue Agent
Content
## User Services

`systemctl --user` units live in `~/.config/systemd/user/` and need `loginctl enable-linger <user>` to run without an active login session — otherwise they stop when the user logs out, which is the exact problem supervision was supposed to solve. Fine for a personal box; for anything shared, a system unit with a dedicated user is clearer.

## PM2
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| Looks supervised | Actually |
|---|---|
| `systemctl start` was run, `enable` was not | Gone after the next reboot; `is-enabled` says `disabled` |
| PM2 running, no startup unit / no `pm2 save` | Gone after the next reboot, and PM2 reports everything fine until then |
| Container running, `restart: no` (the default with `docker run`) | Gone after a daemon restart or a host reboot |
| `nohup`, `screen`, `tmux` | Gone when the session, the box, or the OOM killer decides |
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| Unit in `failed` after a crash loop | Down and not retrying — the start limit was hit (above) |
| Unit ordered after a network mount that arrives late | Starts, fails to find its data, and the restart limit bans it before the mount appears |

Check the whole box in one pass: `systemctl list-units --state=failed` and `systemctl list-unit-files --state=enabled` together answer "what is broken" and "what will come back".

## Write It Down
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.

File System Enumeration

Medium
Category
Data Exfiltration
Content
1. `ss -tlnp` — every listener. Anything on `0.0.0.0` that is not the proxy is a finding: databases, admin UIs, metrics endpoints, message brokers, the app itself.
2. `iptables -S` / `nft list ruleset` — the rules actually in force, including the ones a container runtime inserted (below). `ufw status` is a summary of one tool's intentions, not the kernel's state.
3. From another machine: a port scan of the public IP. What answers from outside is the only exposure that counts, and it regularly differs from what the firewall config implies.
4. `find / -name '.env' -o -name '*.pem' -o -name 'id_*' 2>/dev/null` under the web roots — a secret inside a served directory is a download, not a secret (`static.md`).

An app bound to `0.0.0.0:8080` behind a proxy is reachable directly on 8080: TLS bypassed, forwarded-header trust bypassed, rate limits bypassed, and auth done at the proxy bypassed entirely. Bind loopback or a Unix socket (SKILL.md Rule 7).
Confidence
80% confidence
Finding
Code scans file system directories looking for sensitive files. This could be reconnaissance for credential theft.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
| Method | Verdict |
|---|---|
| Environment file, mode 0640, owned `root:<service>` | The workable default |
| `Environment=` in the unit | No — unit files are world-readable and values appear in `systemctl show` |
| Secret in the repository or the image | No, ever. It is now in every layer, every clone, every backup |
| Secret in a proxy config or a compose file committed to git | Same problem, one directory over |
| systemd `LoadCredential=` | Best available on a plain box: the value is a file readable only by that unit, never in the environment |
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
| Method | Verdict |
|---|---|
| Environment file, mode 0640, owned `root:<service>` | The workable default |
| `Environment=` in the unit | No — unit files are world-readable and values appear in `systemctl show` |
| Secret in the repository or the image | No, ever. It is now in every layer, every clone, every backup |
| Secret in a proxy config or a compose file committed to git | Same problem, one directory over |
| systemd `LoadCredential=` | Best available on a plain box: the value is a file readable only by that unit, never in the environment |
Confidence
70% confidence
Finding
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Rp1

Medium
Category
MCP Rug Pull
Confidence
75% confidence
Finding
Docker image references without a specific tag (:latest is implicit) or digest (@sha256:...) can be silently replaced by a malicious image.

Static analysis

No suspicious patterns detected.