Software supply-chain and source-code security: dependency scanning, an install-time firewall, and AI-powered source-level scanning ( SAST ) .
UBEL is a zero-dependency, source-available (internal-use-only; see License) application security toolkit. This package (@arcane-spark/ubel-node) ships multiple CLIs for dependency-security and source-level scanner:
- SCA — resolves your dependency tree (and, in full-stack mode, other ecosystems present in the repo) and scans it against OSV.dev and NVD in real time, on every scan — not from a periodically-synced local database — with heuristic reachability analysis, SBOM (CycloneDX v1.6), and SARIF output. Both endpoints can be pointed at internal mirrors via
UBEL_OSV_ENDPOINT/UBEL_NVD_ENDPOINTfor air-gapped deployments (see sca/README.md); the KEV/EPSS exploit-intelligence feeds have no mirror setting and are simply reported as unavailable when unreachable. This is the audit/reporting side —healthmode reads what's already installed. - Exploit intelligence — every vulnerability is checked against CISA's Known Exploited Vulnerabilities catalog and scored with FIRST EPSS (probability of exploitation in the next 30 days). By default the policy blocks on any KEV entry or an EPSS score of 10% or more, whatever the severity (tunable with
--block-kev/--epss-threshold). The fields travel into the JSON, HTML, SBOM and SARIF outputs, and a feed outage degrades gracefully — the values becomenull(unknown, never "not exploited") and the matching rule isn't enforced — rather than aborting the scan. Applies to SCA, the firewall and EASM. See sca/README.md. - Executive summary & suggested fixes — every SCA and EASM report opens with a plain-language executive summary for non-technical readers (risk rating, key findings, recommended actions, methodology; SCA adds the policy verdict), where known-exploited and likely-exploited issues raise the rating and the priority order. Each package also gets per-release-line upgrade suggestions (the fewest, highest versions that clear the most vulnerabilities, branch-aware), listed per component in the summary and carried in the JSON, SBOM and SARIF. See the SCA and EASM READMEs.
- Firewall — a distinct mode of the same CLI (
check/install) that gates the install itself before anything touchesnode_modules,pnpm's store,bun's install path, or Composer'svendor/directory, with atomic lockfile revert on violation and SHA-256 TOCTOU checks between scan and install. The same pull → scan → keep-or-remove pattern also gates Docker images (ubel-docker install <image>) before you run them. Also included in this same package:ubel-pip/ubel-uv/ubel-pipxgatepip/uvinstalls and isolated CLI-tool installs behind a dry-run resolution, andubel-apt/ubel-dnf/ubel-yum(one binary per package manager, same as npm/pnpm/bun) gateapt/dnf/yuminstalls behind each one's own native dry-run — neither has a lockfile to revert, so a rejected scan simply never runs the real install rather than reverting one. - Secrets Detection — built on Trivy's ported, Apache-2.0-attributed secret-scanning ruleset (see NOTICE), extended with UBEL's own rules for vendors Trivy's current upstream doesn't cover (HashiCorp Vault tokens, GCP API keys and OAuth tokens, Anthropic and OpenRouter keys, Stripe restricted keys, Twilio Account/App SIDs, and URL-embedded git credentials, among others). Runs standalone via
ubel-secrets, or as part of any SCA/firewall scan. - License Compliance — every scanned package's declared license (SPDX id, free text like "Apache 2.0", npm's
UNLICENSEDproprietary sentinel, a Python trove classifier, an SPDXOR/ANDexpression, or missing entirely) is normalized and checked against the OSI-approved license list, with a derived risk rating (permissive / weak-copyleft / strong-copyleft / proprietary / unknown). Included in every SCA/firewall scan by default — surfaced per-package in the HTML report, as license properties on every SBOM component, and as a dedicated SARIF run. Runs standalone viaubel-license— inventory + license classification only, no OSV/NVD vulnerability lookups, no secrets scan. - Compliance Framework Mapping — every finding across SCA (vulnerabilities, secrets), SAST (vulnerability and malicious-code findings), and Cloud (misconfigurations) is mapped onto OWASP Top 10, PCI DSS, HIPAA, SOC 2, ISO/IEC 27001, NIST SP 800-53, GDPR, and CIS Controls v8, via one shared mapping engine so the same underlying risk maps identically regardless of which module found it. Included by default in every scan, with a report-level per-framework/per-control finding-count summary, across JSON, HTML, and SARIF (where the module emits SARIF). Best-effort guidance, not a certified compliance assessment — see the per-module docs for the full framework list and the disclaimer carried in every report.
- SAST / Malicious-Code Scanner — a separate module: an LLM-powered pipeline (scan → verify → taint-trace) that reads your actual source code, cross-references a structured CWE-mapped vulnerability catalog, and separately screens for intentionally malicious code (backdoors, C2 beacons, supply-chain implants). It also scans IaC, Docker, and Kubernetes manifest files — each as its own dedicated language family, not lumped together.
- Cloud — scans your AWS, GCP, and Azure accounts directly via each provider's own read-only API (live account state, not static IaC files) for misconfigurations: public storage/database/network exposure, over-permissive IAM and cluster (AKS/GKE) authorization, missing encryption, and disabled audit logging (CloudTrail/GuardDuty/VPC flow logs). Runs via
ubel-cloud. See cloud/README.md. - EASM — passively fingerprints the software exposed on a domain/URL over plain HTTP(S) (server banners, version headers, page markup — no auth, no brute force, no exploitation), checks the result against OSV.dev/NVD/wpvulnerability.net (the same vulnerability-lookup engine the SCA module uses, plus a dedicated WordPress plugin/theme/core lookup), and separately checks every host for a fixed set of common misconfigurations: exposed
.env/.git, WordPressxmlrpc.php/user enumeration, TLS/certificate weaknesses, missing/weak security headers and cookie flags, risky HTTP methods (TRACE/PUT/DELETE), CORS misconfiguration, and SPF/DMARC/DKIM email-authentication gaps. Runs viaubel-urlagainst hosts you already know,ubel-domainto discover a domain's subdomains from Certificate Transparency logs (crt.sh) first — no DNS brute-forcing, no wordlists — and scan all of them in one run,ubel-hostto connect-scan every port on one host and fingerprint whatever answers HTTP(S), orubel-easmto combine both — discover a domain's subdomains, resolve them to distinct IPs, port-scan each, and fingerprint the combined result. Authorized use only — see easm/README.md.
UBEL has no telemetry and no UBEL-operated backend. The dependency-scanning CLIs send only package identifiers (PURLs / CPE names) to OSV.dev and NVD, both mirror-configurable via UBEL_OSV_ENDPOINT / UBEL_NVD_ENDPOINT. For exploit intelligence they also send the CVE ids of whatever vulnerabilities they find to FIRST.org's EPSS API and download CISA's public KEV catalog (nothing is sent for that one); neither of those two has an endpoint override, and if they can't be reached the scan still completes with those fields marked unknown. Everything else runs on your own infrastructure. SAST is the exception to "fully local": the code chunks ubel-sast / ubel-mal analyze are sent to the LLM provider you configure (OpenRouter by default, and its API key is the only credential UBEL needs) — point it at a local or self-hosted model endpoint if source code must not leave your network. The EASM and cloud scanners contact only the targets and cloud APIs you point them at, plus the public data sources documented in their READMEs (for example crt.sh and wpvulnerability.net).
npm install -g @arcane-spark/ubel-nodeThis installs the binaries for the SCA/firewall CLI, the SAST module, the cloud scanner, and the EASM scanner:
| Binary | Covers | What it does |
|---|---|---|
ubel-npm / ubel-pnpm / ubel-bun |
SCA + Firewall | Same binary, mode-dependent: health = SCA scan of installed deps; check/install = firewall gate on a lockfile dry-run |
ubel-composer |
SCA + Firewall | health = SCA scan of vendor//composer.lock; check/install = firewall gate on a composer require/update --no-install --no-scripts dry-run, same lockfile-backed shape as npm/pnpm/bun |
ubel-pip / ubel-pipx |
SCA + Firewall | health = SCA scan of a venv's installed packages; check/install = firewall gate on a pip install --dry-run resolution. ubel-pipx additionally installs CLI tools into isolated, managed per-tool venvs with a global shim, reducing blast radius the way pipx itself does |
ubel-uv |
SCA + Firewall | Same health/check/install split as ubel-pip, but targeting a uv-native venv (uv init --bare + uv venv, not a stdlib one) — the real install always still runs as uv pip install -r <generated, exact-pinned file>, same as pip; dry-run uses uv pip install --dry-run internally, which (unlike pip's JSON report) yields no dependency-graph data — see the Python section below. A real install on either engine also syncs any existing requirements.txt/pyproject.toml to the now-installed versions |
ubel-apt / ubel-dnf / ubel-yum |
SCA + Firewall | Same health/check/install split, one binary per native package manager (no auto-detection between them, same as npm/pnpm/bun). Reports and policy live under ~/.ubel/local so routine use never needs sudo — only the real package-manager install does |
ubel-docker |
SCA + Firewall | Scans a container image without running it; install mode pulls, scans, and removes the image on a policy violation |
ubel-secrets |
Secrets | Standalone secrets-only scan of the target directory — no dependency resolution, no LLM calls |
ubel-license |
SCA | Standalone inventory + license-compliance scan — no vulnerability lookups (OSV/NVD), no secrets scan |
ubel-agent |
SCA | AI-agent workspace scan (OS, runtimes, tools, dependencies) |
ubel-cicd |
SCA | Post-install CI/CD scan of the final built workspace (OS, runtimes, tools, dependencies) |
ubel-platform |
SCA | Host platform scan (OS, runtimes, tools) |
ubel-sast |
SAST | Static analysis for accidental vulnerabilities (injection, XSS, insecure deserialization, hardcoded secrets, …) |
ubel-mal |
SAST | Malicious-code scan for intentional backdoors, C2 implants, exfiltration, persistence |
ubel-chunk |
SAST | Utility with no LLM cost to preview how a codebase will be chunked |
ubel-cloud |
Cloud | Live AWS/GCP/Azure account scan via each provider's own API for misconfigurations — public exposure, IAM, encryption, audit logging — not a static IaC/manifest scan |
ubel-url |
EASM | Passive HTTP(S) fingerprinting of a domain/URL + OSV/NVD/wpvulnerability.net vulnerability lookup and misconfiguration checks on what it finds — authorized use only, against infrastructure you own |
ubel-domain |
EASM | Same as ubel-url, but discovers its own target list first — enumerates a domain's subdomains via Certificate Transparency logs (crt.sh), then runs the same fingerprinting/vulnerability/misconfiguration scan against all of them — authorized use only, against infrastructure you own |
ubel-host |
EASM | Connect-scans every port in a range (default 1-30000) on one host, probes whatever accepts a connection for HTTP(S), then runs the same fingerprinting/vulnerability/misconfiguration scan ubel-url does against whatever answered — authorized use only, against infrastructure you own |
ubel-easm |
EASM | Combines ubel-domain's discovery with ubel-host's port sweep — discovers a domain's subdomains, resolves them to distinct IPs, port-scans every IP, and fingerprints the merged IP:port targets plus the subdomains themselves by name — the most invasive EASM entry point; see the "Shared IPs" warning in easm/README.md — authorized use only, against infrastructure you own |
ubel-pip/ubel-uv/ubel-pipx additionally need a python3/python interpreter on PATH (to create their venv); Node.js itself can't provision one. ubel-uv additionally needs the uv binary itself on PATH, separate from Python — same one-binary-per-tool requirement as ubel-pnpm needing pnpm. ubel-composer additionally needs the composer binary itself on PATH, same one-binary-per-tool requirement.
Node.js >=18.0.0 required.
Resolves dependencies (with PURL generation), scans them against OSV.dev and NVD, and annotates each finding with a heuristic reachability verdict (package type, scope, dependency depth, attack vector, and optional source-level import-scan confirmation). Malicious-package advisories (MAL-*) are always flagged. Output: JSON, HTML, CycloneDX v1.6 SBOM, and SARIF 2.1.0 reports.
# Audit the currently installed dependency graph — no install, no lockfile mutation
ubel-npm health
ubel-pnpm health
ubel-bun health
ubel-composer healthhealth mode also supports full-stack monorepo scanning (Python, PHP, Rust, Go, .NET, Java, Ruby, Swift, Flutter/Dart alongside Node) and host/platform scanning (Linux package managers, Windows registry) when invoked programmatically.
yarn is supported in health mode only — it can't do a lockfile-only dry-run, so it has no firewall coverage below.
A distinct mode of the same ubel-npm / ubel-pnpm / ubel-bun binaries: before any real install, a lockfile-only dry-run (--package-lock-only / --lockfile-only) resolves the candidate tree without touching node_modules, scans it, and either proceeds or reverts the lockfile from its on-disk backup. Pre/post-install scripts are always blocked (--ignore-scripts) during this phase. A SHA-256 check re-verifies the lockfile and package.json immediately before the real install, closing the TOCTOU window between scan and install.
The same block-before-you-touch-it pattern applies to ubel-docker: install mode pulls the image, extracts its filesystem without ever running it (docker create + in-process tar extraction, no shell tar, no ENTRYPOINT/CMD execution), scans it, and removes the image again if policy blocks it.
ubel-composer extends the same lockfile-backed pattern npm/pnpm/bun use to PHP: composer require/update --no-install --no-scripts (Composer's own equivalent of --package-lock-only) resolves the candidate tree and writes a candidate composer.lock/composer.json without touching vendor/, UBEL scans that candidate, and either proceeds via composer install --no-scripts or reverts both files from their on-disk backup — the same SHA-256 TOCTOU check and atomic revert as npm/pnpm/bun, just against Composer's own files. Unlike pip's dry-run below, this has no sdist-style caveat: resolving a Composer dependency graph never runs a package's own code, since build/lifecycle scripts only fire on a real composer install/update, which is exactly why --no-scripts covers both the dry-run and the real install.
ubel-pip/ubel-uv/ubel-pipx extend the same before-you-touch-it approach to Python: pip install --dry-run --report (pip) or uv pip install --dry-run (uv) resolves the candidate set — including transitive dependencies — without installing anything, UBEL scans that resolution, and only then runs the real install (pip install -r / uv pip install -r against the same generated, exact-pinned file either way). A successful real install additionally syncs any requirements.txt/pyproject.toml already sitting in the project directory to the versions that actually got installed — see the Python section further down for exactly what that does and doesn't touch. There's no lockfile here, so there's nothing to revert — a blocked scan just means the real install never runs. One caveat worth being upfront about, and it applies to both installers equally since it's inherent to how Python packaging resolution works: resolving a package's metadata can still need to build an sdist when no pre-built wheel is available, and building an sdist can execute arbitrary setup.py/build-backend code — a wheel-only install has no such gap, but a source-only package does carry it. ubel-apt/ubel-dnf/ubel-yum mirror this for Linux host packages — three separate binaries, each bound to exactly one package manager's own native dry-run (apt-get -s, dnf --assumeno, yum --assumeno respectively, no auto-detection between them); their reports and policy live under ~/.ubel/local so ordinary use never needs sudo — only the real install does.
# examples
# Dry-run only — scan and exit, nothing installed
ubel-npm check lodash express
# Scan-gated real install — proceeds only if policy allows
ubel-npm install lodash@4.17.21
# install current project
ubel-pnpm install
# check the current project
ubel-bun check
# pull, scan, and keep or remove a Docker image based on policy
ubel-docker install node:20-alpine
# PHP: dry-run scan, then a scan-gated real install
ubel-composer check monolog/monolog
ubel-composer install monolog/monolog:^3.0
ubel-composer install # no args → resolves from existing composer.lock
# Python: dry-run scan, then a scan-gated real install into ./venv
ubel-pip check requests==2.31.0
ubel-pip install requests==2.31.0
ubel-pip install # no args → falls back to ./requirements.txt, then ./pyproject.toml
# Same, driven by uv instead of pip
ubel-uv check requests==2.31.0
ubel-uv install requests==2.31.0
# Python CLI tool: scan-gated install into an isolated venv + global shim
ubel-pipx install black
# Linux packages: dry-run scan, then a scan-gated `sudo apt install`
ubel-apt check curl
ubel-apt install curl
# (ubel-dnf / ubel-yum work the same way, against dnf/yum instead)Policy (severity threshold, unknown-severity blocking) is configurable via ubel-npm threshold <level> and ubel-npm block-unknown <bool> (same subcommands under ubel-composer/ubel-pip/ubel-uv/ubel-pipx/ubel-apt/ubel-dnf/ubel-yum); malicious-package advisories are always blocked regardless of policy. License-risk policy (license-risk, license-block-unknown) is npm-family-only (npm/pnpm/bun/composer) — it isn't exposed as a subcommand on ubel-pip/ubel-uv/ubel-pipx/ubel-apt/ubel-dnf/ubel-yum, matching those CLIs' narrower mode set.
Exit codes: check and install exit 0 if policy passes, 1 if policy blocks or the scan itself fails — including when a vulnerability lookup against OSV or NVD can't be completed (network error, rate limit, outage, or a malformed response). A failed or incomplete scan is never treated as a pass: for install, nothing is installed and the lockfile is restored from backup.
Full documentation — every mode, policy config, reachability decision ladder, and programmatic API: sca/README.md
ubel-agent, ubel-cicd, and ubel-platform wrap the same health-mode scan engine as ubel-npm health, each with a fixed option set for one deployment context — no <engine> <mode> arguments, just an optional target path. All three print the full JSON report to stdout and exit 1 on a policy block.
# AI-agent sandbox workspace — OS packages + every app ecosystem present
ubel-agent /path/to/agent/workspace
# Post-build CI/CD scan of the final built workspace
ubel-cicd /path/to/build/output
# Host/developer-machine scan — OS packages and dev tools/runtimes only,
# no application dependency resolution. Defaults to the home directory.
ubel-platformFull documentation — exact flags per binary and how they differ from ubel-secrets/ubel-license:
sca/README.md#fixed-configuration-scan-clis
Built on Trivy's ported secret-scanning ruleset (Apache-2.0, attributed in sca/vendor/trivy/NOTICE), extended with rules for vendors not yet covered by Trivy's current upstream ruleset: HashiCorp Vault tokens, Google Cloud API keys and OAuth tokens, Anthropic and OpenRouter API keys, Firebase tokens, Stripe restricted keys, Twilio Account/App SIDs, Square and Braintree credentials, and URL-embedded git credentials. Match previews in every report are redacted — the raw secret value is never written to disk.
# Standalone secrets-only scan — no dependency resolution, no LLM calls
ubel-secrets /path/to/projectSecrets findings are also included in every SCA/firewall scan by default, surfaced in a dedicated tab in the HTML report and as a schema-correct extension on both the SBOM (a ubel:secrets property, since CycloneDX's root schema doesn't permit arbitrary top-level keys) and the SARIF output (its own run, separate from the dependency-vulnerability run).
Every scanned package's declared license — whatever form it arrives in (an SPDX id, free text like "Apache 2.0", npm's UNLICENSED proprietary sentinel, a Python trove classifier, an OR/AND SPDX expression, or missing entirely) — is normalized into a canonical SPDX identifier, checked against the OSI-approved license list, and assigned a risk rating (low / medium / high / unknown) based on license category (permissive, weak-copyleft, strong-copyleft, proprietary, public-domain, unrecognized). Included in every SCA/firewall scan by default.
# Standalone inventory + license scan — resolves dependencies (full-stack,
# every ecosystem present in the repo) and classifies licenses only; no
# OSV/NVD calls, no secrets scan. Same JSON, HTML, CycloneDX SBOM, and
# SARIF 2.1.0 outputs as any other SCA scan.
ubel-license /path/to/projectSurfaced per-package in the HTML report's inventory table and detail view, as license.osi_approved / license.risk / license.category properties on every SBOM component, and as its own SARIF run (ubel-license-compliance) that flags any non-OSI-approved or high-risk license as a finding.
Full documentation: sca/README.md#license-compliance
Every finding produced anywhere in UBEL — SCA vulnerabilities and secrets, SAST vulnerability and malicious-code findings, and Cloud misconfigurations — is mapped onto industry compliance/security frameworks by default, no separate flag needed. All three modules share one mapping engine (sca/compliance_mappings.js): a finding is assigned one or more internal risk categories (e.g. injection, public_exposure, vulnerable_components), and each category carries a fixed list of framework control references, so the same underlying risk maps identically whether it was found by the dependency scanner, the AI SAST pipeline, or the cloud scanner.
Frameworks: OWASP Top 10 (2021), PCI DSS v4.0, HIPAA Security Rule, SOC 2 (Trust Services Criteria), ISO/IEC 27001:2022 (Annex A), NIST SP 800-53 Rev. 5, GDPR, and CIS Controls v8.
Every finding gets a compliance object (categories + per-framework control list); every report gets a top-level compliance_summary aggregating all findings into per-framework/per-control finding counts. Surfaced in a dedicated Compliance tab in every module's HTML report, in the JSON report's compliance/compliance_summary fields, and — for SCA and SAST, which emit SARIF — as compliance/compliance_categories/compliance_frameworks properties on SARIF rules and results.
This is best-effort guidance, not a certified compliance assessment — control identifiers are the stable, publicly documented ones for each framework, but framework text, versioning, and applicable scope can change and depend on your own environment. Every report carries this disclaimer verbatim in compliance_summary.disclaimer.
Full documentation: sca/README.md#compliance-framework-mapping (canonical framework/category reference) · sast/README.md#compliance-framework-mapping · cloud/README.md#compliance-framework-mapping
Chunks your codebase into semantically-bounded units (15 language families — 12 source-code languages (including Dart/Flutter and Swift) plus Docker, IaC, and Kubernetes manifests as their own dedicated families) and runs a three-pass LLM pipeline — scan → verify → taint trace — cross-referenced against a 59-class CWE-mapped vulnerability catalog. A fully separate 15-class malicious-code catalog covers intentionally planted backdoors and implants; that scan (ubel-mal) stops after scan → verify, since reachability isn't the relevant question for code that's itself the payload. Outputs JSON, interactive HTML, and SARIF 2.1.0 reports, ready for CI/CD gating.
# Vulnerability scan
ubel-sast /path/to/project
# Malicious-code / backdoor scan
ubel-mal /path/to/project
# Free preview of how a codebase will be chunked, no LLM calls
ubel-chunk /path/to/projectSupports OpenRouter, OpenAI, Anthropic, Gemini, DeepSeek, NVIDIA, local/Docker-hosted (Ollama-compatible), and fully custom endpoints — selectable per run, no code changes.
Exit codes: governed by --fail-on, which only changes the process exit code — reports on disk always contain every finding regardless of this flag.
ubel-sast(analyze):any(default) fails on any finding, including unresolved ones;validfails only on findings verifiedis_valid: true;exploitablefails only on findings taint-tracedexploitable: true.ubel-mal(malware):any(default) fails on any finding, including unresolved ones;confirmedfails only on findings verifiedis_valid: true— an unresolved finding still fails the build, since "couldn't determine" is never treated as clean.
Full documentation — pipeline mechanics, every flag, token-cost breakdown, and CI examples: sast/README.md
Scans your AWS, GCP, and Azure accounts directly via each provider's own read-only API — live account state, not a static IaC/manifest scan, and nothing is deployed or modified. Covers public storage/database/network exposure (S3/GCS/Storage buckets, RDS/Cloud SQL/Azure SQL, security groups/NSGs/firewall rules open to the internet), IAM posture (overly permissive policies, missing MFA, stale access keys, permissive trust policies), Kubernetes cluster authorization (GKE/AKS control-plane exposure, RBAC/ABAC, legacy auth), missing encryption (EBS, RDS, CloudTrail, storage accounts), and disabled audit logging (CloudTrail, GuardDuty, VPC flow logs). Credentials are only ever used for that run — nothing stored or reused, same model as SAST's LLM credentials.
# Scan whichever providers you have credentials for (default: aws,gcp,azure)
ubel-cloud
# Scan a specific provider only
ubel-cloud --provider aws
# Explicit AWS region override — skips DescribeRegions auto-discovery
ubel-cloud --provider aws --regions us-east-1,eu-west-1Outputs JSON and HTML (no SARIF, no SBOM — this isn't a dependency scan). Exit codes: governed by --fail-on (default critical), same severity-threshold/count syntax as the rest of UBEL — reports on disk always contain every finding regardless of this flag.
Full documentation — every check, per-provider credential setup, and all flags: cloud/README.md
⚠ Authorized use only.
ubel-urlsends live, unauthenticated requests to every target you give it and discloses what it fingerprints to OSV.dev/NVD/ wpvulnerability.net.ubel-domaindoes the same, but discovers its own target list from Certificate Transparency logs first — so it can end up scanning hosts you didn't explicitly name.ubel-hostgoes further: it connect-scans every port in a range (1-30000 by default) on one host and fingerprints whatever answers HTTP(S) — a live port sweep, not just a fingerprint request.ubel-easmcombines both — it discovers a domain's subdomains, resolves each to an IP, then runsubel-host's full port sweep against every distinct IP behind the domain, the most invasive of the four and the most likely to reach infrastructure you didn't intend to touch (shared hosting, a CDN edge IP — see the "Shared IPs" note ineasm/README.md). Only ever run any of these four against infrastructure you own or have explicit, documented authorization to test — the same own-infrastructure-only rule every other UBEL module already holds you to. See easm/README.md for the full notice.
Passively fingerprints the software exposed on a domain/URL over plain HTTP(S) — server banners, version headers, page markup, a small set of well-known paths — turns what it finds into candidate CPE identifiers, and feeds those through the exact same OSV.dev/NVD/wpvulnerability.net vulnerability-lookup engine the SCA module uses (wpvulnerability.net for WordPress plugins/themes/core specifically, which NVD's CPE dictionary mostly misses). No authentication, no brute forcing, no exploitation — just unauthenticated GETs and a lookup against public vulnerability data.
ubel-domain runs the identical scan, with the target list discovered for
you first:
domain → crt.sh subdomain discovery → fingerprint every host
→ group techs by name+version (with the host list for each)
→ vulnerability lookup + misconfiguration checks → one report
Give it a root domain and it enumerates every subdomain a public CA has
logged a certificate for (via crt.sh) — passive discovery
only, no DNS brute-forcing, no zone-transfer attempts — then fingerprints
all of them in one run through the same engine ubel-url uses, so a
technology found on forty subdomains is one inventory row with a forty-host
list, not forty near-identical rows. --list-only prints that discovered
list without sending a single request to any of those hosts, so you can
review and trim it (--exclude, or add hosts crt.sh missed with
--include) before authorizing the real scan.
ubel-host adds a step before fingerprinting instead of before discovery
— give it one host, and it connect-scans every port in --ports (default
1-30000), probes whatever accepts a connection for HTTP(S), and hands only
the HTTP(S)-speaking ports to the same fingerprint/lookup engine ubel-url
uses, so you don't need to already know which port a target's web app or
admin panel lives on. This is genuinely active reconnaissance, not passive
fingerprinting — see the warning above.
ubel-easm is ubel-domain's discovery and ubel-host's port sweep
together across a whole domain: crt.sh discovery → resolve every subdomain
to an IP → collapse to the distinct IPs actually behind the domain
(several subdomains commonly share one) → port-scan and HTTP(S)-probe each
of those IPs → fingerprint the merged IP:port targets and the
discovered subdomains by name (a bare-IP request has no Host header/SNI, so
it can't see a name-based virtual host; controlled by --subdomain-ports).
A component seen on five IPs behind the domain is still one inventory item,
the same name+version dedup ubel-domain gets across subdomains.
Every host is also checked, automatically, for a fixed set of common
misconfigurations, independent of the CVE lookup above: an exposed
.env/.git, exposed phpinfo() output, TLS/certificate weaknesses
(expiry, trust, hostname mismatch, weak/legacy protocol and cipher support),
missing/weak security headers and cookie flags (HSTS, CSP, clickjacking
protection, X-Content-Type-Options, Referrer-Policy,
Permissions-Policy, Secure/HttpOnly/SameSite on any Set-Cookie),
risky HTTP methods (PUT/DELETE/TRACE/CONNECT advertised, plus an
actual Cross-Site Tracing probe), CORS misconfiguration (arbitrary-Origin
reflection, a wildcard paired with credentials, the null Origin), SPF/
DMARC/DKIM email-authentication gaps, and — on hosts already identified as
WordPress — a reachable xmlrpc.php and unauthenticated user enumeration.
This runs the same way across all four entry points.
It's a deliberate subset of an SCA report: no license compliance (no
manifest to read one off of), no dependency sequences/graph (nothing here
is resolved from a lockfile), no reachability analysis (no source code to
trace), and no SBOM/SARIF — JSON and HTML only. CVSS scoring, fix-version
recommendations (including per-component suggested fixes), CISA KEV / FIRST
EPSS exploit intelligence (which also gates the exit code), an executive
summary, and the same compliance framework mapping every other module uses
are all still included. Every detected component is reported
with scopes: ["prod"] — anything fingerprinted over the network is, by
definition, already running.
# One domain, safety guard on by default (skips private/self-IP targets)
ubel-url example.com
# Several targets in one run, one report
ubel-url a.example.com b.example.com --fail-on high
# A lab/localhost target you own
ubel-url localhost:8080 --allow-private
# See what a domain-wide sweep would touch, without touching anything
ubel-domain your-company.example --list-only
# Discover every subdomain of a domain you own, then scan them all
ubel-domain your-company.example --fail-on high
# See what ports are open on a host you own, without fingerprinting anything
ubel-host app.your-company.example --list-only
# Port-scan and fingerprint a host you own, well-known ports only
ubel-host app.your-company.example --ports 1-1024 --fail-on high
# Review the IP grouping a combined sweep would touch, before authorizing it
ubel-easm your-company.example --list-only
# Combined discovery + per-IP port sweep of a domain you own
ubel-easm your-company.example --fail-on highOutputs JSON and HTML only. Exit codes: governed by --fail-on (default
critical), same severity-threshold/count syntax as the rest of UBEL —
reports on disk always contain every finding (vulnerabilities and
misconfigurations alike) regardless of this flag; note that --fail-on
itself currently gates on vulnerabilities/infections only, not on
misconfiguration findings.
Full documentation — the responsible-use notice, safety guard, every
ubel-host/ubel-easm flag, the "Shared IPs" warning, and the full
misconfiguration-check list:
easm/README.md
All binaries exit non-zero on findings that clear their respective gate, so any of them fit natively into a CI runner.
action.yml wraps npx @arcane-spark/ubel-node@<version> as a composite action, behind an explicit command allow-list validated against UBEL's actual registered package.json bin names — so it can never construct or execute a binary name that isn't one of ours, regardless of what a caller passes. command, version, and args are step inputs referenced as shell variables ($COMMAND/$ARGS), never interpolated directly into the script with ${{ }}, closing off GitHub's documented script-injection risk for composite actions.
- uses: AlaBouali/ubel@<commit-sha> # pin to a commit SHA, not a mutable tag
with:
command: npm
version: 0.21.0
args: check
- uses: AlaBouali/ubel@<commit-sha>
with:
command: npm # `command` selects the bin; the mode (check/install/health) goes in `args`
version: 0.21.0
args: install
- uses: AlaBouali/ubel@<commit-sha>
with:
command: sast
args: --fail-on exploitable
- uses: AlaBouali/ubel@<commit-sha>
with:
command: mal
args: --fail-on confirmed
- uses: AlaBouali/ubel@<commit-sha>
with:
command: license # inventory + license compliance only — no OSV/NVD calls, no secrets scan
- uses: AlaBouali/ubel@<commit-sha>
with:
command: cloud
args: --provider aws,gcp,azure --fail-on high
- uses: AlaBouali/ubel@<commit-sha>
with:
command: url # EASM — only ever point `args` at infrastructure you own, see easm/README.md
args: staging.your-own-domain.example --fail-on high
- uses: AlaBouali/ubel@<commit-sha>
with:
command: pip
args: install # scan-gated `pip install`, resolved from ./requirements.txt
- uses: AlaBouali/ubel@<commit-sha>
with:
command: composer
args: install # scan-gated `composer install --no-scripts`, resolved from composer.lock
- uses: AlaBouali/ubel@<commit-sha>
with:
command: apt # dnf/yum work the same way, as their own `command` values
args: check curlcommand must be one of: sast, mal, chunk, cicd, agent, platform, secrets, license, cloud, url, domain, host, easm, npm, pnpm, bun, yarn, composer, docker, pip, pipx, uv, apt, dnf, yum — anything else fails the step before npx ever runs. domain, host, and easm are in the allow-list too, but they actively probe whatever target you pass them: only point them at assets your organization owns, and think twice before running easm unattended (it expands a domain into every subdomain IP, which can include shared CDN/hosting addresses you don't own — see the comments in action.yml).
Equivalent, for self-hosted runners, non-GitHub CI, or a Dockerfile:
# GitHub Actions
- name: UBEL dependency scan (SCA)
run: ubel-npm check
- name: UBEL firewall-gated install
run: ubel-npm install
- name: UBEL PHP firewall-gated install
run: ubel-composer install
- name: UBEL SAST scan
run: ubel-sast --fail-on exploitable
- name: UBEL malicious-code scan
run: ubel-mal --fail-on confirmed
- name: UBEL license compliance scan
run: ubel-license .
- name: UBEL cloud misconfiguration scan
run: ubel-cloud --fail-on high
- name: UBEL external attack surface scan (only against infra you own — see easm/README.md)
run: ubel-url staging.your-own-domain.example --fail-on high
- name: UBEL domain-wide attack surface sweep (discovers subdomains first — only against a domain you own)
run: ubel-domain your-own-domain.example --fail-on high
- name: UBEL port scan + fingerprint (connect-scans 1-1024, only against a host you own)
run: ubel-host staging.your-own-domain.example --ports 1-1024 --fail-on high
- name: UBEL combined discovery + per-IP port sweep (most invasive EASM entry point — only against a domain you own, review with --list-only first)
run: ubel-easm your-own-domain.example --fail-on high# Dockerfile
RUN ubel-npm install
RUN ubel-composer install
RUN ubel-sast --fail-on valid .This document details exactly what UBEL does — and doesn't do — for every ecosystem it supports, across five capability axes:
- SCA — dependency inventory + vulnerability matching (OSV/NVD)
- Firewall — pre-install/pre-deploy blocking, not just after-the-fact reporting
- SAST — LLM-driven source/config vulnerability scanning
- Malware SAST — LLM-driven detection of intentionally malicious code
- Secrets — hardcoded credential detection
- License compliance — SPDX normalization + OSI/risk classification
- Compliant outputs — SARIF, CycloneDX SBOM, JSON, HTML
A quick note before the detail: Firewall and SCA are not the same capability
everywhere. SCA (health scanning) works on anything UBEL can resolve a
dependency tree for. Firewall (blocking a bad install before it lands)
only exists where a package manager supports some form of dry-run
resolution with no (or, for pip/uv, no typical) side effects — today
that's npm, pnpm, bun, and Docker images via a true lockfile-only
dry-run, PHP (Composer) via the same lockfile-backed mechanism
(composer require/update --no-install --no-scripts), plus pip/uv/pipx
(pip install --dry-run / uv pip install --dry-run) and Linux host
packages (apt/dnf/yum's own native dry-run). Ruby, Rust, Go,
Java/Kotlin, C#, and Windows remain SCA-covered but not firewall-covered
below — their package managers genuinely have no dry-run-without-side-effects
equivalent to gate against, which is a mechanical constraint of each
ecosystem's tooling, not an oversight. Swift (SwiftPM/Carthage) and
Flutter/Dart (pub) are SCA-only today as well — see their sections
below.
| Ecosystem | SCA | Firewall | SAST | Malware SAST | Reachability | License Compliance | Secrets |
|---|---|---|---|---|---|---|---|
| Node.js (npm/pnpm/bun) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Node.js (yarn) | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Python (pip/uv/pipx/venv) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| PHP (Composer) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Ruby (Bundler) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Rust (Cargo) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Go (modules) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Java / Kotlin (Maven) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| C# / .NET (NuGet) | ✅ | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
| Swift (SwiftPM / Carthage) | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ |
| Flutter / Dart (pub) | ✅ | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ |
| C/C++ | ❌ | ❌ | ✅ | ✅ | ❌ | ❌ | ✅ |
| Docker images | ✅ (OS + app deps) | ✅ | — | — | — | ✅ | ✅ (in image) |
| Kubernetes manifests | — | — | ✅ (misconfig) | — | — | — | ✅ |
| Terraform / CloudFormation (IaC) | — | — | ✅ (misconfig) | — | — | — | ✅ |
| Linux host (apt/dnf/yum) | ✅ | ✅ | — | — | — | ✅ | — |
| Windows host | ✅ | ❌ | — | — | — | ✅ | — |
| VS Code / Cursor / VSCodium extensions | ✅ | — | — | — | — | — | — |
✅ = built and shipped ·
Cloud account misconfiguration scanning (AWS/GCP/Azure, via ubel-cloud) isn't tied to a dependency ecosystem, so it doesn't have a row here — see the Cloud section above.
SCA: Full lockfile parsing across npm v1/v2/v3, pnpm v5/v6/v9, yarn
classic and berry, and bun v0/v1. Dependency resolution walks the actual
installed node_modules tree in addition to the lockfile, so it reflects
what's really on disk, not just what the manifest declares. Scope
assignment (production / dev / environment) is computed via BFS
propagation from the manifest's declared dependency types down through the
full tree.
Firewall: One of the ecosystems (alongside Docker and PHP/Composer) with a lockfile-backed
pre-install gate — atomic revert and TOCTOU hashing depend on there being a
lockfile to revert to, which is specific to npm/pnpm/bun/Docker/Composer's
mechanics. npm, pnpm, and bun each support a lockfile-only dry run
(--package-lock-only, --lockfile-only, and a node_modules-untouched
equivalent respectively) — UBEL resolves what would be installed, scans
it against policy, and only proceeds if it's clean. TOCTOU is closed with
SHA-256 integrity checks on the lockfile and package.json before and after
resolution, and any policy-blocked change is rolled back atomically via
revert_lock_to_original. Yarn is deliberately excluded from firewalling
— not because UBEL doesn't support it, but because yarn itself has no
side-effect-free dry-run equivalent to hook into: yarn add always writes
to node_modules before you'd get a chance to block it. Yarn projects still
get full SCA, SAST, secrets, and license coverage — they just can't be
gated pre-install the way npm/pnpm/bun can, because yarn's own CLI doesn't
offer a resolution step that stops short of writing to disk. (Python and
Linux host packages also get a real pre-install gate now — see their
sections below — just via a simpler revert-less mechanism, since neither
has a lockfile to roll back.)
SAST / Malware SAST: Full three-pass (scan → verify → taint-trace) vulnerability pipeline and two-pass malware pipeline, JS/TS-aware chunking.
Reachability analysis: Full import-graph reachability — a vulnerable package is downgraded in priority if nothing in your code path actually imports the vulnerable module, and orphaned/unused dependencies get flagged separately. This is one of ten ecosystems with this capability (see the Reachability Analysis note under Cross-Cutting Capabilities).
Editor integration: The VS Code/Cursor/VSCodium extension runs in-process (no shell-out), with dedicated commands for project scan, host scan, and — uniquely for this ecosystem — a scan of the editor's own installed extensions, since a malicious VS Code extension is itself a supply-chain vector most tools never consider.
SCA: Resolves dependencies by walking virtual environment directories
directly (not just parsing requirements.txt), so it reflects the actual
installed environment, including transitive packages a manifest wouldn't
show on its own.
Firewall: Available via ubel-pip/ubel-uv/ubel-pipx — two
installers, same firewall shape, different dry-run mechanics under the
hood. ubel-pip uses pip's own install --dry-run --report (pip ≥22.2)
to resolve the candidate set into a machine-readable report without
installing anything; ubel-uv uses uv pip install --dry-run instead (uv
has no equivalent JSON report — its output is a flat + name==version
list on stderr, see the honesty note below for what that costs). UBEL
scans whichever resolution came back, and only then runs the real install
— pip install -r or uv pip install -r, always against the same
generated, exact-pinned requirements file either way. There's no lockfile
here, so there's no revert step the way npm/pnpm/bun have one: a
policy-blocked scan just means the real install never runs, nothing needs
rolling back. ubel-pip/ubel-uv check/install accept packages on the
command line, or fall back to ./requirements.txt, then
./pyproject.toml's [project] dependencies, when none are given. A
successful real (non-dry-run) install — either installer — also syncs
whichever of those two files already exists in the project directory to
match what's now actually installed: it's a re-run of the same scan
health mode already does, not a separate pip freeze/uv pip freeze.
Existing entries get their pinned version refreshed to the installed
version; anything newly installed but not yet listed gets appended; lines
for packages that aren't actually installed (comments, -r/-e/-c
directives, a spec that failed to resolve) are left alone. Neither file is
created if it doesn't already exist, and a sync failure is logged rather
than failing the install — the install itself already succeeded by that
point. ubel-pipx installs CLI tools into isolated, managed per-tool
virtual environments with a global shim on PATH, the same blast-radius
reduction pipx itself provides — now gated by the same pre-install scan;
there's no uv tool install-equivalent CLI isolation mode here, only
pip's. One more uv-specific difference: ubel-uv init (and the first
check/install that needs a venv) provisions it via uv's own uv init --bare + uv venv rather than the stdlib venv module the other five
engines use — a real uv-recognized project (pyproject.toml present),
not just an interpreter uv happens to be pointed at via --python.
Two honesty notes, both of them limits of the underlying tools rather than gaps in UBEL's own implementation:
- Unlike npm's lockfile dry-run, neither pip's nor uv's dry-run is
unconditionally side-effect-free — if a candidate package has no
pre-built wheel available, resolving its metadata can require building an
sdist, and building an sdist can execute arbitrary
setup.py/build-backend code. This is inherent to Python packaging resolution generally — howpip/uvthemselves resolve source-only packages — not something a scan step layered on top of either tool could close off. Wheel-only installs (the common case) don't have this gap. uv-sourced scans carry less detail thanpip-sourced ones in one respect, and it traces back to whatuv pip install --dry-runitself exposes rather than a choice UBEL made: its output is a flat+ name==versionlist rather than a dependency-annotated report, so it carries no parent/child relationships — every uv-resolved component comes back as its own root with emptyintroduced_by/parents/dependency_sequences, unlike a pip-sourced scan (pip's--dry-run --reportincludes that provenance; uv's dry-run output simply doesn't). Vulnerability scanning itself is unaffected — that's purl-based, not graph-based — but dependency-provenance detail specifically is weaker foruvthan forpiptoday. License data isn't part of this gap: license classification only ever runs onhealth-mode scans for every ecosystem UBEL supports, not just Python, so it's not something acheck/installdry-run needs from any installer.
SAST / Malware SAST: Full coverage, same three-pass/two-pass pipelines.
Reachability analysis: Fully covered, tracking .py files against the
resolved dependency graph — one of ten ecosystems with this capability.
SCA: Resolves the dependency tree from vendor/ and composer.lock.
Firewall: Available via ubel-composer — the same lockfile-backed shape
as npm/pnpm/bun, not the simpler revert-less mechanism pip/apt/dnf/yum use.
composer require/update --no-install --no-scripts (Composer's own
equivalent of --package-lock-only) resolves the candidate dependency tree
and writes a candidate composer.lock/composer.json without touching
vendor/; UBEL scans that candidate, and only then runs the real install —
composer install --no-scripts. A policy-blocked scan reverts
composer.json/composer.lock to their pre-scan state from an on-disk
backup, atomically, the same revert_lock_to_original path npm/pnpm/bun
use. TOCTOU is closed the same way too: SHA-256 checks on both files
immediately before the real install. Unlike pip's dry-run, there's no
sdist-style caveat here — resolving a Composer dependency graph never runs
a package's own code, since Composer's lifecycle scripts only fire on an
actual install/update, which is exactly why --no-scripts covers both
the dry-run and the real install.
SAST / Malware SAST: Full coverage.
Reachability analysis: Fully covered — .php files are scanned for
use/require/include references against the resolved dependency
graph, same signal set (depth, scope, attack vector, import confirmation)
as every other reachability-covered ecosystem.
SCA: Resolves from Gemfile.lock.
Firewall: Not available — same reasoning as Rust/Go/Java/.NET below: no side-effect-free dry-run install path in Bundler for UBEL to hook into.
SAST / Malware SAST: Full coverage, .rb chunking.
Reachability analysis: Fully covered via .rb import scanning.
SCA: Resolves from Cargo.lock, giving exact resolved versions rather
than semver ranges from Cargo.toml.
Firewall: Not available — cargo add/cargo build don't offer an
equivalent gate point.
SAST / Malware SAST: Full coverage.
Reachability analysis: Fully covered via .rs import scanning.
SCA: Resolves from go.sum, which pins exact versions and hashes
already, giving high-confidence version matching against OSV.
Firewall: Not available.
SAST / Malware SAST: Full coverage.
Reachability analysis: Fully covered via .go import scanning.
SCA: Resolves the dependency tree from pom.xml, following transitive
Maven resolution.
Firewall: Not available.
SAST / Malware SAST: Full coverage; Java and Kotlin are tracked as separate language families in the catalog, so idioms specific to each (e.g., Kotlin null-safety bypasses vs. Java reflection abuse) get distinct signal sets rather than one being shoehorned into the other's ruleset.
Reachability analysis: Fully covered — .java, .kt, .groovy, and
.scala files are all scanned under the same maven reachability key.
Note: Gradle-based projects aren't mentioned as a separately-resolved build system — Maven-style resolution is the documented path for this ecosystem today.
SCA: Resolves from packages.lock.json where present, falling back to
obj/project.assets.json (the MSBuild-generated resolved graph) when a
project doesn't use lock-file mode.
Firewall: Not available.
SAST / Malware SAST: Full coverage.
Reachability analysis: Fully covered — .cs, .vb, .fs, and .fsx
files are all scanned under the same nuget reachability key.
SCA: Resolves from the lockfiles Swift tooling already writes, so nothing needs to be built or installed first:
Package.resolved(SwiftPM formats v1, v2, and v3) — next to aPackage.swift, inside an Xcode workspace at<X>.xcworkspace/xcshareddata/swiftpm/, or inside an Xcode project at<X>.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/..build/workspace-state.json— fallback for a package whosePackage.resolvedwasn't committed (common for libraries that gitignore it).Cartfile.resolved(Carthage) —binaryentries are skipped, since they carry no repository identity.
Packages are reported as pkg:swift/<host>/<owner>/<repo>@<version> (OSV
ecosystem SwiftURL), with a leading v stripped from release tags. Local
packages (fileSystem / localSourceControl pins) are first-party code and
aren't reported. A pin that resolves to a branch or a bare commit instead of
a release tag is inventoried with an empty version and dropped from OSV
queries, because a commit hash isn't a version OSV can range-match.
CocoaPods (Podfile.lock) is intentionally not scanned: OSV has no
CocoaPods ecosystem, so those packages could never match an advisory.
Scopes: None of these lockfiles distinguish dev from prod, so every
Swift package is reported as prod.
Firewall: Not available — Swift is SCA-only today.
SAST / Malware SAST: Not covered — Swift isn't one of the language
families in the SAST or malware catalogs. .swift files are still included
in Secrets detection.
Reachability analysis: Covered — .swift files (plus .m/.mm/.h) are
scanned for import <Module> / @import / #import <Module/…> references,
with repository-to-module mapping (e.g. swift-log → Logging).
License compliance: Package.resolved and Cartfile.resolved don't
record licenses, so every Swift package is inventoried with license
unknown.
SCA: A directory is scanned when it contains pubspec.lock or — for
packages and libraries that don't commit their lockfile —
.dart_tool/package_config.json, which dart pub get / flutter pub get
writes. pubspec.lock is preferred when both exist: it carries each
package's version, source (hosted, git, path, or sdk), and dependency kind.
From the package_config.json fallback only hosted packages can be
recovered (their version comes from the pub-cache directory name); git, SDK,
and relative-path packages are skipped there.
Packages are reported as pkg:pub/<name>@<version>, with
?repository_url=<url> for a package hosted on a non-pub.dev registry and
?vcs_url=<url> for a git dependency. sdk sources (flutter,
flutter_test, …) and path sources are skipped — first-party or toolchain
code, not installed third-party packages.
Scopes: From pubspec.lock: direct main and direct overridden →
prod, direct dev → dev, and transitive → prod (the lockfile
doesn't record whether a transitive dependency is reached from dev or main,
so the conservative default is used). Packages recovered from the
package_config.json fallback carry no dev/main signal and are reported as
prod.
Dependency graph: pubspec.lock has no dependency graph, so Flutter/Dart
packages are reported without introduced-by/parent edges.
Known limitation: OSV matches on package name. A package from a private
registry or a git repository that shares its name with a pub.dev package can
be matched against that package's advisories; the repository_url /
vcs_url qualifier keeps the identity distinct in the inventory but doesn't
stop the OSV lookup.
Firewall: Not available — Flutter/Dart is SCA-only today.
SAST / Malware SAST: Not covered — Dart isn't one of the language families in the SAST or malware catalogs.
Reachability analysis: Covered — .dart files are scanned for
import/export 'package:<name>/…' references, including conditional-import
continuation lines. A federated platform implementation (e.g.
url_launcher_android) is also matched via its app-facing package.
License compliance: pubspec.lock doesn't record licenses, so every
Flutter/Dart package is inventoried with license unknown.
SCA: Not applicable — C has no standard ecosystem-level package manager/lockfile for UBEL to resolve a dependency tree from, so there's no SCA/vulnerability-matching layer for C the way there is for the lockfile-based ecosystems above.
SAST / Malware SAST: Covered as its own language family in the catalog — memory-safety and injection-class findings are scanned for directly in source, independent of any dependency graph.
License compliance: Not applicable, for the same reason as SCA — no per-package manifest to read a declared license from.
SCA: The most complete single-target scan in UBEL. Pulls (or reuses a
local) image or accepts an already-exported uncompressed .tar — never
runs the image's ENTRYPOINT/CMD — exports its filesystem to a temp dir,
and scans that filesystem exactly like any other project root: OS packages
via the Linux host scanner and every application-level ecosystem's
dependencies found inside the image (Node, Python, PHP, etc., all at once,
via full_stack).
Firewall: Docker gets true pre-install (here, pre-deploy) gating, with three modes controlling what happens to the image after scanning:
health— scan only, image left exactly as found.check— scan, then always remove the image afterward (throwaway vetting).install— scan, then remove the image only if the scan results in a policy block; a clean scan leaves it in place.
--no-pull scans an image that only exists locally (e.g., right after
docker build, before it's ever pushed to a registry) — this is the
"vet before you ship" path. --keep retains the extracted root filesystem
for debugging. Pointing this at a CI-built image before push, or at a
third-party base image before you adopt it, is the intended pre-deploy
firewall use.
Malware/Secrets/License: All inherited from whatever's found inside the
image — an image with a compromised npm package, a hardcoded AWS key in a
baked-in .env, or a GPL-licensed binary bundled into a proprietary image
all get caught the same way they would in a live checkout.
Coverage: Not a separate scanner — Kubernetes YAML is treated as its
own chunkable language family inside the SAST catalog, so misconfigurations
get scanned with the same LLM-driven pipeline as source code. Verified
classes include: overly permissive RBAC / ClusterRoleBindings, containers
configured to run as root, missing NetworkPolicy isolation on services
exposed via LoadBalancer/NodePort, and similar cluster-hardening gaps.
Not covered: Live cluster state — this scans the YAML files in your
repo, not a running cluster's actual applied configuration or drift from
those files. There's no kubectl-based live posture check.
Coverage: Same mechanism as Kubernetes above — IaC is its own language
family in the catalog. Confirmed classes include hardcoded secrets
embedded directly in .tf/CloudFormation templates and resources
provisioned with encryption-at-rest disabled.
Not covered: Live cloud account state. This is static analysis of the IaC source — it will not detect drift where the deployed resource no longer matches what the template says, and it isn't a cloud security posture management (CSPM) tool that queries your cloud provider's API for the actual state of running resources.
SCA: LinuxHostScanner inventories installed OS packages and matches
them against CPE/CVE data — this is what backs both the standalone
ubel-platform host scan and the OS-package half of every Docker image
scan.
Firewall: Available via three separate binaries — ubel-apt,
ubel-dnf, ubel-yum — each bound to exactly one native package manager,
with no auto-detection between them (the same one-binary-per-tool shape as
ubel-npm/ubel-pnpm/ubel-bun; running ubel-dnf on a box that only has
apt fails clearly rather than silently doing the wrong thing). Each uses
its package manager's own native dry-run — apt-get -s (simulate) for
Debian/Ubuntu, dnf --assumeno for RHEL 8+/AlmaLinux/Rocky,
yum --assumeno for RHEL 7 — to resolve what would be installed (name,
version, and for apt, the source repo/arch) without installing it. UBEL
scans that resolution and only then runs the real
sudo apt/dnf/yum install -y. As with pip, there's no lockfile, so a
blocked scan just means the real install never runs — nothing to revert.
Reports and policy live under ~/.ubel/local specifically so that routine
health/check use never needs elevated privileges; only the real install
step does, same as running the package manager yourself.
License compliance: Fully applied. Per-package license strings are
extracted from rpm metadata, /usr/share/doc/<pkg>/copyright for
dpkg-based systems, and apk metadata, then fed through the same SPDX
normalization/OSI/risk classification layer as every other ecosystem — it
isn't a separate, lesser pipeline for OS packages.
SCA: WindowsHostScanner mirrors the Linux host scanner's role for
Windows — installed software/package inventory matched against CPE/CVE.
Firewall: Not available. Unlike Linux's apt-get -s/dnf --assumeno,
there's no equivalent dry-run resolution UBEL can hook into for Windows'
package managers/installers today — this remains health/detection scanning
of what's already on the machine, not a gate on what's about to be
installed.
License compliance: Fully applied, via its own licenseFor() mapping
feeding the same classification pipeline as every other ecosystem.
Every vulnerability is annotated with a reachability verdict derived from
dependency depth, scope (prod/dev/env), attack vector, orphan-tool
detection, and — where source is available — actual import/require
scanning that confirms whether the vulnerable module is ever referenced.
The import-scan half of this is implemented for Python (.py), Node.js
(.js/.ts/.mjs/.cjs/.jsx/.tsx), Maven/Java+Kotlin (.java,
.kt, .groovy, .scala), NuGet/C# (.cs, .vb, .fs, .fsx), PHP
(.php), Go (.go), Cargo/Rust (.rs), RubyGems/Ruby (.rb), Flutter/Dart
(.dart), and Swift (.swift, plus .m/.mm/.h for Objective-C interop) —
every SCA ecosystem except C. Dart matches import/export 'package:<name>/…';
Swift maps each package's repository to its module names (e.g. swift-log →
Logging) and matches import <Module>, @import, and #import <Module/…>. A known distribution-name-to-
import-name override table (e.g. beautifulsoup4 → bs4,
pyyaml → yaml, opencv-python → cv2) keeps the import match accurate
even where the published package name and the name you actually import
diverge. C, OS packages, Docker, Kubernetes, and IaC don't get this layer,
since there's no per-package "is this imported by my source" question that
applies to them the same way.
Secrets scanning is deliberately ecosystem-independent: it's a plain
file walk over source and config file extensions (.js, .py, .rb,
.go, .java, .php, .cs, .rs, .kt, .swift, .json, .yml,
.yaml, config files, etc.), pattern-matched against a ruleset ported from
Trivy's secret rules (separately attributed and licensed — only that
vendored ruleset is Apache-2.0; the scanner code around it isn't). It
never touches node_modules, vendor/, target/, virtual environments, or
any other dependency directory — it's scanning your code for accidental
commits, not your dependencies. This means it runs identically whether
you're in a Node repo, a Python repo, a Kubernetes manifests repo, or an
image filesystem — there's no per-language variance in what it can find.
It's a pure in-memory scanner with no disk writes of its own; the caller
(main engine) decides whether findings fold into the JSON/HTML/SARIF
report.
A zero-dependency, no-network normalization layer that takes whatever
inconsistent shape a package manager reports a license in — missing/null,
free text ("Apache 2.0", "BSD"), npm's UNLICENSED sentinel (proprietary —
not the SPDX "Unlicense" public-domain license, a common footgun),
SEE LICENSE IN <file> references, Python trove classifiers, full SPDX
expressions like (MIT OR Apache-2.0) — and normalizes it to a canonical
SPDX identifier, checks it against a curated OSI-approved license table,
and assigns a risk tier (permissive → weak copyleft → strong copyleft /
proprietary / unrecognized). It's applied once, uniformly, to the entire
unified scan inventory — Node, Python, PHP, Ruby, Rust, Go, Java/Kotlin,
C#, Swift, Flutter/Dart, and Linux/Windows OS packages alike (Swift and pub
lockfiles record no license data, so those packages come through as
unknown). The only true exception is C (no
package manager/manifest exists to read a license from in the first
place). Policy supports both a license-risk threshold
and a separate license-block-unknown flag for licenses that couldn't be
classified at all — both enforced only against health-mode scans, since
license risk is a compliance concern on what's already installed, not an
install-time security gate.
Malware detection is a two-pass LLM pipeline covering 15 intentional-malice
classes, applied identically across all 12 source-code families (JS,
Python, PHP, Ruby, Go, Rust, Java, Kotlin, Dart, Swift, C#, C) — every malware catalog
entry is tagged for ALL_LANGUAGES, so there's no ecosystem where malware
detection is a second-class capability. It's explicitly separate from the
vulnerability SAST catalog (different pipeline, different intent: is this
code deliberately malicious vs. accidentally exploitable) and ships as
its own CLI (ubel-mal), independent of the SCA/firewall engine.
The vulnerability-finding catalog (64 CWE-mapped classes) spans a wider set than malware detection: the 12 source-code families above (Dart/Flutter and Swift include dedicated mobile classes — local storage, TLS/pinning, WebView bridges, deep links, biometric gates), plus Docker, Kubernetes, and general IaC as first-class chunkable targets — meaning Dockerfiles, K8s manifests, and Terraform/CloudFormation get scanned with the same three-pass scan → verify → taint-trace rigor as application code, not treated as an afterthought bolted onto the dependency scanner.
- SARIF 2.1.0 — full-fidelity output (deterministic SHA-256
fingerprints, not random UUIDs, so the same finding produces the same ID
run over run) that plugs directly into GitHub code scanning and any
other SARIF-consuming pipeline. Results carry KEV / EPSS data and a
rank(100 for a KEV entry, otherwise the EPSS probability), and a KEV finding is reported aterrorlevel regardless of its severity. - CycloneDX 1.6 SBOM — with VEX-style vulnerability annotations, usable as a release artifact independent of any specific CI vendor. Includes KEV / EPSS exploit data and per-package suggested fixes.
- CVSS scoring — v2, v3, v3.1, and v4.0 (a full port of the Red Hat CVSS v4 calculator, BSD-2-Clause, separately attributed) plus SSVC as a normalized "other" scoring method where relevant, all folded into a single normalized severity used consistently across JSON, SARIF, and SBOM output.
- HTML reports — force-directed dependency graphs, inventory modals, and reachability badges/filters wherever reachability data exists in the report (the ten ecosystems above).
- JSON — the canonical, complete report every other format is derived from, including effective-configuration tracing (what policy/thresholds were actually in effect for that specific scan run).
To keep this document honest rather than aspirational:
- Firewall/pre-install gating is npm, pnpm, bun, Composer, Docker, pip/uv/pipx, and Linux host packages (apt/dnf/yum) only — not "every package manager," because most package managers genuinely don't offer a dry-run resolution UBEL can safely gate against. Ruby (Bundler), Rust (Cargo), Go (modules), Java/Kotlin (Maven), C#/.NET (NuGet), and the Windows host all remain without it for that reason — a hard mechanical constraint, not a roadmap gap. Swift (SwiftPM/Carthage) and Flutter/Dart (pub) are SCA-only as well, simply because no install gate has been built for them yet.
- Yarn is the one Node.js package manager left out of that list, for
the same kind of reason, not a different one:
yarn addalways writes tonode_modulesimmediately, with no lockfile-only/dry-run mode the way npm/pnpm/bun each have. That's yarn's own CLI design, not a gap in UBEL's implementation — there's no side-effect-free resolution step here to hook a scan into before something's already been written to disk. - pip's and uv's dry-run firewalls are the one exception with a caveat
rather than a clean guarantee, and that caveat is Python's own packaging
model, not a shortcoming in how UBEL drives either tool: resolving a
source-only package's metadata can require building an sdist, which can
run arbitrary build-backend code, and neither
pip install --dry-runnoruv pip install --dry-runcan avoid that — it's how sdist-based resolution works generally, independent of which tool triggers it. Wheel-only installs don't have this gap; see the Python section above. - Reachability analysis's import-confirmation half covers 10 of the 10 SCA ecosystems — C, OS packages, Docker, Kubernetes, and IaC don't get it, since "is this imported by my source" isn't a meaningful question for those.
- Kubernetes manifest and IaC coverage is static file analysis, not
live cluster posture management — there is no drift detection against
what's actually running in a cluster. (Live account-level posture for
the AWS, GCP, and Azure resources themselves — as opposed to Kubernetes
clusters running on them — is covered separately by
ubel-cloud, via each provider's own API; see the Cloud section above.) - Every ecosystem here — including the OS-level ones (apt/dnf/yum and
Windows) — gets full SCA and license compliance regardless of
firewall status (Swift and Flutter/Dart licenses report as
unknown, since their lockfiles carry no license data); firewalling and inventory/license coverage are tracked and reported independently. - This entire matrix is about the SCA/firewall/reachability/license/SBOM
pipeline for dependency trees you resolve locally (a manifest, a
lockfile, an installed package set, a container image).
ubel-url/ubel-domain/ubel-host/ubel-easm(EASM) are a genuinely separate thing — passive HTTP fingerprinting of a remote domain/URL (plus a fixed set of misconfiguration checks;ubel-host/ubel-easmadditionally run a live, active TCP port scan ahead of the fingerprinting), not a dependency-tree scan — and deliberately doesn't carry license compliance, dependency sequences, reachability analysis, or SBOM/SARIF output, none of which are meaningful concepts for something fingerprinted over the network rather than resolved from a manifest. See the EASM section above and easm/README.md for what it does carry over (OSV/NVD/wpvulnerability.net lookup, CVSS, fix recommendations, compliance mapping) and why.
Source-available, internal-use-only license. Using and modifying UBEL for your organization's own internal needs (including its own CI/CD pipelines and products) is permitted. Redistribution, wrapping or embedding, exposing it to third parties over a network or API, and hosting or automating it as a service for others are not. Security consultants may use it manually during direct client engagements, provided it isn't left behind, automated, or made available to the client as a platform. See LICENSE.md for the full terms.
- Repository: https://github.com/AlaBouali/ubel
- Issues: https://github.com/AlaBouali/ubel/issues
- SCA docs: https://github.com/AlaBouali/ubel/blob/main/sca/README.md
- SAST docs: https://github.com/AlaBouali/ubel/blob/main/sast/README.md
- Cloud docs: https://github.com/AlaBouali/ubel/blob/main/cloud/README.md
- EASM docs: https://github.com/AlaBouali/ubel/blob/main/easm/README.md
UBEL — Find the bug before it finds production.