Linux Server Exposure Audit

    1

    Read-only audit of a Linux server's real exposure surface: what is listening, who runs it, crash loops, and container failures systemd cannot see. Severity-ranked, changes nothing.

    Free

    1 installSecurity scanned

    Works with the AI Agents you already use

    Claude CodeClaude CodeCursorCursorCodex CLICodex CLIMuseMuseDotsDots+22 more

    Linux Server Exposure Audit

    Example session with this skill installed

    I rebuilt a server last month and I want to know what is actually exposed before I announce anything. My monitoring says everything is green, which is exactly why I do not trust it.

    Run the exposure audit.

    • Read your context and instructions
    • Compiled the linux server exposure

    What it did

    Captured the host's read-only facts, classified them by layer, and found

    18 findings: 8 HIGH, 5 MEDIUM, 3 LOW, 2 INFO. Below is the tool's complete output, verbatim.

    SERVER EXPOSURE AUDIT
    ========================================================================
    facts from: D:\Hermeswork\skill_build\server-exposure-audit\scripts\fixtures\unhealthy
    
    HIGH=8  MEDIUM=5  LOW=3  INFO=2
    
    [HIGH] (cloud) C-FW — Exposed ports with no host firewall detected
        5 socket(s) bound to all interfaces, and no active firewalld/ufw was
        detected. If the cloud security group is the only control, a single
        misclick in the console opens the host.
        FIX: Verify the cloud security group, and prefer an in-host firewall
        as a second layer. Do not assume the group is the only boundary.
    
    [HIGH] (container) D-PORT — Container port published on all interfaces: searxng
        Port mapping '0.0.0.0:8081->8080/tcp' binds the container port to
        every host interface, quietly widening the host's exposure to
        include a container the host audit never sees.
        FIX: Publish to 127.0.0.1 instead: `127.0.0.1:8081:8080`.
    
    [HIGH] (container) D-STATE — Container not running: searxng (Restarting)
        Status is 'Restarting (127) 5 seconds ago'. This is the blind spot
        that systemd cannot see: docker.service stays active, `systemctl
        --failed` reports zero, and the container crash-loops underneath
        both of them. If anything depends on it, that dependency is already
        broken.
        FIX: Read `docker logs <name>` for the real exit reason, then fix
        it. Treat container state as its own audit layer — it is not covered
        by unit-level checks.
    
    [HIGH] (host) H-EXPOSE — Port 111 is bound to every interface
        rpcbind listens on 0.0.0.0:111 — reachable from anywhere the
        firewall allows.
        FIX: Bind it to 127.0.0.1 if only local consumers need it, or
        restrict it at the firewall/security group. Most services that are
        proxied by nginx do not need to listen publicly.
    
    [HIGH] (host) H-EXPOSE — Port 8080 is bound to every interface
        doctool-api listens on 0.0.0.0:8080 — reachable from anywhere the
        firewall allows.
        FIX: Bind it to 127.0.0.1 if only local consumers need it, or
        restrict it at the firewall/security group. Most services that are
        proxied by nginx do not need to listen publicly.
    
    [HIGH] (host) H-FAILED — Failed unit: backup-agent.service
        backup-agent.service is in the failed state. A failed unit is
        silent: nothing pages you, and it stays failed until reboot or a
        manual reset.
        FIX: Decide deliberately: repair it, or disable+mask it. Leaving it
        failed means every future audit reports it forever, and real
        failures get lost in the noise.
    
    [HIGH] (host) H-RESTART — Crash loop: nginx.service restarted 137 times
        NRestarts=137. `systemctl is-active` says active, which is exactly
        why this is missed: a service that is restarting successfully looks
        healthy from every naive check.
        FIX: Read the unit's journal and fix the underlying failure. Also
        note: `systemctl is-active` reports the unit's state, not whether
        the workload inside is working.
    
    [HIGH] (host) H-SSH-PW — SSH accepts password authentication
        PasswordAuthentication yes. Every internet-facing SSH port gets
        continuous credential guessing; the only thing standing between the
        guesser and your host is password strength.
        FIX: Switch to key-only: PasswordAuthentication no, then verify you
        can still log in with your key BEFORE closing your session.
    
    [MEDIUM] (container) D-ROOT — Container runs as root: searxng
        Config.User is empty, so the container process runs as root.
        Combined with a dropped capability set this also changes which file
        permissions work — see the bind-mount note below.
        FIX: Set `user:` in the compose file or `--user` at run time.
    
    [MEDIUM] (host) H-DISK — Filesystem / is 88% full
        /dev/vda1 mounted at / is 88% full. At 100% the database and the log
        writer fail in ways that look like application bugs.
        FIX: Find the growth source before it fills. Reclaimed space is
        usually logs, package caches, and old backups.
    
    [MEDIUM] (host) H-ROOT — Service runs as root: docker.service
        User= is empty or root. systemd defaults to root when User= is
        unset, so a unit that looks harmless can be running with full
        privileges.
        FIX: Create a system user and set User=/Group= plus
        NoNewPrivileges=yes and PrivateTmp=yes. Note: when editing a unit,
        the LAST assignment in the same section wins — you must replace the
        existing User= line, not insert a new one above it, or your change
        silently does nothing.
    
    [MEDIUM] (host) H-ROOT — Service runs as root: ai-digital-human.service
        User= is empty or root. systemd defaults to root when User= is
        unset, so a unit that looks harmless can be running with full
        privileges.
        FIX: Create a system user and set User=/Group= plus
        NoNewPrivileges=yes and PrivateTmp=yes. Note: when editing a unit,
        the LAST assignment in the same section wins — you must replace the
        existing User= line, not insert a new one above it, or your change
        silently does nothing.
    
    [MEDIUM] (host) H-SSH-ROOT — SSH permits direct root login
        PermitRootLogin yes. A successful guess is immediately full control,
        and there is no per-person accountability.
        FIX: Set PermitRootLogin prohibit-password and use a normal user
        with sudo.
    
    [LOW] (host) H-NNP — NoNewPrivileges=yes is not set: docker.service
        The service can still gain privileges through setuid binaries.
        FIX: Add NoNewPrivileges=yes. It is free unless the service needs
        setuid, which most do not.
    
    [LOW] (host) H-NNP — NoNewPrivileges=yes is not set: ai-digital-human.service
        The service can still gain privileges through setuid binaries.
        FIX: Add NoNewPrivileges=yes. It is free unless the service needs
        setuid, which most do not.
    
    [LOW] (host) H-SELINUX — SELinux is Disabled
        Mandatory access control is inert, so a compromised service is
        limited only by Unix permissions.
        FIX: This is a deliberate trade-off on many small hosts — raising it
        can break working services. Record the decision instead of leaving
        it undecided.
    
    [INFO] (container) D-CAPDROP — Container drops all capabilities: searxng
        cap_drop: ALL is good practice, with a sharp edge worth knowing: the
        container's root loses DAC_OVERRIDE, so it can no longer bypass file
        permission bits. If the container writes to a bind-mounted host
        directory, its access depends on the actual mode/owner of that
        directory.
        FIX: Do not 'tidy up' permissions on a bind-mounted directory with
        chmod. A tighten that looks purely defensive can cut the container's
        access to the directory and take production down. Verify by
        restarting the container and exercising the dependent path, not by
        watching `docker ps` say Up.
    
    [INFO] (host) H-FAILED — Failed unit: kdump.service
        kdump.service is failed. On small instances this is usually NOT a
        security problem: the kernel reserves crash memory via crashkernel=,
        and if the instance has less RAM than the reservation window
        demands, the reservation is 0 and kdump cannot work at all. Newer
        kexec-tools report this as a hard failure instead of skipping
        quietly.
        FIX: Decide deliberately: repair it, or disable+mask it. Leaving it
        failed means every future audit reports it forever, and real
        failures get lost in the noise.
    
    ========================================================================
    Layers covered: cloud, host, container. Application health is NOT
    covered — a port answering 200 does not mean the app works. Check the
    real user path separately.
    

    The tool verifying itself

    unhealthy fixture : 18 findings {'CRITICAL': 0, 'HIGH': 8, 'MEDIUM': 5, 'LOW': 3, 'INFO': 2}
    healthy fixture   : 0 findings {'CRITICAL': 0, 'HIGH': 0, 'MEDIUM': 0, 'LOW': 0, 'INFO': 0}
    PASSED
    

    Why the monitoring was green anyway

    Three of the eight HIGH findings are invisible to unit-level monitoring:

    FindingWhy monitoring missed it
    D-STATE — container searxng is Restarting (127)docker.service is active and systemctl --failed is zero. Both true, both useless.
    H-RESTART — nginx restarted 137 timessystemctl is-active says active. A unit that restarts successfully looks healthy forever.
    C-FW — exposed ports, no host firewallNothing monitors the absence of a control.

    What it deliberately did not do

    It changed nothing. No service was restarted, no port was closed, no setting was written. The INFO items — including the failed kdump unit, which cannot work on an instance this small because the kernel memory reservation is 0 — are recorded rather than "fixed", so they stop cluttering every future run.

    Application health is out of scope, and the report says so: a port answering 200 does not mean the feature works.

    Connects securely to your tools. The creator never sees your data.

    About this skill

    What it does

    Audits a Linux server's real exposure surface and service health, read-only, and reports severity-ranked findings. It is built on one idea: an audit that checks a single layer is not an audit — the interesting failures live between layers.

    Four layers matter: cloud (what the network allows in), host (what is listening, who runs it, what crashed), container (what Docker is actually doing), and app (does the real user path work). This skill checks the first three mechanically and states plainly that it does not cover the fourth.

    Why use it

    Because the standard checks lie, in specific and repeatable ways:

    • systemctl is-active reports the unit, not the workload. A service that has restarted 137 times is still "active", and your dashboard is still green. The honest counter is NRestarts.
    • Container failures are invisible to systemd. docker.service stays active while containers crash-loop, and systemctl --failed reports zero. Both statements are true and both are useless. Container state has to be its own layer.
    • A service can be running as root while the unit file looks harmless, because systemd uses the last assignment in a section — inserting User=appuser above an existing User=root silently does nothing.
    • cap_drop: ALL changes which file permissions work inside a container. Its root loses the ability to bypass permission bits, so a "purely defensive" chmod on a bind-mounted directory can cut the container's access and take production down.
    • A firewall check can invert its own answer. The string not running contains running. A scanner that searches for running reports an absent firewall as present and drops the finding entirely.

    How it works

    1. Collects a fixed allowlist of read-only facts. --print-commands prints the entire list and runs none of it.
    2. Classifies every fact by layer and severity. HIGH means reachable from outside or already broken for users. INFO means a deliberate trade-off, recorded so it stops reappearing in every future audit.
    3. Emits a report with the specific fix for each finding, not a generic checklist.

    --from <dir> captures facts on one host and analyzes them anywhere — including a host you cannot install anything on. It is also what makes the analyzer testable.

    Verified, not claimed

    It ships a control-group self-check:

    $ python scripts/self_check.py
    unhealthy fixture : 18 findings {'HIGH': 8, 'MEDIUM': 5, 'LOW': 3, 'INFO': 2}
    healthy fixture   : 0 findings
    PASSED
    

    The broken host must produce the expected findings at the expected severities, and the healthy host must produce zero. A scanner that always finds something is as useless as one that finds nothing. One bug in this skill's first draft — the firewall inversion above — was caught by exactly this check.

    What it is not

    • It reports; it does not patch, restart, install, or change a single setting. Fixes are your decision.
    • Not a malware or rootkit scanner.
    • It reads the host's view of itself. The cloud security group must be checked separately.
    • Not an application monitor. A port that accepts a connection is not a feature that works.
    • Requires root for complete results; without it, process names are hidden and the report says so.

    FAQ

    Q: Will it change anything on my server? No. Every probe is read-only and lives in one literal allowlist you can print first with --print-commands. No shell is ever invoked, nothing is written to the host, and no network request is made. It runs with shell=False and a literal argument list, so there is no injection surface.

    Q: Does it need Python packages, or an API key? No. Standard library only, no network, no key. It degrades gracefully when docker, getenforce, or a firewall tool is absent — a host missing a tool loses those checks and reports the rest.

    How to use

    Skills on Agensi run inside your AI agent. Copy the prompt, paste it into your agent, and it connects to Agensi and installs this skill for you. If a payment is needed, your agent shows it to you first.

    Reviews

    No reviews yet

    Be one of the first to try it. Every listed skill passes our trust checks below.

    Security scanned

    Passed our 8-point scan before listing

    1 install

    Downloaded by developers to date

    Free forever

    No account required to browse

    Trust & safety

    Security scanned

    Verified clean 13 days ago

    • Free to download with an account

    Listed13 days ago

    What's inside

    Frequently Asked Questions