Skip to content

Require evidence before reporting unused ConfigMaps and Secrets - #1741

Merged
nadaverell merged 3 commits into
mainfrom
fix/audit-partial-access
Sep 15, 2026
Merged

nadaverell merged 3 commits into
mainfrom
fix/audit-partial-access

Conversation

@nadaverell

@nadaverell nadaverell commented Sep 13, 2026 •

Copy link
Copy Markdown
Contributor

Why

A ConfigMap or Secret can look unused because Radar cannot see its consumer. For example, a workload can consume a Reflector mirror in another namespace while the audit is displaying only the source namespace. Suggesting deletion from that incomplete view is misleading.

This PR makes the unused ConfigMap/Secret check distinguish observed use, supported evidence of no use, and unknown evidence. It builds on the subject authorization and incomplete-scan presentation merged in #1753, and reuses the Reflector metadata parser from #1740. It has no remaining prerequisite on the deferred discovery work in #1750.

Behavior

Available evidence Result
An observed supported reference uses the object Count it as evaluated and passing, even if other consumer inventories are unavailable
No reference, with adequate readable and initially synced consumer inventories Report the object as unused
No reference, with a required inventory unavailable or incomplete for the subject namespace Exclude it from both findings and passing/evaluated counts; disclose incomplete reference evidence

The namespace picker selects subjects. For Reflector-annotated subjects, the collector can inspect authorized consumers of remote mirrors. Use propagates through observed same-kind links, including chains and cycles. Unused automatic mirrors with visible sources are exempt, but their existence does not establish source use.

Typed workload, ServiceAccount and Ingress inventories use the existing per-kind readiness and namespace-scope APIs. The existing dynamic reference handlers share a registry with their ConfigMap/Secret and cross-namespace requirements, so discovery and collection agree about which installed integrations matter. An installed but unsynced consumer kind is unknown, not empty. Partial discovery of a relevant group prevents an absence claim; unrelated discovery failures do not.

Cert-manager Issuer credential references cover ACME, CA, Vault and Venafi fields. ClusterIssuer credentials use the namespace established by an observed controller's explicit flag, including a literal or downward-API environment value. Hidden controllers, unresolved values or configuration files without an explicit namespace flag do not produce a guessed namespace. Explicit flags take precedence over configuration files. An authorized, synced, empty ClusterIssuer inventory needs no controller namespace.

Scope and tradeoffs

The runtime change stays in the audit collector and orphan check. It adds no shared discovery lifecycle, informer failure latch, permission grants, new watches, scan-cache redesign or coverage response schema. Existing missingInputs carries configmap-references and secret-references through REST, Home, AI context and MCP. Tests in the HTTP/MCP adapters keep their authorization assertions while explicitly supplying the core-only inventory they depend on.

Full-access users can also lose unsupported orphan findings while required inventories are still syncing. Restricted users can see fewer orphan evaluations when an installed integration can reference Secrets across namespaces. This is deliberate: an omitted unknown evaluation is preferable to an unsupported deletion suggestion. Known use still counts. The check covers supported reference fields, not arbitrary controller behavior. Existing Istio gateway workload-namespace and Contour delegated TLS resolution limitations are outside this change.

Readiness here means initial synchronization, not continuous watch freshness. The existing scan memo can lag evidence changes by up to five seconds. The per-resource audit endpoint retains its findings-array shape and has no completeness metadata; the scan and AI resource context provide that distinction. Package callers that omit ConfigReferenceEvidence can establish observed use but cannot establish absence.

Validation

  • make test, make tsc, and make build passed.
  • Race tests passed for pkg/audit, pkg/configrefs, and the affected audit/server/MCP scenarios.
  • Live tests used the existing GitOps demo cluster with an explicit kubectl context. Local and authenticated full-access results matched exactly; Secret-denied identities disclosed missing inputs without Secret findings. Alternating and concurrent identities retained separate results.
  • A temporary ConfigMap-only Reflector fixture exercised the central case. Complete visibility evaluated two subjects, with the remotely consumed source passing and the unused ConfigMap reported. A source-namespace-only cache evaluated only the unused ConfigMap and reported configmap-references for the unknown source. Configured REST, dashboard, MCP and the rendered Checks page preserved that warning.
  • Live test fixtures and processes were removed. No Kubernetes grants or current-context changes. One actual embedded-UI screenshot verified the warning; no frontend source changes.

@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

Report incomplete audit coverage under partial Kubernetes access

🐞 Bug fix âœĻ Enhancement 🧊 Tests 📝 Documentation 🕐 40+ Minutes

Grey Divider

AI Description

â€Ē Prevents orphan findings when authorized reference evidence is incomplete or unsynchronized.
â€Ē Follows observed Reflector chains and propagates coverage gaps across all audit consumers.
â€Ē Shares scoped scans safely while withholding unreadable Secret findings, counts, and names.
Diagram

