A default Snyk IaC scan enforces the rules Snyk ships in its managed catalog — but most platform teams eventually hit a policy their vendor doesn't have, like "every S3 bucket must carry a data-classification tag" or "no RoleBinding may reference the cluster-admin ClusterRole outside namespace platform." Snyk's answer is snyk-iac-rules, a Golang SDK that lets you write, test, and ship your own checks in Rego, the query language behind Open Policy Agent (OPA). The rules run inside the same snyk iac test command your pipelines already call, either alongside or instead of Snyk's built-in rule set. This post walks through what the SDK actually does mechanically — scaffolding, Rego syntax, local testing, bundling, and org-wide distribution — based on Snyk's public documentation and the snyk/snyk-iac-rules and snyk/custom-rules-examples repositories on GitHub.
What is the Snyk IaC Rules SDK, and why does it exist?
The Rules SDK is a standalone command-line tool, distributed separately from the main Snyk CLI, whose job is to let you author policy checks that Snyk's own rule catalog doesn't cover. Snyk's IaC scanner is built on Open Policy Agent under the hood, so rather than inventing a proprietary rule format, Snyk exposes that same Rego engine to customers. The GitHub repository snyk/snyk-iac-rules describes it plainly: a tool to "write, debug, test, bundle, and distribute custom rules for the Snyk IaC CLI." As of this writing the repo has shipped more than 40 tagged releases (v1.9.5 at the time of research), and its codebase is roughly two-thirds Go, with the remainder split between shell scripting and Rego itself — a reasonable proxy for how much of the real work happens in the policy layer versus the tooling around it. Because the rules are plain Rego, Snyk explicitly notes they're portable: a policy you write for Snyk IaC can be reused with any other OPA-based tool, and vice versa, since you're not locked into a Snyk-specific DSL.
How do you install the SDK and write your first rule?
You install snyk-iac-rules through one of five channels Snyk documents: npm (npm install snyk-iac-rules@latest -g), Docker (docker pull snyk/snyk-iac-rules:latest), Homebrew (brew tap snyk/tap && brew install snyk-iac-rules), Scoop on Windows, or a standalone binary from GitHub Releases. After installing, snyk-iac-rules --help confirms the binary is on your path, and snyk auth authenticates the CLI against your Snyk account for later distribution steps. To scaffold a new check, the SDK provides a template command, for example snyk-iac-rules template --rule CUSTOM-RULE-8, which generates the skeleton a rule needs — a Rego policy file plus supporting fixtures you fill in with real Terraform, CloudFormation, or Kubernetes manifests to test against. From there, writing the rule is standard Rego: you define a package, a helper predicate that captures the condition you're checking for, and a deny rule that fires when that condition is met.
What does an actual custom Rego rule look like?
A minimal example from Snyk's own developer blog checks that every AWS IAM role carries an owner tag, and it's short enough to read in full:
package rules
aws_iam_role_tags_missing(resource) {
not resource.tags.owner
}
deny[msg] {
resource := input.resource.aws_iam_role[name]
aws_iam_role_tags_missing(resource)
msg := {
"publicId": "CUSTOM-RULE-8",
"title": "IAM Role missing required tags",
"severity": "medium"
}
}
The input document is Snyk's parsed representation of your IaC resources, indexed by resource type and name (input.resource.aws_iam_role[name]), so the rule body reads almost like a sentence: for every IAM role resource, if the owner tag is missing, deny it. Snyk requires the deny rule's output message to carry a defined set of metadata — publicId, title, severity, msg, issue, impact, remediation, and references — which is what lets a custom finding show up in scan output looking indistinguishable from a built-in Snyk rule, with the same severity levels and remediation guidance fields your team already triages by. Snyk's public snyk/custom-rules-examples repository ships ten worked examples under this same pattern — CUSTOM-RULE-1 through CUSTOM-RULE-10 — covering boolean checks (a single missing tag), AND/OR combinations (requiring two tags, or at least one of two), string matching (an owner tag that must match a corporate email domain), an XOR-style rule that branches logic on resource type, and grouped-resource rules that inspect relationships between separate objects, such as a Kubernetes RoleBinding that must not reference a denylisted namespace, or a ConfigMap that must not contain a denylisted key.
How do you test and run a rule before it ever reaches CI?
Before a rule ships anywhere, snyk-iac-rules test runs your unit tests against the fixture files you scaffolded, checking that the deny rule fires on the manifests it should and stays silent on the ones it shouldn't — the same test-driven discipline you'd apply to any policy-as-code check. Once tests pass, snyk-iac-rules build . compiles the rule (or a directory of rules) into a single bundle.tar.gz artifact. That bundle is then handed directly to the Snyk CLI with snyk iac test --rules=bundle.tar.gz ./fixtures/file.tf, which runs your custom checks against real infrastructure code on your machine, before anything is committed. This local loop matters because it means a platform or security engineer can iterate on organization-specific policy — say, an internal tagging standard or a network exposure rule tied to a specific compliance obligation — entirely offline, without waiting on a CI pipeline or a change to Snyk's own managed rule catalog.
How do custom rules get enforced across an entire organization, not just one laptop?
Enforcement scales up through container registries rather than by emailing a tarball around: Snyk's documented path is to push the built bundle to an OCI-compliant registry such as Docker Hub, then register that registry location with Snyk at the Group level via the Group IaC Settings API, pointing to a specific image and version tag. Every snyk iac test run in the organization — whether triggered from a developer's terminal, a pull request check, or a scheduled pipeline job — then pulls that same versioned bundle and evaluates it alongside (or, if configured, instead of) Snyk's built-in rules. Versioning the bundle by tag is what makes this workable for a security team: a change to a custom policy is a new image tag, not a silent behavior change that developers discover mid-scan, and rolling back a bad rule is a matter of repointing the registry reference rather than rewriting pipeline configuration.
What are the practical limits of writing your own Rego rules this way?
The mechanical tradeoff is that the SDK gives you the full expressive power of Rego, but that power comes with the same learning curve OPA has everywhere else it's used — engineers who haven't written Rego before will spend real time on syntax like unification, iteration over input.resource.<type>[name], and the difference between a rule that's undefined versus one that evaluates to false, both of which affect whether a deny fires. Rules also only see what Snyk's parser hands them in the input document for a single scan target, so checks that need cross-repository context (for example, "this Terraform module is only allowed if a sibling module also sets a specific variable") aren't a natural fit for a single self-contained Rego file. And because custom rules are versioned and distributed independently of Snyk's own rule updates, a security team taking on custom Rego policy is also taking on the maintenance of it — fixture coverage, regression tests, and registry hygiene become the team's job rather than the vendor's, in exchange for policy that's specific to their own infrastructure conventions.
How Safeguard Helps
Custom Snyk IaC rules are a strong way to encode an organization's specific infrastructure conventions, but the moment that policy logic lives in a separately versioned Rego bundle, pushed to a registry and referenced by tag, it becomes another artifact in the software supply chain that needs its own visibility — who can push a new bundle version, which registry it pulls from, and whether every pipeline is actually pointed at the version the security team believes is enforced. Safeguard focuses on exactly that class of problem: giving security and platform teams a consistent view of the tools, registries, and policy artifacts that sit in front of production infrastructure, so that a custom guardrail like a tagging or network-exposure rule doesn't quietly drift out of enforcement because a pipeline was reconfigured or a registry reference went stale. For teams building out IaC policy-as-code programs, that supply-chain-level visibility over the enforcement path is the piece that keeps custom Rego rules trustworthy long after the day they were written.