Skip to content

fix: scope exceptions by framework - #188

Merged
matthyx merged 2 commits into
kubescape:mainfrom
Namanbhatt-01:fix/framework-scoped-exceptions
Aug 20, 2026
Merged

matthyx merged 2 commits into
kubescape:mainfrom
Namanbhatt-01:fix/framework-scoped-exceptions

Conversation

@Namanbhatt-01

Copy link
Copy Markdown
Contributor

Summary

  • Scope posture exceptions to the configured framework filters.
  • Prevent an exception in one framework from suppressing a rule failure in another framework.
  • Recalculate control status from rules when a framework filter is provided.
  • Preserve the existing aggregate status behavior when no framework filter is provided.

Tests

  • go test -count=1 ./...
  • go test -count=1 ./reporthandling/helpers/v1/...
  • git diff --check

All checks pass.

@coderabbitai

coderabbitai Bot commented Aug 19, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ab11e155-0757-4c82-8a75-6061861a74d7


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Signed-off-by: Namanbhatt-01 <the.namanbhatt109@gmail.com>
@Namanbhatt-01
Namanbhatt-01 force-pushed the fix/framework-scoped-exceptions branch from b72c923 to 01d62a8 Compare August 19, 2026 15:21

@matthyx matthyx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The filters.go change itself (trimming PosturePolicies per-exception instead of keeping/dropping the whole exception, and fixing the duplicate-append bug) is a solid, well-tested fix. But the overall approach doesn't achieve the PR's stated goal end-to-end, and I'd like that resolved before merging — two inline comments plus the bigger integration issue below.

Critical: the real production pipeline never reaches the new per-framework check.

rules.go#GetStatus now only re-evaluates exceptions dynamically when rule.Status == apis.StatusFailed. But in kubescape's actual pipeline (core/pkg/opaprocessor/processorhandlerutils.go: updateResults), exceptions are applied first, via Result.SetExceptions → control.setExceptions → rule.SetStatus(rule.GetStatus(nil).Status(), nil) (both calls always pass f = nil, unchanged by this PR). Since f == nil there, SetStatus's f == nil branch still unconditionally overwrites rule.Status to Passed (+ SubStatusException) as soon as any exception is attached — with no framework awareness, because FilterExceptions is only framework-aware when it's actually given a populated Filters.

Only after that does SummaryDetails.InitResourcesSummary build the per-framework rollup by calling control.GetStatus(f) with a real per-framework filter (confirmed via summarydetails.go / frameworksummarymethods.go). By that point rule.Status is already Passed, not Failed, so the new rule.Status == apis.StatusFailed guard in GetStatus(f) can never fire — the dynamic per-framework recheck this PR adds is unreachable through the real flow, and the original bug ("exception in one framework suppresses a rule failure in another framework") will still reproduce end-to-end.

The new unit tests don't catch this because they construct ResourceAssociatedRule/ResourceAssociatedControl via struct literals with Status: StatusFailed set directly, bypassing SetStatus entirely — so they never exercise the code path that actually runs in production.

To really fix this, the exception-baking step in exceptions.go (rule.SetStatus(rule.GetStatus(nil).Status(), nil)) needs to stop permanently collapsing the raw failed status into Passed when no framework context is available — e.g. keep rule.Status as the framework-agnostic ground truth (Failed) and let GetStatus(f) be the sole place exceptions get applied, for both the f == nil and f != nil cases. As it stands, SetStatus's f == nil branch and GetStatus's new dynamic check are fighting each other. I'd like to see this validated with a test that goes through SetExceptions → per-framework GetStatus(f), not just direct struct construction, before this merges.

Also flagged two more issues inline:

  • rules.go SetStatus's f != nil matching-exception branch silently no-ops instead of persisting anything, which can leave rule.Status empty in the isOldControl backward-compat path.
  • controls.go's new f != nil branch drops the actionRequired (manual review / requires review / configuration) skip semantics that control.SetStatus(c) computes, since it recomputes purely from rule pass/fail.

Comment thread reporthandling/results/v1/resourcesresults/rules.go Outdated
Comment thread reporthandling/results/v1/resourcesresults/controls.go
Signed-off-by: Namanbhatt-01 <the.namanbhatt109@gmail.com>
@matthyx matthyx moved this to Waiting on Author in KS PRs tracking Aug 20, 2026

@matthyx matthyx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both blockers from the previous review are resolved in b8930c3:

  • Exception baking removed. exceptions.go no longer calls rule.SetStatus(rule.GetStatus(nil).Status(), nil) after attaching exceptions, so rule.Status keeps its raw Failed ground truth instead of being permanently collapsed to Passed. GetStatus(f) is now the single place exceptions get applied, dynamically, for both f == nil and f != nil. Confirmed by TestSetExceptionsKeepsRuleStatusForFrameworkScopedEvaluation, which goes through the real Result.SetExceptions() entry point (the actual call kubescape's pipeline uses) and checks the NSA/MITRE split holds afterward — this is exactly the integration path that was previously unreachable.
  • actionRequired skip semantics preserved. The new control.actionRequired field + applyActionRequiredStatus() correctly reapplies requires-review/manual-review/configuration-skip on top of the per-framework rule aggregate, instead of bypassing it. TestFilteredControlStatusPreservesActionRequired covers all three action-required kinds against a matching vs. non-matching framework and checks both status and sub-status.
  • SetStatus on the rule is now a plain rule.Status = s (no more silent no-op branch), and GetStatus/GetSubStatus no longer mutate rule state as a side effect (TestOldControlFrameworkStatusDoesNotMutateRules checks this explicitly) — also resolves the isOldControl concern from the last round.

Nit (non-blocking): ResourceAssociatedRule.SubStatus/GetSubStatus() (the raw field, not the interface method returned by GetStatus()) is no longer written anywhere, so it's effectively dead now that exceptions are applied dynamically — worth a follow-up cleanup, but I checked kubescape's only external consumer (httphandler/storage/apiserver.go) and it already reads through rule.GetStatus(f).GetSubStatus(), not the raw field, so there's no behavioral regression.

Approving.

@matthyx
matthyx merged commit 097201d into kubescape:main Aug 20, 2026
5 checks passed
@matthyx matthyx moved this from Waiting on Author to To Archive in KS PRs tracking Aug 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants