Skip to content

security(k8s): disposition CKV_K8S_35 β€” 4 secrets passed as environment variablesΒ #3018

Closed
1 of 1 issue completed

Description

@devantler

πŸ€– Generated by the Agentic Engineer

Evidence

Re-measured with the repository's own scripts/megalinter-scan-counts.sh (the reproduction of CI's
invocation β€” a hand-rolled checkov run measures a different thing and produced a wrong plan once
already, see #2787). Current CI-equivalent posture is 12 failing checks, all kubernetes
framework, after #3017 cleared the six probe findings.

CKV_K8S_35 β€” "prefer using secrets as files over secrets as environment variables" β€” is 4 of those
12, and it is the largest group with no disposition and no child issue.
Every other remaining group
is already owned: CKV_K8S_40 by #2898 and #2904, CKV_K8S_38 by #2845, and the three singletons are
individually dispositionable.

The four sites span an application CronJob, an observability CronJob, and two controller workloads in
the docker provider overlay; all are derivable from the scan output rather than listed here.

Why this one is different from its siblings

The dispositions that came before it were verdicts β€” is this UID load-bearing, is this token
needed, is this pod real. This group is not: mounting a secret as a file instead of an environment
variable is a behavioural change to each workload, because the process has to read a path rather
than an env var, and for a third-party image that depends on whether the image supports a
*_FILE-style convention at all. So it cannot be settled by one risk acceptance the way #2904 settles
CKV_K8S_40.

Why it is worth doing rather than excepting

An environment variable is readable by anything that can inspect the process β€” including /proc,
crash dumps, and most logging middleware that dumps the environment β€” whereas a mounted file is
scoped by filesystem permissions and can be rotated in place without a restart. That is a real
reduction in exposure, not a scanner formality.

Expected behaviour

Each of the four either mounts its secret as a file, or carries a scoped checkov.io/skip naming the
concrete image constraint that prevents it β€” the same fix-vs-except ladder the sibling issues use, and
never a disabled check.

