Posture answers a question access control cannot: what is this host exposing right now? A small collector on each Linux host reports its listening ports, the process or container behind each one, and its firewall rules. Shellius works out which of those ports are reachable, and raises a finding when something is exposed that probably should not be.
It is an exposure inventory, not monitoring and not a vulnerability scanner. It does not probe your hosts from outside and it does not look for CVEs.
Scope
Linux hosts only. Windows and RDP-only servers are not collected, and are labelled as such rather than shown as clean. Firewall verdicts are computed for ufw and firewalld. On a host using raw nftables or iptables, ports are reported with a firewall state of unknown instead of a guess.
Required permissions
| Permission | Lets you |
|---|---|
posture.read | See the Posture and Services & ports pages, and the posture tabs on a server. |
posture.mute | Mute, unmute and acknowledge findings. |
posture.expected_ports | Declare a port as expected on one specific server. |
posture.export | Download findings and port inventories as CSV, JSON or PDF. |
posture.settings | Set retention, the organisation-wide expected-public list, and alert rules. |
Export is separate from viewing on purpose: an export takes security data out of Shellius, where access control no longer applies to it.
Install the collector
The collector is installed as part of a normal bootstrap, so newly onboarded hosts report without any extra step. It runs from its own systemd service and timer, about every five minutes, entirely separate from check-principals. A collector that crashes cannot affect SSH logins.
For hosts that were bootstrapped before posture existed, or that use a stored identity and were never bootstrapped at all:
- Open Servers and select the hosts.
- Choose Install collectors.
- Pick Posture collector only. This adds the collector and its timer and does not touch
sshd, CA trust orcheck-principals, so it is safe on hosts that already work. - Review the plan, supply credentials for any host without a saved identity, and run it.
To see which hosts are reporting, open Posture and select Coverage. Each server is Reporting, Stale (it has stopped reporting) or Not installed.
How a verdict is reached
The collector sends raw, observable facts only. Shellius recomputes reachability and findings itself, and ignores any verdict a host sends about itself. A compromised host that could report "all clean" would defeat the point.
Each listening port gets a reachability:
| Reachability | Meaning |
|---|---|
| Internet | Bound to a public or wildcard address, and the firewall allows it or has no default deny. |
| Firewalled | Bound publicly, but the firewall denies it. |
| LAN | Bound to a private address. |
| Loopback | Bound to 127.0.0.1 or ::1. |
| Container | Listening only inside a container. |
| Unknown | The firewall engine is one Shellius cannot reason about. |
Findings
| Finding | Severity | Raised when |
|---|---|---|
DOCKER_FIREWALL_BYPASS | Critical | Docker publishes a port that the host firewall would have blocked. Docker's NAT rules are applied before the firewall's input rules, so the port is open regardless of ufw or firewalld. |
SENSITIVE_PORT_EXPOSED | Critical | A database, cache, cluster or management port — PostgreSQL, MySQL, Redis, MongoDB, Elasticsearch, the Docker API, etcd, the Kubernetes API, RDP, VNC, SMB and similar — is reachable from the internet. |
PORT_EXPOSED | High | Any other port is reachable from the internet and nobody has declared it expected. |
NO_HOST_FIREWALL | High | The host has no firewall. |
FIREWALL_INACTIVE | High | A firewall is installed but switched off. |
SENSITIVE_PORT_WILDCARD_BIND | Medium | A sensitive port is bound to every address and only the firewall keeps it private. |
STOPPED_SERVICE_PORT_OPEN | Medium | The firewall allows a port whose service is installed but stopped. |
SENSITIVE_PORT_LAN | Low | A sensitive port is reachable from the private network. |
STALE_FIREWALL_RULE | Low | The firewall allows a port that nothing listens on. |
UNATTRIBUTED_LISTENER | Low | A port is open and its owning process could not be identified. |
EXPECTED_PUBLIC | Info | An internet-reachable port that is declared expected. |
FIREWALL_STATE_UNKNOWN | Info | Reachability could not be determined. |
Ports 22, 80 and 443 are treated as expected-public out of the box.
The Docker finding is the one to act on
ufw status will tell you the port is blocked, and it will be wrong. If you see DOCKER_FIREWALL_BYPASS, publish the container port on 127.0.0.1 instead of every address, or put it behind a reverse proxy.
Work through findings
Posture lists findings across every server you can see, filterable by severity, customer, environment and status. Services & ports shows the other view of the same data: every listening service across the fleet, from each host's latest snapshot. On a single server, the Open findings and Ports & services tabs show the same for that host.
A finding is in one of these states:
| Status | Meaning |
|---|---|
| Open | Not yet acknowledged, muted or declared expected. |
| Acknowledged | Seen and accepted. Escalation stops, but the finding stays open. |
| Muted | Out of sight until the mute expires. A muted finding notifies nobody. |
| Marked as expected | Someone declared the port public on purpose. Kept so the inventory is complete. |
| Resolved | No longer reported by any collector. Kept for history. |
Findings resolve themselves. Close the port or fix the firewall, and the finding resolves on the next report.
Mute a finding
- Select Mute… on the finding.
- Choose how long under Mute for: 1 day, 7 days, 30 days, or Until date.
- Give a reason. It is required — a suppression nobody can explain later is worse than the noise it removed.
Mark a port as expected
If a port is meant to be public, say so instead of muting it forever.
- On one server: select Mark expected on the finding and add a note. The note is required. Needs
posture.expected_ports. - Across the organisation: add the port to the expected-public list under Administration › Exposure posture. Needs
posture.settings.
Either way the finding drops to EXPECTED_PUBLIC at Info severity, shows which rule matched, and does not reopen.
Alert rules
Findings notify people through rules you define under Administration › Exposure posture, not through a fixed list of admins. A rule matches on any of severity, finding code, customer and environment, and routes to roles, a group or named users, in-app or by email. A rule can send immediately or as a daily digest, throttle repeat notices for the same finding, and escalate to another group if a finding is still open after a set number of hours.
A new organisation starts with one rule, Critical exposure, which sends critical findings to admins and super admins in-app and by email. You can edit or delete it.
What keeps this quiet:
- Changes notify, scans do not. A finding that persists across three hundred reports is one notification.
- Muted findings notify nobody, escalations included.
- Acknowledging stops the escalation clock without resolving the finding.
- Customer scope applies. A user limited to selected customers is never alerted about a customer they cannot see.
Email delivery needs a working mail setup — see Email.
Retention
Set under Administration › Exposure posture.
| Data | Default |
|---|---|
| Snapshots | 7 days |
| Resource metric samples | 24 hours |
| Resolved findings | 90 days after they resolve |
Export
Select Export on Posture or Services & ports, or select servers on the Servers page for a bulk export. Choose CSV, JSON or PDF, pick the columns, and for several servers choose between One file and ZIP, one file per server.
Troubleshooting
A bootstrapped server shows no posture data. Give it a few minutes after install. If it is still empty, check the timer on the host:
systemctl status shellius-posture.timer
journalctl -u shellius-posture.service -n 50The host needs outbound HTTPS to the Shellius API, the same as check-principals. See Ports and firewalls.
A container shows as runtime:<id> instead of its name. The collector deliberately has no access to the Docker socket, so it cannot always resolve a container's name. The port and the finding are still correct.
Every port on a host says unknown. The host uses a firewall engine other than ufw or firewalld. Shellius withholds a verdict rather than guessing.