Skip to content

Releases: YavLabs/shellius

Shellius v2.1.0

Choose a tag to compare

@github-actions github-actions released this 25 Sep 12:30

2.1 is about things that were quietly not happening. A recording that was never
written, a digest with no scheduler behind it, a server list that stopped at 25,
an SSO provider the API accepted and could never use. None of these failed
loudly; most of them looked like "nothing to report". The release also adds
SAML, keeps collectors up to date without anyone connecting to a host, and puts
the three riskiest paths under tests that run against real systems.

Added

  • SAML 2.0 single sign-on. The API had accepted provider: 'saml' since
    2.0 and nothing could consume it — no library, no ACS endpoint, no metadata —
    so a configuration saved that way was accepted and silently unusable. It now
    works, and is configurable from Administration → Single sign-on, including
    the service-provider values to paste into the IdP (EntityID, ACS URL,
    metadata URL, certificate fingerprint), labelled with the name each console
    uses for them. Entra, Okta and AD FS presets carry attribute hints so a stock
    app works without hand-written mapping.

  • RDP session recording. The Sessions page used to say replay was not
    available for RDP, which is backwards: RDP is the higher-risk protocol,
    because a desktop session is not limited to what a shell audit trail would
    show. Recordings are encrypted with the same envelope as SSH casts.
    Keystrokes are deliberately not captured and there is no setting to enable
    them
    — that would record every password typed into the remote desktop.

  • RDP in the Terminals workspace. RDP was previously only reachable on a
    standalone page. It is now a normal tab alongside SSH.

  • Collector auto-update. Signed, staged and self-healing: a canary
    percentage first, widening only while hosts come back healthy, halting on
    evidence rather than a timer. Off by default. It only ever replaces the
    posture collector — never check-principals, sshd config or the CA key.

  • Update awareness. Admins are told when a newer Shellius exists, with a
    fleet view of which collector version each host is running, and can request
    an instance upgrade through a host-side helper (opt-in at install).

  • Posture digests, with a scheduler actually behind them, per recipient and
    scoped to what that recipient may see.

  • End-to-end test harnesses you can run yourself: the RDP client and the
    SSH terminal workspace in a real browser against real hosts, and SAML against
    a real Keycloak. See docs/ and scripts/e2e-*.

  • TUI: RDP access (it used to ask for SSH credentials after an RDP
    approval), permissions refreshed from the server rather than read once at
    sign-in, and accurate access labels.

Changed

  • The TUI's access labels now come from a real policy evaluation rather than
    guessing from the environment, via a new batched endpoint that answers for
    N servers in a fixed number of queries.
  • Administration → Policies no longer sits further in from the card edge than
    the pages beside it.

Fixed

  • The TUI showed only the first 25 servers. It called /api/servers with
    no paging parameters, so anyone with a larger inventory could not see — or
    connect to — the rest. Not an error, just a shorter list than the web UI's.
    Both access-request listings had the same bug.
  • Recording retention was never enforced for object storage. The cleanup
    job only ever looked at the legacy on-disk path, while everything written
    since the terminal hub rewrite uses object storage. Recordings accumulated
    indefinitely while the product documented a 30-day window. See "Migration
    notes".
  • An RDP session could kick itself off Windows. Two connections opened per
    mount; Windows permits one interactive session per user, so the second
    evicted the first and the tab died with "Disconnected by other connection".
  • The audit email digest had no scheduler. Nothing ever ran it.
  • mode: 'digest' on a posture alert rule delivered nothing, silently.
  • RDP pointed at port 22 on servers created through the API, because
    Server.port defaults to 22 regardless of protocol.
  • guacd is built with H.264 enabled everywhere, not only in one compose file —
    without it Windows 10/11 and Server 2016+ render a black desktop while the
    cursor still moves.

Security

  • SAML refuses an unsigned assertion unconditionally: an unsigned assertion
    wrapped in a signed response is the canonical bypass for this protocol.
    Signatures are checked against the configured certificate only — a
    certificate carried in the document's own KeyInfo is never a trust anchor.
    Document type declarations are refused before parsing, SHA-1 is refused
    unless explicitly configured, and Destination is validated against the
    canonical ACS URL rather than the request's Host header.
  • SAML replay protection fails closed. If Redis cannot answer, sign-in is
    refused. An SSO outage during a Redis outage is an inconvenience; accepting
    unbounded assertion replays during one is a silent authentication bypass that
    would look completely normal in every log.
  • The collector updater refuses any version not newer than the one installed,
    defeating a replay of a validly signed older bundle, and refuses to install
    anything at all without a release signing key present on the host.
  • RDP recordings are encrypted before they leave the machine that produced
    them, and keystrokes are never recorded.

