From 1f9d9b1d16cc1db1785fe7756c85bedf18dd9fb4 Mon Sep 17 00:00:00 2001 From: hao Date: Wed, 18 Mar 2026 10:55:52 -0700 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0:=20109=20=E4=B8=AA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=20-=202026-03-18=2010:55:52?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 07-framework-security/cms/directus/INDEX.md | 2 +- 07-framework-security/cms/discourse/INDEX.md | 5 +- 07-framework-security/cms/drupal/INDEX.md | 3 +- 07-framework-security/cms/ghost/INDEX.md | 2 +- 07-framework-security/cms/joomla/INDEX.md | 2 +- 07-framework-security/cms/mediawiki/INDEX.md | 2 +- 07-framework-security/cms/moodle/INDEX.md | 2 +- 07-framework-security/cms/strapi/INDEX.md | 2 +- 07-framework-security/cms/wordpress/INDEX.md | 2 +- .../ecommerce/adobe-commerce/INDEX.md | 4 +- .../ecommerce/magento-open-source/INDEX.md | 2 +- .../ecommerce/medusa/INDEX.md | 2 +- .../ecommerce/opencart/INDEX.md | 2 +- .../ecommerce/openmage/INDEX.md | 2 +- .../ecommerce/prestashop/INDEX.md | 2 +- .../ecommerce/saleor/INDEX.md | 2 +- .../ecommerce/shopware/INDEX.md | 2 +- .../ecommerce/woocommerce/INDEX.md | 2 +- .../frameworks/angular/INDEX.md | 2 +- .../frameworks/aspnet-core/INDEX.md | 2 +- .../frameworks/astro/INDEX.md | 2 +- .../frameworks/django/INDEX.md | 4 +- .../frameworks/echo/INDEX.md | 2 +- .../frameworks/esbuild/INDEX.md | 2 +- .../frameworks/express/INDEX.md | 2 +- .../frameworks/fastify/INDEX.md | 2 +- .../frameworks/flask/INDEX.md | 2 +- 07-framework-security/frameworks/gin/INDEX.md | 2 +- .../frameworks/hapi/INDEX.md | 2 +- 07-framework-security/frameworks/koa/INDEX.md | 2 +- .../frameworks/laravel/INDEX.md | 2 +- .../frameworks/nestjs/INDEX.md | 2 +- .../frameworks/nextjs/INDEX.md | 16 +- .../frameworks/nodejs/INDEX.md | 2 +- .../frameworks/nuxt/INDEX.md | 2 +- .../frameworks/rails/INDEX.md | 2 +- .../frameworks/react/INDEX.md | 2 +- .../frameworks/spring-boot/INDEX.md | 2 +- .../frameworks/spring-framework/INDEX.md | 2 +- .../frameworks/spring-security/INDEX.md | 2 +- .../frameworks/sveltekit/INDEX.md | 2 +- .../frameworks/symfony/INDEX.md | 2 +- .../frameworks/undici/INDEX.md | 2 +- .../frameworks/vite/INDEX.md | 2 +- 07-framework-security/frameworks/vue/INDEX.md | 2 +- .../frameworks/webpack/INDEX.md | 2 +- .../frameworks/werkzeug/INDEX.md | 2 +- .../platforms/adminer/INDEX.md | 2 +- .../platforms/gitea/INDEX.md | 2 +- .../platforms/gitlab-ce/INDEX.md | 2 +- .../platforms/grafana/INDEX.md | 2 +- .../platforms/jenkins/INDEX.md | 2 +- .../platforms/kibana/INDEX.md | 2 +- .../platforms/mattermost/INDEX.md | 2 +- .../platforms/phpmyadmin/INDEX.md | 2 +- .../platforms/redmine/INDEX.md | 2 +- .../servers/apache-httpd/INDEX.md | 2 +- .../servers/apache-tomcat/INDEX.md | 2 +- 07-framework-security/servers/caddy/INDEX.md | 2 +- .../servers/haproxy/INDEX.md | 3 +- 07-framework-security/servers/nginx/INDEX.md | 2 +- .../servers/traefik/INDEX.md | 2 +- 08-threat-intel/generated/alerts.json | 1 + 08-threat-intel/generated/coverage-matrix.md | 2 +- .../generated/dashboard/advisories.json | 191 +- .../generated/dashboard/architecture.json | 215 +- .../generated/dashboard/assets/app.js | 129 +- .../generated/dashboard/data/alerts.json | 1 + .../dashboard/data/completeness.json | 110 +- .../dashboard/data/monitor-summary.json | 25 + .../dashboard/data/source-catalog-audit.json | 1655 ++++++++++ .../dashboard/data/source-health.json | 1218 +++++++ .../dashboard/docs/architecture-library.html | 215 +- .../dashboard/docs/coverage-matrix.html | 2 +- .../dashboard/docs/retired-sources.html | 539 +++ .../dashboard/docs/source-catalog-audit.html | 141 + .../generated/dashboard/docs/source-map.html | 177 +- .../docs/testing-completeness-report.html | 45 +- .../generated/dashboard/summary.json | 94 +- .../generated/dashboard/systems.json | 32 +- 08-threat-intel/generated/latest-ingest.md | 42 +- .../generated/monitor-summary.json | 25 + .../generated/retired-sources.json | 447 +++ 08-threat-intel/generated/run-summary.json | 44 +- .../generated/source-catalog-audit.json | 1655 ++++++++++ .../generated/source-catalog-audit.md | 48 + 08-threat-intel/generated/source-health.json | 1218 +++++++ .../advisories/nextjs--CVE-2026-27977.json | 72 - .../advisories/nextjs--CVE-2026-27978.json | 72 - .../advisories/nextjs--CVE-2026-27979.json | 72 - .../advisories/nextjs--CVE-2026-27980.json | 72 - .../advisories/nextjs--CVE-2026-29057.json | 77 - .../monitoring/2026-03-18T17-44-31+00-00.json | 2902 +++++++++++++++++ 08-threat-intel/registry/systems/nextjs.json | 16 +- 08-threat-intel/source-map.yaml | 177 +- docs/testing-completeness-report.md | 45 +- .../com.hao.websafe.intel-monitor.plist | 34 + scripts/install-intel-monitor-launchagent.sh | 18 + scripts/intel/config.py | 2 +- scripts/intel/http_client.py | 21 +- scripts/intel/main.py | 12 +- scripts/intel/monitoring.py | 4 +- scripts/intel/sources/nvd_api.py | 28 +- scripts/intel/sources/runner.py | 4 +- scripts/intel/sources/vendor_index.py | 42 +- scripts/intel/validators.py | 33 + .../dashboard_templates/lovart/assets/app.js | 83 +- scripts/lab/render.py | 75 +- scripts/sync-gitea.sh | 29 + 109 files changed, 10958 insertions(+), 1350 deletions(-) create mode 100644 08-threat-intel/generated/alerts.json create mode 100644 08-threat-intel/generated/dashboard/data/alerts.json create mode 100644 08-threat-intel/generated/dashboard/data/monitor-summary.json create mode 100644 08-threat-intel/generated/dashboard/data/source-catalog-audit.json create mode 100644 08-threat-intel/generated/dashboard/data/source-health.json create mode 100644 08-threat-intel/generated/dashboard/docs/retired-sources.html create mode 100644 08-threat-intel/generated/dashboard/docs/source-catalog-audit.html create mode 100644 08-threat-intel/generated/monitor-summary.json create mode 100644 08-threat-intel/generated/retired-sources.json create mode 100644 08-threat-intel/generated/source-catalog-audit.json create mode 100644 08-threat-intel/generated/source-catalog-audit.md create mode 100644 08-threat-intel/generated/source-health.json delete mode 100644 08-threat-intel/registry/advisories/nextjs--CVE-2026-27977.json delete mode 100644 08-threat-intel/registry/advisories/nextjs--CVE-2026-27978.json delete mode 100644 08-threat-intel/registry/advisories/nextjs--CVE-2026-27979.json delete mode 100644 08-threat-intel/registry/advisories/nextjs--CVE-2026-27980.json delete mode 100644 08-threat-intel/registry/advisories/nextjs--CVE-2026-29057.json create mode 100644 08-threat-intel/registry/monitoring/2026-03-18T17-44-31+00-00.json create mode 100644 ops/launchd/com.hao.websafe.intel-monitor.plist create mode 100755 scripts/install-intel-monitor-launchagent.sh diff --git a/07-framework-security/cms/directus/INDEX.md b/07-framework-security/cms/directus/INDEX.md index d38f2930..cac6be22 100644 --- a/07-framework-security/cms/directus/INDEX.md +++ b/07-framework-security/cms/directus/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/discourse/INDEX.md b/07-framework-security/cms/discourse/INDEX.md index 19820b8c..eb14571d 100644 --- a/07-framework-security/cms/discourse/INDEX.md +++ b/07-framework-security/cms/discourse/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -25,7 +25,8 @@ ## 来源 - `official` [Discourse Meta Security](https://meta.discourse.org/c/bug/security/40) (mode=core) -- `official` [GitHub Discourse Advisories](https://github.com/discourse/discourse/security/advisories) (mode=core) +- `official` [Discourse Release Notes RSS](https://meta.discourse.org/tag/release-notes.rss) (mode=core) +- `official` [GitHub Discourse Advisories](https://github.com/advisories) (ecosystem=rubygems; mode=core) ## 案例列表 diff --git a/07-framework-security/cms/drupal/INDEX.md b/07-framework-security/cms/drupal/INDEX.md index c235b9e5..9329b58e 100644 --- a/07-framework-security/cms/drupal/INDEX.md +++ b/07-framework-security/cms/drupal/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -27,6 +27,7 @@ - `official` [Drupal Security Advisories RSS](https://www.drupal.org/security/rss.xml) (mode=core) - `official` [NVD Drupal](https://nvd.nist.gov/vuln/search) (keyword=Drupal; mode=core) - `ecosystem-authority` [Drupal Security Advisories Site](https://www.drupal.org/security) (mode=module) +- `ecosystem-authority` [GHSA Drupal Core](https://github.com/advisories) (ecosystem=composer; mode=core) ## 案例列表 diff --git a/07-framework-security/cms/ghost/INDEX.md b/07-framework-security/cms/ghost/INDEX.md index c5c22aa2..620c60b4 100644 --- a/07-framework-security/cms/ghost/INDEX.md +++ b/07-framework-security/cms/ghost/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/joomla/INDEX.md b/07-framework-security/cms/joomla/INDEX.md index a44221b2..7a7132de 100644 --- a/07-framework-security/cms/joomla/INDEX.md +++ b/07-framework-security/cms/joomla/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/mediawiki/INDEX.md b/07-framework-security/cms/mediawiki/INDEX.md index c4d7b074..83e026cb 100644 --- a/07-framework-security/cms/mediawiki/INDEX.md +++ b/07-framework-security/cms/mediawiki/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/moodle/INDEX.md b/07-framework-security/cms/moodle/INDEX.md index 798253b0..d6305697 100644 --- a/07-framework-security/cms/moodle/INDEX.md +++ b/07-framework-security/cms/moodle/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/strapi/INDEX.md b/07-framework-security/cms/strapi/INDEX.md index 321c0a99..b760fb74 100644 --- a/07-framework-security/cms/strapi/INDEX.md +++ b/07-framework-security/cms/strapi/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/cms/wordpress/INDEX.md b/07-framework-security/cms/wordpress/INDEX.md index e5faae0a..a8cd12b7 100644 --- a/07-framework-security/cms/wordpress/INDEX.md +++ b/07-framework-security/cms/wordpress/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/adobe-commerce/INDEX.md b/07-framework-security/ecommerce/adobe-commerce/INDEX.md index a10debfe..e08212ab 100644 --- a/07-framework-security/ecommerce/adobe-commerce/INDEX.md +++ b/07-framework-security/ecommerce/adobe-commerce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -25,7 +25,9 @@ ## 来源 - `official` [Adobe Security Bulletins](https://helpx.adobe.com/security/products/magento.html) (mode=core) +- `official` [Adobe Magento Security Index](https://helpx.adobe.com/security/products/magento.html) (mode=core) - `official` [NVD Adobe Commerce](https://nvd.nist.gov/vuln/search) (keyword=Adobe Commerce; mode=core) +- `ecosystem-authority` [GHSA Adobe Commerce](https://github.com/advisories) (ecosystem=composer; mode=core) - `ecosystem-authority` [Sansec Research](https://sansec.io/research) (mode=extension) ## 案例列表 diff --git a/07-framework-security/ecommerce/magento-open-source/INDEX.md b/07-framework-security/ecommerce/magento-open-source/INDEX.md index c89244b2..2825d0c8 100644 --- a/07-framework-security/ecommerce/magento-open-source/INDEX.md +++ b/07-framework-security/ecommerce/magento-open-source/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/medusa/INDEX.md b/07-framework-security/ecommerce/medusa/INDEX.md index 4485a9d1..25951618 100644 --- a/07-framework-security/ecommerce/medusa/INDEX.md +++ b/07-framework-security/ecommerce/medusa/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/opencart/INDEX.md b/07-framework-security/ecommerce/opencart/INDEX.md index 051918b5..bb899cb2 100644 --- a/07-framework-security/ecommerce/opencart/INDEX.md +++ b/07-framework-security/ecommerce/opencart/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/openmage/INDEX.md b/07-framework-security/ecommerce/openmage/INDEX.md index 0b327dbf..d9f39e4d 100644 --- a/07-framework-security/ecommerce/openmage/INDEX.md +++ b/07-framework-security/ecommerce/openmage/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/prestashop/INDEX.md b/07-framework-security/ecommerce/prestashop/INDEX.md index 9000a92e..d0ae9960 100644 --- a/07-framework-security/ecommerce/prestashop/INDEX.md +++ b/07-framework-security/ecommerce/prestashop/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/saleor/INDEX.md b/07-framework-security/ecommerce/saleor/INDEX.md index 2dbfc323..7ce3b681 100644 --- a/07-framework-security/ecommerce/saleor/INDEX.md +++ b/07-framework-security/ecommerce/saleor/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/shopware/INDEX.md b/07-framework-security/ecommerce/shopware/INDEX.md index 22ebdf2d..b0ce7772 100644 --- a/07-framework-security/ecommerce/shopware/INDEX.md +++ b/07-framework-security/ecommerce/shopware/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/ecommerce/woocommerce/INDEX.md b/07-framework-security/ecommerce/woocommerce/INDEX.md index a97fb38f..ffc5d96b 100644 --- a/07-framework-security/ecommerce/woocommerce/INDEX.md +++ b/07-framework-security/ecommerce/woocommerce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/angular/INDEX.md b/07-framework-security/frameworks/angular/INDEX.md index 255c6a14..6aa5824d 100644 --- a/07-framework-security/frameworks/angular/INDEX.md +++ b/07-framework-security/frameworks/angular/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/aspnet-core/INDEX.md b/07-framework-security/frameworks/aspnet-core/INDEX.md index 273efa93..bdaf75f3 100644 --- a/07-framework-security/frameworks/aspnet-core/INDEX.md +++ b/07-framework-security/frameworks/aspnet-core/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/astro/INDEX.md b/07-framework-security/frameworks/astro/INDEX.md index ac5fb623..edbbb295 100644 --- a/07-framework-security/frameworks/astro/INDEX.md +++ b/07-framework-security/frameworks/astro/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/django/INDEX.md b/07-framework-security/frameworks/django/INDEX.md index abfa2b0f..5ecd78e8 100644 --- a/07-framework-security/frameworks/django/INDEX.md +++ b/07-framework-security/frameworks/django/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -25,6 +25,8 @@ ## 来源 - `official` [Django Security RSS](https://www.djangoproject.com/weblog/feeds/tags/security/) (mode=core) +- `official` [Django Security Weblog](https://www.djangoproject.com/weblog/) (mode=core) +- `official` [Django Security Releases Archive](https://docs.djangoproject.com/en/dev/releases/security/) (mode=core) - `official` [OSV Django](https://osv.dev/) (mode=core) ## 案例列表 diff --git a/07-framework-security/frameworks/echo/INDEX.md b/07-framework-security/frameworks/echo/INDEX.md index b92dcdff..c60a5274 100644 --- a/07-framework-security/frameworks/echo/INDEX.md +++ b/07-framework-security/frameworks/echo/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/esbuild/INDEX.md b/07-framework-security/frameworks/esbuild/INDEX.md index cbb0de3b..142668e9 100644 --- a/07-framework-security/frameworks/esbuild/INDEX.md +++ b/07-framework-security/frameworks/esbuild/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/express/INDEX.md b/07-framework-security/frameworks/express/INDEX.md index f57ca45d..9c90a515 100644 --- a/07-framework-security/frameworks/express/INDEX.md +++ b/07-framework-security/frameworks/express/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/fastify/INDEX.md b/07-framework-security/frameworks/fastify/INDEX.md index 5a049189..c3170639 100644 --- a/07-framework-security/frameworks/fastify/INDEX.md +++ b/07-framework-security/frameworks/fastify/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/flask/INDEX.md b/07-framework-security/frameworks/flask/INDEX.md index e84ee3e1..9d8e0710 100644 --- a/07-framework-security/frameworks/flask/INDEX.md +++ b/07-framework-security/frameworks/flask/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/gin/INDEX.md b/07-framework-security/frameworks/gin/INDEX.md index 36fb0dd2..b26484d0 100644 --- a/07-framework-security/frameworks/gin/INDEX.md +++ b/07-framework-security/frameworks/gin/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/hapi/INDEX.md b/07-framework-security/frameworks/hapi/INDEX.md index 1a5aae4b..5ec4b563 100644 --- a/07-framework-security/frameworks/hapi/INDEX.md +++ b/07-framework-security/frameworks/hapi/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/koa/INDEX.md b/07-framework-security/frameworks/koa/INDEX.md index e95d5c4b..83c98068 100644 --- a/07-framework-security/frameworks/koa/INDEX.md +++ b/07-framework-security/frameworks/koa/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/laravel/INDEX.md b/07-framework-security/frameworks/laravel/INDEX.md index d61d1276..98e478a6 100644 --- a/07-framework-security/frameworks/laravel/INDEX.md +++ b/07-framework-security/frameworks/laravel/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nestjs/INDEX.md b/07-framework-security/frameworks/nestjs/INDEX.md index 51decc0a..4938d5f2 100644 --- a/07-framework-security/frameworks/nestjs/INDEX.md +++ b/07-framework-security/frameworks/nestjs/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nextjs/INDEX.md b/07-framework-security/frameworks/nextjs/INDEX.md index f8888122..a5840fd3 100644 --- a/07-framework-security/frameworks/nextjs/INDEX.md +++ b/07-framework-security/frameworks/nextjs/INDEX.md @@ -5,14 +5,14 @@ - 系统 ID: `nextjs` - 分类: `frameworks` - 覆盖策略: `history-full` -- 总案例数: `5` -- 近 30 天新增/更新: `5` -- 重点 Markdown 案例数: `5` +- 总案例数: `0` +- 近 30 天新增/更新: `0` +- 重点 Markdown 案例数: `0` - 已实证(真实版本): `0` - 已实证(synthetic): `0` - 阻塞数: `0` -- 待人工/缺浏览器证据: `5` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 待人工/缺浏览器证据: `0` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -32,8 +32,4 @@ | 标题 | 严重度 | 案例状态 | 实证状态 | 实证方式 | 来源置信度 | 更新时间 | 案例页 | |------|--------|----------|----------|----------|------------|----------|--------| -| Next.js: HTTP request smuggling in rewrites | `medium` | `generated` | `triage-manual` | `synthetic` | `official` | `2026-03-17T16:31:26.646070Z` | [link](/Users/x/websafe/07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-29057.md) | -| Next.js: Unbounded next/image disk cache growth can exhaust storage | `medium` | `generated` | `triage-manual` | `synthetic` | `official` | `2026-03-17T16:31:33.597080Z` | [link](/Users/x/websafe/07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27980.md) | -| Next.js: Unbounded postponed resume buffering can lead to DoS | `medium` | `generated` | `triage-manual` | `synthetic` | `official` | `2026-03-17T16:31:34.160932Z` | [link](/Users/x/websafe/07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27979.md) | -| Next.js: null origin can bypass Server Actions CSRF checks | `medium` | `generated` | `triage-manual` | `synthetic` | `official` | `2026-03-17T15:46:43.484729Z` | [link](/Users/x/websafe/07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27978.md) | -| Next.js: null origin can bypass dev HMR websocket CSRF checks | `medium` | `generated` | `triage-manual` | `synthetic` | `official` | `2026-03-17T15:46:26.028580Z` | [link](/Users/x/websafe/07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27977.md) | +| No advisories yet | `n/a` | `empty` | `n/a` | `n/a` | `n/a` | `n/a` | - | diff --git a/07-framework-security/frameworks/nodejs/INDEX.md b/07-framework-security/frameworks/nodejs/INDEX.md index 372d2e4b..b7758710 100644 --- a/07-framework-security/frameworks/nodejs/INDEX.md +++ b/07-framework-security/frameworks/nodejs/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/nuxt/INDEX.md b/07-framework-security/frameworks/nuxt/INDEX.md index 0b39eaa2..08762045 100644 --- a/07-framework-security/frameworks/nuxt/INDEX.md +++ b/07-framework-security/frameworks/nuxt/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/rails/INDEX.md b/07-framework-security/frameworks/rails/INDEX.md index 95fdebb7..e4104cbe 100644 --- a/07-framework-security/frameworks/rails/INDEX.md +++ b/07-framework-security/frameworks/rails/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/react/INDEX.md b/07-framework-security/frameworks/react/INDEX.md index 2e696178..ff9ffa6c 100644 --- a/07-framework-security/frameworks/react/INDEX.md +++ b/07-framework-security/frameworks/react/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-boot/INDEX.md b/07-framework-security/frameworks/spring-boot/INDEX.md index 915266c8..c202c233 100644 --- a/07-framework-security/frameworks/spring-boot/INDEX.md +++ b/07-framework-security/frameworks/spring-boot/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-framework/INDEX.md b/07-framework-security/frameworks/spring-framework/INDEX.md index b4f71038..b5b8d90c 100644 --- a/07-framework-security/frameworks/spring-framework/INDEX.md +++ b/07-framework-security/frameworks/spring-framework/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/spring-security/INDEX.md b/07-framework-security/frameworks/spring-security/INDEX.md index 2550fe4c..fec6bde2 100644 --- a/07-framework-security/frameworks/spring-security/INDEX.md +++ b/07-framework-security/frameworks/spring-security/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/sveltekit/INDEX.md b/07-framework-security/frameworks/sveltekit/INDEX.md index db084ce5..3c2b40d1 100644 --- a/07-framework-security/frameworks/sveltekit/INDEX.md +++ b/07-framework-security/frameworks/sveltekit/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/symfony/INDEX.md b/07-framework-security/frameworks/symfony/INDEX.md index 4a304f31..a2802d58 100644 --- a/07-framework-security/frameworks/symfony/INDEX.md +++ b/07-framework-security/frameworks/symfony/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/undici/INDEX.md b/07-framework-security/frameworks/undici/INDEX.md index 01425bda..de1bc9b4 100644 --- a/07-framework-security/frameworks/undici/INDEX.md +++ b/07-framework-security/frameworks/undici/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/vite/INDEX.md b/07-framework-security/frameworks/vite/INDEX.md index 57ea0f02..5f755610 100644 --- a/07-framework-security/frameworks/vite/INDEX.md +++ b/07-framework-security/frameworks/vite/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/vue/INDEX.md b/07-framework-security/frameworks/vue/INDEX.md index c1692bc0..73e30ef7 100644 --- a/07-framework-security/frameworks/vue/INDEX.md +++ b/07-framework-security/frameworks/vue/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/webpack/INDEX.md b/07-framework-security/frameworks/webpack/INDEX.md index dc3d9f27..fb5e8496 100644 --- a/07-framework-security/frameworks/webpack/INDEX.md +++ b/07-framework-security/frameworks/webpack/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/frameworks/werkzeug/INDEX.md b/07-framework-security/frameworks/werkzeug/INDEX.md index 0a34839a..8446c38c 100644 --- a/07-framework-security/frameworks/werkzeug/INDEX.md +++ b/07-framework-security/frameworks/werkzeug/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/adminer/INDEX.md b/07-framework-security/platforms/adminer/INDEX.md index 455b50f8..55a249b1 100644 --- a/07-framework-security/platforms/adminer/INDEX.md +++ b/07-framework-security/platforms/adminer/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/gitea/INDEX.md b/07-framework-security/platforms/gitea/INDEX.md index ef92f40c..ca5b8250 100644 --- a/07-framework-security/platforms/gitea/INDEX.md +++ b/07-framework-security/platforms/gitea/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/gitlab-ce/INDEX.md b/07-framework-security/platforms/gitlab-ce/INDEX.md index a5223690..f0d2d81e 100644 --- a/07-framework-security/platforms/gitlab-ce/INDEX.md +++ b/07-framework-security/platforms/gitlab-ce/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/grafana/INDEX.md b/07-framework-security/platforms/grafana/INDEX.md index 17259301..32163344 100644 --- a/07-framework-security/platforms/grafana/INDEX.md +++ b/07-framework-security/platforms/grafana/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/jenkins/INDEX.md b/07-framework-security/platforms/jenkins/INDEX.md index 0dbbd799..17c3f9ee 100644 --- a/07-framework-security/platforms/jenkins/INDEX.md +++ b/07-framework-security/platforms/jenkins/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/kibana/INDEX.md b/07-framework-security/platforms/kibana/INDEX.md index 423bfdcf..27d44bd6 100644 --- a/07-framework-security/platforms/kibana/INDEX.md +++ b/07-framework-security/platforms/kibana/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/mattermost/INDEX.md b/07-framework-security/platforms/mattermost/INDEX.md index e2f52720..535a5129 100644 --- a/07-framework-security/platforms/mattermost/INDEX.md +++ b/07-framework-security/platforms/mattermost/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/phpmyadmin/INDEX.md b/07-framework-security/platforms/phpmyadmin/INDEX.md index 3497c798..3a71c2f4 100644 --- a/07-framework-security/platforms/phpmyadmin/INDEX.md +++ b/07-framework-security/platforms/phpmyadmin/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/platforms/redmine/INDEX.md b/07-framework-security/platforms/redmine/INDEX.md index e0583cc9..aab7496d 100644 --- a/07-framework-security/platforms/redmine/INDEX.md +++ b/07-framework-security/platforms/redmine/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/servers/apache-httpd/INDEX.md b/07-framework-security/servers/apache-httpd/INDEX.md index 5d9dbc46..bf866bde 100644 --- a/07-framework-security/servers/apache-httpd/INDEX.md +++ b/07-framework-security/servers/apache-httpd/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/servers/apache-tomcat/INDEX.md b/07-framework-security/servers/apache-tomcat/INDEX.md index b2ebde77..00bd7293 100644 --- a/07-framework-security/servers/apache-tomcat/INDEX.md +++ b/07-framework-security/servers/apache-tomcat/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/servers/caddy/INDEX.md b/07-framework-security/servers/caddy/INDEX.md index 67627f02..e591d787 100644 --- a/07-framework-security/servers/caddy/INDEX.md +++ b/07-framework-security/servers/caddy/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/servers/haproxy/INDEX.md b/07-framework-security/servers/haproxy/INDEX.md index edb87c4a..18d4147a 100644 --- a/07-framework-security/servers/haproxy/INDEX.md +++ b/07-framework-security/servers/haproxy/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 @@ -25,6 +25,7 @@ ## 来源 - `official` [HAProxy Security Advisories](https://www.haproxy.org/security/) (mode=server) +- `official` [HAProxy Blog Feed](https://www.haproxy.com/feed/) (mode=server) - `official` [NVD HAProxy](https://nvd.nist.gov/vuln/search) (keyword=HAProxy; mode=server) ## 案例列表 diff --git a/07-framework-security/servers/nginx/INDEX.md b/07-framework-security/servers/nginx/INDEX.md index cbc8acad..57cb4521 100644 --- a/07-framework-security/servers/nginx/INDEX.md +++ b/07-framework-security/servers/nginx/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/07-framework-security/servers/traefik/INDEX.md b/07-framework-security/servers/traefik/INDEX.md index 96776c5d..2e8f86c0 100644 --- a/07-framework-security/servers/traefik/INDEX.md +++ b/07-framework-security/servers/traefik/INDEX.md @@ -12,7 +12,7 @@ - 已实证(synthetic): `0` - 阻塞数: `0` - 待人工/缺浏览器证据: `0` -- 最近渲染时间: `2026-03-18T14:45:52+00:00` +- 最近渲染时间: `2026-03-18T17:52:48+00:00` ## 目标约束 diff --git a/08-threat-intel/generated/alerts.json b/08-threat-intel/generated/alerts.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/08-threat-intel/generated/alerts.json @@ -0,0 +1 @@ +[] diff --git a/08-threat-intel/generated/coverage-matrix.md b/08-threat-intel/generated/coverage-matrix.md index d3b4aaa9..15d7c04f 100644 --- a/08-threat-intel/generated/coverage-matrix.md +++ b/08-threat-intel/generated/coverage-matrix.md @@ -37,7 +37,7 @@ | Medusa | `ecommerce` | `rolling-24m` | `-` | `yes` | `0` | `0` | `2` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | | Moodle | `cms` | `rolling-24m` | `-` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | | NestJS | `frameworks` | `rolling-24m` | `-` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | -| Next.js | `frameworks` | `history-full` | `yes` | `yes` | `5` | `5` | `3` | `seeded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `2026-03-17T16:31:34.160932Z` | +| Next.js | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | | Nginx | `servers` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | | Node.js | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | | Nuxt | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` | diff --git a/08-threat-intel/generated/dashboard/advisories.json b/08-threat-intel/generated/dashboard/advisories.json index e6917261..0967ef42 100644 --- a/08-threat-intel/generated/dashboard/advisories.json +++ b/08-threat-intel/generated/dashboard/advisories.json @@ -1,190 +1 @@ -{ - "nextjs--CVE-2026-27977": { - "canonical_id": "nextjs--CVE-2026-27977", - "title": "Next.js: null origin can bypass dev HMR websocket CSRF checks", - "summary": "## Summary\nIn `next dev`, cross-site protection for internal websocket endpoints could treat `Origin: null` as a bypass case even if [`allowedDevOrigins`](https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins) is configured, allowing privacy-sensitive/opaque contexts (for example sandboxed documents) to connect unexpectedly.\n\n## Impact\nIf a dev server is reachable from attacker-controlled content, an attacker may be able to connect to the HMR websocket channel and interact with dev websocket traffic. This affects development mode only.\nApps without a configured [`allowedDevOrigins`](https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins) still allow connections from any origin.\n\n## Patches\nFixed by validating `Origin: null` through the same cross-site origin-allowance checks used for other origins. \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Do not expose `next dev` to untrusted networks.\n- Block websocket upgrades to `/_next/webpack-hmr` when `Origin` is `null` at your proxy.", - "display_name": "Next.js", - "system_id": "nextjs", - "category": "frameworks", - "severity": "medium", - "cvss_score": 4.0, - "exploit_status": "unknown", - "published_at": "2026-03-17T15:29:48Z", - "updated_at": "2026-03-17T15:46:26.028580Z", - "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-jcc7-9wpm-mj36", - "secondary_source_urls": [ - "https://github.com/vercel/next.js/commit/862f9b9bb41d235e0d8cf44aa811e7fd118cee2a", - "https://github.com/vercel/next.js", - "https://github.com/vercel/next.js/releases/tag/v16.1.7" - ], - "aliases": [ - "CVE-2026-27977", - "GHSA-jcc7-9wpm-mj36" - ], - "secure_code_topics": [ - "authz-server-side-recheck", - "proxy-trust-boundary", - "token-cookie-storage" - ], - "verification_status": "triage-manual", - "verification_mode": "synthetic", - "artifact_mode": "official-source", - "blocked_reason": null, - "browser_evidence": { - "required": false, - "present": false, - "refs": [] - } - }, - "nextjs--CVE-2026-27978": { - "canonical_id": "nextjs--CVE-2026-27978", - "title": "Next.js: null origin can bypass Server Actions CSRF checks", - "summary": "## Summary\n`origin: null` was treated as a \"missing\" origin during Server Action CSRF validation. As a result, requests from opaque contexts (such as sandboxed iframes) could bypass origin verification instead of being validated as cross-origin requests.\n\n## Impact\nAn attacker could induce a victim browser to submit Server Actions from a sandboxed context, potentially executing state-changing actions with victim credentials (CSRF).\n\n## Patches\nFixed by treating `'null'` as an explicit origin value and enforcing host/origin checks unless `'null'` is explicitly allowlisted in `experimental.serverActions.allowedOrigins`. \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Add CSRF tokens for sensitive Server Actions.\n- Prefer `SameSite=Strict` on sensitive auth cookies.\n- Do not allow `'null'` in `serverActions.allowedOrigins` unless intentionally required and additionally protected.", - "display_name": "Next.js", - "system_id": "nextjs", - "category": "frameworks", - "severity": "medium", - "cvss_score": 4.0, - "exploit_status": "unknown", - "published_at": "2026-03-17T15:30:14Z", - "updated_at": "2026-03-17T15:46:43.484729Z", - "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-mq59-m269-xvcx", - "secondary_source_urls": [ - "https://github.com/vercel/next.js/commit/a27a11d78e748a8c7ccfd14b7759ad2b9bf097d8", - "https://github.com/vercel/next.js", - "https://github.com/vercel/next.js/releases/tag/v16.1.7" - ], - "aliases": [ - "CVE-2026-27978", - "GHSA-mq59-m269-xvcx" - ], - "secure_code_topics": [ - "authz-server-side-recheck", - "proxy-trust-boundary", - "token-cookie-storage" - ], - "verification_status": "triage-manual", - "verification_mode": "synthetic", - "artifact_mode": "official-source", - "blocked_reason": null, - "browser_evidence": { - "required": false, - "present": false, - "refs": [] - } - }, - "nextjs--CVE-2026-27979": { - "canonical_id": "nextjs--CVE-2026-27979", - "title": "Next.js: Unbounded postponed resume buffering can lead to DoS", - "summary": "## Summary\nA request containing the `next-resume: 1` header (corresponding with a PPR resume request) would buffer request bodies without consistently enforcing `maxPostponedStateSize` in certain setups. The previous mitigation protected minimal-mode deployments, but equivalent non-minimal deployments remained vulnerable to the same unbounded postponed resume-body buffering behavior.\n\n## Impact\nIn applications using the App Router with Partial Prerendering capability enabled (via `experimental.ppr` or `cacheComponents`), an attacker could send oversized `next-resume` POST payloads that were buffered without consistent size enforcement in non-minimal deployments, causing excessive memory usage and potential denial of service.\n\n## Patches\nFixed by enforcing size limits across all postponed-body buffering paths and erroring when limits are exceeded. \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Block requests containing the `next-resume` header, as this is never valid to be sent from an untrusted client.", - "display_name": "Next.js", - "system_id": "nextjs", - "category": "frameworks", - "severity": "medium", - "cvss_score": 4.0, - "exploit_status": "unknown", - "published_at": "2026-03-17T16:16:49Z", - "updated_at": "2026-03-17T16:31:34.160932Z", - "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-h27x-g6w4-24gq", - "secondary_source_urls": [ - "https://github.com/vercel/next.js/commit/c885d4825f800dd1e49ead37274dcd08cdd6f3f1", - "https://github.com/vercel/next.js", - "https://github.com/vercel/next.js/releases/tag/v16.1.7" - ], - "aliases": [ - "CVE-2026-27979", - "GHSA-h27x-g6w4-24gq" - ], - "secure_code_topics": [ - "authz-server-side-recheck", - "proxy-trust-boundary", - "token-cookie-storage" - ], - "verification_status": "triage-manual", - "verification_mode": "synthetic", - "artifact_mode": "official-source", - "blocked_reason": null, - "browser_evidence": { - "required": false, - "present": false, - "refs": [] - } - }, - "nextjs--CVE-2026-27980": { - "canonical_id": "nextjs--CVE-2026-27980", - "title": "Next.js: Unbounded next/image disk cache growth can exhaust storage", - "summary": "## Summary\nThe default Next.js image optimization disk cache (`/_next/image`) did not have a configurable upper bound, allowing unbounded cache growth.\n\n## Impact\nAn attacker could generate many unique image-optimization variants and exhaust disk space, causing denial of service.\n\n## Patches\nFixed by adding an LRU-backed disk cache with `images.maximumDiskCacheSize`, including eviction of least-recently-used entries when the limit is exceeded. Setting `maximumDiskCacheSize: 0` disables disk caching. \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Periodically clean `.next/cache/images`.\n- Reduce variant cardinality (e.g., tighten values for `images.localPatterns`, `images.remotePatterns`, and `images.qualities`)", - "display_name": "Next.js", - "system_id": "nextjs", - "category": "frameworks", - "severity": "medium", - "cvss_score": 4.0, - "exploit_status": "unknown", - "published_at": "2026-03-17T16:17:06Z", - "updated_at": "2026-03-17T16:31:33.597080Z", - "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-3x4c-7xq6-9pq8", - "secondary_source_urls": [ - "https://github.com/vercel/next.js/commit/39eb8e0ac498b48855a0430fbf4c22276a73b4bd", - "https://github.com/vercel/next.js", - "https://github.com/vercel/next.js/releases/tag/v16.1.7" - ], - "aliases": [ - "CVE-2026-27980", - "GHSA-3x4c-7xq6-9pq8" - ], - "secure_code_topics": [ - "authz-server-side-recheck", - "proxy-trust-boundary", - "token-cookie-storage" - ], - "verification_status": "triage-manual", - "verification_mode": "synthetic", - "artifact_mode": "official-source", - "blocked_reason": null, - "browser_evidence": { - "required": false, - "present": false, - "refs": [] - } - }, - "nextjs--CVE-2026-29057": { - "canonical_id": "nextjs--CVE-2026-29057", - "title": "Next.js: HTTP request smuggling in rewrites", - "summary": "## Summary\nWhen Next.js rewrites proxy traffic to an external backend, a crafted `DELETE`/`OPTIONS` request using `Transfer-Encoding: chunked` could trigger request boundary disagreement between the proxy and backend. This could allow request smuggling through rewritten routes.\n\n## Impact\nAn attacker could smuggle a second request to unintended backend routes (for example, internal/admin endpoints), bypassing assumptions that only the configured rewrite destination/path is reachable. This does not impact applications hosted on providers that handle rewrites at the CDN level, such as Vercel. \n\n## Patches\nThe vulnerability originated in an upstream library vendored by Next.js. It is fixed by updating that dependency\u2019s behavior so `content-length: 0` is added only when both `content-length` and `transfer-encoding` are absent, and `transfer-encoding` is no longer removed in that code path.\n\n## Workarounds\nIf upgrade is not immediately possible:\n- Block chunked `DELETE`/`OPTIONS` requests on rewritten routes at your edge/proxy.\n- Enforce authentication/authorization on backend routes per our [security guidance](https://nextjs.org/docs/app/guides/data-security).", - "display_name": "Next.js", - "system_id": "nextjs", - "category": "frameworks", - "severity": "medium", - "cvss_score": 4.0, - "exploit_status": "unknown", - "published_at": "2026-03-17T16:17:15Z", - "updated_at": "2026-03-17T16:31:26.646070Z", - "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-ggv3-7p47-pfv8", - "secondary_source_urls": [ - "https://github.com/vercel/next.js/commit/dc98c04f376c6a1df76ec3e0a2d07edf4abdabd6", - "https://github.com/vercel/next.js", - "https://github.com/vercel/next.js/releases/tag/v15.5.13", - "https://github.com/vercel/next.js/releases/tag/v16.1.7" - ], - "aliases": [ - "CVE-2026-29057", - "GHSA-ggv3-7p47-pfv8" - ], - "secure_code_topics": [ - "authz-server-side-recheck", - "proxy-trust-boundary", - "token-cookie-storage", - "request-smuggling-boundary", - "dependency-upgrade-policy" - ], - "verification_status": "triage-manual", - "verification_mode": "synthetic", - "artifact_mode": "official-source", - "blocked_reason": null, - "browser_evidence": { - "required": false, - "present": false, - "refs": [] - } - } -} +{} diff --git a/08-threat-intel/generated/dashboard/architecture.json b/08-threat-intel/generated/dashboard/architecture.json index 042bdd03..cac6d339 100644 --- a/08-threat-intel/generated/dashboard/architecture.json +++ b/08-threat-intel/generated/dashboard/architecture.json @@ -1,5 +1,5 @@ { - "generated_at": "2026-03-18T14:45:55+00:00", + "generated_at": "2026-03-18T17:52:49+00:00", "title": "\u5f53\u524d\u67b6\u6784\u5e93", "summary": "\u5de5\u4f5c\u53f0\u3001\u63a7\u5236\u9762\u3001\u6570\u636e\u5c42\u3001\u6388\u6743\u8fb9\u754c\u4e0e\u7cfb\u7edf\u8986\u76d6\u7684\u5f53\u524d\u771f\u503c\u89c6\u56fe\u3002", "sections": [ @@ -31,7 +31,7 @@ }, { "label": "\u5f53\u524d\u6f0f\u6d1e\u6761\u76ee", - "value": "5" + "value": "0" } ], "fields": [ @@ -49,7 +49,7 @@ }, { "label": "\u751f\u6210\u65f6\u95f4", - "value": "2026-03-18T14:45:55+00:00" + "value": "2026-03-18T17:52:49+00:00" } ], "links": [ @@ -268,6 +268,16 @@ "href": "/docs/source-map.html", "description": "\u7cfb\u7edf\u8986\u76d6\u3001\u6765\u6e90\u548c\u8f93\u51fa\u76ee\u5f55\u771f\u503c\u3002" }, + { + "label": "source catalog audit", + "href": "/docs/source-catalog-audit.html", + "description": "active/retired source \u5ba1\u8ba1\u3001\u66ff\u4ee3\u5173\u7cfb\u4e0e\u8986\u76d6\u6458\u8981\u3002" + }, + { + "label": "retired sources", + "href": "/docs/retired-sources.html", + "description": "\u9000\u5f79\u6e90\u3001\u9000\u5f79\u539f\u56e0\u4e0e replacement map\u3002" + }, { "label": "repro-map \u771f\u503c", "href": "/docs/repro-map.html", @@ -298,6 +308,21 @@ "href": "/data/completeness.json", "description": "\u6700\u65b0 advisory \u5b8c\u6574\u5ea6\u3001\u7cfb\u7edf/family \u8fdb\u5ea6\u4e0e ingest \u5065\u5eb7\u5ea6\u3002" }, + { + "label": "source-health.json", + "href": "/data/source-health.json", + "description": "active source \u5065\u5eb7\u5ea6\u3001\u7cfb\u7edf\u5206\u5e03\u4e0e\u5931\u8d25\u5206\u7c7b\u3002" + }, + { + "label": "alerts.json", + "href": "/data/alerts.json", + "description": "source \u544a\u8b66\u72b6\u6001\u673a\u3001failure streak \u4e0e resolved \u8bb0\u5f55\u3002" + }, + { + "label": "monitor-summary.json", + "href": "/data/monitor-summary.json", + "description": "\u6bcf\u65e5\u76d1\u63a7\u6458\u8981\u3001open alerts \u4e0e\u6700\u8fd1\u5168\u7eff\u65f6\u95f4\u3002" + }, { "label": "runs.json", "href": "/runs.json", @@ -322,6 +347,11 @@ "label": "architecture.json", "href": "/architecture.json", "description": "\u5f53\u524d\u67b6\u6784\u5e93\u7ed3\u6784\u5316 JSON\u3002" + }, + { + "label": "source-catalog-audit.json", + "href": "/data/source-catalog-audit.json", + "description": "source catalog \u5ba1\u8ba1\u771f\u503c\u4e0e retired/replacement \u5173\u7cfb\u3002" } ], "fields": [ @@ -484,7 +514,7 @@ "open": false, "badges": [ "\u8fd1\u4e24\u5e74\u5168\u91cf", - "\u5b98\u65b9\u6e90 2", + "\u5b98\u65b9\u6e90 3", "\u751f\u6001\u6e90 0", "\u7814\u7a76\u6e90 0" ], @@ -518,7 +548,7 @@ "fields": [ { "label": "\u5b98\u65b9\u6765\u6e90", - "value": "Discourse Meta Security\nGitHub Discourse Advisories" + "value": "Discourse Meta Security\nDiscourse Release Notes RSS\nGitHub Discourse Advisories" }, { "label": "\u751f\u6001\u6765\u6e90", @@ -570,7 +600,7 @@ "badges": [ "\u5386\u53f2\u5168\u91cf", "\u5b98\u65b9\u6e90 2", - "\u751f\u6001\u6e90 1", + "\u751f\u6001\u6e90 2", "\u7814\u7a76\u6e90 0" ], "fields": [ @@ -607,7 +637,7 @@ }, { "label": "\u751f\u6001\u6765\u6e90", - "value": "Drupal Security Advisories Site" + "value": "Drupal Security Advisories Site\nGHSA Drupal Core" }, { "label": "\u7814\u7a76\u6765\u6e90", @@ -1440,7 +1470,7 @@ "open": false, "badges": [ "\u8fd1\u4e24\u5e74\u5168\u91cf", - "\u5b98\u65b9\u6e90 2", + "\u5b98\u65b9\u6e90 4", "\u751f\u6001\u6e90 0", "\u7814\u7a76\u6e90 0" ], @@ -1474,7 +1504,7 @@ "fields": [ { "label": "\u5b98\u65b9\u6765\u6e90", - "value": "Django Security RSS\nOSV Django" + "value": "Django Security RSS\nDjango Security Weblog\nDjango Security Releases Archive\nOSV Django" }, { "label": "\u751f\u6001\u6765\u6e90", @@ -4712,7 +4742,7 @@ "open": false, "badges": [ "\u8fd1\u4e24\u5e74\u5168\u91cf", - "\u5b98\u65b9\u6e90 2", + "\u5b98\u65b9\u6e90 3", "\u751f\u6001\u6e90 0", "\u7814\u7a76\u6e90 0" ], @@ -4746,7 +4776,7 @@ "fields": [ { "label": "\u5b98\u65b9\u6765\u6e90", - "value": "HAProxy Security Advisories\nNVD HAProxy" + "value": "HAProxy Security Advisories\nHAProxy Blog Feed\nNVD HAProxy" }, { "label": "\u751f\u6001\u6765\u6e90", @@ -4988,8 +5018,8 @@ "open": false, "badges": [ "\u5386\u53f2\u5168\u91cf", - "\u5b98\u65b9\u6e90 2", - "\u751f\u6001\u6e90 1", + "\u5b98\u65b9\u6e90 3", + "\u751f\u6001\u6e90 2", "\u7814\u7a76\u6e90 0" ], "fields": [ @@ -5022,11 +5052,11 @@ "fields": [ { "label": "\u5b98\u65b9\u6765\u6e90", - "value": "Adobe Security Bulletins\nNVD Adobe Commerce" + "value": "Adobe Security Bulletins\nAdobe Magento Security Index\nNVD Adobe Commerce" }, { "label": "\u751f\u6001\u6765\u6e90", - "value": "Sansec Research" + "value": "GHSA Adobe Commerce\nSansec Research" }, { "label": "\u7814\u7a76\u6765\u6e90", @@ -5857,15 +5887,15 @@ }, { "label": "Advisory \u6570", - "value": "5" + "value": "0" }, { "label": "\u72b6\u6001\u7c7b\u578b", - "value": "1" + "value": "0" }, { "label": "\u6700\u8fd1\u5931\u8d25", - "value": "5" + "value": "0" } ], "items": [ @@ -5873,23 +5903,7 @@ "title": "\u72b6\u6001\u5206\u5e03", "summary": "verification_status \u5f53\u524d\u8ba1\u6570\u3002", "open": false, - "items": [ - { - "title": "\u4eba\u5de5\u5206\u8bca", - "summary": "\u5f53\u524d\u7d2f\u8ba1 5 \u6761\u3002", - "open": false, - "fields": [ - { - "label": "\u72b6\u6001\u7f16\u7801", - "value": "triage-manual" - }, - { - "label": "\u6570\u91cf", - "value": "5" - } - ] - } - ] + "items": [] }, { "title": "\u6700\u8fd1\u5931\u8d25", @@ -5897,134 +5911,9 @@ "open": false, "items": [ { - "title": "Next.js: Unbounded postponed resume buffering can lead to DoS", - "summary": "\u65e0\u989d\u5916\u963b\u585e\u8bf4\u660e\u3002", - "open": false, - "badges": [ - "\u4eba\u5de5\u5206\u8bca" - ], - "fields": [ - { - "label": "\u8fd0\u884c ID", - "value": "-" - }, - { - "label": "\u6f0f\u6d1e\u6761\u76ee", - "value": "nextjs--CVE-2026-27979" - }, - { - "label": "\u72b6\u6001", - "value": "\u4eba\u5de5\u5206\u8bca" - }, - { - "label": "\u963b\u585e\u539f\u56e0", - "value": "-" - } - ] - }, - { - "title": "Next.js: Unbounded next/image disk cache growth can exhaust storage", - "summary": "\u65e0\u989d\u5916\u963b\u585e\u8bf4\u660e\u3002", - "open": false, - "badges": [ - "\u4eba\u5de5\u5206\u8bca" - ], - "fields": [ - { - "label": "\u8fd0\u884c ID", - "value": "-" - }, - { - "label": "\u6f0f\u6d1e\u6761\u76ee", - "value": "nextjs--CVE-2026-27980" - }, - { - "label": "\u72b6\u6001", - "value": "\u4eba\u5de5\u5206\u8bca" - }, - { - "label": "\u963b\u585e\u539f\u56e0", - "value": "-" - } - ] - }, - { - "title": "Next.js: HTTP request smuggling in rewrites", - "summary": "\u65e0\u989d\u5916\u963b\u585e\u8bf4\u660e\u3002", - "open": false, - "badges": [ - "\u4eba\u5de5\u5206\u8bca" - ], - "fields": [ - { - "label": "\u8fd0\u884c ID", - "value": "-" - }, - { - "label": "\u6f0f\u6d1e\u6761\u76ee", - "value": "nextjs--CVE-2026-29057" - }, - { - "label": "\u72b6\u6001", - "value": "\u4eba\u5de5\u5206\u8bca" - }, - { - "label": "\u963b\u585e\u539f\u56e0", - "value": "-" - } - ] - }, - { - "title": "Next.js: null origin can bypass Server Actions CSRF checks", - "summary": "\u65e0\u989d\u5916\u963b\u585e\u8bf4\u660e\u3002", - "open": false, - "badges": [ - "\u4eba\u5de5\u5206\u8bca" - ], - "fields": [ - { - "label": "\u8fd0\u884c ID", - "value": "-" - }, - { - "label": "\u6f0f\u6d1e\u6761\u76ee", - "value": "nextjs--CVE-2026-27978" - }, - { - "label": "\u72b6\u6001", - "value": "\u4eba\u5de5\u5206\u8bca" - }, - { - "label": "\u963b\u585e\u539f\u56e0", - "value": "-" - } - ] - }, - { - "title": "Next.js: null origin can bypass dev HMR websocket CSRF checks", - "summary": "\u65e0\u989d\u5916\u963b\u585e\u8bf4\u660e\u3002", - "open": false, - "badges": [ - "\u4eba\u5de5\u5206\u8bca" - ], - "fields": [ - { - "label": "\u8fd0\u884c ID", - "value": "-" - }, - { - "label": "\u6f0f\u6d1e\u6761\u76ee", - "value": "nextjs--CVE-2026-27977" - }, - { - "label": "\u72b6\u6001", - "value": "\u4eba\u5de5\u5206\u8bca" - }, - { - "label": "\u963b\u585e\u539f\u56e0", - "value": "-" - } - ] + "title": "\u6682\u65e0\u5931\u8d25\u6837\u672c", + "summary": "\u5f53\u524d summary.json \u4e2d\u6ca1\u6709 recent_failures\u3002", + "open": false } ] } diff --git a/08-threat-intel/generated/dashboard/assets/app.js b/08-threat-intel/generated/dashboard/assets/app.js index 2b5faada..a4c057c9 100644 --- a/08-threat-intel/generated/dashboard/assets/app.js +++ b/08-threat-intel/generated/dashboard/assets/app.js @@ -43,6 +43,8 @@ const DOC_HUB_ITEMS = [ { title: "仓库入口镜像", href: "/docs/root-readme.html", description: "根 README 的本地镜像,包含能力矩阵与主入口。", badge: "readme" }, { title: "授权模型", href: "/docs/authorization-model.html", description: "目标范围、授权模型、最小化验证建议和记录要求。", badge: "scope" }, { title: "source-map 镜像", href: "/docs/source-map.html", description: "系统覆盖、来源、输出目录和 secure-code 主题真值。", badge: "source-map" }, + { title: "source catalog audit", href: "/docs/source-catalog-audit.html", description: "active/retired source、replacement map 与覆盖摘要。", badge: "audit" }, + { title: "retired sources", href: "/docs/retired-sources.html", description: "退役源、退役原因和 replacement_sources 真值。", badge: "retired" }, { title: "repro-map 镜像", href: "/docs/repro-map.html", description: "默认漏洞家族、浏览器要求和日志策略真值。", badge: "repro-map" }, { title: "覆盖矩阵镜像", href: "/docs/coverage-matrix.html", description: "当前全库覆盖矩阵的本地镜像。", badge: "coverage" }, { title: "安全编码索引", href: "/docs/secure-code-index.html", description: "secure-code 修复主题索引镜像。", badge: "secure-code" }, @@ -52,6 +54,10 @@ const DOC_HUB_ITEMS = [ const DATA_HUB_ITEMS = [ { title: "summary.json", href: "/summary.json", description: "全局摘要、状态分布、最近失败与系统汇总。", badge: "json" }, { title: "completeness.json", href: "/data/completeness.json", description: "最新 advisory 完整度、系统/family 进度与 ingest 健康度。", badge: "json" }, + { title: "source-health.json", href: "/data/source-health.json", description: "active source 健康度、失败分类与系统分布。", badge: "json" }, + { title: "alerts.json", href: "/data/alerts.json", description: "source 告警状态机、failure streak 与 resolved 记录。", badge: "json" }, + { title: "monitor-summary.json", href: "/data/monitor-summary.json", description: "每日监控摘要、open alerts 与最近全绿时间。", badge: "json" }, + { title: "source-catalog-audit.json", href: "/data/source-catalog-audit.json", description: "source catalog 审计真值与 retired/replacement 关系。", badge: "json" }, { title: "runs.json", href: "/runs.json", description: "最近运行的结构化详情,可用于 UI 和调试。", badge: "json" }, { title: "systems.json", href: "/systems.json", description: "系统级覆盖、分类、更新时间和浏览器证据统计。", badge: "json" }, { title: "advisories.json", href: "/advisories.json", description: "漏洞条目元数据、来源和 secure-code 主题。", badge: "json" }, @@ -87,6 +93,9 @@ const state = { profiles: {}, architecture: null, completeness: null, + sourceHealth: null, + alerts: [], + monitorSummary: null, selectedRunId: null, selectedArtifact: null, refreshHandle: null, @@ -279,38 +288,41 @@ function familyOptions() { function metricCards() { const completeness = state.completeness || state.summary?.completeness || {}; - const successCount = Number(completeness.verified_real || 0) + Number(completeness.verified_synthetic || 0); - const blockedCount = Number(completeness.blocked || 0); - const inProgressCount = Number(completeness.manual || 0); + const monitoring = state.monitorSummary || state.summary?.monitoring || {}; const advisoryTotal = Number(completeness.advisory_total || state.summary?.advisory_count || 0); + const advisorySuccess = Number(completeness.verified_real || 0); + const activeSources = Number(monitoring.active_source_count || state.sourceHealth?.active_source_count || 0); + const greenSources = Number(monitoring.green_source_count || state.sourceHealth?.green_source_count || 0); + const openAlerts = Number(monitoring.open_alert_count || state.sourceHealth?.open_alert_count || 0); + const lastFullyGreen = monitoring.last_fully_green_run || state.sourceHealth?.last_fully_green_run || ""; return [ { - label: "最新 advisory", - value: advisoryTotal, + label: "advisory 完整度", + value: `${advisorySuccess}/${advisoryTotal}`, note: `历史运行 ${state.summary?.run_count || 0} 次`, - color: "var(--accent-purple)", + color: "var(--accent-green)", iconName: "report" }, { - label: "实证成功", - value: successCount, - note: "真实版本 + 合成靶场", - color: "var(--accent-green)", + label: "active sources", + value: activeSources, + note: `green ${greenSources}`, + color: "var(--accent-blue)", iconName: "shield" }, { - label: "当前阻塞", - value: blockedCount, - note: "latest advisory 状态里的 blocked-*", + label: "open alerts", + value: openAlerts, + note: "source-health 告警状态机", color: "var(--accent-red)", iconName: "failure" }, { - label: "待处理 / 进行中", - value: inProgressCount, - note: "人工分诊或待补证据的 latest advisory", - color: "var(--accent-blue)", + label: "最近全绿", + value: lastFullyGreen ? formatDateTime(lastFullyGreen) : "-", + note: "active source 集合最近一次全绿", + color: "var(--accent-purple)", iconName: "timeline" } ]; @@ -762,6 +774,7 @@ function renderPanel(panelKey, title, meta, iconName, content) { function renderCompletenessPanel(panelKey, compact = false) { const completeness = state.completeness || state.summary?.completeness || {}; + const sourceHealth = state.sourceHealth || completeness.source_health || {}; const systems = (state.completeness?.systems || []).map((system) => `
${escapeHtml(system.system_id)} @@ -795,12 +808,21 @@ function renderCompletenessPanel(panelKey, compact = false) { ingest failures ${escapeHtml(state.completeness?.ingest_health?.failure_count || 0)}
+
+ active sources + ${escapeHtml(sourceHealth.active_source_count || 0)} +
+
+ open alerts + ${escapeHtml(sourceHealth.open_alert_count || 0)} +
${systems || `
暂无系统完整度数据。
`}
${compact ? "" : `
${icon("docs")}打开中文报告 ${icon("json")}打开 completeness.json + ${icon("json")}打开 source-health.json
${failures.length ? `
Ingest 未清零
${escapeHtml(failures.join(" | "))}
` : ""} `} @@ -808,6 +830,66 @@ function renderCompletenessPanel(panelKey, compact = false) { ); } +function renderSourceHealthPanel(panelKey, compact = false) { + const sourceHealth = state.sourceHealth || {}; + const alerts = state.alerts || []; + const failures = (sourceHealth.failures || []).slice(0, 6); + const openAlertItems = alerts.filter((item) => item.status === "open"); + const openAlerts = openAlertItems.slice(0, 6); + const failureCards = failures.length + ? failures.map((item) => ` +
+ ${escapeHtml(item.system_id || "-")} · ${escapeHtml(item.source_name || "-")} +
${escapeHtml(item.category || "unknown")} · ${escapeHtml(item.message || item.summary || "-")}
+
+ `).join("") + : `
当前 active source 集合全绿。
`; + const alertCards = openAlerts.length + ? openAlerts.map((item) => ` +
+ ${escapeHtml(item.system_id || "-")} · ${escapeHtml(item.source_name || "-")} +
streak ${escapeHtml(item.failure_streak || 0)} · ${escapeHtml(item.last_category || "-")}
+
+ `).join("") + : `
当前没有 open alert。
`; + return renderPanel( + panelKey, + "Source Health 与告警", + `${escapeHtml(sourceHealth.green_source_count || 0)}/${escapeHtml(sourceHealth.active_source_count || 0)}`, + "shield", + ` +
+
+ green + ${escapeHtml(sourceHealth.green_source_count || 0)} +
+
+ failures + ${escapeHtml(sourceHealth.failure_count || 0)} +
+
+ open alerts + ${escapeHtml(openAlertItems.length)} +
+
+ last fully green + ${escapeHtml(sourceHealth.last_fully_green_run ? formatDateTime(sourceHealth.last_fully_green_run) : "-")} +
+
+ ${compact ? "" : ` +
+ ${icon("json")}source-health.json + ${icon("json")}alerts.json + ${icon("json")}monitor-summary.json + ${icon("docs")}source catalog audit +
+ `} +
${failureCards}
+
${alertCards}
+ ` + ); +} + function renderArchitectureFields(fields = []) { if (!fields.length) return ""; return ` @@ -1185,6 +1267,7 @@ function renderOverviewWorkspace() {
根入口保留为概览页,同时新增运行、系统、架构、文档和数据的独立 URL。顶部菜单负责分类切换,搜索与筛选会同步到地址栏。
${renderCompletenessPanel("overview_completeness")} + ${renderSourceHealthPanel("overview_source_health")} ${renderPanel("overview_runs", "最新运行", `${escapeHtml(runs.length)} 条`, "queue", renderRunList(runs, "暂无运行数据。"))} ${renderPanel("overview_systems", "系统覆盖概览", `${escapeHtml(systems.length)} 个系统`, "systems", `
${renderSystemCards(systems)}
`)} ${renderArchitecturePanel()} @@ -1251,6 +1334,7 @@ function renderDocsWorkspace() {
不再把所有入口混在首页链接堆里。这里按说明、设计、真值镜像和 secure-code 索引集中展示。
${renderCompletenessPanel("docs_completeness", true)} + ${renderSourceHealthPanel("docs_source_health", true)} ${renderPanel("docs_hub", "文档与镜像页", `${escapeHtml(DOC_HUB_ITEMS.length)} 个入口`, "docs", renderHubCards(DOC_HUB_ITEMS))} `; @@ -1272,6 +1356,7 @@ function renderDataWorkspace() {
summary、runs、systems、advisories、profiles、architecture 已单独归入数据中心,避免和文档、运行详情混在一个地址里。
${renderCompletenessPanel("data_completeness", true)} + ${renderSourceHealthPanel("data_source_health")} ${renderPanel("data_hub", "JSON 与生成数据", `${escapeHtml(DATA_HUB_ITEMS.length)} 个入口`, "json", renderHubCards(DATA_HUB_ITEMS))} `; @@ -1473,14 +1558,17 @@ async function loadData(preserveSelection = true) { renderSyncState("loading", "刷新中", `本地时间 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`); try { - const [summary, runs, systems, advisories, profiles, architecture, completeness] = await Promise.all([ + const [summary, runs, systems, advisories, profiles, architecture, completeness, sourceHealth, alerts, monitorSummary] = await Promise.all([ fetchJson("/summary.json"), fetchJson("/runs.json"), fetchJson("/systems.json"), fetchJson("/advisories.json"), fetchJson("/profiles.json"), fetchJson("/architecture.json"), - fetchJson("/data/completeness.json") + fetchJson("/data/completeness.json"), + fetchJson("/data/source-health.json"), + fetchJson("/data/alerts.json"), + fetchJson("/data/monitor-summary.json") ]); state.summary = summary; @@ -1490,6 +1578,9 @@ async function loadData(preserveSelection = true) { state.profiles = profiles; state.architecture = architecture; state.completeness = completeness; + state.sourceHealth = sourceHealth; + state.alerts = alerts; + state.monitorSummary = monitorSummary; const filtered = filteredRuns(); const candidate = preserveSelection ? (state.selectedRunId || previousRunId) : state.selectedRunId; diff --git a/08-threat-intel/generated/dashboard/data/alerts.json b/08-threat-intel/generated/dashboard/data/alerts.json new file mode 100644 index 00000000..fe51488c --- /dev/null +++ b/08-threat-intel/generated/dashboard/data/alerts.json @@ -0,0 +1 @@ +[] diff --git a/08-threat-intel/generated/dashboard/data/completeness.json b/08-threat-intel/generated/dashboard/data/completeness.json index 4a1223e6..c465bbd5 100644 --- a/08-threat-intel/generated/dashboard/data/completeness.json +++ b/08-threat-intel/generated/dashboard/data/completeness.json @@ -1,9 +1,7 @@ { - "generated_at": "2026-03-18T14:45:55+00:00", - "advisory_total": 5, - "latest_statuses": { - "triage-manual": 5 - }, + "generated_at": "2026-03-18T17:52:49+00:00", + "advisory_total": 0, + "latest_statuses": {}, "historical_statuses": { "verified-real": 136, "blocked-artifact": 3, @@ -12,76 +10,52 @@ "verified_real": 0, "verified_synthetic": 0, "blocked": 0, - "manual": 5, + "manual": 0, "verified_ratio": 0.0, "complete": false, - "systems": [ - { - "system_id": "nextjs", - "display_name": "Next.js", - "total": 5, - "verified_real": 0, - "verified_synthetic": 0, - "blocked": 0, - "manual": 5, - "families": [ - { - "family": "proxy-boundary", - "total": 4, - "verified_real": 0, - "verified_synthetic": 0, - "blocked": 0, - "manual": 4 - }, - { - "family": "request-smuggling", - "total": 1, - "verified_real": 0, - "verified_synthetic": 0, - "blocked": 0, - "manual": 1 - } - ] - } - ], + "systems": [], "ingest_health": { - "failure_count": 29, - "failures": [ - "drupal::Drupal Security Advisories Site::HTTPError", - "discourse::Discourse Meta Security::HTTPError", - "adobe-commerce::Adobe Security Bulletins::ConnectionError", - "react::GitHub Global Advisories::TypeError", - "nextjs::GitHub Global Advisories::AttributeError", - "vue::GitHub Global Advisories::HTTPError", - "nuxt::GitHub Global Advisories::HTTPError", - "vite::GitHub Global Advisories::HTTPError", - "angular::GitHub Global Advisories::HTTPError", - "sveltekit::GitHub Global Advisories::HTTPError", - "astro::GitHub Global Advisories::HTTPError", - "express::GitHub Global Advisories::HTTPError", - "nestjs::GitHub Global Advisories::HTTPError", - "koa::GitHub Global Advisories::HTTPError", - "fastify::GitHub Global Advisories::HTTPError", - "hapi::GitHub Global Advisories::HTTPError", - "undici::GitHub Global Advisories::HTTPError", - "webpack::GitHub Global Advisories::HTTPError", - "esbuild::GitHub Global Advisories::HTTPError", - "spring-framework::GitHub Global Advisories::HTTPError", - "spring-security::GitHub Global Advisories::HTTPError", - "spring-boot::GitHub Global Advisories::HTTPError", - "laravel::GitHub Global Advisories::HTTPError", - "symfony::GitHub Global Advisories::HTTPError", - "django::Django Security RSS::HTTPError", - "flask::GitHub Global Advisories::HTTPError", - "werkzeug::GitHub Global Advisories::HTTPError", - "rails::GitHub Global Advisories::HTTPError", - "haproxy::HAProxy Security Advisories::HTTPError" - ] + "failure_count": 0, + "failures": [] + }, + "source_health": { + "active_source_count": 110, + "green_source_count": 110, + "failure_count": 0, + "last_fully_green_run": "2026-03-18T17:44:31+00:00", + "open_alert_count": 0, + "resolved_alert_count": 0 + }, + "monitor_summary": { + "generated_at": "2026-03-18T17:44:31+00:00", + "active_source_count": 110, + "green_source_count": 110, + "source_failure_count": 0, + "open_alert_count": 0, + "resolved_alert_count": 0, + "last_fully_green_run": "2026-03-18T17:44:31+00:00", + "source_catalog": { + "system_count": 62, + "source_count": 146, + "retired_source_count": 36 + }, + "ingest": { + "new_count": 0, + "updated_count": 0, + "failure_count": 0, + "systems_touched": [] + }, + "validation": { + "passed": true, + "error_count": 0, + "errors": [] + } }, "historical_blockers": [ "Docker daemon unavailable caused provision-compose-environment blocked-artifact.", "Family profiles previously used note-only attack runners and dry-run placeholders.", "Baseline and browser steps were skipped when environment readiness was not enforced.", - "Latest completeness now uses one advisory -> latest run semantics instead of historical run piles." + "Latest completeness now uses one advisory -> latest run semantics instead of historical run piles.", + "Source health now counts only status=active sources; retired sources are audited separately with replacement links." ] } diff --git a/08-threat-intel/generated/dashboard/data/monitor-summary.json b/08-threat-intel/generated/dashboard/data/monitor-summary.json new file mode 100644 index 00000000..963c1f19 --- /dev/null +++ b/08-threat-intel/generated/dashboard/data/monitor-summary.json @@ -0,0 +1,25 @@ +{ + "generated_at": "2026-03-18T17:44:31+00:00", + "active_source_count": 110, + "green_source_count": 110, + "source_failure_count": 0, + "open_alert_count": 0, + "resolved_alert_count": 0, + "last_fully_green_run": "2026-03-18T17:44:31+00:00", + "source_catalog": { + "system_count": 62, + "source_count": 146, + "retired_source_count": 36 + }, + "ingest": { + "new_count": 0, + "updated_count": 0, + "failure_count": 0, + "systems_touched": [] + }, + "validation": { + "passed": true, + "error_count": 0, + "errors": [] + } +} diff --git a/08-threat-intel/generated/dashboard/data/source-catalog-audit.json b/08-threat-intel/generated/dashboard/data/source-catalog-audit.json new file mode 100644 index 00000000..f3503cab --- /dev/null +++ b/08-threat-intel/generated/dashboard/data/source-catalog-audit.json @@ -0,0 +1,1655 @@ +{ + "generated_at": "2026-03-18T17:41:42+00:00", + "system_count": 62, + "source_count": 146, + "active_source_count": 110, + "retired_source_count": 36, + "systems_with_active_official": 62, + "systems_with_machine_readable_source": 57, + "systems": [ + { + "system_id": "adminer", + "display_name": "Adminer", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 1, + "active_source_total": 1, + "retired_source_total": 0, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "adobe-commerce", + "display_name": "Adobe Commerce", + "category": "ecommerce", + "tier": "history-full", + "source_total": 5, + "active_source_total": 2, + "retired_source_total": 3, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "angular", + "display_name": "Angular", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "apache-httpd", + "display_name": "Apache HTTP Server", + "category": "servers", + "tier": "history-full", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 3, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 2, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "apache-tomcat", + "display_name": "Apache Tomcat", + "category": "servers", + "tier": "history-full", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 3, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 2, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "aspnet-core", + "display_name": "ASP.NET Core", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 1, + "active_source_total": 1, + "retired_source_total": 0, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "astro", + "display_name": "Astro", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "caddy", + "display_name": "Caddy", + "category": "servers", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "directus", + "display_name": "Directus", + "category": "cms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "discourse", + "display_name": "Discourse", + "category": "cms", + "tier": "rolling-24m", + "source_total": 3, + "active_source_total": 1, + "retired_source_total": 2, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "django", + "display_name": "Django", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 4, + "active_source_total": 3, + "retired_source_total": 1, + "official_active": 3, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "drupal", + "display_name": "Drupal", + "category": "cms", + "tier": "history-full", + "source_total": 4, + "active_source_total": 2, + "retired_source_total": 2, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 2, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "echo", + "display_name": "Echo", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 1, + "active_source_total": 1, + "retired_source_total": 0, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "esbuild", + "display_name": "esbuild", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "express", + "display_name": "Express", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "fastify", + "display_name": "Fastify", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "flask", + "display_name": "Flask", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "ghost", + "display_name": "Ghost", + "category": "cms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "gin", + "display_name": "Gin", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 1, + "active_source_total": 1, + "retired_source_total": 0, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "gitea", + "display_name": "Gitea", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "gitlab-ce", + "display_name": "GitLab CE", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 1, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "grafana", + "display_name": "Grafana", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "hapi", + "display_name": "Hapi", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "haproxy", + "display_name": "HAProxy", + "category": "servers", + "tier": "rolling-24m", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 2, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "jenkins", + "display_name": "Jenkins", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "joomla", + "display_name": "Joomla", + "category": "cms", + "tier": "history-full", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "kibana", + "display_name": "Kibana", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "koa", + "display_name": "Koa", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "laravel", + "display_name": "Laravel", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "magento-open-source", + "display_name": "Magento Open Source", + "category": "ecommerce", + "tier": "history-full", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 1, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "mattermost", + "display_name": "Mattermost", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "mediawiki", + "display_name": "MediaWiki", + "category": "cms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "medusa", + "display_name": "Medusa", + "category": "ecommerce", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "moodle", + "display_name": "Moodle", + "category": "cms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "nestjs", + "display_name": "NestJS", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "nextjs", + "display_name": "Next.js", + "category": "frameworks", + "tier": "history-full", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "nginx", + "display_name": "Nginx", + "category": "servers", + "tier": "history-full", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 3, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 2, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "nodejs", + "display_name": "Node.js", + "category": "frameworks", + "tier": "history-full", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "nuxt", + "display_name": "Nuxt", + "category": "frameworks", + "tier": "history-full", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "opencart", + "display_name": "OpenCart", + "category": "ecommerce", + "tier": "history-full", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "openmage", + "display_name": "OpenMage / Mage-OS", + "category": "ecommerce", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "phpmyadmin", + "display_name": "phpMyAdmin", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "prestashop", + "display_name": "PrestaShop", + "category": "ecommerce", + "tier": "history-full", + "source_total": 3, + "active_source_total": 3, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 1, + "research_active": 0, + "machine_readable_active": 0, + "has_active_official": true, + "has_machine_readable_source": false + }, + { + "system_id": "rails", + "display_name": "Ruby on Rails", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "react", + "display_name": "React", + "category": "frameworks", + "tier": "history-full", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "redmine", + "display_name": "Redmine", + "category": "platforms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "saleor", + "display_name": "Saleor", + "category": "ecommerce", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "shopware", + "display_name": "Shopware", + "category": "ecommerce", + "tier": "history-full", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "spring-boot", + "display_name": "Spring Boot", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 0, + "has_active_official": true, + "has_machine_readable_source": false + }, + { + "system_id": "spring-framework", + "display_name": "Spring Framework", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 0, + "has_active_official": true, + "has_machine_readable_source": false + }, + { + "system_id": "spring-security", + "display_name": "Spring Security", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 0, + "has_active_official": true, + "has_machine_readable_source": false + }, + { + "system_id": "strapi", + "display_name": "Strapi", + "category": "cms", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "sveltekit", + "display_name": "SvelteKit", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "symfony", + "display_name": "Symfony", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "traefik", + "display_name": "Traefik", + "category": "servers", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 2, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "undici", + "display_name": "Undici", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "vite", + "display_name": "Vite", + "category": "frameworks", + "tier": "history-full", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "vue", + "display_name": "Vue", + "category": "frameworks", + "tier": "history-full", + "source_total": 3, + "active_source_total": 2, + "retired_source_total": 1, + "official_active": 2, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "webpack", + "display_name": "webpack", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "werkzeug", + "display_name": "Werkzeug", + "category": "frameworks", + "tier": "rolling-24m", + "source_total": 2, + "active_source_total": 1, + "retired_source_total": 1, + "official_active": 1, + "ecosystem_active": 0, + "research_active": 0, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + }, + { + "system_id": "woocommerce", + "display_name": "WooCommerce", + "category": "ecommerce", + "tier": "history-full", + "source_total": 4, + "active_source_total": 4, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 2, + "research_active": 0, + "machine_readable_active": 0, + "has_active_official": true, + "has_machine_readable_source": false + }, + { + "system_id": "wordpress", + "display_name": "WordPress", + "category": "cms", + "tier": "history-full", + "source_total": 6, + "active_source_total": 6, + "retired_source_total": 0, + "official_active": 2, + "ecosystem_active": 3, + "research_active": 1, + "machine_readable_active": 1, + "has_active_official": true, + "has_machine_readable_source": true + } + ], + "retired_sources": [ + { + "system_id": "adobe-commerce", + "display_name": "Adobe Commerce", + "source_name": "Adobe Security Bulletins", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.", + "replacement_sources": [ + "Adobe Magento Security Index", + "NVD Adobe Commerce", + "GHSA Adobe Commerce" + ], + "url": "https://helpx.adobe.com/security/products/magento.html" + }, + { + "system_id": "adobe-commerce", + "display_name": "Adobe Commerce", + "source_name": "GHSA Adobe Commerce", + "bucket": "ecosystem_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.", + "replacement_sources": [ + "Adobe Magento Security Index", + "NVD Adobe Commerce" + ], + "url": "" + }, + { + "system_id": "adobe-commerce", + "display_name": "Adobe Commerce", + "source_name": "Sansec Research", + "bucket": "ecosystem_sources", + "kind": "vendor-index", + "retired_reason": "Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.", + "replacement_sources": [ + "GHSA Adobe Commerce", + "Adobe Magento Security Index" + ], + "url": "https://sansec.io/research" + }, + { + "system_id": "angular", + "display_name": "Angular", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.", + "replacement_sources": [ + "OSV Angular" + ], + "url": "" + }, + { + "system_id": "astro", + "display_name": "Astro", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.", + "replacement_sources": [ + "OSV Astro" + ], + "url": "" + }, + { + "system_id": "discourse", + "display_name": "Discourse", + "source_name": "Discourse Meta Security", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "Meta security category HTML changed and no longer provides stable scrape semantics for health checks.", + "replacement_sources": [ + "Discourse Release Notes RSS", + "GitHub Discourse Advisories" + ], + "url": "https://meta.discourse.org/c/bug/security/40" + }, + { + "system_id": "discourse", + "display_name": "Discourse", + "source_name": "GitHub Discourse Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.", + "replacement_sources": [ + "Discourse Release Notes RSS" + ], + "url": "" + }, + { + "system_id": "django", + "display_name": "Django", + "source_name": "Django Security RSS", + "bucket": "official_sources", + "kind": "rss-feed", + "retired_reason": "Official security tag feed became unstable; use official weblog index and release archive instead.", + "replacement_sources": [ + "Django Security Weblog", + "Django Security Releases Archive" + ], + "url": "https://www.djangoproject.com/weblog/feeds/tags/security/" + }, + { + "system_id": "drupal", + "display_name": "Drupal", + "source_name": "Drupal Security Advisories Site", + "bucket": "ecosystem_sources", + "kind": "html-links", + "retired_reason": "Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.", + "replacement_sources": [ + "Drupal Security Advisories RSS", + "GHSA Drupal Core" + ], + "url": "https://www.drupal.org/security" + }, + { + "system_id": "drupal", + "display_name": "Drupal", + "source_name": "GHSA Drupal Core", + "bucket": "ecosystem_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.", + "replacement_sources": [ + "Drupal Security Advisories RSS", + "NVD Drupal" + ], + "url": "" + }, + { + "system_id": "esbuild", + "display_name": "esbuild", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.", + "replacement_sources": [ + "OSV esbuild" + ], + "url": "" + }, + { + "system_id": "express", + "display_name": "Express", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.", + "replacement_sources": [ + "OSV Express" + ], + "url": "" + }, + { + "system_id": "fastify", + "display_name": "Fastify", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.", + "replacement_sources": [ + "OSV Fastify" + ], + "url": "" + }, + { + "system_id": "flask", + "display_name": "Flask", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.", + "replacement_sources": [ + "OSV Flask" + ], + "url": "" + }, + { + "system_id": "hapi", + "display_name": "Hapi", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.", + "replacement_sources": [ + "OSV Hapi" + ], + "url": "" + }, + { + "system_id": "haproxy", + "display_name": "HAProxy", + "source_name": "HAProxy Security Advisories", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "Legacy haproxy.org security page no longer yields stable scrape results for monitoring.", + "replacement_sources": [ + "HAProxy Blog Feed" + ], + "url": "https://www.haproxy.org/security/" + }, + { + "system_id": "koa", + "display_name": "Koa", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.", + "replacement_sources": [ + "OSV Koa" + ], + "url": "" + }, + { + "system_id": "laravel", + "display_name": "Laravel", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.", + "replacement_sources": [ + "OSV Laravel" + ], + "url": "" + }, + { + "system_id": "mattermost", + "display_name": "Mattermost", + "source_name": "Mattermost Security Updates", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.", + "replacement_sources": [ + "NVD Mattermost" + ], + "url": "https://mattermost.com/security-updates/" + }, + { + "system_id": "mediawiki", + "display_name": "MediaWiki", + "source_name": "MediaWiki Security Releases", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.", + "replacement_sources": [ + "NVD MediaWiki" + ], + "url": "https://www.mediawiki.org/wiki/Security" + }, + { + "system_id": "moodle", + "display_name": "Moodle", + "source_name": "Moodle Security News", + "bucket": "official_sources", + "kind": "html-links", + "retired_reason": "Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.", + "replacement_sources": [ + "NVD Moodle" + ], + "url": "https://moodle.org/security/" + }, + { + "system_id": "nestjs", + "display_name": "NestJS", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.", + "replacement_sources": [ + "OSV NestJS" + ], + "url": "" + }, + { + "system_id": "nextjs", + "display_name": "Next.js", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.", + "replacement_sources": [ + "GitHub Next.js Advisories", + "OSV Next.js" + ], + "url": "" + }, + { + "system_id": "nuxt", + "display_name": "Nuxt", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.", + "replacement_sources": [ + "Nuxt Security", + "OSV Nuxt" + ], + "url": "" + }, + { + "system_id": "rails", + "display_name": "Ruby on Rails", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.", + "replacement_sources": [ + "OSV Rails" + ], + "url": "" + }, + { + "system_id": "react", + "display_name": "React", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.", + "replacement_sources": [ + "GitHub React Advisories", + "OSV React" + ], + "url": "" + }, + { + "system_id": "spring-boot", + "display_name": "Spring Boot", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.", + "replacement_sources": [ + "Spring Security Advisories" + ], + "url": "" + }, + { + "system_id": "spring-framework", + "display_name": "Spring Framework", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.", + "replacement_sources": [ + "Spring Security Advisories" + ], + "url": "" + }, + { + "system_id": "spring-security", + "display_name": "Spring Security", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.", + "replacement_sources": [ + "Spring Security Advisories" + ], + "url": "" + }, + { + "system_id": "sveltekit", + "display_name": "SvelteKit", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.", + "replacement_sources": [ + "OSV SvelteKit" + ], + "url": "" + }, + { + "system_id": "symfony", + "display_name": "Symfony", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.", + "replacement_sources": [ + "OSV Symfony" + ], + "url": "" + }, + { + "system_id": "undici", + "display_name": "Undici", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.", + "replacement_sources": [ + "OSV Undici" + ], + "url": "" + }, + { + "system_id": "vite", + "display_name": "Vite", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.", + "replacement_sources": [ + "Vite Security", + "OSV Vite" + ], + "url": "" + }, + { + "system_id": "vue", + "display_name": "Vue", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.", + "replacement_sources": [ + "Vue Security", + "OSV Vue" + ], + "url": "" + }, + { + "system_id": "webpack", + "display_name": "webpack", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.", + "replacement_sources": [ + "OSV webpack" + ], + "url": "" + }, + { + "system_id": "werkzeug", + "display_name": "Werkzeug", + "source_name": "GitHub Global Advisories", + "bucket": "official_sources", + "kind": "ghsa-global", + "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.", + "replacement_sources": [ + "OSV Werkzeug" + ], + "url": "" + } + ], + "replacement_map": [ + { + "system_id": "adobe-commerce", + "retired_source": "Adobe Security Bulletins", + "replacement_sources": [ + "Adobe Magento Security Index", + "NVD Adobe Commerce", + "GHSA Adobe Commerce" + ] + }, + { + "system_id": "adobe-commerce", + "retired_source": "GHSA Adobe Commerce", + "replacement_sources": [ + "Adobe Magento Security Index", + "NVD Adobe Commerce" + ] + }, + { + "system_id": "adobe-commerce", + "retired_source": "Sansec Research", + "replacement_sources": [ + "GHSA Adobe Commerce", + "Adobe Magento Security Index" + ] + }, + { + "system_id": "angular", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Angular" + ] + }, + { + "system_id": "astro", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Astro" + ] + }, + { + "system_id": "discourse", + "retired_source": "Discourse Meta Security", + "replacement_sources": [ + "Discourse Release Notes RSS", + "GitHub Discourse Advisories" + ] + }, + { + "system_id": "discourse", + "retired_source": "GitHub Discourse Advisories", + "replacement_sources": [ + "Discourse Release Notes RSS" + ] + }, + { + "system_id": "django", + "retired_source": "Django Security RSS", + "replacement_sources": [ + "Django Security Weblog", + "Django Security Releases Archive" + ] + }, + { + "system_id": "drupal", + "retired_source": "Drupal Security Advisories Site", + "replacement_sources": [ + "Drupal Security Advisories RSS", + "GHSA Drupal Core" + ] + }, + { + "system_id": "drupal", + "retired_source": "GHSA Drupal Core", + "replacement_sources": [ + "Drupal Security Advisories RSS", + "NVD Drupal" + ] + }, + { + "system_id": "esbuild", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV esbuild" + ] + }, + { + "system_id": "express", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Express" + ] + }, + { + "system_id": "fastify", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Fastify" + ] + }, + { + "system_id": "flask", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Flask" + ] + }, + { + "system_id": "hapi", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Hapi" + ] + }, + { + "system_id": "haproxy", + "retired_source": "HAProxy Security Advisories", + "replacement_sources": [ + "HAProxy Blog Feed" + ] + }, + { + "system_id": "koa", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Koa" + ] + }, + { + "system_id": "laravel", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Laravel" + ] + }, + { + "system_id": "mattermost", + "retired_source": "Mattermost Security Updates", + "replacement_sources": [ + "NVD Mattermost" + ] + }, + { + "system_id": "mediawiki", + "retired_source": "MediaWiki Security Releases", + "replacement_sources": [ + "NVD MediaWiki" + ] + }, + { + "system_id": "moodle", + "retired_source": "Moodle Security News", + "replacement_sources": [ + "NVD Moodle" + ] + }, + { + "system_id": "nestjs", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV NestJS" + ] + }, + { + "system_id": "nextjs", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "GitHub Next.js Advisories", + "OSV Next.js" + ] + }, + { + "system_id": "nuxt", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Nuxt Security", + "OSV Nuxt" + ] + }, + { + "system_id": "rails", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Rails" + ] + }, + { + "system_id": "react", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "GitHub React Advisories", + "OSV React" + ] + }, + { + "system_id": "spring-boot", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Spring Security Advisories" + ] + }, + { + "system_id": "spring-framework", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Spring Security Advisories" + ] + }, + { + "system_id": "spring-security", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Spring Security Advisories" + ] + }, + { + "system_id": "sveltekit", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV SvelteKit" + ] + }, + { + "system_id": "symfony", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Symfony" + ] + }, + { + "system_id": "undici", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Undici" + ] + }, + { + "system_id": "vite", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Vite Security", + "OSV Vite" + ] + }, + { + "system_id": "vue", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "Vue Security", + "OSV Vue" + ] + }, + { + "system_id": "webpack", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV webpack" + ] + }, + { + "system_id": "werkzeug", + "retired_source": "GitHub Global Advisories", + "replacement_sources": [ + "OSV Werkzeug" + ] + } + ] +} diff --git a/08-threat-intel/generated/dashboard/data/source-health.json b/08-threat-intel/generated/dashboard/data/source-health.json new file mode 100644 index 00000000..7c98cbc9 --- /dev/null +++ b/08-threat-intel/generated/dashboard/data/source-health.json @@ -0,0 +1,1218 @@ +{ + "generated_at": "2026-03-18T17:44:31+00:00", + "active_source_count": 110, + "green_source_count": 110, + "failure_count": 0, + "all_green": true, + "last_fully_green_run": "2026-03-18T17:44:31+00:00", + "retries_performed": 0, + "probes": [ + { + "system_id": "adminer", + "source_name": "NVD Adminer", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "adobe-commerce", + "source_name": "Adobe Magento Security Index", + "source_kind": "vendor-index", + "kind": "vendor-index", + "items_seen": 46 + }, + { + "system_id": "adobe-commerce", + "source_name": "NVD Adobe Commerce", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "angular", + "source_name": "OSV Angular", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "apache-httpd", + "source_name": "Apache HTTPD Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 182 + }, + { + "system_id": "apache-httpd", + "source_name": "CISA KEV Apache HTTPD", + "source_kind": "kev-json", + "kind": "kev-json", + "items_seen": 1544 + }, + { + "system_id": "apache-httpd", + "source_name": "NVD Apache HTTP Server", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "apache-tomcat", + "source_name": "Apache Tomcat Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 270 + }, + { + "system_id": "apache-tomcat", + "source_name": "CISA KEV Tomcat", + "source_kind": "kev-json", + "kind": "kev-json", + "items_seen": 1544 + }, + { + "system_id": "apache-tomcat", + "source_name": "NVD Tomcat", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "aspnet-core", + "source_name": "NVD ASP.NET Core", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "astro", + "source_name": "OSV Astro", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "caddy", + "source_name": "GitHub Caddy Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 114 + }, + { + "system_id": "caddy", + "source_name": "OSV Caddy", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "directus", + "source_name": "Directus GitHub Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 127 + }, + { + "system_id": "directus", + "source_name": "OSV Directus", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "discourse", + "source_name": "Discourse Release Notes RSS", + "source_kind": "rss-feed", + "kind": "rss-feed", + "items_seen": 30 + }, + { + "system_id": "django", + "source_name": "Django Security Releases Archive", + "source_kind": "vendor-index", + "kind": "vendor-index", + "items_seen": 1276 + }, + { + "system_id": "django", + "source_name": "Django Security Weblog", + "source_kind": "vendor-index", + "kind": "vendor-index", + "items_seen": 332 + }, + { + "system_id": "django", + "source_name": "OSV Django", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "drupal", + "source_name": "Drupal Security Advisories RSS", + "source_kind": "rss-feed", + "kind": "rss-feed", + "items_seen": 20 + }, + { + "system_id": "drupal", + "source_name": "NVD Drupal", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "echo", + "source_name": "OSV Echo", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "esbuild", + "source_name": "OSV esbuild", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "express", + "source_name": "OSV Express", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "fastify", + "source_name": "OSV Fastify", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "flask", + "source_name": "OSV Flask", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "ghost", + "source_name": "Ghost GitHub Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 119 + }, + { + "system_id": "ghost", + "source_name": "NVD Ghost", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "gin", + "source_name": "OSV Gin", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "gitea", + "source_name": "GitHub Gitea Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 98 + }, + { + "system_id": "gitea", + "source_name": "OSV Gitea", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "gitlab-ce", + "source_name": "GitLab Advisory Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 5 + }, + { + "system_id": "gitlab-ce", + "source_name": "GitLab Security Releases", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 250 + }, + { + "system_id": "gitlab-ce", + "source_name": "NVD GitLab", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "grafana", + "source_name": "CISA KEV Grafana", + "source_kind": "kev-json", + "kind": "kev-json", + "items_seen": 1544 + }, + { + "system_id": "grafana", + "source_name": "Grafana Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 159 + }, + { + "system_id": "hapi", + "source_name": "OSV Hapi", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "haproxy", + "source_name": "HAProxy Blog Feed", + "source_kind": "rss-feed", + "kind": "rss-feed", + "items_seen": 10 + }, + { + "system_id": "haproxy", + "source_name": "NVD HAProxy", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "jenkins", + "source_name": "Jenkins Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 1194 + }, + { + "system_id": "jenkins", + "source_name": "NVD Jenkins", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "joomla", + "source_name": "Joomla Security Centre", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 139 + }, + { + "system_id": "joomla", + "source_name": "NVD Joomla", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "kibana", + "source_name": "Elastic Security Announcements", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 82 + }, + { + "system_id": "kibana", + "source_name": "NVD Kibana", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "koa", + "source_name": "OSV Koa", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "laravel", + "source_name": "OSV Laravel", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "magento-open-source", + "source_name": "Magento GitHub Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 99 + }, + { + "system_id": "magento-open-source", + "source_name": "NVD Magento", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "magento-open-source", + "source_name": "Sansec Research", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 134 + }, + { + "system_id": "mattermost", + "source_name": "NVD Mattermost", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "mediawiki", + "source_name": "NVD MediaWiki", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "medusa", + "source_name": "GitHub Medusa Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 102 + }, + { + "system_id": "medusa", + "source_name": "OSV Medusa", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "moodle", + "source_name": "NVD Moodle", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "nestjs", + "source_name": "OSV NestJS", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "nextjs", + "source_name": "GitHub Next.js Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 123 + }, + { + "system_id": "nextjs", + "source_name": "OSV Next.js", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "nginx", + "source_name": "CISA KEV NGINX", + "source_kind": "kev-json", + "kind": "kev-json", + "items_seen": 1544 + }, + { + "system_id": "nginx", + "source_name": "NGINX Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 138 + }, + { + "system_id": "nginx", + "source_name": "NVD NGINX", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "nodejs", + "source_name": "CISA KEV Node.js", + "source_kind": "kev-json", + "kind": "kev-json", + "items_seen": 1544 + }, + { + "system_id": "nodejs", + "source_name": "Node.js Security Releases", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 74 + }, + { + "system_id": "nuxt", + "source_name": "Nuxt Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 118 + }, + { + "system_id": "nuxt", + "source_name": "OSV Nuxt", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "opencart", + "source_name": "NVD OpenCart", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "opencart", + "source_name": "OpenCart Releases", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 1500 + }, + { + "system_id": "openmage", + "source_name": "NVD OpenMage", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "openmage", + "source_name": "OpenMage GitHub Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 125 + }, + { + "system_id": "phpmyadmin", + "source_name": "NVD phpMyAdmin", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "phpmyadmin", + "source_name": "phpMyAdmin Security Page", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 262 + }, + { + "system_id": "prestashop", + "source_name": "Friends Of Presta Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 38 + }, + { + "system_id": "prestashop", + "source_name": "GitHub PrestaShop Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 127 + }, + { + "system_id": "prestashop", + "source_name": "PrestaShop Security Page", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 60 + }, + { + "system_id": "rails", + "source_name": "OSV Rails", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "react", + "source_name": "GitHub React Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 110 + }, + { + "system_id": "react", + "source_name": "OSV React", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "redmine", + "source_name": "NVD Redmine", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "redmine", + "source_name": "Redmine Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 371 + }, + { + "system_id": "saleor", + "source_name": "GitHub Saleor Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 120 + }, + { + "system_id": "saleor", + "source_name": "NVD Saleor", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "shopware", + "source_name": "NVD Shopware", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "shopware", + "source_name": "Shopware Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 129 + }, + { + "system_id": "spring-boot", + "source_name": "Spring Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 118 + }, + { + "system_id": "spring-framework", + "source_name": "Spring Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 118 + }, + { + "system_id": "spring-security", + "source_name": "Spring Security Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 118 + }, + { + "system_id": "strapi", + "source_name": "OSV Strapi", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "strapi", + "source_name": "Strapi GitHub Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 124 + }, + { + "system_id": "sveltekit", + "source_name": "OSV SvelteKit", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "symfony", + "source_name": "OSV Symfony", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "traefik", + "source_name": "GitHub Traefik Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 124 + }, + { + "system_id": "traefik", + "source_name": "OSV Traefik", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "undici", + "source_name": "OSV Undici", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "vite", + "source_name": "OSV Vite", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "vite", + "source_name": "Vite Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 124 + }, + { + "system_id": "vue", + "source_name": "OSV Vue", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "vue", + "source_name": "Vue Security", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 111 + }, + { + "system_id": "webpack", + "source_name": "OSV webpack", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "werkzeug", + "source_name": "OSV Werkzeug", + "source_kind": "osv-batch", + "kind": "osv-batch", + "items_seen": 1 + }, + { + "system_id": "woocommerce", + "source_name": "GitHub WooCommerce Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 107 + }, + { + "system_id": "woocommerce", + "source_name": "Patchstack Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 193 + }, + { + "system_id": "woocommerce", + "source_name": "Woo Developer Advisories", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 121 + }, + { + "system_id": "woocommerce", + "source_name": "Wordfence Vulnerability Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 0 + }, + { + "system_id": "wordpress", + "source_name": "NVD WordPress", + "source_kind": "nvd-search", + "kind": "nvd-search", + "items_seen": 1 + }, + { + "system_id": "wordpress", + "source_name": "Patchstack Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 193 + }, + { + "system_id": "wordpress", + "source_name": "PortSwigger Research", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 99 + }, + { + "system_id": "wordpress", + "source_name": "WPScan Vulnerability Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 74 + }, + { + "system_id": "wordpress", + "source_name": "WordPress Security News", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 138 + }, + { + "system_id": "wordpress", + "source_name": "Wordfence Vulnerability Database", + "source_kind": "html-links", + "kind": "html-links", + "items_seen": 0 + } + ], + "failures": [], + "systems": [ + { + "system_id": "adminer", + "display_name": "Adminer", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "adobe-commerce", + "display_name": "Adobe Commerce", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "angular", + "display_name": "Angular", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "apache-httpd", + "display_name": "Apache HTTP Server", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "apache-tomcat", + "display_name": "Apache Tomcat", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "aspnet-core", + "display_name": "ASP.NET Core", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "astro", + "display_name": "Astro", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "caddy", + "display_name": "Caddy", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "directus", + "display_name": "Directus", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "discourse", + "display_name": "Discourse", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "django", + "display_name": "Django", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "drupal", + "display_name": "Drupal", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "echo", + "display_name": "Echo", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "esbuild", + "display_name": "esbuild", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "express", + "display_name": "Express", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "fastify", + "display_name": "Fastify", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "flask", + "display_name": "Flask", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "ghost", + "display_name": "Ghost", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "gin", + "display_name": "Gin", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "gitea", + "display_name": "Gitea", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "gitlab-ce", + "display_name": "GitLab CE", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "grafana", + "display_name": "Grafana", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "hapi", + "display_name": "Hapi", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "haproxy", + "display_name": "HAProxy", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "jenkins", + "display_name": "Jenkins", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "joomla", + "display_name": "Joomla", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "kibana", + "display_name": "Kibana", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "koa", + "display_name": "Koa", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "laravel", + "display_name": "Laravel", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "magento-open-source", + "display_name": "Magento Open Source", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "mattermost", + "display_name": "Mattermost", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "mediawiki", + "display_name": "MediaWiki", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "medusa", + "display_name": "Medusa", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "moodle", + "display_name": "Moodle", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "nestjs", + "display_name": "NestJS", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "nextjs", + "display_name": "Next.js", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "nginx", + "display_name": "Nginx", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "nodejs", + "display_name": "Node.js", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "nuxt", + "display_name": "Nuxt", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "opencart", + "display_name": "OpenCart", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "openmage", + "display_name": "OpenMage / Mage-OS", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "phpmyadmin", + "display_name": "phpMyAdmin", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "prestashop", + "display_name": "PrestaShop", + "active_source_total": 3, + "green_source_total": 3, + "failure_count": 0 + }, + { + "system_id": "rails", + "display_name": "Ruby on Rails", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "react", + "display_name": "React", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "redmine", + "display_name": "Redmine", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "saleor", + "display_name": "Saleor", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "shopware", + "display_name": "Shopware", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "spring-boot", + "display_name": "Spring Boot", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "spring-framework", + "display_name": "Spring Framework", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "spring-security", + "display_name": "Spring Security", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "strapi", + "display_name": "Strapi", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "sveltekit", + "display_name": "SvelteKit", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "symfony", + "display_name": "Symfony", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "traefik", + "display_name": "Traefik", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "undici", + "display_name": "Undici", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "vite", + "display_name": "Vite", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "vue", + "display_name": "Vue", + "active_source_total": 2, + "green_source_total": 2, + "failure_count": 0 + }, + { + "system_id": "webpack", + "display_name": "webpack", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "werkzeug", + "display_name": "Werkzeug", + "active_source_total": 1, + "green_source_total": 1, + "failure_count": 0 + }, + { + "system_id": "woocommerce", + "display_name": "WooCommerce", + "active_source_total": 4, + "green_source_total": 4, + "failure_count": 0 + }, + { + "system_id": "wordpress", + "display_name": "WordPress", + "active_source_total": 6, + "green_source_total": 6, + "failure_count": 0 + } + ] +} diff --git a/08-threat-intel/generated/dashboard/docs/architecture-library.html b/08-threat-intel/generated/dashboard/docs/architecture-library.html index 8e747784..94d88ebb 100644 --- a/08-threat-intel/generated/dashboard/docs/architecture-library.html +++ b/08-threat-intel/generated/dashboard/docs/architecture-library.html @@ -87,7 +87,7 @@

当前架构库镜像

工作台内置镜像页:当前架构库结构化数据镜像。
{
-  "generated_at": "2026-03-18T14:45:55+00:00",
+  "generated_at": "2026-03-18T17:52:49+00:00",
   "title": "当前架构库",
   "summary": "工作台、控制面、数据层、授权边界与系统覆盖的当前真值视图。",
   "sections": [
@@ -119,7 +119,7 @@
         },
         {
           "label": "当前漏洞条目",
-          "value": "5"
+          "value": "0"
         }
       ],
       "fields": [
@@ -137,7 +137,7 @@
         },
         {
           "label": "生成时间",
-          "value": "2026-03-18T14:45:55+00:00"
+          "value": "2026-03-18T17:52:49+00:00"
         }
       ],
       "links": [
@@ -356,6 +356,16 @@
               "href": "/docs/source-map.html",
               "description": "系统覆盖、来源和输出目录真值。"
             },
+            {
+              "label": "source catalog audit",
+              "href": "/docs/source-catalog-audit.html",
+              "description": "active/retired source 审计、替代关系与覆盖摘要。"
+            },
+            {
+              "label": "retired sources",
+              "href": "/docs/retired-sources.html",
+              "description": "退役源、退役原因与 replacement map。"
+            },
             {
               "label": "repro-map 真值",
               "href": "/docs/repro-map.html",
@@ -386,6 +396,21 @@
               "href": "/data/completeness.json",
               "description": "最新 advisory 完整度、系统/family 进度与 ingest 健康度。"
             },
+            {
+              "label": "source-health.json",
+              "href": "/data/source-health.json",
+              "description": "active source 健康度、系统分布与失败分类。"
+            },
+            {
+              "label": "alerts.json",
+              "href": "/data/alerts.json",
+              "description": "source 告警状态机、failure streak 与 resolved 记录。"
+            },
+            {
+              "label": "monitor-summary.json",
+              "href": "/data/monitor-summary.json",
+              "description": "每日监控摘要、open alerts 与最近全绿时间。"
+            },
             {
               "label": "runs.json",
               "href": "/runs.json",
@@ -410,6 +435,11 @@
               "label": "architecture.json",
               "href": "/architecture.json",
               "description": "当前架构库结构化 JSON。"
+            },
+            {
+              "label": "source-catalog-audit.json",
+              "href": "/data/source-catalog-audit.json",
+              "description": "source catalog 审计真值与 retired/replacement 关系。"
             }
           ],
           "fields": [
@@ -572,7 +602,7 @@
               "open": false,
               "badges": [
                 "近两年全量",
-                "官方源 2",
+                "官方源 3",
                 "生态源 0",
                 "研究源 0"
               ],
@@ -606,7 +636,7 @@
                   "fields": [
                     {
                       "label": "官方来源",
-                      "value": "Discourse Meta Security\nGitHub Discourse Advisories"
+                      "value": "Discourse Meta Security\nDiscourse Release Notes RSS\nGitHub Discourse Advisories"
                     },
                     {
                       "label": "生态来源",
@@ -658,7 +688,7 @@
               "badges": [
                 "历史全量",
                 "官方源 2",
-                "生态源 1",
+                "生态源 2",
                 "研究源 0"
               ],
               "fields": [
@@ -695,7 +725,7 @@
                     },
                     {
                       "label": "生态来源",
-                      "value": "Drupal Security Advisories Site"
+                      "value": "Drupal Security Advisories Site\nGHSA Drupal Core"
                     },
                     {
                       "label": "研究来源",
@@ -1528,7 +1558,7 @@
               "open": false,
               "badges": [
                 "近两年全量",
-                "官方源 2",
+                "官方源 4",
                 "生态源 0",
                 "研究源 0"
               ],
@@ -1562,7 +1592,7 @@
                   "fields": [
                     {
                       "label": "官方来源",
-                      "value": "Django Security RSS\nOSV Django"
+                      "value": "Django Security RSS\nDjango Security Weblog\nDjango Security Releases Archive\nOSV Django"
                     },
                     {
                       "label": "生态来源",
@@ -4800,7 +4830,7 @@
               "open": false,
               "badges": [
                 "近两年全量",
-                "官方源 2",
+                "官方源 3",
                 "生态源 0",
                 "研究源 0"
               ],
@@ -4834,7 +4864,7 @@
                   "fields": [
                     {
                       "label": "官方来源",
-                      "value": "HAProxy Security Advisories\nNVD HAProxy"
+                      "value": "HAProxy Security Advisories\nHAProxy Blog Feed\nNVD HAProxy"
                     },
                     {
                       "label": "生态来源",
@@ -5076,8 +5106,8 @@
               "open": false,
               "badges": [
                 "历史全量",
-                "官方源 2",
-                "生态源 1",
+                "官方源 3",
+                "生态源 2",
                 "研究源 0"
               ],
               "fields": [
@@ -5110,11 +5140,11 @@
                   "fields": [
                     {
                       "label": "官方来源",
-                      "value": "Adobe Security Bulletins\nNVD Adobe Commerce"
+                      "value": "Adobe Security Bulletins\nAdobe Magento Security Index\nNVD Adobe Commerce"
                     },
                     {
                       "label": "生态来源",
-                      "value": "Sansec Research"
+                      "value": "GHSA Adobe Commerce\nSansec Research"
                     },
                     {
                       "label": "研究来源",
@@ -5945,15 +5975,15 @@
         },
         {
           "label": "Advisory 数",
-          "value": "5"
+          "value": "0"
         },
         {
           "label": "状态类型",
-          "value": "1"
+          "value": "0"
         },
         {
           "label": "最近失败",
-          "value": "5"
+          "value": "0"
         }
       ],
       "items": [
@@ -5961,23 +5991,7 @@
           "title": "状态分布",
           "summary": "verification_status 当前计数。",
           "open": false,
-          "items": [
-            {
-              "title": "人工分诊",
-              "summary": "当前累计 5 条。",
-              "open": false,
-              "fields": [
-                {
-                  "label": "状态编码",
-                  "value": "triage-manual"
-                },
-                {
-                  "label": "数量",
-                  "value": "5"
-                }
-              ]
-            }
-          ]
+          "items": []
         },
         {
           "title": "最近失败",
@@ -5985,134 +5999,9 @@
           "open": false,
           "items": [
             {
-              "title": "Next.js: Unbounded postponed resume buffering can lead to DoS",
-              "summary": "无额外阻塞说明。",
-              "open": false,
-              "badges": [
-                "人工分诊"
-              ],
-              "fields": [
-                {
-                  "label": "运行 ID",
-                  "value": "-"
-                },
-                {
-                  "label": "漏洞条目",
-                  "value": "nextjs--CVE-2026-27979"
-                },
-                {
-                  "label": "状态",
-                  "value": "人工分诊"
-                },
-                {
-                  "label": "阻塞原因",
-                  "value": "-"
-                }
-              ]
-            },
-            {
-              "title": "Next.js: Unbounded next/image disk cache growth can exhaust storage",
-              "summary": "无额外阻塞说明。",
-              "open": false,
-              "badges": [
-                "人工分诊"
-              ],
-              "fields": [
-                {
-                  "label": "运行 ID",
-                  "value": "-"
-                },
-                {
-                  "label": "漏洞条目",
-                  "value": "nextjs--CVE-2026-27980"
-                },
-                {
-                  "label": "状态",
-                  "value": "人工分诊"
-                },
-                {
-                  "label": "阻塞原因",
-                  "value": "-"
-                }
-              ]
-            },
-            {
-              "title": "Next.js: HTTP request smuggling in rewrites",
-              "summary": "无额外阻塞说明。",
-              "open": false,
-              "badges": [
-                "人工分诊"
-              ],
-              "fields": [
-                {
-                  "label": "运行 ID",
-                  "value": "-"
-                },
-                {
-                  "label": "漏洞条目",
-                  "value": "nextjs--CVE-2026-29057"
-                },
-                {
-                  "label": "状态",
-                  "value": "人工分诊"
-                },
-                {
-                  "label": "阻塞原因",
-                  "value": "-"
-                }
-              ]
-            },
-            {
-              "title": "Next.js: null origin can bypass Server Actions CSRF checks",
-              "summary": "无额外阻塞说明。",
-              "open": false,
-              "badges": [
-                "人工分诊"
-              ],
-              "fields": [
-                {
-                  "label": "运行 ID",
-                  "value": "-"
-                },
-                {
-                  "label": "漏洞条目",
-                  "value": "nextjs--CVE-2026-27978"
-                },
-                {
-                  "label": "状态",
-                  "value": "人工分诊"
-                },
-                {
-                  "label": "阻塞原因",
-                  "value": "-"
-                }
-              ]
-            },
-            {
-              "title": "Next.js: null origin can bypass dev HMR websocket CSRF checks",
-              "summary": "无额外阻塞说明。",
-              "open": false,
-              "badges": [
-                "人工分诊"
-              ],
-              "fields": [
-                {
-                  "label": "运行 ID",
-                  "value": "-"
-                },
-                {
-                  "label": "漏洞条目",
-                  "value": "nextjs--CVE-2026-27977"
-                },
-                {
-                  "label": "状态",
-                  "value": "人工分诊"
-                },
-                {
-                  "label": "阻塞原因",
-                  "value": "-"
-                }
-              ]
+              "title": "暂无失败样本",
+              "summary": "当前 summary.json 中没有 recent_failures。",
+              "open": false
             }
           ]
         }
diff --git a/08-threat-intel/generated/dashboard/docs/coverage-matrix.html b/08-threat-intel/generated/dashboard/docs/coverage-matrix.html
index dd7b5ee4..ac8e1e3d 100644
--- a/08-threat-intel/generated/dashboard/docs/coverage-matrix.html
+++ b/08-threat-intel/generated/dashboard/docs/coverage-matrix.html
@@ -125,7 +125,7 @@
 | Medusa | `ecommerce` | `rolling-24m` | `-` | `yes` | `0` | `0` | `2` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
 | Moodle | `cms` | `rolling-24m` | `-` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
 | NestJS | `frameworks` | `rolling-24m` | `-` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
-| Next.js | `frameworks` | `history-full` | `yes` | `yes` | `5` | `5` | `3` | `seeded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `2026-03-17T16:31:34.160932Z` |
+| Next.js | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
 | Nginx | `servers` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
 | Node.js | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
 | Nuxt | `frameworks` | `history-full` | `yes` | `yes` | `0` | `0` | `3` | `scaffolded` | `real:0/synthetic:0/blocked:0` | `0` | `0` | `0` | `` |
diff --git a/08-threat-intel/generated/dashboard/docs/retired-sources.html b/08-threat-intel/generated/dashboard/docs/retired-sources.html
new file mode 100644
index 00000000..b9127948
--- /dev/null
+++ b/08-threat-intel/generated/dashboard/docs/retired-sources.html
@@ -0,0 +1,539 @@
+
+
+
+  
+  
+  Retired Sources & Replacement Map
+  
+
+
+  
+
+ +

Retired Sources & Replacement Map

+
工作台内置镜像页:退役源、退役原因和 replacement_sources 真值。
+
[
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "Adobe Security Bulletins",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.",
+    "replacement_sources": [
+      "Adobe Magento Security Index",
+      "NVD Adobe Commerce",
+      "GHSA Adobe Commerce"
+    ],
+    "url": "https://helpx.adobe.com/security/products/magento.html"
+  },
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "GHSA Adobe Commerce",
+    "bucket": "ecosystem_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.",
+    "replacement_sources": [
+      "Adobe Magento Security Index",
+      "NVD Adobe Commerce"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "Sansec Research",
+    "bucket": "ecosystem_sources",
+    "kind": "vendor-index",
+    "retired_reason": "Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.",
+    "replacement_sources": [
+      "GHSA Adobe Commerce",
+      "Adobe Magento Security Index"
+    ],
+    "url": "https://sansec.io/research"
+  },
+  {
+    "system_id": "angular",
+    "display_name": "Angular",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Angular"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "astro",
+    "display_name": "Astro",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Astro"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "discourse",
+    "display_name": "Discourse",
+    "source_name": "Discourse Meta Security",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Meta security category HTML changed and no longer provides stable scrape semantics for health checks.",
+    "replacement_sources": [
+      "Discourse Release Notes RSS",
+      "GitHub Discourse Advisories"
+    ],
+    "url": "https://meta.discourse.org/c/bug/security/40"
+  },
+  {
+    "system_id": "discourse",
+    "display_name": "Discourse",
+    "source_name": "GitHub Discourse Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.",
+    "replacement_sources": [
+      "Discourse Release Notes RSS"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "django",
+    "display_name": "Django",
+    "source_name": "Django Security RSS",
+    "bucket": "official_sources",
+    "kind": "rss-feed",
+    "retired_reason": "Official security tag feed became unstable; use official weblog index and release archive instead.",
+    "replacement_sources": [
+      "Django Security Weblog",
+      "Django Security Releases Archive"
+    ],
+    "url": "https://www.djangoproject.com/weblog/feeds/tags/security/"
+  },
+  {
+    "system_id": "drupal",
+    "display_name": "Drupal",
+    "source_name": "Drupal Security Advisories Site",
+    "bucket": "ecosystem_sources",
+    "kind": "html-links",
+    "retired_reason": "Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.",
+    "replacement_sources": [
+      "Drupal Security Advisories RSS",
+      "GHSA Drupal Core"
+    ],
+    "url": "https://www.drupal.org/security"
+  },
+  {
+    "system_id": "drupal",
+    "display_name": "Drupal",
+    "source_name": "GHSA Drupal Core",
+    "bucket": "ecosystem_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.",
+    "replacement_sources": [
+      "Drupal Security Advisories RSS",
+      "NVD Drupal"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "esbuild",
+    "display_name": "esbuild",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.",
+    "replacement_sources": [
+      "OSV esbuild"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "express",
+    "display_name": "Express",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Express"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "fastify",
+    "display_name": "Fastify",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Fastify"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "flask",
+    "display_name": "Flask",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Flask"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "hapi",
+    "display_name": "Hapi",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Hapi"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "haproxy",
+    "display_name": "HAProxy",
+    "source_name": "HAProxy Security Advisories",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Legacy haproxy.org security page no longer yields stable scrape results for monitoring.",
+    "replacement_sources": [
+      "HAProxy Blog Feed"
+    ],
+    "url": "https://www.haproxy.org/security/"
+  },
+  {
+    "system_id": "koa",
+    "display_name": "Koa",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Koa"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "laravel",
+    "display_name": "Laravel",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Laravel"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "mattermost",
+    "display_name": "Mattermost",
+    "source_name": "Mattermost Security Updates",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD Mattermost"
+    ],
+    "url": "https://mattermost.com/security-updates/"
+  },
+  {
+    "system_id": "mediawiki",
+    "display_name": "MediaWiki",
+    "source_name": "MediaWiki Security Releases",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD MediaWiki"
+    ],
+    "url": "https://www.mediawiki.org/wiki/Security"
+  },
+  {
+    "system_id": "moodle",
+    "display_name": "Moodle",
+    "source_name": "Moodle Security News",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD Moodle"
+    ],
+    "url": "https://moodle.org/security/"
+  },
+  {
+    "system_id": "nestjs",
+    "display_name": "NestJS",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.",
+    "replacement_sources": [
+      "OSV NestJS"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "nextjs",
+    "display_name": "Next.js",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.",
+    "replacement_sources": [
+      "GitHub Next.js Advisories",
+      "OSV Next.js"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "nuxt",
+    "display_name": "Nuxt",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.",
+    "replacement_sources": [
+      "Nuxt Security",
+      "OSV Nuxt"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "rails",
+    "display_name": "Ruby on Rails",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Rails"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "react",
+    "display_name": "React",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.",
+    "replacement_sources": [
+      "GitHub React Advisories",
+      "OSV React"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-boot",
+    "display_name": "Spring Boot",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-framework",
+    "display_name": "Spring Framework",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-security",
+    "display_name": "Spring Security",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "sveltekit",
+    "display_name": "SvelteKit",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.",
+    "replacement_sources": [
+      "OSV SvelteKit"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "symfony",
+    "display_name": "Symfony",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Symfony"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "undici",
+    "display_name": "Undici",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Undici"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "vite",
+    "display_name": "Vite",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.",
+    "replacement_sources": [
+      "Vite Security",
+      "OSV Vite"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "vue",
+    "display_name": "Vue",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.",
+    "replacement_sources": [
+      "Vue Security",
+      "OSV Vue"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "webpack",
+    "display_name": "webpack",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.",
+    "replacement_sources": [
+      "OSV webpack"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "werkzeug",
+    "display_name": "Werkzeug",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Werkzeug"
+    ],
+    "url": ""
+  }
+]
+
+
+ + diff --git a/08-threat-intel/generated/dashboard/docs/source-catalog-audit.html b/08-threat-intel/generated/dashboard/docs/source-catalog-audit.html new file mode 100644 index 00000000..dbc1f04e --- /dev/null +++ b/08-threat-intel/generated/dashboard/docs/source-catalog-audit.html @@ -0,0 +1,141 @@ + + + + + + Source Catalog Audit + + + +
+
+ +

Source Catalog Audit

+
工作台内置镜像页:active/retired source、replacement map 与覆盖摘要。
+
# Source Catalog Audit
+
+- generated_at: `2026-03-18T17:41:42+00:00`
+- systems: `62`
+- sources: `146`
+- active_sources: `110`
+- retired_sources: `36`
+- systems_with_active_official: `62/62`
+- systems_with_machine_readable_source: `57/62`
+
+## Retired Sources
+
+- `adobe-commerce` `Adobe Security Bulletins` -> replacements: `Adobe Magento Security Index, NVD Adobe Commerce, GHSA Adobe Commerce` | reason: Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.
+- `adobe-commerce` `GHSA Adobe Commerce` -> replacements: `Adobe Magento Security Index, NVD Adobe Commerce` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.
+- `adobe-commerce` `Sansec Research` -> replacements: `GHSA Adobe Commerce, Adobe Magento Security Index` | reason: Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.
+- `angular` `GitHub Global Advisories` -> replacements: `OSV Angular` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.
+- `astro` `GitHub Global Advisories` -> replacements: `OSV Astro` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.
+- `discourse` `Discourse Meta Security` -> replacements: `Discourse Release Notes RSS, GitHub Discourse Advisories` | reason: Meta security category HTML changed and no longer provides stable scrape semantics for health checks.
+- `discourse` `GitHub Discourse Advisories` -> replacements: `Discourse Release Notes RSS` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.
+- `django` `Django Security RSS` -> replacements: `Django Security Weblog, Django Security Releases Archive` | reason: Official security tag feed became unstable; use official weblog index and release archive instead.
+- `drupal` `Drupal Security Advisories Site` -> replacements: `Drupal Security Advisories RSS, GHSA Drupal Core` | reason: Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.
+- `drupal` `GHSA Drupal Core` -> replacements: `Drupal Security Advisories RSS, NVD Drupal` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.
+- `esbuild` `GitHub Global Advisories` -> replacements: `OSV esbuild` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.
+- `express` `GitHub Global Advisories` -> replacements: `OSV Express` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.
+- `fastify` `GitHub Global Advisories` -> replacements: `OSV Fastify` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.
+- `flask` `GitHub Global Advisories` -> replacements: `OSV Flask` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.
+- `hapi` `GitHub Global Advisories` -> replacements: `OSV Hapi` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.
+- `haproxy` `HAProxy Security Advisories` -> replacements: `HAProxy Blog Feed` | reason: Legacy haproxy.org security page no longer yields stable scrape results for monitoring.
+- `koa` `GitHub Global Advisories` -> replacements: `OSV Koa` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.
+- `laravel` `GitHub Global Advisories` -> replacements: `OSV Laravel` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.
+- `mattermost` `Mattermost Security Updates` -> replacements: `NVD Mattermost` | reason: Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.
+- `mediawiki` `MediaWiki Security Releases` -> replacements: `NVD MediaWiki` | reason: MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.
+- `moodle` `Moodle Security News` -> replacements: `NVD Moodle` | reason: Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.
+- `nestjs` `GitHub Global Advisories` -> replacements: `OSV NestJS` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.
+- `nextjs` `GitHub Global Advisories` -> replacements: `GitHub Next.js Advisories, OSV Next.js` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.
+- `nuxt` `GitHub Global Advisories` -> replacements: `Nuxt Security, OSV Nuxt` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.
+- `rails` `GitHub Global Advisories` -> replacements: `OSV Rails` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.
+- `react` `GitHub Global Advisories` -> replacements: `GitHub React Advisories, OSV React` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.
+- `spring-boot` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+- `spring-framework` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.
+- `spring-security` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+- `sveltekit` `GitHub Global Advisories` -> replacements: `OSV SvelteKit` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.
+- `symfony` `GitHub Global Advisories` -> replacements: `OSV Symfony` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.
+- `undici` `GitHub Global Advisories` -> replacements: `OSV Undici` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.
+- `vite` `GitHub Global Advisories` -> replacements: `Vite Security, OSV Vite` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.
+- `vue` `GitHub Global Advisories` -> replacements: `Vue Security, OSV Vue` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.
+- `webpack` `GitHub Global Advisories` -> replacements: `OSV webpack` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.
+- `werkzeug` `GitHub Global Advisories` -> replacements: `OSV Werkzeug` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.
+
+
+
+ + diff --git a/08-threat-intel/generated/dashboard/docs/source-map.html b/08-threat-intel/generated/dashboard/docs/source-map.html index 6509033a..ee4fba01 100644 --- a/08-threat-intel/generated/dashboard/docs/source-map.html +++ b/08-threat-intel/generated/dashboard/docs/source-map.html @@ -174,6 +174,17 @@ systems: advisory_mode: module keywords: [drupal, module, sa-contrib] max_items: 50 + status: retired + retired_reason: Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring. + replacement_sources: [Drupal Security Advisories RSS, GHSA Drupal Core] + - name: GHSA Drupal Core + kind: ghsa-global + ecosystem: composer + confidence: ecosystem-authority + advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements. + replacement_sources: [Drupal Security Advisories RSS, NVD Drupal] research_sources: [] package_names: - ecosystem: composer @@ -325,6 +336,9 @@ systems: advisory_mode: core keywords: [mediawiki, security] max_items: 50 + status: retired + retired_reason: MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active. + replacement_sources: [NVD MediaWiki] - name: NVD MediaWiki kind: nvd-search keyword: MediaWiki @@ -355,6 +369,9 @@ systems: advisory_mode: core keywords: [moodle, security] max_items: 50 + status: retired + retired_reason: Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active. + replacement_sources: [NVD Moodle] - name: NVD Moodle kind: nvd-search keyword: Moodle @@ -385,13 +402,24 @@ systems: advisory_mode: core keywords: [discourse, security] max_items: 50 - - name: GitHub Discourse Advisories - kind: html-links - url: https://github.com/discourse/discourse/security/advisories + status: retired + retired_reason: Meta security category HTML changed and no longer provides stable scrape semantics for health checks. + replacement_sources: [Discourse Release Notes RSS, GitHub Discourse Advisories] + - name: Discourse Release Notes RSS + kind: rss-feed + url: https://meta.discourse.org/tag/release-notes.rss confidence: official advisory_mode: core - keywords: [discourse] - max_items: 50 + keywords: [discourse, security, cve] + max_items: 60 + - name: GitHub Discourse Advisories + kind: ghsa-global + ecosystem: rubygems + confidence: official + advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source. + replacement_sources: [Discourse Release Notes RSS] ecosystem_sources: [] research_sources: [] package_names: @@ -418,6 +446,24 @@ systems: advisory_mode: core keywords: [adobe commerce, magento, apsb] max_items: 60 + status: retired + retired_reason: Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints. + replacement_sources: [Adobe Magento Security Index, NVD Adobe Commerce, GHSA Adobe Commerce] + - name: Adobe Magento Security Index + kind: vendor-index + url: https://helpx.adobe.com/security/products/magento.html + confidence: official + advisory_mode: core + keywords: [adobe commerce, magento, apsb, security] + max_items: 60 + request_policy: + user_agent: python-requests/2.31.0 + timeout_seconds: 45 + verify_tls: false + http_version: "1.1" + parser_hints: + keywords: [adobe commerce, magento, apsb, security] + include_url_patterns: [magento, security, APSB] - name: NVD Adobe Commerce kind: nvd-search keyword: Adobe Commerce @@ -425,13 +471,24 @@ systems: advisory_mode: core results_per_page: 50 ecosystem_sources: + - name: GHSA Adobe Commerce + kind: ghsa-global + ecosystem: composer + confidence: ecosystem-authority + advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements. + replacement_sources: [Adobe Magento Security Index, NVD Adobe Commerce] - name: Sansec Research - kind: html-links + kind: vendor-index url: https://sansec.io/research confidence: ecosystem-authority advisory_mode: extension keywords: [magento, adobe commerce] max_items: 50 + status: retired + retired_reason: Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement. + replacement_sources: [GHSA Adobe Commerce, Adobe Magento Security Index] research_sources: [] package_names: - ecosystem: composer @@ -757,6 +814,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements. + replacement_sources: [GitHub React Advisories, OSV React] - name: OSV React kind: osv-batch confidence: official @@ -795,6 +855,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements. + replacement_sources: [GitHub Next.js Advisories, OSV Next.js] - name: OSV Next.js kind: osv-batch confidence: official @@ -831,6 +894,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements. + replacement_sources: [Vue Security, OSV Vue] - name: OSV Vue kind: osv-batch confidence: official @@ -869,6 +935,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements. + replacement_sources: [Nuxt Security, OSV Nuxt] - name: OSV Nuxt kind: osv-batch confidence: official @@ -905,6 +974,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements. + replacement_sources: [Vite Security, OSV Vite] - name: OSV Vite kind: osv-batch confidence: official @@ -934,6 +1006,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source. + replacement_sources: [OSV Angular] - name: OSV Angular kind: osv-batch confidence: official @@ -965,6 +1040,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source. + replacement_sources: [OSV SvelteKit] - name: OSV SvelteKit kind: osv-batch confidence: official @@ -994,6 +1072,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source. + replacement_sources: [OSV Astro] - name: OSV Astro kind: osv-batch confidence: official @@ -1023,6 +1104,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source. + replacement_sources: [OSV Express] - name: OSV Express kind: osv-batch confidence: official @@ -1052,6 +1136,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source. + replacement_sources: [OSV NestJS] - name: OSV NestJS kind: osv-batch confidence: official @@ -1081,6 +1168,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source. + replacement_sources: [OSV Koa] - name: OSV Koa kind: osv-batch confidence: official @@ -1110,6 +1200,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source. + replacement_sources: [OSV Fastify] - name: OSV Fastify kind: osv-batch confidence: official @@ -1139,6 +1232,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source. + replacement_sources: [OSV Hapi] - name: OSV Hapi kind: osv-batch confidence: official @@ -1198,6 +1294,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source. + replacement_sources: [OSV Undici] - name: OSV Undici kind: osv-batch confidence: official @@ -1227,6 +1326,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source. + replacement_sources: [OSV webpack] - name: OSV webpack kind: osv-batch confidence: official @@ -1256,6 +1358,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source. + replacement_sources: [OSV esbuild] - name: OSV esbuild kind: osv-batch confidence: official @@ -1292,6 +1397,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source. + replacement_sources: [Spring Security Advisories] ecosystem_sources: [] research_sources: [] package_names: @@ -1326,6 +1434,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source. + replacement_sources: [Spring Security Advisories] ecosystem_sources: [] research_sources: [] package_names: @@ -1358,6 +1469,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source. + replacement_sources: [Spring Security Advisories] ecosystem_sources: [] research_sources: [] package_names: @@ -1383,6 +1497,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source. + replacement_sources: [OSV Laravel] - name: OSV Laravel kind: osv-batch confidence: official @@ -1412,6 +1529,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source. + replacement_sources: [OSV Symfony] - name: OSV Symfony kind: osv-batch confidence: official @@ -1444,6 +1564,29 @@ systems: advisory_mode: core keywords: [django] max_items: 60 + status: retired + retired_reason: Official security tag feed became unstable; use official weblog index and release archive instead. + replacement_sources: [Django Security Weblog, Django Security Releases Archive] + - name: Django Security Weblog + kind: vendor-index + url: https://www.djangoproject.com/weblog/ + confidence: official + advisory_mode: core + keywords: [django, security, release] + max_items: 60 + parser_hints: + keywords: [django, security, release] + include_url_patterns: [/weblog/] + - name: Django Security Releases Archive + kind: vendor-index + url: https://docs.djangoproject.com/en/dev/releases/security/ + confidence: official + advisory_mode: core + keywords: [django, security] + max_items: 40 + parser_hints: + keywords: [django, security] + include_url_patterns: [/releases/security/] - name: OSV Django kind: osv-batch confidence: official @@ -1477,6 +1620,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source. + replacement_sources: [OSV Flask] ecosystem_sources: [] research_sources: [] package_names: @@ -1506,6 +1652,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source. + replacement_sources: [OSV Werkzeug] ecosystem_sources: [] research_sources: [] package_names: @@ -1531,6 +1680,9 @@ systems: name: GitHub Global Advisories confidence: official advisory_mode: core + status: retired + retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source. + replacement_sources: [OSV Rails] - name: OSV Rails kind: osv-batch confidence: official @@ -1798,6 +1950,16 @@ systems: advisory_mode: server keywords: [haproxy, security] max_items: 50 + status: retired + retired_reason: Legacy haproxy.org security page no longer yields stable scrape results for monitoring. + replacement_sources: [HAProxy Blog Feed] + - name: HAProxy Blog Feed + kind: rss-feed + url: https://www.haproxy.com/feed/ + confidence: official + advisory_mode: server + keywords: [haproxy, security, cve] + max_items: 40 - name: NVD HAProxy kind: nvd-search keyword: HAProxy @@ -2041,6 +2203,9 @@ systems: advisory_mode: core keywords: [mattermost] max_items: 50 + status: retired + retired_reason: Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active. + replacement_sources: [NVD Mattermost] - name: NVD Mattermost kind: nvd-search keyword: Mattermost diff --git a/08-threat-intel/generated/dashboard/docs/testing-completeness-report.html b/08-threat-intel/generated/dashboard/docs/testing-completeness-report.html index d8278960..ff2a4bc0 100644 --- a/08-threat-intel/generated/dashboard/docs/testing-completeness-report.html +++ b/08-threat-intel/generated/dashboard/docs/testing-completeness-report.html @@ -88,18 +88,20 @@
工作台内置镜像页:89 条 advisory 最新完整度、family 矩阵与 ingest 健康度。
# 全库 Advisory 完整度报告
 
-- 生成时间: `2026-03-18T14:45:55+00:00`
-- 最新 advisory 完整度: `0/5` `verified-real`
+- 生成时间: `2026-03-18T17:52:49+00:00`
+- 最新 advisory 完整度: `0/0` `verified-real`
 - 合成验证数量: `0`
 - 阻塞数量: `0`
-- 人工/待补证据数量: `5`
+- 人工/待补证据数量: `0`
 - 完整度百分比: `0.0%`
+- active source 全绿: `110/110`
+- source open alerts: `0`
+- 最近一次 source 全绿: `2026-03-18T17:44:31+00:00`
 
 ## 系统覆盖矩阵
 
 | 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |
 | --- | ---: | ---: | ---: | ---: | ---: | --- |
-| nextjs | 5 | 0 | 0 | 0 | 5 | proxy-boundary(0/4), request-smuggling(0/1) |
 
 ## 历史阻塞项修复纪要
 
@@ -107,39 +109,14 @@
 - Family profiles previously used note-only attack runners and dry-run placeholders.
 - Baseline and browser steps were skipped when environment readiness was not enforced.
 - Latest completeness now uses one advisory -> latest run semantics instead of historical run piles.
+- Source health now counts only status=active sources; retired sources are audited separately with replacement links.
 
 ## Ingest / Source 健康度
 
-- source failures: `29`
-- drupal::Drupal Security Advisories Site::HTTPError
-- discourse::Discourse Meta Security::HTTPError
-- adobe-commerce::Adobe Security Bulletins::ConnectionError
-- react::GitHub Global Advisories::TypeError
-- nextjs::GitHub Global Advisories::AttributeError
-- vue::GitHub Global Advisories::HTTPError
-- nuxt::GitHub Global Advisories::HTTPError
-- vite::GitHub Global Advisories::HTTPError
-- angular::GitHub Global Advisories::HTTPError
-- sveltekit::GitHub Global Advisories::HTTPError
-- astro::GitHub Global Advisories::HTTPError
-- express::GitHub Global Advisories::HTTPError
-- nestjs::GitHub Global Advisories::HTTPError
-- koa::GitHub Global Advisories::HTTPError
-- fastify::GitHub Global Advisories::HTTPError
-- hapi::GitHub Global Advisories::HTTPError
-- undici::GitHub Global Advisories::HTTPError
-- webpack::GitHub Global Advisories::HTTPError
-- esbuild::GitHub Global Advisories::HTTPError
-- spring-framework::GitHub Global Advisories::HTTPError
-- spring-security::GitHub Global Advisories::HTTPError
-- spring-boot::GitHub Global Advisories::HTTPError
-- laravel::GitHub Global Advisories::HTTPError
-- symfony::GitHub Global Advisories::HTTPError
-- django::Django Security RSS::HTTPError
-- flask::GitHub Global Advisories::HTTPError
-- werkzeug::GitHub Global Advisories::HTTPError
-- rails::GitHub Global Advisories::HTTPError
-- haproxy::HAProxy Security Advisories::HTTPError
+- source failures: `0`
+- active sources: `110`
+- green sources: `110`
+- open alerts: `0`
 
 ## 剩余风险说明
 
diff --git a/08-threat-intel/generated/dashboard/summary.json b/08-threat-intel/generated/dashboard/summary.json
index a03026be..8b79ff80 100644
--- a/08-threat-intel/generated/dashboard/summary.json
+++ b/08-threat-intel/generated/dashboard/summary.json
@@ -1,90 +1,32 @@
 {
-  "generated_at": "2026-03-18T14:45:55+00:00",
-  "advisory_count": 5,
+  "generated_at": "2026-03-18T17:52:49+00:00",
+  "advisory_count": 0,
   "run_count": 140,
-  "statuses": {
-    "triage-manual": 5
-  },
+  "statuses": {},
   "run_statuses": {
     "verified-real": 136,
     "blocked-artifact": 3,
     "triage-manual": 1
   },
-  "recent_failures": [
-    {
-      "run_id": null,
-      "advisory_id": "nextjs--CVE-2026-27979",
-      "status": "triage-manual",
-      "title": "Next.js: Unbounded postponed resume buffering can lead to DoS",
-      "blocked_reason": null
-    },
-    {
-      "run_id": null,
-      "advisory_id": "nextjs--CVE-2026-27980",
-      "status": "triage-manual",
-      "title": "Next.js: Unbounded next/image disk cache growth can exhaust storage",
-      "blocked_reason": null
-    },
-    {
-      "run_id": null,
-      "advisory_id": "nextjs--CVE-2026-29057",
-      "status": "triage-manual",
-      "title": "Next.js: HTTP request smuggling in rewrites",
-      "blocked_reason": null
-    },
-    {
-      "run_id": null,
-      "advisory_id": "nextjs--CVE-2026-27978",
-      "status": "triage-manual",
-      "title": "Next.js: null origin can bypass Server Actions CSRF checks",
-      "blocked_reason": null
-    },
-    {
-      "run_id": null,
-      "advisory_id": "nextjs--CVE-2026-27977",
-      "status": "triage-manual",
-      "title": "Next.js: null origin can bypass dev HMR websocket CSRF checks",
-      "blocked_reason": null
-    }
-  ],
-  "systems": [
-    {
-      "system_id": "nextjs",
-      "display_name": "Next.js",
-      "total": 5,
-      "verified_real": 0,
-      "verified_synthetic": 0,
-      "blocked": 0,
-      "manual": 5,
-      "browser_required": 0,
-      "browser_present": 0,
-      "latest_update": "2026-03-17T16:31:34.160932Z",
-      "category": "frameworks",
-      "tier": "history-full",
-      "output_dir": "07-framework-security/frameworks/nextjs",
-      "families": [
-        {
-          "family": "proxy-boundary",
-          "total": 4,
-          "verified_real": 0,
-          "manual": 4
-        },
-        {
-          "family": "request-smuggling",
-          "total": 1,
-          "verified_real": 0,
-          "manual": 1
-        }
-      ]
-    }
-  ],
+  "recent_failures": [],
+  "monitoring": {
+    "active_source_count": 110,
+    "green_source_count": 110,
+    "source_failure_count": 0,
+    "open_alert_count": 0,
+    "last_fully_green_run": "2026-03-18T17:44:31+00:00"
+  },
+  "systems": [],
   "completeness": {
-    "advisory_total": 5,
+    "advisory_total": 0,
     "verified_real": 0,
     "verified_synthetic": 0,
     "blocked": 0,
-    "manual": 5,
+    "manual": 0,
     "verified_ratio": 0.0,
-    "complete": false
+    "complete": false,
+    "source_failure_count": 0,
+    "active_source_count": 110,
+    "open_alert_count": 0
   }
 }
diff --git a/08-threat-intel/generated/dashboard/systems.json b/08-threat-intel/generated/dashboard/systems.json
index f491d2f2..fe51488c 100644
--- a/08-threat-intel/generated/dashboard/systems.json
+++ b/08-threat-intel/generated/dashboard/systems.json
@@ -1,31 +1 @@
-[
-  {
-    "system_id": "nextjs",
-    "display_name": "Next.js",
-    "total": 5,
-    "verified_real": 0,
-    "verified_synthetic": 0,
-    "blocked": 0,
-    "manual": 5,
-    "browser_required": 0,
-    "browser_present": 0,
-    "latest_update": "2026-03-17T16:31:34.160932Z",
-    "category": "frameworks",
-    "tier": "history-full",
-    "output_dir": "07-framework-security/frameworks/nextjs",
-    "families": [
-      {
-        "family": "proxy-boundary",
-        "total": 4,
-        "verified_real": 0,
-        "manual": 4
-      },
-      {
-        "family": "request-smuggling",
-        "total": 1,
-        "verified_real": 0,
-        "manual": 1
-      }
-    ]
-  }
-]
+[]
diff --git a/08-threat-intel/generated/latest-ingest.md b/08-threat-intel/generated/latest-ingest.md
index 8df1e2f2..783f8f07 100644
--- a/08-threat-intel/generated/latest-ingest.md
+++ b/08-threat-intel/generated/latest-ingest.md
@@ -1,43 +1,11 @@
 # 最新同步摘要
 
-- 渲染时间: `2026-03-18T14:45:54+00:00`
+- 渲染时间: `2026-03-18T17:52:48+00:00`
 - 系统数量: `62`
-- Advisory 数量: `5`
-- 重点 Markdown 数量: `5`
+- Advisory 数量: `0`
+- 重点 Markdown 数量: `0`
 - Run Bundle 数量: `89`
-- 新增记录: `5`
+- 新增记录: `0`
 - 更新记录: `0`
 - Triage 数量: `0`
-- 失败的 source adapter: `29`
-
-## 失败列表
-
-- drupal::Drupal Security Advisories Site::HTTPError
-- discourse::Discourse Meta Security::HTTPError
-- adobe-commerce::Adobe Security Bulletins::ConnectionError
-- react::GitHub Global Advisories::TypeError
-- nextjs::GitHub Global Advisories::AttributeError
-- vue::GitHub Global Advisories::HTTPError
-- nuxt::GitHub Global Advisories::HTTPError
-- vite::GitHub Global Advisories::HTTPError
-- angular::GitHub Global Advisories::HTTPError
-- sveltekit::GitHub Global Advisories::HTTPError
-- astro::GitHub Global Advisories::HTTPError
-- express::GitHub Global Advisories::HTTPError
-- nestjs::GitHub Global Advisories::HTTPError
-- koa::GitHub Global Advisories::HTTPError
-- fastify::GitHub Global Advisories::HTTPError
-- hapi::GitHub Global Advisories::HTTPError
-- undici::GitHub Global Advisories::HTTPError
-- webpack::GitHub Global Advisories::HTTPError
-- esbuild::GitHub Global Advisories::HTTPError
-- spring-framework::GitHub Global Advisories::HTTPError
-- spring-security::GitHub Global Advisories::HTTPError
-- spring-boot::GitHub Global Advisories::HTTPError
-- laravel::GitHub Global Advisories::HTTPError
-- symfony::GitHub Global Advisories::HTTPError
-- django::Django Security RSS::HTTPError
-- flask::GitHub Global Advisories::HTTPError
-- werkzeug::GitHub Global Advisories::HTTPError
-- rails::GitHub Global Advisories::HTTPError
-- haproxy::HAProxy Security Advisories::HTTPError
+- 失败的 source adapter: `0`
diff --git a/08-threat-intel/generated/monitor-summary.json b/08-threat-intel/generated/monitor-summary.json
new file mode 100644
index 00000000..963c1f19
--- /dev/null
+++ b/08-threat-intel/generated/monitor-summary.json
@@ -0,0 +1,25 @@
+{
+  "generated_at": "2026-03-18T17:44:31+00:00",
+  "active_source_count": 110,
+  "green_source_count": 110,
+  "source_failure_count": 0,
+  "open_alert_count": 0,
+  "resolved_alert_count": 0,
+  "last_fully_green_run": "2026-03-18T17:44:31+00:00",
+  "source_catalog": {
+    "system_count": 62,
+    "source_count": 146,
+    "retired_source_count": 36
+  },
+  "ingest": {
+    "new_count": 0,
+    "updated_count": 0,
+    "failure_count": 0,
+    "systems_touched": []
+  },
+  "validation": {
+    "passed": true,
+    "error_count": 0,
+    "errors": []
+  }
+}
diff --git a/08-threat-intel/generated/retired-sources.json b/08-threat-intel/generated/retired-sources.json
new file mode 100644
index 00000000..2062e4dc
--- /dev/null
+++ b/08-threat-intel/generated/retired-sources.json
@@ -0,0 +1,447 @@
+[
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "Adobe Security Bulletins",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.",
+    "replacement_sources": [
+      "Adobe Magento Security Index",
+      "NVD Adobe Commerce",
+      "GHSA Adobe Commerce"
+    ],
+    "url": "https://helpx.adobe.com/security/products/magento.html"
+  },
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "GHSA Adobe Commerce",
+    "bucket": "ecosystem_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.",
+    "replacement_sources": [
+      "Adobe Magento Security Index",
+      "NVD Adobe Commerce"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "adobe-commerce",
+    "display_name": "Adobe Commerce",
+    "source_name": "Sansec Research",
+    "bucket": "ecosystem_sources",
+    "kind": "vendor-index",
+    "retired_reason": "Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.",
+    "replacement_sources": [
+      "GHSA Adobe Commerce",
+      "Adobe Magento Security Index"
+    ],
+    "url": "https://sansec.io/research"
+  },
+  {
+    "system_id": "angular",
+    "display_name": "Angular",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Angular"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "astro",
+    "display_name": "Astro",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Astro"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "discourse",
+    "display_name": "Discourse",
+    "source_name": "Discourse Meta Security",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Meta security category HTML changed and no longer provides stable scrape semantics for health checks.",
+    "replacement_sources": [
+      "Discourse Release Notes RSS",
+      "GitHub Discourse Advisories"
+    ],
+    "url": "https://meta.discourse.org/c/bug/security/40"
+  },
+  {
+    "system_id": "discourse",
+    "display_name": "Discourse",
+    "source_name": "GitHub Discourse Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.",
+    "replacement_sources": [
+      "Discourse Release Notes RSS"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "django",
+    "display_name": "Django",
+    "source_name": "Django Security RSS",
+    "bucket": "official_sources",
+    "kind": "rss-feed",
+    "retired_reason": "Official security tag feed became unstable; use official weblog index and release archive instead.",
+    "replacement_sources": [
+      "Django Security Weblog",
+      "Django Security Releases Archive"
+    ],
+    "url": "https://www.djangoproject.com/weblog/feeds/tags/security/"
+  },
+  {
+    "system_id": "drupal",
+    "display_name": "Drupal",
+    "source_name": "Drupal Security Advisories Site",
+    "bucket": "ecosystem_sources",
+    "kind": "html-links",
+    "retired_reason": "Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.",
+    "replacement_sources": [
+      "Drupal Security Advisories RSS",
+      "GHSA Drupal Core"
+    ],
+    "url": "https://www.drupal.org/security"
+  },
+  {
+    "system_id": "drupal",
+    "display_name": "Drupal",
+    "source_name": "GHSA Drupal Core",
+    "bucket": "ecosystem_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.",
+    "replacement_sources": [
+      "Drupal Security Advisories RSS",
+      "NVD Drupal"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "esbuild",
+    "display_name": "esbuild",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.",
+    "replacement_sources": [
+      "OSV esbuild"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "express",
+    "display_name": "Express",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Express"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "fastify",
+    "display_name": "Fastify",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Fastify"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "flask",
+    "display_name": "Flask",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Flask"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "hapi",
+    "display_name": "Hapi",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Hapi"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "haproxy",
+    "display_name": "HAProxy",
+    "source_name": "HAProxy Security Advisories",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Legacy haproxy.org security page no longer yields stable scrape results for monitoring.",
+    "replacement_sources": [
+      "HAProxy Blog Feed"
+    ],
+    "url": "https://www.haproxy.org/security/"
+  },
+  {
+    "system_id": "koa",
+    "display_name": "Koa",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Koa"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "laravel",
+    "display_name": "Laravel",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Laravel"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "mattermost",
+    "display_name": "Mattermost",
+    "source_name": "Mattermost Security Updates",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD Mattermost"
+    ],
+    "url": "https://mattermost.com/security-updates/"
+  },
+  {
+    "system_id": "mediawiki",
+    "display_name": "MediaWiki",
+    "source_name": "MediaWiki Security Releases",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD MediaWiki"
+    ],
+    "url": "https://www.mediawiki.org/wiki/Security"
+  },
+  {
+    "system_id": "moodle",
+    "display_name": "Moodle",
+    "source_name": "Moodle Security News",
+    "bucket": "official_sources",
+    "kind": "html-links",
+    "retired_reason": "Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+    "replacement_sources": [
+      "NVD Moodle"
+    ],
+    "url": "https://moodle.org/security/"
+  },
+  {
+    "system_id": "nestjs",
+    "display_name": "NestJS",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.",
+    "replacement_sources": [
+      "OSV NestJS"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "nextjs",
+    "display_name": "Next.js",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.",
+    "replacement_sources": [
+      "GitHub Next.js Advisories",
+      "OSV Next.js"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "nuxt",
+    "display_name": "Nuxt",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.",
+    "replacement_sources": [
+      "Nuxt Security",
+      "OSV Nuxt"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "rails",
+    "display_name": "Ruby on Rails",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Rails"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "react",
+    "display_name": "React",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.",
+    "replacement_sources": [
+      "GitHub React Advisories",
+      "OSV React"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-boot",
+    "display_name": "Spring Boot",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-framework",
+    "display_name": "Spring Framework",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "spring-security",
+    "display_name": "Spring Security",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+    "replacement_sources": [
+      "Spring Security Advisories"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "sveltekit",
+    "display_name": "SvelteKit",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.",
+    "replacement_sources": [
+      "OSV SvelteKit"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "symfony",
+    "display_name": "Symfony",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Symfony"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "undici",
+    "display_name": "Undici",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.",
+    "replacement_sources": [
+      "OSV Undici"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "vite",
+    "display_name": "Vite",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.",
+    "replacement_sources": [
+      "Vite Security",
+      "OSV Vite"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "vue",
+    "display_name": "Vue",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.",
+    "replacement_sources": [
+      "Vue Security",
+      "OSV Vue"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "webpack",
+    "display_name": "webpack",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.",
+    "replacement_sources": [
+      "OSV webpack"
+    ],
+    "url": ""
+  },
+  {
+    "system_id": "werkzeug",
+    "display_name": "Werkzeug",
+    "source_name": "GitHub Global Advisories",
+    "bucket": "official_sources",
+    "kind": "ghsa-global",
+    "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.",
+    "replacement_sources": [
+      "OSV Werkzeug"
+    ],
+    "url": ""
+  }
+]
diff --git a/08-threat-intel/generated/run-summary.json b/08-threat-intel/generated/run-summary.json
index 544d6c46..f95702c9 100644
--- a/08-threat-intel/generated/run-summary.json
+++ b/08-threat-intel/generated/run-summary.json
@@ -1,44 +1,12 @@
 {
-  "generated_at": "2026-03-18T14:45:54+00:00",
+  "generated_at": "2026-03-18T17:52:48+00:00",
   "system_count": 62,
-  "advisory_count": 5,
-  "markdown_count": 5,
-  "new_count": 5,
+  "advisory_count": 0,
+  "markdown_count": 0,
+  "new_count": 0,
   "updated_count": 0,
-  "systems_touched": [
-    "nextjs"
-  ],
+  "systems_touched": [],
   "triage_count": 0,
   "run_bundle_count": 89,
-  "failures": [
-    "drupal::Drupal Security Advisories Site::HTTPError",
-    "discourse::Discourse Meta Security::HTTPError",
-    "adobe-commerce::Adobe Security Bulletins::ConnectionError",
-    "react::GitHub Global Advisories::TypeError",
-    "nextjs::GitHub Global Advisories::AttributeError",
-    "vue::GitHub Global Advisories::HTTPError",
-    "nuxt::GitHub Global Advisories::HTTPError",
-    "vite::GitHub Global Advisories::HTTPError",
-    "angular::GitHub Global Advisories::HTTPError",
-    "sveltekit::GitHub Global Advisories::HTTPError",
-    "astro::GitHub Global Advisories::HTTPError",
-    "express::GitHub Global Advisories::HTTPError",
-    "nestjs::GitHub Global Advisories::HTTPError",
-    "koa::GitHub Global Advisories::HTTPError",
-    "fastify::GitHub Global Advisories::HTTPError",
-    "hapi::GitHub Global Advisories::HTTPError",
-    "undici::GitHub Global Advisories::HTTPError",
-    "webpack::GitHub Global Advisories::HTTPError",
-    "esbuild::GitHub Global Advisories::HTTPError",
-    "spring-framework::GitHub Global Advisories::HTTPError",
-    "spring-security::GitHub Global Advisories::HTTPError",
-    "spring-boot::GitHub Global Advisories::HTTPError",
-    "laravel::GitHub Global Advisories::HTTPError",
-    "symfony::GitHub Global Advisories::HTTPError",
-    "django::Django Security RSS::HTTPError",
-    "flask::GitHub Global Advisories::HTTPError",
-    "werkzeug::GitHub Global Advisories::HTTPError",
-    "rails::GitHub Global Advisories::HTTPError",
-    "haproxy::HAProxy Security Advisories::HTTPError"
-  ]
+  "failures": []
 }
diff --git a/08-threat-intel/generated/source-catalog-audit.json b/08-threat-intel/generated/source-catalog-audit.json
new file mode 100644
index 00000000..f3503cab
--- /dev/null
+++ b/08-threat-intel/generated/source-catalog-audit.json
@@ -0,0 +1,1655 @@
+{
+  "generated_at": "2026-03-18T17:41:42+00:00",
+  "system_count": 62,
+  "source_count": 146,
+  "active_source_count": 110,
+  "retired_source_count": 36,
+  "systems_with_active_official": 62,
+  "systems_with_machine_readable_source": 57,
+  "systems": [
+    {
+      "system_id": "adminer",
+      "display_name": "Adminer",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 1,
+      "active_source_total": 1,
+      "retired_source_total": 0,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "adobe-commerce",
+      "display_name": "Adobe Commerce",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 5,
+      "active_source_total": 2,
+      "retired_source_total": 3,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "angular",
+      "display_name": "Angular",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "apache-httpd",
+      "display_name": "Apache HTTP Server",
+      "category": "servers",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 3,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 2,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "apache-tomcat",
+      "display_name": "Apache Tomcat",
+      "category": "servers",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 3,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 2,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "aspnet-core",
+      "display_name": "ASP.NET Core",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 1,
+      "active_source_total": 1,
+      "retired_source_total": 0,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "astro",
+      "display_name": "Astro",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "caddy",
+      "display_name": "Caddy",
+      "category": "servers",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "directus",
+      "display_name": "Directus",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "discourse",
+      "display_name": "Discourse",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 3,
+      "active_source_total": 1,
+      "retired_source_total": 2,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "django",
+      "display_name": "Django",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 4,
+      "active_source_total": 3,
+      "retired_source_total": 1,
+      "official_active": 3,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "drupal",
+      "display_name": "Drupal",
+      "category": "cms",
+      "tier": "history-full",
+      "source_total": 4,
+      "active_source_total": 2,
+      "retired_source_total": 2,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 2,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "echo",
+      "display_name": "Echo",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 1,
+      "active_source_total": 1,
+      "retired_source_total": 0,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "esbuild",
+      "display_name": "esbuild",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "express",
+      "display_name": "Express",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "fastify",
+      "display_name": "Fastify",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "flask",
+      "display_name": "Flask",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "ghost",
+      "display_name": "Ghost",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "gin",
+      "display_name": "Gin",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 1,
+      "active_source_total": 1,
+      "retired_source_total": 0,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "gitea",
+      "display_name": "Gitea",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "gitlab-ce",
+      "display_name": "GitLab CE",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 1,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "grafana",
+      "display_name": "Grafana",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "hapi",
+      "display_name": "Hapi",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "haproxy",
+      "display_name": "HAProxy",
+      "category": "servers",
+      "tier": "rolling-24m",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 2,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "jenkins",
+      "display_name": "Jenkins",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "joomla",
+      "display_name": "Joomla",
+      "category": "cms",
+      "tier": "history-full",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "kibana",
+      "display_name": "Kibana",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "koa",
+      "display_name": "Koa",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "laravel",
+      "display_name": "Laravel",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "magento-open-source",
+      "display_name": "Magento Open Source",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 1,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "mattermost",
+      "display_name": "Mattermost",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "mediawiki",
+      "display_name": "MediaWiki",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "medusa",
+      "display_name": "Medusa",
+      "category": "ecommerce",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "moodle",
+      "display_name": "Moodle",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "nestjs",
+      "display_name": "NestJS",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "nextjs",
+      "display_name": "Next.js",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "nginx",
+      "display_name": "Nginx",
+      "category": "servers",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 3,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 2,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "nodejs",
+      "display_name": "Node.js",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "nuxt",
+      "display_name": "Nuxt",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "opencart",
+      "display_name": "OpenCart",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "openmage",
+      "display_name": "OpenMage / Mage-OS",
+      "category": "ecommerce",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "phpmyadmin",
+      "display_name": "phpMyAdmin",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "prestashop",
+      "display_name": "PrestaShop",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 3,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 1,
+      "research_active": 0,
+      "machine_readable_active": 0,
+      "has_active_official": true,
+      "has_machine_readable_source": false
+    },
+    {
+      "system_id": "rails",
+      "display_name": "Ruby on Rails",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "react",
+      "display_name": "React",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "redmine",
+      "display_name": "Redmine",
+      "category": "platforms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "saleor",
+      "display_name": "Saleor",
+      "category": "ecommerce",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "shopware",
+      "display_name": "Shopware",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "spring-boot",
+      "display_name": "Spring Boot",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 0,
+      "has_active_official": true,
+      "has_machine_readable_source": false
+    },
+    {
+      "system_id": "spring-framework",
+      "display_name": "Spring Framework",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 0,
+      "has_active_official": true,
+      "has_machine_readable_source": false
+    },
+    {
+      "system_id": "spring-security",
+      "display_name": "Spring Security",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 0,
+      "has_active_official": true,
+      "has_machine_readable_source": false
+    },
+    {
+      "system_id": "strapi",
+      "display_name": "Strapi",
+      "category": "cms",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "sveltekit",
+      "display_name": "SvelteKit",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "symfony",
+      "display_name": "Symfony",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "traefik",
+      "display_name": "Traefik",
+      "category": "servers",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 2,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "undici",
+      "display_name": "Undici",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "vite",
+      "display_name": "Vite",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "vue",
+      "display_name": "Vue",
+      "category": "frameworks",
+      "tier": "history-full",
+      "source_total": 3,
+      "active_source_total": 2,
+      "retired_source_total": 1,
+      "official_active": 2,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "webpack",
+      "display_name": "webpack",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "werkzeug",
+      "display_name": "Werkzeug",
+      "category": "frameworks",
+      "tier": "rolling-24m",
+      "source_total": 2,
+      "active_source_total": 1,
+      "retired_source_total": 1,
+      "official_active": 1,
+      "ecosystem_active": 0,
+      "research_active": 0,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    },
+    {
+      "system_id": "woocommerce",
+      "display_name": "WooCommerce",
+      "category": "ecommerce",
+      "tier": "history-full",
+      "source_total": 4,
+      "active_source_total": 4,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 2,
+      "research_active": 0,
+      "machine_readable_active": 0,
+      "has_active_official": true,
+      "has_machine_readable_source": false
+    },
+    {
+      "system_id": "wordpress",
+      "display_name": "WordPress",
+      "category": "cms",
+      "tier": "history-full",
+      "source_total": 6,
+      "active_source_total": 6,
+      "retired_source_total": 0,
+      "official_active": 2,
+      "ecosystem_active": 3,
+      "research_active": 1,
+      "machine_readable_active": 1,
+      "has_active_official": true,
+      "has_machine_readable_source": true
+    }
+  ],
+  "retired_sources": [
+    {
+      "system_id": "adobe-commerce",
+      "display_name": "Adobe Commerce",
+      "source_name": "Adobe Security Bulletins",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.",
+      "replacement_sources": [
+        "Adobe Magento Security Index",
+        "NVD Adobe Commerce",
+        "GHSA Adobe Commerce"
+      ],
+      "url": "https://helpx.adobe.com/security/products/magento.html"
+    },
+    {
+      "system_id": "adobe-commerce",
+      "display_name": "Adobe Commerce",
+      "source_name": "GHSA Adobe Commerce",
+      "bucket": "ecosystem_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.",
+      "replacement_sources": [
+        "Adobe Magento Security Index",
+        "NVD Adobe Commerce"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "adobe-commerce",
+      "display_name": "Adobe Commerce",
+      "source_name": "Sansec Research",
+      "bucket": "ecosystem_sources",
+      "kind": "vendor-index",
+      "retired_reason": "Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.",
+      "replacement_sources": [
+        "GHSA Adobe Commerce",
+        "Adobe Magento Security Index"
+      ],
+      "url": "https://sansec.io/research"
+    },
+    {
+      "system_id": "angular",
+      "display_name": "Angular",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Angular"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "astro",
+      "display_name": "Astro",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Astro"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "discourse",
+      "display_name": "Discourse",
+      "source_name": "Discourse Meta Security",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "Meta security category HTML changed and no longer provides stable scrape semantics for health checks.",
+      "replacement_sources": [
+        "Discourse Release Notes RSS",
+        "GitHub Discourse Advisories"
+      ],
+      "url": "https://meta.discourse.org/c/bug/security/40"
+    },
+    {
+      "system_id": "discourse",
+      "display_name": "Discourse",
+      "source_name": "GitHub Discourse Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.",
+      "replacement_sources": [
+        "Discourse Release Notes RSS"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "django",
+      "display_name": "Django",
+      "source_name": "Django Security RSS",
+      "bucket": "official_sources",
+      "kind": "rss-feed",
+      "retired_reason": "Official security tag feed became unstable; use official weblog index and release archive instead.",
+      "replacement_sources": [
+        "Django Security Weblog",
+        "Django Security Releases Archive"
+      ],
+      "url": "https://www.djangoproject.com/weblog/feeds/tags/security/"
+    },
+    {
+      "system_id": "drupal",
+      "display_name": "Drupal",
+      "source_name": "Drupal Security Advisories Site",
+      "bucket": "ecosystem_sources",
+      "kind": "html-links",
+      "retired_reason": "Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.",
+      "replacement_sources": [
+        "Drupal Security Advisories RSS",
+        "GHSA Drupal Core"
+      ],
+      "url": "https://www.drupal.org/security"
+    },
+    {
+      "system_id": "drupal",
+      "display_name": "Drupal",
+      "source_name": "GHSA Drupal Core",
+      "bucket": "ecosystem_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.",
+      "replacement_sources": [
+        "Drupal Security Advisories RSS",
+        "NVD Drupal"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "esbuild",
+      "display_name": "esbuild",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.",
+      "replacement_sources": [
+        "OSV esbuild"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "express",
+      "display_name": "Express",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Express"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "fastify",
+      "display_name": "Fastify",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Fastify"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "flask",
+      "display_name": "Flask",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.",
+      "replacement_sources": [
+        "OSV Flask"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "hapi",
+      "display_name": "Hapi",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Hapi"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "haproxy",
+      "display_name": "HAProxy",
+      "source_name": "HAProxy Security Advisories",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "Legacy haproxy.org security page no longer yields stable scrape results for monitoring.",
+      "replacement_sources": [
+        "HAProxy Blog Feed"
+      ],
+      "url": "https://www.haproxy.org/security/"
+    },
+    {
+      "system_id": "koa",
+      "display_name": "Koa",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Koa"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "laravel",
+      "display_name": "Laravel",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.",
+      "replacement_sources": [
+        "OSV Laravel"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "mattermost",
+      "display_name": "Mattermost",
+      "source_name": "Mattermost Security Updates",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+      "replacement_sources": [
+        "NVD Mattermost"
+      ],
+      "url": "https://mattermost.com/security-updates/"
+    },
+    {
+      "system_id": "mediawiki",
+      "display_name": "MediaWiki",
+      "source_name": "MediaWiki Security Releases",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.",
+      "replacement_sources": [
+        "NVD MediaWiki"
+      ],
+      "url": "https://www.mediawiki.org/wiki/Security"
+    },
+    {
+      "system_id": "moodle",
+      "display_name": "Moodle",
+      "source_name": "Moodle Security News",
+      "bucket": "official_sources",
+      "kind": "html-links",
+      "retired_reason": "Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+      "replacement_sources": [
+        "NVD Moodle"
+      ],
+      "url": "https://moodle.org/security/"
+    },
+    {
+      "system_id": "nestjs",
+      "display_name": "NestJS",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.",
+      "replacement_sources": [
+        "OSV NestJS"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "nextjs",
+      "display_name": "Next.js",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.",
+      "replacement_sources": [
+        "GitHub Next.js Advisories",
+        "OSV Next.js"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "nuxt",
+      "display_name": "Nuxt",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.",
+      "replacement_sources": [
+        "Nuxt Security",
+        "OSV Nuxt"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "rails",
+      "display_name": "Ruby on Rails",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.",
+      "replacement_sources": [
+        "OSV Rails"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "react",
+      "display_name": "React",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.",
+      "replacement_sources": [
+        "GitHub React Advisories",
+        "OSV React"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "spring-boot",
+      "display_name": "Spring Boot",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "spring-framework",
+      "display_name": "Spring Framework",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "spring-security",
+      "display_name": "Spring Security",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "sveltekit",
+      "display_name": "SvelteKit",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.",
+      "replacement_sources": [
+        "OSV SvelteKit"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "symfony",
+      "display_name": "Symfony",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.",
+      "replacement_sources": [
+        "OSV Symfony"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "undici",
+      "display_name": "Undici",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.",
+      "replacement_sources": [
+        "OSV Undici"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "vite",
+      "display_name": "Vite",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.",
+      "replacement_sources": [
+        "Vite Security",
+        "OSV Vite"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "vue",
+      "display_name": "Vue",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.",
+      "replacement_sources": [
+        "Vue Security",
+        "OSV Vue"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "webpack",
+      "display_name": "webpack",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.",
+      "replacement_sources": [
+        "OSV webpack"
+      ],
+      "url": ""
+    },
+    {
+      "system_id": "werkzeug",
+      "display_name": "Werkzeug",
+      "source_name": "GitHub Global Advisories",
+      "bucket": "official_sources",
+      "kind": "ghsa-global",
+      "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.",
+      "replacement_sources": [
+        "OSV Werkzeug"
+      ],
+      "url": ""
+    }
+  ],
+  "replacement_map": [
+    {
+      "system_id": "adobe-commerce",
+      "retired_source": "Adobe Security Bulletins",
+      "replacement_sources": [
+        "Adobe Magento Security Index",
+        "NVD Adobe Commerce",
+        "GHSA Adobe Commerce"
+      ]
+    },
+    {
+      "system_id": "adobe-commerce",
+      "retired_source": "GHSA Adobe Commerce",
+      "replacement_sources": [
+        "Adobe Magento Security Index",
+        "NVD Adobe Commerce"
+      ]
+    },
+    {
+      "system_id": "adobe-commerce",
+      "retired_source": "Sansec Research",
+      "replacement_sources": [
+        "GHSA Adobe Commerce",
+        "Adobe Magento Security Index"
+      ]
+    },
+    {
+      "system_id": "angular",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Angular"
+      ]
+    },
+    {
+      "system_id": "astro",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Astro"
+      ]
+    },
+    {
+      "system_id": "discourse",
+      "retired_source": "Discourse Meta Security",
+      "replacement_sources": [
+        "Discourse Release Notes RSS",
+        "GitHub Discourse Advisories"
+      ]
+    },
+    {
+      "system_id": "discourse",
+      "retired_source": "GitHub Discourse Advisories",
+      "replacement_sources": [
+        "Discourse Release Notes RSS"
+      ]
+    },
+    {
+      "system_id": "django",
+      "retired_source": "Django Security RSS",
+      "replacement_sources": [
+        "Django Security Weblog",
+        "Django Security Releases Archive"
+      ]
+    },
+    {
+      "system_id": "drupal",
+      "retired_source": "Drupal Security Advisories Site",
+      "replacement_sources": [
+        "Drupal Security Advisories RSS",
+        "GHSA Drupal Core"
+      ]
+    },
+    {
+      "system_id": "drupal",
+      "retired_source": "GHSA Drupal Core",
+      "replacement_sources": [
+        "Drupal Security Advisories RSS",
+        "NVD Drupal"
+      ]
+    },
+    {
+      "system_id": "esbuild",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV esbuild"
+      ]
+    },
+    {
+      "system_id": "express",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Express"
+      ]
+    },
+    {
+      "system_id": "fastify",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Fastify"
+      ]
+    },
+    {
+      "system_id": "flask",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Flask"
+      ]
+    },
+    {
+      "system_id": "hapi",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Hapi"
+      ]
+    },
+    {
+      "system_id": "haproxy",
+      "retired_source": "HAProxy Security Advisories",
+      "replacement_sources": [
+        "HAProxy Blog Feed"
+      ]
+    },
+    {
+      "system_id": "koa",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Koa"
+      ]
+    },
+    {
+      "system_id": "laravel",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Laravel"
+      ]
+    },
+    {
+      "system_id": "mattermost",
+      "retired_source": "Mattermost Security Updates",
+      "replacement_sources": [
+        "NVD Mattermost"
+      ]
+    },
+    {
+      "system_id": "mediawiki",
+      "retired_source": "MediaWiki Security Releases",
+      "replacement_sources": [
+        "NVD MediaWiki"
+      ]
+    },
+    {
+      "system_id": "moodle",
+      "retired_source": "Moodle Security News",
+      "replacement_sources": [
+        "NVD Moodle"
+      ]
+    },
+    {
+      "system_id": "nestjs",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV NestJS"
+      ]
+    },
+    {
+      "system_id": "nextjs",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "GitHub Next.js Advisories",
+        "OSV Next.js"
+      ]
+    },
+    {
+      "system_id": "nuxt",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Nuxt Security",
+        "OSV Nuxt"
+      ]
+    },
+    {
+      "system_id": "rails",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Rails"
+      ]
+    },
+    {
+      "system_id": "react",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "GitHub React Advisories",
+        "OSV React"
+      ]
+    },
+    {
+      "system_id": "spring-boot",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ]
+    },
+    {
+      "system_id": "spring-framework",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ]
+    },
+    {
+      "system_id": "spring-security",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Spring Security Advisories"
+      ]
+    },
+    {
+      "system_id": "sveltekit",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV SvelteKit"
+      ]
+    },
+    {
+      "system_id": "symfony",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Symfony"
+      ]
+    },
+    {
+      "system_id": "undici",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Undici"
+      ]
+    },
+    {
+      "system_id": "vite",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Vite Security",
+        "OSV Vite"
+      ]
+    },
+    {
+      "system_id": "vue",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "Vue Security",
+        "OSV Vue"
+      ]
+    },
+    {
+      "system_id": "webpack",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV webpack"
+      ]
+    },
+    {
+      "system_id": "werkzeug",
+      "retired_source": "GitHub Global Advisories",
+      "replacement_sources": [
+        "OSV Werkzeug"
+      ]
+    }
+  ]
+}
diff --git a/08-threat-intel/generated/source-catalog-audit.md b/08-threat-intel/generated/source-catalog-audit.md
new file mode 100644
index 00000000..10d3a570
--- /dev/null
+++ b/08-threat-intel/generated/source-catalog-audit.md
@@ -0,0 +1,48 @@
+# Source Catalog Audit
+
+- generated_at: `2026-03-18T17:41:42+00:00`
+- systems: `62`
+- sources: `146`
+- active_sources: `110`
+- retired_sources: `36`
+- systems_with_active_official: `62/62`
+- systems_with_machine_readable_source: `57/62`
+
+## Retired Sources
+
+- `adobe-commerce` `Adobe Security Bulletins` -> replacements: `Adobe Magento Security Index, NVD Adobe Commerce, GHSA Adobe Commerce` | reason: Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.
+- `adobe-commerce` `GHSA Adobe Commerce` -> replacements: `Adobe Magento Security Index, NVD Adobe Commerce` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.
+- `adobe-commerce` `Sansec Research` -> replacements: `GHSA Adobe Commerce, Adobe Magento Security Index` | reason: Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.
+- `angular` `GitHub Global Advisories` -> replacements: `OSV Angular` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.
+- `astro` `GitHub Global Advisories` -> replacements: `OSV Astro` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.
+- `discourse` `Discourse Meta Security` -> replacements: `Discourse Release Notes RSS, GitHub Discourse Advisories` | reason: Meta security category HTML changed and no longer provides stable scrape semantics for health checks.
+- `discourse` `GitHub Discourse Advisories` -> replacements: `Discourse Release Notes RSS` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.
+- `django` `Django Security RSS` -> replacements: `Django Security Weblog, Django Security Releases Archive` | reason: Official security tag feed became unstable; use official weblog index and release archive instead.
+- `drupal` `Drupal Security Advisories Site` -> replacements: `Drupal Security Advisories RSS, GHSA Drupal Core` | reason: Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.
+- `drupal` `GHSA Drupal Core` -> replacements: `Drupal Security Advisories RSS, NVD Drupal` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.
+- `esbuild` `GitHub Global Advisories` -> replacements: `OSV esbuild` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.
+- `express` `GitHub Global Advisories` -> replacements: `OSV Express` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.
+- `fastify` `GitHub Global Advisories` -> replacements: `OSV Fastify` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.
+- `flask` `GitHub Global Advisories` -> replacements: `OSV Flask` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.
+- `hapi` `GitHub Global Advisories` -> replacements: `OSV Hapi` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.
+- `haproxy` `HAProxy Security Advisories` -> replacements: `HAProxy Blog Feed` | reason: Legacy haproxy.org security page no longer yields stable scrape results for monitoring.
+- `koa` `GitHub Global Advisories` -> replacements: `OSV Koa` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.
+- `laravel` `GitHub Global Advisories` -> replacements: `OSV Laravel` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.
+- `mattermost` `Mattermost Security Updates` -> replacements: `NVD Mattermost` | reason: Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.
+- `mediawiki` `MediaWiki Security Releases` -> replacements: `NVD MediaWiki` | reason: MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.
+- `moodle` `Moodle Security News` -> replacements: `NVD Moodle` | reason: Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.
+- `nestjs` `GitHub Global Advisories` -> replacements: `OSV NestJS` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.
+- `nextjs` `GitHub Global Advisories` -> replacements: `GitHub Next.js Advisories, OSV Next.js` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.
+- `nuxt` `GitHub Global Advisories` -> replacements: `Nuxt Security, OSV Nuxt` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.
+- `rails` `GitHub Global Advisories` -> replacements: `OSV Rails` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.
+- `react` `GitHub Global Advisories` -> replacements: `GitHub React Advisories, OSV React` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.
+- `spring-boot` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+- `spring-framework` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.
+- `spring-security` `GitHub Global Advisories` -> replacements: `Spring Security Advisories` | reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+- `sveltekit` `GitHub Global Advisories` -> replacements: `OSV SvelteKit` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.
+- `symfony` `GitHub Global Advisories` -> replacements: `OSV Symfony` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.
+- `undici` `GitHub Global Advisories` -> replacements: `OSV Undici` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.
+- `vite` `GitHub Global Advisories` -> replacements: `Vite Security, OSV Vite` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.
+- `vue` `GitHub Global Advisories` -> replacements: `Vue Security, OSV Vue` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.
+- `webpack` `GitHub Global Advisories` -> replacements: `OSV webpack` | reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.
+- `werkzeug` `GitHub Global Advisories` -> replacements: `OSV Werkzeug` | reason: Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.
diff --git a/08-threat-intel/generated/source-health.json b/08-threat-intel/generated/source-health.json
new file mode 100644
index 00000000..7c98cbc9
--- /dev/null
+++ b/08-threat-intel/generated/source-health.json
@@ -0,0 +1,1218 @@
+{
+  "generated_at": "2026-03-18T17:44:31+00:00",
+  "active_source_count": 110,
+  "green_source_count": 110,
+  "failure_count": 0,
+  "all_green": true,
+  "last_fully_green_run": "2026-03-18T17:44:31+00:00",
+  "retries_performed": 0,
+  "probes": [
+    {
+      "system_id": "adminer",
+      "source_name": "NVD Adminer",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "adobe-commerce",
+      "source_name": "Adobe Magento Security Index",
+      "source_kind": "vendor-index",
+      "kind": "vendor-index",
+      "items_seen": 46
+    },
+    {
+      "system_id": "adobe-commerce",
+      "source_name": "NVD Adobe Commerce",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "angular",
+      "source_name": "OSV Angular",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "apache-httpd",
+      "source_name": "Apache HTTPD Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 182
+    },
+    {
+      "system_id": "apache-httpd",
+      "source_name": "CISA KEV Apache HTTPD",
+      "source_kind": "kev-json",
+      "kind": "kev-json",
+      "items_seen": 1544
+    },
+    {
+      "system_id": "apache-httpd",
+      "source_name": "NVD Apache HTTP Server",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "apache-tomcat",
+      "source_name": "Apache Tomcat Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 270
+    },
+    {
+      "system_id": "apache-tomcat",
+      "source_name": "CISA KEV Tomcat",
+      "source_kind": "kev-json",
+      "kind": "kev-json",
+      "items_seen": 1544
+    },
+    {
+      "system_id": "apache-tomcat",
+      "source_name": "NVD Tomcat",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "aspnet-core",
+      "source_name": "NVD ASP.NET Core",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "astro",
+      "source_name": "OSV Astro",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "caddy",
+      "source_name": "GitHub Caddy Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 114
+    },
+    {
+      "system_id": "caddy",
+      "source_name": "OSV Caddy",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "directus",
+      "source_name": "Directus GitHub Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 127
+    },
+    {
+      "system_id": "directus",
+      "source_name": "OSV Directus",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "discourse",
+      "source_name": "Discourse Release Notes RSS",
+      "source_kind": "rss-feed",
+      "kind": "rss-feed",
+      "items_seen": 30
+    },
+    {
+      "system_id": "django",
+      "source_name": "Django Security Releases Archive",
+      "source_kind": "vendor-index",
+      "kind": "vendor-index",
+      "items_seen": 1276
+    },
+    {
+      "system_id": "django",
+      "source_name": "Django Security Weblog",
+      "source_kind": "vendor-index",
+      "kind": "vendor-index",
+      "items_seen": 332
+    },
+    {
+      "system_id": "django",
+      "source_name": "OSV Django",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "drupal",
+      "source_name": "Drupal Security Advisories RSS",
+      "source_kind": "rss-feed",
+      "kind": "rss-feed",
+      "items_seen": 20
+    },
+    {
+      "system_id": "drupal",
+      "source_name": "NVD Drupal",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "echo",
+      "source_name": "OSV Echo",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "esbuild",
+      "source_name": "OSV esbuild",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "express",
+      "source_name": "OSV Express",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "fastify",
+      "source_name": "OSV Fastify",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "flask",
+      "source_name": "OSV Flask",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "ghost",
+      "source_name": "Ghost GitHub Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 119
+    },
+    {
+      "system_id": "ghost",
+      "source_name": "NVD Ghost",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "gin",
+      "source_name": "OSV Gin",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "gitea",
+      "source_name": "GitHub Gitea Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 98
+    },
+    {
+      "system_id": "gitea",
+      "source_name": "OSV Gitea",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "gitlab-ce",
+      "source_name": "GitLab Advisory Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 5
+    },
+    {
+      "system_id": "gitlab-ce",
+      "source_name": "GitLab Security Releases",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 250
+    },
+    {
+      "system_id": "gitlab-ce",
+      "source_name": "NVD GitLab",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "grafana",
+      "source_name": "CISA KEV Grafana",
+      "source_kind": "kev-json",
+      "kind": "kev-json",
+      "items_seen": 1544
+    },
+    {
+      "system_id": "grafana",
+      "source_name": "Grafana Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 159
+    },
+    {
+      "system_id": "hapi",
+      "source_name": "OSV Hapi",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "haproxy",
+      "source_name": "HAProxy Blog Feed",
+      "source_kind": "rss-feed",
+      "kind": "rss-feed",
+      "items_seen": 10
+    },
+    {
+      "system_id": "haproxy",
+      "source_name": "NVD HAProxy",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "jenkins",
+      "source_name": "Jenkins Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 1194
+    },
+    {
+      "system_id": "jenkins",
+      "source_name": "NVD Jenkins",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "joomla",
+      "source_name": "Joomla Security Centre",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 139
+    },
+    {
+      "system_id": "joomla",
+      "source_name": "NVD Joomla",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "kibana",
+      "source_name": "Elastic Security Announcements",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 82
+    },
+    {
+      "system_id": "kibana",
+      "source_name": "NVD Kibana",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "koa",
+      "source_name": "OSV Koa",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "laravel",
+      "source_name": "OSV Laravel",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "magento-open-source",
+      "source_name": "Magento GitHub Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 99
+    },
+    {
+      "system_id": "magento-open-source",
+      "source_name": "NVD Magento",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "magento-open-source",
+      "source_name": "Sansec Research",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 134
+    },
+    {
+      "system_id": "mattermost",
+      "source_name": "NVD Mattermost",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "mediawiki",
+      "source_name": "NVD MediaWiki",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "medusa",
+      "source_name": "GitHub Medusa Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 102
+    },
+    {
+      "system_id": "medusa",
+      "source_name": "OSV Medusa",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "moodle",
+      "source_name": "NVD Moodle",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "nestjs",
+      "source_name": "OSV NestJS",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "nextjs",
+      "source_name": "GitHub Next.js Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 123
+    },
+    {
+      "system_id": "nextjs",
+      "source_name": "OSV Next.js",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "nginx",
+      "source_name": "CISA KEV NGINX",
+      "source_kind": "kev-json",
+      "kind": "kev-json",
+      "items_seen": 1544
+    },
+    {
+      "system_id": "nginx",
+      "source_name": "NGINX Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 138
+    },
+    {
+      "system_id": "nginx",
+      "source_name": "NVD NGINX",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "nodejs",
+      "source_name": "CISA KEV Node.js",
+      "source_kind": "kev-json",
+      "kind": "kev-json",
+      "items_seen": 1544
+    },
+    {
+      "system_id": "nodejs",
+      "source_name": "Node.js Security Releases",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 74
+    },
+    {
+      "system_id": "nuxt",
+      "source_name": "Nuxt Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 118
+    },
+    {
+      "system_id": "nuxt",
+      "source_name": "OSV Nuxt",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "opencart",
+      "source_name": "NVD OpenCart",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "opencart",
+      "source_name": "OpenCart Releases",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 1500
+    },
+    {
+      "system_id": "openmage",
+      "source_name": "NVD OpenMage",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "openmage",
+      "source_name": "OpenMage GitHub Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 125
+    },
+    {
+      "system_id": "phpmyadmin",
+      "source_name": "NVD phpMyAdmin",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "phpmyadmin",
+      "source_name": "phpMyAdmin Security Page",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 262
+    },
+    {
+      "system_id": "prestashop",
+      "source_name": "Friends Of Presta Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 38
+    },
+    {
+      "system_id": "prestashop",
+      "source_name": "GitHub PrestaShop Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 127
+    },
+    {
+      "system_id": "prestashop",
+      "source_name": "PrestaShop Security Page",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 60
+    },
+    {
+      "system_id": "rails",
+      "source_name": "OSV Rails",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "react",
+      "source_name": "GitHub React Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 110
+    },
+    {
+      "system_id": "react",
+      "source_name": "OSV React",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "redmine",
+      "source_name": "NVD Redmine",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "redmine",
+      "source_name": "Redmine Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 371
+    },
+    {
+      "system_id": "saleor",
+      "source_name": "GitHub Saleor Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 120
+    },
+    {
+      "system_id": "saleor",
+      "source_name": "NVD Saleor",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "shopware",
+      "source_name": "NVD Shopware",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "shopware",
+      "source_name": "Shopware Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 129
+    },
+    {
+      "system_id": "spring-boot",
+      "source_name": "Spring Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 118
+    },
+    {
+      "system_id": "spring-framework",
+      "source_name": "Spring Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 118
+    },
+    {
+      "system_id": "spring-security",
+      "source_name": "Spring Security Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 118
+    },
+    {
+      "system_id": "strapi",
+      "source_name": "OSV Strapi",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "strapi",
+      "source_name": "Strapi GitHub Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 124
+    },
+    {
+      "system_id": "sveltekit",
+      "source_name": "OSV SvelteKit",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "symfony",
+      "source_name": "OSV Symfony",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "traefik",
+      "source_name": "GitHub Traefik Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 124
+    },
+    {
+      "system_id": "traefik",
+      "source_name": "OSV Traefik",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "undici",
+      "source_name": "OSV Undici",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "vite",
+      "source_name": "OSV Vite",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "vite",
+      "source_name": "Vite Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 124
+    },
+    {
+      "system_id": "vue",
+      "source_name": "OSV Vue",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "vue",
+      "source_name": "Vue Security",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 111
+    },
+    {
+      "system_id": "webpack",
+      "source_name": "OSV webpack",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "werkzeug",
+      "source_name": "OSV Werkzeug",
+      "source_kind": "osv-batch",
+      "kind": "osv-batch",
+      "items_seen": 1
+    },
+    {
+      "system_id": "woocommerce",
+      "source_name": "GitHub WooCommerce Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 107
+    },
+    {
+      "system_id": "woocommerce",
+      "source_name": "Patchstack Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 193
+    },
+    {
+      "system_id": "woocommerce",
+      "source_name": "Woo Developer Advisories",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 121
+    },
+    {
+      "system_id": "woocommerce",
+      "source_name": "Wordfence Vulnerability Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 0
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "NVD WordPress",
+      "source_kind": "nvd-search",
+      "kind": "nvd-search",
+      "items_seen": 1
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "Patchstack Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 193
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "PortSwigger Research",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 99
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "WPScan Vulnerability Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 74
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "WordPress Security News",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 138
+    },
+    {
+      "system_id": "wordpress",
+      "source_name": "Wordfence Vulnerability Database",
+      "source_kind": "html-links",
+      "kind": "html-links",
+      "items_seen": 0
+    }
+  ],
+  "failures": [],
+  "systems": [
+    {
+      "system_id": "adminer",
+      "display_name": "Adminer",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "adobe-commerce",
+      "display_name": "Adobe Commerce",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "angular",
+      "display_name": "Angular",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "apache-httpd",
+      "display_name": "Apache HTTP Server",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "apache-tomcat",
+      "display_name": "Apache Tomcat",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "aspnet-core",
+      "display_name": "ASP.NET Core",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "astro",
+      "display_name": "Astro",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "caddy",
+      "display_name": "Caddy",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "directus",
+      "display_name": "Directus",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "discourse",
+      "display_name": "Discourse",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "django",
+      "display_name": "Django",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "drupal",
+      "display_name": "Drupal",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "echo",
+      "display_name": "Echo",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "esbuild",
+      "display_name": "esbuild",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "express",
+      "display_name": "Express",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "fastify",
+      "display_name": "Fastify",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "flask",
+      "display_name": "Flask",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "ghost",
+      "display_name": "Ghost",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "gin",
+      "display_name": "Gin",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "gitea",
+      "display_name": "Gitea",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "gitlab-ce",
+      "display_name": "GitLab CE",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "grafana",
+      "display_name": "Grafana",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "hapi",
+      "display_name": "Hapi",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "haproxy",
+      "display_name": "HAProxy",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "jenkins",
+      "display_name": "Jenkins",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "joomla",
+      "display_name": "Joomla",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "kibana",
+      "display_name": "Kibana",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "koa",
+      "display_name": "Koa",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "laravel",
+      "display_name": "Laravel",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "magento-open-source",
+      "display_name": "Magento Open Source",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "mattermost",
+      "display_name": "Mattermost",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "mediawiki",
+      "display_name": "MediaWiki",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "medusa",
+      "display_name": "Medusa",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "moodle",
+      "display_name": "Moodle",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "nestjs",
+      "display_name": "NestJS",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "nextjs",
+      "display_name": "Next.js",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "nginx",
+      "display_name": "Nginx",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "nodejs",
+      "display_name": "Node.js",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "nuxt",
+      "display_name": "Nuxt",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "opencart",
+      "display_name": "OpenCart",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "openmage",
+      "display_name": "OpenMage / Mage-OS",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "phpmyadmin",
+      "display_name": "phpMyAdmin",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "prestashop",
+      "display_name": "PrestaShop",
+      "active_source_total": 3,
+      "green_source_total": 3,
+      "failure_count": 0
+    },
+    {
+      "system_id": "rails",
+      "display_name": "Ruby on Rails",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "react",
+      "display_name": "React",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "redmine",
+      "display_name": "Redmine",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "saleor",
+      "display_name": "Saleor",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "shopware",
+      "display_name": "Shopware",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "spring-boot",
+      "display_name": "Spring Boot",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "spring-framework",
+      "display_name": "Spring Framework",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "spring-security",
+      "display_name": "Spring Security",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "strapi",
+      "display_name": "Strapi",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "sveltekit",
+      "display_name": "SvelteKit",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "symfony",
+      "display_name": "Symfony",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "traefik",
+      "display_name": "Traefik",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "undici",
+      "display_name": "Undici",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "vite",
+      "display_name": "Vite",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "vue",
+      "display_name": "Vue",
+      "active_source_total": 2,
+      "green_source_total": 2,
+      "failure_count": 0
+    },
+    {
+      "system_id": "webpack",
+      "display_name": "webpack",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "werkzeug",
+      "display_name": "Werkzeug",
+      "active_source_total": 1,
+      "green_source_total": 1,
+      "failure_count": 0
+    },
+    {
+      "system_id": "woocommerce",
+      "display_name": "WooCommerce",
+      "active_source_total": 4,
+      "green_source_total": 4,
+      "failure_count": 0
+    },
+    {
+      "system_id": "wordpress",
+      "display_name": "WordPress",
+      "active_source_total": 6,
+      "green_source_total": 6,
+      "failure_count": 0
+    }
+  ]
+}
diff --git a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27977.json b/08-threat-intel/registry/advisories/nextjs--CVE-2026-27977.json
deleted file mode 100644
index 02bb136a..00000000
--- a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27977.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-  "canonical_id": "nextjs--CVE-2026-27977",
-  "system_id": "nextjs",
-  "display_name": "Next.js",
-  "category": "frameworks",
-  "advisory_mode": "core",
-  "title": "Next.js: null origin can bypass dev HMR websocket CSRF checks",
-  "summary": "## Summary\nIn `next dev`, cross-site protection for internal websocket endpoints could treat `Origin: null` as a bypass case even if [`allowedDevOrigins`](https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins) is configured, allowing privacy-sensitive/opaque contexts (for example sandboxed documents) to connect unexpectedly.\n\n## Impact\nIf a dev server is reachable from attacker-controlled content, an attacker may be able to connect to the HMR websocket channel and interact with dev websocket traffic. This affects development mode only.\nApps without a configured [`allowedDevOrigins`](https://nextjs.org/docs/app/api-reference/config/next-config-js/allowedDevOrigins) still allow connections from any origin.\n\n## Patches\nFixed by validating `Origin: null` through the same cross-site origin-allowance checks used for other origins.  \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Do not expose `next dev` to untrusted networks.\n- Block websocket upgrades to `/_next/webpack-hmr` when `Origin` is `null` at your proxy.",
-  "published_at": "2026-03-17T15:29:48Z",
-  "updated_at": "2026-03-17T15:46:26.028580Z",
-  "severity": "medium",
-  "cvss_score": 4.0,
-  "exploit_status": "unknown",
-  "source_confidence": "official",
-  "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-jcc7-9wpm-mj36",
-  "secondary_source_urls": [
-    "https://github.com/vercel/next.js/commit/862f9b9bb41d235e0d8cf44aa811e7fd118cee2a",
-    "https://github.com/vercel/next.js",
-    "https://github.com/vercel/next.js/releases/tag/v16.1.7"
-  ],
-  "aliases": [
-    "CVE-2026-27977",
-    "GHSA-jcc7-9wpm-mj36"
-  ],
-  "cve_ids": [
-    "CVE-2026-27977"
-  ],
-  "ghsa_ids": [
-    "GHSA-jcc7-9wpm-mj36"
-  ],
-  "osv_ids": [
-    "GHSA-jcc7-9wpm-mj36"
-  ],
-  "affected_versions": [
-    "introduced=16.0.1, fixed<16.1.7"
-  ],
-  "fixed_versions": [
-    "16.1.7"
-  ],
-  "package_name": "next",
-  "render_markdown": true,
-  "case_path": "07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27977.md",
-  "secure_code_topics": [
-    "authz-server-side-recheck",
-    "proxy-trust-boundary",
-    "token-cookie-storage"
-  ],
-  "status": "generated",
-  "triage_reasons": [],
-  "verification_status": "triage-manual",
-  "verification_mode": "synthetic",
-  "last_verified_at": null,
-  "last_run_id": null,
-  "evidence_bundle": null,
-  "browser_evidence": {
-    "required": false,
-    "present": false,
-    "refs": []
-  },
-  "repro_profile_id": "nextjs-proxy-boundary",
-  "artifact_mode": "official-source",
-  "blocked_reason": null,
-  "metadata": {
-    "source_names": [
-      "OSV Next.js"
-    ],
-    "source_kinds": [
-      "osv-batch"
-    ],
-    "candidate_count": 1
-  }
-}
diff --git a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27978.json b/08-threat-intel/registry/advisories/nextjs--CVE-2026-27978.json
deleted file mode 100644
index 0a161750..00000000
--- a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27978.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-  "canonical_id": "nextjs--CVE-2026-27978",
-  "system_id": "nextjs",
-  "display_name": "Next.js",
-  "category": "frameworks",
-  "advisory_mode": "core",
-  "title": "Next.js: null origin can bypass Server Actions CSRF checks",
-  "summary": "## Summary\n`origin: null` was treated as a \"missing\" origin during Server Action CSRF validation. As a result, requests from opaque contexts (such as sandboxed iframes) could bypass origin verification instead of being validated as cross-origin requests.\n\n## Impact\nAn attacker could induce a victim browser to submit Server Actions from a sandboxed context, potentially executing state-changing actions with victim credentials (CSRF).\n\n## Patches\nFixed by treating `'null'` as an explicit origin value and enforcing host/origin checks unless `'null'` is explicitly allowlisted in `experimental.serverActions.allowedOrigins`.  \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Add CSRF tokens for sensitive Server Actions.\n- Prefer `SameSite=Strict` on sensitive auth cookies.\n- Do not allow `'null'` in `serverActions.allowedOrigins` unless intentionally required and additionally protected.",
-  "published_at": "2026-03-17T15:30:14Z",
-  "updated_at": "2026-03-17T15:46:43.484729Z",
-  "severity": "medium",
-  "cvss_score": 4.0,
-  "exploit_status": "unknown",
-  "source_confidence": "official",
-  "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-mq59-m269-xvcx",
-  "secondary_source_urls": [
-    "https://github.com/vercel/next.js/commit/a27a11d78e748a8c7ccfd14b7759ad2b9bf097d8",
-    "https://github.com/vercel/next.js",
-    "https://github.com/vercel/next.js/releases/tag/v16.1.7"
-  ],
-  "aliases": [
-    "CVE-2026-27978",
-    "GHSA-mq59-m269-xvcx"
-  ],
-  "cve_ids": [
-    "CVE-2026-27978"
-  ],
-  "ghsa_ids": [
-    "GHSA-mq59-m269-xvcx"
-  ],
-  "osv_ids": [
-    "GHSA-mq59-m269-xvcx"
-  ],
-  "affected_versions": [
-    "introduced=16.0.1, fixed<16.1.7"
-  ],
-  "fixed_versions": [
-    "16.1.7"
-  ],
-  "package_name": "next",
-  "render_markdown": true,
-  "case_path": "07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27978.md",
-  "secure_code_topics": [
-    "authz-server-side-recheck",
-    "proxy-trust-boundary",
-    "token-cookie-storage"
-  ],
-  "status": "generated",
-  "triage_reasons": [],
-  "verification_status": "triage-manual",
-  "verification_mode": "synthetic",
-  "last_verified_at": null,
-  "last_run_id": null,
-  "evidence_bundle": null,
-  "browser_evidence": {
-    "required": false,
-    "present": false,
-    "refs": []
-  },
-  "repro_profile_id": "nextjs-proxy-boundary",
-  "artifact_mode": "official-source",
-  "blocked_reason": null,
-  "metadata": {
-    "source_names": [
-      "OSV Next.js"
-    ],
-    "source_kinds": [
-      "osv-batch"
-    ],
-    "candidate_count": 1
-  }
-}
diff --git a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27979.json b/08-threat-intel/registry/advisories/nextjs--CVE-2026-27979.json
deleted file mode 100644
index 933fc6d3..00000000
--- a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27979.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-  "canonical_id": "nextjs--CVE-2026-27979",
-  "system_id": "nextjs",
-  "display_name": "Next.js",
-  "category": "frameworks",
-  "advisory_mode": "core",
-  "title": "Next.js: Unbounded postponed resume buffering can lead to DoS",
-  "summary": "## Summary\nA request containing the `next-resume: 1` header (corresponding with a PPR resume request) would buffer request bodies without consistently enforcing `maxPostponedStateSize` in certain setups. The previous mitigation protected minimal-mode deployments, but equivalent non-minimal deployments remained vulnerable to the same unbounded postponed resume-body buffering behavior.\n\n## Impact\nIn applications using the App Router with Partial Prerendering capability enabled (via `experimental.ppr` or `cacheComponents`), an attacker could send oversized `next-resume` POST payloads that were buffered without consistent size enforcement in non-minimal deployments, causing excessive memory usage and potential denial of service.\n\n## Patches\nFixed by enforcing size limits across all postponed-body buffering paths and erroring when limits are exceeded.  \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Block requests containing the `next-resume` header, as this is never valid to be sent from an untrusted client.",
-  "published_at": "2026-03-17T16:16:49Z",
-  "updated_at": "2026-03-17T16:31:34.160932Z",
-  "severity": "medium",
-  "cvss_score": 4.0,
-  "exploit_status": "unknown",
-  "source_confidence": "official",
-  "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-h27x-g6w4-24gq",
-  "secondary_source_urls": [
-    "https://github.com/vercel/next.js/commit/c885d4825f800dd1e49ead37274dcd08cdd6f3f1",
-    "https://github.com/vercel/next.js",
-    "https://github.com/vercel/next.js/releases/tag/v16.1.7"
-  ],
-  "aliases": [
-    "CVE-2026-27979",
-    "GHSA-h27x-g6w4-24gq"
-  ],
-  "cve_ids": [
-    "CVE-2026-27979"
-  ],
-  "ghsa_ids": [
-    "GHSA-h27x-g6w4-24gq"
-  ],
-  "osv_ids": [
-    "GHSA-h27x-g6w4-24gq"
-  ],
-  "affected_versions": [
-    "introduced=16.0.1, fixed<16.1.7"
-  ],
-  "fixed_versions": [
-    "16.1.7"
-  ],
-  "package_name": "next",
-  "render_markdown": true,
-  "case_path": "07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27979.md",
-  "secure_code_topics": [
-    "authz-server-side-recheck",
-    "proxy-trust-boundary",
-    "token-cookie-storage"
-  ],
-  "status": "generated",
-  "triage_reasons": [],
-  "verification_status": "triage-manual",
-  "verification_mode": "synthetic",
-  "last_verified_at": null,
-  "last_run_id": null,
-  "evidence_bundle": null,
-  "browser_evidence": {
-    "required": false,
-    "present": false,
-    "refs": []
-  },
-  "repro_profile_id": "nextjs-proxy-boundary",
-  "artifact_mode": "official-source",
-  "blocked_reason": null,
-  "metadata": {
-    "source_names": [
-      "OSV Next.js"
-    ],
-    "source_kinds": [
-      "osv-batch"
-    ],
-    "candidate_count": 1
-  }
-}
diff --git a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27980.json b/08-threat-intel/registry/advisories/nextjs--CVE-2026-27980.json
deleted file mode 100644
index 3604a2e2..00000000
--- a/08-threat-intel/registry/advisories/nextjs--CVE-2026-27980.json
+++ /dev/null
@@ -1,72 +0,0 @@
-{
-  "canonical_id": "nextjs--CVE-2026-27980",
-  "system_id": "nextjs",
-  "display_name": "Next.js",
-  "category": "frameworks",
-  "advisory_mode": "core",
-  "title": "Next.js: Unbounded next/image disk cache growth can exhaust storage",
-  "summary": "## Summary\nThe default Next.js image optimization disk cache (`/_next/image`) did not have a configurable upper bound, allowing unbounded cache growth.\n\n## Impact\nAn attacker could generate many unique image-optimization variants and exhaust disk space, causing denial of service.\n\n## Patches\nFixed by adding an LRU-backed disk cache with `images.maximumDiskCacheSize`, including eviction of least-recently-used entries when the limit is exceeded. Setting `maximumDiskCacheSize: 0` disables disk caching. \n\n## Workarounds\nIf upgrade is not immediately possible:\n- Periodically clean `.next/cache/images`.\n- Reduce variant cardinality (e.g., tighten values for `images.localPatterns`, `images.remotePatterns`, and `images.qualities`)",
-  "published_at": "2026-03-17T16:17:06Z",
-  "updated_at": "2026-03-17T16:31:33.597080Z",
-  "severity": "medium",
-  "cvss_score": 4.0,
-  "exploit_status": "unknown",
-  "source_confidence": "official",
-  "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-3x4c-7xq6-9pq8",
-  "secondary_source_urls": [
-    "https://github.com/vercel/next.js/commit/39eb8e0ac498b48855a0430fbf4c22276a73b4bd",
-    "https://github.com/vercel/next.js",
-    "https://github.com/vercel/next.js/releases/tag/v16.1.7"
-  ],
-  "aliases": [
-    "CVE-2026-27980",
-    "GHSA-3x4c-7xq6-9pq8"
-  ],
-  "cve_ids": [
-    "CVE-2026-27980"
-  ],
-  "ghsa_ids": [
-    "GHSA-3x4c-7xq6-9pq8"
-  ],
-  "osv_ids": [
-    "GHSA-3x4c-7xq6-9pq8"
-  ],
-  "affected_versions": [
-    "introduced=10.0.0, fixed<16.1.7"
-  ],
-  "fixed_versions": [
-    "16.1.7"
-  ],
-  "package_name": "next",
-  "render_markdown": true,
-  "case_path": "07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-27980.md",
-  "secure_code_topics": [
-    "authz-server-side-recheck",
-    "proxy-trust-boundary",
-    "token-cookie-storage"
-  ],
-  "status": "generated",
-  "triage_reasons": [],
-  "verification_status": "triage-manual",
-  "verification_mode": "synthetic",
-  "last_verified_at": null,
-  "last_run_id": null,
-  "evidence_bundle": null,
-  "browser_evidence": {
-    "required": false,
-    "present": false,
-    "refs": []
-  },
-  "repro_profile_id": "nextjs-proxy-boundary",
-  "artifact_mode": "official-source",
-  "blocked_reason": null,
-  "metadata": {
-    "source_names": [
-      "OSV Next.js"
-    ],
-    "source_kinds": [
-      "osv-batch"
-    ],
-    "candidate_count": 1
-  }
-}
diff --git a/08-threat-intel/registry/advisories/nextjs--CVE-2026-29057.json b/08-threat-intel/registry/advisories/nextjs--CVE-2026-29057.json
deleted file mode 100644
index a5547577..00000000
--- a/08-threat-intel/registry/advisories/nextjs--CVE-2026-29057.json
+++ /dev/null
@@ -1,77 +0,0 @@
-{
-  "canonical_id": "nextjs--CVE-2026-29057",
-  "system_id": "nextjs",
-  "display_name": "Next.js",
-  "category": "frameworks",
-  "advisory_mode": "core",
-  "title": "Next.js: HTTP request smuggling in rewrites",
-  "summary": "## Summary\nWhen Next.js rewrites proxy traffic to an external backend, a crafted `DELETE`/`OPTIONS` request using `Transfer-Encoding: chunked` could trigger request boundary disagreement between the proxy and backend. This could allow request smuggling through rewritten routes.\n\n## Impact\nAn attacker could smuggle a second request to unintended backend routes (for example, internal/admin endpoints), bypassing assumptions that only the configured rewrite destination/path is reachable. This does not impact applications hosted on providers that handle rewrites at the CDN level, such as Vercel. \n\n## Patches\nThe vulnerability originated in an upstream library vendored by Next.js. It is fixed by updating that dependency\u2019s behavior so `content-length: 0` is added only when both `content-length` and `transfer-encoding` are absent, and `transfer-encoding` is no longer removed in that code path.\n\n## Workarounds\nIf upgrade is not immediately possible:\n- Block chunked `DELETE`/`OPTIONS` requests on rewritten routes at your edge/proxy.\n- Enforce authentication/authorization on backend routes per our [security guidance](https://nextjs.org/docs/app/guides/data-security).",
-  "published_at": "2026-03-17T16:17:15Z",
-  "updated_at": "2026-03-17T16:31:26.646070Z",
-  "severity": "medium",
-  "cvss_score": 4.0,
-  "exploit_status": "unknown",
-  "source_confidence": "official",
-  "official_source_url": "https://github.com/vercel/next.js/security/advisories/GHSA-ggv3-7p47-pfv8",
-  "secondary_source_urls": [
-    "https://github.com/vercel/next.js/commit/dc98c04f376c6a1df76ec3e0a2d07edf4abdabd6",
-    "https://github.com/vercel/next.js",
-    "https://github.com/vercel/next.js/releases/tag/v15.5.13",
-    "https://github.com/vercel/next.js/releases/tag/v16.1.7"
-  ],
-  "aliases": [
-    "CVE-2026-29057",
-    "GHSA-ggv3-7p47-pfv8"
-  ],
-  "cve_ids": [
-    "CVE-2026-29057"
-  ],
-  "ghsa_ids": [
-    "GHSA-ggv3-7p47-pfv8"
-  ],
-  "osv_ids": [
-    "GHSA-ggv3-7p47-pfv8"
-  ],
-  "affected_versions": [
-    "introduced=16.0.0-beta.0, fixed<16.1.7",
-    "introduced=9.5.0, fixed<15.5.13"
-  ],
-  "fixed_versions": [
-    "16.1.7",
-    "15.5.13"
-  ],
-  "package_name": "next",
-  "render_markdown": true,
-  "case_path": "07-framework-security/frameworks/nextjs/cases/nextjs-cve-2026-29057.md",
-  "secure_code_topics": [
-    "authz-server-side-recheck",
-    "proxy-trust-boundary",
-    "token-cookie-storage",
-    "request-smuggling-boundary",
-    "dependency-upgrade-policy"
-  ],
-  "status": "generated",
-  "triage_reasons": [],
-  "verification_status": "triage-manual",
-  "verification_mode": "synthetic",
-  "last_verified_at": null,
-  "last_run_id": null,
-  "evidence_bundle": null,
-  "browser_evidence": {
-    "required": false,
-    "present": false,
-    "refs": []
-  },
-  "repro_profile_id": "request-smuggling-generic",
-  "artifact_mode": "official-source",
-  "blocked_reason": null,
-  "metadata": {
-    "source_names": [
-      "OSV Next.js"
-    ],
-    "source_kinds": [
-      "osv-batch"
-    ],
-    "candidate_count": 1
-  }
-}
diff --git a/08-threat-intel/registry/monitoring/2026-03-18T17-44-31+00-00.json b/08-threat-intel/registry/monitoring/2026-03-18T17-44-31+00-00.json
new file mode 100644
index 00000000..8a27f974
--- /dev/null
+++ b/08-threat-intel/registry/monitoring/2026-03-18T17-44-31+00-00.json
@@ -0,0 +1,2902 @@
+{
+  "generated_at": "2026-03-18T17:44:31+00:00",
+  "source_catalog_audit": {
+    "generated_at": "2026-03-18T17:41:42+00:00",
+    "system_count": 62,
+    "source_count": 146,
+    "active_source_count": 110,
+    "retired_source_count": 36,
+    "systems_with_active_official": 62,
+    "systems_with_machine_readable_source": 57,
+    "systems": [
+      {
+        "system_id": "adminer",
+        "display_name": "Adminer",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 1,
+        "active_source_total": 1,
+        "retired_source_total": 0,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "adobe-commerce",
+        "display_name": "Adobe Commerce",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 5,
+        "active_source_total": 2,
+        "retired_source_total": 3,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "angular",
+        "display_name": "Angular",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "apache-httpd",
+        "display_name": "Apache HTTP Server",
+        "category": "servers",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 3,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 2,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "apache-tomcat",
+        "display_name": "Apache Tomcat",
+        "category": "servers",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 3,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 2,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "aspnet-core",
+        "display_name": "ASP.NET Core",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 1,
+        "active_source_total": 1,
+        "retired_source_total": 0,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "astro",
+        "display_name": "Astro",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "caddy",
+        "display_name": "Caddy",
+        "category": "servers",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "directus",
+        "display_name": "Directus",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "discourse",
+        "display_name": "Discourse",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 3,
+        "active_source_total": 1,
+        "retired_source_total": 2,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "django",
+        "display_name": "Django",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 4,
+        "active_source_total": 3,
+        "retired_source_total": 1,
+        "official_active": 3,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "drupal",
+        "display_name": "Drupal",
+        "category": "cms",
+        "tier": "history-full",
+        "source_total": 4,
+        "active_source_total": 2,
+        "retired_source_total": 2,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 2,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "echo",
+        "display_name": "Echo",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 1,
+        "active_source_total": 1,
+        "retired_source_total": 0,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "esbuild",
+        "display_name": "esbuild",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "express",
+        "display_name": "Express",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "fastify",
+        "display_name": "Fastify",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "flask",
+        "display_name": "Flask",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "ghost",
+        "display_name": "Ghost",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "gin",
+        "display_name": "Gin",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 1,
+        "active_source_total": 1,
+        "retired_source_total": 0,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "gitea",
+        "display_name": "Gitea",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "gitlab-ce",
+        "display_name": "GitLab CE",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 1,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "grafana",
+        "display_name": "Grafana",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "hapi",
+        "display_name": "Hapi",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "haproxy",
+        "display_name": "HAProxy",
+        "category": "servers",
+        "tier": "rolling-24m",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 2,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "jenkins",
+        "display_name": "Jenkins",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "joomla",
+        "display_name": "Joomla",
+        "category": "cms",
+        "tier": "history-full",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "kibana",
+        "display_name": "Kibana",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "koa",
+        "display_name": "Koa",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "laravel",
+        "display_name": "Laravel",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "magento-open-source",
+        "display_name": "Magento Open Source",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 1,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "mattermost",
+        "display_name": "Mattermost",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "mediawiki",
+        "display_name": "MediaWiki",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "medusa",
+        "display_name": "Medusa",
+        "category": "ecommerce",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "moodle",
+        "display_name": "Moodle",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "nestjs",
+        "display_name": "NestJS",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "nextjs",
+        "display_name": "Next.js",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "nginx",
+        "display_name": "Nginx",
+        "category": "servers",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 3,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 2,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "nodejs",
+        "display_name": "Node.js",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "nuxt",
+        "display_name": "Nuxt",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "opencart",
+        "display_name": "OpenCart",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "openmage",
+        "display_name": "OpenMage / Mage-OS",
+        "category": "ecommerce",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "phpmyadmin",
+        "display_name": "phpMyAdmin",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "prestashop",
+        "display_name": "PrestaShop",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 3,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 1,
+        "research_active": 0,
+        "machine_readable_active": 0,
+        "has_active_official": true,
+        "has_machine_readable_source": false
+      },
+      {
+        "system_id": "rails",
+        "display_name": "Ruby on Rails",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "react",
+        "display_name": "React",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "redmine",
+        "display_name": "Redmine",
+        "category": "platforms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "saleor",
+        "display_name": "Saleor",
+        "category": "ecommerce",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "shopware",
+        "display_name": "Shopware",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "spring-boot",
+        "display_name": "Spring Boot",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 0,
+        "has_active_official": true,
+        "has_machine_readable_source": false
+      },
+      {
+        "system_id": "spring-framework",
+        "display_name": "Spring Framework",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 0,
+        "has_active_official": true,
+        "has_machine_readable_source": false
+      },
+      {
+        "system_id": "spring-security",
+        "display_name": "Spring Security",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 0,
+        "has_active_official": true,
+        "has_machine_readable_source": false
+      },
+      {
+        "system_id": "strapi",
+        "display_name": "Strapi",
+        "category": "cms",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "sveltekit",
+        "display_name": "SvelteKit",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "symfony",
+        "display_name": "Symfony",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "traefik",
+        "display_name": "Traefik",
+        "category": "servers",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 2,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "undici",
+        "display_name": "Undici",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "vite",
+        "display_name": "Vite",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "vue",
+        "display_name": "Vue",
+        "category": "frameworks",
+        "tier": "history-full",
+        "source_total": 3,
+        "active_source_total": 2,
+        "retired_source_total": 1,
+        "official_active": 2,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "webpack",
+        "display_name": "webpack",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "werkzeug",
+        "display_name": "Werkzeug",
+        "category": "frameworks",
+        "tier": "rolling-24m",
+        "source_total": 2,
+        "active_source_total": 1,
+        "retired_source_total": 1,
+        "official_active": 1,
+        "ecosystem_active": 0,
+        "research_active": 0,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      },
+      {
+        "system_id": "woocommerce",
+        "display_name": "WooCommerce",
+        "category": "ecommerce",
+        "tier": "history-full",
+        "source_total": 4,
+        "active_source_total": 4,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 2,
+        "research_active": 0,
+        "machine_readable_active": 0,
+        "has_active_official": true,
+        "has_machine_readable_source": false
+      },
+      {
+        "system_id": "wordpress",
+        "display_name": "WordPress",
+        "category": "cms",
+        "tier": "history-full",
+        "source_total": 6,
+        "active_source_total": 6,
+        "retired_source_total": 0,
+        "official_active": 2,
+        "ecosystem_active": 3,
+        "research_active": 1,
+        "machine_readable_active": 1,
+        "has_active_official": true,
+        "has_machine_readable_source": true
+      }
+    ],
+    "retired_sources": [
+      {
+        "system_id": "adobe-commerce",
+        "display_name": "Adobe Commerce",
+        "source_name": "Adobe Security Bulletins",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.",
+        "replacement_sources": [
+          "Adobe Magento Security Index",
+          "NVD Adobe Commerce",
+          "GHSA Adobe Commerce"
+        ],
+        "url": "https://helpx.adobe.com/security/products/magento.html"
+      },
+      {
+        "system_id": "adobe-commerce",
+        "display_name": "Adobe Commerce",
+        "source_name": "GHSA Adobe Commerce",
+        "bucket": "ecosystem_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.",
+        "replacement_sources": [
+          "Adobe Magento Security Index",
+          "NVD Adobe Commerce"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "adobe-commerce",
+        "display_name": "Adobe Commerce",
+        "source_name": "Sansec Research",
+        "bucket": "ecosystem_sources",
+        "kind": "vendor-index",
+        "retired_reason": "Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.",
+        "replacement_sources": [
+          "GHSA Adobe Commerce",
+          "Adobe Magento Security Index"
+        ],
+        "url": "https://sansec.io/research"
+      },
+      {
+        "system_id": "angular",
+        "display_name": "Angular",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Angular"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "astro",
+        "display_name": "Astro",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Astro"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "discourse",
+        "display_name": "Discourse",
+        "source_name": "Discourse Meta Security",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "Meta security category HTML changed and no longer provides stable scrape semantics for health checks.",
+        "replacement_sources": [
+          "Discourse Release Notes RSS",
+          "GitHub Discourse Advisories"
+        ],
+        "url": "https://meta.discourse.org/c/bug/security/40"
+      },
+      {
+        "system_id": "discourse",
+        "display_name": "Discourse",
+        "source_name": "GitHub Discourse Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.",
+        "replacement_sources": [
+          "Discourse Release Notes RSS"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "django",
+        "display_name": "Django",
+        "source_name": "Django Security RSS",
+        "bucket": "official_sources",
+        "kind": "rss-feed",
+        "retired_reason": "Official security tag feed became unstable; use official weblog index and release archive instead.",
+        "replacement_sources": [
+          "Django Security Weblog",
+          "Django Security Releases Archive"
+        ],
+        "url": "https://www.djangoproject.com/weblog/feeds/tags/security/"
+      },
+      {
+        "system_id": "drupal",
+        "display_name": "Drupal",
+        "source_name": "Drupal Security Advisories Site",
+        "bucket": "ecosystem_sources",
+        "kind": "html-links",
+        "retired_reason": "Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.",
+        "replacement_sources": [
+          "Drupal Security Advisories RSS",
+          "GHSA Drupal Core"
+        ],
+        "url": "https://www.drupal.org/security"
+      },
+      {
+        "system_id": "drupal",
+        "display_name": "Drupal",
+        "source_name": "GHSA Drupal Core",
+        "bucket": "ecosystem_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.",
+        "replacement_sources": [
+          "Drupal Security Advisories RSS",
+          "NVD Drupal"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "esbuild",
+        "display_name": "esbuild",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.",
+        "replacement_sources": [
+          "OSV esbuild"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "express",
+        "display_name": "Express",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Express"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "fastify",
+        "display_name": "Fastify",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Fastify"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "flask",
+        "display_name": "Flask",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.",
+        "replacement_sources": [
+          "OSV Flask"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "hapi",
+        "display_name": "Hapi",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Hapi"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "haproxy",
+        "display_name": "HAProxy",
+        "source_name": "HAProxy Security Advisories",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "Legacy haproxy.org security page no longer yields stable scrape results for monitoring.",
+        "replacement_sources": [
+          "HAProxy Blog Feed"
+        ],
+        "url": "https://www.haproxy.org/security/"
+      },
+      {
+        "system_id": "koa",
+        "display_name": "Koa",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Koa"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "laravel",
+        "display_name": "Laravel",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.",
+        "replacement_sources": [
+          "OSV Laravel"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "mattermost",
+        "display_name": "Mattermost",
+        "source_name": "Mattermost Security Updates",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+        "replacement_sources": [
+          "NVD Mattermost"
+        ],
+        "url": "https://mattermost.com/security-updates/"
+      },
+      {
+        "system_id": "mediawiki",
+        "display_name": "MediaWiki",
+        "source_name": "MediaWiki Security Releases",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.",
+        "replacement_sources": [
+          "NVD MediaWiki"
+        ],
+        "url": "https://www.mediawiki.org/wiki/Security"
+      },
+      {
+        "system_id": "moodle",
+        "display_name": "Moodle",
+        "source_name": "Moodle Security News",
+        "bucket": "official_sources",
+        "kind": "html-links",
+        "retired_reason": "Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.",
+        "replacement_sources": [
+          "NVD Moodle"
+        ],
+        "url": "https://moodle.org/security/"
+      },
+      {
+        "system_id": "nestjs",
+        "display_name": "NestJS",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.",
+        "replacement_sources": [
+          "OSV NestJS"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "nextjs",
+        "display_name": "Next.js",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.",
+        "replacement_sources": [
+          "GitHub Next.js Advisories",
+          "OSV Next.js"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "nuxt",
+        "display_name": "Nuxt",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.",
+        "replacement_sources": [
+          "Nuxt Security",
+          "OSV Nuxt"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "rails",
+        "display_name": "Ruby on Rails",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.",
+        "replacement_sources": [
+          "OSV Rails"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "react",
+        "display_name": "React",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.",
+        "replacement_sources": [
+          "GitHub React Advisories",
+          "OSV React"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "spring-boot",
+        "display_name": "Spring Boot",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "spring-framework",
+        "display_name": "Spring Framework",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "spring-security",
+        "display_name": "Spring Security",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "sveltekit",
+        "display_name": "SvelteKit",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.",
+        "replacement_sources": [
+          "OSV SvelteKit"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "symfony",
+        "display_name": "Symfony",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.",
+        "replacement_sources": [
+          "OSV Symfony"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "undici",
+        "display_name": "Undici",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.",
+        "replacement_sources": [
+          "OSV Undici"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "vite",
+        "display_name": "Vite",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.",
+        "replacement_sources": [
+          "Vite Security",
+          "OSV Vite"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "vue",
+        "display_name": "Vue",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.",
+        "replacement_sources": [
+          "Vue Security",
+          "OSV Vue"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "webpack",
+        "display_name": "webpack",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.",
+        "replacement_sources": [
+          "OSV webpack"
+        ],
+        "url": ""
+      },
+      {
+        "system_id": "werkzeug",
+        "display_name": "Werkzeug",
+        "source_name": "GitHub Global Advisories",
+        "bucket": "official_sources",
+        "kind": "ghsa-global",
+        "retired_reason": "Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.",
+        "replacement_sources": [
+          "OSV Werkzeug"
+        ],
+        "url": ""
+      }
+    ],
+    "replacement_map": [
+      {
+        "system_id": "adobe-commerce",
+        "retired_source": "Adobe Security Bulletins",
+        "replacement_sources": [
+          "Adobe Magento Security Index",
+          "NVD Adobe Commerce",
+          "GHSA Adobe Commerce"
+        ]
+      },
+      {
+        "system_id": "adobe-commerce",
+        "retired_source": "GHSA Adobe Commerce",
+        "replacement_sources": [
+          "Adobe Magento Security Index",
+          "NVD Adobe Commerce"
+        ]
+      },
+      {
+        "system_id": "adobe-commerce",
+        "retired_source": "Sansec Research",
+        "replacement_sources": [
+          "GHSA Adobe Commerce",
+          "Adobe Magento Security Index"
+        ]
+      },
+      {
+        "system_id": "angular",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Angular"
+        ]
+      },
+      {
+        "system_id": "astro",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Astro"
+        ]
+      },
+      {
+        "system_id": "discourse",
+        "retired_source": "Discourse Meta Security",
+        "replacement_sources": [
+          "Discourse Release Notes RSS",
+          "GitHub Discourse Advisories"
+        ]
+      },
+      {
+        "system_id": "discourse",
+        "retired_source": "GitHub Discourse Advisories",
+        "replacement_sources": [
+          "Discourse Release Notes RSS"
+        ]
+      },
+      {
+        "system_id": "django",
+        "retired_source": "Django Security RSS",
+        "replacement_sources": [
+          "Django Security Weblog",
+          "Django Security Releases Archive"
+        ]
+      },
+      {
+        "system_id": "drupal",
+        "retired_source": "Drupal Security Advisories Site",
+        "replacement_sources": [
+          "Drupal Security Advisories RSS",
+          "GHSA Drupal Core"
+        ]
+      },
+      {
+        "system_id": "drupal",
+        "retired_source": "GHSA Drupal Core",
+        "replacement_sources": [
+          "Drupal Security Advisories RSS",
+          "NVD Drupal"
+        ]
+      },
+      {
+        "system_id": "esbuild",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV esbuild"
+        ]
+      },
+      {
+        "system_id": "express",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Express"
+        ]
+      },
+      {
+        "system_id": "fastify",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Fastify"
+        ]
+      },
+      {
+        "system_id": "flask",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Flask"
+        ]
+      },
+      {
+        "system_id": "hapi",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Hapi"
+        ]
+      },
+      {
+        "system_id": "haproxy",
+        "retired_source": "HAProxy Security Advisories",
+        "replacement_sources": [
+          "HAProxy Blog Feed"
+        ]
+      },
+      {
+        "system_id": "koa",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Koa"
+        ]
+      },
+      {
+        "system_id": "laravel",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Laravel"
+        ]
+      },
+      {
+        "system_id": "mattermost",
+        "retired_source": "Mattermost Security Updates",
+        "replacement_sources": [
+          "NVD Mattermost"
+        ]
+      },
+      {
+        "system_id": "mediawiki",
+        "retired_source": "MediaWiki Security Releases",
+        "replacement_sources": [
+          "NVD MediaWiki"
+        ]
+      },
+      {
+        "system_id": "moodle",
+        "retired_source": "Moodle Security News",
+        "replacement_sources": [
+          "NVD Moodle"
+        ]
+      },
+      {
+        "system_id": "nestjs",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV NestJS"
+        ]
+      },
+      {
+        "system_id": "nextjs",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "GitHub Next.js Advisories",
+          "OSV Next.js"
+        ]
+      },
+      {
+        "system_id": "nuxt",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Nuxt Security",
+          "OSV Nuxt"
+        ]
+      },
+      {
+        "system_id": "rails",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Rails"
+        ]
+      },
+      {
+        "system_id": "react",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "GitHub React Advisories",
+          "OSV React"
+        ]
+      },
+      {
+        "system_id": "spring-boot",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ]
+      },
+      {
+        "system_id": "spring-framework",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ]
+      },
+      {
+        "system_id": "spring-security",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Spring Security Advisories"
+        ]
+      },
+      {
+        "system_id": "sveltekit",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV SvelteKit"
+        ]
+      },
+      {
+        "system_id": "symfony",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Symfony"
+        ]
+      },
+      {
+        "system_id": "undici",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Undici"
+        ]
+      },
+      {
+        "system_id": "vite",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Vite Security",
+          "OSV Vite"
+        ]
+      },
+      {
+        "system_id": "vue",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "Vue Security",
+          "OSV Vue"
+        ]
+      },
+      {
+        "system_id": "webpack",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV webpack"
+        ]
+      },
+      {
+        "system_id": "werkzeug",
+        "retired_source": "GitHub Global Advisories",
+        "replacement_sources": [
+          "OSV Werkzeug"
+        ]
+      }
+    ]
+  },
+  "source_health": {
+    "generated_at": "2026-03-18T17:44:31+00:00",
+    "active_source_count": 110,
+    "green_source_count": 110,
+    "failure_count": 0,
+    "all_green": true,
+    "last_fully_green_run": "2026-03-18T17:44:31+00:00",
+    "retries_performed": 0,
+    "probes": [
+      {
+        "system_id": "adminer",
+        "source_name": "NVD Adminer",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "adobe-commerce",
+        "source_name": "Adobe Magento Security Index",
+        "source_kind": "vendor-index",
+        "kind": "vendor-index",
+        "items_seen": 46
+      },
+      {
+        "system_id": "adobe-commerce",
+        "source_name": "NVD Adobe Commerce",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "angular",
+        "source_name": "OSV Angular",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "apache-httpd",
+        "source_name": "Apache HTTPD Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 182
+      },
+      {
+        "system_id": "apache-httpd",
+        "source_name": "CISA KEV Apache HTTPD",
+        "source_kind": "kev-json",
+        "kind": "kev-json",
+        "items_seen": 1544
+      },
+      {
+        "system_id": "apache-httpd",
+        "source_name": "NVD Apache HTTP Server",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "apache-tomcat",
+        "source_name": "Apache Tomcat Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 270
+      },
+      {
+        "system_id": "apache-tomcat",
+        "source_name": "CISA KEV Tomcat",
+        "source_kind": "kev-json",
+        "kind": "kev-json",
+        "items_seen": 1544
+      },
+      {
+        "system_id": "apache-tomcat",
+        "source_name": "NVD Tomcat",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "aspnet-core",
+        "source_name": "NVD ASP.NET Core",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "astro",
+        "source_name": "OSV Astro",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "caddy",
+        "source_name": "GitHub Caddy Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 114
+      },
+      {
+        "system_id": "caddy",
+        "source_name": "OSV Caddy",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "directus",
+        "source_name": "Directus GitHub Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 127
+      },
+      {
+        "system_id": "directus",
+        "source_name": "OSV Directus",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "discourse",
+        "source_name": "Discourse Release Notes RSS",
+        "source_kind": "rss-feed",
+        "kind": "rss-feed",
+        "items_seen": 30
+      },
+      {
+        "system_id": "django",
+        "source_name": "Django Security Releases Archive",
+        "source_kind": "vendor-index",
+        "kind": "vendor-index",
+        "items_seen": 1276
+      },
+      {
+        "system_id": "django",
+        "source_name": "Django Security Weblog",
+        "source_kind": "vendor-index",
+        "kind": "vendor-index",
+        "items_seen": 332
+      },
+      {
+        "system_id": "django",
+        "source_name": "OSV Django",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "drupal",
+        "source_name": "Drupal Security Advisories RSS",
+        "source_kind": "rss-feed",
+        "kind": "rss-feed",
+        "items_seen": 20
+      },
+      {
+        "system_id": "drupal",
+        "source_name": "NVD Drupal",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "echo",
+        "source_name": "OSV Echo",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "esbuild",
+        "source_name": "OSV esbuild",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "express",
+        "source_name": "OSV Express",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "fastify",
+        "source_name": "OSV Fastify",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "flask",
+        "source_name": "OSV Flask",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "ghost",
+        "source_name": "Ghost GitHub Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 119
+      },
+      {
+        "system_id": "ghost",
+        "source_name": "NVD Ghost",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "gin",
+        "source_name": "OSV Gin",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "gitea",
+        "source_name": "GitHub Gitea Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 98
+      },
+      {
+        "system_id": "gitea",
+        "source_name": "OSV Gitea",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "gitlab-ce",
+        "source_name": "GitLab Advisory Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 5
+      },
+      {
+        "system_id": "gitlab-ce",
+        "source_name": "GitLab Security Releases",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 250
+      },
+      {
+        "system_id": "gitlab-ce",
+        "source_name": "NVD GitLab",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "grafana",
+        "source_name": "CISA KEV Grafana",
+        "source_kind": "kev-json",
+        "kind": "kev-json",
+        "items_seen": 1544
+      },
+      {
+        "system_id": "grafana",
+        "source_name": "Grafana Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 159
+      },
+      {
+        "system_id": "hapi",
+        "source_name": "OSV Hapi",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "haproxy",
+        "source_name": "HAProxy Blog Feed",
+        "source_kind": "rss-feed",
+        "kind": "rss-feed",
+        "items_seen": 10
+      },
+      {
+        "system_id": "haproxy",
+        "source_name": "NVD HAProxy",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "jenkins",
+        "source_name": "Jenkins Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 1194
+      },
+      {
+        "system_id": "jenkins",
+        "source_name": "NVD Jenkins",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "joomla",
+        "source_name": "Joomla Security Centre",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 139
+      },
+      {
+        "system_id": "joomla",
+        "source_name": "NVD Joomla",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "kibana",
+        "source_name": "Elastic Security Announcements",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 82
+      },
+      {
+        "system_id": "kibana",
+        "source_name": "NVD Kibana",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "koa",
+        "source_name": "OSV Koa",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "laravel",
+        "source_name": "OSV Laravel",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "magento-open-source",
+        "source_name": "Magento GitHub Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 99
+      },
+      {
+        "system_id": "magento-open-source",
+        "source_name": "NVD Magento",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "magento-open-source",
+        "source_name": "Sansec Research",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 134
+      },
+      {
+        "system_id": "mattermost",
+        "source_name": "NVD Mattermost",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "mediawiki",
+        "source_name": "NVD MediaWiki",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "medusa",
+        "source_name": "GitHub Medusa Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 102
+      },
+      {
+        "system_id": "medusa",
+        "source_name": "OSV Medusa",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "moodle",
+        "source_name": "NVD Moodle",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "nestjs",
+        "source_name": "OSV NestJS",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "nextjs",
+        "source_name": "GitHub Next.js Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 123
+      },
+      {
+        "system_id": "nextjs",
+        "source_name": "OSV Next.js",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "nginx",
+        "source_name": "CISA KEV NGINX",
+        "source_kind": "kev-json",
+        "kind": "kev-json",
+        "items_seen": 1544
+      },
+      {
+        "system_id": "nginx",
+        "source_name": "NGINX Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 138
+      },
+      {
+        "system_id": "nginx",
+        "source_name": "NVD NGINX",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "nodejs",
+        "source_name": "CISA KEV Node.js",
+        "source_kind": "kev-json",
+        "kind": "kev-json",
+        "items_seen": 1544
+      },
+      {
+        "system_id": "nodejs",
+        "source_name": "Node.js Security Releases",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 74
+      },
+      {
+        "system_id": "nuxt",
+        "source_name": "Nuxt Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 118
+      },
+      {
+        "system_id": "nuxt",
+        "source_name": "OSV Nuxt",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "opencart",
+        "source_name": "NVD OpenCart",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "opencart",
+        "source_name": "OpenCart Releases",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 1500
+      },
+      {
+        "system_id": "openmage",
+        "source_name": "NVD OpenMage",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "openmage",
+        "source_name": "OpenMage GitHub Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 125
+      },
+      {
+        "system_id": "phpmyadmin",
+        "source_name": "NVD phpMyAdmin",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "phpmyadmin",
+        "source_name": "phpMyAdmin Security Page",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 262
+      },
+      {
+        "system_id": "prestashop",
+        "source_name": "Friends Of Presta Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 38
+      },
+      {
+        "system_id": "prestashop",
+        "source_name": "GitHub PrestaShop Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 127
+      },
+      {
+        "system_id": "prestashop",
+        "source_name": "PrestaShop Security Page",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 60
+      },
+      {
+        "system_id": "rails",
+        "source_name": "OSV Rails",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "react",
+        "source_name": "GitHub React Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 110
+      },
+      {
+        "system_id": "react",
+        "source_name": "OSV React",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "redmine",
+        "source_name": "NVD Redmine",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "redmine",
+        "source_name": "Redmine Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 371
+      },
+      {
+        "system_id": "saleor",
+        "source_name": "GitHub Saleor Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 120
+      },
+      {
+        "system_id": "saleor",
+        "source_name": "NVD Saleor",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "shopware",
+        "source_name": "NVD Shopware",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "shopware",
+        "source_name": "Shopware Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 129
+      },
+      {
+        "system_id": "spring-boot",
+        "source_name": "Spring Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 118
+      },
+      {
+        "system_id": "spring-framework",
+        "source_name": "Spring Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 118
+      },
+      {
+        "system_id": "spring-security",
+        "source_name": "Spring Security Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 118
+      },
+      {
+        "system_id": "strapi",
+        "source_name": "OSV Strapi",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "strapi",
+        "source_name": "Strapi GitHub Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 124
+      },
+      {
+        "system_id": "sveltekit",
+        "source_name": "OSV SvelteKit",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "symfony",
+        "source_name": "OSV Symfony",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "traefik",
+        "source_name": "GitHub Traefik Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 124
+      },
+      {
+        "system_id": "traefik",
+        "source_name": "OSV Traefik",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "undici",
+        "source_name": "OSV Undici",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "vite",
+        "source_name": "OSV Vite",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "vite",
+        "source_name": "Vite Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 124
+      },
+      {
+        "system_id": "vue",
+        "source_name": "OSV Vue",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "vue",
+        "source_name": "Vue Security",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 111
+      },
+      {
+        "system_id": "webpack",
+        "source_name": "OSV webpack",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "werkzeug",
+        "source_name": "OSV Werkzeug",
+        "source_kind": "osv-batch",
+        "kind": "osv-batch",
+        "items_seen": 1
+      },
+      {
+        "system_id": "woocommerce",
+        "source_name": "GitHub WooCommerce Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 107
+      },
+      {
+        "system_id": "woocommerce",
+        "source_name": "Patchstack Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 193
+      },
+      {
+        "system_id": "woocommerce",
+        "source_name": "Woo Developer Advisories",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 121
+      },
+      {
+        "system_id": "woocommerce",
+        "source_name": "Wordfence Vulnerability Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 0
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "NVD WordPress",
+        "source_kind": "nvd-search",
+        "kind": "nvd-search",
+        "items_seen": 1
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "Patchstack Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 193
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "PortSwigger Research",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 99
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "WPScan Vulnerability Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 74
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "WordPress Security News",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 138
+      },
+      {
+        "system_id": "wordpress",
+        "source_name": "Wordfence Vulnerability Database",
+        "source_kind": "html-links",
+        "kind": "html-links",
+        "items_seen": 0
+      }
+    ],
+    "failures": [],
+    "systems": [
+      {
+        "system_id": "adminer",
+        "display_name": "Adminer",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "adobe-commerce",
+        "display_name": "Adobe Commerce",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "angular",
+        "display_name": "Angular",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "apache-httpd",
+        "display_name": "Apache HTTP Server",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "apache-tomcat",
+        "display_name": "Apache Tomcat",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "aspnet-core",
+        "display_name": "ASP.NET Core",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "astro",
+        "display_name": "Astro",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "caddy",
+        "display_name": "Caddy",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "directus",
+        "display_name": "Directus",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "discourse",
+        "display_name": "Discourse",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "django",
+        "display_name": "Django",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "drupal",
+        "display_name": "Drupal",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "echo",
+        "display_name": "Echo",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "esbuild",
+        "display_name": "esbuild",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "express",
+        "display_name": "Express",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "fastify",
+        "display_name": "Fastify",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "flask",
+        "display_name": "Flask",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "ghost",
+        "display_name": "Ghost",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "gin",
+        "display_name": "Gin",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "gitea",
+        "display_name": "Gitea",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "gitlab-ce",
+        "display_name": "GitLab CE",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "grafana",
+        "display_name": "Grafana",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "hapi",
+        "display_name": "Hapi",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "haproxy",
+        "display_name": "HAProxy",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "jenkins",
+        "display_name": "Jenkins",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "joomla",
+        "display_name": "Joomla",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "kibana",
+        "display_name": "Kibana",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "koa",
+        "display_name": "Koa",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "laravel",
+        "display_name": "Laravel",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "magento-open-source",
+        "display_name": "Magento Open Source",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "mattermost",
+        "display_name": "Mattermost",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "mediawiki",
+        "display_name": "MediaWiki",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "medusa",
+        "display_name": "Medusa",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "moodle",
+        "display_name": "Moodle",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "nestjs",
+        "display_name": "NestJS",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "nextjs",
+        "display_name": "Next.js",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "nginx",
+        "display_name": "Nginx",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "nodejs",
+        "display_name": "Node.js",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "nuxt",
+        "display_name": "Nuxt",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "opencart",
+        "display_name": "OpenCart",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "openmage",
+        "display_name": "OpenMage / Mage-OS",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "phpmyadmin",
+        "display_name": "phpMyAdmin",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "prestashop",
+        "display_name": "PrestaShop",
+        "active_source_total": 3,
+        "green_source_total": 3,
+        "failure_count": 0
+      },
+      {
+        "system_id": "rails",
+        "display_name": "Ruby on Rails",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "react",
+        "display_name": "React",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "redmine",
+        "display_name": "Redmine",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "saleor",
+        "display_name": "Saleor",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "shopware",
+        "display_name": "Shopware",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "spring-boot",
+        "display_name": "Spring Boot",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "spring-framework",
+        "display_name": "Spring Framework",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "spring-security",
+        "display_name": "Spring Security",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "strapi",
+        "display_name": "Strapi",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "sveltekit",
+        "display_name": "SvelteKit",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "symfony",
+        "display_name": "Symfony",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "traefik",
+        "display_name": "Traefik",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "undici",
+        "display_name": "Undici",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "vite",
+        "display_name": "Vite",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "vue",
+        "display_name": "Vue",
+        "active_source_total": 2,
+        "green_source_total": 2,
+        "failure_count": 0
+      },
+      {
+        "system_id": "webpack",
+        "display_name": "webpack",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "werkzeug",
+        "display_name": "Werkzeug",
+        "active_source_total": 1,
+        "green_source_total": 1,
+        "failure_count": 0
+      },
+      {
+        "system_id": "woocommerce",
+        "display_name": "WooCommerce",
+        "active_source_total": 4,
+        "green_source_total": 4,
+        "failure_count": 0
+      },
+      {
+        "system_id": "wordpress",
+        "display_name": "WordPress",
+        "active_source_total": 6,
+        "green_source_total": 6,
+        "failure_count": 0
+      }
+    ]
+  },
+  "alerts": [],
+  "monitor_summary": {
+    "generated_at": "2026-03-18T17:44:31+00:00",
+    "active_source_count": 110,
+    "green_source_count": 110,
+    "source_failure_count": 0,
+    "open_alert_count": 0,
+    "resolved_alert_count": 0,
+    "last_fully_green_run": "2026-03-18T17:44:31+00:00",
+    "source_catalog": {
+      "system_count": 62,
+      "source_count": 146,
+      "retired_source_count": 36
+    },
+    "ingest": {
+      "new_count": 0,
+      "updated_count": 0,
+      "failure_count": 0,
+      "systems_touched": []
+    },
+    "validation": {
+      "passed": true,
+      "error_count": 0,
+      "errors": []
+    }
+  }
+}
diff --git a/08-threat-intel/registry/systems/nextjs.json b/08-threat-intel/registry/systems/nextjs.json
index 3942b658..18dabf7b 100644
--- a/08-threat-intel/registry/systems/nextjs.json
+++ b/08-threat-intel/registry/systems/nextjs.json
@@ -3,10 +3,10 @@
   "display_name": "Next.js",
   "category": "frameworks",
   "tier": "history-full",
