---
name: sg-gov-secure-pipeline
description: >-
  Set up or audit a repository and CI/CD pipeline that satisfies the Singapore
  Government's ICT&SS Policy Reform (IM8 successor) Secure Development (SD-1..SD-10) and
  Software Supply Chain (SC-1..SC-9) controls. Use whenever creating a repo, configuring
  CI/CD, reviewing pipeline or infra config, or preparing for a security audit on a
  Singapore government agency project. Triggers: "IM8 pipeline requirements", "ICT&SS
  secure development", branch protection, secret scanning, push protection, dependency
  pinning, lockfiles, SAST/DAST gates, artifact signing, cosign, provenance, supply chain
  security, environment segregation, DevSecOps for agency work, SHIP-HATS, SSP secure
  development controls.
---

# sg-gov-secure-pipeline: SD + SC controls for SG government delivery

You are configuring or auditing a repo + pipeline against 19 controls from the ICT&SS
Policy Reform Cybersecurity Control Catalog: **SD-1..SD-10** (Secure Development) and
**SC-1..SC-9** (Software Supply Chain). A security audit of the delivery pipeline checks
these controls. Most are configuration, and are far easier to set up at project start
than to retrofit during an audit.

## Source and currency

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

- SD: https://info.standards.tech.gov.sg/control-catalog/cybersecurity/sd/
- SC: https://info.standards.tech.gov.sg/control-catalog/cybersecurity/sc/

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

## Reference files

- `references/sd-sc-controls.md` — full text of all 19 controls (statement,
  recommendations, risk statement, parameters). Read it when you need exact wording,
  e.g. for an audit response or SSP documentation.
- `references/platform-recipes.md` — concrete GitHub and GitLab recipes per control,
  with plan-gating notes and open-source fallbacks (gitleaks, semgrep, trivy, ZAP,
  cosign). Read it when actually configuring a platform.

## 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 the parameter values**. Four of these 19 controls carry
agency-defined parameters; the catalog sets no values:

| Control | Parameter | What the SSP defines |
|---|---|---|
| SD-4 | sd-4_prm_1 | Where static analysis runs (typically "CI/CD pipeline, per merge request") |
| SD-5 | sd-5_prm_1, sd-5_prm_2 | Dependency scan frequency in days, and where it runs |
| SD-6 | sd-6_prm_1, sd-6_prm_2 | Where secret detection runs, and the remediation SLA in days for true positives |
| SD-9 | sd-9_prm_1 | Where dynamic analysis runs |

**Ask for the project's SSP (or the agency ISO/CISO contact) and use its values. Never
invent a frequency or SLA.** Writing "7 days" into a pipeline when the SSP says 3 is a
finding. If no SSP exists yet, flag that first: PM-3/PM-4/PM-5 (SSP development,
residual-risk approval, central submission) are Level 0 in every cybersecurity SSP. The
sibling skill **sg-gov-ssp-navigator** determines which SSP applies and which level each SD/SC
control sits at for this system. Notably, SD-8 (environment segregation) is promoted to
**Level 0 (mandatory, no deviation) in the medium-risk cloud SSP**.

## Procedure

Work through the five lifecycle stages in order; each stage's controls assume the
previous stage is in place. For **setup**, apply each step. For **audit**, verify each step and
record pass/fail with evidence (a settings screenshot, a config file path, a pipeline
run URL). Platform-specific mechanics for every step are in
`references/platform-recipes.md`.

### Stage 1 — Repository settings

