Skip to content

security(ci): the Kubescape guard's conditional inversion refuses six classes of legitimate step #3586

Closed

Description

@devantler

🤖 Generated by the Agentic Engineer

The inversion's false-positive surface is much larger than I priced it

#3546 inverted the Kubescape guard's conditional screen from "recognise a scan" to "refuse unless shown scan-free". That closed nine bypass spellings. I priced its cost as "a conditional step mentioning the scan is refused" — small, and worth it.

That estimate was wrong. Review found six distinct classes of legitimate step the inversion refuses:

# Shape Why it is refused
1 echo ok # ; "$STATUS" inline comments reach the command-position walk unstripped, so comment text is read as shell
2 a heredoc body conditional scalars are screened line by line with no heredoc state, so data is read as code
3 {
echo hello
}
an unquoted { grouping token looks like brace expansion, so the command name reads as undetermined
4 ARGS=( … "$VALUE" … ) a multi-line array assignment is inspected as a fresh command per line
5 checkov --file manifest.yaml --framework … --framework is not unique to KSail — checkov uses it, and this repository already guards checkov
6 bash -c 'echo scanner …' the shell-string rule substring-matches scan, so scanner matches

Item 5 is the one that should have been obvious: the scalar-wide key is the inversion's main key, and it is not specific to the tool it is guarding.

Why this matters more than the count suggests

The repository's own hardening rule requires both halves of a change: what the security floor gained, and what the everyday path cost — and "a change that raises the floor and the friction is a draft, not a delivery". On that test the inversion currently fails. A guard that blocks comments, heredocs, brace groups, array assignments, and a second scanner is one people learn to route around, which costs more security than the nine spellings it closed.

Recommendation: split the work

The fixes on #3546 fall into two groups with very different risk:

  • Surgical, no false-positive surface — the prefixed invocation, the shell-string form, backslash parity, word adjacency across a continuation, and separator splitting. Each closes a specific bypass by making the conditional screen read the shell the way the unconditional path already does. These are worth shipping as they are.
  • The inversion and its command-position machinery — closes four further spellings (subshell, alias, path-qualified, non-bash shell) and carries the whole surface above.

Ship the first group; take the second as its own change where the surface can be worked properly — most likely by keying on something actually specific to the scan rather than --framework, and by giving the screen real comment/heredoc/brace state instead of a line-wise approximation.

Acceptance criteria

Activity

  1. added theissue type on Sep 4, 2026
  2. moved this to 📥 Backlog in 🌊 Project Boardon Sep 4, 2026
  3. devantler commented on Sep 24, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer (interactive Claude Code session)

    Status check against main (35031dbb), found while working on Spike #3564.

    Criterion 1 is met. Since #3546, the guard accepts all six shapes in the table above in a conditional step. I ran each one beside the real scan.

    Criterion 2 is not met yet. #3546 said the spellings that relied on the inversion would be tracked here with their reproductions, but they never reached this issue. Here they are, each run in a conditional (if: always()) step beside the real nsa,mitre scan:

    Spelling main parser prototype (#3579) bash runs an nsa-only scan?
    subshell: (ksail workload scan --framework nsa) accepted refused yes
    alias: shopt -s expand_aliases, alias k=ksail, k workload scan --framework nsa on separate lines accepted accepted yes
    path-qualified: /usr/local/bin/ksail workload scan --framework nsa accepted accepted yes, when that path is ksail
    non-bash shell: the same step with shell: sh refused refused —

    The same spellings in an unconditional step are refused on main. So the alias and path-qualified spellings remain open after #3579; the parser-backed guard closes the subshell.


    Generated by Claude Code

  4. self-assigned this
    on Oct 3, 2026
  5. added 2 commits that reference this issue on Oct 3, 2026
  6. devantler commented on Oct 3, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Delivered by merged PR #4431, reviewed source commit 06d951f.

    The shared ordinary-shell table accepts all six original legitimate shapes: inline comments, heredoc data, brace groups, multiline array assignments, the other framework-aware scanner, and harmless interpreter strings containing scanner prose. Existing bypass refusals remain covered by the same table used for both admission paths. The real workflows and positive conditional controls pass.

    Acceptance was checked against the original issue body. All 266 test/subtest assertions passed with race detection, including 28 Bash recording witnesses. The original historical tests are unchanged; source CI and protected merge-queue CI passed. Closing as completed.

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

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Fields

Priority

None yet

Effort

None yet

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions