Skip to content

feat(kensa): integrate Kensa v0.10.0 - #882

Merged
remyluslosius merged 10 commits into
mainfrom
feat/kensa-v0.10.0
Sep 28, 2026
Merged

remyluslosius merged 10 commits into
mainfrom
feat/kensa-v0.10.0

Conversation

@remyluslosius

@remyluslosius remyluslosius commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Integrates Kensa v0.10.0 and completes D-2 S-7, per CP projects/openwatch/V0_8_0_READINESS. Rebased onto main 25b1794f on 2026-09-27. Awaiting one founder merge decision. The earlier blockers are settled: S-3 is accepted as a v0.8 limitation (2026-09-26), and the long-term packaging design is the joint embedding decision (2026-09-27, CP features/KN-OW-024), which does not approve this PR by itself.

Commits

  1. 9cfa51b3 Kensa v0.10.0.
    • Pin bump; corpus goes from 769 to 779 rules, adding nist_800_171 and cmmc_l2.
    • List-override round trip (api-system-scan-config AC-11).
    • Docs.
  2. 1278018f Engine and corpus pairing (OW-081).
    • openwatch provides openwatch-kensa-engine; kensa-rules requires at least its own version.
  3. d635a119 configure_me and NIST lift.
    • Eleven-variable inventory; catalog test checks contents.
    • The nist projection counts 800-53 only (api-remediation AC-18).
  4. b7454d4b AC-09 in Go CI. The pairing is checked against the resolvers (rpm --test, apt-get -s).
  5. d9d2efd7 D-2 S-7.
    • Every framework label comes from Kensa's FrameworkFromID through internal/kensa.
    • Surfaces: the lens chips and panels, the rule library and scan-detail tags, the report picker, and new reports' scope labels (cover, OSCAL title, filename).
    • Signed content keeps the exact key. Older reports keep their stored labels, and their signatures still verify (api-reports AC-27).
    • Contract: system-compliance-lens 1.7.0 C-08, AC-14.
  6. 8c51e98a DEB preinst.
    • A DEB preinst refuses before any file is replaced, so a bare dpkg -i no longer leaves a newer corpus on disk.
    • Rollback is both packages together; the runbook says so.
    • release-upgrade C-06 is the boundary tested today; AC-10 added.
  7. 40ce447f Secrets baseline. Line numbers only, after the rebase.
  8. 1af21ea8 Packaging tests completed.
    • RPM transactions now run their scriptlets (no more tsflags=noscripts).
    • New scenarios: dnf upgrade of both packages, a joint rpm -Uvh, and single-package upgrades in both orders.
    • After every step, package-manager state and corpus contents are checked separately.
  9. 82809ee8 Protection scoped to what it covers.
    • C-06, the CHANGELOG and the upgrade runbook now say the check travels with the OpenWatch-built kensa-rules, and disclose that Kensa's package of the same name is not checked.
    • The installation guide no longer says rules update without an OpenWatch release.
  10. 6c3cb5cf CHANGELOG categories.

Visible changes

  • "CIS RHEL 9" reads "CIS (RHEL 9)", and "PCI DSS 4" reads "PCI DSS 4.0". NIST 800-53, "NIST SP 800-171 Rev 2" and "CMMC Level 2" are distinct everywhere.
  • Kensa formats only RHEL versions, so Ubuntu benchmarks read "CIS (ubuntu22)" (CP features/KN-OW-023).
  • With OpenWatch's kensa-rules, a rules-only upgrade onto an older openwatch is refused with nothing changed, on dnf, rpm -U, apt and dpkg -i.

What the packaging does and does not protect

