KICS on Ubuntu 24.04 on Azure User Guide
Overview
KICS - Keeping Infrastructure as Code Secure - is the open-source static-analysis scanner from Checkmarx that finds security misconfigurations and compliance issues in Infrastructure as Code before it is ever applied to a cloud account. It parses Terraform, CloudFormation, Kubernetes manifests, Ansible, Dockerfile and Docker Compose, Helm charts, ARM and Bicep, OpenAPI, gRPC, Pulumi and Crossplane, and evaluates each against an embedded library of more than two thousand queries covering the security guidance of the major cloud and platform providers.
Every finding names the query, the platform, the file, the line, a severity (CRITICAL, HIGH, MEDIUM, LOW, INFO) and the CWE it maps to, and results can be written as JSON or SARIF for a code review, a dashboard or a pipeline gate. Because the query library ships with the scanner and is evaluated locally, scanning needs no outbound internet at all - this VM can sit in a locked-down subnet with no egress and still scan.
The cloudimg image is a ready-to-run IaC-security-scanner host, not just the binary. The pinned scanner and its pinned query library are both verified against their published checksums at build time, a simple wrapper makes a full scan a single command, a daily timer scans a directory you choose and writes machine-readable JSON and SARIF reports, and read-only self-test fixtures let you prove detection works on your own VM. Backed by 24/7 cloudimg support.
What is included:
- KICS 2.1.20 (
/usr/local/bin/kics), installed from the official release tarball and verified against the upstream SHA256 checksum - The full pinned query library (1811 queries) under
/opt/kics/assets/queries, from the same release and checksum-verified, so scanning is fully offline kics-scan- a convenience wrapper that supplies the query paths, sokics-scan -p <dir>works with no flags to rememberkics-scan-run+kics-scan.timer- a daily scan of your IaC, writing JSON + SARIF withlatest.*symlinks for CI pickup- Read-only self-test fixtures under
/usr/share/kics/fixtures(an insecure Terraform sample and a locked-down clean sample) so you can prove the scanner works on your own VM - An appliance configuration at
/etc/kics/scan.conf - 24/7 cloudimg support
This is a command-line product: no web UI, no listening service, no admin account and no password. SSH on port 22 is the only open port, and the SSH key you choose at launch is the only way in.
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a good starting point. NSG inbound: allow 22/tcp from your management network. No inbound application ports are needed, and no outbound internet is needed for scanning.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for KICS by cloudimg, and select Create. On Basics pick your subscription, resource group, region and size; under Administrator account choose SSH public key and paste your key; under Inbound port rules allow SSH (22). Then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name kics \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
There is no password and no console to log in to - this product has no credentials at all. /etc/kics/credentials on the VM is an informational note that says exactly that, and points at the paths below.
Step 4 - Confirm KICS is installed
kics version
Keeping Infrastructure as Code Secure 2.1.20
The query library that ships with the image is pinned to the same release and installed read-only:
find /opt/kics/assets/queries -name metadata.json | wc -l
1811
Step 5 - Scan an insecure Terraform sample
The image ships a deliberately insecure Terraform fixture - a security group that opens every TCP port to 0.0.0.0/0. Scan it with the kics-scan wrapper, which supplies the pinned query paths for you. Results are written to the -o directory:
kics-scan -p /usr/share/kics/fixtures/insecure -o /tmp/kics-insecure --report-formats json,sarif; echo "exit: $?"
KICS prints a results summary and a per-query breakdown. This is the real output from this image - 66 HIGH findings, dominated by the wide-open ingress rule:
--- Results Summary ---
CRITICAL: 0
HIGH: 66
MEDIUM: 3
LOW: 1
INFO: 3
TOTAL: 73
--- Queries Summary ---
Platform Query Name Severity CWE Results
Terraform Remote Desktop Port Open To Internet HIGH 668 1
Terraform Sensitive Port Is Exposed To Entire Network HIGH 200 63
Terraform Unknown Port Exposed To Internet HIGH 200 1
Terraform Unrestricted Security Group Ingress HIGH 668 1
Terraform HTTP Port Open To Internet MEDIUM 319 1
...
Results saved to file /tmp/kics-insecure/results.json

KICS exits with a non-zero status when it finds results (the exit status reflects the highest severity found), which is what makes it usable as a pipeline gate.
Step 6 - Confirm a locked-down sample passes
The clean fixture is a security group restricted to a single port and a private CIDR. It must report zero HIGH or CRITICAL findings:
kics-scan -p /usr/share/kics/fixtures/clean -o /tmp/kics-clean --report-formats json; echo "exit: $?"
--- Results Summary ---
CRITICAL: 0
HIGH: 0
MEDIUM: 0
LOW: 2
INFO: 2
TOTAL: 4
No HIGH or CRITICAL misconfigurations - the well-configured security group passes. The remaining LOW/INFO notes (for example "Resource Not Using Tags") are informational hygiene, not security exposures.

