Protection Score Reference¶
Authorised application boundary
Authorised application code is trusted. An authorised app can send out a secret it has copied or encoded. Current content checks do not provide a general guarantee against this disclosure or a malicious authorised app. Memory-read tests and a Ready agent do not prove copied/encoded-secret exfiltration prevention. Enforced NetworkPolicy can restrict destinations, but allowed traffic remains a channel. See Security Model.
A score is not deployment qualification
The 175-point total reports individual component checks, not complete enforcement or a probability of compromise. A high score cannot distinguish Tier 1 from Tier 2 with required-vector gaps. Verify the active BPF LSM stack, agent /status kernel_tier and /healthz enforcement_vectors, and health/readiness on the exact release and node image. The measured GKE Ubuntu image lacks active BPF LSM; the 29 October COS test passed default strict readiness with Tier 1 lsm+fmod_ret. See the measured platform matrix.
The protection score is a numeric measure of which defense mechanisms are active for a cloudtaser-protected process. The maximum score is 175 points across 17 checks. This page documents every check, its point value, what it proves, and how to enable it.
The score includes indicators for multiple enforcement mechanisms:
- LSM check —
ebpf_lsm_enforce(15 pts) credits an attested agent report withlsm_count > 0. Synchronous denial additionally requiresbpfin the actual active LSM stack and the relevant hook to be effective. The measured GKE Ubuntu6.8.0-1062-gkeimage builds BPF LSM but does not activate it. COS config enables BPF LSM; that does not imply all required vectors are covered. - Kprobe tier (fallback) —
ebpf_kprobes(10 pts). Synchronous deny viabpf_override_return(). Requires bothCONFIG_BPF_KPROBE_OVERRIDE=yand the target syscall in the upstreamALLOW_ERROR_INJECTIONallow-list. Stays attached as a structural fallback on hooks where LSM is also live; does the deny on hooks LSM cannot reach. - Detection and reactive response — availability and response depend on the vector and caller. They do not satisfy the default synchronous-enforcement gap gate. Do not infer detection or termination for an unavailable vector from a coarse mode label.
The wrapper queries the agent's /status endpoint and awards the appropriate tier's points based on what is actually attached. Both LSM and kprobe checks can be active simultaneously, including on overlapping paths. These are component-check credits, not a count of independently protected vectors.
Score Breakdown¶
| Check | Points | What It Proves | How to Enable |
|---|---|---|---|
memfd_secret |
15 | Secret pages physically removed from kernel direct map | Automatic (requires Linux 5.14+ kernel) |
mlock |
10 | Secret pages pinned in RAM, cannot be swapped to disk | Automatic |
core_dump_exclusion |
5 | Secret pages excluded from core dumps via MADV_DONTDUMP |
Automatic |
dumpable_disabled |
5 | Process marked non-dumpable via PR_SET_DUMPABLE(0); /proc access restricted, ptrace blocked |
Automatic |
guard_pages |
5 | Unmapped guard pages bracket each secret allocation; any over- or under-read into adjacent memory faults immediately | Automatic |
buffer_canary |
5 | Canary values adjacent to secret buffers detect overflow or corruption of the protected region | Automatic |
token_protected |
10 | OpenBao authentication token stored in protected memory (not on disk or in env) | Automatic |
vault_tls_verified |
20 | TLS certificate chain to OpenBao vault fully verified (not skipped or downgraded) | Automatic (requires valid CA certificate in wrapper config) |
broker_tls_verified |
20 | TLS certificate chain to beacon broker relay fully verified (not skipped or downgraded) | Automatic (requires valid CA certificate in wrapper config) |
environ_scrubbed |
5 | Wrapper's /proc/self/environ is structurally clean -- secrets enter Go heap memory after the wrapper has already exec'd |
Automatic (structural property of fork+exec architecture) |
getenv_interposer |
10 | Optional LD_PRELOAD interposer blocks getenv() from returning secrets on the heap (glibc-only enhancement) |
Use a glibc-based container image (not Alpine/musl) |
ebpf_agent_connected |
10 | eBPF enforcement agent is active and monitoring this process | Add cloudtaser.io/ebpf: "true" annotation to the pod |
cpu_mitigations |
5 | Spectre/Meltdown hardware mitigations active | Automatic on modern CPUs |
ebpf_enforce_mode |
15 | eBPF agent running in blocking mode (not just monitoring) | Deploy eBPF daemonset with enforceMode: true |
ebpf_lsm_enforce |
15 | Synchronous in-kernel deny via BPF LSM hooks (strongest enforcement tier) | Kernel with CONFIG_BPF_LSM=y AND bpf in the boot LSM stack |
ebpf_kprobes |
10 | Synchronous syscall blocking via kprobe override (fallback tier behind LSM) | CONFIG_BPF_KPROBE_OVERRIDE=y and an allowlisted target; verify the exact image and required-vector coverage |
confidential_vm |
10 | Confidential Computing capability detected; technology and attestation require separate verification | Select and verify the actual technology; GKE flags distinguish SEV, SEV-SNP and TDX |
Maximum score: 175 points
Score history
The maximum has moved several times as checks were added: 115 (pre-LSM) → 125 (Phase L4: ebpf_lsm_enforce added, ebpf_kprobes demoted 15→10) → 135 (guard_pages and buffer_canary, 5 pts each) → 175 (vault_tls_verified and broker_tls_verified, 20 pts each). Review your CLOUDTASER_MIN_PROTECTION_SCORE value if you pinned it against an older ceiling.
When both LSM and kprobe checks are active
The agent's per-hook decision matrix keeps both LSM and kprobe attached when both load. The LSM hook fires first synchronously; the kprobe stays as the structural fallback (covers the rare case of LSM detach via manual unpin or kernel module reload). The score credits both mechanisms even when their coverage overlaps; it must not be interpreted as a sum of independent security guarantees.
Checks Explained¶
memfd_secret (15 points)¶
The highest-value automatic check. When the kernel supports memfd_secret (Linux 5.14+), the wrapper stores secrets in memory pages that are physically removed from the kernel's direct map. This means even a root attacker with kernel module access cannot read the secret pages through the direct map -- they are simply not there.
Fails when: The kernel version is below 5.14, or CONFIG_SECRETMEM is not set.
mlock (10 points)¶
Secrets are pinned in physical RAM using mlock(). This prevents the kernel from swapping secret pages to disk, where they could be recovered from swap partitions or swap files.
Fails when: CAP_IPC_LOCK is not available and the RLIMIT_MEMLOCK limit is too low. Most Kubernetes runtimes grant sufficient limits by default.
core_dump_exclusion (5 points)¶
Secret pages are marked with MADV_DONTDUMP, which excludes them from core dump files. If the application crashes, the core dump will not contain secret values.
Fails when: The madvise syscall fails (extremely rare).
dumpable_disabled (5 points)¶
The wrapper calls prctl(PR_SET_DUMPABLE, 0) to mark itself as non-dumpable. This restricts /proc/pid/mem access and blocks ptrace attach from non-root processes. Combined with the eBPF agent, this closes the /proc attack surface.
Fails when: The prctl syscall fails (extremely rare).
guard_pages (5 points)¶
The wrapper brackets each secret allocation with unmapped guard pages. Any read or write that runs off the end of (or before the start of) the protected region hits an unmapped page and faults immediately, instead of silently touching adjacent memory. This contains buffer over-read bugs (Heartbleed-class) that would otherwise expose neighbouring secret bytes.
Fails when: The platform cannot map guard pages around the allocation (extremely rare; e.g. severe address-space or RLIMIT constraints).
buffer_canary (5 points)¶
Known canary values are written immediately adjacent to secret buffers. The wrapper verifies the canaries around each use; a modified canary indicates a buffer overflow or memory corruption touching the protected region, and the wrapper aborts rather than continue with possibly-compromised secret memory.
Fails when: Canary placement or verification is unavailable (extremely rare).
token_protected (10 points)¶
OpenBao authentication token is stored in the same protected memory as the secrets themselves (memfd_secret when available, mlock-ed pages otherwise). The token never appears in environment variables, files, or unprotected memory.
Fails when: memfd_secret is not available and mlock also fails.
vault_tls_verified (20 points)¶
The wrapper verified the full TLS certificate chain when connecting to the OpenBao vault server. This is the highest-value transport-security check. Without TLS verification, a man-in-the-middle attacker positioned on the network path between the wrapper and vault can intercept every secret the wrapper fetches. A compromised vault channel defeats every memory-protection mechanism downstream: if the attacker controls what the wrapper receives, no amount of memfd_secret or eBPF enforcement protects the real secret values.
The wrapper performs standard Go crypto/tls verification against the CA certificate provided in its configuration (VAULT_CACERT or the system trust store). The check confirms that the TLS handshake completed with a valid, non-expired certificate chain that chains to the expected CA, and that no TLS_SKIP_VERIFY or equivalent downgrade was active.
Fails when: The vault CA certificate is missing, expired, or does not match the server's presented certificate. Also fails if the wrapper is configured with VAULT_SKIP_VERIFY=true or equivalent TLS downgrade -- in that case the connection may succeed but the check correctly reports unverified.
broker_tls_verified (20 points)¶
The wrapper verified the full TLS certificate chain when connecting to the beacon broker relay. The broker relay is the P2P transport that carries secrets from the EU-hosted port to the target cluster. An unverified broker connection exposes the same risk as an unverified vault connection -- a network-positioned attacker can intercept or tamper with secrets in transit.
The wrapper verifies the broker's TLS certificate against the CA certificate provided in its configuration. The check confirms that the TLS handshake completed with full chain verification, the certificate is valid and non-expired, and no TLS downgrade was active.
Fails when: The broker CA certificate is missing, expired, or does not match the broker's presented certificate. Also fails if TLS verification was explicitly skipped.
environ_scrubbed (5 points)¶
The wrapper's own /proc/self/environ is structurally clean of secret values. This is a property of the fork+exec architecture rather than a runtime mutation:
- The wrapper's container is started with a configuration-only env (
CLOUDTASER_*,VAULT_ADDR, etc.). No secret bytes are present in the kernel'senvp[]at exec time. - Linux freezes
/proc/PID/environas the byte rangemm->env_start..mm->env_endrecorded at the moment ofexecve(). The kernel does NOT update this byte range when a process subsequently callsos.Setenv/setenv/putenv-- those calls modify the C library's heap-allocatedenvironarray, not the kernel's snapshot. - Secrets are fetched from vault into Go heap memory (and into the protected
memfd_secretregion) only after the wrapper has exec'd. They never enter the wrapper's frozenenvp[]snapshot.
The check therefore confirms a structural invariant: an attacker reading /proc/<wrapper-PID>/environ finds only the configuration env that the operator webhook set. The child's /proc/<child-PID>/environ does contain the secrets (this is how the application reads them) and is protected from cross-process reads by the eBPF kprobe on openat("/proc/<pid>/environ") for all monitored PIDs in the protected pod cgroup.
Fails when: The wrapper detects secret keys in its own /proc/self/environ -- which would indicate a configuration bug (operator webhook setting secrets in the container spec) rather than a wrapper failure.
getenv_interposer (10 points)¶
An optional glibc-only enhancement. The wrapper injects an LD_PRELOAD library (libcloudtaser.so) that intercepts getenv() calls in the child process. When the application calls getenv("PGPASSWORD"), the interposer returns a pointer to the memfd_secret-backed memory instead of a heap-allocated copy. This prevents secrets from leaking onto the application's heap.
This is an enhancement on top of the default env-var delivery path, not a replacement. All binaries -- including those that do not activate the interposer -- receive secrets through the universal fork+exec execve() mechanism.
Fails when: The container uses musl libc (Alpine) or statically linked binaries, which do not support LD_PRELOAD. Switch to a Debian, Ubuntu, or glibc-based image to enable this optional check.
ebpf_agent_connected (10 points)¶
The eBPF enforcement agent running on the node has registered this process for protection. The agent monitors all relevant syscalls and will block or kill any process attempting to read the protected process's memory, environment, or file descriptors.
Fails when: The eBPF daemonset is not deployed, or the pod does not have the cloudtaser.io/ebpf: "true" annotation.
cpu_mitigations (5 points)¶
The kernel has Spectre and Meltdown mitigations active. These hardware mitigations prevent speculative execution side-channel attacks that could leak secret values across process boundaries.
Fails when: The kernel is booted with mitigations=off, or the CPU does not support the required microcode updates. Modern cloud provider kernels always have mitigations enabled.
ebpf_enforce_mode (15 points)¶
The attested agent reports its enforce-mode flag. This does not prove every required syscall has an effective deny path; inspect the vector table and readiness independently.
Fails when: The eBPF daemonset is deployed with enforceMode: false (monitoring only).
ebpf_lsm_enforce (15 points)¶
Strongest enforcement tier. The eBPF agent has attached BPF LSM hooks to one or more security-critical kernel paths. LSM hooks (security/security.c) are the canonical Linux Security Module API: they fire inside the kernel before the protected operation runs, and the LSM program returns 0 (allow) or -EPERM (deny) to control whether the operation completes. Unlike kprobe override, LSM does NOT require the syscall function to appear in any allow-list — every kernel that builds with CONFIG_BPF_LSM=y and has bpf in its boot LSM stack accepts BPF LSM programs against any LSM hook the kernel exposes.
Check the running image, not the distribution name. GKE Ubuntu 6.8.0-1062-gke has CONFIG_BPF_LSM=y but no active bpf; the default requireLSM gate refuses startup. The measured COS 6.12.105+ node had active BPF LSM and passed default strict readiness through lsm+fmod_ret. Qualify other images and releases independently.
Required for actual LSM enforcement: BPF LSM must be built and bpf must be present in the active /sys/kernel/security/lsm list. The wrapper check credits the verified lsm_count > 0 report; attachment can succeed even when BPF LSM is inactive, so this count is not an independent active-stack check (#1199). Follow the approved node-image procedure; managed repair and upgrades can undo local boot changes.
The agent's /status endpoint reports lsm_count (number of BPF LSM hooks successfully attached). The wrapper awards this check's points when lsm_count > 0. The agent also publishes cloudtaser_ebpf_lsm_loaded{program="..."} per-program metrics for granular alerting.
ebpf_kprobes (10 points)¶
Fallback enforcement tier behind LSM. The eBPF agent is using kprobe-based synchronous syscall blocking via bpf_override_return(). When CONFIG_BPF_KPROBE_OVERRIDE=y AND the target syscall function appears in the upstream kernel's ALLOW_ERROR_INJECTION allow-list, the agent can prevent a syscall from executing by overriding its return value before it completes.
In Phase L4 of the BPF LSM migration (BPF LSM implementation), the kprobe tier was demoted from 15 to 10 points because BPF LSM now covers the same primary vectors with a stronger primitive (no ALLOW_ERROR_INJECTION gate, more distros). Kprobes remain valuable for two reasons:
- Structural fallback on LSM-covered hooks — when both LSM and kprobe load for the same vector, the agent's per-hook decision matrix keeps both attached. The LSM hook fires first synchronously; the kprobe stays loaded so a later LSM detach (manual unpin, kernel module reload) still leaves a synchronous deny path.
- Hooks LSM cannot reach — a small number of vectors are kprobe-only because no equivalent LSM hook exists upstream. The kprobe path covers those independently.
Fails when: The kernel does not have CONFIG_BPF_KPROBE_OVERRIDE=y. COS disables this option while enabling CONFIG_FUNCTION_ERROR_INJECTION. The measured COS agent used fmod_ret for synchronous paths and reported Tier 1 despite kprobes_active=false. A missing kprobe score is therefore not proof of an enforcement gap. See the recorded COS result and check actual vector states and readiness.
confidential_vm (10 points)¶
The wrapper detects a Confidential Computing capability. This boolean does not distinguish AMD SEV from SEV-SNP or establish an attestation policy. Verify the actual technology, image and attestation chain separately.
Fails when: Confidential Computing is not detected. On GKE, --enable-confidential-nodes alone selects SEV; explicit SEV-SNP uses --confidential-node-type=sev_snp on N2D Milan. C3 uses TDX. Verify the actual technology and attestation separately; see the GKE guide.
Opt-In Security Enhancements¶
These controls are not currently scored checks (they do not contribute to the numeric protection score) but provide meaningful security improvements for specific runtime classes. They are documented here alongside the scored controls because they affect the overall security posture of a cloudtaser-protected process.
Exec-family interposition / env-snapshot-strip mode (opt-in, v0.2.184+)¶
The LD_PRELOAD interposer (libcloudtaser.so) can interpose the full exec family to strip secret values from the envp handed to the kernel at every exec boundary. This closes the gap where CLOUDTASER_KEEP_SECRETS_IN_ENV=true leaks secret values into every descendant's /proc/<pid>/environ.
What it protects: When CLOUDTASER_ENV_SNAPSHOT_MODE=strip is set, the interposer:
- Populates the process environ with secret values at library init (so environ-snapshot runtimes -- CPython
os.environ, bash$VAR, Perl%ENV-- see them at interpreter startup). - Interposes
execve,execv,execvp,execvpe,execl,execlp,execle,fexecve,posix_spawn,posix_spawnp,system,popen/pclose, andexecveat(where the libc exports it) to strip the mapped keys from the envp before it reaches the kernel. - Fails closed: if the envp cannot be stripped (more than 32768 entries, or a required function pointer is NULL), the exec fails with
ENOMEMrather than passing the unstripped envp. The strip path is async-signal-safe (bounded VLA, no heap) and safe invforkchildren.
Compared to KEEP_SECRETS_IN_ENV=true: Strip mode gives environ-snapshot runtimes access to secrets in-process while preventing the values from reaching any fork+exec descendant's /proc/<pid>/environ. KEEP_SECRETS_IN_ENV=true hands the plaintext to every descendant in the tree.
Limitations:
- Values snapshotted at interpreter start do not rotate on SIGHUP (
getenv()callers still see rotated values; the interpreter's startup snapshot does not). - Raw
syscall(SYS_execve, ...)and statically linked spawners bypass LD_PRELOAD (inherent limitation of the interposition mechanism). /proc/<pid>/environof the wrapped process itself holds thesetenv()'d values; cross-process reads are covered by the eBPF LSM hooks on Tier 1/2 platforms.
How to enable: Set CLOUDTASER_ENV_SNAPSHOT_MODE=strip on the wrapper. Recommended for CPython, bash, and Perl entrypoints as a replacement for CLOUDTASER_KEEP_SECRETS_IN_ENV=true. Default is OFF (unset).
When it may become a scored check: The reviewed default decision (auto-enable strip mode when the wrapper's startup detection identifies CPython/bash/Perl) is tracked separately; once the default is ON, it may be integrated into the protection score as a check alongside getenv_interposer.
See the wrapper's KNOWN-LIMITATIONS.md for the full security analysis and the Wrapper Configuration Reference for configuration details.
Score Interpretation¶
The maximum remains 175 points across 17 checks. Inspect which checks are missing, rather than interpreting a range as a certification of full protection or suitability for regulated workloads. A high total can coexist with incomplete required-vector enforcement; even a nonzero LSM count does not prove the active LSM stack.
Transport verification, node enforcement, runtime bootstrap compatibility and readiness are independent requirements. A score cannot quantify residual risk or establish compliance. Check the agent's kernel_tier, full vector table and readiness evidence, and retain the exact component versions and node image.
Platform qualification before score comparisons¶
| Platform evidence | Current default behavior | Required next check |
|---|---|---|
GKE Ubuntu 6.8.0-1062-gke: BPF LSM built, absent from the active stack |
Agent startup refused by requireLSM |
Qualify a supported active-LSM image; a larger score or kprobe capability cannot bypass this gate |
COS 6.12.105+, measured 29 October: active BPF LSM, kprobe override off |
Tier 1 lsm+fmod_ret, Ready with default strict settings |
Retain the exact tested cohort; verify every required vector and readiness on your own nodes |
| Other cloud/node images | Depends on exact build, boot stack and permissions | Use the measured platform matrix, not a distribution-wide score ceiling |
The arithmetic loss of a 10-point check does not imply that a 165-point deployment is usable. Default enforcement gates can refuse the workload before a wrapper ever publishes its score. Confidential Computing adds a separate check; it does not repair kernel enforcement gaps.
Checking the Score¶
Wrapper Logs¶
The wrapper logs the protection score at startup. This synthetic all-checks example illustrates the 175-point arithmetic; it is not a measured result from stock GKE Ubuntu or a qualification of current default COS deployments:
[cloudtaser-wrapper] Protection score: 175/175
[cloudtaser-wrapper] memfd_secret: OK (+15)
[cloudtaser-wrapper] mlock: OK (+10)
[cloudtaser-wrapper] core_dump_exclusion: OK (+5)
[cloudtaser-wrapper] dumpable_disabled: OK (+5)
[cloudtaser-wrapper] guard_pages: OK (+5)
[cloudtaser-wrapper] buffer_canary: OK (+5)
[cloudtaser-wrapper] token_protected: OK (+10)
[cloudtaser-wrapper] vault_tls_verified: OK (+20)
[cloudtaser-wrapper] broker_tls_verified: OK (+20)
[cloudtaser-wrapper] environ_scrubbed: OK (+5)
[cloudtaser-wrapper] getenv_interposer: OK (+10)
[cloudtaser-wrapper] ebpf_agent_connected: OK (+10)
[cloudtaser-wrapper] cpu_mitigations: OK (+5)
[cloudtaser-wrapper] ebpf_enforce_mode: OK (+15)
[cloudtaser-wrapper] ebpf_lsm_enforce: OK (+15)
[cloudtaser-wrapper] ebpf_kprobes: OK (+10)
[cloudtaser-wrapper] confidential_vm: OK (+10)
A missing kprobe score and a present LSM score describe only those two checks. They do not establish a supported platform ceiling or prove all required vectors are enforced.
CLI Status Command¶
Kubernetes Events¶
The wrapper emits Kubernetes events on the pod for score changes:
# Synthetic example — illustrative shape, not captured from a live cluster
# (see Phase L4 status note at the top of this page).
Events:
Type Reason Message
---- ------ -------
Normal ProtectionActive Protection score: 175/175 (all defenses active)
Warning ProtectionDegraded Protection score: 155/175 (ebpf_kprobes, confidential_vm unavailable on COS)
Prometheus Metrics¶
# HELP cloudtaser_protection_score Current protection score
# TYPE cloudtaser_protection_score gauge
cloudtaser_protection_score{pod="my-app-7d8f9c6b4-xk2mn",namespace="production"} 175
# HELP cloudtaser_protection_max Maximum possible protection score
# TYPE cloudtaser_protection_max gauge
cloudtaser_protection_max{pod="my-app-7d8f9c6b4-xk2mn",namespace="production"} 175
Enforcing Minimum Scores¶
Use the CLOUDTASER_MIN_PROTECTION_SCORE environment variable to prevent the wrapper from starting if the score is too low:
Choose a threshold for the checks your workload requires, and enforce kernel/transport prerequisites independently. A sum cannot require a particular check: different missing controls can produce the same total. Raising CLOUDTASER_MIN_PROTECTION_SCORE does not change the agent's enforcement or readiness policy; lowering it must not be used to bypass a required control.
Migrating from older thresholds
The maximum rose from 135 to 175 when the two TLS checks added 40 points. Adding 40 to an old threshold preserves the arithmetic only when both TLS checks pass; it does not prove an equivalent security posture. Review the named checks, vector states and readiness rather than treating a numerical offset as qualification.
Related¶
- Security Model -- trust boundaries and threat model
- Memory Protection -- how memfd_secret and mlock work
- eBPF Enforcement -- how the eBPF agent blocks attack vectors
- GKE Deployment Guide -- current GKE prerequisites and refusal diagnostics
- Kubernetes Compatibility -- per-distribution feature support