KICS 2.2.0 on AWS User Guide
Overview
This guide covers launching and using the KICS 2.2.0 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg OWASP ZAP and Vulnerability Scanners listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.
KICS (Keeping Infrastructure as Code Secure) is the open source infrastructure-as-code scanner from Checkmarx. It reads Terraform, CloudFormation, AWS CDK output, Kubernetes manifests, Helm charts, Dockerfiles, Docker Compose, Ansible, Azure Resource Manager, Google Deployment Manager, OpenAPI and more, and reports security misconfigurations before they are deployed: a security group open to the internet, a public database, an unencrypted volume, a container running as root. Each finding carries a severity, a CWE, the file and line, and the expected and actual values. Results can be written as JSON or SARIF for a pipeline gate, a code review or a dashboard.
Upstream KICS ships as a Docker image. The cloudimg image adds:
- A native KICS 2.2.0 with no container runtime. The scanner is compiled by cloudimg from the official KICS v2.2.0 source with Go 1.27.3 and updated
klauspost/compress,golang.org/x/net,containerdand OpenTelemetry modules, and installed with the full query library from the same release commit. The build provenance and an integrity check (sudo kics-verify) ship on the instance. - A scheduled scan that cannot phone home. A daily timer scans a directory you choose. The scan runs as its own unprivileged
kicsuser inside a systemd sandbox with no network access at all: every address is denied, including the EC2 instance metadata service, the operating system is read-only to it, and it holds no capabilities. KICS needs no network to scan; its queries are on disk. - Reports only root can read. A report describes where your infrastructure is weak, so the scheduled scan writes its JSON and SARIF reports with mode
0600into dated0700directories under/var/lib/kics/reports(owned bykics). Ordinary users cannot read or even list them, and the system journal gets only a one line count of findings. - A self-test on every new instance. On first boot the instance checks the binary and the query library against their build-time fingerprints and scans two bundled fixtures: a deliberately insecure Terraform file that must be flagged and a clean Dockerfile that must not.
- Key-only SSH (no password or keyboard-interactive login, no root login), IMDSv2 only, and a fully patched Ubuntu 24.04 LTS with unattended security upgrades switched on.
This is a command-line product: there is no web UI, no listening service, no account and no password. SSH on port 22 is the only open port.
Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large
Prerequisites
- An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet. Scanning needs no outbound internet access; you only need a way to get your code onto the instance (for example
git cloneover a NAT gateway or a VPC endpoint). - A security group that allows TCP 22 (SSH) from your own networks only. No other inbound port is needed.
Step 1: Launch the instance
In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick KICS 2.2.0 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large (KICS runs its queries in parallel, so more vCPUs make large scans faster), your key pair and a security group as above. Size the root volume for the code you will scan.
With the AWS CLI (replace the values in angle brackets):
aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
--instance-type m5.large --key-name <your-key-pair> \
--security-group-ids <sg-allowing-22-from-your-network> --subnet-id <subnet-id> \
--metadata-options HttpTokens=required,HttpEndpoint=enabled \
--tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=kics}]'
Step 2: Connect to your instance
| Operating system | SSH user |
|---|---|
| Ubuntu 24.04 | ubuntu |
ssh -i <your-key.pem> ubuntu@<instance-public-ip>
There is no password to look up: /etc/kics/credentials on the instance is an informational note that says so and lists the paths used below.
Step 3: Check the installation
kics version
systemctl is-enabled kics-scan.timer kics-firstboot.service
systemctl is-active kics-scan.timer
Keeping Infrastructure as Code Secure 2.2.0
enabled
enabled
active
kics-firstboot.service ran once on the first boot of your instance and started the self-test. kics-scan.timer runs the scheduled scan once a day. Confirm the self-test passed:
sudo journalctl -u kics-selftest.service -o cat --no-pager | grep SELFTEST
kics-scan-run: SELFTEST OK: insecure fixture exit 60 (total=14 critical=1 high=3 medium=4 low=2 info=4 files=1 queries=1101); clean fixture exit 0 with 0 findings
The self-test takes about two minutes of CPU, so on a brand new instance give it a minute or two before you look.
Step 4: Scan the insecure fixture
/usr/share/kics/fixtures/insecure/main.tf is a small Terraform file with an SSH security group open to 0.0.0.0/0 and a publicly accessible, unencrypted RDS instance. Never apply it. Scan it and write JSON and SARIF reports to ~/kics-out/insecure:
kics scan --no-progress --no-color --ci -p /usr/share/kics/fixtures/insecure -o ~/kics-out/insecure --report-formats json,sarif > ~/kics-out-insecure.log 2>&1; echo "exit code: $?"
grep -A 18 'Queries Summary' ~/kics-out-insecure.log
exit code: 60
7:37PM INF --- Queries Summary ---
Platform Query Name Severity CWE Results
Terraform RDS DB Instance Publicly Accessible CRITICAL 668 1
Terraform DB Instance Storage Not Encrypted HIGH 311 1
Terraform Sensitive Port Is Exposed To Entire Network HIGH 200 1
Terraform Unrestricted Security Group Ingress HIGH 668 1
Terraform IAM Database Auth Not Enabled MEDIUM 285 1
Terraform RDS With Backup Disabled MEDIUM 754 1
Terraform RDS Without Logging MEDIUM 778 1
Terraform Security Group With Unrestricted Access To SSH MEDIUM 732 1
Terraform IAM Access Analyzer Not Enabled LOW 710 1
Terraform Tags Not Copied to RDS Cluster Snapshot LOW 284 1
Terraform Resource Not Using Tags INFO 665 2
Terraform Security Group Not Used INFO 284 1
Terraform Security Group Rule Without Description INFO 710 1
The JSON report has the counts and every finding with its file and line:
jq '{total_counter, queries_total, files_scanned, severity_counters}' ~/kics-out/insecure/results.json
jq -r '.queries[] | "\(.severity) \(.query_name) \(.files[0].file_name | split("/")[-1]):\(.files[0].line)"' ~/kics-out/insecure/results.json | head -4
{
"total_counter": 14,
"queries_total": 1101,
"files_scanned": 1,
"severity_counters": {
"CRITICAL": 1,
"HIGH": 3,
"INFO": 4,
"LOW": 2,
"MEDIUM": 4,
"TRACE": 0
}
}
CRITICAL RDS DB Instance Publicly Accessible main.tf:19
HIGH DB Instance Storage Not Encrypted main.tf:20
HIGH Sensitive Port Is Exposed To Entire Network main.tf:6
HIGH Unrestricted Security Group Ingress main.tf:10
KICS loads only the queries for the platforms it finds, which is why a Terraform-only scan runs 1,101 of the 1,813 queries on the image.

