Skip to content

fix: prevent panic on non-string ScanCP args.name/namespace - #415

Open
rootp1 wants to merge 6 commits into
kubescape:mainfrom
rootp1:fix/scancp-args-type-assertion-panic
Open

fix: prevent panic on non-string ScanCP args.name/namespace#415
rootp1 wants to merge 6 commits into
kubescape:mainfrom
rootp1:fix/scancp-args-type-assertion-panic

Conversation

@rootp1

@rootp1 rootp1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Overview

Both the HTTP controller and the service-layer validation for the ScanCP (application profile scan) path perform unchecked type assertions on Args["name"] and Args["namespace"]. A request with a non-string value for either field causes a panic instead of a validation error.

This PR replaces the unchecked type assertions in controllers/http.go, core/services/scan.go (ValidateScanCP), and core/services/scan.go (ScanCP) with comma-ok assertions, so malformed values fall through to the existing domain.ErrMissingCpInfo validation error path instead of panicking.

How to Test

go test ./controllers/... ./core/services/...

Added TestScanService_ValidateScanCP, covering missing args and non-string name/namespace values, which previously would panic.

Related issues/PRs:

Resolved #414

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • New and existing unit tests pass locally with my changes

Summary by CodeRabbit

  • Bug Fixes

    • Improved scan validation to handle missing or invalid name and namespace values without crashing.
    • Invalid scan inputs now produce validation results instead of unexpected failures.
  • Tests

    • Added coverage for missing arguments, incorrect value types, and valid scan inputs.

rootp1 added 4 commits July 31, 2026 23:23
Signed-off-by: rootp1 <arnav.iitr@gmail.com>
…ed fix

Signed-off-by: rootp1 <arnav.iitr@gmail.com>
Use comma-ok type assertions in the ScanCP handler and validation path so malformed args.name/args.namespace values return ErrMissingCpInfo instead of panicking.

Signed-off-by: rootp1 <arnav.iitr@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

ScanCP now handles missing or non-string name and namespace arguments without panicking. Existing validation processes these values, and table-driven tests cover invalid and valid inputs.

Changes

Scan argument validation

Layer / File(s) Summary
Safe argument extraction
controllers/http.go, core/services/scan.go
ScanCP and ValidateScanCP use checked assertions for name and namespace. Invalid values become empty strings and flow through existing validation.
Validation coverage
core/services/scan_test.go
Table-driven tests cover missing arguments, non-string values, and valid name and namespace strings.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: matthyx

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the ScanCP panic fix for non-string name and namespace arguments.
Linked Issues check ✅ Passed The changes prevent ScanCP panics and allow malformed arguments to return domain.ErrMissingCpInfo, as required by issue #414.
Out of Scope Changes check ✅ Passed The changes are limited to safe ScanCP argument handling and focused validation tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

…e-assertion-panic

# Conflicts:
#	repositories/apiserver_test.go
@rootp1

rootp1 commented Aug 1, 2026

Copy link
Copy Markdown
Contributor Author

@matthyx can you please have a look whenever you have a chance to

@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.

Reviewed at cc7daf0.

The core fix is right. Args is a map[string]interface{} populated straight from the request JSON (controllers/http.go:194, Args: c.Args), so args.name: 123 — or no args at all — reaches newScan.Args[domain.ArgsName].(string) at line 105 before any validation runs. I put your new test in front of main's core/services/scan.go and it reproduces exactly what #414 describes:

--- FAIL: TestScanService_ValidateScanCP/missing_args
panic: interface conversion: interface {} is nil, not string

With the comma-ok assertions the malformed value falls through to ErrMissingCpInfo, which is the behaviour the issue asks for, and go test ./controllers/... ./core/services/... is clean here.

One blocker: this PR re-introduces the VEX backfill loop we dropped in #404 on purpose. repositories/apiserver.go and repositories/apiserver_test.go have nothing to do with #414, and the code they carry is the unreachable loop from the pre-rebase version of that PR. Details inline. Everything else is non-blocking.

Follow-up, not for this PR

The same bug class is still live one layer down, and there it is worse. adapters/v1/backend.go:322-340 does an unchecked val.(string) on seven workload.Args[...] values, fed by the same client-supplied args map (registryScanCommandToScanCommand also does Args: c.Args). Confirmed by calling SubmitCVE with Args: {"registryName": 123}:

panic: interface conversion: interface {} is int, not string
	.../adapters/v1/backend.go:322

SubmitCVE runs inside a h.workerPool.Submit(...) task, and gammazero/workerpool has no recover() anywhere in its worker loop — so unlike the panic this PR fixes, which gin.Recovery() (cmd/http/main.go:121-122) downgrades to a 500, that one takes the process down. backend.go:174 in the same file already does it the safe way, so the fix is mechanical. Worth its own issue rather than growing this PR.

Comment thread repositories/apiserver.go Outdated
Comment on lines +789 to +798

