Container security
SecureVibe — prevention-first security for AI-written code. Signed SKILL.md knowledge that makes AI coding assistants write secure code at generation time, plus a deterministic CI gate. Offline · keyless · Ed25519-signed. By ShieldNet360.
npx -y skills add ShieldNet-360/secure-vibe --skill container-securityAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 2 stars2 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
Hardening rules for Dockerfile, OCI images, Kubernetes manifests, and Helm charts
SKILL.md
12.7 KB, as published. Nobody here has run it
Container Security
Rules (for AI agents)
ALWAYS
- Use multi-stage builds: separate builder/test stages from the final runtime
image so build toolchains and source aren't shipped. The last stage should be
FROM distroless,FROM scratch,FROM alpine:<digest>, or another minimal base — pinned by SHA256 digest, not just tag. <!-- pattern: { id: dkr-multi-stage, severity: high, check: llm } --> <!-- pattern: { id: dkr-pinned-base-digest, severity: high, cwe: 1357, check: deterministic } --> - Run as a non-root user:
USER <uid>(numeric UID >= 10000 for K8srunAsNonRootpolicies to be enforceable). Set USER explicitly on the final stage — omitting USER entirely leaves the container running as root by default, which is the same asUSER root. <!-- pattern: { id: dkr-missing-user-directive, severity: critical, cwe: 250, check: deterministic } --> <!-- pattern: { id: dkr-non-root-user, severity: critical, cwe: 250, check: deterministic } --> - Use
npm ci(and equivalentspnpm install --frozen-lockfile,yarn install --frozen-lockfile) in container builds, notnpm install.npm installmutates the lockfile and resolves versions per-build, producing non-deterministic images that drift from the lockfile. <!-- pattern: { id: dkr-npm-install-not-ci, severity: medium, check: deterministic } --> - Add a
.dockerignoreexcluding.git,node_modules,.env,*.pem,*.key,target/,.terraform/,dist/,coverage/. <!-- pattern: { id: dkr-dockerignore-exists, severity: high, check: llm } --> - Enable BuildKit (
DOCKER_BUILDKIT=1or# syntax=docker/dockerfile:1) for--mount=type=secretsupport and better cache isolation. <!-- pattern: { id: dkr-build-with-buildkit, severity: low, check: llm } --> - Emit an SBOM (
docker buildx --sbom=true/syft) and attach it to the image so downstream scanners can audit the dependency set. <!-- pattern: { id: dkr-sbom-emitted, severity: medium, check: llm } --> - Pin apt packages and clean lists in the same layer:
apt-get install -y --no-install-recommends pkg=1.2.3 && rm -rf /var/lib/apt/lists/*. Unpinned installs make image contents non-reproducible. <!-- pattern: { id: dkr-apt-version-pin, severity: medium, cwe: 1357, check: deterministic } --> - Set explicit
HEALTHCHECKfor long-running services and matchinglivenessProbe/readinessProbe/startupProbein K8s. <!-- pattern: { id: dkr-healthcheck-defined, severity: medium, check: llm } --> - Set resource
requestsandlimitson every container (CPU and memory). - Drop all Linux capabilities then add back only what's needed:
securityContext.capabilities.drop: [ALL]. - Apply a seccomp profile (
RuntimeDefaultat minimum) and AppArmor / SELinux where available. - Mark filesystem read-only:
readOnlyRootFilesystem: true; useemptyDirvolumes for the few paths that must be writable. - Scan every image in CI (Trivy, Grype, Snyk, or your registry's scanner) and fail builds on CRITICAL or HIGH severity findings. <!-- pattern: { id: dkr-image-scan-required, severity: high, check: llm } -->
- Pull base images by SHA256 digest in production manifests, not by mutable tag.
- For multi-tenant workloads (per-user/per-customer sessions on shared
infra), isolate tenants at the kernel boundary: a separate VM — or
gVisor / Kata — per tenant, never just separate containers on one shared
daemon. Drop
privileged, enable user namespaces, and give each tenant its own network. A privileged container on a shared host escapes to the host trivially, so on shared infra that is full compromise of every co-tenant. - Expose container orchestration to clients only through a scoped, authenticated broker API that performs the few operations a client may request (start/stop my session). The client must never hold direct daemon or cluster access.
NEVER
- Run containers as root or with
privileged: true/allowPrivilegeEscalation: trueoutside of explicit, audited system pods (e.g., CNI plugins). - Use end-of-life base images. As of mid-2026 this includes
node:< 18,python:< 3.10,alpine:< 3.17,debian:< 11 (bullseye),ubuntu:< 20.04,centos:*,ruby:< 3.1, and any non-LTS Node/Python release. EOL images stop receiving security patches; a maintained image with no public CVEs is still safer than an EOL one. Pin viaendoflife.date/<runtime>if the runtime is unfamiliar. <!-- pattern: { id: dkr-eol-base-image, severity: critical, cwe: 1104, check: llm } --> - Mount the host docker socket (
/var/run/docker.sock) inside an application container. It's effectively root on the host. - Expose the container daemon API over the network (
tcp://…:2375, or:2376even with TLS) to clients or apps. The daemon API is root-on-host: whoever reaches it runs arbitrary privileged containers and mounts the host filesystem. (A desktop/CLI app talking straight to a remote daemon is the same anti-pattern as a mounteddocker.sock, just over TCP.) - Ship a single shared client credential (one mTLS cert/key, token, or kubeconfig bundled into every copy of a distributed app) to reach that daemon or cluster. Every install holds the same key — trivially extracted from the app bundle — so it grants every user identical access and cannot be revoked per-user. Issue per-user / per-session, short-lived, scoped credentials.
- Run a tenant's container
privilegedon a host shared with other tenants, or attach tenant containers to a shared external bridge network — the first gives container-escape → co-tenant takeover, the second gives cross-tenant L3 reachability. - Embed secrets in image layers via
ENV,ARG,COPY, or byecho-ing them to a file. Even if--squash'd, BuildKit cache and registry layers leak. <!-- pattern: { id: dkr-no-secrets-in-env, severity: critical, cwe: 798, check: deterministic } --> <!-- pattern: { id: dkr-no-secrets-in-build-args, severity: critical, cwe: 798, check: llm } --> <!-- pattern: { id: dkr-no-secret-leak-in-layers, severity: critical, check: llm } --> - Run
curl … | shorwget -O- … | shin aRUN— piping an unverified remote script to a shell is arbitrary remote code at build time. Download, verify a pinned SHA-256, then execute. <!-- pattern: { id: dkr-no-curl-pipe-sh, severity: critical, cwe: 829, check: deterministic } --> - Use
latest,stable,slim, or unversioned tags as the final image base — builds become non-reproducible and quietly pick up CVEs. <!-- pattern: { id: dkr-explicit-latest-tag, severity: high, check: deterministic } --> - Use
ADD <url>to fetch remote resources during build (usecurl --failwith a checksum verify andRUNinstead, or vendor the artifact). <!-- pattern: { id: dkr-no-add-remote, severity: medium, check: deterministic } --> - Disable
automountServiceAccountTokenwhen the workload needs the K8s API, but DO disable it (automountServiceAccountToken: false) when it doesn't. - Use
hostNetwork: true,hostPID: true, orhostIPC: truefor application pods. - Run pods in the
kube-systemnamespace, or any namespace without aNetworkPolicyand PodSecurity admission policy.
KNOWN FALSE POSITIVES
- Operators that legitimately need cluster-admin access (kubelet, CSI drivers,
CNI plugins) require elevated privileges; they belong in
kube-systemor a dedicated namespace with auditing, not in application namespaces. - Bare-metal Kubernetes nodes sometimes legitimately disable
seccompfor drivers that aren't compatible; document the exception. - One-shot debugging pods (kubectl debug, ephemeral containers) intentionally bypass many of these controls; they should not be persisted as YAML in the repo.
- A remote Docker / K8s endpoint over mTLS (
:2376) is acceptable for an operator's own CI / build farm where each operator holds a personal, revocable cert — the anti-pattern is shipping one shared cert inside a distributed end-user app. privilegedor a shared bridge network within a single trust domain (one team's own microservices, or a sim stack on the developer's own machine) is lower-risk than the multi-tenant case; these rules target the shared-host, cross-tenant blast radius specifically.
Context (for humans)
Containers leak two ways: image-layer leaks (secrets in ENV, build artifacts
left in the final image, vulnerable base CVEs) and runtime escapes (privileged
mode, docker.sock, host namespaces). NIST SP 800-190 frames these as image
risks, registry risks, orchestrator risks, and runtime risks.
AI assistants almost always generate Dockerfiles that work and ship — fast — but
they default to a single-stage FROM node / FROM python and USER root. This
skill is the counterweight; pair it with iam-best-practices for cluster
RBAC and supply-chain-security for image provenance beyond the pod.
A distinct, often-missed class is remote-daemon and multi-tenancy exposure.
Handing a client app direct daemon access (tcp://host:2375 + a cert shipped in
the app bundle) makes every user root on the host; running multiple tenants'
privileged containers on one shared daemon with a shared network means one
tenant's escape compromises all of them. The container hardening flags
(privileged, host namespaces, capabilities) matter most precisely where the
blast radius is multi-tenant — isolate at the VM/kernel boundary, and never let
a client touch the daemon directly.
Verify & lock (triaging a finding)
A scanner/review hit (hadolint, trivy, a flagged Dockerfile/manifest line) is a candidate, not a confirmed bug. Confirm it against the built artifact, fix it, then lock it so the regression can't sneak back through CI.
- Confirm it's real (build & inspect, don't just read the Dockerfile). Build
the image and interrogate the runtime, not the source text — many findings are
false until proven on the artifact:
- Root user:
docker inspect -f '{{.Config.User}}' imgempty or0/root, ordocker run --rm img idreportsuid=0. Real if it runs as root; FP ifUSERis a numeric UID >= 10000. :latest/unpinned or EOL base: check the finalFROMresolves to a mutable tag (latest,slim,stable) or an EOL runtime instead of a@sha256:digest.- Secrets in layers:
docker history --no-trunc img(ordive) shows a token inENV/ARG/COPY. Present in any layer = real, even if--squash'd. curl … | sh: an unpinned remote script piped to a shell in aRUN.- Privileged / host namespaces:
docker inspect/ pod spec showsprivileged,allowPrivilegeEscalation, mounteddocker.sock,hostNetwork/PID/IPC, or a writable root FS (readOnlyRootFilesystemunset). FP if the artifact already shows the hardened property, or it's a documented system-pod exception (CNI/CSI, debug pod).
- Root user:
- Fix, then lock with a regression test (a CI config-test — dev's call on
unit vs. integration). Assert the property on the built image, e.g.
docker inspect/docker run idshows non-root USER >= 10000, the final base is digest- pinned and not EOL,docker historycontains no secret, root FS is read-only, and noprivileged/host namespaces; add a benign baseline that passes. A hadolint ortrivy image --exit-code 1gate counts. Commit it to CI so the guard can't be silently dropped.
References
checklists/dockerfile_hardening.yamlchecklists/k8s_pod_security.yaml- CIS Docker Benchmark.
- CIS Kubernetes Benchmark.
- NIST SP 800-190.