Step 7 - Scan your own Infrastructure as Code
Put your IaC checkouts under /srv/iac (the default scan target), or point the scanner at wherever they already live. KICS auto-detects the platform (Terraform, Kubernetes, CloudFormation, Ansible, Dockerfile, Helm and more):
kics-scan -p /path/to/your/terraform -o /tmp/kics-report --report-formats json,sarif
You can also scan multiple paths and exclude directories - see kics scan --help for the full flag set. The wrapper passes everything through to kics scan.
Step 8 - Scheduled scans and machine-readable reports
kics-scan-run is the scheduled entry point. It reads /etc/kics/scan.conf, scans SCAN_TARGET, and writes both a JSON and a SARIF report with latest.* symlinks. Run it once by hand against the insecure fixture to see the reports appear:
sudo kics-scan-run /usr/share/kics/fixtures/insecure
ls -l /var/lib/kics/reports/latest.json /var/lib/kics/reports/latest.sarif
[kics-scan-run] scanning /usr/share/kics/fixtures/insecure for IaC security misconfigurations
[kics-scan-run] results found (KICS exit 50) - see /var/lib/kics/reports/kics-scan-20260719T201454Z.json
[kics-scan-run] reports: ... (latest.json / latest.sarif updated)
lrwxrwxrwx 1 root root ... latest.json -> .../kics-scan-20260719T201454Z.json
lrwxrwxrwx 1 root root ... latest.sarif -> .../kics-scan-20260719T201454Z.sarif
SARIF is the format GitHub code scanning, Azure DevOps and most IDEs consume directly, so latest.sarif can be uploaded as-is.

The scan timer is enabled and armed out of the box, and so is the first-boot provisioning unit:
systemctl is-enabled kics-scan.timer kics-firstboot.service
enabled
enabled
Step 9 - Reading the results (severity counts)
The JSON report carries a severity_counters object and a total_counter, so a pipeline can gate on severity without parsing the whole document:
jq -r '.severity_counters | "CRITICAL=\(.CRITICAL) HIGH=\(.HIGH) MEDIUM=\(.MEDIUM) LOW=\(.LOW) INFO=\(.INFO)"' /var/lib/kics/reports/latest.json
CRITICAL=0 HIGH=66 MEDIUM=3 LOW=1 INFO=3
A common CI gate is "fail the build if any HIGH or CRITICAL result is present":
HC=$(jq '(.severity_counters.CRITICAL // 0) + (.severity_counters.HIGH // 0)' /var/lib/kics/reports/latest.json)
[ "$HC" -eq 0 ] || { echo "KICS found $HC HIGH/CRITICAL misconfiguration(s)"; exit 1; }

Configuration
The scheduled scan reads /etc/kics/scan.conf:
grep -v '^#' /etc/kics/scan.conf | grep .
SCAN_TARGET="/srv/iac"
REPORT_DIR="/var/lib/kics/reports"
QUERIES_PATH="/opt/kics/assets/queries"
LIBRARIES_PATH="/opt/kics/assets/libraries"
REPORT_RETENTION_DAYS=30
SCAN_TARGET- the directory the daily scan walks for IaC. Point it at your checkouts.QUERIES_PATH/LIBRARIES_PATH- the pinned, shipped query library and Rego libraries. PointQUERIES_PATHat your own directory of KICS queries to use a custom set.REPORT_DIR/REPORT_RETENTION_DAYS- where reports are written and how many days of dated reports are kept.
After editing, restart the timer:
sudo systemctl restart kics-scan.timer
Custom queries and platforms
KICS ships the full query library compiled into the image, so most users need no configuration. To restrict a scan to particular platforms, or to add your own queries, pass the flags straight through the wrapper:
# Scan only Terraform and Kubernetes
kics-scan -p /srv/iac --type Terraform,Kubernetes -o /tmp/kics-report
# Use your own directory of KICS queries
kics-scan -p /srv/iac -q /etc/kics/my-queries -o /tmp/kics-report
# Fail only on HIGH or above
kics-scan -p /srv/iac --fail-on high -o /tmp/kics-report
See kics scan --help for the complete set of flags.
Maintenance
- Scans: run daily by
kics-scan.timer; run one on demand withsudo kics-scan-run. Inspect history withjournalctl -u kics-scan.service. - Reports:
/var/lib/kics/reports/latest.jsonandlatest.sarifalways point at the most recent scan; dated reports are pruned afterREPORT_RETENTION_DAYS. - Upgrades: download a newer release from the KICS releases, verify it against the published
checksums.txt, and replace/usr/local/bin/kicsand the query library under/opt/kics/assets. - Security patches: unattended-upgrades remains enabled, so the OS continues to receive security updates automatically.
- Air-gapped use: nothing about scanning requires egress - the query library is local, so this VM scans fully offline.
Support
cloudimg provides 24/7 expert support for this image. Contact support@cloudimg.co.uk.