Migration notes

  • Recording retention starts working on upgrade. Because it has not been
    enforced for object storage, your bucket may hold recordings older than
    RECORDING_RETENTION_DAYS (default 30). The first cleanup run after upgrade
    will delete them. If you need them, copy them out or raise the window
    before upgrading.
  • RDP recording needs configuration to do anything. It requires object
    storage and a recordings volume shared between guacd and the backend. The
    bundled compose files add it; a hand-rolled deployment needs the volume and
    RDP_RECORDINGS_DIR / GUACD_RDP_RECORDINGS_DIR. Without them RDP works
    exactly as before and is simply not recorded. GUACD_RDP_RECORDINGS_DIR
    must be an absolute path — guacd's working directory is /, and a relative
    path makes it record nothing while reporting success.
  • Collector auto-update is off by default for every existing and new
    organization. Hosts bootstrapped before this release have no release signing
    key and will not update until re-bootstrapped; there is deliberately no way
    to push a signing key to a host out of band.
  • One migration (20261014000000_saml_sso) adds nullable SAML columns and
    drops NOT NULL from sso_configs.client_id, because SAML has no client id.
    That is a widening change — every row satisfying the old constraint still
    satisfies the new one — and the requirement moves to the API's validation,
    where the per-protocol rules already live.

Shellius v2.0.0

Choose a tag to compare

@vaidyayash8 vaidyayash8 released this 23 Sep 03:09
d590810

2.0 answers the three questions a buyer's security team asks that Shellius
could not answer: can we get the audit log into our SIEM, what happens when we
remove someone from the IdP, and is there an API. Notifications can now also
reach Slack, Google Chat, Teams and webhooks — and be acted on from Slack. It
fixes two security holes and a set of things that were quietly not working.

Security

  • Production can no longer be approved from an email link alone. The
    approval email carries a 24-hour URL that acts as the named approver with no
    login at all — reasonable for a dev box, and a way around the authentication
    half of the approval flow for production, where a forwarded message or a
    shared inbox was enough. A production decision now needs a session belonging
    to that approver; being signed in as somebody else is not enough, not even a
    super admin. Non-production keeps one-click. Every decision now records how
    it was made, so "was this approved by someone logged in, or by whoever held
    a link?" has an answer.

  • Suspending or deleting an account now actually cuts its SSH access.
    Suspension revoked sessions and refresh tokens, but left the person's
    already-issued SSH certificates and approved access requests alone — and
    certificateService.verify(), which every host calls on every SSH
    connection, never checked whether the human was still employed. A suspended
    user's certificate kept authenticating until it expired, up to the policy's
    maximum session duration. Disabling an account now revokes its certificates,
    access requests and API tokens in one cascade, and certificate verification
    refuses a certificate whose owner is no longer active.

    If you have suspended anyone whose certificate had not yet expired, they had
    access until it did. Audit → filter by certificate.* will show whether a
    suspended user's certificate was used.

Added

  • API tokens. Two kinds: service accounts, which belong to the
    organization and survive whoever created them, and personal access
    tokens
    , which belong to a person and can never exceed that person's own
    permissions. A token's permissions are recomputed on every request against
    the owner's live role, so demoting someone narrows every token they hold
    immediately — there is nothing to re-mint. Tokens cannot reach sign-in, MFA,
    SSO, the personal vault, the terminal, or token management itself, and can
    never hold a permission marked non-delegable. Shown once, expiry required
    (90 days by default), rotatable and revocable, with last-used time and IP.
    See docs/api-tokens.md.

  • Audit export. The audit log can now be streamed to a webhook
    (HMAC-signed, for Splunk/Datadog/Panther or anything that speaks HTTP), to
    S3 as gzipped NDJSON, to a syslog collector over TCP+TLS, or sent as
    a scheduled email digest. Delivery is at-least-once: the cursor advances
    only after the destination accepts a batch, so a crash replays rather than
    skips, and every record carries its immutable id for deduplication. A
    failing destination backs off and auto-disables after ten consecutive
    failures with a notification, because an audit pipeline that has quietly
    died is worse than one that is loudly broken. Delivery history, including
    the failures, is visible per sink. See docs/audit-export.md.

  • Audit retention and archiving. Optional per-organization retention, off
    by default — the default remains to keep everything forever. Nothing is
    deleted without an archive unless you explicitly ask for that, and nothing
    is ever deleted past what an active sink has not yet shipped. That last rule
    matters: retention and sinks are configured on different screens, often by
    different people, and a short retention plus a stalled sink would destroy
    entries that reached neither place. The API reports the holding point, so
    "why has nothing been deleted?" is answerable.

  • SSO deprovisioning. Shellius can now reconcile accounts against the
    identity provider's own directory — Microsoft Entra, Okta, Google Workspace
    or GitHub org membership — and flag or suspend the people who are no longer
    in it. A provider with no directory API says so explicitly rather than
    silently doing nothing.

    This is the only feature that can disable an account without a human
    deciding to, so it is deliberately timid. Dry run is on by default. The
    credential is tested before the directory is even read; an empty directory
    or one that has halved since the last good run aborts the whole pass;
    absence must persist past a grace period; the run aborts entirely if it
    would act on more than a set percentage or count of your people; someone
    still reachable through another sign-in provider is left alone; and the last
    active super admin is never suspended. An aborted run is the feature
    working, and says what it saw. See docs/directory-sync.md.

  • Chat notifications. Notifications can be sent to Slack, Google
    Chat
    , Microsoft Teams, or any endpoint that takes JSON, with
    per-destination filtering by event, environment, customer and severity. On
    Slack — and only on Slack, with a bot token — they carry working approve and
    deny buttons and can be sent as direct messages; Google Chat webhooks are
    one-way and Teams workflow webhooks have no interaction callback, so those
    carry a link instead.

    What is never sent to a channel: the six payloads that are bearer
    credentials — an invite, a password reset, an email verification, an
    approval link, an SSO link approval, an MFA code — because a channel is read
    by everyone in it. Nothing is auto-serialised either: a chat message is
    built from an explicit field list, so a field added to a notification later
    cannot quietly start appearing in Slack.

    Approving from Slack needs a signed request within five minutes and an
    explicitly linked account, and goes through exactly the same authorisation
    as the web UI. Accounts are linked by pressing a button and confirming in
    Shellius, never matched by email — a Slack workspace administrator can set a
    member's email address. Production approvals from chat stay off until an
    organization turns them on. See docs/chat-notifications.md.

