Back to skill

Security audit

VPS

Security checks for vulnerabilities and agentic risk

Overview

This VPS operations skill is coherent and scoped, with sensitive actions disclosed and guarded by confirmation requirements.

Install only if you want the agent to maintain local infrastructure records under ~/Clawic/data. Review proposed writes to shared server, domain, and finance inventories, and never paste raw secrets unless you expect the agent to replace them with pointers. Treat any real rebuild, destroy, resize, address release, or snapshot deletion as requiring your explicit confirmation.

Vulnerability Patterns
  • 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
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Memory PoisoningPersistent Context Injection, Context Window Stuffing, Memory Manipulation
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (22)

Credential Access

High
Category
Privilege Escalation
Content
**A domain that points at a host** goes to the shared `~/Clawic/data/domains/domains.md` — registrar, expiry, and where it points — because a migration is a DNS operation as much as a server one, and the TTL is what sets the cutover window.

**No credential is ever written anywhere under `~/Clawic/data/`** — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `file:~/.ssh/id_ed25519`, `keychain:hetzner-api`, `1password:Infra/root-password`, `env:RESTIC_PASSWORD`.

A VPS is a machine someone rents you and can take away. Two things decide every outcome: whether you can get back in when SSH stops working, and whether the data exists somewhere the provider account cannot reach. Everything else is tuning. Quote the monthly price with its currency, say what happens if the box dies tonight, and prefer a cheaper plan plus a rebuild script over a bigger plan. Work from defaults immediately: never open with questions about their provider, their budget, or their distro. The one exception to silence is `provider` — while it is unset, name which provider's console and plan names you are assuming before giving steps (Rule 8). That is a statement, not a question. Precedence for any value: `config.yaml` → `~/Clawic/profile.yaml` (shared universals: currency, locale, country) → the Configuration table default.
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
**A domain that points at a host** goes to the shared `~/Clawic/data/domains/domains.md` — registrar, expiry, and where it points — because a migration is a DNS operation as much as a server one, and the TTL is what sets the cutover window.

**No credential is ever written anywhere under `~/Clawic/data/`** — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `file:~/.ssh/id_ed25519`, `keychain:hetzner-api`, `1password:Infra/root-password`, `env:RESTIC_PASSWORD`.

A VPS is a machine someone rents you and can take away. Two things decide every outcome: whether you can get back in when SSH stops working, and whether the data exists somewhere the provider account cannot reach. Everything else is tuning. Quote the monthly price with its currency, say what happens if the box dies tonight, and prefer a cheaper plan plus a rebuild script over a bigger plan. Work from defaults immediately: never open with questions about their provider, their budget, or their distro. The one exception to silence is `provider` — while it is unset, name which provider's console and plan names you are assuming before giving steps (Rule 8). That is a statement, not a question. Precedence for any value: `config.yaml` → `~/Clawic/profile.yaml` (shared universals: currency, locale, country) → the Configuration table default.
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
**A domain that points at a host** goes to the shared `~/Clawic/data/domains/domains.md` — registrar, expiry, and where it points — because a migration is a DNS operation as much as a server one, and the TTL is what sets the cutover window.

**No credential is ever written anywhere under `~/Clawic/data/`** — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `file:~/.ssh/id_ed25519`, `keychain:hetzner-api`, `1password:Infra/root-password`, `env:RESTIC_PASSWORD`.

A VPS is a machine someone rents you and can take away. Two things decide every outcome: whether you can get back in when SSH stops working, and whether the data exists somewhere the provider account cannot reach. Everything else is tuning. Quote the monthly price with its currency, say what happens if the box dies tonight, and prefer a cheaper plan plus a rebuild script over a bigger plan. Work from defaults immediately: never open with questions about their provider, their budget, or their distro. The one exception to silence is `provider` — while it is unset, name which provider's console and plan names you are assuming before giving steps (Rule 8). That is a statement, not a question. Precedence for any value: `config.yaml` → `~/Clawic/profile.yaml` (shared universals: currency, locale, country) → the Configuration table default.
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
**A domain that points at a host** goes to the shared `~/Clawic/data/domains/domains.md` — registrar, expiry, and where it points — because a migration is a DNS operation as much as a server one, and the TTL is what sets the cutover window.

