Repository navigation
CveDeck 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.