**1a. Central VCS (SC-1).** Code lives in the organisation's sanctioned Git platform:
for whole-of-government teams, usually SHIP-HATS (GovTech's GitLab-based CI/CD suite),
otherwise an agency-managed GitHub org or GitLab instance. Personal accounts and
unversioned file shares fail the control. Why: without central version control you lose
code history, consistency, and the base every other control here depends on.

**1b. Push protection for secrets (SD-1).** Enable secret push protection (GitHub push
protection / GitLab secret push protection; gitleaks pre-commit + CI as fallback) so
secrets are rejected *at push*, before they enter history. Why: a secret in Git history
is compromised even after deletion, so blocking it at push is the cheapest fix.

**1c. Protected default branch (SD-2).** No direct pushes, no force pushes, no branch
deletion on the default branch, for everyone including admins (empty bypass list, or
audited break-glass only). Why: force pushes destroy history; direct pushes bypass every
CI gate and review requirement you set up below.

**1d. Peer review before merge (SC-3).** Require ≥1 approval from a designated reviewer
before merge; dismiss stale approvals on new commits; block self-approval. Use
CODEOWNERS/approval rules to route sensitive paths (auth, crypto, IaC) to the right
reviewers. Why: unreviewed merges are the main channel for both accidental defects and
malicious code.

**1e. Commit signing (SC-2).** Configure the repo to reject unsigned commits (GitHub
ruleset "Require signed commits"; GitLab push rule "Reject unsigned commits", which
needs Premium; see recipes for the fallback). Onboard every developer with SSH or GPG
signing keys registered to their platform account before enforcing, or the team is
blocked. Why: unsigned commits can impersonate any author; signing ties each change to
a verified identity.

### Stage 2 — Dependency hygiene

**2a. Pin everything (SC-4).** Direct *and transitive* dependencies pinned via a
committed lockfile (`package-lock.json`, `Pipfile.lock`, `go.sum`, `Cargo.lock`, …), and
enforce lockfile sync in CI. Version ranges in a manifest alone fail the control because
transitive versions still float. Also pin container base images by digest and CI actions
by commit SHA. Why: unpinned dependencies mean every build can pull different,
possibly compromised, code without anyone noticing (version drift, dependency-confusion, hijacked releases).

If you cannot resolve an action's commit SHA or an image's digest right now (e.g. no
network access), pin what you can, mark each unresolved pin as an explicit blocking TODO
in the file, and report the control as **open — not satisfied**. Never let a runbook or
audit response claim SC-4 compliance while `uses:` lines still carry mutable tags: the
claim will fail audit, and it tells the team the work is done when it isn't.

**2b. Install only pinned versions at deploy (SC-6).** Every install step in CI and
Dockerfiles uses the lockfile-strict command: `npm ci` not `npm install`,
`pipenv sync` not `pipenv install`, `yarn install --immutable`,
`pip install --require-hashes`, etc. Audit grep: `npm install`/bare `pip install` in
Dockerfiles and deploy scripts. Why: a strict install makes the deploy reproduce exactly
what was reviewed and scanned; a resolving install can ship code nobody has seen.

**2c. Dependency scanning on a schedule (SD-5).** SCA scanning (Dependabot + dependency
review, GitLab Dependency Scanning, or osv-scanner/trivy/grype on a scheduled job)
running at least every **sd-5_prm_1 days per the SSP**, in the location the SSP names
(sd-5_prm_2). Emit an SBOM (SPDX or CycloneDX; syft works) as scan evidence. Why: known
CVEs in outdated libraries are the most common exploited weakness; scanning only counts
as a control if it runs on the required cadence.

### Stage 3 — CI gates

**3a. Tests must pass before merge (SD-3).** Required status checks / "pipelines must
succeed" on the default branch, with the check names pinned so a skipped workflow can't
silently satisfy the gate. Why: the default branch is what ships; a merge that never ran
tests puts regressions one deploy away from production.

**3b. Static analysis (SD-4).** SAST (CodeQL, GitLab SAST, or semgrep) plus IaC scanning
(trivy/checkov/KICS) in the location the SSP names (sd-4_prm_1, typically the CI
pipeline per merge request). True-positive findings are **remediated or formally
risk-accepted before production deploy**; keep an auditable triage trail (suppressions
link to an issue with an approver). Why: static analysis catches vulnerability classes
(injection, misconfiguration) before they exist in any running environment.

**3c. Secret detection with an SLA (SD-6).** Full-history and ongoing secret scanning
(distinct from SD-1's push-time block: this catches what slipped through or predates
enablement) in the SSP-named location (sd-6_prm_1). True positives remediated within
**sd-6_prm_2 days per the SSP**, and remediation always means: **revoke/rotate the
credential, then purge it from Git history**. Rewriting history without revoking does not
close the exposure. Why: exposed secrets are live credentials; the SLA bounds the exposure window.

**3d. Masked, protected CI secrets (SD-7).** CI secrets limited to protected
pipelines/environments (so a branch pipeline or fork PR can't read deploy credentials)
and masked in job logs (GitLab masked variables; GitHub Actions secrets +
`::add-mask::` for derived values). Prefer OIDC federation to the cloud provider over
long-lived credentials. Why: CI logs are broadly readable; an unmasked secret in
a log is an unmonitored copy, and unprotected variables let anyone who can push a branch
exfiltrate them.

**3e. Dynamic analysis (SD-9).** DAST (GitLab DAST, OWASP ZAP baseline/full scan)
against a deployed staging/test environment, in the SSP-named location (sd-9_prm_1);
findings addressed or risk-accepted before production deploy. Active scans target
non-production only. Why: runtime testing finds what static analysis cannot:
auth flaws, misconfigured endpoints, input-validation gaps in the composed system.

### Stage 4 — Build and artifact integrity

**4a. Consistent build with provenance (SC-5).** All release artifacts built by the
pipeline (never a laptop), with a record of how each artifact was built and deployed:
GitHub artifact attestations / SLSA provenance, GitLab runner artifact metadata
(`RUNNER_GENERATE_ARTIFACTS_METADATA`), or minimally commit SHA + pipeline URL bound to
the artifact. Why: without a build record you cannot prove, to an auditor or during an
incident, that what is running came from reviewed source.

**4b. Sign artifacts during build (SC-7).** Sign code and container images with Cosign
(keyless via the CI job's OIDC identity is the low-maintenance option) or AWS
Signer/Notation. Sign the digest, never a floating tag. Why: an unsigned artifact can be
swapped for a tampered one anywhere between registry and runtime with no detection.

**4c. Verify signatures before deploy (SC-8).** A verification step gating deployment:
a pipeline stage (`cosign verify` / `gh attestation verify` pinned to the exact expected
identity: repo, workflow, issuer) and/or a Kubernetes admission controller (Sigstore
policy-controller, Kyverno `verifyImages`). Why: signing achieves nothing unless
something verifies it; verification is what stops unsigned or foreign artifacts from
running.

### Stage 5 — Deploy segregation and process

**5a. Prod / non-prod segregation (SD-8).** Segregate across **all six planes** the
control names: applications, services, data, secrets, roles, networks. Ideally use
separate cloud tenant accounts per environment (on GCC: separate accounts). Check each
plane explicitly: no production data in non-prod, separate secret stores, no standing
developer write access to prod, no network path from non-prod into prod, CI deploy
credentials scoped per environment (GitHub environments / GitLab protected environments
+ per-environment OIDC trust). Why: non-prod is the soft target; without segregation a
compromised dev environment cascades straight into production. **Level 0 in the
medium-risk cloud SSP**, so no deviation is possible there.

**5b. SSDLC framework (SD-10).** Document which framework governs the lifecycle (NIST
SSDF (SP 800-218), OWASP SAMM, or Microsoft SDL), with roles and responsibilities per
phase, and map its practices to concrete evidence (this pipeline's gates, threat
modelling at design, VAPT per ST-4, patch cadence). The other 18 controls here are the
build/deploy-phase evidence. Why: point controls without an owning framework decay;
audits ask "who is accountable" as much as "is it configured".

**5c. InnerSource (SC-9).** Default repos to internal visibility on the agency/WOG
platform, with CONTRIBUTING.md and a maintainer list, after screening for confidential
algorithms or embedded sensitive data (SD-1/SD-6 clean is a prerequisite). The Singapore
Government Developer Portal's InnerSource guidelines are the named framework. Why:
closed silos duplicate effort and hide bugs from the colleagues most likely to spot
them.

## Audit checklist

Verify each row; record evidence (setting location, config path, pipeline run URL).
"Param" marks controls whose pass criteria come from the SSP.

| ID | Check | Where | Param |
|---|---|---|---|
| SC-1 | Code in sanctioned central Git platform, SSO+MFA | Platform org | |
| SD-1 | Secret push protection enabled (or gitleaks pre-commit + CI) | Repo security settings | |
| SD-2 | Default branch: no direct push, no force push, no deletion, empty bypass | Ruleset / protected branch | |
| SC-2 | Unsigned commits rejected; all devs have signing keys | Ruleset / push rules | |
| SC-3 | ≥1 required approval; stale approvals dismissed; no self-approval | Ruleset / approval rules | |
| SD-3 | Named CI checks required before merge; no skip-path bypass | Ruleset / merge checks | |
| SC-4 | Lockfile committed and CI-enforced; base images by digest; actions by SHA | Repo + CI config | |
| SC-6 | All deploy installs lockfile-strict (npm ci, pipenv sync, …) | Dockerfiles, CI, deploy scripts | |
| SD-5 | SCA scan every ≤ sd-5_prm_1 days in sd-5_prm_2; SBOM emitted | Scheduler + scan reports | ✓ |
| SD-4 | SAST + IaC scan in sd-4_prm_1; findings fixed or risk-accepted pre-prod | CI config + triage trail | ✓ |
| SD-6 | Secret detection in sd-6_prm_1; true positives revoked+purged ≤ sd-6_prm_2 days | Scanner + remediation records | ✓ |
| SD-7 | CI secrets protected-pipeline-only and masked in logs; OIDC preferred | CI variable settings | |
| SD-9 | DAST in sd-9_prm_1 vs staging; findings fixed or risk-accepted pre-prod | CI config + triage trail | ✓ |
| SC-5 | Pipeline-only builds; provenance/attestation per artifact | Workflow + attestation store | |
| SC-7 | Artifacts signed at build (cosign/AWS Signer), by digest | Build job + registry | |
| SC-8 | Signature verification gates deploy (pipeline stage or admission controller) | Deploy job / cluster policy | |
| SD-8 | Prod/non-prod segregated: apps, services, data, secrets, roles, networks | Cloud accounts + IAM + network | |
| SD-10 | SSDLC framework documented with roles; practices mapped to evidence | Team docs / wiki | |
| SC-9 | Internal visibility + contribution docs, after sensitivity screening | Repo visibility + docs | |

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`.

## Related skills in this repo

- **sg-gov-ssp-navigator** — which SSP applies and which level each SD/SC control sits at for
  this system; where sd-4/5/6/9 parameter values live. Use it before declaring any
  control out of scope.
- **sg-gov-secure-coding-as** — the AS (Application Security) family: input validation,
  password policy, session handling, CSP/HSTS. SD/SC secure the pipeline; AS secures the
  code flowing through it.
- **sg-gov-service-shell** — scaffolding an agency service (SGDS shell, banner, footer)
  whose repo this skill then hardens.
- **sg-gov-dss-accessibility** — Digital Service Standards / WCAG 2.2 controls; its automated
  checks belong in the SD-3 CI gate.
- **sg-gov-container-security** — CS controls for a containerised build: base-image hardening
  and runtime policy. SC secures the image's provenance and signing (SC-7/SC-8); CS
  secures what's inside it. Use both for a containerised delivery.
- **sg-gov-security-testing** — ST controls: the DAST gate here feeds the VAPT programme, and
  ST-5 sets the remediation SLAs for what SAST/DAST and dependency scanning surface.
- **sg-gov-logging-monitoring** — LM-17 owns the DORA metrics that measure this delivery
  pipeline: deployment frequency, lead time, change failure rate, and time to restore.