**No credential is ever written anywhere under `~/Clawic/data/`** — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `file:~/.ssh/id_ed25519`, `keychain:hetzner-api`, `1password:Infra/root-password`, `env:RESTIC_PASSWORD`.

A VPS is a machine someone rents you and can take away. Two things decide every outcome: whether you can get back in when SSH stops working, and whether the data exists somewhere the provider account cannot reach. Everything else is tuning. Quote the monthly price with its currency, say what happens if the box dies tonight, and prefer a cheaper plan plus a rebuild script over a bigger plan. Work from defaults immediately: never open with questions about their provider, their budget, or their distro. The one exception to silence is `provider` — while it is unset, name which provider's console and plan names you are assuming before giving steps (Rule 8). That is a statement, not a question. Precedence for any value: `config.yaml` → `~/Clawic/profile.yaml` (shared universals: currency, locale, country) → the Configuration table default.
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
**A domain that points at a host** goes to the shared `~/Clawic/data/domains/domains.md` — registrar, expiry, and where it points — because a migration is a DNS operation as much as a server one, and the TTL is what sets the cutover window.

**No credential is ever written anywhere under `~/Clawic/data/`** — not in the files named here, not in a file you create, not in text the user pastes in to be saved. Store the pointer and strip the value: `file:~/.ssh/id_ed25519`, `keychain:hetzner-api`, `1password:Infra/root-password`, `env:RESTIC_PASSWORD`.

A VPS is a machine someone rents you and can take away. Two things decide every outcome: whether you can get back in when SSH stops working, and whether the data exists somewhere the provider account cannot reach. Everything else is tuning. Quote the monthly price with its currency, say what happens if the box dies tonight, and prefer a cheaper plan plus a rebuild script over a bigger plan. Work from defaults immediately: never open with questions about their provider, their budget, or their distro. The one exception to silence is `provider` — while it is unset, name which provider's console and plan names you are assuming before giving steps (Rule 8). That is a statement, not a question. Precedence for any value: `config.yaml` → `~/Clawic/profile.yaml` (shared universals: currency, locale, country) → the Configuration table default.
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Memory Manipulation

High
Category
Memory Poisoning
Content
- Encrypt anything leaving the server. Object storage in another account is another account.
- **The passphrase is the backup.** Losing it makes every copy worthless — this is a more common total-loss cause than any hardware failure.
- The passphrase lives in the user's password manager, with a second holder if there is a second person. Never in `~/Clawic/data/`, never in the repository, never only in the head of the person who set it up. The runbook records the pointer: `1password:Infra/backup-passphrase`.
- The backup credential on the server should be **append-only** where the storage supports it: a compromised box can then add backups but not delete history.

## The Restore Drill
Confidence
80% confidence
Finding
Skill manipulates agent memory, state, or stored context. Memory corruption can alter personality, override safety rules, or cause unpredictable behavior.

Credential Access

High
Category
Privilege Escalation
Content
## Provider Accounts
| Provider | Account / project | Login owner | 2FA | Billing | API token | Support tier |
|----------|-------------------|-------------|-----|---------|-----------|--------------|
| hetzner | infra-main | user | yes | card, monthly | keychain:hetzner-api | standard |