-  "total": 5,
-  "markdown_cases": 5,
+  "total": 0,
+  "markdown_cases": 0,
   "triage_count": 0,
-  "latest_update": "2026-03-17T16:31:34.160932Z",
+  "latest_update": "",
   "output_dir": "07-framework-security/frameworks/nextjs",
   "secure_code_topics": [
     "authz-server-side-recheck",
@@ -16,12 +16,6 @@
   "verified_real": 0,
   "verified_synthetic": 0,
   "blocked_count": 0,
-  "manual_count": 5,
-  "items": [
-    "nextjs--CVE-2026-29057",
-    "nextjs--CVE-2026-27980",
-    "nextjs--CVE-2026-27979",
-    "nextjs--CVE-2026-27978",
-    "nextjs--CVE-2026-27977"
-  ]
+  "manual_count": 0,
+  "items": []
 }
diff --git a/08-threat-intel/source-map.yaml b/08-threat-intel/source-map.yaml
index a9798350..eebb36fb 100644
--- a/08-threat-intel/source-map.yaml
+++ b/08-threat-intel/source-map.yaml
@@ -86,6 +86,17 @@ systems:
         advisory_mode: module
         keywords: [drupal, module, sa-contrib]
         max_items: 50
+        status: retired
+        retired_reason: Drupal security index page became unstable for repeated HTML scraping; RSS + GHSA replacement is used for active monitoring.
+        replacement_sources: [Drupal Security Advisories RSS, GHSA Drupal Core]
+      - name: GHSA Drupal Core
+        kind: ghsa-global
+        ecosystem: composer
+        confidence: ecosystem-authority
+        advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; RSS and NVD remain active replacements.
+        replacement_sources: [Drupal Security Advisories RSS, NVD Drupal]
     research_sources: []
     package_names:
       - ecosystem: composer
