.png)
Never trust, always verify — applied to every pipeline task at runtime.
____________________________________________________________
In December 2020, the SolarWinds breach sent shockwaves through the industry. Attackers injected malicious code into a build pipeline, and the compromised artifact shipped to over 18,000 organizations. More recently, the Codecov bash uploader incident and the 3CX supply chain attack demonstrated the same pattern: CI/CD pipelines are high-value targets, and traditional perimeter defenses are not enough.
At Harness, we’ve spent the past several months building a system to address this problem at the platform level. We call it the Zero Trust Service (ZTS) — a customer-controlled authorization layer that intercepts every delegate task before it executes, evaluates it against a chain of policy validators, and returns a binary allow/deny decision.
This post dives into why we built it, how the architecture works, and what it looks like in practice.
The Problem: Poisoned Pipeline Execution
CI/CD platforms execute code. That’s their job. But this creates an inherent tension: the same mechanism that makes pipelines powerful — arbitrary script execution, container orchestration, infrastructure provisioning — also makes them dangerous.
Consider a simple pipeline where a malicious step runs rm -rf / on your infrastructure.
This is a textbook example of Poisoned Pipeline Execution (PPE) — OWASP’s CICD-SEC-4. A bad actor with write access to a repository can modify a pipeline definition to execute arbitrary commands on your infrastructure. The commands run with the full privileges of the runner (Delegate), which typically has access to cloud credentials, artifact registries, and production environments.
Traditional defenses — RBAC, approval gates, OPA policies — operate at the control plane level. They govern who can trigger a pipeline and what the pipeline definition looks like. But once a task is dispatched to a runner, the runner trusts the incoming task and executes whatever it receives. There's no runtime verification of what's actually inside that task before it runs.
That gap is exactly what ZTS closes.
The Core Idea: Intercept Every Task Pre-Execution
The fundamental insight is simple: insert a verification step between “delegate receives task” and “delegate executes task.” Every task, every time, no exceptions.

Architecture flow diagram: Harness Platform (SaaS) → Delegate → ZTS /api/verify → allowed/denied, with ZTS inside the Customer Infrastructure boundary
The sequence works like this:
1. Delegate requests a task from the Harness Platform (SaaS).
2. Harness Platform provides the task — a shell script, a container build, a deployment, etc.
3. Before executing, the Delegate sends the full task payload to ZTS for authorization.
4. ZTS evaluates the task against its validator chain and returns allowed: true or allowed: false with a reason.
5. If allowed, the Delegate executes the task and sends the output back to ZTS for audit logging.
6. If denied, the Delegate aborts and returns an error to the Platform. The pipeline fails.
Two critical design decisions:
- ZTS is built, owned, and operated entirely by the customer — giving you full control and full trust over it. It runs in your infrastructure, next to your Delegates. Harness SaaS never sees the verification logic or the audit logs. You define the policies.
- If ZTS is unreachable, no tasks execute. This is fail-closed by design. A Delegate configured with ZTS will not execute any task it cannot verify. This prevents bypass via network disruption.
Architecture: Validators All the Way Down
At the core of ZTS is a single Go interface. A validator receives a verify request and returns either nil (allow) or an error (deny). That’s the entire contract.

Code screenshot: verifier.Interface definition — a single Handle(ctx, request) method returning error
Validators compose into chains. The first validator that returns an error stops the chain and denies the task. This gives you short-circuit evaluation — a failed account check never reaches the more expensive shell script parser.
Extensibility: Bring Your Own
ZTS ships as a Go library and reference implementation. Every major component sits behind a pluggable interface, so you assemble your own instance from the pieces you need:
This means your security team can answer questions like: “Show me every denied shell script task in the last 7 days, and let me see exactly what script was blocked” — without any custom tooling.
Observability: Prometheus Metrics Out of the Box
ZTS exports Prometheus metrics for every decision:
- zts_verify_requests_total — counter by status (authorized, unauthorized, error) and account.
- zts_verify_request_duration_seconds — histogram of verification latency.
- Per-validator metrics via the instrumented wrapper — which validators run, how often each denies, latency per validator.
The repository includes a ready-to-use Prometheus + Grafana stack with pre-built dashboards, so you can go from zero to full observability in minutes.
Deployment: Your Infrastructure, Your Rules
ZTS is designed to run alongside your Delegates in your own infrastructure. The repository includes production-ready Kubernetes manifests — namespace, deployment with health probes, ClusterIP service, secrets for SCM tokens, and ConfigMaps for environment variable overrides.
Enabling ZTS on the Delegate requires just two environment variables:
[ INSERT IMAGE: Code screenshot: ZERO_TRUST_SERVICE_ENABLED=true and ZERO_TRUST_SERVICE_URL=http://zero-trust-service... ]
Code screenshot: ZERO_TRUST_SERVICE_ENABLED=true and ZERO_TRUST_SERVICE_URL=http://zero-trust-service...
One ZTS instance serves many Delegates. The architecture is stateless — you can scale it horizontally behind a load balancer.
Extensibility: Bring Your Own
ZTS is also a Go library. Every major component is behind a pluggable interface:
HTML for Webflow — Pluggable interfaces:
Writing a custom validator is as simple as implementing a single method. Or, if you want to keep your policy logic completely separate from ZTS, use the webhook validator and implement an HTTP endpoint in whatever language or framework you prefer.
What Ships in the Box
These are reference validators we built to cover the most common enterprise requirements. Treat them as a starting point — drop the ones you don't need, rewrite the ones you do, and add your own.
1. Account Allowlist
Maintains a set of approved Harness account IDs. Any task from an unrecognized account is rejected immediately, preventing cross-tenant task injection.
2. Shell Script Validator
As one example of what runtime validation can do, we parse shell scripts into an AST (using mvdan.cc/sh) and check each command against an allowlist — rather than relying on string matching, which is easily bypassed. Dynamic command names like $CMD are auto-denied. Your own implementation could go further, or skip this entirely.
3. Container Image Allowlist
For CI build tasks and container-based steps, the example we built recursively walks the task parameters looking for image references and checks each against a prefix allowlist — ensuring only approved container images are used. This blocks a common attack vector: modifying a pipeline to use a malicious container image that exfiltrates secrets or installs backdoors.
4. Step Lookup
This validator cross-references each delegate task against the resolved pipeline YAML, checking whether the step identified by the task's fully-qualified name actually exists in the pipeline definition. If someone injects a task that doesn't correspond to a legitimate pipeline step, this catches it.
The "resolved" part matters. Harness pipelines often reference templates — deploy_template v1.2 looks harmless on the surface, but the commands that actually execute live inside the template body. Without resolving them, the validator would only see the reference, not the steps.
So we built a Pipeline Resolver alongside it: it fetches the pipeline YAML from your Git provider (GitHub, GitLab, Bitbucket, Azure DevOps, Harness Code), recursively expands template references up to a configurable depth, and merges template inputs with specs. Multiple SCM providers are supported in parallel.
5. Custom Webhook
The escape hatch for everything else. The webhook validator forwards the full task payload to an external HTTP endpoint and interprets the response as allow or deny. This is how you integrate ZTS with HashiCorp Sentinel, a custom Python service, or any other policy engine you already run. ZTS handles the interception; your system handles the decision.
Audit: Every Decision is Recorded
Auditing is a first-class capability in ZTS. Every verify request and its outcome can be recorded to an audit trail — and because the audit layer sits behind a pluggable interface, you integrate whatever auditing system you already run and route every decision there.
As a default implementation, the SDK ships a writer that logs to disk in two parts:
- Metadata records (compact JSONL) for fast filtering and querying — timestamp, account ID, task type, task ID, allowed/denied, which validators ran, which one failed.
- Raw payloads (full JSON) for forensic analysis — the complete task payload as sent by the Delegate.
Whichever backend you plug in, your security team can answer questions like: "Show me every denied shell script task in the last 7 days, and let me see exactly what script was blocked" — using the tooling you already trust.
Observability: Metrics for Every Decision
Metrics are abstracted behind a pluggable interface — plug in whatever monitoring system you already run. Our SDK comes with a Prometheus implementation that exports metrics for every decision:
- zts_verify_requests_total — counter by status (authorized, unauthorized, error) and account.
- zts_verify_request_duration_seconds — histogram of verification latency.
- Per-validator metrics via the instrumented wrapper — which validators run, how often each denies, latency per validator.
Deployment: Your Infrastructure, Your Rules
ZTS is designed to run alongside your Delegates in your own infrastructure. Enabling ZTS on the Delegate requires just these environment variables:

One ZTS instance serves many Delegates. The architecture is stateless — you can scale it horizontally behind a load balancer.
Putting It All Together
Here’s what happens when a pipeline runs with ZTS enabled:
1. A developer pushes a commit that triggers a pipeline.
2. The Harness Platform creates tasks for each step and dispatches them to the Delegate.
3. The Delegate receives a task — say, a shell script step running helm upgrade --install myapp ./chart.
4. Before executing, the Delegate sends the full task payload to ZTS for verification.
5. ZTS runs the validator chain:
- require_account: Is the account in the allowlist? Yes. Continue.
- shellscript: Parse the script. Commands found: helm. Is helm in the allowed commands? Yes. Continue.
- webhook (if enabled): Forward to external policy service. Response: allow.
6. ZTS returns allowed: true and writes an audit record.
7. The Delegate executes the task.
8. After execution, the Delegate sends the task output to ZTS for audit logging.
Now imagine a different scenario. An attacker modifies the pipeline to inject a script that exfiltrates secrets to an external server.
ZTS catches it:
- If the command is not in the allowed commands list, the shellscript validator denies it immediately.
- If the attacker also changed the container image, the image_allowlist catches that too.
- The audit log records exactly what was blocked, when, and why — giving your security team full visibility.
The pipeline fails. The secrets stay safe. Your SOC gets a Grafana alert.
Why This Matters
The shift toward Zero Trust in CI/CD is not theoretical. Regulatory frameworks like NIST SP 800-218 (SSDF) and SLSA are increasingly requiring runtime integrity checks in build and deployment systems. Enterprises in regulated industries — financial services, healthcare, government — need these controls to meet compliance requirements.
But more practically: as AI-generated code becomes more prevalent in development workflows, the surface area for malicious or unintended code in pipelines is growing. Having a runtime verification layer that evaluates every task against explicit policy isn’t just good security hygiene — it’s becoming a necessity.
ZTS gives you that layer. It’s customer-controlled, fully extensible, fail-closed by default, and designed to integrate with the security tools you already trust.
____________________________________________________________
Interested in running a Zero Trust Service alongside your Delegates? To learn more, visit harness.io or reach out to your Harness account team.