## Exposure
| Host | Port | Service | Open to | Why | Layer |
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
- **Username.** `root` is disabled on many images by default and should be disabled on all of them; the admin user is the one with the key.
- **Which key.** If several keys are loaded, the daemon may try the wrong ones and hit the attempt limit before reaching yours. Specify the identity explicitly to test.
- **Permissions on disk.** The daemon refuses keys in a home directory or `.ssh` directory that is group- or world-writable, and says almost nothing about it in the client output. Home not writable by group or others, `.ssh` at 700, `authorized_keys` at 600, all owned by the user.
- **Verbose client output names the failing step**, and the daemon's log names the reason. Read the server side: the client only ever says "denied".
- **The key was added to the wrong user.** Copying to root's `authorized_keys` and logging in as the admin user is the classic version.
- **SELinux** on RHEL-family images blocks reads of `authorized_keys` after a manual file copy that lost its context. Symptom: perfect-looking permissions and a denial anyway.
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.

Autonomous Decision Making

Medium
Category
Excessive Agency
Content
- Most providers block outbound port 25 by default, because a rented server that can deliver mail is a spam engine the moment it is compromised.
- Some unblock on request for an established account; some effectively never do (`providers.md`).
- **Submission ports for authenticated relaying are not blocked.** This is why the relay path works without asking anyone for anything.
- A blocked port 25 presents as connection timeouts to every receiver, which looks like a DNS or firewall problem on your side and is neither. Test whether an outbound connection to a known mail exchanger on 25 succeeds at all before debugging anything else.

## The Four Things Receivers Check
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.

Missing User Warnings

Medium
Confidence
86% confidence
Finding
This markdown file gives operational instructions for destroying instances, deleting snapshots, and removing provider resources, which can permanently remove data or infrastructure. While it discusses billing and retention, it does not explicitly warn the reader to verify backups or confirm resource identity before deletion, despite the destructive nature of the actions.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Every mainstream provider accepts user-data at creation. This converts Rule 7 from an aspiration into the default path.

A first-boot configuration worth having covers: the admin user and its authorized key, sudo without password only if the user asked for it, package update, the handful of packages the box always needs, swap, timezone, the host firewall with the SSH port allowed *before* enabling, sshd hardening, and automatic security updates. That is the whole First Hour, executed before you first log in.

- **Keep it in the project repository, not in the provider's web form.** The form is a copy; the repo is the source. A configuration that only exists in a console text box is lost with the account.
- **Version it.** Note the version used in `changes/<year>.md` when a host is created, so "why is this box different" has an answer.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Every mainstream provider accepts user-data at creation. This converts Rule 7 from an aspiration into the default path.

A first-boot configuration worth having covers: the admin user and its authorized key, sudo without password only if the user asked for it, package update, the handful of packages the box always needs, swap, timezone, the host firewall with the SSH port allowed *before* enabling, sshd hardening, and automatic security updates. That is the whole First Hour, executed before you first log in.

- **Keep it in the project repository, not in the provider's web form.** The form is a copy; the repo is the source. A configuration that only exists in a console text box is lost with the account.
- **Version it.** Note the version used in `changes/<year>.md` when a host is created, so "why is this box different" has an answer.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Every mainstream provider accepts user-data at creation. This converts Rule 7 from an aspiration into the default path.

A first-boot configuration worth having covers: the admin user and its authorized key, sudo without password only if the user asked for it, package update, the handful of packages the box always needs, swap, timezone, the host firewall with the SSH port allowed *before* enabling, sshd hardening, and automatic security updates. That is the whole First Hour, executed before you first log in.

- **Keep it in the project repository, not in the provider's web form.** The form is a copy; the repo is the source. A configuration that only exists in a console text box is lost with the account.
- **Version it.** Note the version used in `changes/<year>.md` when a host is created, so "why is this box different" has an answer.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
Every mainstream provider accepts user-data at creation. This converts Rule 7 from an aspiration into the default path.

A first-boot configuration worth having covers: the admin user and its authorized key, sudo without password only if the user asked for it, package update, the handful of packages the box always needs, swap, timezone, the host firewall with the SSH port allowed *before* enabling, sshd hardening, and automatic security updates. That is the whole First Hour, executed before you first log in.