Step 5: Confirm a clean scan reports nothing
/usr/share/kics/fixtures/clean/Dockerfile pins its base image by digest, runs as a non-root user and has a health check:
kics scan --no-progress --no-color --ci -p /usr/share/kics/fixtures/clean -o ~/kics-out/clean --report-formats json > ~/kics-out-clean.log 2>&1; echo "exit code: $?"
jq '.total_counter' ~/kics-out/clean/results.json
exit code: 0
0
Step 6: Scan your own infrastructure code
Point -p at a directory or a single file. As an example, a Dockerfile and a Terraform S3 bucket with a public ACL:
mkdir -p ~/iac-demo
cat > ~/iac-demo/Dockerfile <<'EOF'
FROM ubuntu:latest
RUN apt-get update && apt-get install -y curl
COPY app /app
CMD ["/app/run"]
EOF
cat > ~/iac-demo/bucket.tf <<'EOF'
resource "aws_s3_bucket" "logs" {
bucket = "example-logs-bucket"
}
resource "aws_s3_bucket_acl" "logs" {
bucket = aws_s3_bucket.logs.id
acl = "public-read"
}
EOF
kics scan --no-progress --no-color --ci -p ~/iac-demo -o ~/kics-out/demo --report-formats json > ~/kics-out-demo.log 2>&1; echo "exit code: $?"
jq -r '.queries[] | "\(.severity) \(.platform) \(.query_name) \(.files[0].file_name | split("/")[-1]):\(.files[0].line)"' ~/kics-out/demo/results.json
exit code: 60
CRITICAL Terraform S3 Bucket ACL Allows Read Or Write to All Users bucket.tf:7
HIGH Dockerfile Missing User Instruction Dockerfile:1
MEDIUM Dockerfile Apt Get Install Pin Version Not Defined Dockerfile:2
MEDIUM Dockerfile Image Version Using 'latest' Dockerfile:1
MEDIUM Terraform S3 Bucket Logging Disabled bucket.tf:1
MEDIUM Terraform S3 Bucket Without Versioning bucket.tf:1
LOW Dockerfile Healthcheck Instruction Missing Dockerfile:1
LOW Terraform IAM Access Analyzer Not Enabled bucket.tf:1
INFO Dockerfile APT-GET Not Avoiding Additional Packages Dockerfile:2
INFO Dockerfile Apt Get Install Lists Were Not Deleted Dockerfile:2
INFO Terraform Resource Not Using Tags bucket.tf:1
Each finding in results.json also has description, expected_value, actual_value and a link to the query's documentation, which is usually enough to fix it. To scan one of your own repositories, clone it and point -p at it:
git clone https://github.com/<your-org>/<your-infra-repo>.git ~/work/infra
kics scan --no-progress --ci -p ~/work/infra -o ~/kics-out/infra --report-formats json,sarif
SARIF and the CWE taxonomy. KICS adds the CWE taxonomy to a SARIF report only when it finds its CWE tables relative to the current directory, so for SARIF you upload to GitHub code scanning or an IDE, run it from /opt/kics/bin with absolute paths (the scheduled scan does this for you):
(cd /opt/kics/bin && kics scan --no-progress --ci -p /usr/share/kics/fixtures/insecure -o ~/kics-out/sarif --report-formats sarif > /dev/null 2>&1)
jq -r '.runs[0].taxonomies[].name' ~/kics-out/sarif/results.sarif
Categories
CWE
Network use. When you run kics scan yourself, KICS makes one HTTPS request to api.github.com to see whether a newer KICS release exists; it sends nothing about your code or findings, and with no internet access it skips the check silently. The scheduled scan cannot make it at all (Step 9).
Step 7: The exit code contract for CI
KICS exits with a code for the highest severity it found, which makes it usable as a merge gate:
| Exit code | Meaning |
|---|---|
0 |
No findings |
20 |
Highest severity found: INFO |
30 |
Highest severity found: LOW |
40 |
Highest severity found: MEDIUM |
50 |
Highest severity found: HIGH |
60 |
Highest severity found: CRITICAL |
1 |
The scan did not complete |
--fail-on chooses which severities count for the exit code; anything else exits 0. To fail a pipeline only on CRITICAL and HIGH:
kics scan --no-progress --ci -p ~/iac-demo -o ~/kics-out/gate --report-formats json --fail-on critical,high > /dev/null 2>&1; echo "fail-on critical,high exit: $?"
kics scan --no-progress --ci -p /usr/share/kics/fixtures/clean -o ~/kics-out/gate-clean --report-formats json --fail-on critical,high > /dev/null 2>&1; echo "clean fixture exit: $?"
fail-on critical,high exit: 60
clean fixture exit: 0

Step 8: The scheduled scan and its reports
The scheduled scan reads /etc/kics/scan.conf, scans SCAN_TARGET (default /srv/iac) and writes a dated report directory plus a latest link. Copy the example code into it, run one now and read its log:
sudo cp -r ~/iac-demo /srv/iac/iac-demo
sudo systemctl start kics-scan.service
sudo journalctl -u kics-scan.service -n 4 --no-pager -o cat
kics-scan-run: scanning /srv/iac with KICS (formats json,sarif)
kics-scan-run: FINDINGS (total=11 critical=1 high=1 medium=4 low=2 info=3 files=2 queries=1149) - see /var/lib/kics/reports/kics-scan-20261107T193937Z
kics-scan.service: Deactivated successfully.
Finished kics-scan.service - Scheduled KICS infrastructure-as-code scan of SCAN_TARGET (cloudimg).
systemctl start waits for the scan to finish, which takes a minute or so. The reports belong to the kics user and only root can read them:
sudo ls -l /var/lib/kics/reports/latest/
ls /var/lib/kics/reports 2>&1 || true
-rw------- 1 kics kics 2385 Nov 7 19:40 kics.log
-rw------- 1 kics kics 12515 Nov 7 19:40 results.json
-rw------- 1 kics kics 34417 Nov 7 19:40 results.sarif
ls: cannot access '/var/lib/kics/reports': Permission denied
Read a report with sudo. results.sarif is SARIF 2.1.0, which GitHub code scanning, GitLab and most IDEs import directly:
sudo jq '.severity_counters' /var/lib/kics/reports/latest/results.json
sudo jq -r '.runs[0].tool.driver.name' /var/lib/kics/reports/latest/results.sarif
{
"CRITICAL": 1,
"HIGH": 1,
"INFO": 3,
"LOW": 2,
"MEDIUM": 4,
"TRACE": 0
}
KICS

A scan that finds something exits with the severity code from Step 7; systemd treats 0, 20, 30, 40, 50 and 60 as normal results, and the journal line FINDINGS (...) - see <directory> tells you which report to open. The scheduled scan runs as the kics user, so everything under SCAN_TARGET must be readable by that user: files 0644 and directories 0755, or group kics. To collect reports from another system, copy them off as root (for example sudo cp into a directory you control) and decide there whether they may leave the instance.
Step 9: What the scheduled scan can reach
The scheduled scan's sandbox is set in its systemd unit:
systemctl show -p User -p WorkingDirectory -p IPAddressDeny -p ProtectSystem -p ProtectHome -p ReadWritePaths -p NoNewPrivileges -p CapabilityBoundingSet kics-scan.service
IPAddressDeny=0.0.0.0/0 ::/0
WorkingDirectory=/opt/kics/bin
CapabilityBoundingSet=
User=kics
ReadWritePaths=/var/lib/kics
ProtectHome=read-only
ProtectSystem=strict
NoNewPrivileges=yes
IPAddressDeny=0.0.0.0/0 ::/0 is how systemd reports the unit's deny rules, the instance metadata service (169.254.169.254) and every other address: the scan cannot open any network connection, so a malicious file in a scanned repository cannot use it to reach the instance metadata service or anything else. The self-test and first boot units carry the same rules. The only path the scan may write to is /var/lib/kics, so keep REPORT_DIR under it.
Step 10: Configuration
grep -v '^#' /etc/kics/scan.conf | grep .
SCAN_TARGET="/srv/iac"
REPORT_DIR="/var/lib/kics/reports"
REPORT_FORMATS="json,sarif"
FAIL_ON="critical,high,medium,low,info"
EXTRA_ARGS=""
REPORT_RETENTION_DAYS=30
SCAN_TARGET: the directory (or file) the daily scan reads.REPORT_DIRandREPORT_RETENTION_DAYS: where reports go (keep it under/var/lib/kics) and how many days of dated reports are kept.REPORT_FORMATS: any of the formatskics scan --report-formatsaccepts, for examplejson,sarif,html,junit.FAIL_ON: the severities that count for the exit code and theFINDINGSjournal line.EXTRA_ARGS: extrakics scanflags, for example--exclude-paths /srv/iac/vendoror--type terraform,dockerfileto limit the platforms.
The file is read at the start of every scan, so a change takes effect on the next scan. Edit it with sudo nano /etc/kics/scan.conf. To change the schedule, add a drop-in with sudo systemctl edit kics-scan.timer (for example OnCalendar=hourly). The default is OnCalendar=daily with up to 30 minutes of random delay.
Step 11: How the scanner on this image was built
sudo kics-verify
binary OK sha256 954de7fb7da36efe5b771d35043b317e4102aa27a8bc9bea801b824b6d8c1fd9
query library OK 3668 95ed41a3824a65af0780b72f00cdad7ec38f01fb7a4c6f6eb6668233747e36bb
kics-verify recomputes the binary's hash and a manifest of every file in the query library and compares them with the values recorded at build time; first boot runs it before the self-test. The full record:
cat /usr/share/kics/build-provenance/BUILD-PROVENANCE.txt
KICS 2.2.0 on this image was compiled by cloudimg from the unmodified upstream source at
https://github.com/Checkmarx/kics.git commit 453066aa9c66014ad7aee7dd0149ba1944c78050 (tag v2.2.0), with the module bumps below to clear Go security
advisories. Licence: Apache-2.0 (/usr/share/kics/LICENSE.upstream). KICS is a Checkmarx open-source project.
toolchain go1.27.3 linux/amd64, https://go.dev/dl/go1.27.3.linux-amd64.tar.gz sha256 65efbc80fdc1fa2cd2631cad5b481777a68e8725f34e89f50fc6ac714e379b40
modules go get github.com/klauspost/compress@v1.20.2 golang.org/x/net@v0.62.0 github.com/containerd/containerd@v1.7.36 go.opentelemetry.io/otel/sdk@v1.45.0
(module proxy + sum.golang.org); minimum version selection raised the other lines in go.mod.diff.
go.mod sha256 7a856afba59fa5a66e97c68500344bb6f8c7afb2214af8c0db1c679d461023c0, go.sum sha256 4c0ce348eeda987735b303f3cb762263742f7b9f3d2e96e82e38bad7e1ac874f (pinned at build)
flags CGO_ENABLED=0 go build -trimpath
-ldflags "-X github.com/Checkmarx/kics/v2/internal/constants.Version=2.2.0 -X github.com/Checkmarx/kics/v2/internal/constants.SCMCommit=453066aa9c66014ad7aee7dd0149ba1944c78050"
(not stripped; no Sentry DSN; no descriptions endpoint)
govulncheck v1.9.0 -mode=binary on the compiled binary: GO-2026-5064 GO-2026-5338 GO-2026-5622 GO-2026-5932 GO-2026-6791
(GO-2026-5064/5338/5622/6791 = containerd CRI checkpoint restore, containerd/v2 >= 2.1.0 only, CRI not linked;
GO-2026-5932 = golang.org/x/crypto/openpgp deprecation, no fixed version exists)
queries /opt/kics/bin/assets: assets/{queries,libraries,cwe_csv,similarityID_transition} at the same commit
(git trees 42ca44ce4c8042c4d8086c3b0cf0700841ead48f 1cdb355a60c3321f91f8b58e893625af92d9b0c9 25cc837d21e2b96a8776190973e979e68c32be34 23642ca6b27f7ffccab960025d52016f42b67735),
1813 queries, shipped without the per-query test/ fixture directories;
manifest (count sha256) 3668 95ed41a3824a65af0780b72f00cdad7ec38f01fb7a4c6f6eb6668233747e36bb -- re-check: sudo kics-verify
upstream source tarball v2.2.0.tar.gz sha256 3be94ee0b393699b72f00be8603260c60d5531c61aada740cfcbbc3009d1ef05
The remaining advisories do not affect KICS: four are in the containerd CRI checkpoint-restore code of containerd/v2 2.1 and later, which this binary does not link, and no fixed version of golang.org/x/crypto/openpgp exists. The same directory holds the module list compiled into the binary (buildinfo.txt), the exact go.mod change (go.mod.diff) and the full govulncheck report.
Step 12: Access to the instance
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication) '
permitrootlogin no
pubkeyauthentication yes
passwordauthentication no
kbdinteractiveauthentication no
SSH accepts only the key pair you chose at launch (/etc/ssh/sshd_config.d/10-cloudimg-key-only.conf), and no account on the image has a password. Nothing else listens on a public address.
Step 13: Components
| Component | Path |
|---|---|
| KICS binary | /opt/kics/bin/kics (linked from /usr/local/bin/kics) |
| Query library | /opt/kics/bin/assets |
| Scheduled scan runner | /usr/local/bin/kics-scan-run |
| Scan, self-test and first boot units | /etc/systemd/system/kics-scan.service, kics-scan.timer, kics-selftest.service, kics-firstboot.service |
| Configuration | /etc/kics/scan.conf |
| Reports | /var/lib/kics/reports (kics, mode 0700) |
| Default scan target | /srv/iac |
| Self-test fixtures | /usr/share/kics/fixtures |
| Integrity check | /usr/local/sbin/kics-verify |
| Build provenance and licence | /usr/share/kics/build-provenance/, /usr/share/kics/LICENSE.upstream |
| Key-only SSH drop-in | /etc/ssh/sshd_config.d/10-cloudimg-key-only.conf |
Step 14: Security recommendations
- Allow port 22 in the security group from your own networks only.
- Treat every report as sensitive: it is a map of where your infrastructure is weak. Keep reports under
/var/lib/kicsand copy them off only to systems you trust. - Fix CRITICAL and HIGH findings before you apply the code, and gate merges on them with
--fail-on critical,high. - Keep the operating system patched; unattended security upgrades are on, and
sudo apt-get update && sudo apt-get upgradeapplies everything else. New KICS releases, with their new queries, arrive as new versions of this listing.
Support and licensing
KICS is open source under the Apache License 2.0 (a copy is at /usr/share/kics/LICENSE.upstream); KICS is a Checkmarx open source project, and this image is not affiliated with or endorsed by Checkmarx. There is no per user or per CPU licence fee. The AMI price covers cloudimg's build, hardening, first boot setup and support.
- Email: support@cloudimg.co.uk
- Website: https://www.cloudimg.co.uk
- Support: 24/7, with a 24 hour response SLA