KICS 2.2.0
Overview
This guide covers launching and using the KICS 2.2.0 on Ubuntu 24.04 AMI from SecureAnyCloud on Amazon EC2. It is one version of the SecureAnyCloud 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. It reports security misconfigurations before you deploy them, such as a security group open to the internet, a public database, an unencrypted volume or a container running as root.
Each finding carries a severity, a CWE, the file and line, and the expected and actual values. You can write results as JSON or SARIF for a pipeline gate, a code review or a dashboard.
Upstream KICS ships as a Docker image. This SecureAnyCloud image runs KICS natively on Ubuntu 24.04 LTS and adds the following.
What you get:
- A native KICS 2.2.0 with no container runtime. SecureAnyCloud compiles the scanner from the official KICS v2.2.0 source with Go 1.27.3 and updated klauspost/compress, golang.org/x/net, containerd and OpenTelemetry modules. It is 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 kics user 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 the scan, and the scan holds no capabilities. KICS needs no network to scan because its queries are on disk.
- Reports that only root can read. A report describes where your infrastructure is weak. The scheduled scan therefore 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 receives 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. It then 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 turned on.
This is a command-line product. It has 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, but you 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.
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, select your Region, then choose Launch through EC2.
Choose m5.large, your key pair and a security group as described above. KICS runs its queries in parallel, so more vCPUs make large scans faster. Size the root volume for the code you will scan.
Alternatively, launch 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}]'Connect to your instance
Connect over SSH as the default user for your operating system. For Ubuntu 24.04, the user is 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.
Check the installation
kics version systemctl is-enabled kics-scan.timer kics-firstboot.service systemctl is-active kics-scan.timerKeeping Infrastructure as Code Secure 2.2.0 enabled enabled activekics-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 that the self-test passed:
sudo journalctl -u kics-selftest.service -o cat --no-pager | grep SELFTESTkics-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 findingsThe self-test takes about two minutes of CPU. On a new instance, wait a minute or two before you check the result.
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.logexit 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 1The 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:10KICS loads only the queries for the platforms it finds. This is why a Terraform-only scan runs 1,101 of the 1,813 queries on the image.
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.jsonexit code: 0 0Scan your own infrastructure code
Point -p at a directory or a single file. The following example creates a Dockerfile and a Terraform S3 bucket with a public ACL, then scans them:
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.jsonexit 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:1Each finding in results.json also has description, expected_value, actual_value and a link to the documentation of the query, which is usually enough to fix the issue. 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,sarifSARIF 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. For SARIF that you upload to GitHub code scanning or an IDE, run KICS 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.sarifCategories CWENetwork use. When you run kics scan yourself, KICS makes one HTTPS request to api.github.com to check whether a newer KICS release exists. The request sends nothing about your code or findings, and KICS skips the check silently when there is no internet access. The scheduled scan cannot make the request at all (Step 9).
The exit code contract for CI
KICS exits with a code for the highest severity it found, so you can use it as a merge gate:
- 0: no findings
- 20: highest severity found is INFO
- 30: highest severity found is LOW
- 40: highest severity found is MEDIUM
- 50: highest severity found is HIGH
- 60: highest severity found is CRITICAL
- 1: the scan did not complete
--fail-on chooses which severities count for the exit code. Any other severity exits 0. To fail a pipeline only on CRITICAL and HIGH, run:
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: 0The 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 the target, run a scan 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 catkics-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 (sac).systemctl start waits for the scan to finish, which takes about a minute. 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 deniedRead 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 } KICSA 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 that user must be able to read everything under SCAN_TARGET. Use mode 0644 for files and 0755 for directories, or group kics. To collect reports from another system, copy them off as root (for example with sudo cp into a directory you control), and decide there whether they may leave the instance.
What the scheduled scan can reach
The sandbox of the scheduled scan is defined in its systemd unit:
systemctl show -p User -p WorkingDirectory -p IPAddressDeny -p ProtectSystem -p ProtectHome -p ReadWritePaths -p NoNewPrivileges -p CapabilityBoundingSet kics-scan.serviceIPAddressDeny=0.0.0.0/0 ::/0 WorkingDirectory=/opt/kics/bin CapabilityBoundingSet= User=kics ReadWritePaths=/var/lib/kics ProtectHome=read-only ProtectSystem=strict NoNewPrivileges=yesIPAddressDeny=0.0.0.0/0 ::/0 is how systemd reports the deny rules of the unit, which cover 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.
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=30SCAN_TARGET: the directory (or file) that 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 formats thatkics 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/vendor, or--type terraform,dockerfileto limit the platforms.
The scan reads the file at the start of every run, 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.
How the scanner on this image was built
sudo kics-verifybinary OK sha256 954de7fb7da36efe5b771d35043b317e4102aa27a8bc9bea801b824b6d8c1fd9 query library OK 3668 95ed41a3824a65af0780b72f00cdad7ec38f01fb7a4c6f6eb6668233747e36bbkics-verify recomputes the hash of the binary 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.txtKICS 2.2.0 on this image was compiled by sac 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 3be94ee0b393699b72f00be8603260c60d5531c61aada740cfcbbc3009d1ef05The 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.
Access to the instance
sudo sshd -T | grep -E '^(passwordauthentication|kbdinteractiveauthentication|permitrootlogin|pubkeyauthentication) 'permitrootlogin no pubkeyauthentication yes passwordauthentication no kbdinteractiveauthentication noSSH accepts only the key pair you chose at launch (/etc/ssh/sshd_config.d/10-sac-key-only.conf), and no account on the image has a password. Nothing else listens on a public address.
Components
These are the key components and their paths:
- 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 andkics-firstboot.service - Configuration:
/etc/kics/scan.conf - Reports:
/var/lib/kics/reports(ownerkics, mode 0700) - Default scan target:
/srv/iac - Self-test fixtures: /usr/share/kics/fixtures
- Integrity check: /usr/local/sbin/kics-verify
- Build provenance and license: /usr/share/kics/build-provenance/ and /usr/share/kics/LICENSE.upstream
- Key-only SSH drop-in:
/etc/ssh/sshd_config.d/10-sac-key-only.conf
Security recommendations
- Allow port 22 in the security group from your own networks only.
- Treat every report as sensitive, because it maps where your infrastructure is weak. Keep reports under
/var/lib/kicsand copy them 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 license fee. The AMI price covers the SecureAnyCloud build, hardening, first-boot setup and support.
- Email: sales@secureanycloud.com
- Website: https://secureanycloud.com
- Support: 24/7, with a 24 hour response SLA
Deploy on AWS
Launch KICS 2.2.0 on Ubuntu 24.04 on AWS with 24/7 support from SecureAnyCloud.
View on Marketplace
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com