- **Keep it in the project repository, not in the provider's web form.** The form is a copy; the repo is the source. A configuration that only exists in a console text box is lost with the account.
- **Version it.** Note the version used in `changes/<year>.md` when a host is created, so "why is this box different" has an answer.
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
83% confidence
Finding
This markdown file instructs the operator to perform resizes, including operations that power off the machine and may irreversibly increase disk size. While the document mentions these facts, it presents them as procedural guidance rather than a clear cautionary warning about impact to user availability and irreversible changes.

Unsafe Defaults

Medium
Category
Tool Misuse
Content
| Users | One account per human, no shared logins, no passwordless sudo unless the user asked for it |
| Backups | 3-2-1 with one copy outside the provider account, restore timed (`backups.md`) |
| Exposure verified | Scanned from another machine, both IPv4 and IPv6, results in `## Exposure` |
| Secrets on disk | Application secrets in a file readable only by its service user, never in the repository, never in a world-readable `.env` |
| Logs | Retained long enough to investigate — a compromise found on day 20 with 7 days of logs cannot be understood |

## What Gets Compromised, In Order
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
| Users | One account per human, no shared logins, no passwordless sudo unless the user asked for it |
| Backups | 3-2-1 with one copy outside the provider account, restore timed (`backups.md`) |
| Exposure verified | Scanned from another machine, both IPv4 and IPv6, results in `## Exposure` |
| Secrets on disk | Application secrets in a file readable only by its service user, never in the repository, never in a world-readable `.env` |
| Logs | Retained long enough to investigate — a compromise found on day 20 with 7 days of logs cannot be understood |

## What Gets Compromised, In Order
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.

Session Persistence

Medium
Category
Rogue Agent
Content
| Provider abuse notice or a bandwidth spike with flat traffic | The box is sending: spam, scans, or a proxy. This is how most owners find out |
| CPU pinned with no matching workload | Mining. Often hidden behind a process name that mimics a kernel thread |
| Unknown entries in `authorized_keys`, or a new sudo-capable user | Persistence installed |
| Cron, systemd timer, or unit file you did not create | Persistence, usually re-installing the payload after every cleanup |
| A binary in a temporary directory that is running | Almost always malicious; nothing legitimate does this |
| Outbound connections to addresses nothing should be talking to | Command and control, or exfiltration |
| Log files truncated, or a gap in a log's timeline | Someone tidied up. Absence of evidence is evidence here |
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

Low
Confidence
89% confidence
Finding
The skill explicitly instructs the agent to write decision records and update existing memory files in the same turn, but it does not require confirmation, diff display, or safeguards against overwriting incorrect paths or clobbering existing user-maintained data. In an agent setting, this creates a real integrity risk: the model may modify persistent operational records based on incomplete context or a mistaken decision, and those changes can propagate into later infrastructure actions.

Natural-Language Policy Violations

Low
Confidence
86% confidence
Finding
The template includes a fixed `data_residency: EU` value as part of the example configuration, which can impose a regional constraint without presenting it as a user choice or justified region-specific requirement. This may steer downstream skill behavior toward an EU-only locale/residency assumption even when the user has not opted in.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The skill directs the agent to write provider-account metadata and recurring cost information into persistent local files without any explicit user confirmation or warning that state will be modified. While the content is mostly operational metadata rather than raw secrets, it still includes sensitive account-management details and can cause unintended persistence, privacy issues, or silent state changes across sessions.

Natural-Language Policy Violations

Low
Confidence
88% confidence
Finding
Line L64 directs provisioning to use UTC by default and only use local time if the user has declared otherwise. This is a locale/time policy choice imposed by default rather than presented as an explicit user choice, which can conflict with organizational language/locale opt-in requirements.

Static analysis

Detected: suspicious.exposed_secret_literal

File appears to expose a hardcoded API secret or token.

Critical
Code
suspicious.exposed_secret_literal
Location
memory-template.md:56