graph TD
  A["Authenticated Callers"] --> B["Read Scope"] --> C["Scan Memo"] --> D["Evidence Snapshot"] --> E{"Coverage Complete"}
  E -->|Yes| F["Orphan Findings"] --> H["Consumer Surfaces"]
  E -->|No| G["Coverage Rows"] --> H
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Disable the entire orphan check on any gap
  • ➕ Simpler completeness model and response contract
  • ➕ Eliminates all false-positive orphan findings under partial evidence
  • ➖ Suppresses valid findings from unaffected kinds and namespaces
  • ➖ Provides less actionable detail about the missing evidence
2. Issue fresh API lists for every audit
  • ➕ Avoids relying on potentially stale informer stores
  • ➕ Can evaluate permissions directly for each request
  • ➖ Adds API-server load and request latency
  • ➖ Complicates consistency across many typed and dynamic resources
  • ➖ Still requires careful partial-failure and authorization handling
3. Preserve best-effort negative findings
  • ➕ Maintains previous result volume and simpler evaluation behavior
  • ➕ Requires fewer API and UI changes
  • ➖ Can incorrectly label resources unused
  • ➖ May leak information through Secret counts or cached cross-user results
  • ➖ Presents empty findings as an unsafe all-clear

Recommendation: Keep the PR's per-kind, per-namespace evidence snapshot and conservative negative-finding policy. It preserves known-use conclusions and unaffected findings while preventing false orphan reports; scoped memoization offsets the additional collection cost, and explicit coverage rows make uncertainty visible without exposing restricted inventory.

Files changed (44) +1675 / -266

Enhancement (16) +447 / -35
diagnoser.goPreserve incomplete audit context in AI diagnosis +4/-0

Preserve incomplete audit context in AI diagnosis

â€Ē Adds an audit-incomplete health signal and instructs diagnosis prompts not to interpret skipped checks as passing or deletion safety.

internal/ai/diagnoser.go

scope.goResolve authorization scope and scan identity +116/-0

Resolve authorization scope and scan identity

â€Ē Adds namespace, Secret, and cluster-resource authorization modeling. Generates stable scan keys incorporating selected namespaces, permissions, discovery dependencies, and informer readiness.

internal/audit/scope.go

summary.goInclude incomplete coverage in resource summaries +12/-3

Include incomplete coverage in resource summaries

â€Ē Uses the shared scoped scan memo and marks a resource audit summary incomplete when matching coverage rows exist.

internal/auditcontext/summary.go

tools_audit.goExpose bounded coverage through MCP audits +15/-11

Expose bounded coverage through MCP audits

â€Ē Runs caller-scoped cached audits and returns coverage rows alongside findings. Caps coverage at 50 rows and reports whether additional rows were omitted.

internal/mcp/tools_audit.go

ai_diagnose.goAdd scoped coverage state to AI health signals +2/-1

Add scoped coverage state to AI health signals

â€Ē Uses request-aware audit summaries and forwards incomplete coverage into AI diagnosis health context.

internal/server/ai_diagnose.go

dashboard.goReport incomplete audit status on the dashboard +6/-3

Report incomplete audit status on the dashboard

â€Ē Runs the dashboard audit with caller scope and exposes an incomplete flag whenever coverage gaps remain.

internal/server/dashboard.go

AuditCard.tsxRender incomplete dashboard audits as unknown +10/-7

Render incomplete dashboard audits as unknown

â€Ē Adds an unknown severity state and replaces all-passing messaging with neutral incomplete-coverage language when no findings are available.

packages/k8s-ui/src/components/audit/AuditCard.tsx

ChecksView.tsxDistinguish incomplete empty audit results +5/-4

Distinguish incomplete empty audit results

â€Ē Adds an incomplete input and displays a neutral empty state instead of claiming every resource passed.

packages/k8s-ui/src/components/checks/ChecksView.tsx

coverage.goDefine audit coverage contracts and aggregation +60/-0

Define audit coverage contracts and aggregation

â€Ē Introduces coverage reasons, per-check coverage rows, reference snapshots, and skipped-subject aggregation for the orphan check.

pkg/audit/coverage.go

helpers.goFilter coverage with audit settings +7/-0

Filter coverage with audit settings

â€Ē Applies ignored namespace and disabled-check settings to coverage rows alongside findings and evaluation counts.

pkg/audit/helpers.go

types.goExpose reference snapshots and coverage results +5/-6

Expose reference snapshots and coverage results

â€Ē Extends audit inputs and outputs with ConfigReferenceSnapshot and coverage rows. Reuses the shared config-reference identity type.

pkg/audit/types.go

reflector.goParse and traverse EmberStack Reflector topology +105/-0

Parse and traverse EmberStack Reflector topology

