Repository navigation
Stackurity v0.4.0
Security
Found by turning the security documentation into tests (see "Verification" in docs/security-audit.md); each fix has its own commit.
- Workspace admins could disable shared intelligence feeds for every workspace. Enabling or disabling a feed now needs an instance administrator.
- A workspace admin could set the password, name or status of an instance administrator whose only workspace was theirs. That now needs an instance administrator.
- Zoned IPv6 link-local addresses (
fe80::1%eth0) passed the private-network check for webhooks, Jira, SMTP, Git and Kubernetes destinations. IPv4-compatible IPv6, local-use NAT64 and site-local ranges are now blocked too. - A workspace could point its routes, assets, findings and teams at another workspace's channels, teams and users by id, and probe which ids exist. Migration 0025 adds database triggers that keep references inside the workspace; team members must be members of the workspace.
- The output caps on plugins, git, stack packs and runtime detection were never applied. They now are.
- Image scans for workspaces without private network access run Syft through a guarded proxy, so DNS rebinding and redirects to internal addresses are refused.
- Versions longer than 256 characters are no longer compared (they could keep the shared evaluator busy for hours); such components are reported as not evaluated.
- Git repository URLs with IPv4 in decimal, octal or hexadecimal notation, or with a query or fragment, are refused; clone errors redact the Basic credential.
- One malformed or over-long entry in a lockfile or an installed package no longer makes the whole scan invalid; it is skipped and reported as partial coverage.
- A release
currentlink must resolve inside the application, through every link. - Delivery errors redact Slack and Teams URLs in every form; plugin logs and errors redact the plugin's credentials; failed sign-ins no longer record what was typed as the email unless it is an existing account; malformed Git and Kubernetes URLs are not echoed in errors.
Added
- Update notices for administrators. Every 12 hours one server replica reads the public list of Stackurity releases from GitHub (an anonymous request with a generic
User-Agent; no version, id or other data about the installation is sent) and compares it with the running version locally. Instance administrators see a banner when a newer release exists (dismissable per version) or a stronger one when a newer release has a Security section (it can only be snoozed for 24 hours), and Settings → What's new (/updates, also linked from the version in the sidebar) shows the notes of every newer release with upgrade steps.GET /api/v1/updatesreturns the same for instance administrators.STACKURITY_UPDATE_CHECK=offturns the check off entirely, andSTACKURITY_UPDATE_URLpoints it at a mirror; see docs/network.md. - Coverage states. Every asset says how far its findings can be trusted: Healthy and recently evaluated, Stale intelligence, Failed collection, Partially evaluated, Unsupported or Awaiting verification, each with its reason (
GET /api/v1/assets/{id}/coverage-summary, the asset page, the Coverage page and the overview). Inventory rows say whether they were checked, and the Inventory and component pages mark each component that was not ("not checked: "). Seedocs/compatibility.md. - A matching corpus of real advisories for every supported ecosystem (
internal/intel/testdata/corpus), run through the whole pipeline in the tests. - How priorities are decided (
docs/risk.md): every input and factor, how missing evidence is scored and why, a table of 36 validated scenarios and the invariants the scoring keeps. A scenario suite and property tests ininternal/riskcheck them on every build. - Each finding records the risk policy version that produced its priority (
priority_policy_version;policy_versionin the explanation is now that version, withcurrent_policy_versionnext to it). - Risk policy changes are audited in the same transaction, with the full policy before and after, both versions and the changed fields.
PUT /risk-policyreturnsversion,previous_versionandchanged. - Explanations name the evidence behind exposure (set on the asset, reported by the collector, or a workload running the image) and say when a severity is missing.
- Release checks in CI (
.github/workflows/release-checks.yml, on pull requests to main and before every release). For each earlier release still in use (0.2.0, 0.3.0 and 0.3.1, listed in the workflow), a job installs it from GHCR with demo data, a second workspace and findings, starts the current build on the same database and checks that migrations apply,/readyzis 200, the API answers and nothing was lost; restores apg_dumpof the result into a new PostgreSQL server (which has nostackurity_approle) and checks again; and runs the documented rollback. A tag publishes nothing unless these pass. Run them locally withtools/release-checks/upgrade.sh. docs/install.mddescribes how to roll back an upgrade (recreate the database, restore the pre-upgrade backup, start the previous image), anddocs/remediation.mdhas a "Failure handling" section on what automatic fixing does when a change, an agent or the network fails.- Crash recovery tests (
internal/crashtest, run in CI): the real server is killed during a feed refresh, an inventory upload, a notification delivery, a periodic task and a job, and restarted.docs/reliability.mdlists what is guaranteed (previous data kept, work resumed or failed visibly, findings never duplicated, notifications at least once with a delivery id to deduplicate) and how each is tested. - The Helm chart is published and signed with each release:
helm install stackurity oci://ghcr.io/stackurity/charts/stackurity --version 0.4.0. Also new outside this repository: the GitHub Actionstackurity/scan-action@v1and the Homebrew formulabrew install stackurity/tap/stackurity.
Changed
- An asset with no environment adds
environment_unknown(default +0.5) instead of scoring like a known non-production asset, and SOS rules that require production match it, as unknown exposure already matches rules that require exposure. A confirmed known-exploited finding on an asset with no environment is now SOS; set environments to avoid it. - The risk policy editor rejects weights that point the wrong way (for example negative points for known exploitation, or unknown exposure weighted above exposed). Stored policies keep working until next saved.
PUT /risk-policyfills fields it leaves out with their defaults instead of 0, as reading a stored policy already did.- Automatic fixing (Pro): an approval by a person lapses if the rollout has not started within 14 days; the plan is cancelled and proposed again for a fresh decision. A host whose package changed since the plan was made is dropped from the plan before release, instead of failing the rollout when its agent refuses the stale change.
- The agent retries a remediation result report through brief network or server outages (four attempts). The server accepts a repeated report without recording it twice, and answers 409, not 404, to a report that contradicts what is recorded.
- GitHub release notes are now this changelog's section for the version, instead of a generated list of pull requests, and the release workflow fails when the section is missing. Tags with a prerelease suffix (
v0.4.0-rc.1) are published as prereleases.
Fixed
These change findings after upgrading: expect new findings on Rocky Linux and RHEL 10 hosts and for Drupal modules, and fewer on Ubuntu ESM and FIPS hosts.
- Rocky Linux binary packages such as
openssl-libswere never matched: Rocky's advisories name source packages. - RHEL 10 hosts matched no advisory: Red Hat publishes RHEL 10 per minor release.
- Ubuntu Pro FIPS channels were applied to regular builds (patched ESM hosts were reported affected, with a FIPS build as the fix) and regular fixes to FIPS builds. For FIPS builds, FIPS-updates is preferred over the frozen certified channel.
- Composer advisories published for another repository (Drupal contrib modules) were never matched.
- Fixes published only for SLES for SAP or HPC are labelled as needing a subscription on plain SLES.
- A release the feed has no advisories for (too new, or no longer covered) is reported as not evaluated instead of clean; so are unreadable RubyGems versions, unresolved Maven properties and, when a record matched without a comparison, any unparseable version.
- The Go standard library reported as
go1.22.1and PyPI names with dots are matched. - Evaluation gaps are recorded per inventory row: evaluating another asset or workspace no longer clears a gap. Reasons say exactly why a component could not be checked.
- A saved SOS rule picked up the conditions of the default rule at the same position that it did not set itself (for example
production: true), so the rule applied was narrower than the one shown. - An image run by one internal workload and one whose exposure could not be decided scored as internal; it is now unknown.
- Lower confidence (
needs_validation,stale) no longer raises a score that is already below zero. - Automatic fixing (Pro): a change already on a host when its plan was revoked, its policy disabled or its rollout halted was never verified or timed out. It stayed in flight for ever and blocked every later plan for that package on that host.
- Automatic fixing (Pro): when an agent applied a change but its report was lost, the change timed out after two hours and, with
auto_rollback, was rolled back although it had worked. Fresh inventory now settles such changes, and timeouts never queue a rollback, since nothing is known to have changed. - Automatic fixing (Pro): a change released shortly before the maintenance window closed, and not fetched in time, failed the whole plan at its dispatch deadline. It now waits for the next window.
- Automatic fixing (Pro): two rollout loops running at once (replicas whose worker leases overlapped) could each release up to
max_concurrentchanges. - Automatic fixing (Pro): a plan whose changes were all cancelled was marked
succeeded. A halted plan now says which host failed and why. - A background job whose server was killed on every attempt (for example a feed sync that runs the server out of memory) was retried for ever: interrupted attempts did not count towards
max_attempts. It now fails after the limit, with the interruption as its error, and shows on Platform health and in metrics. - An interrupted feed sync showed no error until its retry finished; it now shows as failing ("the sync started ... was interrupted") as soon as the next attempt starts.
- A crash between storing new advisories and queueing their evaluation could delay new findings until the daily evaluation; evaluation is now queued before the feed's cursor moves.
Upgrading
- Update agents along with the server: they retry remediation reports, which the server now expects.
- Migrations 0023 to 0025 run at startup. 0023 rewrites every inventory row once and replaces
evaluation_gapswith a view; 0025 adds triggers that refuse references to another workspace's channels, teams and users (existing rows are kept). Each feed's first sync after the upgrade scans its stored advisories once to learn which releases it covers; for Ubuntu this sync is slow. - Expect findings to change: new ones on Rocky Linux and RHEL 10 hosts and for Drupal modules, fewer on Ubuntu ESM and FIPS hosts. A confirmed known-exploited finding on an asset with no environment is now SOS.
- Approvals by a person now lapse after 14 days if the rollout has not started.
- The update check contacts
api.github.comevery 12 hours; add it to an egress allowlist or setSTACKURITY_UPDATE_CHECK=off.
Full changelog: https://stackurity.dev/changelog.html