sg-gov-container-security · git:20261027.6f21efa · 2026-10-27 · sha256 eb6a34d79add0d19

sg-gov-container-security git:20261027.6f21efaA

Immutable. This exact content never changes and is served at /api/v1/blob/eb6a34d79add0d19.

---
name: sg-gov-container-security
description: >-
  Build, scan, distribute, and run containers that satisfy the Singapore Government's
  ICT&SS Policy Reform (IM8 successor) Container Security (CS-1..CS-11) controls. Use
  whenever writing or reviewing a Dockerfile, a Kubernetes manifest or Helm chart, a
  container build pipeline, or registry/runtime configuration for a Singapore government
  agency project, or when preparing for a security audit or VAPT of a containerised
  workload. Triggers: "ICT&SS container requirements", IM8 container security, Dockerfile
  hardening, minimal/distroless/alpine/scratch base image, digest-pinned FROM, non-root
  container user, read-only root filesystem, runtime secrets, Hadolint, image scanning
  (Trivy, Grype, Amazon Inspector), private container registry, Kubernetes API private
  endpoint, namespace/workload segmentation, NetworkPolicy, Falco / runtime protection,
  container security audit, GCC container workload, CS control catalog.
---

# sg-gov-container-security: CS controls for SG government containers

You are building or auditing a containerised workload against the **11 Container Security
controls (CS-1..CS-11)** of the ICT&SS Policy Reform Cybersecurity Control Catalog. The
family's scope is *securing container building, distribution, and deployment*. Almost all
of these are configuration in a Dockerfile or an orchestrator manifest, and an audit or
VAPT of a container platform checks them.

## Source and currency

Control text in this skill and `references/cs-controls.md` is embedded from
**info.standards.tech.gov.sg as of 2026-07-22**. The standards iterate actively. For any
compliance-critical decision, verify against the live page:

- CS: https://info.standards.tech.gov.sg/control-catalog/cybersecurity/cs/

Machine-readable OSCAL: https://github.com/GovtechSG/tech-standards. Useful for tooling,
but it **lags the site**; the site is authoritative.

## Reference files

- `references/cs-controls.md` — full text of all 11 controls (statement, recommendations,
  risk statement, the CS-7 parameter) plus cross-family notes. Read it when you need exact
  wording, e.g. for an audit response or SSP documentation.
- `references/platform-recipes.md` — concrete Dockerfile, Kubernetes, and CI recipes per
  control, open-source-first (Trivy, Grype, Hadolint, Falco, Cosign) with cloud-native
  equivalents. Read it when configuring a build or cluster.

## Before you start: find the SSP

Every agency system has a **System Security Plan (SSP)** that selects which controls apply
at which level and **sets parameter values**. One CS control carries an agency-defined
parameter, which the catalog leaves blank:

| Control | Parameter | What the SSP defines |
|---|---|---|
| CS-7 | cs-7_prm_1 | The location where container image scanning occurs (e.g. "CI pipeline on push, plus continuous registry scanning") |

**Ask for the project's SSP (or the agency ISO/CISO contact) and use its value. Do not
invent the scan location**; write what the SSP says. If no SSP exists yet, flag that
first; the sibling skill **sg-gov-ssp-navigator** determines which SSP applies and at which level
each CS control sits for this system.

Container Security also **depends on the Software Supply Chain (SC) family**: image signing
is SC-7 and signature verification before deploy is SC-8. Both live in the
**sg-gov-secure-pipeline** skill. CS-8 is about registry *privacy*, not provenance. When
a workload needs "signed images verified before they run", that is SC-7/SC-8; point the
team at sg-gov-secure-pipeline for it.

## Procedure

Work the container lifecycle in order: **build → distribute → run**. For **setup**, apply
each step. For **audit**, verify each step and record pass / fail / not-applicable-per-SSP
with evidence (the Dockerfile line, the manifest field, the CI run URL, the registry
setting). Recipes for every step are in `references/platform-recipes.md`.

### Stage 1 — Build the image (Dockerfile)

**1a. Minimal base image (CS-2).** Build on the smallest viable base (`scratch`,
`distroless`, `wolfi`, `alpine`, or a `-slim` variant), using a multi-stage build so
compilers, package managers, and shells never reach the runtime image. Why: every package
in the base is attack surface and a potential CVE; a minimal image has less to exploit and
less to patch.

**1b. Unique, digest-pinned base tags (CS-1).** Pin `FROM` by `@sha256:` digest, never
`latest` or a floating tag; let Renovate/Dependabot raise reviewed digest bumps. Why:
rolling tags silently change what you build on between builds, introducing untracked
drift and vulnerabilities. (This also satisfies the base-image half of SC-4 pinning.) If
you cannot resolve a digest right now, pin the most specific immutable tag, mark the
digest as a blocking TODO, and report CS-1 as **open (not satisfied)**. A mutable `FROM`
is never compliant.

**1c. Non-root default user (CS-4).** Create a dedicated user and set `USER <numeric-uid>`
in the Dockerfile, with only the permissions the container needs; re-assert it at runtime
with `runAsNonRoot: true` / `runAsUser`, `allowPrivilegeEscalation: false`, and drop all
capabilities. Why: a container running as root that escapes its boundary is root on
concerns beyond itself; least privilege contains the blast radius.

**1d. No build-time secrets (CS-3).** Never `COPY`/`ARG`/`ENV` a credential into an image;
it persists in a layer (and history) even if later removed. Use BuildKit
`--mount=type=secret` for build-time needs, and inject runtime secrets via the
orchestrator (K8s `Secret` files sourced from a vault via CSI/External Secrets; Docker
secrets), never `ENV`. Why: an image is copied and stored widely, so a secret baked into
it should be treated as leaked. Audit existing images with `trivy image --scanners secret`.

**1e. Lint the Dockerfile (CS-5).** Run **Hadolint** as a required CI check (and ideally
pre-commit), tuning via `.hadolint.yaml` rather than blanket-ignoring. Why: linting
catches the misconfigurations and anti-patterns (unpinned installs, missing `USER`,
shell-form pitfalls) that become the vulnerabilities above, before the image is built.
Wire it into the SD-3 required-checks gate (see sg-gov-secure-pipeline).

### Stage 2 — Distribute (registry + scanning)

**2a. Scan images in the SSP-named location (CS-7).** Scan built images for known
vulnerabilities in **cs-7_prm_1** (per the SSP) with Trivy/Grype/Amazon Inspector: enable
**scan-on-push** at the registry, scan in CI, and **block deployment on Critical/High**
findings (`--ignore-unfixed` to stay actionable; suppressions link to an approved risk
acceptance). Scan the **digest**, not a tag. Why: an image bundles OS packages and app
layers that carry their own CVEs; scanning is the gate that stops known-exploitable images
from shipping, and continuous registry scanning catches CVEs disclosed *after* push. This
complements SD-5 (which scans declared dependencies); you need both.

**2b. Private registry only (CS-8).** Host org-built images exclusively in a **private**
registry (ECR private, ACR with public access disabled, GCP Artifact Registry internal,
Harbor, private GitLab registry) with anonymous pull off and push scoped by IAM/RBAC. Why:
images can contain proprietary code, config, and inadvertently sensitive data; a public
registry exposes all of it and invites tampering.

### Stage 3 — Run (orchestrator + runtime)

**3a. Read-only root filesystem (CS-6).** Set `readOnlyRootFilesystem: true` and mount
explicit writable paths (`emptyDir`/`tmpfs` for scratch, volumes for stateful data);
`docker run --read-only --tmpfs /tmp` for plain Docker. Patch by rebuild + redeploy, never
by editing a running container. Why: an immutable filesystem denies an attacker the
ability to drop tools, tamper with binaries, or persist, and enforces the
immutable-infrastructure model the whole family assumes.

**3b. Private orchestrator API (CS-9).** The Kubernetes API server (or equivalent) must
not be internet-reachable: EKS private/CIDR-restricted endpoint, AKS/GKE private cluster
with authorized networks, or a self-managed API server bound to a private interface behind
VPN. Why: a publicly exposed control-plane API is a direct path to the whole cluster;
restricting reachability removes it from the internet attack surface.

**3c. Workload segmentation (CS-10).** Isolate workloads with **namespaces** plus
**default-deny NetworkPolicies** (a namespace without network policy is not isolation),
per-namespace RBAC and ResourceQuota; separate node pools or clusters for sensitive
tenants. Why: flat clusters let one compromised pod move laterally to everything;
segmentation contains an incident to its blast radius.

