惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V2EX - 技术
V2EX - 技术
P
Privacy International News Feed
Security Latest
Security Latest
H
Hacker News: Front Page
T
Tenable Blog
The Hacker News
The Hacker News
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
S
Security @ Cisco Blogs
Project Zero
Project Zero
O
OpenAI News
AI
AI
Spread Privacy
Spread Privacy
C
CERT Recently Published Vulnerability Notes
The Last Watchdog
The Last Watchdog
G
GRAHAM CLULEY
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
Scott Helme
Scott Helme
Application and Cybersecurity Blog
Application and Cybersecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
C
CXSECURITY Database RSS Feed - CXSecurity.com
NISL@THU
NISL@THU
A
Arctic Wolf
T
Threat Research - Cisco Blogs
PCI Perspectives
PCI Perspectives
N
News and Events Feed by Topic
C
Cyber Attacks, Cyber Crime and Cyber Security
C
Cybersecurity and Infrastructure Security Agency CISA
Simon Willison's Weblog
Simon Willison's Weblog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Know Your Adversary
Know Your Adversary
Google Online Security Blog
Google Online Security Blog
罗磊的独立博客
L
LINUX DO - 最新话题
U
Unit 42
S
Security Affairs
有赞技术团队
有赞技术团队
WordPress大学
WordPress大学
博客园 - 【当耐特】
T
The Exploit Database - CXSecurity.com
S
Schneier on Security
月光博客
月光博客
Engineering at Meta
Engineering at Meta
腾讯CDC
F
Full Disclosure
Cyberwarzone
Cyberwarzone
S
SegmentFault 最新的问题
Recorded Future
Recorded Future
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
博客园 - 司徒正美
The Cloudflare Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
What Really Happens After SCC Admission in OpenShift?
Nandan Hegde · 2026-05-21 · via DEV Community

A Deep Dive from Security Context Constraints to Linux Kernel Capability Computation


A Debugging Story That Will Change How You Think About Container Capabilities

You are a platform engineer running OpenShift. A development team runs a monitoring sidecar as a non-root user that needs to perform ICMP ping health checks. They need CAP_NET_RAW - the capability required for raw socket access. Straightforward enough.

The SCC is configured to allow the capability:

apiVersion: security.openshift.io/v1
kind: SecurityContextConstraints
metadata:
  name: net-raw-scc
allowedCapabilities:
  - NET_RAW
requiredDropCapabilities:
  - ALL
runAsUser:
  type: MustRunAsRange
  uidRangeMin: 1000
  uidRangeMax: 65534
fsGroup:
  type: MustRunAs
seLinuxContext:
  type: MustRunAs

Enter fullscreen mode Exit fullscreen mode

And the PodSpec requests it:

apiVersion: v1
kind: Pod
metadata:
  name: ping-test
spec:
  containers:
  - name: monitor
    image: registry.example.com/monitor:latest
    securityContext:
      runAsUser: 1000
      capabilities:
        add:
        - NET_RAW
        drop:
        - ALL

Enter fullscreen mode Exit fullscreen mode

The Pod is admitted. The container starts successfully. But inside the container:

$ ping -c 1 10.0.0.1
ping: Operation not permitted

Enter fullscreen mode Exit fullscreen mode

The developer checks the process capabilities:

$ cat /proc/1/status | grep Cap
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000002000
CapAmb: 0000000000000000

Enter fullscreen mode Exit fullscreen mode

CapEff is empty. The capability is not effective. ping fails with Operation not permitted.

Everything looks correct. SCC allows it. PodSpec requests it. CRI-O starts the container successfully. But the privilege is not there at runtime.

If you have never traced the full lifecycle from SCC admission through CRI-O's OCI spec generation to the Linux kernel's execve() capability computation, this will seem like a bug. It is not. To understand why, we need to follow the entire security pipeline from the OpenShift API server's admission decision down to the capability evaluation in CRI-O.


What SCC Actually Does (and What It Does Not)

Security Context Constraints are OpenShift's mechanism for controlling what security configuration is allowed to enter the cluster. SCC predates Kubernetes Pod Security Admission (PSA) and provides significantly more granular control. While PSA operates on a coarse three-tier model (Privileged, Baseline, Restricted), SCCs allow fine-grained field-level policies: which capabilities are allowed, which UIDs, which volume types, whether privilege escalation is permitted, and so on.

SCC Selection and Admission

When a Pod is created, the OpenShift API server's admission controller evaluates it against all SCCs available to the requesting user's service account. For a deeper treatment of SCC management, see the Red Hat blog on managing SCCs.

The Critical Distinction