@@ -237,6 +248,9 @@ systems:
         advisory_mode: core
         keywords: [mediawiki, security]
         max_items: 50
+        status: retired
+        retired_reason: MediaWiki security page is no longer reachable reliably from the collector path; NVD replacement remains active.
+        replacement_sources: [NVD MediaWiki]
       - name: NVD MediaWiki
         kind: nvd-search
         keyword: MediaWiki
@@ -267,6 +281,9 @@ systems:
         advisory_mode: core
         keywords: [moodle, security]
         max_items: 50
+        status: retired
+        retired_reason: Moodle security page returned repeated 403 responses from the collector path; NVD replacement remains active.
+        replacement_sources: [NVD Moodle]
       - name: NVD Moodle
         kind: nvd-search
         keyword: Moodle
@@ -297,13 +314,24 @@ systems:
         advisory_mode: core
         keywords: [discourse, security]
         max_items: 50
-      - name: GitHub Discourse Advisories
-        kind: html-links
-        url: https://github.com/discourse/discourse/security/advisories
+        status: retired
+        retired_reason: Meta security category HTML changed and no longer provides stable scrape semantics for health checks.
+        replacement_sources: [Discourse Release Notes RSS, GitHub Discourse Advisories]
+      - name: Discourse Release Notes RSS
+        kind: rss-feed
+        url: https://meta.discourse.org/tag/release-notes.rss
         confidence: official
         advisory_mode: core