Acceptance criteria

  • Each of the four sites is either converted to a file mount or carries a scoped skip naming the
    image constraint, verified per-image rather than assumed.
  • scripts/megalinter-scan-counts.sh reports 0 CKV_K8S_35 and a total of 8.
  • A newly added workload passing a secret via env is still flagged (ablation, as in fix(security): disposition the six unreachable-probe checkov findingsΒ #3017).
  • The counts recorded in .mega-linter.yml are corrected in the same PR.

Rough size: medium β€” four sites, each needing its image checked before choosing fix or skip.

Part of #2787

Activity

  1. coderabbitai commented on Aug 8, 2026

    @coderabbitai
    Contributor
    πŸ”— Related PRs

    #2860 - security(k8s): record an explicit service-account-token verdict per workload [merged]
    #2893 - security(k8s): disposition CKV_SECRET_6 as key references, not secret material [merged]
    #2900 - security(k8s): withdraw the CKV_K8S_40 suppressions whose reasons do not hold [merged]
    #2911 - fix(security): disposition the three unconstrained CKV_K8S_40 sites [merged]
    #2929 - fix(security): preserve vendored Checkov dispositions [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 8, 2026
  3. devantler commented on Aug 11, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    The group is down to 1 of 4, and two of them were not fixable

    Measured on fc812a37 with scripts/megalinter-scan-counts.sh --checkov-only (checkov 3.3.0 local;
    CI runs 3.3.2): the whole checkov backlog is 7, of which CKV_K8S_35 Γ—3 β€” and 5 / Γ—1 with
    #3086 applied.

    site disposition
    CNPG alert webhook fixed β€” #3082 (merged)
    MinIO server (docker provider) excepted β€” #3086
    MinIO mc bucket Job (docker provider) excepted β€” #3086
    umami provisioning CronJob open β€” the one worth doing

    Correcting this issue's premise

    This issue says the group is "worth doing rather than excepting", on the reasoning that mounting a
    file instead of an env var is a behavioural change we can make, gated on "whether the image
    supports a *_FILE-style convention at all". For the MinIO pair the answer to that gate is no,
    and it is worth recording so it is not re-litigated:

    • MinIO server β€” its documented root-credential interface is MINIO_ROOT_USER /
      MINIO_ROOT_PASSWORD and it exposes no file-based equivalent. The *_FILE spelling that
      circulates for other images is undocumented for the MinIO server and reported not to initialise it,
      so adopting it would trade a scanner finding for a test-bed that silently fails to start.
    • mc client β€” no documented file-based credential input. Reading a mounted file in the shell
      would move the value into the process argv, which is not an improvement over the environment.
      Hand-writing mc's own config.json is the only genuinely file-based route and depends on an
      internal format platform CI cannot verify: static validation only, no full-cluster system test.
      An unverifiable change to the local backup test-bed would surface as a silent breakage the next
      time someone ran it.

    Both are the local Docker test-bed rather than a production credential, which is why an exception is
    proportionate here and would not be for umami. Both carry a resource-scoped checkov.io/skip, never
    a disabled check β€” verified by ablation in both directions: removing an annotation brings its finding
    back, and a newly added workload reading a Secret from the environment in the same namespace is
    still flagged.

    What remains

    Only the umami provisioning CronJob, and it is the one site where the fix is available: its
    consumer is a Node script defined inline in the same manifest, so it can read a mounted path
    directly. The non-obvious part is the TENANTS contract, which wires members by environment
    variable name
    ("passwordEnv": "ASCOACHINGOGVANER_PASSWORD"), so that indirection and its
    onboarding comment change with it. It is also a production workload holding an admin credential, and
    deployed only to the Hetzner provider β€” so behavioural proof is post-merge, on the next 15-minute
    tick.

    Acceptance for closing this issue is therefore just that one site: checkov reports 0
    CKV_K8S_35, with the ablation control still flagging a new env-var secret.

    (I briefly filed #3087 for that last site before spotting this issue already owned the group; #3087
    is closed as superseded and its detail is folded in above.)

  4. devantler commented on Aug 19, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    Measurement update β€” the counts in the body are stale, the issue is not

    Re-measured on main @4d2a3dc4 with scripts/megalinter-scan-counts.sh --checkov-only:

    checkov β€” 0 failing checks (1 parsing error; CI has 1)
      kubernetes 0 / 2131 Β· cloudformation 0 / 0 Β· github_actions 0 / 150
    

    The body's "12 failing checks, of which CKV_K8S_35 is 4" no longer describes the tree β€” checkov is at
    zero and is a blocking gate again (#3205).

    That is not because this issue was resolved. Exactly one CKV_K8S_35 site remains, and it is
    suppressed by an annotation that defers to this issue:

    k8s/providers/docker/infrastructure/controllers/minio/job.yaml
      checkov.io/skip1: "CKV_K8S_35=deferred to #3018 -- `mc alias import` is the file-based route,
      but adopting it restructures this Job's wait loop and no environment here can exercise the result"
    

    So the zero is partly held up by this deferral. The other three sites were addressed on their own
    merits (#3117 moved the umami provisioning secrets to files; the coroot cron-jobs carry reasoned
    in-line explanations rather than skips).

    Scope now: 1 site, not 4 β€” the minio bootstrap Job in the docker provider, whose blocker is
    stated in the annotation itself: mc alias import is the file-based route, but adopting it
    restructures the Job's wait loop, and no local environment exercises the result.

    Leaving this open deliberately: closing it would orphan an annotation that points here, and the
    suppression would then have no tracking issue behind it.

  5. self-assigned this
    on Aug 19, 2026
  6. added a commit that references this issue on Aug 19, 2026
  7. moved this from πŸ“₯ Backlog to πŸ‘€ In Review in 🌊 Project Boardon Aug 19, 2026
  8. added a commit that references this issue on Aug 22, 2026
  9. devantler commented on Aug 22, 2026

    @devantler
    ContributorAuthor

    πŸ€– Generated by the Agentic Engineer

    Completion verification β€” 2026-08-22

    Verified against current main@64c4ba23027b057f7bd89961cd426610f89186e9:

    • scripts/megalinter-scan-counts.sh --checkov-only reports 0 failing checks across the cloudformation, kubernetes, and github_actions frameworks, with the expected single parsing error. The local Checkov is 3.3.10 while CI pins 3.3.9; the current default head is also green, so the repository-owned exact-version gate agrees with the local zero.
    • The MinIO bucket credential contract passes: no secretKeyRef/secretRef or MC_HOST_* credential reaches the environment, no credential enters argv, the mounted file is imported directly, and no CKV_K8S_35 skip remains.
    • The CNPG alert and Umami provisioning credential contracts pass as well.
    • An unlisted synthetic Pod using a secretKeyRef environment variable is still rejected by CKV_K8S_35, proving the zero is not caused by disabling the check.

    The last tracked deferral was removed by #3245 through completed child #3247. The historical acceptance target of 8 total findings has been surpassed by later security slices: the current comparable total is 0, and .mega-linter.yml already records that current baseline.

    All remaining acceptance criteria are satisfied, so this parent is complete.

  10. moved this from πŸ‘€ In Review to βœ… Done in 🌊 Project Boardon Aug 22, 2026
  11. removed their assignment
    on Aug 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    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