Here is what engineers frequently misunderstand: SCC does not enforce runtime privileges. SCC is an admission-time gate. It determines whether a particular security configuration is allowed to be submitted to the cluster. Once the pod passes admission, the SCC's job is done. It has no runtime component. It does not communicate with CRI-O. It does not write iptables rules, adjust kernel parameters, or configure capabilities on processes.

The actual runtime enforcement happens in an entirely separate stack: kubelet -> CRI-O -> OCI runtime -> Linux kernel.


How SCC Translates Into Runtime Configuration

CRI-O has no concept of SCCs. It receives a CreateContainerRequest and it translates that into an OCI runtime specification. Let's trace the key SCC-derived fields through this pipeline.

For SCC fields like allowHostNetwork, allowHostPID, and allowHostIPC, the SCC-to-runtime translation is straightforward. CRI-O receives namespace mode flags via CRI and directly configures whether the container joins the host's namespace or gets its own. The mapping is direct and predictable.

Capabilities are where the translation becomes non-obvious.

Privilege and Capability Configuration

This is where the pipeline becomes consequential for our debugging scenario. SCC fields like allowPrivilegedContainer, allowedCapabilities, defaultAddCapabilities, and requiredDropCapabilities gate what can appear in the PodSpec.

CRI-O receives the security config from CreateContainerRequest and processes them in SpecSetPrivileges():

func (c *container) SpecSetPrivileges(ctx context.Context, securityContext *types.LinuxContainerSecurityContext, cfg *config.Config) error {
    specgen := c.Spec()
    if c.Privileged() {
        specgen.SetupPrivileged(true)
    } else {
        caps := securityContext.GetCapabilities()
        if err := c.SpecSetupCapabilities(caps, cfg.DefaultCapabilities, cfg.AddInheritableCapabilities); err != nil {
            return err
        }
    }
    // ...
}

Enter fullscreen mode Exit fullscreen mode

This is the fork point. Privileged containers get a fundamentally different treatment than non-privileged ones. To understand what happens in each branch and why our non-root container loses its capability, we need to understand how Linux capabilities actually work at the kernel level.


How CRI-O Handles Capabilities

Linux capabilities decompose the traditional superuser model into distinct privilege units. Capabilities exist in five distinct sets, and the kernel evaluates each set differently:

  • Bounding (CapBnd): Upper limit on what capabilities a process can ever acquire.
  • Permitted (CapPrm): The pool from which effective capabilities can be drawn. A capability must be permitted before it can be effective.
  • Effective (CapEff): What the kernel actually checks during privileged operations. If CAP_NET_RAW is absent from this set, creating a raw socket for ping returns Permission Denied regardless of what the other sets contain.
  • Inheritable (CapInh): Capabilities that can carry across execve() only when the executed binary also has matching file inheritable capabilities set.
  • Ambient (CapAmb): Introduced in Linux 4.3 specifically to solve the non-root capability problem. Ambient capabilities automatically populate both the Permitted and Effective sets after execve(), without requiring file capabilities on the binary.

For the full specification of these sets, see capabilities(7).

Now, with this context, let's examine what CRI-O actually writes into each set and why the kernel's treatment of those sets differs between root and non-root processes.

Privileged Containers

When a container is privileged, CRI-O calls SetupPrivileged(true) on the spec generator. This populates all five capability sets with every known capability (41 at the time of writing):

func (g *Generator) SetupPrivileged(privileged bool) {
    if privileged {
        // ...
        g.Config.Process.Capabilities.Bounding = append(..., finalCapList...)
        g.Config.Process.Capabilities.Effective = append(..., finalCapList...)
        g.Config.Process.Capabilities.Inheritable = append(..., finalCapList...)
        g.Config.Process.Capabilities.Permitted = append(..., finalCapList...)
        g.Config.Process.Capabilities.Ambient = append(..., finalCapList...)
    }
}

Enter fullscreen mode Exit fullscreen mode

It is worth noting that dropCapabilities in the PodSpec's security context has no effect when a container runs in privileged mode. The entire add/drop logic is bypassed. All capabilities are unconditionally granted across all five sets, regardless of what the PodSpec requests.

Non-Privileged Containers

For non-privileged containers, CRI-O first builds the full list of capabilities to apply. Before processing the PodSpec's add/drop lists, CRI-O merges in a set of 9 default capabilities unless the PodSpec specifies drop: ALL:

if !addAll && !dropAll {
    caps.AddCapabilities = append(caps.AddCapabilities, defaultCaps...)
}

Enter fullscreen mode Exit fullscreen mode

These defaults are defined in CRI-O's configuration and include: CHOWN, DAC_OVERRIDE, FSETID, FOWNER, SETGID, SETUID, SETPCAP, NET_BIND_SERVICE, and KILL. This means that even if a PodSpec adds no capabilities at all, the container still receives these 9 by default. They can be overridden via the default_capabilities setting in crio.conf.