// Backfill statements that were already marked "affected" by a version of kubevuln
// predating action_statement support, or that were left stale because their CVE fell
// out of the current relevancy set before the backfill could run on a prior update.
for i, s := range vexDoc.Statements {
if s.Status == v1beta1.Status(vex.StatusAffected) && s.ActionStatement == "" {
vexDoc.Statements[i].ImpactStatement = ""
vexDoc.Statements[i].ActionStatement = defaultActionStatement
}
}

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.

Blocker — this is the backfill loop from the pre-rebase version of #404, which we dropped there on purpose, and none of it belongs to #414.

Recap from that thread: I asked for this loop in my first review, then #401 (a5e45e3) landed the reset pass in updateVEX that sends every statement back to not_affected with ActionStatement = "" before markRelevantVulnerabilitiesAsAffectedInVex reapplies the manifest. That made the loop unreachable, so 53b2adf dropped it — merged #404 (33b467b) has no backfill.

It is unreachable on this branch too, on both callers:

  • createVEX (apiserver.go:856) — statements are constructed not_affected, and the mark loop then sets ActionStatement = buildActionStatement(v), which never returns "" (it falls back to defaultActionStatement).
  • updateVEX (apiserver.go:952-957) — the reset loop clears Status and ActionStatement immediately before the call.

Same probe as last time: replace the loop body with panic("BACKFILL LOOP REACHED") and run the suite.

$ go test ./repositories/...
ok  	github.com/kubescape/kubevuln/repositories	2.761s

Zero hits — including from the test this PR adds below.

How it got here: the branch was cut before that rework (35619a3 and 2f04c5a predate it) and the cc7daf0 merge of upstream/main auto-merged apiserver.gomain never had the loop, so there was nothing for the merge to remove and your side was kept verbatim. git checkout upstream/main -- repositories/apiserver.go repositories/apiserver_test.go should get this PR back to the three files #414 is about.

Comment thread repositories/apiserver_test.go Outdated
Comment on lines +451 to +509
// TestAPIServerStore_updateVEX_backfillsActionStatementOnStaleAffectedStatements guards against a
// regression where a statement marked "affected" by an older kubevuln (before action_statement
// support) kept an empty action_statement forever. Since updateVEX now resets every statement to
// "not_affected" before reapplying the current filtered manifest, a CVE that fell out of the
// relevancy set is expected to be reset rather than backfilled.
func TestAPIServerStore_updateVEX_backfillsActionStatementOnStaleAffectedStatements(t *testing.T) {
cveManifest := tools.FileToCVEManifest("testdata/nginx-cve.json")
cveManifestFiltered2 := tools.FileToCVEManifest("testdata/nginx-cve-filtered-2.json")
cveManifestFiltered := tools.FileToCVEManifest("testdata/nginx-cve-filtered.json")

a := NewFakeAPIServerStorage("kubescape")

ctx := context.TODO()
workload := domain.ScanCommand{
ImageHash: "sha256:32fdf92b4e986e109e4db0865758020cb0c3b70d6ba80d02fe87bad5cc3dc228",
InstanceID: "apiVersion-apps/v1/namespace-kubescape/kind-ReplicaSet/name-kubevuln-65bfbfdcdd/containerName-kubevuln",
Wlid: "wlid://cluster-aaa/namespace-anyNamespaceJob/job-anyJob",
ImageTag: "registry.k8s.io/coredns/coredns:v1.10.1",
ContainerName: "anyJobContName",
}
ctx = context.WithValue(ctx, domain.WorkloadKey{}, workload)

// First store: CVE-2005-2541 is relevant and marked affected.
err := a.StoreVEX(ctx, cveManifest, cveManifestFiltered2, false)
assert.Equal(t, err, nil)

vexContainer, err := a.StorageClient.OpenVulnerabilityExchangeContainers(a.Namespace).Get(context.Background(), cveManifest.Name, metav1.GetOptions{})
assert.Equal(t, err, nil)

// Simulate a document written by a pre-fix kubevuln: affected but no action_statement.
found := false
for i, stmt := range vexContainer.Spec.Statements {
if stmt.Vulnerability.Name == "CVE-2005-2541" {
vexContainer.Spec.Statements[i].ActionStatement = ""
found = true
}
}
require.True(t, found, "expected CVE-2005-2541 to be present after the first store")

_, err = a.StorageClient.OpenVulnerabilityExchangeContainers(a.Namespace).Update(ctx, vexContainer, metav1.UpdateOptions{})
assert.Equal(t, err, nil)

// Second store: CVE-2005-2541 is no longer in the filtered manifest.
err = a.StoreVEX(ctx, cveManifest, cveManifestFiltered, false)
assert.Equal(t, err, nil)

vexContainer, err = a.StorageClient.OpenVulnerabilityExchangeContainers(a.Namespace).Get(context.Background(), cveManifest.Name, metav1.GetOptions{})
assert.Equal(t, err, nil)

found = false
for _, stmt := range vexContainer.Spec.Statements {
if stmt.Vulnerability.Name == "CVE-2005-2541" {
found = true
assert.Equal(t, v1beta1.Status(vex.StatusNotAffected), stmt.Status, "statement no longer relevant should be reset to not_affected")
assert.Empty(t, stmt.ActionStatement, "not_affected statement must not carry an action_statement")
}
}
require.True(t, found, "expected CVE-2005-2541 statement to still be present after the update")
}

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.

