Repository navigation
Releases: reprodev/cvedeck
Release list
CveDeck 0.10.2
Changed
- FastAPI 0.143.0, pydantic 2.14.0, SQLAlchemy 2.1.4 and hypothesis 6.168.5
(Dependabot #32), and vite 8.3.4, vitest 5.0.3 and @vitejs/plugin-react
6.1.2 (Dependabot #31). FastAPI 0.143 makes its OpenTelemetry export opt-in;
CveDeck ships neither the OpenTelemetry SDK nor an exporter and sets no
endpoint, so nothing was exported before and nothing is now. Both sets were
trialled in a scratch copy first; every test passes, and the frontend
lockfile changes versions only and gains no install script.
CveDeck 0.10.1
Fixed
- A new Ubuntu release's kernel is checked in its first scan. The Ubuntu
kernel feed was refreshed for the releases the fleet already ran, so the
first host of a new release -- the first 24.04 machine, say -- read "feed not
fetched yet" until the next scheduled refresh, up to a day later. A scan that
meets an Ubuntu release with no fresh feed now fetches it there and then, once
per release per batch, and checks the kernel in the same scan. A failed fetch
leaves the scan whole and the kernel saying why; it is not retried for every
host. Never when the feed is switched off, nor in demo mode.
Added
- Old kernels get a command, not just advice. When kernels nothing runs
carry CVEs, the kernel card now offers the distribution's own way to remove
them --apt autoremove --purge,dnf remove --oldinstallonly,zypper purge-kernels-- each of which keeps the running kernel and the newest. apt
also offers other packages nothing depends on, and the card says to review
its list. Alpine and Arch replace the kernel in place, so nothing is offered.
CveDeck 0.10.0
Upgrading: Ubuntu hosts' kernels are checked from this release on. A new
feed is fetched, once a day, for each Ubuntu release the fleet runs:
Canonical's OVAL CVE feed, from security-metadata.canonical.com. Nothing
about a host is sent, but the request names the release.
CVEDECK_UBUNTU_KERNEL_FEED=off turns it off. The first refresh after
upgrading fetches it; the next scan of each Ubuntu host checks its kernel. The
migration (a7c2e9d4b1f6) adds one table and runs on start-up.
Added
- Ubuntu's kernel is checked against Canonical's own feed (Req 12.11).
OSV's answer for an Ubuntu kernel runs to gigabytes, so 0.9.0 left it
unchecked. Canonical's per-release feed is 11.9 MB for 22.04; it is
streamed (144 MB, 23 s) into one compressed table per kernel flavour, and a
scan checks the running kernel against its own flavour locally. Canonical
rates every kernel CVE, so they carry a severity. Measured on the real feed
for the generic 22.04 kernel: 5.15.0-25 has 10,704 applicable CVEs, 6,450 with
a fix, which are listed; the current 5.15.0-198 has 4,254 with no fix, which
are counted (Req 12.9). - The machine page says why a kernel was not checked: feed off, not
fetched yet or stale, release unknown or out of support, running kernel
unknown or not from a package, flavour not in the feed, or not scanned since
the feed arrived. On Ubuntu it also says only the running kernel is checked. - Each Ubuntu release's feed appears with the others, as "Ubuntu 22.04 kernel".
Security
- Better than reading the feed as written, in two places. The kernel
flavour comes from the running image's package, not fromuname -r: on 22.04
the generic and RISC-V kernels share aunamepattern, and every host would
otherwise carry the RISC-V kernel's CVEs. And the image's installed package
version is compared with each fix, not the fragment ofuname -r: compared
that way, a host running exactly the fixed kernel reads as vulnerable. - A feed that changes shape is refused, not believed. Only the shapes found
in the real 22.04 and 24.04 feeds are accepted; anything else fails the
refresh and keeps the previous table, and the kernel reads as not checked. - A healthy kernel feed cannot stand in for exploit data. Whether the fleet
view shows exploitation is decided on the KEV and EPSS feeds alone; counting
any usable feed would have shown "0 exploited" with no catalogue behind it.
Changed
- FastAPI 0.142.1, uvicorn 0.54.0, SQLAlchemy 2.1.1, hypothesis 6.168.3
(Dependabot #28), and vitest 5.0.2 (Dependabot #27), each trialled in a
scratch copy first. - The counted kernel CVEs now name every kernel that was assessed, so a kernel
with nothing counted and one that was not assessed read differently.
CveDeck 0.9.1
Changed
-
Kernel lookups take two-thirds the time and three-quarters the memory.
Measured with the real client on an old Debian 12 kernel, 8,180 advisories,
identical results before and after: 95 s and 286 MB at peak, now 61 s and
221 MB. Each OSV record is cut down to what matching reads as its page
arrives -- the enumerated affected-version lists were most of the answer --
and the re-read of a paged release answer now runs alongside the full fetch
instead of before it. -
SQLAlchemy 2.1.0 and hypothesis 6.168.1 (Dependabot #26), and vite 8.3.1
(Dependabot #25). SQLAlchemy 2.1 is a minor release and was trialled in a
scratch copy first; every test passes, the migration tests included, and the
frontend lockfile gains no install script.
Fixed
- A fix in another release is named as people write it. A fix note read
"upstream fix in Red Hat:enterprise_linux:10.2: 0:6.12.0-211.58.1.el10_2";
it now reads "upstream fix in RHEL 10.2: 6.12.0-211.58.1.el10_2". Red Hat's
extended-support streams read "RHEL 9.2 EUS" and so on. Display only: which
release a host is, and which advisories are filtered, is unchanged.
CveDeck 0.9.0
Upgrading: the first scan after this release will report kernel findings on
Debian, RHEL, Rocky, AlmaLinux, SUSE, openSUSE and Alpine hosts. They were always
there. 0.8.15 matched every package by its source except the kernel, and each
host said so. The kernel is now matched too, and the machine page shows it apart
from everything else. Ubuntu's kernel is the exception, and its hosts still say
their kernel was not checked. The migration (f3d9a1c5e7b2) adds one nullable
column and runs on start-up.
Security
-
An answer OSV sends in pages is read to the last page. OSV splits a large
answer into pages and says so; the client read the first and stopped, and a
first page looks exactly like a complete answer. Every page is now read, and a
page that fails -- or an answer past a fixed ceiling -- makes the whole lookup
unanswered rather than the part that arrived (Req 2.9). A paged answer about
the host's own release is read in full too, because leaving it unknown
switched off the release filter (Req 14.7) for exactly the largest answers. -
The kernel is matched against its advisories (Req 12.5). It is asked
under its source,linux, wherever that is practical -- measured against live
OSV for an old kernel: Debian 12's answer is 9,171 advisories in 5 pages
(56 MB), RHEL, Rocky, AlmaLinux, SUSE and Alpine a page or two. A signed
kernel image is asked under the source it wraps: Debian'slinux-signed-amd64
has no advisories of its own. -
Ubuntu's kernel is not, and says so. Each Ubuntu kernel advisory lists
every Ubuntu kernel flavour, and the answer for one 22.04 kernel was still
arriving after 51 pages and 3.5 GB. Hosts matched against a tracker their
kernel was not built from (Arch, Fedora, Oracle Linux, Amazon Linux) are not
either. Their pages state that the kernel was not checked. -
What the host can fix is listed; the rest is counted. Measured with the
real client against live OSV, an up-to-date Debian 12 kernel matches 2,319
kernel CVEs: none fixable in Debian 12 -- 2,187 fixed only in Debian 13, 132
fixed nowhere -- and Debian publishes no severity for any kernel CVE. A kernel
from early 2024 matches 8,137. A kernel CVE is a finding where the host's
release has a fix, where it is known exploited, or where its exploitation was
not checked; the rest are counted per installed kernel and the machine page
states them, by where their fix is (Req 12.9). An unscored kernel finding
ranks after every scored one -- under the userland rule thousands would sit
above every High -- and known exploitation still ranks first (Req 12.10). -
A kernel lookup that fails costs the kernel, not the scan (Req 12.7). The
kernel is asked apart from everything else. If its answer fails, the userland
findings are kept, the scan is marked partial, and -- as for any partial scan
-- the kernel findings it could not re-check are kept rather than resolved.
Added
-
Kernel matching takes time on Debian. The first host scanned with an old
Debian kernel waits about 100 seconds for the answer (thousands of
advisories across several pages); every other host on the same kernel
version in that scan reuses it. -
Which installed kernel is running (Req 12.6).
uname -rhas been
collected since Req 12 and is now compared with each installed kernel, per
package manager, against real package lists from Debian, Ubuntu, Rocky,
AlmaLinux, Alpine, openSUSE and Arch. Every kernel finding says whether it is
in the running kernel. A host that did not report its kernel has every kernel
finding treated as live; a container, whose running kernel is its host's,
says the running kernel was not checked. -
A kernel card on the machine page (Req 12.8): the running kernel, how many
CVEs are in it and how many of those are known exploited, and the one action
that fixes them. That is a reboot when the kernel a reboot would bring up is
already installed and does not have the finding -- an upgraded host that was
never rebooted is told to reboot, not to upgrade again. Otherwise it is an
upgrade and a reboot, and on Debian the command upgrades the metapackage
(linux-image-amd64): a fixed kernel arrives under a new package name, so
upgrading the installed one does nothing. CVEs only in kernels installed but
not running are counted separately and sort after live findings. -
A Kernel / Userland selector on the findings list, and
is_kerneland
kernel_runningon every finding, in the API and the CSV export. Kernel
findings stay in every total.GET /api/machines/{id}/kernellists a host's
installed kernels.
Fixed
- Two installed kernels of the same name are two kernels. RPM and SUSE
install each kernel beside the last askernel-coreorkernel-default, and a
finding was identified by its CVE and package name alone -- so a CVE in both
would have been one finding against whichever came first. A kernel finding is
now identified by version as well. - The host's bulk fix plan leaves the kernel out and says where its fix is,
rather than offering an--only-upgradeline that cannot bring a newer kernel. - The stored inventory's running kernel and pending-reboot state were never read
back. Nothing used them until now; they are read now.
Changed
- The demo fleet carries kernels: one host upgraded and not rebooted, one with
an old kernel still installed, and an Ubuntu host whose kernel is not checked.
CveDeck 0.8.15
Upgrading: the first scan after this release will report many more
findings. They were always there. CveDeck had only ever asked about packages
by their binary name, and the distributions publish advisories under the
source package. On out-of-date test hosts scanned end to end, the same hosts
went from 38 findings to 204 (Debian 12.0), 36 to 235 (Ubuntu 22.04), and 6 to
22 (Alpine 3.18.0) -- and not one finding the previous release reported was
lost. Scan history marks the new ones as new, which is true: nothing on the
host changed, the question did.
Security
-
Packages are looked up under the name the distribution publishes them
under. OSV has 46 advisories for Debian 12'sglibcat 2.36-9 and none for
libc6, the binary actually installed; 51 foropenssland none for
libssl3. A library was checked only when some binary happened to share its
source's name, and glibc never was -- so CVE-2023-4911, the glibc root
escalation known as Looney Tunables, was never reported on any Debian or
Ubuntu host. The collector now records each package's source package and
source version (dpkg, rpm, apk and pacman all report them), and every package
is asked about under its source and its own name: Debian, Ubuntu, Alpine,
Rocky and SUSE publish under the source, AlmaLinux sometimes under the binary,
and asking under only one kind loses the other's (Req 1.12, 2.8). -
A CVE is one finding per source package, however many binaries it ships
as. It is reported against one representative installed binary -- the one
named like the source when there is one, so a finding already matched through
it keeps its history -- and the finding lists every installed binary of that
source. The fix commands, the host's fix plan and the CSV export name them
all: upgradinglibc-binalone leaveslibc6vulnerable. The blast radius is
what depends on any part of the source (Property 17). Checked end to end:
running the generated command on a test host upgraded both glibc binaries and
the rescan resolved every glibc finding. -
RPM versions carry their epoch. The rpm query dropped it, so every fix
published as1:3.0.7-...compared as newer than any installed version. On
a Rocky 9 host 21 CVEs were reported against packages already past their fix,
each checked version by version; an AlmaLinux 9 image reported 89 advisories
where 18 are real. All are gone.
Documented
- The kernel is not checked yet, and each host now says so. The kernel's
source has thousands of advisories per release and OSV pages the answer, so
it gets its own release. Until then each host's page states how many kernel
packages were not checked, andGET /api/machinesreports
kernel_packages_unchecked-- a kernel with no findings no longer reads as a
clean one (Req 12.5).
Changed
- React 19.3, and psycopg 3.3.6 (Dependabot #24, #23). React 19 was trialled in
a scratch copy first: the type check, every frontend test and the build pass
unchanged, and the lockfile gains no install script. - The sync to an online database now carries each package's dependencies, which
it had silently dropped since they were added, and its source.
Verify the image before running it:
gh attestation verify oci://ghcr.io/reprodev/cvedeck:0.8.15 --owner reprodevCveDeck 0.8.14
0.8.13 hardened what a scanned host, a browser and an anonymous visitor could
reach. This release turns the same question on everything else that comes in
from outside: the actions that build and publish the image, the intel feeds,
and the one page that called a third party.
Security
-
The release pipeline trusts nothing that can be moved under it. The
release job holds the token that publishes the image every user installs, and
it ran eight third-party actions by movable tag. Every action in both
workflows is now pinned to a full commit SHA, with the release in a comment
that Dependabot updates alongside it. The release builds cold -- it no longer
reads the cache CI writes from every pull request -- and a dispatch tag
reaches the shell only through the environment, validated asvX.Y.Z, never
expanded into the script. Checkouts no longer keep the job's token.
tests/test_workflow_pins.pyholds all of it. -
The image is scanned before it is published, and signed after. Trivy
scans the built image; a critical vulnerability with a published fix stops the
release. The pushed image carries BuildKit provenance, an SBOM, and a
Sigstore-signed build-provenance attestation. Verify it with
gh attestation verify oci://ghcr.io/reprodev/cvedeck:0.8.14 --owner reprodev
-- see DEPLOYMENT.md, "Verifying the image". CI runs the same scan over
critical and high findings, as advice. -
The intel feeds are bounded. Every client read its whole response into
memory, and EPSS gunzipped it with no limit, so a compromised mirror or a
misconfigured feed URL could send a small file that expanded until the process
died. Responses are now read up to a ceiling counted after decoding, and the
gunzip stops at its own. The ceilings were set from measurement and sit far
above real data -- the largest OSV answer found was 61 MB, for the kernel on
Ubuntu 22.04. EPSS redirects stay on, because its "current" URL redirects to a
dated file, but only to the host that was asked. A breach is a failed refresh
that keeps yesterday's cache, or an OSV lookup reported as unanswered -- never
an empty answer (Req 10.18). -
A collector with no host-key store refuses instead of trusting. The
fallback was paramiko'sAutoAddPolicy, which accepts any key from anyone.
Every production path passes a store, but the scanner engine's default
collector did not. It now refuses any unknown host before sending a
credential, and nothing in the application may constructAutoAddPolicy
(Req 17.12).
Removed
- The Swagger page at
/api/docs. It loaded its code from jsDelivr, so every
visit told a third party the address of the instance and that it runs
CveDeck. Nothing in the product used it./api/openapi.jsonremains, behind
sign-in, for scripts and for any API tool you run yourself; every response now
carries one strict content policy with no exception (Req 16.21).
Fixed
- A slow resolver can no longer hold a discovery sweep. Reverse-DNS lookups
had no timeout; they now give up after two seconds and the host is listed
without a name (Req 8.13). - A manual release dispatch built images with no version tags. On a
workflow_dispatchthe ref is a branch, so the semver tags came out empty; the
validated tag is now passed explicitly.
Changed
Verify-Release.ps1now sends every demo write and requires a refusal, and
checks the image's content policy, the absent Swagger page and the absent curl.
Upgrading: nothing to do. If you used /api/docs, load /api/openapi.json (signed in) into an API tool you run locally. From this release on, verify an image before running it:
gh attestation verify oci://ghcr.io/reprodev/cvedeck:0.8.14 --owner reprodevCveDeck 0.8.13
A release with no features in it. The lock -- sign-in, sessions, tokens, pinned
host keys -- held up to a full review; what did not were the doors around it:
the routes a demo left open, what a scanned host was allowed to send back, the
commands handed to an operator's root shell, and what a browser was never told.
Security
-
Demo mode is read-only, for every route at once. A demo needs no login,
and through 0.8.12 four write routes had no demo guard: remediation add and
update, the sync, and discovery enroll. Enroll upserts, and overwrote an
existing machine's hostname, platform and scan status, so any visitor could
rewrite the seeded fleet for every visitor after them. Refusals were per
route, which is how 0.8.8 and 0.8.9 each missed one too; they are now attached
to the routers as a whole, like the login gate, so a route added later is
refused without being named. A test walks every route with an empty body and
asserts the refusal (Req 15.9). -
Copyable fix commands quote every name that came off a scanned host. The
upgrade, purge and check commands copied into a root shell interpolated the
package name unquoted, and a crafted version string can end up inside that
name. Names are now quoted the wayshlex.quotedoes -- an ordinary name
stays bare, so commands read as before -- with a PowerShell equivalent for
Windows ids. Host-derived text in the fix script's comment lines loses its
line breaks, and a control character in a name becomes?. Property-tested
against a strict shell word splitter over arbitrary Unicode (Req 14.10). -
What a scanned host sends back is bounded. Remote output was read to EOF
with no size limit, and the 45-second timeout applied per read, so a host
trickling a byte at a time held a scan open indefinitely. Commands now stop at
32 MiB and 180 seconds, enforced by closing the channel rather than by a clock
a blocked read never returns to, and either bound fails the scan with the
reason -- never a truncated inventory, which would report every finding past
the cut as resolved (Req 1.11). -
Security headers on every response. There were none. The dashboard now
runs under a content policy allowing nothing but itself -- no CDN, no inline
script or style -- with framing denied, no referrer,nosniff, and a
permissions policy (Req 16.15). -
With login off, a request from another site is refused. The cookie
Origin check never ran without a session, so any page a user visited could
POST to an instance running withCVEDECK_AUTH=disabled. A state-changing
request whoseOrigin,RefererorSec-Fetch-Sitenames another site is
now refused; one that names nothing, which is every script, is still served.
CVEDECK_CORS_ORIGINS=*is refused at start-up (Req 16.16). -
CVEDECK_ALLOWED_HOSTS, against DNS rebinding. An Origin check cannot
see a rebinding page, which the browser thinks is same-origin; theHost
header can. Set the names you reach CveDeck by and any other gets a 400.
Loopback always passes, so health checks keep working. Off by default, since
defaulting it on would break every install reached by an unlisted name, and
the log warns while login is off and it is unset (Req 16.17). -
Sign-in attempts with made-up usernames are throttled. The throttle keyed
on address and username, so a new username each time never tripped it -- and
each attempt costs a full scrypt verification, about 32 MiB, by design. Failures
now also count per address, with a looser limit of 20, and at most four
verifications run at once (Req 16.20). -
cvedeck-admin reset-passwordrevokes every API token. It is the
recovery after a compromise and it ended sessions only, so a token planted by
whoever got in survived it (Req 16.12). Settings gains Revoke all
tokens (Req 16.18). -
A 422 no longer echoes the rejected value. FastAPI's default returned it
asinput-- for an oversized private key, or a scan target missing its id,
that was the key or the password. Requests also gain upper bounds on every
list and string a caller controls, and ports outside 1-65535 are refused
(Req 16.13, 16.14). -
The container writes its data readable by its own user only. The
database -- the fleet's inventory, token hashes, host-key pins -- was written
0644. The entrypoint now setsumask 0027and closes the volume to other
users on every start, fixing files an older release left open.PUID=0and
PGID=0are refused, as is aCVEDECK_DATA_DIRinside the image.
Added
- A security log.
app.securitylines record each scan, sweep and
connection test with who asked, from where and whether the server's SSH key
was used; each forgotten host key; refused cross-site requests; unknown or
revoked tokens; and throttled sign-ins. No credential appears in any of them
(Req 16.19). BIND_ADDRin the compose file and.env.example. The default stays
0.0.0.0; the docs now say that Docker's port publishing bypasses ufw.
Changed
- The compose file runs least-privilege:
no-new-privileges, every
capability dropped and six added back (CHOWN,DAC_OVERRIDE,FOWNER,
SETUID,SETGID,NET_RAW), a pids limit. Discovery's ICMP was verified
working under it. The admin-password secret its comments referred to now has
thesecrets:blocks to go with it. - curl is gone from the image; the health check is a Python probe.
- No dependency install script runs during the image build or
install.sh,
which runs as root.install.shpins pip like the Dockerfile. - The systemd units add
UMask=0077,ProtectClock,ProtectHostname,
ProtectProc=invisible,RestrictRealtimeandSystemCallArchitectures. - The nginx example adds HSTS, TLS 1.2+ and
server_tokens off-- and only
those, since CveDeck now sends its own content policy.
Documented
- WinRM is not usable yet, and SECURITY.md now says so. It claimed a Windows
connection test "does connect". It does not: the session is built with equal
read and operation timeouts, which pywinrm 0.5 rejects before any traffic, so
no password is ever sent. The collector also turns certificate validation
off. Both are left for the release that brings Windows matching, and are
listed under known limitations until then.
Upgrading: nothing to do. The first start closes the data volume to other users; a PUID/PGID of 0 is now refused (set CVEDECK_ALLOW_ROOT=1 if intended). If you run with CVEDECK_AUTH=disabled, set CVEDECK_ALLOWED_HOSTS. After cvedeck-admin reset-password, recreate the API tokens your scripts use.
CveDeck 0.8.12
The git hooks are this project's primary guard against publishing a secret. The
last three releases were all about checks positioned where they could not see
what they were written to catch, and nobody had turned that question on the
hooks themselves. It had two answers.
No database migration, no application code changes.
A secret added and then deleted was never caught
The pre-push content scan compared two trees — git diff BASE LOCAL_SHA. A file
added in one commit and removed in the next nets out to nothing, so it never
appeared in the diff and was never examined. That is exactly the case the check
exists for.
Three documents described the correct behaviour while the code did not have it:
the comment three lines above the scan ("the first catches a secret added then
deleted"), AGENTS.md §6 ("history included"), and the methodology document
("a secret removed in a later commit still leaks"). A specification with no test
behind it, since 0.6.0.
It scans per-commit patches across the pushed range now, so a secret is caught
by having been committed rather than by surviving to the tip. Verified both ways
round in a scratch repository — a private key block and a forbidden filename,
each added and then deleted before pushing, pass the old hook and are refused by
the new one, on a first push as well as an incremental one.
If you use these hooks, this is the release to take.
The hooks no longer publish one machine's release workflow
More than half of pre-push implemented something specific to how this
repository is published: a private staging repository, a clean room that
receives only snapshots, the config that tells them apart, the release identity,
version consistency across three files, and a spec-citation gate. None of it
does anything in anyone else's clone.
What stays published is the half that is useful in any clone — credential,
filename and content scanning, on commit and on push. The rest moved into an
untracked local overlay that the tracked hooks source when it exists and are
complete without. That is the same arrangement the local pattern list has always
used, for the same reason: a tracked file naming the things that must never be
published would publish them.
Nothing was leaked by the old arrangement. It was simply a public repository
carrying a description of how one maintainer's disk is arranged, for ten
releases, because nobody had read it closely enough to ask.
Contributing no longer promises a gate you don't get
CONTRIBUTING.md said the hooks "refuse to push a branch whose tests are red or
whose version strings disagree". Both checks only ever ran when pushing to this
project's own remote, so a clone had neither.
It now says what the hooks do, and points at CI — which runs both suites and the
image build on every pull request, so the feedback is not lost, it just comes
from the right place. A hook that refuses to back up work in progress is a hook
people switch off.
Upgrading
Nothing to do for a running instance. If you have the hooks enabled, pull and
they are already in effect:
git config core.hooksPath .githooks
docker pull ghcr.io/reprodev/cvedeck:0.8.12
Full changelog: https://github.com/reprodev/cvedeck/blob/main/CHANGELOG.md
CveDeck 0.8.11
0.8.10 fixed three guards that could not see what they guarded. This release is
what happened when that question was asked of the checks themselves.
No database migration. Pull and restart.
The fleet table was making the claim the host page stopped making
0.8.10 fixed the host page, which had been reporting "None actively exploited"
for a host where one finding of forty-one had been checked. Its notes described
the fleet view as already strict.
The fleet view is strict about the feed, not about the host. Its Exploited
column asked whether any feed was usable fleet-wide, and then printed the host's
KEV count — so a host holding unchecked findings got a confident 0, one
screen up from the page that had just stopped saying it. Feed health cannot
answer that question: a host re-scanned while a feed was down carries unchecked
findings however healthy the feed looks by the time you load the page.
The column now carries the same four states as the host page: a count when
something is confirmed, a plain zero only when every finding was checked,
0 so far when some were and none are listed, and a dash when none were
checked at all. GET /api/machines gained kev_unchecked_count, so anything
reading the API can make the same distinction.
Three tables had labels nothing read, and the guard skipped them
The discovery sweep's host table and both of the Settings page's — API tokens
and pinned host keys — carried complete, correct data-label attributes that
did nothing, because none of them sat inside the container that reads those
attributes at phone width. All three now reflow into cards like every other
table in the product.
The check that should have caught this began:
if (!text.includes("table-container")) continue;
That is: examine a table only if it is already in the card system. A table left
out of the card system — the thing the rule exists to prevent — was out of scope
for the rule by construction. It selects on <table now, and a companion check
requires every table to be in the card system at all.
The emoji rule beside it scanned the components and not the stylesheet, which is
the one file that can put a pictograph on screen through content: and so
bypass the palette entirely. There were no offenders; the hole is closed while
it is still empty.
A comparator that was correct by specification accident
compareBySeverity returned NaN for any two findings without a CVSS score,
which is every pair in the Unscored band. It behaved only because
Array.prototype.sort coerces a NaN comparison to zero — which happens to be
the right answer.
Found by mutation: inverting the ordering rule left all 412 frontend tests
green. The rule it implements — a finding whose advisory published a severity
band but no number leads its band, rather than falling to the bottom of a list
sorted by a number it does not have — had no test at all. Both are explicit now,
and both are pinned.
The demo can show the state it could not
0.8.10 added a partially-checked presentation and the demo fixture could not
produce one: every seeded host either carried confirmed exploited findings or
had never been checked at all.
cache-01.lan is now a host scanned again while the KEV feed was unusable. The
findings it already had are enriched; the ones that scan added were never
checked, and those are the four the catalogue lists as exploited. So every
number that can honestly be computed about that host reads zero, and the host
genuinely carries four actively exploited findings. That is the case worth being
able to see.
Also
- Contributors are now told how their change gets published. This
repository's history is published as snapshots, so a contributor's commit
cannot survive into it as their commit.CONTRIBUTING.mdsays so plainly
now, along with the credit actually on offer — by name in the changelog —
rather than leaving it to be discovered after the work is done. - Dependabot groups React with its own renderer and both type packages. A
major had movedreactand@types/reactto 19 while leavingreact-domand
@types/react-domat 18, producing a lockfile npm could not resolve. It read
as "React 19 is hard"; it was a group that split a set which must move
together. - Dependencies: alembic 1.20.0, SQLAlchemy 2.0.54, uvicorn 0.53.0, psycopg
3.3.5, hypothesis 6.168.0, fast-check 4.10, vitest 5.0.1. TypeScript 7 and
React 19 are deliberately not taken here; each is its own change.
Upgrading
docker pull ghcr.io/reprodev/cvedeck:0.8.11
Full changelog: https://github.com/reprodev/cvedeck/blob/main/CHANGELOG.md