When the PodSpec.SecurityContext.Capabilities specifies drop: ALL, CRI-O clears the entire list first including these defaults and then applies individual adds on top. This is why the drop: ALL + add: [specific cap] pattern is common in production: it gives you a clean, minimal capability set.

After building the final list, CRI-O populates three capability sets Bounding, Effective, and Permitted for each capability:

if err := specgen.AddProcessCapabilityBounding(capPrefixed); err != nil {
    return err
}
if err := specgen.AddProcessCapabilityEffective(capPrefixed); err != nil {
    return err
}
if err := specgen.AddProcessCapabilityPermitted(capPrefixed); err != nil {
    return err
}

Enter fullscreen mode Exit fullscreen mode

The capability section of the OCI spec is identical regardless of whether the container runs as root or non-root. But the runtime outcome is drastically different.

Root User (UID 0): Capabilities Work

There is no UID transition. The process starts as UID 0 and stays as UID 0. The kernel never clears any capability sets. What CRI-O writes into the OCI spec is exactly what the process ends up with: CapEff contains the requested capabilities.

Non-Root User (UID != 0): Capabilities Vanish

The OCI runtime starts the process as root, applies the capability sets from the OCI spec, then eventually switches to the target UID (e.g., 1000). During this UID transition from 0 to non-zero, the kernel clears the Permitted and Effective sets:

Capability Set After UID Transition
CapBnd Preserved
CapPrm Cleared by kernel
CapEff Cleared by kernel
CapInh Empty (never set by CRI-O)
CapAmb Empty (never set by CRI-O)

The only mechanism that survives a UID transition and populates the Effective set for a non-root process is ambient capabilities. CRI-O intentionally does not set them:

// Make sure to remove all ambient capabilities. Kubernetes is not yet ambient capabilities aware
// and pods expect that switching to a non-root user results in the capabilities being
// dropped. This should be revisited in the future.

Enter fullscreen mode Exit fullscreen mode

This is a deliberate security decision: Kubernetes assumes runAsNonRoot drops capabilities. Setting ambient capabilities would break that contract.

There is an existing enhancement proposal to add ambient capability support to Kubernetes, which would allow selectively granting effective capabilities to non-root container processes.


So What Can You Actually Do About It?

Knowing that the kernel clears CapEff during the UID transition, and that CRI-O intentionally omits ambient capabilities, what options does our engineer have to make CAP_NET_RAW work for UID 1000?

File Capabilities (setcap) -- Recommended

The most targeted approach. Apply the capability directly to the binary during image build:

setcap 'cap_net_raw=+ep' /usr/bin/ping

Enter fullscreen mode Exit fullscreen mode

This sets file-level Permitted and Effective bits. When the kernel executes this binary, it sees the file capability and promotes it into the process's Permitted and Effective sets, even for non-root users as long as the capability remains in the Bounding set (which it does, as we saw in our CapBnd: 0000000000002000 output).

Tradeoff: Requires modifying the container image. File capabilities are stored as extended attributes, so your build pipeline and storage driver must preserve them. Also, since any capability can be embedded in the image filesystem, this shifts the trust boundary to image provenance. You need to trust that the image hasn't been tampered with.

Running as Root

As we traced above, running as UID 0 avoids the UID transition entirely. No transition, no clearing.

Tradeoff: The most common SCC in production (restricted-v2) prohibits this. Running as root while explicitly dropping all unnecessary capabilities can reduce the exposure.

Privileged Containers

Privileged mode populates all five sets including Ambient, and disables SELinux, AppArmor, and seccomp.

Tradeoff: This is using a sledgehammer for a thumbtack. As we saw in the CRI-O code, privileged mode bypasses the entire capability add/drop logic. You cannot selectively restrict a privileged container. It gets everything unconditionally.


Conclusion

Let's return to our debugging scenario. The engineer's SCC was correct. The PodSpec was correct. CRI-O did exactly what it was supposed to: it wrote CAP_NET_RAW into the Bounding, Effective, and Permitted sets of the OCI spec. But none of that mattered, because:

  1. SCC admitted the pod and its job ended there.
  2. CRI-O translated the CRI request into an OCI spec with the capability in three sets, but not in Ambient.
  3. The OCI runtime applied the spec, then switched to UID 1000.
  4. The kernel cleared Permitted and Effective during the UID transition.
  5. The process started with CapEff: 0000000000000000.

Once you understand where SCC ends and where Linux capability semantics begin, container privilege debugging becomes far less mysterious. The kernel is not ignoring SCC — it is faithfully applying capability transition rules exactly as designed.

The next time you see CapEff: 0000000000000000 in a non-root container, you will know exactly where to look.