Shipping Code with Confidence: My Zero-Trust 5-Gate GitHub Actions Security Pipeline 🛡️

If you work in Cloud Security or DevSecOps, you know a painful truth: your CI/CD pipeline is both your strongest defense and your highest-value attack surface.
Between supply-chain compromises targeting third-party GitHub Actions (like tj-actions/changed-files), secret leaks, dependency CVEs, and Content Security Policy (CSP) regressions at the CDN edge, running npm run build isn’t enough anymore.
Even for jorgeliauw.nl (built with Astro, Nginx, and Cloud Run), I wanted a pipeline that practices what I preach: Zero-Trust CI/CD modeled on OWASP Top 10 and OWASP ASVS L2 (v5.0).
Every commit passes through 5 pre-deploy security gates and 62 live post-deploy smoke checks in two minutes—with zero third-party GitHub Actions. Here is why securing the full chain matters, how it works, and how to deploy it!
Beyond the Pipeline: Securing the Full SDLC Chain
As highlighted in Google Cloud Threat Intelligence’s guide on Hardening Code Pipelines and CI/CD Infrastructure, you cannot treat the pipeline in isolation. Attackers target the weakest link across the Software Development Lifecycle (SDLC):
- Endpoint (Workstations & IDEs): Pre-commit checks, signed commits, and least-privilege IDE extensions.
- Code Repo (GitHub / GitLab): Branch protection, required reviews, and zero hardcoded secrets (
OWASP A07). - Artifact Management (NPM, Docker, PyPI): Pinned dependencies,
package-lock.jsonaudits, and image scans (OWASP A06). - CI/CD (Build & Test): Zero third-party Actions (
OWASP CI/CD-SEC-4), keyless OIDC, and static (CWE-79/CWE-94), secret, and AST checks. - Deployment (Live Infrastructure): Rootless Cloud Run containers and live verification of HTTP headers, TLS/DNS, and dotfile exposure (
ASVS V14.4/CWE-538).
Want a glimpse of how to build this secure development environment on Google Cloud? Software Supply Chain Security on Google Cloud unites Cloud Workstations & Cloud Code, Assured Open Source Software, Cloud Build, Artifact Registry (Artifact Analysis), and Cloud Deploy + Binary Authorization before GKE or Cloud Run (dedicated blog post coming soon!):
How It Works: 5 Pre-Deploy Gates + Live Smoke Testing
Our workflow (.github/workflows/deploy.yml) runs two sequential jobs:
Job 1: Pre-Deploy 5-Gate Orchestrator (scripts/ci-security-scan.mjs)
A zero-dependency Node.js script (scripts/ci-security-scan.mjs) runs locally and in CI (--require-all-binaries), normalizing findings into { scanner, severity, file, line, ruleId, why, remediation }:
- Gate 1 — Syntax & E2E Suite: Runs
node --check, builds Astro, and executes 34 assertions (scripts/test-security-suite.mjs) verifying CSP,rel="noopener noreferrer", and Nginx rules. - Gate 2 — OWASP ASVS L2 Invariants: Blocks
CWE-79DOM XSS sinks (innerHTML,document.write),CWE-94eval(), inlinestyle="", inlineon*=, and token prefixes. - Gate 3 — Trivy (
v0.69.3): Auditspackage-lock.jsonandDockerfileforHIGH/CRITICALCVEs and misconfigs (catching 5 transitive CVEs inzod,vite,undici,source-map-js, andsharpon run #1!). - Gate 4 — Gitleaks (
v8.30.1): Runsgitleaks detect --no-gitacross source anddist/files so credentials never leak. - Gate 5 — Semgrep SAST (
.semgrep.yml): Evaluates AST rules blocking DOM XSS sinks,eval, ReDoS (new RegExp(var)),execSync, and wildcardpostMessage(..., "*").
If any gate fails, it emits GitHub annotations (::error file=...,line=...::), writes a Markdown table to $GITHUB_STEP_SUMMARY, comments on the commit/PR, and exits 1.
Job 2: Keyless Cloud Run Deploy + 62-Check Smoke Test (scripts/live-security-smoke.mjs)
When Job 1 passes on main, Job 2 exchanges GitHub’s OIDC JWT with Google Cloud STS, deploys to Cloud Run, invalidates Cloud CDN, and runs scripts/live-security-smoke.mjs against https://jorgeliauw.nl:
- Dotfile & Source Exposure: Asserts
/.env,/.git/HEAD,/package.json,/nginx.conf, and/%3Cscript%3Ealert(1)%3C/script%3Ereturn403/404with zero source leakage or reflected<script>. - Strict Headers & SHA-256 Integrity: Verifies
Content-Security-Policy,Strict-Transport-Security,nosniff,DENY,COOP, andCORP—and checks live asset SHA-256 hashes. - Method Allowlists & CORS: Ensures
DELETE,PUT,PATCH, andTRACEreturn405/403, and foreignOriginheaders are rejected. - Transport,
security.txt& DNS: Verifies HTTPS301redirects,/.well-known/security.txt, and DNSCAA,SPF,DMARC, andDNSSECvia DoH.
How to Deploy This in Your Own Pipeline
Adopt this pattern in three steps:
1. Replace third-party Actions with raw Git & pinned binaries
In .github/workflows/deploy.yml, check out code with git fetch and install pinned binaries:
- name: Zero-Trust Checkout & Scanner Install
run: |
git init . && git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git -c http.extraheader="AUTHORIZATION: basic $(printf 'x-access-token:%s' "${GH_TOKEN}" | base64 -w 0)" \
fetch --no-tags --prune --depth=1 origin "${GITHUB_SHA}"
git checkout --force FETCH_HEAD
curl -sSfL "https://github.com/aquasecurity/trivy/releases/download/v0.69.3/trivy_0.69.3_Linux-64bit.tar.gz" | sudo tar -xz -C /usr/local/bin trivy
curl -sSfL "https://github.com/gitleaks/gitleaks/releases/download/v8.30.1/gitleaks_8.30.1_linux_x64.tar.gz" | sudo tar -xz -C /usr/local/bin gitleaks
pipx install semgrep
2. Add AST Guardrails in .semgrep.yml
Add .semgrep.yml in your repo root so Gate 5 catches dangerous AST patterns:
rules:
- id: asvs-cwe-79-dom-xss-sink
languages: [javascript, typescript]
severity: ERROR
message: "OWASP ASVS L2 (CWE-79): Unsafe DOM XSS sink detected."
pattern-either:
- pattern: $EL.innerHTML = $VAL
- pattern: document.write(...)
- id: asvs-cwe-345-postmessage-wildcard
languages: [javascript, typescript]
severity: ERROR
message: "OWASP ASVS L2 (CWE-345): postMessage with wildcard '*' origin."
pattern: $WIN.postMessage($DATA, "*")
3. Gate deployment on Job 1 & run post-deploy smoke tests
Require needs: security-and-syntax-check on your deploy job, then run the smoke test after deployment:
- name: Run Post-Deploy Live Black-Box Security Smoke Test
env:
SMOKE_BASE_URL: "https://your-domain.com"
run: |
for attempt in 1 2 3; do
if node scripts/live-security-smoke.mjs; then exit 0; fi
sleep 10
done
exit 1
Explore scripts/ci-security-scan.mjs, scripts/live-security-smoke.mjs, and .github/workflows/deploy.yml directly in my GitHub repo.
Secure the full chain, ditch third-party supply-chain risk, and ship with confidence. VAMOS! 🚀🛡️

Jorge Liauw Calo
Security Engineer at Google Cloud (Google Cybershield) with 13+ years of experience in Cybersecurity across highly regulated industries including Semiconductor, Banking, Fintech, and Insurance. Active member of the Google Cloud Community BeNeLux and Google Cloud Security Community Amsterdam.