Through the Looking Glass: A Caucus-Race and a Long Tale
This is post 2 in a seven-part series on what the Anchore Enterprise API makes possible for container security teams.
Who Are You? Why SBOMs Matter
The Caterpillar’s first question to Alice was “Who are you?” — and Alice, who had changed size three times that morning, struggled to answer. When a Critical CVE drops, that’s exactly the question a security team needs to answer about every container image in their fleet: who is in here, at what version, where. Without a reliable inventory, the answer comes from guessing, grepping, or manually inspecting images one at a time. With a complete Software Bill of Materials (SBOM), it becomes a query.
An SBOM is exactly what it sounds like — a complete, structured inventory of every component that makes up a piece of software. For container images, that means every OS package, every language-ecosystem library, every binary, every framework — regardless of how it got there or which layer of the image it lives in. It’s the answer to the Caterpillar’s question, for software.
Beyond vulnerability response, SBOMs underpin license compliance, supply chain attestation, regulatory requirements, and informed conversations with development teams about what they’re actually shipping. The value compounds quickly once you have SBOM data that’s accurate, current, and — critically — accessible programmatically.
Anchore Enterprise generates a detailed SBOM for every container image it analyzes, covering more than 35 package ecosystems. All of that data is available through the API, which means it can flow into whatever workflows, tooling, and processes your organization already relies on.
Retrieving SBOM Content
Like the Mouse’s Long Tale, the list of ecosystems Anchore understands runs on and on — but you query them all through the same shape of request. The /images/{imageDigest}/content endpoint returns the package inventory for an analyzed image, scoped to a single ecosystem at a time. Retrieving OS packages with curl looks like this:
curl -s -u _api_key:<your-api-key>
"https://wonderland.example.com/v2/images/sha256:<digest>/content/os" \
| jq .Substitute the content type for any of the ecosystems Anchore understands — OS packages, Java, Python, Go, npm, NuGet, Ruby gems, Rust crates, and many more, along with non-package content types like files (a complete file inventory, useful for secret detection and configuration auditing) and malware. A single image digest can yield package data spanning dozens of ecosystems, all queryable through the same endpoint pattern. For the canonical list of supported content types, see the Anchore content-types reference in the official documentation.
Exporting SBOMs in Standard Formats
Alice spent her time in Wonderland trying bottles labelled Drink Me and cakes labelled Eat Me — same Alice, different containers, different effects. Downstream consumers of SBOM data are similar: same image inventory, different format expected. If you need to share SBOM data with external tools, auditors, or downstream systems, Anchore can pour the same SBOM into whichever bottle the asker brought — three industry-standard formats, directly from the API.
SPDX JSON — widely used for license compliance and supply chain documentation:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/sboms/spdx-json" \
| jq . CycloneDX JSON — commonly used for vulnerability exchange and DevSecOps toolchains:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/sboms/cyclonedx-json" \
| jq . Native Anchore JSON — the full internal representation, useful when you want every detail Anchore captured:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/sboms/native-json" \
| jq . Having these available programmatically means generating and delivering a compliance artifact for any image in your fleet is a one-liner — no manual export step required.
License Data
The Queen’s gardeners spent Chapter 8 hastily painting white roses red because they’d planted the wrong colour and an audit was imminent. License compliance is the equivalent in container land — much easier to catch a copyleft component before it ships than to repaint the roses after the fact. Anchore surfaces per-package license information through a dedicated endpoint:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/content/licenses" \
| jq . The response includes each package alongside its identified license or licenses. With that data in hand you can build automated checks for licenses incompatible with your distribution model, flag images that include packages under copyleft licenses, or generate license manifests for your legal and compliance teams — all without manual inspection.
Vulnerability Data
The /images/{imageDigest}/vuln/{vuln_type} endpoint returns vulnerability findings for an analyzed image. To retrieve all vulnerabilities:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/vuln/all" \
| jq . You can narrow the scope by replacing all with os or non-os depending on what you’re interested in. The response includes CVE IDs, severity ratings, affected package names and versions, and fix information where available.
With vulnerability data accessible via the API you can do things the UI isn’t designed for: correlate findings across your entire image fleet, track which vulnerabilities have fixes available and which don’t, filter by severity thresholds, or extract the data into whatever system your security team uses to manage their work.
In Python, pulling Critical and High findings for an image looks like this:
import requests
ANCHORE_URL = "https://wonderland.example.com/v2"
AUTH = ("_api_key", "<your-api-key>")
def get_vulnerabilities(digest, min_severity=("Critical", "High")):
resp = requests.get(
f"{ANCHORE_URL}/images/{digest}/vuln/all",
auth=AUTH,
)
resp.raise_for_status()
vulns = resp.json().get("vulnerabilities", [])
return [v for v in vulns if v.get("severity") in min_severity]
digest = "sha256:<digest>"
findings = get_vulnerabilities(digest)
for v in findings:
print(
f"{v['vuln']} | {v['severity']} | "
f"{v['package']} {v['package_version']} | "
f"fix: {v.get('fix', 'none')}"
)
Policy Evaluation
Vulnerability data tells you what’s present in an image. Policy evaluation tells you whether that image meets your organization’s standards — three possible verdicts (go, warn, or stop), which in the Queen of Hearts’ more direct vocabulary translate to “carry on,” “be careful,” and “off with its head.” The /images/{imageDigest}/check endpoint returns the current policy evaluation result:
curl -s -u _api_key:<your-api-key> \
"https://wonderland.example.com/v2/images/sha256:<digest>/check?tag=docker.io/wonderland/whiterabbit-api:latest" \
| jq . Note that tag is a required parameter — it determines which tag context to use for the policy evaluation. The response is a PolicyEvaluation object containing an evaluations array, each entry of which includes the final_action (go, warn, or stop), the overall status (pass or fail), and a details object with the full list of individual findings.
In Python, checking evaluation status and extracting stop-level findings looks like this:
import requests
ANCHORE_URL = "https://wonderland.example.com/v2"
AUTH = ("_api_key", "<your-api-key>")
def get_policy_evaluation(digest, tag):
resp = requests.get(
f"{ANCHORE_URL}/images/{digest}/check",
params={"tag": tag, "detail": True},
auth=AUTH,
)
resp.raise_for_status()
return resp.json()
def parse_evaluation(result):
tag = result.get("evaluated_tag")
evaluations = result.get("evaluations", [])
if not evaluations:
return tag, None, []
evaluation = evaluations[0]
final_action = evaluation.get("final_action")
findings = evaluation.get("details", {}).get("findings", [])
stops = [f for f in findings if f.get("action") == "stop"]
return tag, final_action, stops
digest = "sha256:<digest>"
tag = "docker.io/wonderland/whiterabbit-api:latest"
result = get_policy_evaluation(digest, tag)
tag, final_action, stops = parse_evaluation(result)
print(f"{tag}: {final_action}")
for f in stops:
print(
f" STOP — gate: {f['gate']}, "
f"trigger: {f['trigger_id']}, "
f"message: {f['message']}"
) Putting It Together: A Caucus-Race Across the Fleet
Lewis Carroll never explains the rules of the Dodo’s Caucus-Race because there aren’t any — the runners just go in a circle until the Dodo decides they’re done, and at the end everyone gets a prize. The script below works on the same principle: it iterates over every image in your fleet, cross-references the package inventory against a watch list of internal libraries, and summarises the vulnerability posture and policy status for each image. One pass through the API, every image accounted for, no one left out.
import requests
from collections import defaultdict
ANCHORE_URL = "https://wonderland.example.com/v2"
AUTH = ("_api_key", "<your-api-key>")
TRACKED_PACKAGES = {"internal-auth-lib", "legacy-crypto-util"}
ECOSYSTEMS = [
"os", "files", "java", "python", "go", "npm", "nuget", "gem",
"rust-crate", "hex", "dart-pub", "php-composer", "php-pear", "php-pecl",
"swift", "pod", "homebrew", "conan", "conda", "terraform",
"linux-kernel", "linux-kernel-module", "github-action",
"github-action-workflow", "wordpress-plugin", "lua-rocks",
"graalvm-native-image", "erlang-otp", "hackage", "r-package",
"opam", "swiplpack", "bitnami", "binary", "malware",
]
def get_images():
resp = requests.get(f"{ANCHORE_URL}/images", auth=AUTH)
resp.raise_for_status()
return resp.json()
def get_content(digest, content_type):
resp = requests.get(
f"{ANCHORE_URL}/images/{digest}/content/{content_type}",
auth=AUTH,
)
resp.raise_for_status()
return resp.json().get("content", [])
def get_vuln_summary(digest):
resp = requests.get(
f"{ANCHORE_URL}/images/{digest}/vuln/all", auth=AUTH
)
resp.raise_for_status()
vulns = resp.json().get("vulnerabilities", [])
counts = defaultdict(int)
for v in vulns:
counts[v.get("severity", "Unknown")] += 1
return counts
def get_policy_status(digest, tag):
resp = requests.get(
f"{ANCHORE_URL}/images/{digest}/check",
params={"tag": tag},
auth=AUTH,
)
resp.raise_for_status()
evaluations = resp.json().get("evaluations", [])
if evaluations:
return evaluations[0].get("final_action", "unknown")
return "unknown"
def analyze_image(image):
digest = image["imageDigest"]
tag = image["image_detail"][0].get("fulltag", digest[:20])
flagged = []
for content_type in ECOSYSTEMS:
try:
for pkg in get_content(digest, content_type):
if pkg.get("package") in TRACKED_PACKAGES:
flagged.append(
f"{pkg['package']} ({pkg.get('version')})"
)
except requests.HTTPError:
pass
vuln_counts = get_vuln_summary(digest)
policy_status = get_policy_status(digest, tag)
return tag, flagged, vuln_counts, policy_status
def main():
for image in get_images():
tag, flagged, vulns, policy = analyze_image(image)
print(f"\n{tag}")
print(f" Policy: {policy}")
print(
f" Critical: {vulns.get('Critical', 0)} "
f"High: {vulns.get('High', 0)} "
f"Medium: {vulns.get('Medium', 0)}"
)
print(f" Tracked: {', '.join(flagged) if flagged else '—'}")
if __name__ == "__main__":
main() This is the kind of fleet-wide visibility that benefits teams managing container images at scale — knowing not just what’s in each image, but how it’s affected by current vulnerabilities and whether it meets policy, all from a single pass through the API. No winners, no losers, every image accounted for.
Up Next
The Hatter’s table is already set. Next in the series: A Mad Tea-Party — Event-Driven Workflows with Anchore Notifications. We’ll shift from querying SBOM and vulnerability data to reacting to it — configuring webhooks to fire on security events, building receivers that connect Anchore’s event stream to Slack, Jira, or your own internal tooling, and closing the loop between detection and action.
If you’re an Anchore Enterprise customer looking to build with the API, the Customer Success team is the fastest way to get unblocked — reach out through the Anchore Support Portal. If you’re not a customer yet but want to see what any of this looks like against your own container images, request a demo and we’ll walk you through it.