infinri / ase
Automated Security Evaluator -- CVE monitoring for Magento / Adobe Commerce / Mage-OS stores. Polls KEV/NVD/GHSA/OSV/Packagist, prioritizes with CVSS+EPSS+KEV, alerts via Slack.
Requires
- php: ^8.4
- ext-curl: *
- ext-json: *
- ext-mbstring: *
- ext-openssl: *
- composer/semver: ^3.4
- monolog/monolog: ^3.10
- vlucas/phpdotenv: ^5.6
Requires (Dev)
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^13.1
This package is auto-updated.
Last update: 2026-04-25 00:15:00 UTC
README
CVE monitoring for Magento / Adobe Commerce / Mage-OS stores. One command, one Slack channel, one exit code for CI.
Why this exists
Magento stores live downstream of a noisy security ecosystem. CISA KEV, NVD, GitHub Security Advisories, OSV, and Packagist each publish vulnerability data with different coverage, latency, and signal-to-noise. Reading all five by hand is nobody's job. Skipping them is how stores get popped.
Most teams land in one of two failure modes:
- Alert fatigue. A generic CVE feed pipes every CVSS >= 7 into Slack. After day three, the channel is muted. After week one, a real P0 gets missed.
- Blind spots. The team subscribes to a single source (usually Adobe's security bulletin) and misses KEV additions, Packagist advisories for third-party modules, and EPSS spikes on old CVEs.
ASE closes both gaps. It polls four feeds by default (KEV, NVD, GHSA, Packagist) -- with OSV available as an optional fifth -- deduplicates across them, filters against your composer.lock when configured so it only shows CVEs that actually affect installed versions, scores every finding with CVSS + EPSS + KEV, and alerts on just P0 and P1 -- the two tiers worth a ping. Anything below P1 is dropped before notification so the Slack channel stays signal.
No flood on day one. The first run imports the current state silently -- every existing vulnerability is marked as already-notified at its current priority, and nothing is posted to Slack. Subsequent runs only alert on genuinely new findings or priority escalations (e.g., a known CVE gets added to CISA KEV). Because only P0/P1 emits, and messages are throttled 1.5s apart, even a large backfill produces a bounded stream of alerts rather than a channel-drowning flood.
It is a CLI. It runs under cron. It exits 0, 1, or 2 based on what it found, so you can gate a CI pipeline on it. That's the whole surface.
What it catches
A KEV-listed RCE drops against a Magento module you have installed. ASE polls KEV on its next cycle, matches the CVE's vulnerable range against your composer.lock, classifies it as P0, posts a Slack alert with:
- CVE ID and canonical description
- CVSS score and vector, EPSS percentile, KEV status
- Exact installed version vs. fixed version
- Links to NVD, GHSA (if cross-referenced), and the Packagist page for the fixed release
- A one-line composer update command to remediate
If you ran it in CI that same hour, ase --dry-run --format=json would have exited 2 and failed the deploy.
Quick start
# Install globally (adds `ase` to your PATH) composer global require infinri/ase # Minimal config: one Slack webhook export SLACK_WEBHOOK_URL='https://hooks.slack.com/services/...' # Point ASE at your project's composer.lock, then scan without sending alerts. export COMPOSER_LOCK_PATH=/path/to/your/magento/project/composer.lock ase --dry-run --format=json # Optional: verify your webhook wiring before scheduling under cron ase --test-alert
COMPOSER_LOCK_PATH is optional. When set, ASE auto-detects Magento edition, the vendor list, the NVD CPE prefix, and runs installed-version matching. When unset, ASE runs in project-agnostic mode: feeds still poll, but composer-ecosystem filtering is disabled and a warning is logged. For Magento monitoring, always set it -- in .env for production/cron, or export for ad-hoc runs.
--dry-run doesn't touch Slack or persist state. --format=json emits a machine-readable report to stdout; operational logs stream to stderr in JSON format (one object per line), so --format=json output on stdout is always clean for piping into jq, CI artifacts, or a log shipper. Together they're the safe way to evaluate ASE before wiring it into a real channel.
--test-alert posts a sample P0 (and a P1 if SLACK_WEBHOOK_P1 is set) to the configured webhooks so you can verify the Slack side is connected without waiting for a real CVE to drop.
When you're ready for notifications, drop --dry-run and schedule under cron (example below).
CLI reference
ase [flags]
Flags:
--dry-run Scan but do not send Slack alerts or persist state
--format=<human|json> Output format (default: human)
--since <YYYY-MM-DD> Backfill from a specific date (first run only)
--test-slack Send a test message to the configured channel and exit
--test-alert Send a P0 sample (and a P1 sample if SLACK_WEBHOOK_P1 is set) for wiring verification
Exit codes:
0 No P0 or P1 finding in the alertable set
1 At least one P1 (and no P0) in the alertable set
2 At least one P0 in the alertable set, or a fatal config error
The alertable set is what this run would alert on: new findings plus priority escalations. Already-notified findings at the same priority don't count.
Configuration
Configuration is env-driven. Either export variables in your shell, drop them in a .env next to the binary, or put them in your system cron environment.
Required (for normal runs)
| Variable | Description |
|---|---|
SLACK_WEBHOOK_URL |
Slack incoming webhook URL. Optional when using --dry-run or --format=json. |
Recommended
| Variable | Description | Default |
|---|---|---|
NVD_API_KEY |
Free NVD API key (lifts rate limit from 5 to 50 req/30s) | none |
GITHUB_TOKEN |
GitHub PAT, public scope is enough (higher GHSA rate limit) | none |
COMPOSER_LOCK_PATH |
Absolute path to your project's composer.lock. Optional. When set, enables Magento edition detection, vendor filter, CPE prefix auto-detection, and installed-version matching. When unset, ASE runs in project-agnostic mode (feeds still poll; composer-ecosystem filtering is disabled, warning logged). For Magento monitoring, set this. |
none |
SLACK_WEBHOOK_P1 |
Optional second webhook for P1 alerts. When unset, P1 alerts are silently skipped (logged as a warning). | none |
LOG_FILE_LEVEL |
Minimum level for the rotating file log. One of DEBUG, INFO, NOTICE, WARNING, ERROR, CRITICAL, ALERT, EMERGENCY (case-insensitive). Set to DEBUG when troubleshooting to capture per-HTTP-request detail; leave at INFO otherwise to keep the file log tight. Stderr always streams at INFO. |
INFO |
Feed control
| Variable | Description | Default |
|---|---|---|
ENABLED_FEEDS |
Comma-separated list of feeds to poll. OSV is off by default -- add osv here to turn it on (see "Enabling OSV" below). |
kev,nvd,ghsa,packagist |
ECOSYSTEMS |
Additive -- merged with composer (auto-detected from your lockfile) |
empty |
VENDOR_FILTER |
Additive -- merged with vendor names parsed from your composer.lock (KEV filtering) |
empty |
NVD_CPE_PREFIX |
Override -- if set, replaces the auto-detected CPE. Auto-detection maps Magento edition: community -> cpe:2.3:a:magento:magento, enterprise -> cpe:2.3:a:adobe:commerce |
auto |
Poll intervals (seconds)
| Variable | Default | Notes |
|---|---|---|
POLL_INTERVAL_KEV |
7200 | CISA KEV updates on business hours |
POLL_INTERVAL_NVD |
7200 | NIST recommends no more than every 2 hours |
POLL_INTERVAL_GHSA |
1800 | GitHub Advisories, 30 min |
POLL_INTERVAL_OSV |
1800 | OSV, 30 min |
POLL_INTERVAL_PACKAGIST |
3600 | Packagist, 1 hour |
Priority thresholds
| Variable | Default | Description |
|---|---|---|
CVSS_CRITICAL_THRESHOLD |
9.0 | P0 trigger when combined with EPSS |
CVSS_HIGH_THRESHOLD |
7.0 | P1 boundary |
EPSS_HIGH_THRESHOLD |
0.10 | 10% exploit probability threshold |
Priority system
ASE only tracks and alerts on P0 and P1 findings. Anything below those thresholds is dropped before notification or persistence.
| Priority | Criteria | Notification |
|---|---|---|
| P0 Immediate | In CISA KEV, OR (CVSS >= 9.0 AND EPSS >= 10%) | SLACK_WEBHOOK_URL, exit code 2 |
| P1 Urgent | (CVSS >= 7.0 AND EPSS >= 10%), OR known ransomware, OR affects installed version with CVSS >= 7.0 | SLACK_WEBHOOK_P1 if set, else skipped with a one-line warning. Exit code 1. |
Two-webhook model. Slack incoming webhooks are channel-scoped. P0 always posts to SLACK_WEBHOOK_URL -- the only required webhook. P1 posts to a separate SLACK_WEBHOOK_P1 webhook when you want those alerts in a different channel; leave it unset to suppress P1 alerts entirely.
Escalation re-notification: If a vulnerability's priority increases (e.g., added to CISA KEV, EPSS spike), a new alert fires with escalation context, even if previously notified at a lower tier.
Getting API keys
NVD API Key (free, 10x rate limit):
- https://nvd.nist.gov/developers/request-an-api-key
- Enter email, request key
- Set
NVD_API_KEY
GitHub Token (optional):
- https://github.com/settings/tokens
- Generate token (classic) -- no scopes needed for public advisories
- Set
GITHUB_TOKEN
Slack Webhook:
- https://api.slack.com/apps -- create a new app
- Enable Incoming Webhooks, add to target channel
- Set
SLACK_WEBHOOK_URL
KEV, EPSS, Packagist, and OSV (when enabled) need no authentication.
Advanced deployment
For production you'll typically deploy under cron with dedicated log/state directories rather than relying on composer global's user-scoped install.
git clone https://github.com/infinri/A.S.E.git /opt/ase cd /opt/ase composer install --no-dev --optimize-autoloader cp .env.example .env # edit .env -- at minimum set: # SLACK_WEBHOOK_URL (P0 alerts) # COMPOSER_LOCK_PATH (absolute path to your Magento project's composer.lock; # required for Magento monitoring -- enables edition # detection, vendor filter, CPE prefix, and installed- # version matching) sudo mkdir -p /var/lib/ase /var/log/ase /var/run/ase sudo chown "$(whoami)" /var/lib/ase /var/log/ase /var/run/ase
Cron
# Main run every 30 minutes, flock prevents overlap */30 * * * * /usr/bin/flock -n /tmp/ase.lock /opt/ase/bin/ase >> /var/log/ase/cron.log 2>&1 # Heartbeat, hourly 30 * * * * /opt/ase/bin/heartbeat.sh
Feeds with longer poll intervals (KEV, NVD at 2h) automatically skip runs where their interval has not elapsed.
Disabling a feed
Remove its name from ENABLED_FEEDS in .env. No code change needed.
ENABLED_FEEDS=kev,nvd
# ghsa and packagist now skipped
Enabling OSV
OSV is off by default because Packagist advisories and GHSA both cover the Composer ecosystem already, and OSV aggregates GHSA. If you want OSV as a third cross-check, add it to the list:
ENABLED_FEEDS=kev,nvd,ghsa,packagist,osv
When enabled, OSV issues a single POST /v1/querybatch call populated from your composer.lock (one query per installed package), then hydrates each returned advisory via /v1/vulns/{id}.
State and troubleshooting
ASE persists notification state to STATE_FILE (default var/state/state.json, or /var/lib/ase/state.json in the advanced-deployment layout below). The state file is what keeps you from getting re-alerted on the same CVE every run; it records which vulnerabilities have been notified and at what priority.
Reset state (force a silent re-import):
rm /var/lib/ase/state.json
# Next run will be treated as first-run: silent import, no Slack pings.
Do this if you misconfigured webhooks and got a partial flood, swapped feeds, or want a clean baseline against a newly-updated composer.lock. The state file contains no secrets -- only CVE IDs, priorities, and timestamps.
Self-monitoring
- Heartbeat:
bin/heartbeat.shalerts via syslog if the last successful run was >24h ago. - Feed health: consecutive failures per feed are tracked; 3+ failures logs ERROR.
- Schema drift: warnings on missing expected fields in API responses.
- Structured logs on stderr: every log record is a JSON object carrying a per-run
run_id(UUIDv4) so you can correlate across Datadog/Loki/CloudWatch. Credentials (Slack webhooks, GitHub tokens, NVD keys, Bearer headers, URL basic-auth) are masked by an in-process redactor before the handler writes.
Requirements
- PHP 8.4+ with
curl,json,mbstring,opensslat runtime (declared incomposer.jsonrequire) - Composer 2.x for installation (
fileinfois needed during install, not at runtime) flock(util-linux) if running under cron
Optional: pdo_sqlite for a future state migration.
Development
git clone https://github.com/infinri/A.S.E.git cd A.S.E composer install composer test # phpunit composer stan # phpstan level 8
Architecture
Deep dive: HANDBOOK.md covers module layout, scoring internals, feed contracts, state file schema, and operational playbook.
Releases
See CHANGELOG.md for release history and upgrade notes. v1.x is in active development.
License
MIT. See LICENSE.
Security
Report vulnerabilities in ASE itself privately. See SECURITY.md.