â€Ē Adds metadata-only reflection parsing between observed same-kind endpoints. Tracks automatic and unresolved mirrors and propagates consumer use backward through renamed chains and cycles.

pkg/configrefs/reflector.go

evidence.goExpose informer evidence health and scope +78/-0

Expose informer evidence health and scope

â€Ē Classifies watch termination errors, reports typed and dynamic watch failures, enumerates dynamic readiness states, and identifies incomplete namespace coverage.

pkg/k8score/evidence.go

types.goAdd incomplete state to resource audit summaries +1/-0

Add incomplete state to resource audit summaries

â€Ē Extends resource context responses with an optional incomplete audit indicator.

pkg/resourcecontext/types.go

client.tsType audit coverage API responses +10/-0

Type audit coverage API responses

â€Ē Adds the coverage row and reason union types and exposes coverage and missing inputs on audit responses.

web/src/api/client.ts

AuditView.tsxDisplay audit coverage gaps in the main view +11/-0

Display audit coverage gaps in the main view

â€Ē Summarizes skipped checks, distinguishes watch failures from other gaps, recommends reconnecting when needed, and prevents incomplete empty results from appearing healthy.

web/src/components/audit/AuditView.tsx

Bug fix (13) +519 / -217
coverage.goCollect scoped reference evidence and coverage state +254/-0

Collect scoped reference evidence and coverage state

â€Ē Builds ConfigMap and Secret reference snapshots from authorized typed and dynamic inventories. Tracks synchronization, discovery, scope, watch failures, reflection coverage, and unavailable subjects by kind and namespace.

internal/audit/coverage.go

crd_config_refs.goModel and filter dynamic reference dependencies +59/-122

Model and filter dynamic reference dependencies

â€Ē Replaces the handler switch with dependency metadata describing ConfigMap support and cross-namespace behavior. Filters dynamic objects by caller scope, synchronization, and watch health before collecting references.

internal/audit/crd_config_refs.go

runner.goApply read scope before running audit checks +32/-6

Apply read scope before running audit checks

â€Ē Restricts subject namespaces and Secrets before evaluation, attaches the new reference snapshot, and treats lister failures as unavailable inventory instead of empty inventory.

internal/audit/runner.go

audit_scope.goBuild caller-specific MCP audit options +22/-0

Build caller-specific MCP audit options

â€Ē Resolves namespace, Secret, and cluster-scoped reference permissions from the MCP request context for scoped audits.

internal/mcp/audit_scope.go

resource_context.goScope MCP resource audit summaries +2/-2

Scope MCP resource audit summaries

â€Ē Passes request-derived audit options into per-resource summary generation.

internal/mcp/resource_context.go

tools.goPropagate MCP request context to audit summaries +1/-1

Propagate MCP request context to audit summaries

â€Ē Updates resource-context construction to compute audit summaries using the active caller context.

internal/mcp/tools.go

ai_handlers.goScope AI resource audit context by caller +5/-5

Scope AI resource audit context by caller

â€Ē Converts audit summary helpers into server methods so request authorization is applied to AI resource context and diagnosis rows.

internal/server/ai_handlers.go

audit_handlers.goUse shared scoped scans in audit endpoints +2/-38

Use shared scoped scans in audit endpoints

â€Ē Replaces the server-local namespace-only cache with the shared identity-aware audit memo for list and resource endpoints.

internal/server/audit_handlers.go

audit_scope.goBuild request-specific server audit scope +22/-0

Build request-specific server audit scope

â€Ē Derives authorized namespaces, Secret list access, and cluster-scoped dynamic grants for server audit calls.

internal/server/audit_scope.go

namespace_scope.goInvalidate audit scans after context switches +2/-0

Invalidate audit scans after context switches

â€Ē Clears memoized audit results when cluster or namespace context changes.

internal/server/namespace_scope.go

checks.goRequire complete evidence for orphan findings +89/-29

Require complete evidence for orphan findings

â€Ē Consumes explicit reference snapshots, propagates use through reflection chains, exempts unused automatic mirrors, and records skipped coverage rather than treating unknown objects as passing. Adds reusable typed reference collection.

pkg/audit/checks.go

cache.goLatch invalidating typed informer failures +13/-11

Latch invalidating typed informer failures

â€Ē Records when a post-sync typed informer watch error makes cached evidence unreliable while preserving existing diagnostics.

pkg/k8score/cache.go

dynamic_cache.goTrack invalidating dynamic watch failures +16/-3

Track invalidating dynamic watch failures

â€Ē Installs dynamic informer watch-error handlers that latch evidence unavailability after synchronization while retaining default error handling.

pkg/k8score/dynamic_cache.go

Tests (12) +607 / -14
diagnoser_test.goTest incomplete audit diagnosis framing +11/-0

Test incomplete audit diagnosis framing

â€Ē Verifies incomplete coverage appears in the health frame without being treated as an active health issue.

