Through the Looking Glass: Humpty Dumpty
This is post 4 in a seven-part series on what the Anchore Enterprise API makes possible for container security teams.
“When I use a word,” Humpty Dumpty said in rather a scornful tone, “it means just what I choose it to mean — neither more nor less.” Anchore Enterprise’s GraphQL subsystem takes the same approach to data: you define exactly what you want back, in exactly the shape you want it, without over-fetching or stringing together multiple REST calls to assemble the picture yourself.
The REST endpoints we’ve explored in earlier posts are ideal for targeted, per-image operations. GraphQL is the right tool when you need to ask broader questions across your container fleet — things like “which images have Critical vulnerabilities and which runtime containers are they running in?” or “what is the policy compliance posture across all of my registered tags?” These are questions that would require multiple REST calls, response aggregation, and client-side filtering. With GraphQL, they become a single query.
Pick Your Wall: The Reporting Endpoints
Humpty Dumpty had one wall; you get two. Anchore Enterprise exposes two GraphQL endpoints depending on the scope you need.
https://<anchore-host>/v2/reports/graphqlThis endpoint is scoped to the authenticated user’s account. The x-anchore-account header is required:
curl -s -u _api_key:<your-api-key> \
-X POST "https://wonderland.example.com/v2/reports/graphql" \
-H "Content-Type: application/json" \
-H "x-anchore-account: mad-hatter-team" \
-d '{"query": "{ __typename }"}'
https://<anchore-host>/v2/reports/global/graphqlThis endpoint requires administrator access and returns data across all accounts. No account header required:
curl -s -u _api_key:<your-api-key> \
-X POST "https://wonderland.example.com/v2/reports/global/graphql" \
-H "Content-Type: application/json" \
-d '{"query": "{ __typename }"}'Glory For You: Exploring the Schema
“There’s glory for you!” Humpty Dumpty told Alice. “I don’t know what you mean by ‘glory,’” Alice replied. “Of course you don’t — till I tell you.” Before writing queries, let GraphQL tell you. Introspection returns the full list of available query types, filter inputs, and response fields directly from your deployment:
curl -s -u _api_key:<your-api-key> \
-X POST "https://wonderland.example.com/v2/reports/graphql" \
-H "Content-Type: application/json" \
-H "x-anchore-account: mad-hatter-team" \
-d '{
"query": "{ __schema { queryType { fields { name description args { name type { name kind ofType { name kind } } } } } } }"
}' | jq .You can also import the endpoint into a tool like Insomnia or GraphQL Playground — both support introspection natively and give you an interactive schema explorer and query builder. The full list of available query types is summarised below. All queries accept limit, nextToken, and a typed filter object.
| Query | Description |
| imagesByVulnerability | Unique vulnerabilities and the images affected |
| tagsByVulnerability | Unique vulnerabilities and affected tags (hierarchical) |
| artifactsByVulnerability | Unique vulnerabilities and affected artifacts |
| runtimeInventoryImagesByVulnerability | Vulnerabilities in runtime inventory images |
| kubernetesRuntimeVulnerabilitiesByNamespace | Vulnerabilities by Kubernetes namespace |
| vulnerabilitiesByKubernetesContainer | Vulnerabilities by Kubernetes container |
| vulnerabilitiesByEcsContainer | Vulnerabilities by ECS container |
| runtimeInventoryUnscannedImages | Runtime inventory images not yet analyzed |
| policyEvaluationsByTag | Policy evaluations for tags (hierarchical) |
| policyEvaluationsByRuntimeInventoryImage | Policy evaluations for runtime inventory images |
| imagesWithStig | Images with STIG compliance information |
| runtimeImagesWithStig | Runtime images with STIG compliance information |
| metrics | Available metrics in the system |
| metricData | Metric data points, chronologically descending |
| scheduledQueries | Configured scheduled queries |
| scheduledQueryExecutions | Executions for a given scheduled query |
Putting Humpty Back Together: Cross-Image Vulnerability Summaries
All the King’s horses and all the King’s men couldn’t put Humpty together again. A single GraphQL query can. Where REST would need a series of round trips — list images, fetch vulnerabilities per image, fetch tags per image, join client-side — imagesByVulnerability returns a list of unique vulnerabilities alongside every container image affected by each one, in one response.
Filtering is done via nested filter objects — severity filtering lives under vulnerability, with separate filter objects available for artifact, registry, repository, tag, and image. Note that severity values are GraphQL enum literals and are written without quotes.
{
imagesByVulnerability(
limit: 500
filter: { vulnerability: { severity: Critical } }
) {
pageInfo { nextToken count }
results {
vulnerabilityId
cve
imagesCount
images {
digest
distro
tags {
registryName
repositoryName
tagName
current
}
artifacts {
artifactName
artifactVersion
artifactType
severity
fixedIn
isKev
epssScore
}
}
}
}
}
To filter on multiple severities, use severities instead of severity:
filter: { vulnerability: { severities: [Critical, High] } }In Python, with pagination handled via pageInfo.nextToken:
import requests
ANCHORE_URL = "https://wonderland.example.com/v2/reports/graphql"
AUTH = ("_api_key", "<your-api-key>")
HEADERS = {"x-anchore-account": "mad-hatter-team"}
VULN_QUERY = """
query($nextToken: String) {
imagesByVulnerability(
limit: 500
nextToken: $nextToken
filter: { vulnerability: { severity: Critical } }
) {
pageInfo { nextToken count }
results {
vulnerabilityId
cve
imagesCount
images {
digest
distro
tags {
registryName
repositoryName
tagName
current
}
artifacts {
artifactName
artifactVersion
artifactType
severity
fixedIn
isKev
epssScore
}
}
}
}
}
"""
def get_images_by_vulnerability():
results = []
next_token = None
while True:
resp = requests.post(
ANCHORE_URL,
json={
"query": VULN_QUERY,
"variables": {"nextToken": next_token},
},
auth=AUTH,
headers=HEADERS,
)
resp.raise_for_status()
data = resp.json()["data"]["imagesByVulnerability"]
results.extend(data.get("results", []))
next_token = data.get("pageInfo", {}).get("nextToken")
if not next_token:
break
return results
def main():
findings = get_images_by_vulnerability()
print(
f"Critical vulnerabilities affecting images: "
f"{len(findings)}\n"
)
for f in findings:
cve = f.get("cve") or f.get("vulnerabilityId")
print(f"{cve} — {f.get('imagesCount')} image(s)")
for img in f.get("images", []):
for tag in img.get("tags", []):
if tag.get("current"):
print(
f" {tag['registryName']}/"
f"{tag['repositoryName']}:{tag['tagName']}"
)
for artifact in img.get("artifacts", []):
fix = artifact.get("fixedIn")
fix_str = f"fix: {fix}" if fix else "no fix"
kev_str = " [KEV]" if artifact.get("isKev") else ""
print(
f" {artifact['artifactName']} "
f"{artifact['artifactVersion']} "
f"— {fix_str}{kev_str}"
)
if __name__ == "__main__":
main()From the Wall: Policy Compliance Across the Fleet
What the cross-image vulnerability query did for CVE exposure, policyEvaluationsByTag does for compliance — give you the whole fleet’s view from one vantage point. It returns policy evaluations in a hierarchical view: registry → repository → tag → evaluations. Each tag may have multiple evaluation records; use the latest field to identify the most current result.
{
policyEvaluationsByTag(limit: 1000) {
pageInfo { nextToken count }
results {
registryName
repositoriesCount
tagsCount
account
repositories {
repositoryName
tagsCount
tags {
tagName
imageDigest
current
evaluations {
result
reason
lastEvaluatedAt
latest
}
}
}
}
}
}
In Python, flattening the hierarchy to produce a compliance summary:
import requests
from collections import Counter
ANCHORE_URL = "https://wonderland.example.com/v2/reports/graphql"
AUTH = ("_api_key", "<your-api-key>")
HEADERS = {"x-anchore-account": "mad-hatter-team"}
POLICY_QUERY = """
query($nextToken: String) {
policyEvaluationsByTag(limit: 1000, nextToken: $nextToken) {
pageInfo { nextToken count }
results {
registryName
account
repositories {
repositoryName
tags {
tagName
imageDigest
current
evaluations {
result
reason
lastEvaluatedAt
latest
}
}
}
}
}
}
"""
def get_policy_evaluations():
all_registries = []
next_token = None
while True:
resp = requests.post(
ANCHORE_URL,
json={
"query": POLICY_QUERY,
"variables": {"nextToken": next_token},
}, auth=AUTH,
headers=HEADERS,
)
resp.raise_for_status()
data = resp.json()["data"]["policyEvaluationsByTag"]
all_registries.extend(data.get("results", []))
next_token = data.get("pageInfo", {}).get("nextToken")
if not next_token:
break
return all_registries
<br>def flatten_latest_evaluations(registries):
flat = []
for registry in registries:
for repo in registry.get("repositories", []):
for tag in repo.get("tags", []):
for evaluation in tag.get("evaluations", []):
if evaluation.get("latest"):
flat.append({<br> "full_tag": (
f"{registry['registryName']}/"
f"{repo['repositoryName']}:"
f"{tag['tagName']}" ),
"image_digest": tag.get("imageDigest"),
"result": evaluation.get("result"),
"reason": evaluation.get("reason"),
"last_evaluated_at": evaluation.get(
"lastEvaluatedAt"
),
})
return flat
def main():
registries = get_policy_evaluations()
evaluations = flatten_latest_evaluations(registries)
counts = Counter(e["result"] for e in evaluations)
print("Fleet Policy Compliance Summary")
print(f" Total tags evaluated: {len(evaluations)}")
print(f" Pass: {counts.get('Pass', 0)}")
print(f" Fail: {counts.get('Fail', 0)}")
print()
failing = [e for e in evaluations if e["result"] == "Fail"]
if failing:
print("Failing tags:")
for e in failing:
print(
f" {e['full_tag']} "
f"(evaluated: {e['last_evaluated_at']})"
)
if __name__ == "__main__":
main()For administrator access across all accounts, swap the endpoint and remove the account header:
ANCHORE_URL = "https://wonderland.example.com/v2/reports/global/graphql"
HEADERS = {}Beyond the Wall: Runtime Inventory Queries
Sitting on the wall, Humpty Dumpty had a view of the static landscape. Runtime inventory queries let you see what’s actually moving on the other side. One of the more distinctive aspects of this GraphQL schema is its deep integration with runtime inventory. runtimeInventoryImagesByVulnerability combines vulnerability data with live Kubernetes and ECS inventory, answering not just “which container images are vulnerable?” but “which vulnerable images are currently running in my clusters?”
{
runtimeInventoryImagesByVulnerability(
limit: 500
filter: { vulnerability: { severity: Critical } }
) {
pageInfo { nextToken count }
results {
vulnerabilityId
cve
imagesCount
images {
digest
tags {
registryName
repositoryName
tagName
current
}
artifacts {
artifactName
artifactVersion
severity
fixedIn
isKev
}
}
}
}
}Use introspection to explore kubernetesRuntimeVulnerabilitiesByNamespace and vulnerabilitiesByKubernetesContainer for even more granular Kubernetes-scoped views.
Up Next
The GraphQL reporting interface gives you a query language designed around the questions container security and platform teams actually ask — not individual image lookups, but fleet-wide views of vulnerability exposure, policy compliance, and runtime context. The typed filter inputs and paginated responses are consistent across all query types, so once you’re comfortable with the pattern, exploring the rest of the schema is straightforward.
Next in the series: Who Stole the Tarts? — Chasing the Cheshire Cat Through Zero-Day Vulnerabilities. When a new CVE drops, the question changes from “what does our fleet look like?” to “are we on fire?” — and you need an answer in minutes, not hours. We’ll use the /query/vulnerabilities and /query/images/by-package endpoints to rapidly assess blast radius the moment a vulnerability is disclosed.
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.