Fixed

  • A posture alert rule set to "Daily digest" notified nobody at all. It
    suppressed the per-event email and deferred to a batching job that was never
    written. The mode is removed until that job exists, and existing rules are
    migrated to immediate so they start sending; throttling still bounds volume.
  • Escalating an unacknowledged posture finding did nothing. The escalation
    group was stored, validated and returned by the API, but never read — so an
    escalation reached exactly the people who had already ignored it.
  • Break-glass never emailed anyone. Its template existed and was never
    registered, so the highest-severity event in the product reached
    administrators only as an in-app row they had to notice.
  • An access request could be decided twice. The pending check and the
    update were separate statements, so two approvals arriving together both
    succeeded — two approvals, two audit entries, two different expiry times for
    one request.
  • A notification failure could orphan a request or lose an audit entry. A
    failure while notifying approvers threw after the request had been
    committed and before the audit write, leaving a pending request nobody was
    told about and nothing in the log describing it. Decisions are now audited
    before anyone is told, and notifying can no longer throw.
  • Clicking a user's name from the Users list did nothing, and the same for
    a policy from the Policies list. Both worked from everywhere else.
  • Filtering notifications by "Directory sync" returned an error, and those
    notifications showed a generic icon.

Migration notes

  • Nine migrations. Run scripts/backup-db.sh first, then
    docker compose ... exec backend npx prisma migrate deploy as usual (or let
    docker/entrypoint-backend.sh do it).

  • On a large audit_logs table, 20261002000000_audit_read_indexes
    creates three indexes and will hold a write lock for the duration, because
    Prisma runs each migration in a single transaction and CREATE INDEX CONCURRENTLY cannot be used inside one. To avoid the lock, create them by
    hand with CONCURRENTLY first and then mark the migration applied:

    CREATE INDEX CONCURRENTLY "audit_logs_org_id_created_at_id_idx" ON "audit_logs" ("org_id", "created_at", "id");
    CREATE INDEX CONCURRENTLY "audit_logs_org_id_action_idx" ON "audit_logs" ("org_id", "action");
    CREATE INDEX CONCURRENTLY "audit_logs_org_id_resource_type_idx" ON "audit_logs" ("org_id", "resource_type");
    npx prisma migrate resolve --applied 20261002000000_audit_read_indexes
    
  • If you sign in with Microsoft Entra or Okta, read this before arming
    directory sync.
    Deprovisioning has to match your users against the
    provider's directory, and Entra makes that harder than it looks: the
    identifier Entra puts in a sign-in token is unique per application, so it
    appears nowhere in Microsoft Graph. Shellius now records the correct
    directory identifier at sign-in, but it cannot be reconstructed for
    accounts that already exist
    — those fill in as each person next signs in.

    This is safe by construction: a user whose directory identifier is unknown
    is never judged, and each run reports how many were skipped for that reason.
    But it means an Entra or Okta organization should leave directory sync in
    dry run until that count has come down. The rollout steps are in
    docs/directory-sync.md. GitHub and Google identities are backfilled
    automatically, because there the identifier provably matches.

  • Two behaviour changes on upgrade, both deliberate. A production access
    request can no longer be approved from the emailed link without signing in —
    approvers w...

Read more

Shellius v1.7.6

Choose a tag to compare

@github-actions github-actions released this 22 Sep 21:26
a5d9ccd

Fixed

  • Services & ports and a server's Ports tab now name the real service. Hosts on older collectors showed half an answer; each port is now matched to the host's own service inventory:
    • A container port shows the container's name and short ID, with its compose file (or image) and the address it forwards to underneath — instead of container 172.27.0.2:3000.
    • A pm2 port shows the app's name and pm2 ID, with its working directory or script underneath — instead of the launcher's name (serve) and a node command line.
    • A port nobody was seen holding, but which exactly one service declares, is named after that service in italics and marked "owner not confirmed".
    • A specific bind address is shown in the subtext.
  • Posture shows a loading skeleton. The summary tiles, the coverage line and the section counts were a blank gap and "0" counts until the summary arrived.

Shellius v1.7.5

Choose a tag to compare

@github-actions github-actions released this 22 Sep 11:50
0f4aec7

Security

  • Services & ports leaked across customers. A user limited to some customers could see another customer's entire port inventory by putting that customer's ID in the filter. The Type/Protocol filter counts also included every customer's hosts. Both are fixed, with tests.

