Skip to content

The framework guard reads raw YAML, so an output-only command can satisfy it #3060

Description

@devantler

🤖 Generated by the Agentic Engineer

Evidence

scripts/guard-kubescape-gate-frameworks.sh (added in #3057) asserts the Kubescape gate still names
every required framework. It matches against the raw workflow text and excludes comment lines, so a
command that merely prints the invocation still counts as one:

run: |
  echo "ksail workload scan --framework nsa,mitre"   # decoy — satisfies the guard
  FRAMEWORKS=nsa
  ksail workload scan --framework "$FRAMEWORKS"      # the real command, unreadable to the guard

The regex never sees the variable form, finds only the echo, and exits 0 on a repository scanning one
framework. Reproduced by Codex during review of #3057 and accepted there rather than patched.

Why it was not fixed inline, and why a blacklist is the wrong shape

The obvious fix — also reject echo and printf — is the third iteration of the same move. #3057
already excluded comments after one decoy, and this is the next spelling. Enumerating forbidden
prefixes only ever catches the ones somebody thought of: cat, a heredoc, :, true &&, a string
in a with: block. Each round closes one spelling and leaves the class open.

The convergent fix inverts the direction: parse the workflow structurally and read only what
actually executes
, rather than grepping text and subtracting known decoys.

Current exposure is bounded

  • The realistic regression — someone edits nsa,mitre to nsa — is caught today.
  • Anything the guard cannot read triggers its fail-closed path, so an unreadable form is a failure,
    never a silent pass.
  • Reaching the hole requires deliberately adding an output-only line that names the frameworks. That
    is an adversarial construction, not a plausible edit.

So this is a real gap in a defence-in-depth check, not an open door.

Smallest useful change

Rewrite the guard as a Go validator beside the repo's existing ones
(scripts/validate-concurrency-queue, scripts/validate-merge-group-heal,
scripts/validate-flux-verify), which is also what AGENTS.md's "bash first, Go when it grows"
rule points at now that this script parses, normalises and compares sets. It would parse the workflow
YAML, extract run: scalars, and identify genuine invocations — dropping the text-matching entirely.

Acceptance criteria

  • The guard identifies scan invocations from parsed workflow structure, not raw text
  • The echo decoy above is rejected, with that exact fixture in the test suite
  • Every existing assertion in scripts/tests/test-kubescape-gate-frameworks-guard.sh still holds
    — required members, identical sets across both workflows, fail-closed on no invocation, and the
    wiring checks
  • Ablation-proved: removing the structural parse makes the decoy case fail
  • The residual note in the guard header is removed rather than reworded

Rough size: S–M. Follow-up to #3057. Related: #2823.

Activity

  1. coderabbitai commented on Aug 10, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #2830 - feat(ci): publish Kubescape posture findings to Code Scanning [merged]
    #2917 - feat(security): reconcile live Kubescape findings into deduped backlog issues [merged]
    #2930 - feat(ci): verify published evidence before promoting the prod digest [merged]
    #2947 - fix(ci): refuse to promote a prod digest whose evidence cannot be verified [merged]
    #3043 - security(supply-chain): trust only commit-pinned shared publish workflows [merged]


    📝 Issue Planner

    Check the box below or use the @coderabbitai plan command to generate an implementation plan and prompts that you can use with your favorite coding assistant.

    • Create Plan

    🧪 Issue enrichment is currently in open beta.

    You can configure auto-planning by selecting labels in the issue_enrichment configuration.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  2. moved this to 📥 Backlog in 🌊 Project Boardon Aug 10, 2026
  3. added a commit that references this issue on Aug 10, 2026
    1da2132
  4. devantler commented on Aug 10, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Closed as fixed in #3057 at 1da2132a, without the Go rewrite this issue proposed.

    The framing here was that closing the class needed structural YAML parsing, because the alternative on the table was another exclusion. CodeRabbit reviewing the same head supplied the third option: invert to an allow-list — require ksail as the line's first non-blank token. That rejects every output-only form at once (echo, printf, cat, heredoc bodies, :, true &&, a quoted string in a with: block) with no parser.

    Four decoy shapes are pinned in scripts/tests/test-kubescape-gate-frameworks-guard.sh, and reverting to the comment-only exclusion makes the echo case fail, so the inversion is ablation-proved. The residual note is removed from the guard header rather than reworded.

    A structural parse may still be worth it one day, but it is no longer needed for this hole.

  5. moved this from 📥 Backlog to ✅ Done in 🌊 Project Boardon Aug 10, 2026
  6. devantler commented on Aug 10, 2026

    @devantler
    ContributorAuthor

    🤖 Generated by the Agentic Engineer

    Reopening — I closed this on an overclaim

    I closed this saying the allow-list inversion (require ksail as the first non-blank token) closed
    the class. It did not. Codex reproduced a surviving decoy on the very next review round:

    run: |
      cat <<'EOF'
      ksail workload scan --framework nsa,mitre
      EOF
      FRAMEWORKS=nsa
      ksail workload scan --framework "$FRAMEWORKS"

    The heredoc body line genuinely begins with ksail, so the allow-list accepts it, while the real
    command's variable form stays unreadable. Guard reports identical valid sets; Kubescape scans NSA only.

    What I got wrong, precisely

    The inversion closed the command-shape class — echo, printf, cat <<EOF as a command, a
    quoted string in a with: block. Those all fail because their first token is not ksail.

    It does not touch the shell-context class: a line whose first token is ksail but which is
    data rather than an executed command — a heredoc body, and by extension anything inside a
    non-executing block. Those are different axes, and I treated closing one as closing both.

    Why I am not adding heredoc detection

    Same reason the original issue gave, and it survives the correction: tracking heredoc state in a
    line-oriented bash matcher is the start of reimplementing a shell parser one construct at a time, and
    the next construct is already visible (a case branch, if false; then, a commented-out block that
    bash -n would still parse). The convergent fix remains what this issue asked for — parse the
    workflow YAML, take the run: scalars, and identify commands with shell awareness.

    Current exposure, restated honestly

    Unchanged from the original filing, and the allow-list did narrow it:

    • The realistic regression — editing nsa,mitre to nsa — is caught.
    • Every output-only command form is now caught (four decoys pinned in the suite).
    • Anything unreadable trips the fail-closed path.
    • What survives is a deliberately-constructed heredoc-plus-variable pair. Adversarial, not accidental.

    So: a real remaining gap in a defence-in-depth check, narrower than when this was filed, and not an
    open door. The guard header's residual note is restored and points here.

    Acceptance criteria from the original filing stand, plus:

    • The heredoc decoy above is rejected, with that exact fixture in the suite
  7. moved this from ✅ Done to 🫴 Ready in 🌊 Project Boardon Aug 10, 2026
  8. self-assigned this
    on Aug 22, 2026
  9. added a commit that references this issue on Aug 22, 2026
  10. moved this from 🫴 Ready to 👀 In Review in 🌊 Project Boardon Aug 22, 2026
  11. moved this from 👀 In Review to ✅ Done in 🌊 Project Boardon Aug 23, 2026
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

Fields

No fields configured for Security.

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions