# Helm Chart & Kubernetes Security Monitor (Artifact Hub) (`sashaebashu/k8s-package-security`) Actor

Audit the Helm charts and Kubernetes packages you run: container-image CVEs, signatures, deprecation and abandonment — plus an alert when a chart you already trust picks up a critical vulnerability or loses its signature.

- **URL**: https://apify.com/sashaebashu/k8s-package-security.md
- **Developed by:** [Sasha Ebashu](https://apify.com/sashaebashu) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Helm Chart & Kubernetes Package Security Monitor

Your cluster will not tell you that the chart you installed last year was deprecated in March, lost
its signature in June, and now ships images with seven critical CVEs. Artifact Hub knows all three.
This Actor reads it for the packages **you** run, scores the exposure, and tells you when it moves.

Built on Artifact Hub's public API. No key, no login, no scraping.

### The four ways a chart goes bad quietly

| Signal | Why it matters |
|---|---|
| **Container CVEs** | Artifact Hub runs Trivy against the images a chart deploys. `vulnerabilitiesCritical` / `High` / `Medium` are those counts, and `vulnerabilities` lists the CVEs with **the version that fixes each one**. |
| **Deprecation** | A deprecated chart never errors. It simply stops receiving fixes. 381 of the 18,056 Helm charts on the Hub are deprecated. |
| **Signature** | `signed` and `signatures` (cosign, prov). Of the 60 most-starred charts, only 30 are signed. A chart that *loses* its signature is its own alert. |
| **Abandonment** | `daysSinceRelease` and `isStale`. One chart in the top 60 last shipped 1,365 days ago. |

### Read this before you trust a zero

**"Not scanned" is not "no vulnerabilities."** 28 of the 100 most-starred Helm charts have no
security report at all. Reporting `0 critical` for those would tell you an unexamined chart is
clean, so this Actor does not: counts stay **`null`**, `securityScanned` is `false`, and
`securityScanReason` says which of the three causes applies — never scanned, the publisher disabled
scanning, or **every image was excluded from scanning by the publisher** (an all-zero report that
examined nothing).

An unscanned package therefore scores *worse* than a scanned clean one. Unknown is exposure.

### Risk scoring, and why you can defend the number

The formula is published so you can argue with it, or recompute it with your own weights.

| Signal | Points |
|---|---|
| Critical vulnerabilities | 12 each, capped at 40 |
| High vulnerabilities | 2 each, capped at 25 |
| Medium vulnerabilities | 0.5 each, capped at 8 |
| **Never scanned** | 18 |
| Marked deprecated | 25 |
| Not signed | 8 |
| Publisher not verified | 5 |
| No release in over a year | 15 (7 past half that) |
| Last scan older than 30 days | 5 |

`low` under 20 · `medium` 20–49 · `high` 50–79 · `critical` 80+. `riskReasons` spells out every
component that fired.

**A high score is not an accusation.** A chart deploying seven images has more surface than one
deploying a single distroless binary, and a busy chart with an old base image is not necessarily
exploitable in your configuration. The score measures exposure; you supply the judgement.

### Monitor mode

Run it on a schedule against the charts you deploy. The second run labels what moved:

| `changeType` | Means |
|---|---|
| **`deprecated`** | The publisher just marked it deprecated — plan a migration |
| **`new_critical_vulnerabilities`** | Critical count went up, with `criticalDelta` |
| `signature_lost` | It was signed before and is not now |
| `security_scan_lost` | It had a report and no longer does — you have gone blind, not clean |
| `vulnerabilities_up` | High-severity count rose |
| `new_version` | An upgrade is available |
| `vulnerabilities_down` / `security_scan_added` | It got better |
| **`no_longer_listed`** | Withdrawn from Artifact Hub entirely |
| `left_your_filter` | Still published, but no longer matches this run's filters |
| `new`, `unchanged` | First sighting / nothing moved |

Two things this deliberately does **not** do:

- It will not report a package as gone unless the sweep actually covered your whole scope. If a
  request failed, or the run stopped at your own `maxItems`, disappearances are suppressed —
  a partial sweep must never announce everything it did not reach as deleted.
- It will not call a vulnerability count "improved" when the comparison is meaningless. A chart
  that was scanned last week and is unscanned today has not dropped to zero, and is reported as
  `security_scan_lost` instead.

### Input

| Field | What it does |
|---|---|
| **Packages** | What you actually run. An Artifact Hub URL, `kind/repository/name`, or `repository/name` (assumed Helm). |
| **Search query / Kind / Repositories / Organizations** | Discover packages instead of naming them. |
| **Only deprecated / unsigned / with vulnerabilities / never scanned** | Audit filters. They run *inside* the sweep, so asking for 20 deprecated charts keeps paging until it has 20 — not until it has read 20 charts. |
| **Include the vulnerability list** | Full Trivy detail per package: CVE, affected package, installed version, fixed version, CVSS. One extra request per scanned package. |
| **Include release history** | Total releases, previous version, releases in the last 90 days, first release date. |
| **Treat as abandoned after (days)** | Your definition of stale, not a hidden constant. |
| **Monitor mode / Output only changes** | The alert behaviour above. |

### Notes & limits

Properties of the source, measured rather than assumed:

- **`deprecated=true` on the Hub's search API means "include deprecated", not "only deprecated".**
  With it set, a Helm search returns 18,056 rows instead of 17,675 — the extra 381 are the
  deprecated ones. Reading that total as the deprecated count would be wrong by 47×. This Actor
  filters deprecation itself.
- **The kind of a package lives on its repository, not on the package**, in search results. A
  reader that only looks at the package object gets no kind, and therefore no link.
- **The package detail endpoint returns `stars: null`** even for a package with hundreds of stars —
  only search carries them. Packages you name by hand therefore have unknown stars (reported as
  `null`, and a minimum-stars filter will not silently delete them); `subscriptions` is populated
  there instead.
- **Artifact Hub is behind a burst limiter** that answers 429 with an empty body and no
  `Retry-After`. The Actor backs off and retries; four parallel requests is comfortable.
- Search pages cap at 60 results, and the end of a listing is an **empty** page — never a short one.
- The Actor honours your **Maximum cost per run** and stops when the cap is reached.

# Actor input Schema

## `packages` (type: `array`):

The charts and packages you actually run. Paste an Artifact Hub URL, a `kind/repository/name` path, or just `repository/name` (assumed to be a Helm chart). Leave empty to discover packages with the fields below instead.

## `searchQuery` (type: `string`):

Find packages by keyword instead of naming them, for example `postgres` or `ingress`.

## `kind` (type: `string`):

Restrict discovery to one kind of package. Helm charts are 17,675 of the ~21,400 packages on Artifact Hub.

## `repositories` (type: `array`):

Limit discovery to these Artifact Hub repositories, for example `bitnami` or `prometheus-community`.

## `organizations` (type: `array`):

Limit discovery to these publishing organizations.

## `sort` (type: `string`):

How Artifact Hub orders the packages it returns.

## `onlyDeprecated` (type: `boolean`):

Keep only packages the publisher has marked deprecated — the ones that will quietly stop receiving fixes.

## `onlyUnsigned` (type: `boolean`):

Keep only packages with no signature, so you cannot verify who built what you deploy.

## `onlyWithVulnerabilities` (type: `boolean`):

Keep only packages whose scan actually found critical or high issues. Never-scanned packages are excluded, because an absent scan is not a finding.

## `onlyUnscanned` (type: `boolean`):

Keep only packages with no security report at all — the blind spots in your supply chain.

## `minStars` (type: `integer`):

Skip packages below this many Artifact Hub stars. Useful for auditing only what has real adoption.

## `staleAfterDays` (type: `integer`):

How many days without a release make a package count as stale. Drives `isStale` and part of the risk score.

## `includeVulnerabilities` (type: `boolean`):

Download the full Trivy report per package: CVE ids, affected package, installed version and the version that fixes it. Adds one large request per scanned package.

## `minVulnerabilitySeverity` (type: `string`):

Only used when the vulnerability list is on. Severity counts in the summary always cover everything.

## `maxVulnerabilitiesPerPackage` (type: `integer`):

Caps the list so one badly-aged image cannot produce a thousand-row record. Sorted worst first, so the cap keeps what matters.

## `includeReleaseHistory` (type: `boolean`):

Adds total releases, the previous version, the first release date and the release count over the last 90 days. One extra request per discovered package; packages you name by hand get it for free.

## `maxItems` (type: `integer`):

Stop after this many packages.

## `maxConcurrency` (type: `integer`):

Artifact Hub sits behind a burst limiter; 4 is comfortable. Raising this makes rate-limit pauses more likely, not the run faster.

## `monitorMode` (type: `boolean`):

Compare against the previous run of the same scope and label what changed: new critical CVEs, deprecation, a lost signature, a new version.

## `onlyChanges` (type: `boolean`):

With monitor mode on, skip packages where nothing moved — an empty run then means nothing needs your attention.

## Actor input object example

```json
{
  "packages": [
    "https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack",
    "argo/argo-cd",
    "ingress-nginx/ingress-nginx"
  ],
  "kind": "",
  "sort": "relevance",
  "onlyDeprecated": false,
  "onlyUnsigned": false,
  "onlyWithVulnerabilities": false,
  "onlyUnscanned": false,
  "minStars": 0,
  "staleAfterDays": 365,
  "includeVulnerabilities": false,
  "minVulnerabilitySeverity": "high",
  "maxVulnerabilitiesPerPackage": 25,
  "includeReleaseHistory": false,
  "maxItems": 100,
  "maxConcurrency": 4,
  "monitorMode": false,
  "onlyChanges": false
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `alerts` (type: `string`):

No description

## `security` (type: `string`):

No description

## `full` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "packages": [
        "https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack",
        "argo/argo-cd",
        "ingress-nginx/ingress-nginx"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("sashaebashu/k8s-package-security").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "packages": [
        "https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack",
        "argo/argo-cd",
        "ingress-nginx/ingress-nginx",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("sashaebashu/k8s-package-security").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "packages": [
    "https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack",
    "argo/argo-cd",
    "ingress-nginx/ingress-nginx"
  ]
}' |
apify call sashaebashu/k8s-package-security --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,sashaebashu/k8s-package-security"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/7NHRlJ57391CsqsBn/builds/CnUsmcPNsUEeHQSWy/openapi.json