Blocker — goes away with the same revert as apiserver.go, but flagging separately because this test is misleading on its own terms.

The name says backfillsActionStatementOnStaleAffectedStatements; the assertions say the opposite — not_affected and an empty ActionStatement, i.e. that the backfill does not happen. Your own doc comment on lines 453-455 says as much. It passes, but it passes against the reset behaviour from #401, not against the loop it is named for: delete that loop and this test is still green.

The invariant it does check is already covered, and more broadly. TestAPIServerStore_storeVEX_updateRestoresNotAffected (line 360) walks every statement in the document rather than just CVE-2005-2541, and already asserts assert.Empty(t, stmt.ActionStatement, ...) at line 408. So there is no coverage lost by dropping these 60 lines.

Comment thread controllers/http.go
Comment on lines +105 to +106
name, _ := newScan.Args[domain.ArgsName].(string)
namespace, _ := newScan.Args[domain.ArgsNamespace].(string)

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.

Non-blocking, but the handler is the half of this fix with no test behind it.

#414's repro hits these two lines before ValidateScanCP is ever reached, yet the only test added is at the service layer — and there is no TestHTTPController_ScanCP at all today (http_test.go has Alive, GenerateSBOM, Ready, ScanCVE, ScanRegistry, registryScanCommandToScanCommand, ContextCancellationIsDetached). Drop either , _ here and the suite stays green.

A small handler test closes it: POST /v1/scanCP with "args": {"name": 123, "namespace": true}, asserting the validation error is returned and no job reaches the pool. One thing to watch — register the route on a bare gin.New() rather than gin.Default(), otherwise gin.Recovery() converts a regression into the same 500 the validation path returns and the test can't tell the two apart. The contextSpyScanService harness at line 289 already gives you the "did the worker run" signal.

Comment on lines +637 to +640
if (err != nil) != tt.wantErr {
t.Errorf("ValidateScanCP() error = %v, wantErr %v", err, tt.wantErr)
return
}

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.

Nit: wantErr bool is satisfied by any error, but the contract in #414 and in your description is specifically that malformed values fall through to domain.ErrMissingCpInfo. A wantErr error field with assert.ErrorIs(t, err, tt.wantErr) pins the behaviour you actually claim, and would still trip if someone later swaps the sentinel or short-circuits earlier in the function.

@matthyx matthyx moved this to Waiting on Author in KS PRs tracking Aug 3, 2026
Signed-off-by: rootp1 <arnav.iitr@gmail.com>
@rootp1

rootp1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@matthyx you're right, that was leftover from a merge conflict resolution and doesn't belong in this PR. I removed the backfill loop from apiserver.go and the accompanying test, keeping this PR scoped to the ScanCP args type assertion fix.

@rootp1

rootp1 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

@matthyx pushed the fix, could you take another look when you have a chance?

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/services/scan_test.go`:
- Around line 607-621: Expand the invalid-argument cases in the scan validation
tests around the existing “non-string name and namespace” workload so
domain.ArgsName and domain.ArgsNamespace are each tested independently as
missing and non-string. Retain or replace the combined case only if useful, and
ensure each case expects validation failure so the validator must reject either
invalid argument individually.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c173bffe-fb47-49f5-a549-a5e915210003

📥 Commits

Reviewing files that changed from the base of the PR and between 9115f31 and a8e2c82.

📒 Files selected for processing (3)
  • controllers/http.go
  • core/services/scan.go
  • core/services/scan_test.go

Comment on lines +607 to +621
{
name: "missing args",
workload: domain.ScanCommand{},
wantErr: true,
},
{
name: "non-string name and namespace",
workload: domain.ScanCommand{
Args: map[string]interface{}{
domain.ArgsName: 123,
domain.ArgsNamespace: true,
},
},
wantErr: true,
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Cover each invalid argument independently.

The current invalid case makes both domain.ArgsName and domain.ArgsNamespace invalid at the same time. Add cases where only one argument is missing or non-string. The combined case would still pass if the validation condition changed from || to &&.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/services/scan_test.go` around lines 607 - 621, Expand the
invalid-argument cases in the scan validation tests around the existing
“non-string name and namespace” workload so domain.ArgsName and
domain.ArgsNamespace are each tested independently as missing and non-string.
Retain or replace the combined case only if useful, and ensure each case expects
validation failure so the validator must reject either invalid argument
individually.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Waiting on Author

Development

Successfully merging this pull request may close these issues.

Bug: application profile scan path panics on missing or non-string args.name / args.namespace

2 participants