**3d. Runtime protection (CS-11).** Deploy a runtime threat-detection tool (**Falco**,
or managed GuardDuty EKS / Defender for Containers / GKE runtime) that detects
changes and threats in running containers; on detection, **isolate and replace** the
instance with a rebuilt, patched image, cycling from clean images until a patch exists.
Why: builds and scans are point-in-time; runtime protection is the only control that sees
a live compromise. This **replaces IS-7 (malware) and IS-8 (EDR)** for containers; do
not also demand a host AV/EDR agent inside the container.

## Audit checklist

Verify each row; record evidence (Dockerfile line, manifest field, registry/cluster
setting, CI run URL). "Param" marks the control whose pass criteria come from the SSP.

| ID | Check | Where | Param |
|---|---|---|---|
| CS-2 | Minimal base (scratch/distroless/wolfi/alpine/-slim); multi-stage; no toolchain in runtime image | Dockerfile | |
| CS-1 | `FROM` digest-pinned (`@sha256:`), no rolling tag; reviewed digest bumps | Dockerfile | |
| CS-4 | Non-root `USER` (numeric UID) in image; `runAsNonRoot`/no-priv-esc/drop-caps at runtime | Dockerfile + securityContext | |
| CS-3 | No secret via `COPY`/`ARG`/`ENV`; BuildKit secret mounts; runtime injection from vault | Dockerfile + orchestrator | |
| CS-5 | Hadolint runs as a required CI check (and/or pre-commit) | CI config | |
| CS-7 | Image scanning in cs-7_prm_1; scan-on-push; Critical/High blocks deploy; scans the digest | Registry + CI | ✓ |
| CS-8 | Images only in a private registry; anonymous pull off; scoped push | Registry settings | |
| CS-6 | `readOnlyRootFilesystem: true` with explicit writable mounts; patch by rebuild | securityContext / run flags | |
| CS-9 | Orchestrator API not internet-reachable (private/CIDR-restricted endpoint) | Cluster networking | |
| CS-10 | Namespaces + default-deny NetworkPolicies + per-ns RBAC/quota | Cluster config | |
| CS-11 | Runtime protection deployed (Falco/GuardDuty/Defender); isolate-and-replace on detection | Cluster / cloud console | |

Report audit results per control ID with pass / fail / not-applicable-per-SSP, the
evidence found, and for failures the fix from `references/platform-recipes.md`. Where a
pass depends on the SSP (CS-7 scan location), ask for the SSP value rather than inventing
one.

## Related skills in this repo

- **sg-gov-secure-pipeline** — SD (Secure Development) + SC (Software Supply Chain), including
  image signing (SC-7) and signature verification before deploy (SC-8) and base-image
  pinning (SC-4). CS secures the image and its runtime; SC secures its provenance and the
  pipeline that produces it. Use both for a containerised delivery.
- **sg-gov-secure-coding-as** — AS (Application Security) + CK (Crypto/Key Management): the code
  running *inside* the container. CS-3's runtime secrets pair with AS-8 secrets management.
- **sg-gov-ssp-navigator** — which SSP applies and which level each CS control sits at for this
  system; where the cs-7_prm_1 value lives. Use it before declaring any control out of
  scope.
- **sg-gov-service-shell** — scaffolding an agency service whose container image this skill
  then hardens.
- **sg-gov-security-testing** — ST controls: CS-7 image scanning feeds the vulnerability
  programme, and ST-5 sets the remediation SLAs for the CVEs your scans surface.
- **sg-gov-data-protection** — DP encryption-at-rest for mounted volumes and the registry, and
  the residency (DP-1) that constrains which region the registry and cluster run in.
- **sg-gov-network-security** — the NS family owns the VPC/subnet/security-group boundary
  *around* the cluster (NS-1 segmentation, NS-3 deny-by-default, NS-4 private
  connectivity); CS-10 owns pod-level NetworkPolicy segmentation *inside* it, and CS-9's
  private API endpoint applies the same principle to the cluster control plane. Load both
  when networking a containerised workload.
- **sg-gov-resiliency-recovery** — RS-1 multi-AZ placement for containerised workloads
  (multi-AZ node groups, topology spread, pod disruption budgets) and backup/DR for the
  cluster; CS hardens the containers, RS/BR keeps them available and
  recoverable.
- **sg-gov-logging-monitoring** — CS-11 runtime detections (Falco/GuardDuty) are a log source;
  route them to the LM central alerting and GCSOC pipeline rather than a local sink.