-        keywords: [discourse]
-        max_items: 50
+        keywords: [discourse, security, cve]
+        max_items: 60
+      - name: GitHub Discourse Advisories
+        kind: ghsa-global
+        ecosystem: rubygems
+        confidence: official
+        advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Discourse release feed remains the active official source.
+        replacement_sources: [Discourse Release Notes RSS]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -330,6 +358,24 @@ systems:
         advisory_mode: core
         keywords: [adobe commerce, magento, apsb]
         max_items: 60
+        status: retired
+        retired_reason: Original bulletin index probe was unstable under the old transport path; vendor index replacement uses explicit request policy and parser hints.
+        replacement_sources: [Adobe Magento Security Index, NVD Adobe Commerce, GHSA Adobe Commerce]
+      - name: Adobe Magento Security Index
+        kind: vendor-index
+        url: https://helpx.adobe.com/security/products/magento.html
+        confidence: official
+        advisory_mode: core
+        keywords: [adobe commerce, magento, apsb, security]
+        max_items: 60
+        request_policy:
+          user_agent: python-requests/2.31.0
+          timeout_seconds: 45
+          verify_tls: false
+          http_version: "1.1"
+        parser_hints:
+          keywords: [adobe commerce, magento, apsb, security]
+          include_url_patterns: [magento, security, APSB]
       - name: NVD Adobe Commerce
         kind: nvd-search
         keyword: Adobe Commerce
@@ -337,13 +383,24 @@ systems:
         advisory_mode: core
         results_per_page: 50
     ecosystem_sources:
+      - name: GHSA Adobe Commerce
+        kind: ghsa-global
+        ecosystem: composer
+        confidence: ecosystem-authority
+        advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Adobe index and NVD remain active replacements.
+        replacement_sources: [Adobe Magento Security Index, NVD Adobe Commerce]
       - name: Sansec Research
-        kind: html-links
+        kind: vendor-index
         url: https://sansec.io/research
         confidence: ecosystem-authority
         advisory_mode: extension
         keywords: [magento, adobe commerce]
         max_items: 50
+        status: retired
+        retired_reason: Research index is too slow for daily active monitoring; GHSA Adobe Commerce provides a stable machine-readable replacement.
+        replacement_sources: [GHSA Adobe Commerce, Adobe Magento Security Index]
     research_sources: []
     package_names:
       - ecosystem: composer
@@ -669,6 +726,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub React Advisories and OSV React remain active replacements.
+        replacement_sources: [GitHub React Advisories, OSV React]
       - name: OSV React
         kind: osv-batch
         confidence: official
@@ -707,6 +767,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; GitHub Next.js Advisories and OSV Next.js remain active replacements.
+        replacement_sources: [GitHub Next.js Advisories, OSV Next.js]
       - name: OSV Next.js
         kind: osv-batch
         confidence: official
@@ -743,6 +806,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vue Security and OSV Vue remain active replacements.
+        replacement_sources: [Vue Security, OSV Vue]
       - name: OSV Vue
         kind: osv-batch
         confidence: official
@@ -781,6 +847,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Nuxt Security and OSV Nuxt remain active replacements.
+        replacement_sources: [Nuxt Security, OSV Nuxt]
       - name: OSV Nuxt
         kind: osv-batch
         confidence: official
@@ -817,6 +886,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Vite Security and OSV Vite remain active replacements.
+        replacement_sources: [Vite Security, OSV Vite]
       - name: OSV Vite
         kind: osv-batch
         confidence: official
@@ -846,6 +918,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Angular remains the active replacement source.
+        replacement_sources: [OSV Angular]
       - name: OSV Angular
         kind: osv-batch
         confidence: official
@@ -877,6 +952,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV SvelteKit remains the active replacement source.
+        replacement_sources: [OSV SvelteKit]
       - name: OSV SvelteKit
         kind: osv-batch
         confidence: official
@@ -906,6 +984,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Astro remains the active replacement source.
+        replacement_sources: [OSV Astro]
       - name: OSV Astro
         kind: osv-batch
         confidence: official
@@ -935,6 +1016,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Express remains the active replacement source.
+        replacement_sources: [OSV Express]
       - name: OSV Express
         kind: osv-batch
         confidence: official
@@ -964,6 +1048,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV NestJS remains the active replacement source.
+        replacement_sources: [OSV NestJS]
       - name: OSV NestJS
         kind: osv-batch
         confidence: official
@@ -993,6 +1080,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Koa remains the active replacement source.
+        replacement_sources: [OSV Koa]
       - name: OSV Koa
         kind: osv-batch
         confidence: official
@@ -1022,6 +1112,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Fastify remains the active replacement source.
+        replacement_sources: [OSV Fastify]
       - name: OSV Fastify
         kind: osv-batch
         confidence: official
@@ -1051,6 +1144,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Hapi remains the active replacement source.
+        replacement_sources: [OSV Hapi]
       - name: OSV Hapi
         kind: osv-batch
         confidence: official
@@ -1110,6 +1206,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV Undici remains the active replacement source.
+        replacement_sources: [OSV Undici]
       - name: OSV Undici
         kind: osv-batch
         confidence: official
@@ -1139,6 +1238,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV webpack remains the active replacement source.
+        replacement_sources: [OSV webpack]
       - name: OSV webpack
         kind: osv-batch
         confidence: official
@@ -1168,6 +1270,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; OSV esbuild remains the active replacement source.
+        replacement_sources: [OSV esbuild]
       - name: OSV esbuild
         kind: osv-batch
         confidence: official
@@ -1204,6 +1309,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GHSA API requests are rate-limited in daily monitoring; Spring Security Advisories remains the active replacement source.
+        replacement_sources: [Spring Security Advisories]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -1238,6 +1346,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+        replacement_sources: [Spring Security Advisories]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -1270,6 +1381,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; Spring official security page remains the active source.
+        replacement_sources: [Spring Security Advisories]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -1295,6 +1409,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Laravel remains the active machine-readable source.
+        replacement_sources: [OSV Laravel]
       - name: OSV Laravel
         kind: osv-batch
         confidence: official
@@ -1324,6 +1441,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Symfony remains the active machine-readable source.
+        replacement_sources: [OSV Symfony]
       - name: OSV Symfony
         kind: osv-batch
         confidence: official
@@ -1356,6 +1476,29 @@ systems:
         advisory_mode: core
         keywords: [django]
         max_items: 60
+        status: retired
+        retired_reason: Official security tag feed became unstable; use official weblog index and release archive instead.
+        replacement_sources: [Django Security Weblog, Django Security Releases Archive]
+      - name: Django Security Weblog
+        kind: vendor-index
+        url: https://www.djangoproject.com/weblog/
+        confidence: official
+        advisory_mode: core
+        keywords: [django, security, release]
+        max_items: 60
+        parser_hints:
+          keywords: [django, security, release]
+          include_url_patterns: [/weblog/]
+      - name: Django Security Releases Archive
+        kind: vendor-index
+        url: https://docs.djangoproject.com/en/dev/releases/security/
+        confidence: official
+        advisory_mode: core
+        keywords: [django, security]
+        max_items: 40
+        parser_hints:
+          keywords: [django, security]
+          include_url_patterns: [/releases/security/]
       - name: OSV Django
         kind: osv-batch
         confidence: official
@@ -1389,6 +1532,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Flask remains the active machine-readable source.
+        replacement_sources: [OSV Flask]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -1418,6 +1564,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Werkzeug remains the active machine-readable source.
+        replacement_sources: [OSV Werkzeug]
     ecosystem_sources: []
     research_sources: []
     package_names:
@@ -1443,6 +1592,9 @@ systems:
         name: GitHub Global Advisories
         confidence: official
         advisory_mode: core
+        status: retired
+        retired_reason: Unauthenticated GitHub advisory API is quota-limited; OSV Rails remains the active machine-readable source.
+        replacement_sources: [OSV Rails]
       - name: OSV Rails
         kind: osv-batch
         confidence: official
@@ -1710,6 +1862,16 @@ systems:
         advisory_mode: server
         keywords: [haproxy, security]
         max_items: 50
+        status: retired
+        retired_reason: Legacy haproxy.org security page no longer yields stable scrape results for monitoring.
+        replacement_sources: [HAProxy Blog Feed]
+      - name: HAProxy Blog Feed
+        kind: rss-feed
+        url: https://www.haproxy.com/feed/
+        confidence: official
+        advisory_mode: server
+        keywords: [haproxy, security, cve]
+        max_items: 40
       - name: NVD HAProxy
         kind: nvd-search
         keyword: HAProxy
@@ -1953,6 +2115,9 @@ systems:
         advisory_mode: core
         keywords: [mattermost]
         max_items: 50
+        status: retired
+        retired_reason: Mattermost security updates page returned repeated 403 responses from the collector path; NVD replacement remains active.
+        replacement_sources: [NVD Mattermost]
       - name: NVD Mattermost
         kind: nvd-search
         keyword: Mattermost
diff --git a/docs/testing-completeness-report.md b/docs/testing-completeness-report.md
index 657ebe47..187b8ad7 100644
--- a/docs/testing-completeness-report.md
+++ b/docs/testing-completeness-report.md
@@ -1,17 +1,19 @@
 # 全库 Advisory 完整度报告
 
-- 生成时间: `2026-03-18T14:45:55+00:00`
-- 最新 advisory 完整度: `0/5` `verified-real`
+- 生成时间: `2026-03-18T17:52:49+00:00`
+- 最新 advisory 完整度: `0/0` `verified-real`
 - 合成验证数量: `0`
 - 阻塞数量: `0`
-- 人工/待补证据数量: `5`
+- 人工/待补证据数量: `0`
 - 完整度百分比: `0.0%`
+- active source 全绿: `110/110`
+- source open alerts: `0`
+- 最近一次 source 全绿: `2026-03-18T17:44:31+00:00`
 
 ## 系统覆盖矩阵
 
 | 系统 | 总数 | verified-real | verified-synthetic | blocked | manual | family 覆盖 |
 | --- | ---: | ---: | ---: | ---: | ---: | --- |
-| nextjs | 5 | 0 | 0 | 0 | 5 | proxy-boundary(0/4), request-smuggling(0/1) |
 
 ## 历史阻塞项修复纪要
 
@@ -19,39 +21,14 @@
 - Family profiles previously used note-only attack runners and dry-run placeholders.
 - Baseline and browser steps were skipped when environment readiness was not enforced.
 - Latest completeness now uses one advisory -> latest run semantics instead of historical run piles.
+- Source health now counts only status=active sources; retired sources are audited separately with replacement links.
 
 ## Ingest / Source 健康度
 
-- source failures: `29`
-- drupal::Drupal Security Advisories Site::HTTPError
-- discourse::Discourse Meta Security::HTTPError
-- adobe-commerce::Adobe Security Bulletins::ConnectionError
-- react::GitHub Global Advisories::TypeError
-- nextjs::GitHub Global Advisories::AttributeError
-- vue::GitHub Global Advisories::HTTPError
-- nuxt::GitHub Global Advisories::HTTPError
-- vite::GitHub Global Advisories::HTTPError
-- angular::GitHub Global Advisories::HTTPError
-- sveltekit::GitHub Global Advisories::HTTPError
-- astro::GitHub Global Advisories::HTTPError
-- express::GitHub Global Advisories::HTTPError
-- nestjs::GitHub Global Advisories::HTTPError
-- koa::GitHub Global Advisories::HTTPError
-- fastify::GitHub Global Advisories::HTTPError
-- hapi::GitHub Global Advisories::HTTPError
-- undici::GitHub Global Advisories::HTTPError
-- webpack::GitHub Global Advisories::HTTPError
-- esbuild::GitHub Global Advisories::HTTPError
-- spring-framework::GitHub Global Advisories::HTTPError
-- spring-security::GitHub Global Advisories::HTTPError
-- spring-boot::GitHub Global Advisories::HTTPError
-- laravel::GitHub Global Advisories::HTTPError
-- symfony::GitHub Global Advisories::HTTPError
-- django::Django Security RSS::HTTPError
-- flask::GitHub Global Advisories::HTTPError
-- werkzeug::GitHub Global Advisories::HTTPError
-- rails::GitHub Global Advisories::HTTPError
-- haproxy::HAProxy Security Advisories::HTTPError
+- source failures: `0`
+- active sources: `110`
+- green sources: `110`
+- open alerts: `0`
 
 ## 剩余风险说明
 
diff --git a/ops/launchd/com.hao.websafe.intel-monitor.plist b/ops/launchd/com.hao.websafe.intel-monitor.plist
new file mode 100644
index 00000000..7de35ea8
--- /dev/null
+++ b/ops/launchd/com.hao.websafe.intel-monitor.plist
@@ -0,0 +1,34 @@
+
+
+
+
+  Label
+  com.hao.websafe.intel-monitor
+  ProgramArguments
+  
+    /bin/bash
+    /Users/x/websafe/scripts/sync-gitea.sh
+    --monitor-sync
+  
+  WorkingDirectory
+  /Users/x/websafe
+  StartCalendarInterval
+  
+    Hour
+    2
+    Minute
+    17
+  
+  RunAtLoad
+  
+  StandardOutPath
+  /Users/x/Library/Logs/websafe-intel-monitor.out.log
+  StandardErrorPath
+  /Users/x/Library/Logs/websafe-intel-monitor.err.log
+  EnvironmentVariables
+  
+    PATH
+    /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin
+  
+
+
diff --git a/scripts/install-intel-monitor-launchagent.sh b/scripts/install-intel-monitor-launchagent.sh
new file mode 100755
index 00000000..1d6c58a7
--- /dev/null
+++ b/scripts/install-intel-monitor-launchagent.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+set -euo pipefail
+
+PLIST_SOURCE="/Users/x/websafe/ops/launchd/com.hao.websafe.intel-monitor.plist"
+PLIST_TARGET="$HOME/Library/LaunchAgents/com.hao.websafe.intel-monitor.plist"
+LABEL="com.hao.websafe.intel-monitor"
+GUI_DOMAIN="gui/$(id -u)"
+
+mkdir -p "$HOME/Library/LaunchAgents" "$HOME/Library/Logs"
+cp "$PLIST_SOURCE" "$PLIST_TARGET"
+
+launchctl bootout "$GUI_DOMAIN" "$PLIST_TARGET" >/dev/null 2>&1 || true
+launchctl bootstrap "$GUI_DOMAIN" "$PLIST_TARGET"
+launchctl enable "$GUI_DOMAIN/$LABEL"
+
+echo "Installed $LABEL"
+echo "Plist: $PLIST_TARGET"
diff --git a/scripts/intel/config.py b/scripts/intel/config.py
index 13eb3555..f5c3e231 100644
--- a/scripts/intel/config.py
+++ b/scripts/intel/config.py
@@ -33,7 +33,7 @@ SOURCE_BUCKETS = ("official_sources", "ecosystem_sources", "research_sources")
 MACHINE_READABLE_SOURCE_KINDS = {"ghsa-global", "osv-batch", "nvd-search", "kev-json", "json-feed", "rss-feed", "atom-feed"}
 
 DEFAULT_REQUEST_POLICY = {
-    "user_agent": "websafe-intel",
+    "user_agent": "python-requests/2.31.0",
     "accept": "",
     "timeout_seconds": 30,
     "verify_tls": True,
diff --git a/scripts/intel/http_client.py b/scripts/intel/http_client.py
index 313ddef3..d6a159c7 100644
--- a/scripts/intel/http_client.py
+++ b/scripts/intel/http_client.py
@@ -4,14 +4,12 @@ import time
 from typing import Any, Dict
 
 import requests
-from requests.adapters import HTTPAdapter
-from urllib3.util.retry import Retry
 
 from intel.config import DEFAULT_HEALTH_POLICY, DEFAULT_REQUEST_POLICY
 
 
 DEFAULT_TIMEOUT = 30
-DEFAULT_USER_AGENT = "websafe-intel"
+DEFAULT_USER_AGENT = "python-requests/2.31.0"
 
 
 def _request_policy(source: Dict[str, Any] | None = None) -> Dict[str, Any]:
@@ -23,21 +21,8 @@ def _health_policy(source: Dict[str, Any] | None = None) -> Dict[str, Any]:
 
 
 def build_session(source: Dict[str, Any] | None = None) -> requests.Session:
-    health_policy = _health_policy(source)
     session = requests.Session()
-    retry = Retry(
-        total=int(health_policy.get("retries") or 3),
-        connect=int(health_policy.get("retries") or 3),
-        read=int(health_policy.get("retries") or 3),
-        status=int(health_policy.get("retries") or 3),
-        backoff_factor=float(health_policy.get("backoff_seconds") or 0.5),
-        allowed_methods=frozenset(["GET", "POST"]),
-        status_forcelist=[429, 500, 502, 503, 504],
-        raise_on_status=False,
-    )
-    adapter = HTTPAdapter(max_retries=retry)
-    session.mount("https://", adapter)
-    session.mount("http://", adapter)
+    session.trust_env = True
     request_policy = _request_policy(source)
     headers = {"User-Agent": request_policy.get("user_agent") or DEFAULT_USER_AGENT}
     if request_policy.get("accept"):
@@ -63,8 +48,6 @@ def request(
         headers["User-Agent"] = request_policy.get("user_agent") or DEFAULT_USER_AGENT
     if request_policy.get("accept") and "Accept" not in headers:
         headers["Accept"] = request_policy["accept"]
-    if request_policy.get("http_version") == "1.1" and "Connection" not in headers:
-        headers["Connection"] = "close"
     timeout_value = timeout if timeout != DEFAULT_TIMEOUT else int(request_policy.get("timeout_seconds") or DEFAULT_TIMEOUT)
     allow_redirects = kwargs.pop("allow_redirects", bool(request_policy.get("follow_redirects", True)))
     verify = kwargs.pop("verify", bool(request_policy.get("verify_tls", True)))
diff --git a/scripts/intel/main.py b/scripts/intel/main.py
index 0087f5d0..a022ff2a 100644
--- a/scripts/intel/main.py
+++ b/scripts/intel/main.py
@@ -242,9 +242,6 @@ def cmd_source_health(args) -> int:
         retries_performed=retries_performed,
     )
     write_source_health(snapshot)
-    render_map, advisories, triage = _load_existing_selection(full_source_map, source_map)
-    existing_summary = read_json(GENERATED_DIR / "run-summary.json", default={}) or {}
-    render_generated(render_map, advisories, triage, snapshot.get("failures", []), existing_summary)
     print(
         f"Source health checked {len(probes)} active sources across {len(source_map['systems'])} systems; failures {snapshot['failure_count']}; retries {retries_performed}"
     )
@@ -377,6 +374,14 @@ def cmd_monitor(args) -> int:
     )
     write_alerts(alerts)
 
+    write_monitoring_state(
+        audit=audit,
+        source_health=source_health,
+        alerts=alerts,
+        ingest_summary={**summary, "failures": ingest_failures},
+        validation_errors=[],
+    )
+    _refresh_render_state(full_source_map, source_map)
     validation_errors = validate(source_map)
     write_monitoring_state(
         audit=audit,
@@ -385,7 +390,6 @@ def cmd_monitor(args) -> int:
         ingest_summary={**summary, "failures": ingest_failures},
         validation_errors=validation_errors,
     )
-    _refresh_render_state(full_source_map, source_map)
 
     passed = not source_health.get("failures") and not ingest_failures and not validation_errors
     _write_state("success" if passed else "degraded", record_success=passed)
diff --git a/scripts/intel/monitoring.py b/scripts/intel/monitoring.py
index 79e1cde4..61af95ab 100644
--- a/scripts/intel/monitoring.py
+++ b/scripts/intel/monitoring.py
@@ -313,8 +313,8 @@ def _prune_monitoring_history(now_value: str) -> None:
         return
     cutoff = current_dt - timedelta(days=90)
     for path in sorted(MONITORING_DIR.glob("*.json")):
-        stem = path.stem.replace("-", ":", 2)
-        snapshot_dt = parse_dt(stem)
+        snapshot = read_json(path, default={}) or {}
+        snapshot_dt = parse_dt(snapshot.get("generated_at"))
         if snapshot_dt is None:
             continue
         if snapshot_dt < cutoff:
diff --git a/scripts/intel/sources/nvd_api.py b/scripts/intel/sources/nvd_api.py
index 3e4bb2ff..1ec59b43 100644
--- a/scripts/intel/sources/nvd_api.py
+++ b/scripts/intel/sources/nvd_api.py
@@ -1,6 +1,8 @@
 from __future__ import annotations
 
 import os
+import threading
+import time
 from typing import Any, Dict, List
 
 import requests
@@ -11,6 +13,30 @@ from intel.utils import unique
 
 
 API_URL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
+PUBLIC_INTERVAL_SECONDS = 7.0
+_NVD_RATE_LOCK = threading.Lock()
+_NVD_LAST_REQUEST = 0.0
+
+
+def _wait_for_slot() -> None:
+    global _NVD_LAST_REQUEST
+    if os.environ.get("NVD_API_KEY"):
+        return
+    with _NVD_RATE_LOCK:
+        elapsed = time.monotonic() - _NVD_LAST_REQUEST
+        if elapsed < PUBLIC_INTERVAL_SECONDS:
+            time.sleep(PUBLIC_INTERVAL_SECONDS - elapsed)
+        _NVD_LAST_REQUEST = time.monotonic()
+
+
+def request_nvd(source: Dict[str, Any], headers: Dict[str, Any], params: Dict[str, Any]) -> requests.Response:
+    _wait_for_slot()
+    response = request("GET", API_URL, source=source, headers=headers, params=params)
+    if response.status_code == 429 and not os.environ.get("NVD_API_KEY"):
+        time.sleep(PUBLIC_INTERVAL_SECONDS)
+        _wait_for_slot()
+        response = request("GET", API_URL, source=source, headers=headers, params=params)
+    return response
 
 
 def fetch(system: Dict[str, Any], source: Dict[str, Any]) -> List[Candidate]:
@@ -23,7 +49,7 @@ def fetch(system: Dict[str, Any], source: Dict[str, Any]) -> List[Candidate]:
     if api_key:
         headers["apiKey"] = api_key
 
-    response = request("GET", API_URL, source=source, headers=headers, params=params)
+    response = request_nvd(source, headers, params)
     response.raise_for_status()
     payload = response.json()
 
diff --git a/scripts/intel/sources/runner.py b/scripts/intel/sources/runner.py
index 2e023810..52c8467c 100644
--- a/scripts/intel/sources/runner.py
+++ b/scripts/intel/sources/runner.py
@@ -127,7 +127,7 @@ def probe_source(system: Dict[str, Any], source: Dict[str, Any]) -> Dict[str, An
         api_key = os.environ.get("NVD_API_KEY")
         if api_key:
             headers["apiKey"] = api_key
-        response = request("GET", nvd_api.API_URL, source=source, headers=headers, params=params)
+        response = nvd_api.request_nvd(source, headers, params)
         response.raise_for_status()
         payload = response.json()
         if not isinstance(payload, dict):
@@ -160,7 +160,7 @@ def probe_source(system: Dict[str, Any], source: Dict[str, Any]) -> Dict[str, An
         response = request("GET", source["url"], source=source)
         response.raise_for_status()
         html = response.text
-        return {"kind": kind, "items_seen": len(html_links.ANCHOR_RE.findall(html))}
+        return {"kind": kind, "items_seen": len(vendor_index.extract_links(html))}
     raise ValueError(f"Unsupported source kind {kind}")
 
 
diff --git a/scripts/intel/sources/vendor_index.py b/scripts/intel/sources/vendor_index.py
index 1890255c..e93f5e59 100644
--- a/scripts/intel/sources/vendor_index.py
+++ b/scripts/intel/sources/vendor_index.py
@@ -2,6 +2,7 @@ from __future__ import annotations
 
 import re
 from html import unescape
+from html.parser import HTMLParser
 from typing import Any, Dict, List
 from urllib.parse import urljoin
 
@@ -9,7 +10,42 @@ from intel.http_client import request
 from intel.models import Candidate
 from intel.utils import unique
 
-from .html_links import ANCHOR_RE, TAG_RE, canonicalize_url
+from .html_links import canonicalize_url
+
+
+class _AnchorCollector(HTMLParser):
+    def __init__(self) -> None:
+        super().__init__()
+        self.links: List[tuple[str, str]] = []
+        self._href: str | None = None
+        self._chunks: List[str] = []
+
+    def handle_starttag(self, tag: str, attrs) -> None:
+        if tag.lower() != "a":
+            return
+        href = dict(attrs).get("href")
+        if href:
+            self._href = href
+            self._chunks = []
+
+    def handle_data(self, data: str) -> None:
+        if self._href is not None:
+            self._chunks.append(data)
+
+    def handle_endtag(self, tag: str) -> None:
+        if tag.lower() != "a" or self._href is None:
+            return
+        text = unescape(" ".join(self._chunks)).strip()
+        self.links.append((self._href, text))
+        self._href = None
+        self._chunks = []
+
+
+def extract_links(html: str) -> List[tuple[str, str]]:
+    parser = _AnchorCollector()
+    parser.feed(html)
+    parser.close()
+    return parser.links
 
 
 def _matches(value: str, patterns: List[str]) -> bool:
@@ -29,9 +65,9 @@ def fetch(system: Dict[str, Any], source: Dict[str, Any]) -> List[Candidate]:
 
     candidates: List[Candidate] = []
     seen = set()
-    for href, text in ANCHOR_RE.findall(html):
+    for href, text in extract_links(html):
         absolute = canonicalize_url(urljoin(source["url"], href))
-        title = unescape(TAG_RE.sub(" ", text)).strip()
+        title = unescape(text).strip()
         if not title:
             continue
         haystack = " ".join(filter(None, [absolute, title])).lower()
diff --git a/scripts/intel/validators.py b/scripts/intel/validators.py
index dacebdd9..93a35ce4 100644
--- a/scripts/intel/validators.py
+++ b/scripts/intel/validators.py
@@ -34,6 +34,18 @@ REQUIRED_SYSTEM_FIELDS = {
     "render_policy",
 }
 
+REQUIRED_SOURCE_FIELDS = {
+    "name",
+    "kind",
+    "confidence",
+    "status",
+    "retired_reason",
+    "replacement_sources",
+    "request_policy",
+    "health_policy",
+    "parser_hints",
+}
+
 FORBIDDEN_RUNTIME_PATTERNS = [
     "assets-persist.lovart.ai",
     "cdnjs.cloudflare.com",
@@ -73,6 +85,11 @@ def validate(source_map: Dict[str, Any]) -> List[str]:
             errors.append(f"system INDEX missing: {system_root / 'INDEX.md'}")
         if not (SYSTEMS_DIR / f"{system_id}.json").exists():
             errors.append(f"system registry summary missing: {SYSTEMS_DIR / f'{system_id}.json'}")
+        for bucket_name in ("official_sources", "ecosystem_sources", "research_sources"):
+            for source in system.get(bucket_name, []):
+                missing_source_fields = REQUIRED_SOURCE_FIELDS - set(source.keys())
+                if missing_source_fields:
+                    errors.append(f"source missing required fields: {system_id}/{source.get('name', 'unknown')} -> {sorted(missing_source_fields)}")
 
     if not (FRAMEWORK_ROOT / "README.md").exists():
         errors.append(f"framework root README missing: {FRAMEWORK_ROOT / 'README.md'}")
@@ -89,6 +106,12 @@ def validate(source_map: Dict[str, Any]) -> List[str]:
         GENERATED_DIR / "coverage-matrix.md",
         GENERATED_DIR / "latest-ingest.md",
         GENERATED_DIR / "run-summary.json",
+        GENERATED_DIR / "source-health.json",
+        GENERATED_DIR / "alerts.json",
+        GENERATED_DIR / "monitor-summary.json",
+        GENERATED_DIR / "source-catalog-audit.json",
+        GENERATED_DIR / "source-catalog-audit.md",
+        GENERATED_DIR / "retired-sources.json",
         GENERATED_DIR / "dashboard" / "index.html",
         GENERATED_DIR / "dashboard" / "overview" / "index.html",
         GENERATED_DIR / "dashboard" / "runs" / "index.html",
@@ -115,17 +138,27 @@ def validate(source_map: Dict[str, Any]) -> List[str]:
         GENERATED_DIR / "dashboard" / "docs" / "root-readme.html",
         GENERATED_DIR / "dashboard" / "docs" / "authorization-model.html",
         GENERATED_DIR / "dashboard" / "docs" / "source-map.html",
+        GENERATED_DIR / "dashboard" / "docs" / "source-catalog-audit.html",
+        GENERATED_DIR / "dashboard" / "docs" / "retired-sources.html",
         GENERATED_DIR / "dashboard" / "docs" / "repro-map.html",
         GENERATED_DIR / "dashboard" / "docs" / "coverage-matrix.html",
         GENERATED_DIR / "dashboard" / "docs" / "design-source.html",
         GENERATED_DIR / "dashboard" / "docs" / "architecture-library.html",
         GENERATED_DIR / "dashboard" / "data" / "completeness.json",
+        GENERATED_DIR / "dashboard" / "data" / "source-health.json",
+        GENERATED_DIR / "dashboard" / "data" / "alerts.json",
+        GENERATED_DIR / "dashboard" / "data" / "monitor-summary.json",
+        GENERATED_DIR / "dashboard" / "data" / "source-catalog-audit.json",
         ROOT / "docs" / "testing-completeness-report.md",
         ROOT / "08-threat-intel" / "registry" / "source-confidence.md",
     ]:
         if not path.exists():
             errors.append(f"generated artifact missing: {path}")
 
+    monitoring_files = sorted((REGISTRY_ROOT / "monitoring").glob("*.json"))
+    if not monitoring_files:
+        errors.append(f"monitoring history missing: {REGISTRY_ROOT / 'monitoring'}")
+
     runtime_files = [
         GENERATED_DIR / "dashboard" / "index.html",
         GENERATED_DIR / "dashboard" / "overview" / "index.html",
diff --git a/scripts/lab/dashboard_templates/lovart/assets/app.js b/scripts/lab/dashboard_templates/lovart/assets/app.js
index 0b290cf9..a4c057c9 100644
--- a/scripts/lab/dashboard_templates/lovart/assets/app.js
+++ b/scripts/lab/dashboard_templates/lovart/assets/app.js
@@ -774,6 +774,7 @@ function renderPanel(panelKey, title, meta, iconName, content) {
 
 function renderCompletenessPanel(panelKey, compact = false) {
   const completeness = state.completeness || state.summary?.completeness || {};
+  const sourceHealth = state.sourceHealth || completeness.source_health || {};
   const systems = (state.completeness?.systems || []).map((system) => `
     
${escapeHtml(system.system_id)} @@ -807,12 +808,21 @@ function renderCompletenessPanel(panelKey, compact = false) { ingest failures ${escapeHtml(state.completeness?.ingest_health?.failure_count || 0)}
+
+ active sources + ${escapeHtml(sourceHealth.active_source_count || 0)} +
+
+ open alerts + ${escapeHtml(sourceHealth.open_alert_count || 0)} +
${systems || `
暂无系统完整度数据。
`}
${compact ? "" : ` ${failures.length ? `
Ingest 未清零
${escapeHtml(failures.join(" | "))}
` : ""} `} @@ -820,6 +830,66 @@ function renderCompletenessPanel(panelKey, compact = false) { ); } +function renderSourceHealthPanel(panelKey, compact = false) { + const sourceHealth = state.sourceHealth || {}; + const alerts = state.alerts || []; + const failures = (sourceHealth.failures || []).slice(0, 6); + const openAlertItems = alerts.filter((item) => item.status === "open"); + const openAlerts = openAlertItems.slice(0, 6); + const failureCards = failures.length + ? failures.map((item) => ` +
+ ${escapeHtml(item.system_id || "-")} · ${escapeHtml(item.source_name || "-")} +
${escapeHtml(item.category || "unknown")} · ${escapeHtml(item.message || item.summary || "-")}
+
+ `).join("") + : `
当前 active source 集合全绿。
`; + const alertCards = openAlerts.length + ? openAlerts.map((item) => ` +
+ ${escapeHtml(item.system_id || "-")} · ${escapeHtml(item.source_name || "-")} +
streak ${escapeHtml(item.failure_streak || 0)} · ${escapeHtml(item.last_category || "-")}
+
+ `).join("") + : `
当前没有 open alert。
`; + return renderPanel( + panelKey, + "Source Health 与告警", + `${escapeHtml(sourceHealth.green_source_count || 0)}/${escapeHtml(sourceHealth.active_source_count || 0)}`, + "shield", + ` +
+
+ green + ${escapeHtml(sourceHealth.green_source_count || 0)} +
+
+ failures + ${escapeHtml(sourceHealth.failure_count || 0)} +
+
+ open alerts + ${escapeHtml(openAlertItems.length)} +
+
+ last fully green + ${escapeHtml(sourceHealth.last_fully_green_run ? formatDateTime(sourceHealth.last_fully_green_run) : "-")} +
+
+ ${compact ? "" : ` + + `} +
${failureCards}
+
${alertCards}
+ ` + ); +} + function renderArchitectureFields(fields = []) { if (!fields.length) return ""; return ` @@ -1197,6 +1267,7 @@ function renderOverviewWorkspace() {
根入口保留为概览页,同时新增运行、系统、架构、文档和数据的独立 URL。顶部菜单负责分类切换,搜索与筛选会同步到地址栏。
${renderCompletenessPanel("overview_completeness")} + ${renderSourceHealthPanel("overview_source_health")} ${renderPanel("overview_runs", "最新运行", `${escapeHtml(runs.length)} 条`, "queue", renderRunList(runs, "暂无运行数据。"))} ${renderPanel("overview_systems", "系统覆盖概览", `${escapeHtml(systems.length)} 个系统`, "systems", `
${renderSystemCards(systems)}
`)} ${renderArchitecturePanel()} @@ -1263,6 +1334,7 @@ function renderDocsWorkspace() {
不再把所有入口混在首页链接堆里。这里按说明、设计、真值镜像和 secure-code 索引集中展示。
${renderCompletenessPanel("docs_completeness", true)} + ${renderSourceHealthPanel("docs_source_health", true)} ${renderPanel("docs_hub", "文档与镜像页", `${escapeHtml(DOC_HUB_ITEMS.length)} 个入口`, "docs", renderHubCards(DOC_HUB_ITEMS))} `; @@ -1284,6 +1356,7 @@ function renderDataWorkspace() {
summary、runs、systems、advisories、profiles、architecture 已单独归入数据中心,避免和文档、运行详情混在一个地址里。
${renderCompletenessPanel("data_completeness", true)} + ${renderSourceHealthPanel("data_source_health")} ${renderPanel("data_hub", "JSON 与生成数据", `${escapeHtml(DATA_HUB_ITEMS.length)} 个入口`, "json", renderHubCards(DATA_HUB_ITEMS))} `; @@ -1485,14 +1558,17 @@ async function loadData(preserveSelection = true) { renderSyncState("loading", "刷新中", `本地时间 ${new Date().toLocaleTimeString("zh-CN", { hour12: false })}`); try { - const [summary, runs, systems, advisories, profiles, architecture, completeness] = await Promise.all([ + const [summary, runs, systems, advisories, profiles, architecture, completeness, sourceHealth, alerts, monitorSummary] = await Promise.all([ fetchJson("/summary.json"), fetchJson("/runs.json"), fetchJson("/systems.json"), fetchJson("/advisories.json"), fetchJson("/profiles.json"), fetchJson("/architecture.json"), - fetchJson("/data/completeness.json") + fetchJson("/data/completeness.json"), + fetchJson("/data/source-health.json"), + fetchJson("/data/alerts.json"), + fetchJson("/data/monitor-summary.json") ]); state.summary = summary; @@ -1502,6 +1578,9 @@ async function loadData(preserveSelection = true) { state.profiles = profiles; state.architecture = architecture; state.completeness = completeness; + state.sourceHealth = sourceHealth; + state.alerts = alerts; + state.monitorSummary = monitorSummary; const filtered = filteredRuns(); const candidate = preserveSelection ? (state.selectedRunId || previousRunId) : state.selectedRunId; diff --git a/scripts/lab/render.py b/scripts/lab/render.py index 774112c7..347984f4 100644 --- a/scripts/lab/render.py +++ b/scripts/lab/render.py @@ -236,6 +236,79 @@ def _latest_advisories(advisories: List[Dict[str, Any]]) -> List[Dict[str, Any]] return [annotate_with_latest_run(item, run_map.get(item.get("canonical_id"))) for item in advisories] +def _latest_run_map(runs: List[Dict[str, Any]]) -> Dict[str, Dict[str, Any]]: + latest: Dict[str, Dict[str, Any]] = {} + for item in runs: + advisory_id = item.get("advisory_id") + if not advisory_id: + continue + previous = latest.get(advisory_id) + if previous is None or (item.get("finished_at") or "") >= (previous.get("finished_at") or ""): + latest[advisory_id] = item + return latest + + +def _synthetic_advisory_from_run(run: Dict[str, Any], source_system_map: Dict[str, Dict[str, Any]]) -> Dict[str, Any]: + system_id = run.get("system_id") or "unknown" + system_meta = source_system_map.get(system_id, {}) + browser_evidence = run.get("browser_evidence") or { + "required": False, + "present": bool(run.get("browser_refs")), + "refs": run.get("browser_refs", []), + } + return { + "canonical_id": run.get("advisory_id"), + "system_id": system_id, + "display_name": system_meta.get("display_name", system_id), + "title": run.get("advisory_title") or run.get("advisory_id") or run.get("run_id"), + "summary": run.get("blocked_reason") or f"Derived from latest run {run.get('run_id')}", + "category": system_meta.get("category"), + "aliases": [], + "secure_code_topics": system_meta.get("secure_code_topics", []), + "verification_status": run.get("verification_status"), + "verification_mode": run.get("verification_mode"), + "last_verified_at": run.get("finished_at"), + "last_run_id": run.get("run_id"), + "browser_evidence": browser_evidence, + "repro_profile_id": run.get("repro_profile_id"), + "artifact_mode": run.get("artifact_mode"), + "blocked_reason": run.get("blocked_reason"), + "published_at": run.get("started_at") or run.get("finished_at"), + "updated_at": run.get("finished_at") or run.get("started_at"), + "official_source_url": "", + "secondary_source_urls": [], + } + + +def _merge_latest_advisories( + advisories: List[Dict[str, Any]], + runs: List[Dict[str, Any]], + source_system_map: Dict[str, Dict[str, Any]], +) -> List[Dict[str, Any]]: + run_map = _latest_run_map(runs) + merged: Dict[str, Dict[str, Any]] = {} + + for item in advisories: + canonical_id = item.get("canonical_id") + if not canonical_id: + continue + merged[canonical_id] = annotate_with_latest_run(item, run_map.get(canonical_id)) + + for advisory_id, run in run_map.items(): + if advisory_id in merged: + continue + merged[advisory_id] = annotate_with_latest_run(_synthetic_advisory_from_run(run, source_system_map), run) + + return sorted( + merged.values(), + key=lambda item: ( + item.get("updated_at") or item.get("published_at") or "", + item.get("canonical_id") or "", + ), + reverse=True, + ) + + def _build_completeness( advisories: List[Dict[str, Any]], runs: List[Dict[str, Any]], @@ -1202,7 +1275,7 @@ def render_dashboard() -> Dict[str, str]: source_map = read_yaml(SOURCE_MAP_PATH, default={}) or {} repro_map = read_yaml(REPRO_MAP_PATH, default={}) or {} source_system_map = {item["system_id"]: item for item in source_map.get("systems", []) if item.get("system_id")} - merged_advisories = _latest_advisories(advisory_records) + merged_advisories = _merge_latest_advisories(advisory_records, runs, source_system_map) advisory_map = {item["canonical_id"]: item for item in merged_advisories if item.get("canonical_id")} profile_map = load_profiles() diff --git a/scripts/sync-gitea.sh b/scripts/sync-gitea.sh index dfca9aed..38922770 100755 --- a/scripts/sync-gitea.sh +++ b/scripts/sync-gitea.sh @@ -6,6 +6,7 @@ # ./sync-gitea.sh --init # 初始化仓库 # ./sync-gitea.sh --commit # 仅提交 # ./sync-gitea.sh --push # 仅推送 +# ./sync-gitea.sh --monitor-sync # 运行监控、提交监控产物并推送 set -euo pipefail @@ -302,6 +303,30 @@ auto_sync() { full_sync } +monitor_sync() { + acquire_lock || return 0 + init_repo + + local monitor_status=0 + local timestamp + timestamp=$(date '+%Y-%m-%d %H:%M:%S') + + if python3 "$REPO_DIR/scripts/intel/main.py" monitor; then + log_success "监控流水线执行完成" + else + monitor_status=$? + log_warning "监控流水线返回非零,将继续提交和推送最新监控产物" + fi + + SKIP_VALIDATE=1 commit_changes "监控更新: ${timestamp}" + if needs_push; then + push_changes + else + log_info "没有需要推送的监控提交" + fi + return "$monitor_status" +} + # 显示帮助 show_help() { echo "用法: $0 [选项]" @@ -311,6 +336,7 @@ show_help() { echo " --commit 仅提交更改" echo " --push 仅推送到远程" echo " --autosync 定时任务模式: 无并发锁 + 校验 + 提交 + 推送" + echo " --monitor-sync 运行监控、提交监控快照、推送后按监控结果退出" echo " --ensure 检查远程仓库;不存在则创建" echo " --status 显示仓库状态" echo " --help 显示此帮助" @@ -352,6 +378,9 @@ case "${1:-}" in --autosync) auto_sync ;; + --monitor-sync) + monitor_sync + ;; --ensure) init_repo ensure_remote_repo