Reusable workflows enable security platform teams to codify guardrails once and roll them out to every repository. This article walks through building a multi-stage pipeline where security controls move as code, not documentation.
Architectural Pattern
Producer Repo
Hosts versioned reusable workflows (.github/workflows/*.yml) that encapsulate validation, testing, and deployment steps with security baked in.
Consumer Repos
Call the reusable workflows via uses: org/security-workflows/.github/workflows/sast.yml@v2. Inputs/outputs expose just enough flexibility for app teams.
Central Policy
Organization rules enforce pinned SHAs, protected branches, and required checks so no team can bypass the shared controls.
Example Reusable Workflow
The following workflow performs lint, SAST, and container scanning stages. It runs on hardened, ephemeral runners using OIDC to access cloud registries.
# .github/workflows/container-secure.yml
defaults:
run:
shell: bash
on:
workflow_call:
inputs:
language:
required: true
type: string
image-name:
required: true
type: string
secrets:
aws-role-arn:
required: true
jobs:
sast:
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
if: inputs.language == 'node'
- uses: actions/setup-python@v5
if: inputs.language == 'python'
- name: Run Semgrep
uses: returntocorp/semgrep-action@v1
with:
auditOn: push
container-build:
needs: sast
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ secrets.aws-role-arn }}
aws-region: us-east-1
- name: Login to ECR
uses: aws-actions/amazon-ecr-login@v2
- name: Build and Push Image
run: |
docker build -t ${{ inputs.image-name }} .
docker tag ${{ inputs.image-name }} $ECR_REGISTRY/${{ inputs.image-name }}:${{ github.sha }}
docker push $ECR_REGISTRY/${{ inputs.image-name }}:${{ github.sha }}
container-scan:
needs: container-build
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- name: Invoke Trivy Scanner
uses: aquasecurity/trivy-action@0.24.0
with:
image-ref: ${{ needs.container-build.outputs.image-digest }}
format: 'table'
exit-code: '1'
vuln-type: 'os,library'
Versioning and Change Control
Tag each workflow release and enforce @v2-style references. Combine with CODEOWNERS so security engineering approves every change. Consumers can adopt fixes via Dependabot or Renovate automations.
Scaling Observability
Pipe workflow_job and workflow_run events to a SIEM or data warehouse. This enables:
- Tracking adoption (% of repos consuming reusable workflows).
- Measuring policy drift (runs without required OIDC permissions).
- Alerting on suspicious behavior (workflow dispatches outside change window).
Takeaways
Reusable workflows turn DevSecOps from a checklist into a product. Build them with strong defaults, a clear contract, and continuous feedback loops so they evolve alongside your threat model.