Skip to content

Add Kubernetes privileged workload matcher - #112

Open
jadenfix wants to merge 6 commits into
vercel-labs:mainfrom
jadenfix:jaden/k8s-privileged-workload
Open

jadenfix wants to merge 6 commits into
vercel-labs:mainfrom
jadenfix:jaden/k8s-privileged-workload

Conversation

@jadenfix

@jadenfix jadenfix commented Jul 19, 2026 •

Copy link
Copy Markdown

What changed

Adds a k8s-privileged-workload matcher for privileged containers, privilege escalation, host namespaces, Windows host processes, hostPath volumes, root UID, unmasked /proc, and dangerous Linux capabilities, including SYS_BOOT, SYS_TIME, AUDIT_CONTROL, IPC_OWNER, PERFMON, CHECKPOINT_RESTORE, and SYS_RESOURCE.

The matcher reads YAML 1.1 syntax nodes, including escaped JSON/YAML keys, rather than matching field names in arbitrary text. It checks pod specs in Pods, PodTemplates, workload templates, CronJobs, and generic/typed Lists. Container checks cover regular, init, and ephemeral containers. YAML and JSON file globs are included. Flow maps, multiline capabilities, commented document separators, and YAML integer spellings are covered; hostPath is checked only in volume definitions.

Why

deepsec checks Kubernetes secret handling but lacked a workload-privilege matcher. The syntax-node approach also addresses the advisory review's coverage gaps and unrelated-key false positives.

Verification

  • Final scanner suite: 2,006 tests pass, including all matcher examples.
  • Eight regression cases fail against e186dea and pass with this revision, including a valid 12,000-link merge chain that previously throws RangeError. Added controls cover explicit and merge precedence, cycles, wide merge graphs, template-action limits, comments, source lines, and dropped capabilities.
  • Ten follow-up regressions fail against bf1a009 and pass with this revision: unmatched template openings, missing capabilities, custom API-group namesakes, and Kubernetes files under .github. Controls retain valid multiline actions, comments, literal braces inside Go strings, supported workload API groups, typed-list API inheritance, and workflow filtering.
  • Workspace build, lint, knip, bundle, and all 29 bundle tests pass.
  • Ran the bundled scanner on 31 YAML manifests from kubernetes/examples: one candidate, manually confirmed as the privileged Pod in _archived/podsecuritypolicy/rbac/pod_priv.yaml.
  • Full suite: 2,497 passed, 3 failed, 1 skipped. The three failures are the unchanged core shutdown tests. A standalone tsx -e reproduction fails to open its IPC socket with listen EPERM, so two children exit before READY and the third test times out. Pipeline e2e and all 29 bundle tests pass. No full-suite pass is claimed.
  • Upstream CI still requires maintainer approval.

Notes for reviewer

  • Input is split at --- markers. The matcher skips any document over 1,048,576 UTF-16 code units, files over 16 times that, and documents the YAML parser reports as invalid. YAML merge keys (<<) are followed iteratively. Multiline Go actions, comments, and quoted closing delimiters are masked in place before parsing; template expressions aren't evaluated. Unmatched openings are left intact. Delimiter lookahead is limited to 65,536 UTF-16 code units per opening and twice the file length in total iterations. Files exceeding the total lookahead budget or 100,000 openings are skipped.
  • Each pattern retains at most 64 distinct source lines and a 2,048-character snippet. Snippets and locations refer to the original source.
  • Alias targets are indexed once in source order, without expanding object graphs. Field lookup indexes each visited map once, caches requested fields, and shares a 1,000,000-step budget across the document. Further uncached lookups stop when the budget is exhausted. Workload traversal and merge lookup are iterative and deduplicate visited nodes; alias indexing and workload traversal also have explicit budgets.
  • Duplicate-key checking is disabled to avoid the parser's quadratic duplicate-key scan on wide maps. Field lookup uses the last explicit value.
  • Workload kinds are checked against their API groups: core, apps, extensions, and batch, plus apps.openshift.io DeploymentConfigs and argoproj.io Rollouts. Typed Lists inherit their API version when an item omits it. Unrelated custom API groups reusing workload kind names are ignored.
  • First-party Kubernetes files under .github are scanned. Workflow YAML is excluded by its parsed shape rather than its directory.
  • Helm dependency charts under charts/<name>/charts/ are skipped; first-party chart templates are scanned as-is.
  • Adds a pinned direct yaml@2.9.0 scanner dependency. Other dependency versions are unchanged.

Code, tests, and this description were prepared with OpenAI Codex assistance.

@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

@jadenfix is attempting to deploy a commit to the Vercel Labs Team on Vercel.

A member of the Team first needs to authorize it.

@jadenfix
jadenfix force-pushed the jaden/k8s-privileged-workload branch from 8b9856f to 355fd8a Compare July 19, 2026 21:09
@jadenfix
jadenfix marked this pull request as ready for review July 19, 2026 21:10
@jadenfix

jadenfix commented Jul 19, 2026 •

Copy link
Copy Markdown
Author

Ready for review at e1c02cd. The scanner package builds, all 1,859 matcher and matcher-example tests pass, Biome passes on the changed TypeScript, and git diff --check is clean. Adversarial coverage now includes first-party Helm charts, block-list capabilities, mixed YAML documents, ConfigMaps, dropped capabilities, annotations, block scalars, and exact source lines. GitHub Actions remain action_required for this fork, and Vercel previews require Vercel Labs authorization; an upstream maintainer must approve those service gates.

Comment thread packages/scanner/src/matchers/k8s-privileged-workload.ts Outdated
@lokesh0186

Copy link
Copy Markdown

I independently exercised the privileged-container portion of this matcher with IaC-Guard-V 0.1.0a1 against the current PR head (783195c…) and found one reproducible false negative:

securityContext: {privileged: true}

The equivalent block form is detected by the matcher, but this inline-map form produces no DeepSec finding. IaC-Guard-V’s protected kubernetes_no_privileged_containers_v1 oracle classifies the exact Pod resource as FAIL / ASSERTION_VIOLATED, consistent with Kubernetes Pod Security Standards.

I also found two additional boundary cases that may be useful depending on the intended matcher scope: a top-level kind: List containing a privileged Pod, and Windows hostProcess: true.

Minimal fixtures, exact SHAs, reproduction steps, and sanitized results are here:
https://github.com/lokesh0186/iac-guard-v/tree/556b0f29262ec149dafb30a3c1d9de8bb1c22b6c/examples/public-reproductions/vercel-labs-deepsec-112

@jadenfix
jadenfix force-pushed the jaden/k8s-privileged-workload branch from 783195c to 3f3f541 Compare October 5, 2026 19:42
@jadenfix

jadenfix commented Oct 5, 2026

Copy link
Copy Markdown
Author

@lokesh0186 thanks for testing this and for the clear repros! You were right, the patterns only matched block-style YAML so securityContext: {privileged: true} slipped through. Inline flow maps work for every field now, including capabilities: {add: [SYS_ADMIN]} and spec: {hostNetwork: true}. I also picked up both of the edge cases you mentioned: items inside a top-level kind: List are checked one by one, and windowsOptions.hostProcess: true is flagged. Tests added for all of them.

@auto-maintain

auto-maintain Bot commented Oct 5, 2026 •

Copy link
Copy Markdown

🤖 auto-maintain review

Automated, advisory triage for @jadenfix's PR. Facts below are read from the GitHub API.

Check Result
Author's merged PRs (this repo) 0 — ⚠️ first-time contributor
Account established ✅ (age 941d · 13 followers · 103 public repos)
Commits signed/verified ⚠️ 0/5

Review panel: 🟡 medium highest severity

deepsec maintainer code review: 🟡 medium

The matcher has a concrete capability-coverage gap that should be fixed before merge.

  • packages/scanner/src/matchers/k8s-privileged-workload.ts:29 — The dangerous-capability set omits PERFMON and CHECKPOINT_RESTORE, so workloads explicitly adding these host-impacting Linux capabilities are silently missed.

General code review: 🟡 medium

The matcher has concrete false-negative and false-positive cases that should be addressed before merge.

  • packages/scanner/src/matchers/k8s-privileged-workload.ts:148 — Template masking treats any unmatched `{{` as extending to EOF, so a valid quoted value containing `{{` can blank later privileged settings and make the document unparseable, causing a false negative.
  • packages/scanner/src/matchers/k8s-privileged-workload.ts:304 — The matcher accepts any string-valued apiVersion, so custom resources in unrelated API groups that reuse names such as Pod or Deployment are incorrectly scanned as Kubernetes workloads and can produce false positives.

Scanner and matcher quality: 🟡 medium

The matcher has concrete recall gaps in template masking, capability coverage, and path filtering that should be addressed before merge.

  • packages/scanner/src/matchers/k8s-privileged-workload.ts:148 — An unmatched literal `{{` is treated as a Helm action extending to EOF. Valid YAML such as a quoted Mustache fragment can therefore mask all later documents and hide privileged workloads; unmatched openings should be left intact or masking should be path-gated.
  • packages/scanner/src/matchers/k8s-privileged-workload.ts:29 — The dangerous-capability allowlist omits high-impact capabilities such as `PERFMON`, `CHECKPOINT_RESTORE`, and `SYS_RESOURCE`, so Kubernetes containers granted kernel/process inspection or resource-limit bypass privileges produce no candidate.
  • packages/scanner/src/matchers/k8s-privileged-workload.ts:241 — The unconditional `.github` path exclusion silently skips first-party Kubernetes manifests stored beneath `.github`, despite syntax-aware parsing already preventing workflow-YAML key false positives.

Sandbox and credential boundaries: 🟡 medium

No sandbox, credential, egress, or agent-boundary surface is touched; the only actionable issue is an unbounded Go-template masking scan that can silently blank the rest of a manifest and hide real findings.

  • packages/scanner/src/matchers/k8s-privileged-workload.ts:163 — maskTemplates scans a Go action forward without any bound: an unterminated `{{`, or an unbalanced quote inside an action (line 163 treats `"`, `'`, and backtick as Go string delimiters), makes `end` run to EOF, so everything from that point to the end of the file is blanked before parsing. Since masking is applied to every `.yaml`/`.yml`/`.json` file containing `{{` — not just Helm chart templates — a single stray `{{` (e.g. `note: "see {{"`) or an apostrophe in an action (`{{ don't }}`) silently suppresses all privileged-workload detection for the remainder of the file. Bound the scan (next `{{`, blank line, or line end) and fall back to leaving the text unmasked when no `}}` is found.

Durability and distributed state: 🟢 low

No durability or distributed-state defects found in the changed code.

Posted by auto-maintain. This automated code review is advisory; a human maintainer makes the call.

Flags workload manifests that run privileged, share host namespaces,
run as Windows host processes, mount hostPath volumes, run as UID 0,
unmask /proc, or add dangerous capabilities. Handles block and flow
YAML, multi-document files, documents written entirely in flow style,
PodTemplate, and workloads inside List or typed lists such as PodList.

Quoted string values, comments, block scalars, and metadata labels and
annotations are not matched. Helm dependency charts under
charts/<name>/charts/ are skipped. Snippets show the original manifest
lines around the first hit.
@jadenfix
jadenfix force-pushed the jaden/k8s-privileged-workload branch from 3f3f541 to 674d4ad Compare October 5, 2026 20:28
Kubernetes decodes manifests with sigs.k8s.io/yaml, which follows YAML
1.1, so `hostPID: yes` and `allowPrivilegeEscalation: on` are true and
were being missed. Count y, yes and on in all their spellings.

A quoted "true" or "0" is a string, which Kubernetes rejects for these
boolean and integer fields, so stop unquoting those. A nodeSelector or
matchLabels entry like `privileged: "true"` was reported as a
privileged container.
@jadenfix

jadenfix commented Oct 8, 2026

Copy link
Copy Markdown
Author

Addressed the advisory findings in a62af88. The matcher now reads bounded YAML 1.1 syntax nodes, covers JSON/flow Lists and multiline capabilities, scopes hostPath to volumes, and caps stored locations and snippets. All 1,972 scanner tests, workspace build, lint, knip and 29 bundle tests pass. A scan of 31 Kubernetes example manifests produced one confirmed privileged-Pod candidate. The description records input limits and the three environment-blocked shutdown tests. Could a maintainer approve the fork CI and review this revision when convenient? Thanks!

Kubernetes resolves `<<` merge keys, so a securityContext that inherits
`privileged: true` from an anchor now matches. Explicit keys still win,
and earlier merge sources win over later ones.

Go template actions are masked in place before parsing, so first-party
Helm templates parse as YAML instead of being skipped. Lines and offsets
don't move, and snippets still show the original template text.

The 1 MiB limit now applies per document. A multi-document bundle up to
16 MiB is split at `---` markers and only an oversized document is
skipped. Files without "kind" skip parsing entirely.
@jadenfix

jadenfix commented Oct 9, 2026

Copy link
Copy Markdown
Author

Pushed e186dea for the latest advisory review on a62af88. Merge keys (<<: *ctx and <<: [*a, *b]) are now followed, with explicit keys taking precedence. Go template actions are masked in place before parsing, so Helm templates in first-party charts get scanned and line numbers stay the same. The 1 MiB limit now applies per document, so a large --- bundle only skips the oversized document instead of the whole file. Each case has a regression test. Full test suite, lint, knip, bundle tests and typecheck pass.

@auto-maintain

auto-maintain Bot commented Oct 9, 2026

Copy link
Copy Markdown

⚠️ auto-maintain: review severity raised to 🔴 high

A new push changed this PR and the review now contains a higher-severity finding. See the updated review comment above.

Resolve merge keys iteratively with shared work limits and cached map
indexes. Mask Go strings and multiline comments without moving source
locations, parse escaped field names, and flag four host-impacting
capabilities. Add regressions for deep chains, cycles, merge precedence,
template limits, escaped keys, and capability drop controls.

Prepared with OpenAI Codex assistance.
Leave unmatched Go action openings intact and bound delimiter lookahead.
Validate kind/API group pairs, inherit typed-list API versions, scan
first-party manifests under .github, and flag PERFMON,
CHECKPOINT_RESTORE, and SYS_RESOURCE when explicitly added.

Prepared with OpenAI Codex assistance.
@jadenfix

Copy link
Copy Markdown
Author

The advisory comment above targets bf1a009. These findings are addressed in the current 1a69fd4: unmatched template openings are preserved, template scans have per-opening and total budgets, PERFMON/CHECKPOINT_RESTORE/SYS_RESOURCE are included, workload kinds are checked against their API groups, and first-party Kubernetes manifests under .github are scanned. The PR description lists the regression coverage and validation results for this revision.

@jadenfix

Copy link
Copy Markdown
Author

The standard CI runs for #112 at 1a69fd4 and #113 at 0553db9 still report action_required. Could a maintainer approve the #112 CI run and #113 CI run so upstream validation can run on both revisions? Each PR description records its local validation results. Thanks.

This branch has not been deployed

No deployments
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