internal/ai/diagnoser_test.go

coverage_test.goTest audit evidence scope and readiness handling +188/-0

Test audit evidence scope and readiness handling

â€Ē Covers cross-namespace evidence, Secret authorization, dynamic inventory synchronization, namespace authority, list failures, partial discovery, and dependency-domain isolation.

internal/audit/coverage_test.go

crd_config_refs_test.goValidate dynamic ConfigMap dependency metadata +5/-0

Validate dynamic ConfigMap dependency metadata

â€Ē Ensures every handler producing ConfigMap references is marked as a ConfigMap dependency for coverage calculations.

internal/audit/crd_config_refs_test.go

memo_test.goTest scan memo isolation and invalidation +45/-0

Test scan memo isolation and invalidation

â€Ē Verifies reuse for identical scans, separation between Secret permission scopes, explicit invalidation, and isolation across cluster cache replacement.

internal/audit/memo_test.go

audit_scope_test.goTest MCP authorization and coverage bounds +70/-0

Test MCP authorization and coverage bounds

â€Ē Verifies unreadable Secrets do not affect MCP findings or counts and confirms coverage output is capped at 50 rows with truncation indicated.

internal/mcp/audit_scope_test.go

audit_raw_test.goUpdate raw audit tests for shared memo invalidation +2/-3

Update raw audit tests for shared memo invalidation

â€Ē Invalidates the centralized audit scan memo when test settings change.

internal/server/audit_raw_test.go

audit_scope_test.goTest server audit cache authorization isolation +53/-0

Test server audit cache authorization isolation

â€Ē Confirms users without Secret list permission receive no Secret findings, evaluated counts, or hidden inventory counts, without contaminating authorized cached scans.

internal/server/audit_scope_test.go

checks_test.goProvide explicit complete evidence to orphan tests +9/-9

Provide explicit complete evidence to orphan tests

â€Ē Updates existing orphan fixtures to declare complete reference coverage before expecting negative unused-resource findings.

pkg/audit/checks_test.go

coverage_test.goTest conservative orphan and reflection coverage behavior +79/-0

Test conservative orphan and reflection coverage behavior

â€Ē Verifies unknown evidence does not produce findings or passes, known references still prove use, reflection coverage gates source findings, and settings filter coverage rows.

pkg/audit/coverage_test.go

reflector_test.goTest Reflector identity, chains, and cycles +47/-0

Test Reflector identity, chains, and cycles

â€Ē Covers renamed mirrors, deterministic topology, automatic-mirror behavior, malformed annotations, missing endpoints, and cycle-safe use propagation.

pkg/configrefs/reflector_test.go

cache_test.goUpdate typed watch-error tests for sync state +2/-2

Update typed watch-error tests for sync state

â€Ē Passes pre-sync status explicitly into existing watch-error recording tests.

pkg/k8score/cache_test.go

evidence_test.goTest evidence failure classification and recovery +96/-0

Test evidence failure classification and recovery

â€Ē Verifies normal watch closures do not invalidate evidence, startup failures can recover, and post-sync authorization failures remain latched until cache recreation.

pkg/k8score/evidence_test.go

Documentation (2) +34 / -0
configuration.mdDocument partial-access audit coverage semantics +29/-0

Document partial-access audit coverage semantics

â€Ē Explains how selected namespaces differ from evidence scope, when orphan coverage becomes incomplete, and how informer failures affect authority. Documents the snapshot requirement for library callers and Reflector behavior.

docs/configuration.md

mcp.mdDocument MCP audit coverage reporting +5/-0

Document MCP audit coverage reporting

â€Ē Describes coverage rows, the 50-row MCP limit, truncation signaling, and why empty findings with gaps are not an all-clear.

docs/mcp.md

Other (1) +68 / -0
memo.goAdd shared identity-aware audit scan memo +68/-0

Add shared identity-aware audit scan memo

â€Ē Introduces a five-second memo shared by audit consumers and keyed by cache identity, authorization scope, options, discovery, and inventory readiness. Supports explicit invalidation and guards against state changes during scans.

internal/audit/memo.go

@qodo-code-review

qodo-code-review Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0) ðŸŽĻ UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Hosted audits falsely show all-clear ✓ Resolved 🔗 Cross-repo conflict ≡ Correctness
Description
ScanResults.Coverage is new, but Radar Hub's radarAuditScan omits it and propagates only
MissingInputs to Radar Hub Web. When partial access produces no findings and coverage gaps without
missing inputs, the fleet dashboard reaches its healthy “All checks clear” state instead of
reporting incomplete coverage.
Code

pkg/audit/types.go[117]

+	Coverage []CheckCoverage      `json:"coverage,omitempty"`
Relevance

●●● Strong

Coverage is explicitly intended to propagate through audit consumers; omitting it creates a false
healthy state.

PR-#1513

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Radar adds and populates a separate coverage field for skipped audit subjects. Radar Hub's scan DTO
has no corresponding field and forwards only missing inputs, while Radar Hub Web bases its
partial-coverage state exclusively on missing inputs and otherwise selects healthy all-clear
messaging.

pkg/audit/types.go[114-125]
pkg/audit/checks.go[1656-1659]
External repo: skyhook-dev/radar-hub, internal/server/fleet_handlers.go [1051-1063]
External repo: skyhook-dev/radar-hub, internal/server/fleet_handlers.go [1192-1208]
External repo: skyhook-dev/radar-hub-web, src/pages/home/dashboardData.ts [188-220]
External repo: skyhook-dev/radar-hub-web, src/pages/home/PostureCards.tsx [219-245]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Radar now reports incomplete audit evidence through `ScanResults.coverage`, but Radar Hub silently drops that field and Radar Hub Web consequently treats an incomplete scan with no findings as an all-clear. Coordinate updates so the hosted fleet contract preserves and displays the new coverage state while remaining compatible with older agents.
## Fix Focus Areas
- pkg/audit/types.go[116-121]
- /cross_repos/radar-hub/internal/server/fleet_handlers.go[1051-1063]
- /cross_repos/radar-hub-web/src/pages/home/PostureCards.tsx[219-245]
## Recommended Fix
Add the optional coverage rows to Radar Hub's raw scan DTO and fleet response, propagate them per cluster, and aggregate them into an incomplete signal in Radar Hub Web. Use that signal alongside `missingInputs` when selecting the checks card accent and all-clear wording; treat absent coverage from older agents as the legacy behavior.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Custom issuer secrets look unused ✓ Resolved 🐞 Bug ≡ Correctness
Description
collectConfigEvidence leaves clusterIssuerNamespaces unknown whenever deployment evidence is not
cluster-wide, but dynamic reference extraction independently falls back to the hard-coded
cert-manager namespace. When cert-manager uses a custom cluster-resource namespace that is visible
to the audit but its controller Deployment is outside the caller's scope, coverage can be considered
complete while the valid ClusterIssuer Secret reference is omitted and the Secret reaches the
orphan finding path.
Code

internal/audit/coverage.go[R182-184]

+	if evidence.Deployments != nil && typedCoverage(cache, "deployments", "").Complete && (scope == nil || scope.Namespaces == nil) {
+		deps.clusterIssuerNamespaces = certManagerClusterResourceNamespaces(evidence.Deployments)
+	}
Relevance

●●● Strong

Accepted precedent directly flags hard-coded ClusterIssuer namespace attribution causing omitted
Secret references.

PR-#1067

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The authoritative namespace is only recorded with cluster-wide Deployment coverage, but the
extraction context derives its own value from scoped Deployments and defaults to cert-manager when
none are found. Per-namespace coverage does not skip the ClusterIssuer dependency when the
authoritative namespace remains unknown, allowing the omitted reference to become a negative orphan
conclusion.

internal/audit/coverage.go[182-186]
internal/audit/coverage.go[101-133]
internal/audit/crd_config_refs.go[84-100]
internal/audit/crd_config_refs.go[281-290]
internal/audit/crd_config_refs.go[688-717]
pkg/audit/checks.go[1010-1035]
pkg/audit/checks.go[1078-1088]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A scoped audit can treat ClusterIssuer evidence as complete while reference extraction assumes the default cert-manager namespace, producing false orphan findings for Secrets in a custom cluster-resource namespace.
## Fix Focus Areas
- internal/audit/coverage.go[182-218]
- internal/audit/crd_config_refs.go[84-100]
- internal/audit/crd_config_refs.go[281-290]
- internal/audit/crd_config_refs.go[688-717]
## Recommended Fix
Use one shared cluster-resource namespace result for both coverage and reference extraction. If the namespace cannot be established from authoritative deployment evidence, mark affected Secret reference coverage incomplete rather than falling back to the default namespace; otherwise pass the derived namespace explicitly into ClusterIssuer extraction.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Cluster audits ignore user grants ✓ Resolved 🐞 Bug â›Ļ Security
Description
RunFromCache applies the new ReadScope only by intersecting namespaces, then runs the dynamic
Crossplane scan without consulting ClusterResources or the caller's list permission for each
watched resource. For a namespace-limited identity, HTTP audit responses include denied
cluster-scoped findings, while MCP's namespace-only post-filter removes the same findings even when
that identity has permission to list them.
Code

internal/audit/runner.go[R38-39]

+	if scope != nil {
+		namespaces = scope.subjectNamespaces(namespaces)
Relevance

●● Moderate

Concrete authorization gap, but no closely matching precedent confirms team acceptance of this
dynamic-scan behavior.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new scope is reduced to a namespace list before the existing dynamic scan runs, while that scan
deliberately retains objects with an empty namespace and turns them into audit subjects. The server
returns those results without a cluster-resource authorization pass, whereas MCP drops all
empty-namespace findings based only on its namespace allow-list.

internal/audit/runner.go[31-67]
internal/audit/runner.go[133-173]
internal/audit/scope.go[21-33]
internal/server/audit_handlers.go[64-92]
internal/mcp/tools_audit.go[100-111]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Cluster-scoped dynamic audit subjects are collected without applying the authenticated caller's resource grants, causing denied findings to appear through HTTP and authorized findings to disappear through MCP.
## Fix Focus Areas
- internal/audit/runner.go[31-76]
- internal/audit/runner.go[133-176]
- internal/audit/scope.go[13-50]
- internal/mcp/tools_audit.go[100-111]
## Recommended Fix
Pass the read scope into dynamic subject collection and authorize each cluster-scoped watched GVR before including its objects. Represent grants for every cluster-scoped dynamic audit source, not only config-reference handlers, and replace MCP's blanket empty-namespace rejection with the same resource-grant decision.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Two reference comments narrate loops ✓ Resolved 📘 Rule violation ⚙ Maintainability
Description
CollectConfigObjectRefs adds comments that merely label the referenced-name initialization and the
Ingress TLS iteration performed immediately below them. Removing these comments leaves the intent
unchanged and prevents later readers from mistaking narration for an additional constraint.
Code

pkg/audit/checks.go[1968]

+	// Build set of referenced ConfigMap and Secret names (namespace/name)
Relevance

●●● Strong

New comments restate immediately visible initialization and loop behavior, matching the stated
repository rule.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 3036542 prohibits newly added comments that only restate obvious behavior. The comment at line
1968 labels maps and loops that visibly collect ConfigMap and Secret names, while line 1985 labels
the immediately following Ingress TLS secret loop.

Rule 3036542: Avoid explanatory comments that restate obvious code behavior
pkg/audit/checks.go[1968-1974]
pkg/audit/checks.go[1985-1992]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Two comments in `CollectConfigObjectRefs` only restate the behavior of the code immediately below them and provide no rationale, constraint, or invariant.
## Fix Focus Areas
- pkg/audit/checks.go[1968-1968]
- pkg/audit/checks.go[1985-1985]
## Recommended Fix
Remove both explanatory comments while leaving the reference collection logic unchanged.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Tip of the day
ðŸ’Ą Did you know, you can reply 'qodo' on any finding to push back, ask questions, or dig deeper

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment thread pkg/audit/checks.go Outdated
Comment thread internal/audit/runner.go Outdated
Comment thread internal/audit/coverage.go Outdated
Comment thread pkg/audit/types.go Outdated
@nadaverell
nadaverell force-pushed the fix/audit-partial-access branch 2 times, most recently from 28ec884 to f989e7b Compare September 13, 2026 13:22

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/audit/coverage.go Outdated
@nadaverell
nadaverell force-pushed the fix/audit-partial-access branch from f989e7b to 18c07b4 Compare September 13, 2026 13:37

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale Bugbot comment from a previous run.

Comment thread internal/audit/coverage.go Outdated
@nadaverell
nadaverell force-pushed the fix/audit-partial-access branch from 18c07b4 to e56fe2b Compare September 13, 2026 13:55
nadaverell added a commit that referenced this pull request Sep 13, 2026
## Summary

Add EmberStack Reflector support to Secret and ConfigMap details and
resource topology. Operators can follow a configuration's source and
visible mirrors, inspect reflection settings and recorded copy evidence,
and spot local configuration mistakes without treating restricted
visibility as a missing or unused object.

Follow-up to #1710. The companion audit PR #1741 uses the identical
shared dependency parser; either PR can merge first.

## Resource details

A Reflector section explains source and mirror roles, automatic versus
manual mirrors, reflection permission, and configured namespace rules.
Sources and visible mirrors link to the existing resource drawers. Name
patterns and label selectors are shown as alternatives within each rule;
Radar does not evaluate controller regexes or predict a destination set.
Large visible-mirror lists initially show ten entries with an expansion
control.

Mirrors show the recorded source version and copy time. When an
authorized source is present in the topology snapshot, the section
compares its observed resource version with the mirror's recorded
version. Cached versions can lag reconciliation. The result is
explicitly metadata evidence: metadata-only source edits change
versions, while local mirror edits may preserve the recorded version.
Neither equality nor mismatch is a content comparison or a
synchronization-health verdict.

Local warnings cover malformed/self source declarations, invalid
booleans, automatic creation without reflection permission, disabled
sources with visible mirrors, and mirror chains whose updates are not
propagated as ordinary source updates. Mirror details and Secret edit
confirmation explain that local edits can persist until a source change
overwrites them. Editing permissions and controls retain their existing
behavior.

The README supported-resources table and integration documentation
describe these surfaces and their limits.

## Relationships and access

A shared metadata-only parser resolves explicit `reflects` annotations
between observed same-kind core resources, including differently named
mirrors. Resource topology adds a source-to-mirror **Reflects to**
configuration edge. It implies no ownership or cascading deletion.
Traffic topology is unchanged; Secret nodes remain hidden by default.

The relationship response adds an optional `reflection` object
containing the observed source, its resource version, and visible
mirrors. Existing configuration/consumer arrays remain available to API
consumers; the drawer avoids duplicating reflection links in its generic
Related Resources section. Existing shared UI consumers can render
annotation details without injecting relationship data.

Private relationship caches retain ConfigMaps/PVCs on large clusters and
include Secrets. REST, MCP, and SSE remove unreadable namespaces, Secret
endpoints, and cluster-scoped kinds before projection; callers receive
cloned projections rather than mutations of the shared cache. SSE groups
payloads by Secret grants. Source-version evidence comes from these
already filtered nodes, with no additional object fetches or Secret
contents.

Secret list authorization also applies to existing name-only
Traefik/Contour TLS Secret stubs. Readers without that permission no
longer see those stubs or incident edges.

## Verification

- Full application build, frontend type checks, root Go tests, and
shared Go module tests passed.
- Shared UI suite: 3,662 tests passed, one skipped. Web suite: 1,148
passed. The final focused reflection suite adds two further diagnostic
cases (19 passed).
- Parser/topology tests cover chains, cycles, differently named mirrors,
missing endpoints, namespace/kind filters, Secret visibility, edge
semantics, and filtering source-version evidence. Existing authorization
tests cover shared-cache isolation.
- Test-cluster UI/API checks covered source/mirror navigation, matching
and differing version records, unavailable sources, disabled-reflection
warnings, ordinary-resource controls, and Secret edit confirmation
(cancelled without writing). Visual checks covered both themes at 1280
and 1920 widths; browser console had no errors.

Live checks used manually seeded annotations, not a running Reflector
controller. Earlier restricted-identity tests covered raw
detail/neighborhood filtering; this expansion adds a regression test for
withholding source-version metadata.

## Limits

Unavailable sources are not called missing, and visible mirror counts
are not expected totals. Controller availability, name conflicts, absent
automatic mirrors, destination eligibility, and synchronization lag
require evidence beyond this integration. Reflector's installed
version/configuration determines its namespace-rule behavior. No new
controller watches, permissions, or mutation actions are added.

Repository documentation is included. A separate public docs-site
navigation entry requires coordinated metadata in the radar-docs
repository.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches shared topology caches, per-user relationship projection, and
Secret visibility in SSE/API paths—authorization mistakes could leak
metadata or hide legitimate edges.
> 
> **Overview**
> Adds **first-class EmberStack Reflector** support for core
**ConfigMaps** and **Secrets** across UI, topology, REST/MCP context,
and docs—without new CRDs or controller watches.
> 
> **Operator surfaces:** ConfigMap/Secret drawers get **Reflector**
summary banners and a collapsible section (source/mirror role,
reflection settings, recorded `reflected-version` / `reflected-at`,
local config warnings). Mirrors link to authorized sources; Secret edits
warn that local changes may be overwritten. Generic Related Resources no
longer duplicate reflection links.
> 
> **Backend:** A shared **`configrefs` parser** resolves `reflects`
annotations between observed same-kind objects. Topology gains
**Reflects to** configuration edges; relationship APIs expose optional
**`reflection`** (source, mirrors, resource versions).
**`resourceContext.reflection`** on `get_resource`/REST AI context
includes declared vs observed source, copy metadata, and up to 20
RBAC-filtered mirrors. Cross-namespace lookup uses
**`ReflectionLookup`** over the resource cache.
> 
> **Access & caching:** Relationship topology is **cloned and filtered
per user** (namespaces + Secret list RBAC) so shared caches are not
mutated. SSE/MCP/REST strip unauthorized Secret nodes; relationship
caches **include Secrets** and retain ConfigMaps/PVCs on large clusters
for reflection edges.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
8630abf. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 85ea382. Configure here.

Comment thread internal/audit/scope.go
Comment thread internal/audit/coverage.go Outdated
nadaverell added a commit that referenced this pull request Sep 14, 2026
Radar can cache more resources than an authenticated viewer may read.
Previously, audit could evaluate cached Secrets and cluster-scoped
Crossplane resources without checking that viewer’s grants, exposing
findings and evaluation counts about hidden subjects. This PR applies
those checks before evaluation and carries incomplete-input warnings
through the audit surfaces.

### Authorization and behavior

Secret subjects require `list secrets` in their namespace.
Namespace-local grants remain valid when cluster-wide listing is denied.
Cluster-scoped Crossplane subjects require `list` on their exact API
group/resource. Other namespaced subjects retain Radar’s existing
namespace-visibility policy.

HTTP audit (including raw and per-resource reads), Home, AI context, MCP
audit, and MCP resource summaries use the same subject scope. The
existing five-second HTTP memo includes authorization scope and
typed/dynamic cache identity, preventing one caller’s results from
satisfying another caller’s differently scoped request.

Local and fully authorized scans retain their findings and evaluation
counts. Restricted callers lose unauthorized subjects and their counts.
Existing `missingInputs` metadata now reaches Home, Checks, MCP and
AI/resource context, including when no findings remain. Partial Secret
grants are disclosed; namespace-local grants covering the whole ready
namespace inventory do not produce a false warning.

Home and Checks show neutral empty states when nothing was evaluated,
and qualify results when inputs are missing. Fully evaluated clean scans
retain their positive state. These presentation changes also affect
full-access users when their scan evaluates nothing. The shared UI gains
optional coverage props; downstream hosts need to supply them to display
missing-input warnings. Per-resource REST audit responses remain arrays.

### Scope and risk

This is the subject-authorization prerequisite for #1741. It does not
solve whether orphan checks have enough consumer evidence to assert that
a resource is unused. That broader coverage work remains separate. No
discovery, informer lifecycle, Kubernetes grant, or cluster-write
behavior changes.

Risk is medium and concentrated in audit reads: incorrect scoping could
hide valid findings, disclose hidden subjects through counts, or reuse
another caller’s results. Tests cover these boundaries and live requests
verify identity isolation. Full-access backend results are preserved.
Cold permission checks still fan out across candidate namespaces and
watched resource types with bounded concurrency; the existing
single-slot memo can recompute across differing grant profiles.
Large-cluster cold latency is not benchmarked here. Existing
permission-cache invalidation timing and other kinds’ namespace-based
authorization policy remain unchanged.

Coverage warnings remain scan-wide: namespaces later excluded by local
settings can still trigger them, and a terminating namespace can briefly
trigger one after its grants disappear. The absence of a warning does
not certify that Radar’s own cache covers every namespace; that existing
limitation belongs to the separate coverage work in #1741.

### Validation

- Full Go QA, shared audit/resource-context package tests, TypeScript
checks, and the full application build passed before the final
warning-only correction. Affected audit tests and race checks were rerun
for that correction.
- Full frontend suites passed: 3,695 shared UI tests (one existing skip)
and 1,152 web tests.
- Race tests covered audit, audit-context, HTTP and MCP packages.
Regression cases include denial, namespace-local grants, exact cluster
grants, group collisions, scope/cache isolation, warning propagation and
neutral empty states.
- Live-tested against the existing Kubernetes 1.36.1 GitOps demo cluster
using explicit contexts. Local and authenticated full-access responses
had identical summaries and check counts. A real Secret-denied identity
received no Secret findings, with missing-input warnings across HTTP,
Home and MCP. Alternating identities and 12 concurrent requests retained
their own results.
- Live MCP resource context preserved a missing-input warning at zero
findings. Browser checks covered partial findings, partial empty states,
authorized zero-evaluation states, and light/dark presentation in seven
screenshots.
- Namespace-local positive grants and cluster-scoped Crossplane grant
combinations have automated regression coverage, not live fixture
coverage. The final all-namespace warning correction is
regression-tested; the earlier live/browser run predates that
correction.

All live checks were read-only against the cluster. The current
kubeconfig context was preserved, temporary Radar/proxy processes were
stopped, and no grants or fixtures were changed.
@nadaverell
nadaverell force-pushed the fix/audit-partial-access branch from f5687f3 to c10300a Compare September 14, 2026 15:34
Comment thread internal/audit/config_evidence.go Fixed
@nadaverell nadaverell changed the title Report incomplete audit coverage under partial access Require evidence before reporting unused ConfigMaps and Secrets Sep 14, 2026
@nadaverell
nadaverell merged commit 0fd4d04 into main Sep 15, 2026
9 checks passed
@nadaverell
nadaverell deleted the fix/audit-partial-access branch September 15, 2026 08:15
hisco added a commit that referenced this pull request Sep 17, 2026
â€Ķespaces

#1792 rewrote this description to say Radar only sees the namespaces it can
read, so a live consumer may sit outside that view. That is no longer true.
Since #1741 the check drops a subject unless every typed consumer kind and
every known CRD reference kind is synced and covers its namespace, the read
scope allows it, and reflections are complete. Incomplete evidence means no
finding at all, plus a configmap-references or secret-references missing
input. The namespace case the sentence described cannot produce a finding.

What stays unknowable is a consumer of a kind Radar does not parse: a CRD
outside the reference registry, or an app that reads the Secret by name in
code. The description now hedges on that instead.

Remediation is unchanged. Confirming usage and keeping a copy of a Secret
before deleting are right either way.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants