README
ΒΆ
Conscan
An open source project from Peek8.io.
Secure, lean, and compliant containers made simple.
Conscan scans a container image forvulnerabilities, exposed secrets, inefficient file storage, OS packages and software dependencies in use (SBOM) and check CIS(Center for Internet Security) Benchmarks.
Conscan Report in CLI (Table Format):
Conscan Report in HTML Format:
Introduction
Conscan is a lightweight yet powerful container image scanning tool that helps you secure and optimize your container workloads. It analyzes container images for potential security risks, misconfigurations, and inefficienciesβgiving developers and DevOps teams actionable insights before deploying to production.
With conscan, you can:
- Detect vulnerabilities in OS packages and application dependencies.
- Identify exposed secrets inside images.
- Generate SBOM (Software Bill of Materials) for transparency and compliance.
- Spot inefficient file storage that bloats images.
- Validate container security against CIS Benchmarks.
- Export results in JSON, table, or HTML formats for easy integration and reporting.
- Scan both local images and images from remote registries (e.g., Docker Hub).
- By integrating conscan into your CI/CD pipeline, you can shift security left and ensure your containers are secure, lean, and compliant from the very beginning.
Motivation
There are already great security tools like Trivy, Grype, and Syft β so why build conscan?
While working with containerized applications, I noticed a few gaps when using these tools individually:
-
π Fragmented Workflows β Each tool specializes in one area (vulnerabilities, SBOM, secrets, CIS), which often requires juggling multiple commands and parsing different outputs.
-
π Inconsistent Reporting β Output formats and structures vary between tools, making it harder to integrate results into CI/CD pipelines or share with teams.
-
π₯ Developer Experience β New users often struggle with setup, remembering CLI flags, and combining results from multiple scanners.
-
π¦ One-stop Scanning β Sometimes you just want to run a single command against an image and get everything β vulnerabilities, SBOM, secrets, CIS checks, and efficiency hints.
Conscan was built to address these pain points by:
- Providing a unified interface to multiple best-in-class scanners.
- Normalizing results into consistent outputs (table, JSON, HTML).
- Supporting both local and remote images seamlessly.
- Focusing on developer-first usability and CI/CD integration.
In short: π Conscan doesnβt try to reinvent the wheel β it puts the best wheels together on one car. ππ¨
Features
π Vulnerability Scanning
Scans OS packages and software dependencies against known vulnerabilities.
π Secret Detection
Detects API keys, tokens, and other sensitive information accidentally embedded in images.
π¦ SBOM Generation
Produces a detailed Software Bill of Materials for visibility into components in use.
π Storage Efficiency Analysis
Highlights large or unnecessary files that increase image size.
π‘ CIS Benchmark Checks
Validates images against Center for Internet Security (CIS) recommendations.
π Flexible Report Formats
Supports JSON, table, and HTML outputs for automation, human readability, and sharing.
π₯ Local & Remote Image Support
Works with images stored locally or pulled directly from remote registries like Docker Hub.
β‘ Fast & CI/CD Friendly
Designed to be easily integrated into build pipelines with minimal overhead.
Powered By
Conscan leverages well-established open-source security and compliance tools under the hood, combining their strengths into a single unified workflow:
- Trivy β Vulnerability scanning, secret detection, and misconfigurations
- Grype β Deep vulnerability scanning of OS packages and application dependencies
- Syft β SBOM (Software Bill of Materials) generation
- Dive β Check inefficient files by discovering ways to shrink the size of your Docker/OCI image.
- Dockle β Container Image Linter for Security, Check CIS Benchmarks
By orchestrating these tools behind the scenes, conscan provides a streamlined developer experience with consistent reporting, multiple output formats, and optional CIS benchmark validation.
Installation
Pre-Requisites
If you want to install the binary at your machine, you need to install the following tools to make the binary work:
You can go to the corresponding sites and install them as per the installation guide, or can use install-dependencies.sh script.
Binary
You can get the latest version binary from releases page.
Download the archive file for your operating system/architecture. Unpack the archive, and put the binary somewhere in your $PATH (on UNIX-y systems, /usr/local/bin or the like).
you can check your os at terminal using command: $uname -s and architecture by $uname -m.
For example, if your os is Darwin and architecture is arm64, you can run the following command to install:
$ conscan_version=0.1.0-alpha1
$ wget -qO- "https://github.com/peek8/conscan/releases/download/v${conscan_version}/conscan_${conscan_version}_darwin_arm64.tar.gz" | tar -xz -C /usr/local/bin conscan
- NOTE: Make sure that the binary is executable. (
chmod +x conscan)
Use Docker
You can also run the conscan using the Container Image:
$ docker run --rm -it \
--name conscan ghcr.io/peek8/conscan:latest \
scan $(ImageName):$(ImageTag)
For example, to scan the alpine:latest image, you can use:
$ docker run --rm -it \
--name conscan ghcr.io/peek8/conscan:latest \
scan alpine:latest
It is highly recommended to mount a persistent cache dir on the host into the Conscan container. This will make the scanning as it will persist the Vulnerability(and other) Databases.
Example:
$ docker run --rm -it \
-v ./cache:/.cache \
--name conscan ghcr.io/peek8/conscan:latest \
scan alpine:lates
You can see all the available image tags at github container repository.
Quick Start
General Usage
$ conscan scan [Flags] yourimage:tag
Examples:
Scan a container image locally available which uses the Podman/Docker daemon for local images
$ conscan scan alpine-sec:1.0
Scan container images from registry eg. dockerhub
$ conscan scan docker.io/yourimage:tag
or from github image repo
$ conscan scan ghcr.io/yourimage:tag
By default, conscan will scan everything. If you are interested for specific scan report, you can use the flag --scanners with comma separated values. The supported values are: [vuln secret package cis storage]. For example, to scan for only vulnerabilities and exposed secrets you can use like:
$ conscan scan --scanners=vuln,secret yourimage:tag
Report Formats
Conscan supports multiple output formats to fit different workflows:
- Table β Human-readable in CLI
- JSON β For integration with pipelines & automation
- HTML β Shareable reports for teams and auditors
To get report in different format, you can use the flag --format(-f in short). Supported values are: [json table html], if not provided default format is table.
Examples:
By default, table format report
$ conscan scan alpine-sec:1.0
HTML format report: to view the html its better to save report to a file and view the report in a browser, In that case you can use --output flag
$ conscan scan --format html --output report.html alpine-sec:1.0
JSON format report
$ conscan scan --format html --output report.html alpine-sec:1.0
Sample Json output
{
"CreatedAt": "2025-10-01T11:37:31.562284+06:00",
"CreatedAtStr": "2025-10-01 05:37:31 UTC",
"ArtifactName": "alpine-sec:1.0",
"ArtifactType": "container_image",
"metadata": {
"Size": 8463360,
"sizeStr": "8 MB",
"OS": {
"Family": "alpine",
"Name": "3.22.0_alpha20250108"
},
"ImageID": "sha256:cf7a952180715b0ae2148cc8d832130bd008a295e707d7f8fdc3bdf144b630ba",
"RepoTags": [
"docker.io/asraf344/alpine-sec:1.0",
"ghcr.io/asraf344/alpine-sec:1.0",
"localhost/alpine-sec:1.0"
],
"RepoDigests": [
"docker.io/asraf344/alpine-sec@sha256:924f7457fa28ebd1e55c8d142e8f866a8020eaea4f2f9ec92bc47ef1ae7135ba",
"ghcr.io/asraf344/alpine-sec@sha256:924f7457fa28ebd1e55c8d142e8f866a8020eaea4f2f9ec92bc47ef1ae7135ba",
"localhost/alpine-sec@sha256:924f7457fa28ebd1e55c8d142e8f866a8020eaea4f2f9ec92bc47ef1ae7135ba"
],
"ImageConfig": {
"architecture": "arm64",
"os": "linux",
"created": "2025-09-23T11:01:55.383691556Z"
}
},
"vulnerabilities": [
{
"VulnerabilityID": "CVE-2025-26519",
"PkgID": "musl@1.2.5-r9",
"PkgName": "musl",
"InstalledVersion": "1.2.5-r9",
"FixedVersion": "1.2.5-r10",
"Status": "fixed",
"DataSourceURL": "https://security.alpinelinux.org/vuln/CVE-2025-26519",
"Title": "musl libc 0.9.13 through 1.2.5 before 1.2.6 has an out-of-bounds write ...",
"Description": "musl libc 0.9.13 through 1.2.5 before 1.2.6 has an out-of-bounds write vulnerability when an attacker can trigger iconv conversion of untrusted EUC-KR text to UTF-8.",
"Severity": "High",
"_": 4,
"CweIDs": [
"CWE-787"
],
"CvssScore": 8.1,
"References": [
"http://www.openwall.com/lists/oss-security/2025/02/13/2",
"http://www.openwall.com/lists/oss-security/2025/02/13/3",
],
"PublishedDate": "2025-02-14T04:15:09.05Z",
"LastModifiedDate": "2025-02-14T17:15:23.09Z"
}
],
"vulnerabilitySummary": {
"totalCount": 14,
"criticalCount": 0,
"highCount": 2,
"mediumCount": 6,
"lowCount": 6,
"unknowsCount": 0
},
"secrets": [
{
"Target": "/tst.txt",
"Category": "GitHub",
"Severity": "CRITICAL",
"Title": "GitHub Personal Access Token",
"StartLine": 4,
"EndLine": 7,
"Content": "\n# Fake GitHub Token\nGITHUB_TOKEN=****************************************1234",
"Description": "Secret(s) found in file system",
"LocationType": "FileSystem"
},
{
"Target": "/tst.txt",
"Category": "Slack",
"Severity": "MEDIUM",
"Title": "Slack Webhook",
"StartLine": 7,
"EndLine": 10,
"Content": "\n# Fake Slack Webhook\nSLACK_WEBHOOK=*****************************************************************************",
"Description": "Secret(s) found in file system",
"LocationType": "FileSystem"
},
{
"Target": "alpine-sec:1.0",
"Category": "GitHub",
"Severity": "CRITICAL",
"Title": "GitHub Personal Access Token",
"StartLine": 45,
"EndLine": 48,
"Content": "\n \"Env\": [\n \"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\",\n \"GITHUB_TOKEN=****************************************6759\"\n ],",
"Description": "Secret(s) found in Environment Variables",
"LocationType": "EnvVar"
}
],
"sboms": {
"spdxVersion": "SPDX-2.3",
"dataLicense": "CC0-1.0",
"SPDXID": "SPDXRef-DOCUMENT",
"name": "alpine-sec",
"documentNamespace": "https://anchore.com/syft/image/alpine-sec-3bf7b6ab-6c74-4153-8346-e2079f0be8ef",
"creationInfo": {
"licenseListVersion": "3.27",
"creators": [
"Organization: Anchore, Inc",
"Tool: syft-1.32.0"
],
"created": "2025-10-01T05:37:33Z"
},
"packages": [
{
"name": "alpine-baselayout",
"SPDXID": "SPDXRef-Package-apk-alpine-baselayout-3eb66fe65cb1f527",
"versionInfo": "3.6.8-r1",
"supplier": "Person: Natanael Copa (ncopa@alpinelinux.org)",
"originator": "Person: Natanael Copa (ncopa@alpinelinux.org)",
"downloadLocation": "https://git.alpinelinux.org/cgit/aports/tree/main/alpine-baselayout",
"filesAnalyzed": true,
"packageVerificationCode": {
"packageVerificationCodeValue": "6a22bff30e2aed347029eeb9d51c810613705455"
},
"sourceInfo": "acquired package info from APK DB: /lib/apk/db/installed",
"licenseConcluded": "NOASSERTION",
"licenseDeclared": "GPL-2.0-only",
"copyrightText": "NOASSERTION",
"description": "Alpine base dir structure and init scripts",
"externalRefs": [
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:a:alpine-baselayout:alpine-baselayout:3.6.8-r1:*:*:*:*:*:*:*"
},
{
"referenceCategory": "SECURITY",
"referenceType": "cpe23Type",
"referenceLocator": "cpe:2.3:a:alpine-baselayout:alpine_baselayout:3.6.8-r1:*:*:*:*:*:*:*"
}
]
}
]
},
"cisScans": {
"image": "alpine-sec:1.0",
"summary": {
"fatal": 1,
"warn": 1,
"info": 2,
"skip": 0,
"pass": 12
},
"details": [
{
"code": "CIS-DI-0010",
"title": "Do not store credential in environment variables/files",
"level": "FATAL",
"alerts": [
"Suspicious ENV key found : GITHUB_TOKEN on /bin/sh -c #(nop) ENV GITHUB_TOKEN=******* (You can suppress it with --accept-key)"
]
},
{
"code": "CIS-DI-0001",
"title": "Create a user for the container",
"level": "WARN",
"alerts": [
"Last user should not be root"
]
},
{
"code": "CIS-DI-0005",
"title": "Enable Content trust for Docker",
"level": "INFO",
"alerts": [
"export DOCKER_CONTENT_TRUST=1 before docker pull/build"
]
},
{
"code": "CIS-DI-0006",
"title": "Add HEALTHCHECK instruction to the container image",
"level": "INFO",
"alerts": [
"not found HEALTHCHECK statement"
]
}
]
},
"storageAnalysis": {
"image_source": "",
"efficiency": 100,
"wasted_bytes": 0,
"wasted_bytes_human": "0 B",
"user_wasted_percent": 0,
"inefficient_files": [],
"results": [
{
"name": "highestUserWastedPercent",
"status": "PASS"
},
{
"name": "lowestEfficiency",
"status": "PASS"
}
]
}
}
You can download/view the full JSON here: resources/sample-json-report.json
Private Registry Authentication
Local Registry Credentials
If you are in your laptop/PC and want to use the conscan at CLI, then to scan an image from private registry, you have to docker login first.
When a container runtime is not present, conscan can still utilize credentials configured in common credential sources (such as ~/.docker/config.json). It will pull images from private registries using these credentials.
The common syntax of docker login command is:
$ docker login registry.example.com --user your-user --password superSecret
- If you have token, you can use that as password, eg to login dockerhub using token, you can use following command:
$ echo $DOCKER_TOKEN | podman login docker.io -u your-user --password-stdin
- Same way for Github Registry:
$ echo $CR_PAT | docker login ghcr.io -u your-user --password-stdin
- For AWS ECR, you have to get login password first and use that at
docker login, eg
$ aws ecr get-login-password --region $AWS_REGION \
| docker login --username AWS \
--password-stdin 123456.dkr.ecr.$AWS_REGION.amazonaws.com
Note: The above aws command will work provided that you have the proper aws configuration settings for aws cli. One easy option is to use environment variables like:
$ export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
$ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ export AWS_REGION=us-west-2
There are other options, see more details at Configuring settings for the AWS CLI page.
For all the private registry while using
conscanto scan image use the Full Image path with registry, eg:
$ conscan scan registry.example.com/your-image:tag
Registry Credentials in Container
If you want to use docker/podman to scan some images from private registry, the easy way would be to mount the docker config.json into container.
For example, in some linux machine:
$ docker run --rm -it \
-v ./cache:/.cache \
-v ~/.docker/config.json:/.docker/config.json \
--name conscan ghcr.io/peek8/conscan:latest \
scan registry.example.com/your-image:tag
At Mac, the docker config.json file might be at path ~/.config/containers/auth.json, in that case it would be:
$ docker run --rm -it \
-v ./cache:/.cache \
-v ~/.config/containers/auth.json:/.docker/config.json \
--name conscan ghcr.io/peek8/conscan:latest \
scan registry.example.com/your-image:tag
Registry Credentials in Kubernetes
Use Simple Secret
You can create a Secret using the above mentioned ~/.docker/config.json. And mount that secret in the conscan container.
- Create secret
secret.yamlusing config.json
apiVersion: v1
kind: Secret
metadata:
name: registry-config
namespace: awesomeapp
data:
config.json: <base64 encoded config.json>
Apply it:
$ kubectl apply -f secret.yaml
- Create your pod running conscan. The
config.jsonfile needs to be mounted at/.docker/config.json. here's pod.yaml:
apiVersion: v1
kind: Pod
spec:
containers:
- image: ghcr.io/peek8/conscan:latest
name: conscan-private-registry
volumeMounts:
- mountPath: /.docker
name: registry-config
readOnly: true
args:
- scan
- "registry.example.com/your-image:tag"
volumes:
- name: registry-config
secret:
secretName: registry-config
- Apply pod.yaml
$ kubectl apply -f pod.yaml
Note: At
argssection of pod, you can add--formatto get different formats than table eg.--format jsonfor json format. And to save it to a file use --output.
Use Image Pull Secret
You can also create secret of type kubernetes.io/dockerconfigjson that can be used in Pod at imagePullSecrets field.
The secret would be in format:
apiVersion: v1
kind: Secret
metadata:
name: regcred
namespace: awesomeapps
data:
.dockerconfigjson: UmVhbGx5IHJlYWxseSByZWVlZWVlZWVlZWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGxsbGx5eXl5eXl5eXl5eXl5eXl5eXl5eSBsbGxsbGxsbGxsbGxsbG9vb29vb29vb29vb29vb29vb29vb29vb29vb25ubm5ubm5ubm5ubm5ubm5ubm5ubm5ubmdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2cgYXV0aCBrZXlzCg==
type: kubernetes.io/dockerconfigjson
And the pod.yaml will be like:
apiVersion: v1
kind: Pod
metadata:
name: private-reg
spec:
containers:
- image: ghcr.io/peek8/conscan:latest
name: conscan-private-registry
imagePullSecrets:
- name: regcred
See more about using imagePullSecrets at Kubernetes Private Registry doc
Integration with CI/CD
Integrating With Github Action
We can easily use this conscan tool with github action. With the github action the html reports are generated and these reports can be published directly to GitHub Pages using GitHub Actions, no manual steps required π.
This makes it super easy to:
β Automate container image vulnerability scans
β Host & share scan results securely
β Integrate with your CI/CD workflow seamlessly
Hereβs a quick example of how it works:
- GitHub Actions runs on each push
- conscan scans all container images
- Reports are committed and published automatically to the eg. gh-pages branch
- Accessible instantly at GitHub Pages link.
I have setup a repo named conscan-sample where I am building some sample images (those images doesn't have any real use case, just for testing) and scanning them with conscan tool and publish the report to github pages.
For more details, See the scan Job and update-index Job at the github action workflows at conscan-sample repo.
The generate reports are available at Conscan-sample Github Pages.
Isn't it awesome, You can actually do a lot without doing almost anything (other than copy/paste) :).
Cheers, Happy Scanning !! π π
π License
- Apache 2.0, see more details at LICENSE File.
Community
Conscan is a Peek8 open source project. Learn about our open source work and portfolio here. If you want to collaborate with us or Invest at Peek8, please contact us here.
Issues and PRs are most welcome! Whether it's docs, code improvement, or examples β contributions help the community.
Last but not the least, If you like Conscan and it helps you to have better container security, consider giving the repository a β. Your support helps build a better security first k8s ecosystem.
Directories
ΒΆ
| Path | Synopsis |
|---|---|
|
Package cmd contains all the commands and subcommands to scan the image
|
Package cmd contains all the commands and subcommands to scan the image |
|
pkg
|
|
|
log
Package log contains log related functionalities
|
Package log contains log related functionalities |
|
models
Package models provide the struct for the data
|
Package models provide the struct for the data |
|
report
Package report provides staff for report generation
|
Package report provides staff for report generation |
|
report/aggregator
Package aggregator aggregates different types of report
|
Package aggregator aggregates different types of report |
|
scanner
Package scanner scans the container image for vulnerabilities, securities, licenses etc
|
Package scanner scans the container image for vulnerabilities, securities, licenses etc |
|
utils
Package utils contains utils function
|
Package utils contains utils function |