Building org-wide, self-service security pipelines with reusable Harness templates
The problem
A year ago, our CI/CD security posture looked like most growing engineering orgs': inconsistent. Every team owned its own pipeline, and security controls — dependency scanning, secret detection, SAST, image scanning — were either bolted on ad hoc, copy-pasted from whichever team had last dealt with an audit finding, or missing entirely. Some pipelines ran Trivy. Others didn't scan container images at all. Secrets management practices varied wildly from team to team, and enforcing even a baseline policy meant chasing down dozens of pipeline owners individually.
The result was predictable: security findings that should have been caught in minutes were surfacing in later-stage reviews or audits, engineers were duplicating the same scanning logic across pipelines with slightly different (and often outdated) configurations, and there was no single place to push a fix when a new CVE or policy requirement came down. When a new scanning requirement landed, rolling it out meant manually touching every pipeline across every team — a process that never actually reached full coverage.
Why ad hoc fixes weren't enough
Our first instinct was the obvious one: write documentation, share example YAML, ask teams to adopt it. That failed the way it usually does. Docs went stale. Teams copied a snapshot of a pipeline config and never pulled updates. Enforcement was voluntary, so coverage was inconsistent, and every security fix had to be manually re-propagated to every consumer. We needed the fix to live in one place and update everywhere automatically — which meant we needed a platform-level primitive, not a wiki page.
The solution: account-level reusable security templates
We rebuilt our security tooling as a set of account-level (org-wide) Harness CI/CD templates — versioned, centrally maintained pipeline building blocks that any team could pull into their own pipeline with a few lines of configuration, rather than hand-rolling scanning logic themselves.
Each template encapsulated one security control as a reusable step or stage:
- Secret scanning (Gitleaks) — runs on every push, gated to block merges on verified secret exposure
- SAST — static analysis wired to fail builds on critical/high findings above an agreed threshold, tuned over time to keep false positives manageable
- Dependency/SCA scanning — flags vulnerable dependencies and, where relevant, kicks off SBOM generation
- Container image scanning (Trivy) — scans built images before they're allowed to push to the registry
Teams consume these templates the same way they'd use any shared library: reference the template version, pass a handful of parameters (severity thresholds, exclusions, notification targets), and get the full control without writing or maintaining the underlying logic themselves. The majority of our pipelines run on GitHub Actions and Harness CI/CD, with the rest on Azure DevOps for specific tracking and logging workflows — so we designed the templates to be portable in principle, keeping the enforcement logic decoupled from any one platform's quirks.
Critically, this isn't a "set it and forget it" library. We treat the template set as a living, contribution-driven system: when a new vulnerability class needs coverage, a scanning tool gets upgraded, or an audit surfaces a gap, the fix goes into the shared template once and every consuming pipeline inherits it on its next run. Engineers across the org regularly contribute enhancements back — new checks, tuned thresholds, support for a new artifact type — the same way you'd contribute to an internal open-source project.
Results
- Manual pipeline security work dropped dramatically — what used to require touching every team's pipeline individually to roll out a new control now happens in one template update.
- Time-to-rollout for a new security requirement went from weeks to days, in some cases hours, since propagation is automatic for any pipeline referencing the shared template.
- Coverage became consistent org-wide instead of dependent on which team happened to prioritize it — every pipeline built on the templates gets the same baseline controls by default, not by request.
- Audit prep time shrank significantly, since we can point to a single, versioned source of truth for what security controls are enforced, rather than reconstructing per-team configurations.
Lessons learned
- Centralizing logic beats centralizing mandates. Asking teams to comply with a policy is weaker than giving them a default that's easier to adopt than to avoid.
- Versioning matters as much as the controls themselves. Teams need the confidence to pin a version and upgrade on their own schedule, or adoption stalls.
- Treat the template set as a product, not a policy doc. Regular contributions, a changelog, and an easy path for teams to request or add new checks kept engagement high and prevented the templates from going stale the way our original documentation did.
- Start with the highest-leverage control, not the most controls. Secret scanning was our first template because it had the clearest, least controversial value — that early win built the trust needed to expand into SAST and image scanning later.
What's next
We're extending the template set to cover policy-as-code enforcement (OPA/Conftest-style checks) and working on making the templates genuinely platform-agnostic, so the same control logic can be consumed identically whether a team is on Harness, GitHub Actions, or Azure DevOps.
If you're standardizing CI/CD security across a multi-team org, I'd love to hear how you approached it — get in touch.