Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions .github/workflows/deploy-cms.yml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ jobs:
BREVO_API_KEY: ${{ secrets.BREVO_API_KEY }}
GOOGLE_APPLICATION_CREDENTIALS_JSON: ${{ secrets.GOOGLE_APPLICATION_CREDENTIALS_JSON }}
CRUX_API_KEY: ${{ secrets.CRUX_API_KEY }}
YOUTUBE_API_KEY: ${{ secrets.YOUTUBE_API_KEY }}
YOUTUBE_CHANNEL_ID: ${{ vars.YOUTUBE_CHANNEL_ID }}
YOUTUBE_EXPLAINER_PLAYLIST_ID: ${{ vars.YOUTUBE_EXPLAINER_PLAYLIST_ID }}
YOUTUBE_DEMO_PLAYLIST_ID: ${{ vars.YOUTUBE_DEMO_PLAYLIST_ID }}
VIDEOS_AUTO_PUBLISH: ${{ vars.VIDEOS_AUTO_PUBLISH }}
CLARITY_API_TOKEN: ${{ secrets.CLARITY_API_TOKEN }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CALCOM_SIGNING_SECRET: ${{ secrets.CALCOM_SIGNING_SECRET }}
Expand Down Expand Up @@ -256,6 +261,11 @@ jobs:
echo "PARTNERS_SENDER_NAME=${PARTNERS_SENDER_NAME}"
echo "GOOGLE_APPLICATION_CREDENTIALS_JSON=${GOOGLE_APPLICATION_CREDENTIALS_JSON}"
echo "CRUX_API_KEY=${CRUX_API_KEY}"
echo "YOUTUBE_API_KEY=${YOUTUBE_API_KEY}"
echo "YOUTUBE_CHANNEL_ID=${YOUTUBE_CHANNEL_ID}"
echo "YOUTUBE_EXPLAINER_PLAYLIST_ID=${YOUTUBE_EXPLAINER_PLAYLIST_ID}"
echo "YOUTUBE_DEMO_PLAYLIST_ID=${YOUTUBE_DEMO_PLAYLIST_ID}"
echo "VIDEOS_AUTO_PUBLISH=${VIDEOS_AUTO_PUBLISH}"
echo "CLARITY_API_TOKEN=${CLARITY_API_TOKEN}"
echo "CLOUDFLARE_API_TOKEN=${CLOUDFLARE_API_TOKEN}"
echo "CLOUDFLARE_ACCOUNT_TAG=${CLOUDFLARE_ACCOUNT_TAG}"
Expand Down
3 changes: 2 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,7 @@ that triggers each and the file that sends it.

## Background jobs

Twelve cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RUN=true` — set this in `.env` to enable; omitting it (e.g. in test runs) prevents spurious fires.
Thirteen cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RUN=true` — set this in `.env` to enable; omitting it (e.g. in test runs) prevents spurious fires.

| Job | Schedule (UTC) | File |
|-----|---------------|------|
Expand All @@ -336,6 +336,7 @@ Twelve cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RU
| Meilisearch reindex | daily 05:00 | `reindex-meili.ts` |
| Content-insights snapshot rebuild | daily 06:30 | `refresh-content-insights.ts` |
| Core Web Vitals (CrUX) refresh | daily 06:45 | `refresh-crux.ts` (no-op without `CRUX_API_KEY`) |
| YouTube video sync | hourly :07 | `sync-youtube-videos.ts` (RSS without `YOUTUBE_API_KEY`, Data API with it; new uploads become needs-review drafts) |

Update this table when adding/changing jobs. Every job needs a test file and the `PAYLOAD_AUTO_RUN` gate.

Expand Down
3 changes: 2 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,7 @@ that triggers each and the file that sends it.

## Background jobs

Twelve cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RUN=true` — set this in `.env` to enable; omitting it (e.g. in test runs) prevents spurious fires.
Thirteen cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RUN=true` — set this in `.env` to enable; omitting it (e.g. in test runs) prevents spurious fires.

| Job | Schedule (UTC) | File |
|-----|---------------|------|
Expand All @@ -327,6 +327,7 @@ Twelve cron tasks in `apps/cms/src/payload/jobs/`. All gated by `PAYLOAD_AUTO_RU
| Meilisearch reindex | daily 05:00 | `reindex-meili.ts` |
| Content-insights snapshot rebuild | daily 06:30 | `refresh-content-insights.ts` |
| Core Web Vitals (CrUX) refresh | daily 06:45 | `refresh-crux.ts` (no-op without `CRUX_API_KEY`) |
| YouTube video sync | hourly :07 | `sync-youtube-videos.ts` (RSS without `YOUTUBE_API_KEY`, Data API with it; new uploads become needs-review drafts) |

Update this table when adding/changing jobs. Every job needs a test file and the `PAYLOAD_AUTO_RUN` gate.

Expand Down
18 changes: 18 additions & 0 deletions apps/cms/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,24 @@ CONSENT_INGEST_SECRET=
CRUX_API_KEY=
# CRUX_ORIGIN=https://www.cleanstart.com

# YouTube video sync (hourly task, apps/cms/src/payload/jobs/sync-youtube-videos.ts).
# YOUTUBE_API_KEY is OPTIONAL. Unset = the sync reads the channel's RSS feed (title, date, description,
# hashtags; duration is typed in by editors). Set = YouTube Data API v3 adds duration, privacy and
# embeddable checks and "missing on YouTube" detection. Google Cloud -> enable "YouTube Data API v3"
# -> API key, restricted to that API. Read only and free within quota (about 3 units per run).
YOUTUBE_API_KEY=
# Defaults to the CleanStart channel (UC13c-XRmRlkWpp6WMG9SDjw).
YOUTUBE_CHANNEL_ID=
# Optional. A new upload gets its Category suggested from which of these two YouTube playlists it is in.
# Each takes a bare playlist id or a full youtube.com/playlist?list=... URL. A video in both gets no
# suggestion. The suggestion is written once, when the draft is created, and never changes it later; the
# draft is still "Needs review" and an editor confirms it. An invalid value is ignored with a log warning.
YOUTUBE_EXPLAINER_PLAYLIST_ID=
YOUTUBE_DEMO_PLAYLIST_ID=
# Reserved: new uploads always land as needs-review drafts today, because the publish gate needs a
# format that nothing guesses. Setting this to true does not bypass the gate.
VIDEOS_AUTO_PUBLISH=

# Edge caching for anonymous reads of public content collections (seconds).
# Unset or 0 = disabled, and src/proxy.ts changes no response.
#
Expand Down
283 changes: 283 additions & 0 deletions apps/cms/scripts/data/videos-catalog.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
{
"topics": [
{
"slug": "base-images",
"name": "Base images",
"aliases": [
"baseimage",
"containerbaseimages"
]
},
{
"slug": "attack-surface",
"name": "Attack surface",
"aliases": [
"attacksurfacereduction",
"containerattacksurface"
]
},
{
"slug": "cves",
"name": "CVEs",
"aliases": [
"cve",
"vulnerabilitymanagement",
"vulnerabilities"
]
},
{
"slug": "supply-chain",
"name": "Supply chain",
"aliases": [
"softwaresupplychain",
"supplychainsecurity",
"softwaresupplychainsecurity"
]
},
{
"slug": "sbom",
"name": "SBOM",
"aliases": [
"sboms",
"software bill of materials"
]
},
{
"slug": "vex",
"name": "VEX",
"aliases": []
},
{
"slug": "provenance",
"name": "Provenance",
"aliases": [
"containerprovenance"
]
},
{
"slug": "dependencies",
"name": "Dependencies",
"aliases": [
"dependencysecurity",
"cleanlibraries"
]
},
{
"slug": "posture",
"name": "Security posture",
"aliases": [
"securityposture",
"sspm"
]
},
{
"slug": "container-sprawl",
"name": "Container sprawl",
"aliases": []
},
{
"slug": "ai",
"name": "AI",
"aliases": []
}
],
"videos": [
{
"externalId": "uBItoX-x7Do",
"slug": "what-is-vex",
"title": "What Is VEX? How It Tells Which Vulnerabilities Matter",
"format": "explainer",
"topics": [
"vex",
"sbom",
"cves"
],
"summary": "A vulnerability scanner can flag hundreds of CVEs in a single container image, and some of them can’t be exploited where you run it. VEX (Vulnerability Exploitability eXchange) is how a software supplier tells you which ones apply.",
"durationSeconds": 101,
"publishedAt": "2026-10-13T14:17:14+00:00",
"thumb": "maxres"
},
{
"externalId": "m9olMTM72tI",
"slug": "sboms-explained",
"title": "SBOMs Explained: What They Tell You and What They Don’t",
"format": "explainer",
"topics": [
"sbom",
"provenance"
],
"summary": "A critical CVE drops. Is that component in your container image at all? An SBOM answers that in seconds. Whether it puts you at risk is a separate question, and this 2-minute explainer covers both.",
"durationSeconds": 113,
"publishedAt": "2026-10-12T10:58:15+00:00",
"thumb": "maxres"
},
{
"externalId": "UViY8slB4G8",
"slug": "clean-libraries-ide-demo",
"title": "Clean Libraries Demo: Scan and Fix Vulnerabilities in Your IDE",
"format": "demo",
"topics": [
"dependencies",
"cves"
],
"summary": "See Clean Libraries in action inside VS Code. As you code, it scans your project dependencies for known vulnerabilities and flags risky libraries directly in the IDE.",
"durationSeconds": 188,
"publishedAt": "2026-09-25T05:19:56+00:00",
"thumb": "maxres"
},
{
"externalId": "dQpZX5vMgqk",
"slug": "what-is-container-sprawl",
"title": "What Is Container Sprawl?",
"format": "explainer",
"topics": [
"container-sprawl",
"posture"
],
"summary": "Container sprawl occurs when container images grow faster than teams can track, secure, and manage them. This video explains the causes of container sprawl, its impact on container security, and why visibility across registries, clusters, and running workloads matters.",
"durationSeconds": 98,
"publishedAt": "2026-09-11T03:47:39+00:00",
"thumb": "maxres"
},
{
"externalId": "nh4n-o_ulvk",
"slug": "meet-cubr",
"title": "Meet CUBR, Your Guide to Trusted Software",
"format": "demo",
"topics": [
"supply-chain",
"provenance"
],
"summary": "Forged from precision, powered by trust, and built on the principles of secure-by-design, CUBR represents CleanStart's vision for a safer software supply chain.",
"durationSeconds": 105,
"publishedAt": "2026-07-30T10:27:29+00:00",
"thumb": "maxres"
},
{
"externalId": "Il4uk_RPD5M",
"slug": "clean-libraries-secure-dependencies",
"title": "Clean Libraries: Secure Software Dependencies at the Source",
"format": "demo",
"topics": [
"dependencies",
"supply-chain"
],
"summary": "Every release introduces hundreds of dependencies, including transitive packages and AI-generated libraries. Without verification, untrusted components become part of your software supply chain.",
"durationSeconds": 159,
"publishedAt": "2026-07-24T09:56:59+00:00",
"thumb": "hq"
},
{
"externalId": "6T3PGeattMI",
"slug": "what-is-provenance-in-containers",
"title": "What Is Provenance in Containers?",
"format": "explainer",
"topics": [
"provenance",
"supply-chain",
"sbom"
],
"summary": "Container provenance is a verifiable record of where a container image came from and how it was built. This video explains what provenance captures, why most images don't include it, and how a signed record lets you catch tampering before an image reaches production.",
"durationSeconds": 89,
"publishedAt": "2026-07-09T04:16:00+00:00",
"thumb": "maxres"
},
{
"externalId": "81vUYFBS-dY",
"slug": "public-vs-hardened-vs-cleanstart-images",
"title": "Public vs Hardened vs CleanStart Images",
"format": "explainer",
"topics": [
"base-images",
"attack-surface"
],
"summary": "Every container starts with a base image, but not all base images are built the same. This video compares Public Images, Hardened Images, and CleanStart Source-Built Images to show how image composition impacts vulnerabilities, attack surface, and trust.",
"durationSeconds": 153,
"publishedAt": "2026-07-02T18:47:00+00:00",
"thumb": "maxres"
},
{
"externalId": "92d9bzMUoI4",
"slug": "base-images-explained",
"title": "Base Images Explained in 60 Seconds",
"format": "explainer",
"topics": [
"base-images"
],
"summary": "A base image is the foundation that everything else is built on, from dependencies to application code. In this 60-second explainer, discover what a base image is and why choosing the right one matters.",
"durationSeconds": 59,
"publishedAt": "2026-06-24T20:23:34+00:00",
"thumb": "maxres"
},
{
"externalId": "1pPSjboIzoU",
"slug": "what-is-a-shell-less-container",
"title": "What Is a Shell-Less Container? Container Security Decoded",
"format": "explainer",
"topics": [
"attack-surface",
"base-images"
],
"summary": "Most production containers ship with a shell they never use. It sits inside the runtime as convenience tooling, and it quietly becomes one of the easiest paths for an attacker to turn a single vulnerability into full operational control.",
"durationSeconds": 131,
"publishedAt": "2026-06-10T12:14:14+00:00",
"thumb": "maxres"
},
{
"externalId": "GqFPH4KKqpA",
"slug": "cleansight-software-security-posture-management",
"title": "CleanSight: Complete Software Security Posture Management",
"format": "demo",
"topics": [
"posture",
"cves"
],
"summary": "Introducing CleanSight, a complete Software Security Posture Management platform designed to give you unified visibility across your infrastructure, clusters, and containers.",
"durationSeconds": 73,
"publishedAt": "2026-05-27T13:36:30+00:00",
"thumb": "maxres"
},
{
"externalId": "juh9IYHXXZA",
"slug": "cve-fatigue-explained",
"title": "CVE Fatigue Explained: Why Your Vulnerability Backlog Never Gets Shorter",
"format": "explainer",
"topics": [
"cves",
"base-images"
],
"summary": "2,000 CVEs flagged. Your team patches, rebuilds, redeploys. Next week: 500 more. The cycle repeats. This video breaks down what CVE fatigue is, where it actually comes from, and how teams escape it.",
"durationSeconds": 154,
"publishedAt": "2026-05-22T04:46:17+00:00",
"thumb": "maxres"
},
{
"externalId": "neWpaEOf3XM",
"slug": "busybox-in-containers",
"title": "BusyBox in Containers: The Risk Nobody Talks About",
"format": "explainer",
"topics": [
"attack-surface",
"base-images"
],
"summary": "BusyBox ships in 85% of lightweight container images. A single C binary from 1996, bundling 300+ utilities into one executable. 47 CVEs in five years. Nobody on your team installed it. Nobody reviewed it. It just showed up.",
"durationSeconds": 117,
"publishedAt": "2026-05-13T12:55:14+00:00",
"thumb": "maxres"
},
{
"externalId": "WvxSvEzXbiQ",
"slug": "what-is-attack-surface-in-containers",
"title": "What Is Attack Surface in Containers?",
"format": "explainer",
"topics": [
"attack-surface",
"base-images"
],
"summary": "Your container image has hundreds of packages. How many does your application actually need? Probably fewer than you think, and every extra one is a potential vulnerability.",
"durationSeconds": 160,
"publishedAt": "2026-04-30T06:56:26+00:00",
"thumb": "maxres"
}
]
}
Loading
Loading