Server security
Skill zerostaff/server-security-skill/skills/server-security
Audit and harden basic security on a Debian/Ubuntu server over SSH — system updates, non-root sudo user, SSH key authentication, sshd hardening, UFW firewall, unattended-upgrades, fail2ban, sudo and home directory permissions. Use this whenever the user mentions auditing a server, hardening SSH, locking down a VPS, checking server security, configuring ufw, setting up fail2ban, securing a new server, or asks "is my server secure" / "what should I do to my new VPS" — even when they don't use the word "audit". Always produces a read-only audit report first, then applies fixes only after explicit confirmation, with a fail-safe automatic rollback for SSH configuration changes so the user cannot get locked out.From its SKILL.md
npx -y skills add zerostaff/server-security-skill --skill server-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
16.4 KB, ~4.1k tokens by cl100k_base, as published. Nobody here has run it
Server Security
A disciplined audit-then-harden workflow for a fresh or unsupervised Debian/Ubuntu server, reached over SSH. The skill covers the eight areas that block roughly 80% of automated attacks: system packages, a non-root sudo user, ed25519 SSH keys, sshd hardening, UFW, unattended-upgrades, fail2ban, and sudo + home permissions.
The skill is split into six phases. Do not skip phases. The audit phase must not mutate anything; mutations only happen in Phase 5 after the user explicitly approves the plan.
When parsing or fixing a specific check, consult references/checks.md for the precise commands and target values. When touching sshd_config, consult references/ssh-safety.md for the fail-safe rollback protocol — never edit sshd without it.
Phase 1 — Connect and detect environment
Ask the user for whatever connection details are missing. Required:
- Host (IP or DNS name)
- User to connect as (often
rootinitially on a fresh server, or an existingdeploy-style user) - Port (default 22)
- Identity file (optional; SSH agent or default keys are fine)
Then verify reachability and capabilities. Use one ssh invocation that runs a small bundle of read-only commands and parse the output:
ssh -o ConnectTimeout=10 -o BatchMode=yes -p <port> <user>@<host> \
'set -e; \
. /etc/os-release && echo "OS=$ID VERSION=$VERSION_ID"; \
id; \
command -v sudo >/dev/null && echo "sudo=present" || echo "sudo=missing"; \
sudo -n true 2>/dev/null && echo "sudo=passwordless" || echo "sudo=needs-password"; \
uname -r'
From the output decide:
- OS check. If
IDis notdebianorubuntu(or a derivative likelinuxmint,pop), stop. Tell the user the skill currently targets Debian/Ubuntu and ask whether to continue at their own risk (in which case some commands may fail). - Privilege check. Many audit commands require root reads (
/etc/ssh/sshd_config.d/,sshd -T,ufw status,fail2ban-client). If the user hassudobut it requires a password, the skill must run with sudo and the user must be prepared to type their password — or grant passwordless sudo for the duration. If the connecting user has nosudoat all (and is not root), abort with a clear message. - Connecting-as-root note. Connecting as root is normal on a brand-new VPS. The skill should plan to create a non-root user as part of the fix, then advise switching the SSH session to that user before disabling root login.
Speed tip. If you will run more than a few SSH commands, enable a ControlMaster session so each subsequent command does not pay the handshake cost:
mkdir -p ~/.ssh/cm && \
ssh -o ControlMaster=auto \
-o ControlPath=~/.ssh/cm/%r@%h:%p \
-o ControlPersist=10m \
-p <port> <user>@<host> true
Subsequent ssh calls with the same ControlPath reuse the connection. Tear it down at the end with ssh -O exit <user>@<host>.
Phase 2 — Audit (read-only)
Run all eight checks. Read only — no mutations, no service restarts, no package installs. If a command requires root, prepend sudo (it will use the cached or passwordless sudo).
Collect the raw output and parse into a structured result per check. references/checks.md contains the exact detection command and the expected pass condition for each. The eight checks are:
- System updates —
apt list --upgradablecount, age of/var/cache/apt/pkgcache.bin. - Non-root sudo user — at least one non-root user in
sudo(orwheelon RHEL-derivatives, but we only support Debian/Ubuntu) with a populated~/.ssh/authorized_keys. - SSH key strength — the keys in
authorized_keysare ed25519 or rsa ≥ 3072 bits. - sshd hardening — effective values via
sudo sshd -Tmatch the target table inreferences/checks.md. Also enumerate/etc/ssh/sshd_config.d/*.confin lexical order and flag any earlier-loading file that conflicts. - UFW firewall — active, default-deny incoming, default-allow outgoing, SSH port allowed.
- Unattended-upgrades — package installed, enabled in
20auto-upgrades, security origin enabled in50unattended-upgrades, automatic-reboot configured. - Fail2ban — service active,
sshdjail enabled, ban policy reasonable. - Sudo and home permissions —
sudo visudo -cclean, target user's home is mode700.
For each check, record:
status:pass/warn/fail/skippedcurrent: what was observed (short string, e.g.PermitRootLogin=yes)target: what the article expectsseverity:info/low/medium/highnotes: anything the user needs to know (e.g. "drop-in00-cloud-init.confsetsPasswordAuthentication yesbefore our hardening file would load")
Phase 3 — Present report
Produce a single markdown report in this exact shape so the user can scan it in one pass:
# Server security audit — <host>
Connected as `<user>` to `<host>:<port>` on <ID> <VERSION>. Kernel `<uname -r>`.
## Summary
- ✅ Passing: N
- ⚠️ Warnings: N
- ❌ Failing: N
## Findings
| # | Area | Status | Current | Target | Severity |
|---|------|--------|---------|--------|----------|
| 1 | System updates | ⚠️ | 23 packages upgradable | up to date | low |
| 2 | Non-root sudo user | ✅ | `deploy` with ed25519 key | non-root + key | – |
| 3 | SSH PermitRootLogin | ❌ | `yes` | `no` | high |
| … |
## Details
### 3. SSH PermitRootLogin
Currently `PermitRootLogin yes` (set in `/etc/ssh/sshd_config` line 32). Allowing direct root logins exposes the most-targeted account on the server to credential attacks. Fix: drop `PermitRootLogin no` into `/etc/ssh/sshd_config.d/01-hardening.conf`. **Before disabling, verify a non-root sudo user with a working key — Phase 2 check #2.**
### …
For each failing or warning check, include a short Details paragraph explaining what was found and why it matters. Do not propose commands here — that comes in Phase 4.
Phase 4 — Confirm fix plan
Group fixes into two batches and present them separately.
Batch A — Safe fixes (low risk of lockout):
apt update && apt upgrade -yand install missing packages.- Create the non-root sudo user and install the SSH public key (if missing).
2b. Configure sudo authentication for that user — NOPASSWD drop-in or
passwd. Ask the operator to pick one during the Phase 4 prompt; this is mandatory, not optional. Seereferences/checks.md§ 2 for the trade-offs. - UFW configuration.
- Unattended-upgrades configuration.
- Fail2ban configuration.
- Home directory permissions, sudoers validation.
Batch B — SSH hardening (lockout risk if misconfigured):
7. Drop-in /etc/ssh/sshd_config.d/01-hardening.conf with the target values.
8. Restart ssh — but only via the rollback-guarded protocol in references/ssh-safety.md.
Show each batch as a numbered list. For each item, show the exact command(s) that will run. Ask the user, in one message, "Apply Batch A?" — yes/no/select-subset — and separately "Apply Batch B?" with an explicit note that this is the lockout-risk batch and the rollback protocol will run automatically.
If the user wants to cherry-pick, accept a list like A: 1,3,5 or natural language ("skip the firewall, do the rest"). Re-state what will run and ask once more for confirmation before doing anything.
Phase 5 — Apply fixes
Interactive commands. Anything that prompts the operator for input — passwd, an editor opened by visudo without -c, dpkg-reconfigure at its default priority, apt configfile diffs — cannot run through the agent's shell. The agent's stdin is not wired to the operator. When the skill needs one of these, tell the operator to run it in a separate terminal (or in their existing one) and wait for confirmation in chat before continuing. Never try to script around an interactive prompt with heredocs, expect, or by accepting the secret in chat to "automate" it — these either fail outright or leak the secret into the transcript.
Order matters. Run Batch A first, in this order, stopping the whole batch on the first failure:
-
System updates and packages
sudo apt update sudo apt upgrade -y sudo apt install -y sudo curl wget gnupg ca-certificates ufw fail2ban unattended-upgradesapt upgrade -ycan prompt about modified config files (/etc/...). PassDEBIAN_FRONTEND=noninteractiveplus-o Dpkg::Options::="--force-confold"to keep existing config files. If a kernel upgrade installed, note that a reboot is recommended (do not reboot inside the skill — surface it as a final action item). -
Non-root user — account + key. Skip if a suitable user already exists with a working key.
sudo adduser --gecos "" --disabled-password <name> sudo usermod -aG sudo <name> sudo install -d -m 700 -o <name> -g <name> /home/<name>/.ssh echo "<public-key>" | sudo install -m 600 -o <name> -g <name> /dev/stdin /home/<name>/.ssh/authorized_keysThe public key must come from the user. If they do not have one, point them at
ssh-keygen -t ed25519 -a 100 -C "<name>@<hostname>"on their local machine — never generate the private key on the server.
2b. Non-root user — sudo authentication. adduser --disabled-password puts ! in /etc/shadow — the account cannot use sudo until you give it a way to authenticate. This is a mandatory step before Batch B; do not skip it. The operator picked Option A or Option B during Phase 4 — apply the matching commands now (full rationale in references/checks.md § 2):
- Option A — NOPASSWD sudo:
printf '%s ALL=(ALL) NOPASSWD:ALL\n' "<name>" \ | sudo install -m 0440 -o root -g root /dev/stdin /etc/sudoers.d/90-<name> sudo visudo -c - Option B — password-protected sudo:
passwdis interactive and cannot run through the agent's shell. Do not try — no heredoc, no piping, no accepting the password in chat. Tell the operator to open a separate terminal, log in as the existing sudo user (or root), and run there:
Wait for the operator to confirm "done" in chat before continuing.sudo passwd <name>
Verify before continuing. Ask the operator to open a second terminal: ssh -p <port> <name>@<host>, then run a sudo command in that session — sudo -n true for Option A, sudo true for Option B. Wait for the operator to confirm "got a shell + sudo works" in chat. Without this confirmation, Batch B is unsafe: the SSH rollback can only be cancelled from a session that can run sudo, and the existing root session may not survive the reload.
-
UFW.
sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow <ssh-port>/tcp comment 'ssh' sudo ufw logging low yes | sudo ufw enableIf the user already has other rules (web ports, etc.), preserve them — list current rules first with
sudo ufw status numberedand confirm. -
Unattended-upgrades. Configure non-interactively rather than running
dpkg-reconfigure:sudo tee /etc/apt/apt.conf.d/20auto-upgrades >/dev/null <<'EOF' APT::Periodic::Update-Package-Lists "1"; APT::Periodic::Unattended-Upgrade "1"; APT::Periodic::AutocleanInterval "7"; EOFThen patch
/etc/apt/apt.conf.d/50unattended-upgradesto setUnattended-Upgrade::Automatic-Reboot "true";,Unattended-Upgrade::Automatic-Reboot-Time "04:00";, andUnattended-Upgrade::Remove-Unused-Kernel-Packages "true";. Seereferences/checks.mdfor the sed/awk patterns. -
Fail2ban.
sudo cp -n /etc/fail2ban/jail.conf /etc/fail2ban/jail.localWrite
/etc/fail2ban/jail.d/sshd.localwith the values fromreferences/checks.md(bantime, findtime, maxretry, mode=aggressive). Then:sudo systemctl enable --now fail2ban sudo fail2ban-client status sshd -
Home permissions and sudoers.
sudo chmod 700 /home/<name> sudo visudo -c
Then Batch B — only proceed if Batch A items 2 and 2b both succeeded and the operator has confirmed, from a second terminal logged in as the deploy user, that both ssh and sudo work in that session. Without both confirmations, refuse to touch sshd; explain that disabling root login or password auth before key + sudo are verified in the deploy session will leave the operator unable to cancel the rollback if the root session dies during reload.
- sshd hardening with rollback. Use the full protocol in
references/ssh-safety.md. The short version:- Write the proposed config to
/tmp/01-hardening.conf.newand validate withsudo sshd -t -f /tmp/01-hardening.conf.new— abort on error. - Back up any existing
/etc/ssh/sshd_config.d/01-hardening.confto/tmp/01-hardening.conf.bak. - Schedule an auto-rollback in 5 minutes using a transient
systemd-rununit that restores the backup (or deletes the new file if there was no backup) and restarts ssh. - Move the new file into place and
sudo systemctl reload ssh(reload, not restart — existing sessions stay alive either way, but reload is the documented hot-swap path). - Tell the user, verbatim: "I have applied the SSH hardening. Open a new terminal and run
ssh -p <port> <name>@<host>to confirm you can still log in. Reply once you see a shell prompt — I have 5 minutes before the config rolls back automatically." - On confirmation, cancel the rollback unit.
- If the user reports they cannot log in, do not try to fix it — wait. The rollback will fire and restore working ssh.
- Write the proposed config to
Phase 6 — Verify and report
After Batch A and (optionally) Batch B, re-run the audit phase. Show a tiny diff: "Before: 3 failing, 2 warnings. After: 0 failing, 1 warning." Surface any item that did not improve and explain why.
Finish with a short action list of things the user must do off-server (or on-server, but outside this skill's scope):
- Confirm their local SSH config has
Host <alias>\n HostName <host>\n User <name>\n IdentityFile ~/.ssh/<key>so future connections use the new user automatically. - Don't leave the deploy user as "NOPASSWD sudo + no password set" forever. If Option A was chosen during step 2b, anyone who acquires the SSH private key gets instant root with no second factor. Run
sudo passwd <name>to set a sudo password. Then either remove/etc/sudoers.d/90-<name>to require the password on every sudo (full second factor restored), or leave NOPASSWD for ergonomics — that is a trade-off the operator should make consciously. The one state to avoid is "non-root user with NOPASSWD and no password set": no second factor, andsu -/ any password-gated tool will not work either. - Ubuntu locks the root account by default. With
PermitRootLogin noand a locked root, the only way in is via the deploy user — which is exactly the desired state. If the operator ever genuinely needssu - rootfrom deploy (rare),sudo passwd rootsets a root password; otherwise leave root locked, because a locked root account combined withPermitRootLogin nois strictly better than any password-protected root. - If a kernel was upgraded, schedule a reboot.
- Rotate any API keys or secrets that may have been pasted to the server during setup, and create per-project keys with spend limits in Anthropic / OpenAI / etc. consoles if the server runs LLM agents.
Non-goals
- Application-layer hardening (nginx, docker, postgres, app configs) — out of scope for v1; see
references/checks.mdfor what is not covered. - OS reinstall / image rebuild. The skill assumes a running server you can SSH into.
- IDS, SIEM, audit logging beyond fail2ban, and full CIS-benchmark compliance — also out of scope.
- Generating SSH keypairs on the server — keys must be generated on the user's local machine; only the public half ever touches the server.
What ships with it: 2 files
26.1 KB alongside SKILL.md
references/
- checks.md16.2 KB
- ssh-safety.md9.9 KB
Gives 0 of the 12 instructions most security skills give in ~4.1k tokens
Counted across 648 of the 828 authors here whose files we hold, read 2026-08-07
- Parameterize all database queriesin 68 of 648, across 51 files
- Hash passwords using bcrypt, scrypt, or argon2in 49 of 648, across 36 files
- Apply rate limiting to authentication endpointsin 48 of 648, across 24 files
- Configure security headersin 35 of 648, across 19 files
- Validate all inputsin 32 of 648, across 24 files
- Validate all external input at the system boundaryin 29 of 648, across 19 files
- Run containers as a non-root userin 28 of 648, across 15 files
- Use httponly secure samesite cookies for sessionsin 26 of 648, across 15 files
- Run dependency audits before every releasein 21 of 648, across 10 files
- Encode output to prevent cross-site scriptingin 21 of 648, across 11 files
- Copy dependencies before source codein 20 of 648, across 9 files
- Store secrets in environment variablesin 20 of 648, across 18 files
Said here and by no other author read
- produce a read-only audit report first
- ask for missing connection details
- verify os is debian or ubuntu
- abort if user lacks sudo
- run all eight security checks
- stop the batch on the first failure
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.