Fixed

  • Bulk install: the results list showed hosts that were never selected. When a run finished, the page behind it reloaded, and the installer quietly rebuilt its plan. The results are now fixed at the moment you start.

  • Bulk install: typed credentials never worked. A password typed instead of a saved identity was never used as a fallback, so every host that needed it failed with "No credentials available for this host". A host whose own saved identity fails now also falls back to the credentials you enter.

  • "Waiting for its first report" looked stuck. Its icon didn't spin, and nothing re-checked. The Server page, the Servers list and Customer Details now re-check every 15 s while a collector install is pending, and there is a Check now button.

  • The Resources panel (CPU, memory, disk, load) was always empty on real hosts. No collector ever measured them. Collector 1.1.2 does. Until a host has it, the panel says why and offers the update.

  • Ports & services was hard to read. It is now:

    • Service: a readable name, plus the recognised protocol and where it's defined.
    • Type: systemd, Docker, pm2, Podman or process.
    • Port and Protocol as separate columns.
  • Other ports fixes:

    • Containers show their own name and image.
    • The process name is stored.
    • "Not listening" is shown as a chip like the other reachability values.
  • Search boxes did nothing on Access Requests, Sessions, Certificates, Users and Policies. Tables that page on the server also sorted only the current page. Both now run on the server.

  • Server, customer and user filters were capped at 100 entries. They now search as you type.

  • Filters were lost on refresh or when following a link. They now live in the URL. The Dashboard's "active sessions" / "active certificates" links and Customer Details' "view servers" link now open already filtered.

  • Date "to" filters left out the day you picked. That day is now included.

  • Other bugs:

    • Policies' pager used the wrong page size.
    • The Audit log's resource and action lists were wrong; they now come from your data.
    • The Services export ignored the State filter and exported nothing for Container-internal.
    • The Posture tab's Filters button could disappear.
    • Posture section counts ignored filters.
    • Certificates' "expiring soon" counted only the current page.
    • The Sessions "Active" tab faked paging.
    • Reviewers saw an empty Requester filter.
  • Services & ports export stopped at 200 rows.

  • Services & ports could show a port twice under a type or reachability filter.

  • Grouped lists no longer load the flat list first and then flash into groups.

  • Phones:

    • The list toolbar wraps instead of cutting Sort to "S…".
    • Posture search gets its own row.
    • Service cards lead with the service name.
    • Server cards show the collector state.
  • The backend test suite exits on its own. Redis, queue and database connections are closed after each file.

Added

  • Retry failed hosts in bulk install: one host or all of them, with the same method or with different credentials.
  • A Refresh button on every page. Access Requests and Sessions also refresh on their own every 30 s, and the Dashboard every 60 s.
  • Detailed filters on every list that shows data:
    • Services & ports: server, type, port / port range, findings.
    • Posture: server, finding type, last seen, search.
    • Server Posture tab: type, bind, severity.
    • Servers: protocol, OS, active, last check.
    • Customer Details: the standard filter drawer.
    • Customers: paging, search, filters.
    • Users: locked, last login, scope, group.
    • Policies: org-wide, environment, subject.
    • Access Requests: reviewer, customer.
    • Sessions: customer, client IP.
    • Certificates: customer.
    • Audit log: actor picker, IP, resource.
    • Keystore: linked key, usage, expired, unused.
    • Notifications: paging, type, date.
  • Posture collector 1.1.2.
  • Nested grouping on Servers, Posture, Services & ports and Keystore → Export to Servers:
    • Use the Group button beside Filters to add up to 3 levels, then reorder or remove them.
    • Groups are counted over every matching row, not just one page. Filters and search still apply.
    • Groups are collapsible and show their counts. A group loads its rows, paged, only when opened.
    • The grouping is saved in the URL and remembered per list.
  • Column sorting on Services & ports, over all matching rows. The sort is kept in the URL and used by the export.
  • Audit log search by any resource's name: policies, groups, roles, shared Keystore identities and keys, deployments, access requests, certificates, sessions and more. These resources also get labels in the list. Personal vault items are never looked up by name. Deletes now save the resource's name in the audit entry, so deleted resources stay searchable by name. This applies from this release on.

Changed

  • Keystore → Export to Servers is one list:
    • It no longer switches between a batch view and a flat view when a filter is applied.
    • It opens grouped by export run. Each run's header shows its summary, and retry and output are row actions.
    • Filters always apply, and the list covers every run, not just the latest 20.

Upgrade notes

  • Migrations: 20260929000000_listener_process_name adds host_listeners.process_name.
  • Reinstall collectors to get 1.1.2 and resource metrics. Hosts on older versions are pre-selected under needs a reinstall.

Shellius v1.7.4

Choose a tag to compare

@github-actions github-actions released this 21 Sep 22:58
0b8ef2c

Fixed

  • The page kept showing the replaced collector's warnings after a reinstall. The installer only enabled the timer, which does nothing to a timer that's already running, so the new collector first reported up to 5½ minutes later. The installer now starts a run immediately. Until the new collector reports, the page says Collector reinstalled — waiting for its first report.
  • Docker hosts and hosts with custom firewall chains were "degraded" forever. Containers shown by ID, firewall rules the collector can't evaluate, and a truncated socket list are limitations of the host, not faults, so no reinstall could clear them. They are now shown as notes, never counted as degraded, and never pre-selected for reinstall. This also applies to hosts still on older collectors. The container note had in fact never been shown at all, because its counter was lost in a subshell; that is fixed too.
  • Outdated collectors weren't flagged for reinstall. The 1.7.3 notes said they were; they weren't. They now show Update available and are pre-selected in the installer.
  • line 328: /proc/<pid>/cmdline: No such file or directory on every run. It came from a grep matching its own process.
  • The host's auth log got three lines per listening port, every 5 minutes. The collector now asks systemctl show without sudo first, and once per unit instead of once per port.
  • The "Check on the host" commands misled. A blocking systemctl start looked hung, and the report script's log was missed by journalctl -u. The checks now start the run without blocking, read the log by its tag, and include a command that runs the collector exactly as the service does.
  • "Last seen" on the Server page's Agent card was always blank. It read a field that doesn't exist.
  • Certificates: filtering by type read a field that doesn't exist, and filtering by environment could widen a scoped user's results beyond their customer scope.

Added

  • SSH trust and Collector status columns on the Servers list and Customer Details, and on the Server page's Onboarding card. SSH trust shows the CA agent's state (healthy, legacy token, agent silent, no heartbeat, install failed, installing, not installed, identity auth). Collector shows reporting, update available, degraded, reports refused, stopped reporting, waiting for report or not installed. Clicking a badge explains the state and offers the fix. Both statuses can be filtered on the Servers page.

  • Reinstall posture collector in the Server page's Host menu and in the row actions on the Servers list and Customer Details.

  • Detailed filters:

    • Access requests: server, requester, protocol, environment and date.
    • Sessions: server, user, auth method, protocol, environment and date.
    • Certificates: server, user, type, environment and validity date.
    • Users: manager and MFA.
    • Customers: status and whether they have servers.
    • Keystore identities and keys: type, source, certificate and last used.
    • Notifications: type.
    • Ports: protocol.

    Lists that page on the server filter on the server, within the viewer's customer scope. Every filtered empty list offers Clear filters.

  • Posture collector 1.1.1.

Upgrade notes

  • Migration: 20260928000000_posture_installed_at adds servers.posture_installed_at.

Shellius v1.7.3

Choose a tag to compare

@github-actions github-actions released this 21 Sep 22:10
2070cef

Fixed

  • Hosts kept showing "Collector degraded" after a reinstall, and then "stopped reporting". Once 1.7.2 made the collector's sudo grant work, it started reporting Docker ports it found in the NAT table as source: "nat". The API's schema did not allow that value, so it refused the whole snapshot. The host looked dead, the last (degraded) snapshot stayed on screen, and reinstalling changed nothing. The API now accepts it. It also accepts two other shapes the collector has always sent and the API silently dropped: firewall rules placed beside firewall instead of inside it (so no firewall rule ever reached the reachability logic), and ownerId instead of ownerRef. Collectors already on hosts start reporting again as soon as the server is upgraded, with no reinstall needed.
  • A wrong sudo password hung an install for 15 minutes. It now fails at once, and the install dialogs ask for the right password.
  • The sudo password could appear in the install log. A pty echoes what is written to it. Install output is now scrubbed.
  • The ports table listed the same port twice (0.0.0.0 and ::, or 127.0.0.53 and 127.0.0.54). These are separate sockets, not duplicates from a reinstall; ingest replaces a host's listeners on every snapshot. They now show as one row with every bind.
  • Access requests, sessions, certificates, the audit log and notifications showed a server's hostname but not its name. They now show the name, with the hostname under it.

Added

  • Refused reports are visible. When the API rejects a host's snapshot, it records why, and the Server page shows Reports refused with the reason instead of "stopped reporting". The report script also sends the failures it catches before anything can be sent: collector missing, crashed, or snapshot too large.
  • Every collector warning explains itself and offers the fix. Degraded, refused and stale banners say what the problem means for the data shown. Each has a Reinstall collector button where a reinstall helps, and host commands to copy where it does not. An outdated collector shows Update to 1.1.0.
  • Reinstall from the bulk installer without losing your place. Degraded, refused and silent hosts get their own group, Installed, but needs a reinstall, selected by default. Healthy hosts can be ticked one by one. The single "re-run on hosts already done" switch is gone. The coverage list offers Reinstall per host, and "Install / reinstall on N hosts".
  • Save a host's sudo password. When sudo needs a password, tick Save it for next time in the install dialogs. The password is kept encrypted in the org Keystore, only once the install succeeds with it, and later installs on that host use it automatically. You can manage it from the Collector card on the Server page (save, change or forget). The bulk installer asks for a sudo password once, up front, and Use it for all waiting hosts applies one password to every host still waiting.
  • Posture collector 1.1.0. Hosts with neither ufw nor firewalld now have their iptables or nftables INPUT rules evaluated. They are no longer permanently degraded, and an empty INPUT chain is reported as an unfiltered host. Listeners are attributed to whoever started them: pm2 apps are found through their God Daemon and named from pm2's own pid files, including when launched as infisical run -- npm start or from an ecosystem file. Secret injectors (infisical, doppler, op, dotenv, …) are shown as via infisical run, never with their arguments. A process started by hand in an SSH session is labelled as unsupervised.
  • Every reference to another entity links to it. Servers, customers, users, requests and sessions open their detail pages. When you lack access to the destination, the reference is plain text.