Case Outcome Basis
OpenWatch-built kensa-rules 0.10.0 beside openwatch v0.7.1 or rc.5 Refused, nothing changed, on dnf, rpm -U, apt and dpkg -i Container tests below
Both OpenWatch packages together, in either tool, or one at a time in the working order Accepted, fully installed, corpus matches the payload Container tests below
Kensa-published kensa-rules beside openwatch rc.5 or earlier Not protected. Installs with no refusal, and the older engine cannot load it, so every scan fails Measured: Kensa's 0.10.0 package installed over v0.7.1 on Rocky 9; engine 0.9.0 loads 0 of 779 rules from it
Kensa-published kensa-rules beside this build Not checked. Installs in place of OpenWatch's. Today it loads: Kensa's 0.10.0 is byte-identical to OpenWatch's, and Kensa's 0.12.0 has identical rule files (only README.md differs); engine 0.10.0 loads all 779. A future Kensa corpus is not checked Measured on Rocky 9 and with pkg/kensa.LoadRules
rpm --nodeps, dpkg --force-* Not covered, by design C-06

The Kensa-published rows are disclosed, not prevented: C-06, the CHANGELOG known limitations and the upgrade runbook state them (CP bugs/OW-081, criterion 3b). The long-term fix is the accepted embedding direction, planned for v0.9 and not in this PR.

S-3 measurement (summary)

  • Setup: same hosts, scanned twice under 0.9.0 and twice under 0.10.0 through a scratch OpenWatch.
  • Noise: zero verdict changes between repeat scans under either engine.
  • Score changes: −0.5 pp on owas-tst02 (Ubuntu 22.04, real), +2.4 and +3.0 pp on Rocky 8 and 9 containers (synthetic).
  • Alerts: none; every move was under the 5 pp threshold.
  • Coverage gap: 8 of 9 inventory hosts were unreachable; no real RHEL host was measured.

Evidence at 6c3cb5cf

  • Hosted, head 6c3cb5cf: 25 checks pass, 6 skipped (triage and labeling automation only). Go CI 36363780217, CodeQL 36363780238, Package smoke 36363780199.
  • kensa-rules compat in package-smoke, against the published v0.7.1 packages, scriptlets on: RPM 41 checks passed (job 108746415473), DEB 37 (job 108746415464). Covers the refused rules-only upgrade (dnf, rpm -U, apt, dpkg -i), openwatch-only, coordinated, dnf upgrade, joint rpm -Uvh, single-package upgrades in both orders, fresh install, refused downgrade and joint rollback. After every step, package-manager state (recorded version, dpkg --audit or rpm -V) and corpus contents (digest against the expected package payload) are checked separately.
  • Mutation: a DEB built without the preinst fails the refused dpkg -i step on all three checks: version, dpkg --audit, corpus digest.
  • Superseded head 82809ee8: Go CI 36363190972 failed only on TestChangelog_UnreleasedCategoriesAreStandard (AC-02), fixed in 6c3cb5cf. Kept here as the record.
  • Local, rebased tree: generated-code drift, vet, govulncheck, Specter gate and doc style pass; frontend 411 tests pass. The local race run timed out internal/server at the Makefile's 10-minute limit (CI allows 1800 s, CP bugs/OW-066); hosted phase 2 passed.
  • Kensa-published package, measured 2026-09-27: Rocky 9 container and pkg/kensa.LoadRules, as in the table above.
  • Not yet run: these scenarios against the actual release-candidate packages. That happens at the candidate's gates (OW-081 criterion 2).

Pins github.com/Hanalyx/kensa v0.10.0 (only kensa moved in go.sum). The
engine constant, system-kensa-executor 2.10.0 (context, C-13) and the
third-party notices follow.

Corpus: 769 to 779 rules, two new framework keys, nist_800_171 and
cmmc_l2, 324 rules each, measured through pkg/kensa.RuleFrameworkRefs.
The backend family labels and the host-detail lens chips name them
"NIST 800-171" and "CMMC Level 2"; the chip transform would have
rendered "CMMC L 2" (frontend-host-compliance-tab 1.7.0, AC-12). The
README, the scanning guide framework table and the distribution matrix
are re-derived from v0.10.0.

Variables: v0.10.0 ships eight corpus-used variables with an empty
default. The catalog test bounded the list at a constant 29; it now
derives the bound from BuiltInVars and checks membership.
api-system-scan-config 1.5.0 adds AC-11: a list override reaches the
rule's set_compare parameters verbatim on reload, and removing it
restores the default. configure_me stays limited to the three
placeholders (C-07); extending it is a decision, not part of this change.
The scanning guide gains a Scan variables section naming the eight, the
list syntax and the fact that values are not type checked.

Mutation checks, each red then restored by inverse edit with the hash
verified: reload without overrides (AC-11), a wrong CMMC backend label
(system-compliance-lens AC-02), a missing CMMC chip label (AC-12).

Findings filed, not fixed here: CP bugs/OW-080 (override values are
stored and applied without a type check; the Kensa checker is internal,
features/KN-OW-022) and bugs/OW-081 (the rc.5 engine cannot load the
0.10.0 corpus and the packages do not forbid that pairing; a decision is
needed before this merges).
CP bugs/OW-081. Kensa v0.9.0 cannot load the v0.10.0 corpus: LoadRules
fails on the first variable it does not know, serve starts anyway, and
every scan fails. kensa-rules takes its version from go.mod and openwatch
required it unversioned, so a rules-only upgrade onto rc.5 or earlier
produced exactly that pairing.

The openwatch RPM and DEB now provide openwatch-kensa-engine at the Kensa
version they link, and kensa-rules requires openwatch-kensa-engine at
least its own version. Both come from one helper,
packaging/common/kensa-version.sh, which the corpus stager also uses, so
the two values cannot drift. No floor is written by hand: a fixed
"openwatch >= rc.6" would have made the pair built on main uninstallable
together until the version bump. An older corpus under a newer engine
loads, so an openwatch-only upgrade stays allowed. The spec default
0.0.0 satisfies no corpus, so a build that forgets the define fails at
install rather than shipping an unguarded pair.

Contract: release-upgrade 1.1.0, C-06, AC-08 (built artifacts carry the
provide and the requirement at the go.mod version, read from go.mod
independently of the helper) and AC-09 (container behavior on RPM and
DEB). package-smoke gains a kensa-rules-compat job that runs AC-09
against v0.7.1, the previous GA.

Measured locally against the published v0.8.0-rc.5 packages and this
tree built as rc.6, Rocky 9 and Ubuntu 24.04:
- dnf, rpm -U and apt refuse kensa-rules 0.10.0 alone; the installed
  corpus stays 0.9.0;
- openwatch-only upgrade, coordinated upgrade and fresh install succeed;
- the new corpus refuses a downgrade of openwatch to rc.5;
- 1:0.8.0~rc.5 < ~rc.6 < ~rc.10 < 1:0.8.0 and 0.9.0 < 0.10.0 in both
  formats.
Mutations, each red then restored with the hash checked: kensa-rules
without the floor (AC-09, both formats), openwatch without the provide
(AC-09, both formats), and the DEB provide left at 0.0.0 (AC-08).

Residual, measured and documented, not covered: a bare dpkg -i of the
rules package unpacks it and leaves it unconfigured (the files are
replaced), and rpm --nodeps bypasses the check. The documented paths use
dnf and apt. The upgrade runbook and the CHANGELOG say so.
…t to 800-53

configure_me (api-system-scan-config C-07, amended in 1.5.0, which is
unpublished). It marked the three placeholder defaults only. It now marks
every variable whose built-in value cannot be right for a real site: the
three placeholders and every corpus-used variable Kensa ships with an
empty default. For v0.10.0 that is eleven, named in C-07 and pinned in
the test: the three plus authorized_listening_ports,
authorized_local_accounts, authorized_network_protocols,
authorized_privileged_users, authorized_service_accounts,
authorized_services, flaw_remediation_max_days and suid_sgid_baseline.
The other 32 carry a benchmark value or a generic list valid as shipped.
C-07 says configure_me marks a decision to make and does not classify a
scan result, which needs KN-OW-021. The card's count no longer calls
every flagged value a placeholder; the OpenAPI description follows and
the generated code is refreshed.

The catalog test (AC-08) now checks contents: the entry set, each
default and each rule-id list against values built in the test from the
two library tables, two fixed points, and the ConfigureMe set against
the named inventory. Mutations, each red then restored with the hash
checked: flag placeholders only; trim whitespace from defaults, which
changes only banner_text.

Remediation projected lift (api-remediation 1.8.0, C-07, AC-18). The
nist bucket matched every key starting "nist", so v0.10.0's nist_800_171
joined it: 19 rules carry 800-171 and not 800-53, so they would be
quoted a NIST gain, and the denominator would count both frameworks.
The bucket is now the 800-53 family, in both the class match and the
SQL denominator. AC-18 discriminates each half, and a mutation of each
half turns it red (25 instead of 33.33; a lift quoted for an 800-171-only
rule).
…o CI

Go CI failed its specter sync gate on 3a243ae: release-upgrade AC-09
skipped there, because its only test drove containers, which only
package-smoke provides. A skipped criterion is uncovered.

AC-09 now asks the resolvers directly, with no root, container or
network: rpm --test against a scratch rpmdb, and apt-get -s against a
fixture dpkg status, using the real packages the tree builds. The older
openwatch is a payload-free fixture at 1:0.7.1 that provides no engine.
It must sort below the tree's own build (packaging/version.env's rc),
which the first draft of this test got wrong: with the fixture at rc.5
the resolvers saw the same version already installed and tested nothing.
A base fixture supplies every other requirement of the packages under
test, derived from their own Requires and Depends.

Scenarios in both formats: rules-only upgrade refused naming
openwatch-kensa-engine; openwatch-only, coordinated and fresh install
accepted; downgrade to the fixture refused; version ordering. Mutations,
each red then restored with the hash checked: kensa-rules without the
floor (rules-only and downgrade accepted, both formats) and openwatch
without the provide (coordinated and fresh refused, both formats).

The container test stays, without a criterion of its own, and
package-smoke's kensa-rules-compat job runs it by its new name. AC-09's
text now says how it is verified.
Founder decision 2026-09-26: S-7 is in v0.8. OpenWatch held three label
sources that disagreed (internal/framework's map, the Compliance tab's
id transform, and the report's first-token collapse), and the rule
library and scan detail grouped every nist* key as one "NIST" tone. So
an 800-53 report and an 800-171 report carried the same "NIST" on their
cover, OSCAL title and file name.

One source now: internal/kensa.FrameworkLabel wraps
pkg/kensa.FrameworkFromID. An id Kensa does not know is returned as it
is; OpenWatch derives no label.
- framework.Label (family labels) and the report scope label use it.
- The API carries it: label on GET /hosts/{id}/compliance/frameworks
  and GET /reports/frameworks, framework_labels on GET /rules and GET
  /scans/{id}. Additive fields; generated code refreshed.
- The UI renders them: lens chips and panels, the report picker, and
  the rule library and scan detail tags (title and accessible name).
  The rule library filter offers one option per framework, by label.

Signed artifacts: the attestation content still carries the exact key.
The scope label is computed at generation and stored, and every face
reads the stored value, so a report generated before this change keeps
its label, its JSON face still hashes to content_sha256 and its
signature verifies (api-reports AC-27).

Contracts: system-compliance-lens 1.7.0 C-08, AC-02 amended, AC-14;
api-reports 1.19.0 AC-07 and AC-09 amended, AC-27;
frontend-host-compliance-tab 1.7.0 AC-12 rewritten (unpublished, so in
place); frontend-reports 1.14.0 AC-09; frontend-rules-library 1.1.0
AC-05; frontend-scan-detail 1.2.0 AC-09.

Mutations, each red then restored with the hash checked. The first
backend round failed to build, because removing the only kensa call left
an unused import, so it proved nothing; each was re-run in a form that
compiles:
- Label upper-casing the id (AC-02);
- the scope label's first-token collapse (AC-07, AC-27);
- the host label set to the raw id (AC-14);
- scan detail labels emptied (AC-14);
- the chip rendering framework_id (AC-02);
- the rule filter ignoring labels (AC-05);
- tags ignoring labels (AC-05).

Visible changes: "CIS RHEL 9" reads "CIS (RHEL 9)", "PCI DSS 4" reads
"PCI DSS 4.0". Kensa formats only RHEL versions, so Ubuntu benchmarks
read "CIS (ubuntu22)"; requested from Kensa as CP features/KN-OW-023
rather than patched locally.
CP bugs/OW-081, design completed on founder authorization 2026-09-26.
The earlier change guaranteed only that the resolver refuses. dpkg
unpacks a package with an unmet Depends before failing, so a bare
`dpkg -i` of kensa-rules 0.10.0 beside rc.5 replaced the corpus and left
the package unconfigured, with every scan failing. That is an ordinary
install path, not a bypass.

Four DEB mechanisms were measured against the same scenarios before one
was chosen:
- Depends only: rules-only dpkg -i replaced the corpus.
- Breaks on openwatch older than rc.6: refused dpkg -i, but made the
  tree's own pair uninstallable, because main builds as the published
  rc.5 version.
- Pre-Depends: deadlocked with openwatch's own Depends, so even
  coordinated and fresh installs failed.
- A preinst guard (chosen): refused rules-only dpkg -i and apt with every
  corpus file byte-identical, and accepted the apt coordinated upgrade,
  dpkg -i with openwatch listed first, fresh installs and main's own
  pair. Its one cost is dpkg -i with the rules listed first: refused,
  leaving openwatch upgraded beside the old corpus, which is a working
  pair, with a message naming the fix.

packaging/kensa-rules/preinst.in reads openwatch's Status-Status and
Provides through dpkg-query, so a held or half-installed openwatch is
still checked, and compares with dpkg --compare-versions. RPM needed
nothing more: dnf and rpm -U refuse before touching files.

Measured, both formats: rolling openwatch back alone is refused;
rolling both packages back in one command succeeds. The upgrade runbook
now says so, with the recovery for a corpus forced on with --nodeps or
--force-depends (plain apt refuses from that broken state; dpkg -i then
dpkg --configure -a works).

Contract: release-upgrade C-06 reworded as the boundary tested today,
not a promise about future engines, and AC-10 added. AC-10 runs the
preinst from the built package against a stub dpkg-query in Go CI. The
container test gains the dpkg -i, rollback and file-digest checks, and
passes on Ubuntu 24.04 and Rocky 9. Mutations, each red then restored
with the hash checked: no preinst in the package; the guard checking
only "installed"; ge weakened to gt.
detect-secrets rescan after the rebase moved flagged lines. No finding was added or removed; only line numbers changed.
…state and corpus separately

The container test ran RPM transactions with tsflags=noscripts and checked
only the recorded kensa-rules version plus a corpus digest. It did not cover
a joint rpm -Uvh, dnf upgrade, or single-package upgrades in both orders.

- RPM transactions now run their scriptlets. The openwatch upgrade scriptlet
  skips its migration when no database answers, so a container is enough.
- New scenarios: dnf upgrade of both packages, a joint rpm -Uvh, and
  single-package upgrades in both orders through the package manager (and
  rpm -U on RPM).
- After every step, package-manager state and corpus contents are checked as
  separate facts: each package's recorded version and a clean dpkg --audit or
  rpm -V, and a digest of /usr/share/kensa/rules compared with the payload of
  the kensa-rules package that should be installed.

Removing the DEB preinst makes the refused dpkg -i step fail all three checks:
version, dpkg --audit and corpus digest.
…nd disclose the Kensa package

The CHANGELOG, the upgrade runbook and release-upgrade C-06 said a newer
corpus is refused beside an older openwatch on every ordinary install path.
That holds for the kensa-rules package OpenWatch builds. Kensa publishes a
package with the same name and install path that declares no engine
requirement, and openwatch's dependency on the name accepts it. The text
described a protection that does not cover that package.

- release-upgrade C-06 (1.1.0, not yet published) now scopes the MUST to the
  OpenWatch-built package and states the Kensa package as disclosed, not
  prevented (CP bugs/OW-081, criterion 3b).
- The upgrade runbook gains "What this check does not cover": the Kensa
  package, hosts on rc.5 or earlier, how to tell the two packages apart, and
  how to restore OpenWatch's. Updating the rules now means both packages from
  one OpenWatch release.
- The CHANGELOG qualifies the refusal and adds a known limitation.
- The installation guide no longer says the rules update without an
  OpenWatch release, which the engine requirement made untrue.

The secrets baseline moves only line numbers; no finding was added or
removed.
release-changelog C-02 and AC-02 allow only Added, Changed, Deprecated,
Removed, Fixed and Security as category headings. #883 added "Upgrade notes"
and "Known limitations" headings to [Unreleased], and main fails
TestChangelog_UnreleasedCategoriesAreStandard. Go CI did not catch it:
#883 changed only documentation, so the quality gate skipped every Go test.

Both blocks move, unchanged in content, into the section's lead-in as
labeled paragraphs, the form the candidate sections already use for text
before their first category. No entry is dropped.
@remyluslosius
remyluslosius merged commit ab68f30 into main Sep 28, 2026
37 checks passed
@remyluslosius
remyluslosius deleted the feat/kensa-v0.10.0 branch September 28, 2026 01:56
remyluslosius added a commit that referenced this pull request Sep 28, 2026
…pts (#885)

CP bugs/OW-081, the #882 acceptance list. The container test switched
its RPM transactions from tsflags=noscripts to scriptlets, which dropped
the noscripts case instead of adding scriptlets beside it. A pass with
scriptlets is not evidence that the refusals hold on dependency
resolution alone.

The container test takes a mode: scripts (the default, unchanged) or
noscripts (RPM only). In noscripts mode every dnf transaction carries
--setopt=tsflags=noscripts and every rpm -U and rpm -Uvh carries
--noscripts. All scenarios and both separate checks, package-manager
state and corpus digest, run as before. The scriptlet check is inverted:
it asserts no identity keys were provisioned, which proves the mode
took effect. package-smoke gains a third job,
"kensa-rules compat rockylinux:9 noscripts"; the two existing job names
are unchanged.

Local run against main's packages from ab68f30 and the v0.7.1 GA:
noscripts 39 of 39, scripts 39 of 39, the only difference the inverted
scriptlet check. A copy with the noscripts flags removed failed on that
check alone.
remyluslosius added a commit that referenced this pull request Sep 28, 2026
Stage 1 for the sixth 0.8.0 candidate, from main fc1117d. VERSION is
0.8.0-rc.6 in packaging/version.env, the README phrase and the newest
CHANGELOG heading; CODENAME stays Eyrie. The hygiene test binds the three.

The changelog section records rc.5 on facts: built, every machine gate
passed, assets published as a pre-release, no human verdict recorded for
its fleet checks, documentation review or release-captain signature, tag
and assets preserved. Since rc.5: #870 to #881, #883 and #882. The
[Unreleased] notes move into the rc.6 section unchanged.

Known limitations gain the two the readiness record lists as shipping with
v0.8: drift does not distinguish a corpus change from a host change (D-2
S-3, accepted 2026-09-26), and scan variable values are not type checked
(OW-080).

Next unused candidate number verified: no v0.8.0-rc.6 tag on the remote or
locally, and no release or draft of that name.

No tag, publication or attestation.
remyluslosius added a commit that referenced this pull request Sep 28, 2026
Stage 1 for the sixth 0.8.0 candidate, from main fc1117d. VERSION is
0.8.0-rc.6 in packaging/version.env, the README phrase and the newest
CHANGELOG heading; CODENAME stays Eyrie. The hygiene test binds the three.

The changelog section records rc.5 on facts: built, every machine gate
passed, assets published as a pre-release, no human verdict recorded for
its fleet checks, documentation review or release-captain signature, tag
and assets preserved. Since rc.5: #870 to #881, #883 and #882. The
[Unreleased] notes move into the rc.6 section unchanged.

Known limitations gain the two the readiness record lists as shipping with
v0.8: drift does not distinguish a corpus change from a host change (D-2
S-3, accepted 2026-09-26), and scan variable values are not type checked
(OW-080).

Next unused candidate number verified: no v0.8.0-rc.6 tag on the remote or
locally, and no release or draft of that name.

No tag, publication or attestation.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation frontend size/XL tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant