IaC security scanning: what is found in manifests before deployment
Secrets in manifests are the most common and most costly defect.
An access key to an image registry, a token for an external service, or a database password written directly into a pod description or build environment variables end up in the repository history, where they live longer than the manifests themselves.
Deleting the line in the next commit is not enough: the value remains in old revisions and in the builder cache, so you have to change the secret itself, revoke issued access permissions, and figure out who else used it.
Excessive access permissions are introduced at the description stage and then replicated with every deployment.
A service account with cluster administrator rights where only read and write access to a single branch is needed; a container running as superuser; a role allowed to access external addresses although internal network access would suffice.
A separate line is services exposed outward by default: an entry point without source restrictions, a publicly accessible storage, a debug port that was forgotten to be closed before release.
The third class of defects is the discrepancy between the infrastructure description and its actual state.
An on-call manual fix during an incident closes the incident but remains unnoticed: the next apply plan shows “no changes,” while the environment is already living by different rules.
On the next deployment, such an environment either overwrites the manual modifications or breaks on them — and recovery proceeds from memory rather than from the description.
The cost of any of these defects is multiplied by deployment speed: the more frequent the releases, the higher the chance of dragging a secret or an extra permission into production.
Checks inside the pipeline catch this before apply — let's break down how IaC security scanning is structured and what exactly it stops on the way to the environment.
What configuration control provides: speed, failures, downtime
The point of built-in checks is not the act of scanning itself, but which numbers change after they are enabled. We look at four metrics: build time, the share of deployments that had to be rolled back, recovery time after a failed apply, and total downtime.
Configuration control affects all four if it is built into the pipeline rather than moved to a separate manual stage.
Build speed hardly suffers: only changed modules are checked, the result is cached between runs, and heavy rules run in parallel with the other steps.
The increase in build time stays within a few percent — noticeably less than the time needed for manual analysis of a single configuration error.
The main effect is the class of failures that simply stops reaching the environment.
Ports exposed outward, publicly accessible storage, roles with excessive permissions, keys in plaintext inside templates — such defects are caught at the change review stage, not during operation.
Recovery time is reduced for the same reason: configuration lives as a versioned set of files, so a failed apply is rolled back to the previous state rather than fixed manually on the live environment.
Downtime due to a configuration error moves from a range of hours to a range of minutes — the environment returns to a working state through the same automated pass.
Deployment predictability is a consequence of a single set of checks for test and production environments.
Configuration drift is visible before apply, so divergence between environments stops being a surprise at release time, and the reduction in failures becomes measurable rather than declarative.
Check modes: comparison of approaches and their limitations
A single mode does not close the loop: approaches differ in when they trigger and in what state they see at all.
Build-time checking reads descriptions before apply, drift control compares fact with what is declared in the environment, audit examines the already deployed environment on a schedule, and manual review relies on context that cannot be expressed by a rule.
| Mode | What it detects | Where it is limited |
|---|---|---|
| Build-time checking | Open ports, forbidden parameters, relationship errors in descriptions before apply | Does not see changes made to the environment manually |
| Drift control | Deviation of actual state from declared state | Does not distinguish an intentional change from an accidental one |
| Periodic configuration audit | Accumulated setting deviations and outdated resources | Between runs, the deviation remains unnoticed |
| Manual review of descriptions | Semantic errors that cannot be described by a rule | Does not scale and depends on the reviewer’s attention |
The conclusion from the comparison is simple: you cannot close the loop with a single mode.
Build cuts off the main part of errors before deployment, drift control catches what slipped past it, audit picks up slow deviations, and review remains for changes where human context is needed.
At the same time, we keep the rules declarative — one set of policies runs both at build time and on the live environment, for example through Checkov together with scheduled tasks.
Implementation stages: from inventory to handover to operation
Implementing security checks in infrastructure descriptions does not start with enabling the scanner in the build. First, we record exactly what we are checking: repositories with working manifests, reusable modules, environment templates.
Otherwise, the very first run produces hundreds of findings on archived branches, the rule annoys the team, and it is disabled a week later.
- Configuration inventory. We compile a list of repositories, branches, and modules that are actually applied to live environments. Archived and experimental ones are marked separately — they go neither into the rules nor into reporting.
- Rule and severity level scheme. For each resource class, we decide what blocks the build, what produces a warning, and what goes only into the report. We agree on the scheme with the teams in advance so the rule does not change on release day.
- Setting up rules and exceptions. We enable the set of checks, configure exceptions by paths and resource labels, and keep the scanner configuration in the repository — then edits go through review like regular code.
- Testing on a test environment. We run it on a copy of real manifests, measure the number of findings, check time, and false positive share. We also look separately at how much the check lengthens the build — if noticeable, we move heavy rules into a separate step.
- Noise suppression. We close legitimate deviations with pinpoint exceptions with a comment and review date, rather than disabling the rule entirely. This way the findings list stays short and is actually read.
- Handover to operation. We connect the check to the main pipeline, assign owners for reviewing reports, hand over documentation, and go through typical findings with the teams.
After handover, stages 3 and 5 are repeated as new resource types appear — the rule scheme lives together with the infrastructure rather than freezing on the implementation date.
Handover artifacts: IaC modules, policies, and procedures
Infrastructure description scanning ends not with a report, but with a set of artifacts that your team maintains further on its own. Everything remains in your repository next to the environment description — without dependence on our engineers or proprietary services.
- Reusable IaC modules: building blocks for describing networks, storage, roles, and databases with unified input parameters — a new service is deployed from a template rather than by copying someone else’s file.
- A rule library calibrated for your environment: which checks block the build, which work as warnings, and what justifies each exception.
- Access policies for environments, secrets, and image registries: roles, areas of responsibility, and the procedure for granting permissions — a new engineer enters the environment according to instructions, not via chat.
- Response procedures: who reviews a finding, in which case the build stops, and in which case a deviation is recorded with a remediation deadline and owner.
- Configuration documentation: a map of environments, connections between services, and the points where the check result is embedded in the build and delivery pipeline.
- Infrastructure diagrams assembled from the description, and a baseline values log — you can see how many findings there were at the start and which classes are gone.
The artifacts are handed over with change history and editing rights for your team. Our engineers stay in touch to review disputed findings and expand the rule library.
Case study: how secret leakage from configurations was eliminated
The customer is a product team with several dozen repositories where infrastructure is described declaratively.
Secrets got into configurations out of inertia: the database password was written directly into the manifest’s environment variables, and keys to external services were put into module parameters. Checking was manual: before release, an engineer went through configuration diffs by eye.
This took 3-4 hours per release, and defects still slipped through. Over a quarter, 5-6 secret leaks reaching the production environment were recorded.
Investigating one incident took 6-10 hours: first find out which key leaked and where it was used, then revoke and replace it, then check whether there had been any access.
About 40% of detected defects were repeat ones — the same type of error surfaced in a new repository a few weeks later.
Checks were built into the build and delivery pipeline. Gitleaks runs on every merge request and blocks the change if a line resembles a key or token; the developer immediately gets the file and line number.
In parallel, static configuration analysis catches open ports, excessive permissions, and unencrypted volumes before they reach the main branch. A separate rule closed the commit history — old secrets are no longer pulled into new releases.
Two months later, incident investigation took 20-30 minutes instead of 6-10 hours: the finding comes with ready context, not as the question “where does this live.” Repeat defects dropped to about 5% — a rule added once to common policies works across all repositories at once. Manual audit before release was removed entirely, and those hours went to development tasks.
How do build-time checks differ from runtime environment checks?
Build-time checking works with infrastructure as code: manifests, templates, and modules. It answers the question of what exactly we are going to deploy — open access rules, excessive role permissions, unencrypted volumes, references to unverified images.
Such control is cheap: it runs before apply and stops a dangerous change at the review stage without requiring access to the production environment.
The coverage boundaries of static control are rigid: it sees only what is described by code.
A resource created manually through the control panel, an account added outside the description, or a rule corrected directly in the environment do not get into the analysis — the discrepancy between description and fact remains invisible until the next reconciliation.
Runtime environment checking reads the actual state: which roles exist, which rules are open, which volumes are mounted, what is actually running.
It catches configuration drift and workarounds, but it cannot prevent anything — the change has already been applied, and we react after the fact.
Therefore, the choice of control mode is not “either/or” but “and,” and the modes have different tasks. Build is responsible for prevention, the environment for detecting discrepancies and confirming that what is deployed matches the checked description.
The third layer is regular reconciliation of the description with fact: it shows where the environment has diverged from the code and brings such places back under control.
Technical doubts: false positives and rule coverage
“The scanner will be noisy, we’ll drown in findings” and “our infrastructure is already deployed manually” are the objections we hear most often. Both are addressed by fine-tuning rules, not by abandoning checks.
We start with a baseline set: public ports, lack of encryption, open access to storage. We enable the rest as we go through it — this way noise does not drown out what actually leads to failure.
Rules produce a flood of false positives — what to do about it?
We review each finding and assign it to one of three groups: a real error, a context-based exception, or an imprecise rule formulation. For the second, we write exceptions by path and resource tag; for the third, we adjust the threshold or disable it with a note in the registry. Noise drops to isolated cases.
Can it be implemented gradually without blocking releases?
Yes. First, checks run in warning mode and do not stop the pipeline. Then blocking is enabled for test environments, and only after the rule set has stabilized — for production ones.
Infrastructure is already deployed — should everything be described anew?
No. We import existing resources into state: they continue to work but come under description management. Then checks run on changes and gradually cover what is already deployed.
What about secrets in the repository?
A separate rule searches for keys and tokens in descriptions and variables. We do not just mark what is found: we revoke the key, move it to a secret store, and put a reference in the code. We clean the commit history — a deleted file remains in it.
Let's discuss the task: where to start implementing checks
To make the conversation concrete, it is useful to collect several artifacts in advance — then at the first meeting we will outline the scope of the current state audit and the order of work, rather than clarifying basic things.
Below is what most often speeds up the start of implementing IaC security scanning in the build pipeline.
- List of environments: development, test, pre-production, production, how many accounts and clusters, where configurations are maintained manually and where they are already described by code.
- Current build pipeline diagram: at which step checks run, what blocks the build, and what passes as a warning.
- Known problem areas: incidents due to configuration discrepancies, drift between stands, rules that are bypassed manually.
- List of infrastructure description tools: modules, templates, state storage method — to understand what to connect first.
- Access and response requirements: who reviews findings, what is considered blocking, where reports go, and how exceptions are documented.
- Release constraints: windows when the pipeline can be changed, and the allowable build delay.
- Responsible persons: who owns the infrastructure code and makes decisions on disputed rules.
This will be enough to come back with an implementation plan: stages, environment priority, role distribution, and control points. Describe the task in a convenient form — we will come back with an estimate and work plan.