Upgrade notes

  • Migration: 20260927000000_posture_rejections_and_sudo_credential adds servers.sudo_credential_id, posture_rejected_at and posture_reject_reason.
  • Expect new findings once hosts report again. Firewall rules now reach the verdict logic for the first time, so STALE_FIREWALL_RULE, STOPPED_SERVICE_PORT_OPEN and allow-rule-based PORT_EXPOSED can appear. They were always true. iptables-only hosts gain real verdicts too.
  • To get collector 1.1.0 (firewall parsing, pm2/launcher attribution), run Install collectors. Hosts on 1.0.0 are now listed under needs a reinstall and show Update to 1.1.0.

Shellius v1.7.2

Choose a tag to compare

@github-actions github-actions released this 21 Sep 02:52
90ac672

Fixed

  • Every host reported "Collector degraded", with unknown owners and UNKNOWN reachability. The collector's systemd unit set NoNewPrivileges=true, which stops setuid programs such as sudo from gaining privilege — and the collector reaches root only through sudo and its narrow sudoers grant. Every privileged read failed on every host: listening sockets, ufw/firewalld state, the NAT table and systemctl show. The grant itself was fine. The unit no longer sets NoNewPrivileges, nor RestrictSUIDSGID or ProtectKernelModules, which older systemd releases treat as implying it. ProtectSystem is now full rather than strict, so the root commands sudo runs can take their lock files under /run.
  • pm2 apps were never reported from a host. The unit's ProtectHome=true hid /root and /home from the collector entirely. It is now read-only; ordinary file permissions still decide what the collector can open, so homes that are private (0700/0750) stay private.

Changed

  • A collector privilege failure now names its cause. The collector used to discard sudo's own error message, so this bug showed up only as "sudo grant missing?". The degraded reason for listening sockets, the firewall and the NAT table now ends with what sudo actually said.

Upgrade notes

  • Existing hosts keep the broken unit until the collector is installed again. After upgrading, run Install collectors with Re-run on hosts that are already done; the installer rewrites the unit and reloads systemd. Hosts whose sudo asks for a password will ask again.

Shellius v1.7.1

Choose a tag to compare

@github-actions github-actions released this 21 Sep 02:27
32f51fc

Fixed

  • A collector install on a host whose sudo asks for a password hung forever. The installer ran sudo bash in a terminal session, so sudo printed its prompt and waited for a typist who does not exist. Nothing errored and the connection never closed, so the host never finished, the run's heading eventually said "Install finished" over rows that were still spinning, and pressing Stop left them spinning. It also meant 1.7.0's fallback — retry with the batch's credentials when a certificate install fails — could never fire in the very case it was for, because a hang is not a failure. With no sudo password the installer now uses sudo -n, which stops immediately with "a password is required"; every install has a 15-minute hard limit; and any host still queued or running when a run ends is marked not run instead of spinning, with the heading saying Install stopped when that is what happened. The copy-paste commands for running an install by hand are unchanged — a person at a terminal can answer the prompt.
  • Checkbox ticks sat outside their boxes in the bulk-install host list. A margin on the checkbox moved the box but not its tick. Margins now move the whole control, so this cannot recur elsewhere.

Added

  • Enter a sudo password for just the host that needs one. A host whose sudo wants a password now shows as amber Needs attention rather than as a failure among failures. Expanding it offers a password box and Retry this host: the certificate still logs in, the password is given to sudo only, it is used once and not stored, and the hosts that already succeeded are not reinstalled. The run's summary updates the moment a retry succeeds.
  • The single-host install window accepts a sudo password in certificate mode. 1.7.0 hid that field on the grounds that a certificate carries no password — true, but the certificate only handles login, and a typed password can handle sudo. Hiding it left exactly these hosts with no way forward.

Changed

  • The Notifications page matches the top-bar dropdown: a coloured icon for each type, the title in bold while unread, the message underneath. It used to show the same blue "info" badge for every type, so an approval and a break-glass alert looked identical on the page. Both now read one shared type map. The Related column is gone — it only ever showed a raw type name or a dash, and clicking a row already opens the related item.

Shellius v1.7.0

Choose a tag to compare

@github-actions github-actions released this 21 Sep 02:01
0c15a5c

Added

  • Install collectors across the fleet in one run. Installing one host at a time is fine for a host you just added and hopeless for an inventory that predates posture — so the collector never got installed anywhere and the posture pages stayed empty while reporting nothing wrong. Install collectors plans the whole run first and shows what it will not touch and why, because a silent skip is how you end up believing a fleet is covered. Bounded concurrency, one live log per host, and one host's failure never throws away the rest of the batch. A manual path hands back one command per host for a change window or config management.
  • Hosts that are already bootstrapped need no stored password. A bootstrapped host already trusts your certificate authority — that is the point of bootstrapping it — yet the installer used to skip exactly those hosts for "no credentials", so a fleet bootstrapped before posture existed could plan to zero targets. Shellius now signs a five-minute, this-host-only certificate and installs with no secret at all. Available in the single-host wizard as well, where it leads. Certificate installs need passwordless sudo; where that is not available the bulk run retries once with the credentials you supplied and says so in the log.
  • Services & ports, a fleet-wide inventory in the sidebar. Answers "where is nginx deployed", "what is listening on 6379 anywhere", and "which of these is reachable from the internet" without opening each server in turn. Exportable.
  • Services that are installed but not running are inventory too. The collector now reports stopped systemd units, stopped containers and stopped pm2 apps, along with container ports that are published only inside the container network. A stopped service with a firewall rule still open is its own finding (STOPPED_SERVICE_PORT_OPEN) rather than a generic stale rule, because "nginx is down but 443 is still open" and "nobody knows what this rule is for" call for different actions.
  • Bulk import can keep the credentials it is given. Import secrets are destroyed once a host is bootstrapped, which is a safe default and a surprising one: it left a freshly imported fleet with nothing stored, which is precisely the population a later bulk install has to skip. Set storeAsIdentity on a row to save its password or key to the Keystore and bind it to the server; identityName names it, and rows sharing a name share one entry, so fifty servers behind one bastion key produce one identity rather than fifty.
  • Needs attention on the Dashboard. A full-width widget naming the worst findings and the hosts they are on, instead of a metric card whose one number could only tell you to go and look somewhere else. Collector coverage sits in the same widget, because "0 critical" across a fleet where most hosts are not reporting is the one number on that page that reads as good news while meaning the opposite. Posture in the sidebar now carries a badge count.
  • Resource history per server, named services on every listening port, and row detail modals throughout posture.
  • Per-server expected public ports, bulk acknowledge / mute, and posture exports (findings, listeners, services).

Changed

  • One Filters button and a drawer, everywhere. Lists with several dropdowns above them put six controls and a search box on one row, which wrapped to two rows on a laptop and grew every time a filter was added. The search box stays; everything else moved into a drawer behind a Filters button, with a count of what is applied, a one-click clear, and Apply/Cancel — so changing four filters on a server-paginated list is one request instead of four, and a filter opened by mistake can be backed out of. Applied across seven lists, and the same drawer becomes the app's bottom sheet on a phone.
  • The findings inbox is sections, not tabs. Tabs made three of the four views invisible: nothing on the page said a muted, acknowledged or expected finding existed, so the only way to remember them was to already know. Every count is now on screen at once, with the open queue expanded and the rest closed. The same change on the Posture page and the server's Open findings tab; Services and Ports lost their duplicate tabs and are one table again.
  • Collector coverage is grouped by what you can do about it. A flat list of hosts with no collector mixed one needing a password with a Windows box that can never run one, and offered "Install on all of them" — which handed every one of them to a planner that immediately refused half, so the number you clicked was never the number that ran. Coverage now reads the same plan the installer runs from, grouped into ready to install, needs credentials, stopped reporting, already done, and cannot run it.
  • Hosts that cannot run the collector are no longer counted as a gap. Windows and RDP-only hosts were counted as "not installed", making the fleet read as permanently short of covered with no action that could ever close it. They are now reported separately and excluded from the coverage count. On upgrade this will look like your numbers changed — an org with Windows hosts will see its "X of Y reporting" denominator drop.
  • Security posture on Customer Details is a Dashboard-style widget, with one "View all" affordance and the install action attached to the coverage warning it remedies, rather than two identical text links in the corner.
  • The whole 1.6 posture surface at phone width: compact tiles, mobile cards for findings and ports, shared section titles, and table-level actions such as Export no longer disappearing below md.
  • Sortable columns across the app, real breadcrumbs, and context-aware back links.

Fixed

  • The Posture tiles now count what the table shows. The summary counted active servers while the findings half counted every server in the organization, so the tiles and the list under them described different populations — visible only when a server was deactivated. The tiles also covered Critical and High alone, so four rows could sit under tiles totalling one.
  • Firewall rules were reported twice. ufw lists an IPv4 and an IPv6 rule per entry; the collector deduplicated on the port spec but not on the source column, so Anywhere and Anywhere (v6) survived as two rules and roughly forty entries produced eighty findings.
  • Running containers were collected and then shown nowhere. The inventory loaded stopped services only, so a host running forty containers wrote all of them to the database and displayed none — the data existed, the UI looked complete, and nothing said otherwise.
  • Severity filters returned 400 on every click. The API expects uppercase severities and the UI sent lowercase.
  • Every enabled-but-inactive oneshot systemd unit was reported as a stopped service, burying real ones under fifteen rows of snapd.*, apparmor and e2scrub.
  • A second Quick connect button on the Servers page: the top bar already renders the same one.
  • Double empty states and double metric rows on the Posture and Services pages; the expected-port dialog now closes on save.

Security

  • Install certificates are minted, bound and revoked. Certificates issued for an install are persisted and bound to the host they were issued for — which is also what makes them work, since check-principals verifies every certificate against the API on the way in. They last 300 seconds, name one principal, carry permit-pty and nothing else, and are revoked the moment the install returns rather than being left valid for the rest of their lifetime. The ephemeral private key never leaves memory and a temp directory that is removed in a finally.
  • Certificate signing was silently broken for standard permits. ssh-keygen rejects -O extension=permit-pty — extension= is for names it does not recognise, and the five standard permits are options in their own right — so any certificate requesting one would have failed to sign at all. Dormant only because the direct-issuance route defaults to requesting none.
  • Identities created by storeAsIdentity are organization-scope only and never personal-vault items, and a name collision with a different username fails the import row loudly rather than binding a server to someone else's identity.

Migration notes

Shellius v1.6.0

Choose a tag to compare

@github-actions github-actions released this 20 Sep 20:55
638f2f5

Added

  • Customer scope for users and groups. A user can now be limited to specific customers instead of the whole organization. Scope is granted on the user (Administration → Users) and on groups (Administration → Groups), and a person's effective scope is the union of their own grants and every group they belong to — a group with "entire organization" lifts any narrower personal grant, which the forms say out loud. Servers, customers, access requests, certificates, sessions, keystore, search, posture and the dashboard all filter by it. Users with no scope restriction behave exactly as before. See docs/rbac/customer-scope-spec.md.
  • Security posture. A new Posture page and a per-server Posture tab show what each host actually exposes: listening ports with the process and unix user behind them, firewall engine and state, and resource samples over time. Findings (PORT_EXPOSED, FIREWALL_OFF, FIREWALL_STATE_UNKNOWN, …) are long-lived records that open, re-open, escalate and resolve, so "open for 14 days" and "who resolved it" are answerable. Each can be acknowledged or muted with a reason for a chosen period.
    • The collector reads iptables/nft NAT as well as ss, so a container published with userland-proxy=false — which has no host listener at all, only a DNAT rule — is still reported. Where IPv4 and IPv6 firewall policy disagree it reports FIREWALL_STATE_UNKNOWN rather than quietly answering for IPv4.
    • Alert rules (Administration → Posture) route findings by severity, code, customer and environment to named users, roles or a group, in-app and by email, with a throttle window, opt-in resolution notices and an optional escalation after N hours. The recipient list is intersected with customer scope, so an alert can never be the thing that reveals a server someone is not allowed to see. A quiet default rule (CRITICAL → admins) is seeded for every organization.
    • Retention, collection interval and an org-wide expected public ports list are configurable; a daily prune job enforces retention.
  • Break-glass is now usable. A break-glass policy can be invoked from the server's More menu: Shellius verifies the person with their authenticator when two-factor is enrolled and with an emailed one-time code when it is not, then grants a short, fully audited session. Only servers matched by a break-glass policy offer it, and a DENY policy still wins.
  • Group membership from the user form. Creating or editing a user now assigns groups directly, with the scope consequences of each group spelled out in the form.
  • A single "Bootstrap host" wizard. Every install entry point — server detail, the servers list, and the Posture page's collector coverage list — opens the same two-step wizard: automatic (Shellius connects over SSH) or manual (copy a command), then SSH bootstrap, posture collector, or both. Automatic installs can use a saved Keystore identity instead of pasting a key or password.
  • Servers that authenticate with a stored identity can be bootstrapped. Previously they offered no install option at all; the identity that already reaches the host is now preselected as the way in, so it can install the posture collector or upgrade the host to certificate authentication.
  • Collector coverage list. The Posture page's "reporting X of Y" tile opens the list of the other hosts, grouped by not installed / stale / reporting, with a per-row install action.

Changed

  • Policies moved into Administration, directly below Roles. A Role says what you may do in Shellius; a Policy says which hosts you may reach — the two were easy to confuse, and putting them side by side is the clearest fix. /policies still redirects, and the g p shortcut is unchanged.
  • Sidebar regrouped into Overview (Dashboard, Terminals, Notifications), Inventory, Access (Access requests, Certificates, Keystore) and Security & audit (Posture, Sessions, Audit log).
  • The Posture page states plainly that it is a host-only view: cloud security groups and external firewalls are not read yet, so a port shown as exposed may be blocked upstream and one shown as closed may still be reachable.

Fixed

  • Scrolling inside the terminal on phones. Dragging inside the terminal scrolled the page instead of the scrollback, because the element the gesture landed on is a sibling of the scrolling viewport rather than a child. Touch drag and flick now scroll the terminal itself; full-screen programs such as less and vim are left alone.
  • Posture alerts were never delivered. The dispatcher wrote a notification type that is not in the database enum, so every write failed and was swallowed by the per-recipient error handler: rules fired, findings transitioned, and nobody was told. Also repairs the notification icon map, whose keys had drifted from the enum so every notification showed a generic bell.
  • Double empty state on the Posture page.

Security

  • Customer scope could be bypassed on a direct customer lookup. The scope predicate contributes an id key, so spreading it into a query that already looked up a customer by id silently replaced that lookup: a scoped user requesting a customer they are allowed to see could receive a different customer's record and server breakdown. Five call sites were affected, including update and delete, which made it a wrong-target write and not only a read. All now compose the predicate instead of overwriting, and the hazard is documented where the predicate is defined.
  • Host provisioning and bootstrap tokens are scope-checked. Both could previously be aimed at a server outside the caller's scope.
  • Out-of-scope records return 404, not 403. A 403 confirms that the record exists, which is itself a disclosure.
  • Aggregate counts are scoped too — key deployment counts no longer include servers the caller cannot see.
  • Posture alert emails escape interpolated values: finding text carries process names and unix users read off the host, and a compromised host must not be able to inject markup into an administrator's mailbox.

Migration notes

  • Four migrations apply on start: 20260920163811_add_customer_scope, 20260920170305_add_posture, 20260920184744_add_group_access_scope, 20260923000000_posture_notification_type. No manual steps.
  • Nothing changes for existing users by default. Everyone stays unscoped (full organization access) until a customer scope is granted.
  • New permissions (posture.read, posture.mute, posture.settings, access.break_glass, scope management) are granted to existing roles by tier on first boot.
  • The posture collector is not installed on any host automatically. Install it per host from the Posture page or the server's Bootstrap host wizard; hosts without it simply report nothing.