Skip to content

Repository files navigation

TELUS Digital

Cisco Advisory Impact Analyzer

Check a Cisco advisory bundle against your firewall inventory


Cisco publishes firewall vulnerabilities in bundles — an Event Response page listing every advisory released that day. Give this tool the link to one and it tells you which of your firewalls and software releases that publication impacts, and the release that fixes each.

It asks Cisco's PSIRT advisory API — the same published data behind Cisco's public Software Checker — which of the bundle's advisories apply to each firewall's software release on its exact hardware model. Every verdict comes from Cisco; nothing is inferred, and a firewall it could not check is never reported as safe.

Installed as a uv tool: one install command, then a single caia command you can run from any folder. Works on macOS, Windows, and Linux.


Before you start — what you need

# Requirement How to get it
1 uv (recommended) — or just Python 3.9+ It manages Python for you. Not sure if you have it? See Get uv below — check first, install only if needed. Can't install uv? See Install without uv — you only need Python.
2 Git The uv install command below pulls the tool straight from GitHub, so Git must be installed first. Not sure if you have it? See Get Git below.
3 Cisco PSIRT API credentials A client ID and client secret from https://developer.cisco.com/psirt/ — each person needs their own (steps below).
4 Your firewall inventory as an Excel .xlsx file in the folder you run from You build this yourself (format below).
5 Internet access to apix.cisco.com, id.cisco.com and sec.cloudapps.cisco.com The first two are Cisco's advisory API and its token endpoint; the third serves the bundle page. Usually already available; corporate proxies can block these.

Your credentials are stored once, per user, via caia --config. The tool refuses to run without them and points you at --config.

Get uv

First check whether uv is already installed — then install it only if it isn't.

1. Check if it's installed. Works the same on macOS, Windows, and Linux — it prints a version number if uv is there, or says command not found (macOS/Linux) / errors (Windows) if it isn't:

uv --version

2. Not installed? Install it for your operating system:

  • macOS / Linux — official standalone installer (no admin/root needed):

    curl -LsSf https://astral.sh/uv/install.sh | sh
  • Windows — official standalone installer, run in PowerShell:

    powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"

Prefer a package manager? Use brew install uv (macOS); winget install --id=astral-sh.uv -e or scoop install main/uv (Windows); or pipx install uv / pip install uv (any OS).

After installing, open a new terminal and re-run uv --version to confirm. More methods and details: https://docs.astral.sh/uv/getting-started/installation/.

Get Git

The install command below (uv tool install ... --from git+https://...) fetches the tool straight from GitHub, so Git needs to be installed first — uv uses it under the hood.

1. Check if it's installed:

git --version

2. Not installed? Install it for your operating system:

  • macOS — running git --version with no Git installed prompts you to install the Xcode Command Line Tools (which include Git); accept that prompt. Or use Homebrew: brew install git.

  • Windows — download the official installer from https://git-scm.com/download/win, or:

    winget install --id Git.Git -e --source winget
  • Linux — install via your distro's package manager, e.g.:

    sudo apt-get update && sudo apt-get install -y git

After installing, open a new terminal and re-run git --version to confirm. More details: https://git-scm.com/downloads.

Inventory format

Your inventory .xlsx must have a sheet named FW_List with these columns (header names can vary slightly — matching is flexible):

FirewallName Model Firewalltype IOS version Priority
FWLOC1-001 ISA-3000 FTD 7.4.2 2
FWLOC3-004 ASA 5506 ASA 9.16(4)67 1

Firewalltype should be FTD, ASA, or ASAv. Put the file in whatever folder you plan to run the tool from; the report is written to an output/ folder next to it.


Install (one time)

With uv on your machine, install the tool straight from GitHub:

uv tool install caia-cli --from git+https://github.com/xavient/caia-cli

This puts a single caia command on your PATH. There is no repository to clone and no virtual environment to create or activate.

Upgrading from the old name? Before version 3.0.0 the tool installed as cisco-advisory-impact-agent. Both own the caia command, so remove the old one first — then run the install above:

uv tool uninstall cisco-advisory-impact-agent

Your saved credentials are stored outside the tool and are not touched by this, so there's no need to re-run --config. Note that caia --update cannot make this jump for you: the distribution name changed, so an old install has to be replaced with the command above.

Then set your Cisco credentials once (stored per-user, used from any folder):

caia --config

--config asks for your Cisco client ID and client secret, checks them against Cisco, and saves them to a per-user config file — see Configuration below.


Install without uv (Python only)

If you can't install uv (e.g. a locked-down machine), you can install and run the tool with just Python 3.9 or newer — the venv and pip modules that ship with Python. No admin rights and no extra tools are needed.

1. Get the code. On the GitHub page, click the green Code button → Download ZIP, and unzip it. (Or git clone the repo.) Open a terminal inside the unzipped folder.

2. Create an isolated environment and install the tool into it:

macOS / Linux:

python3 -m venv .venv
source .venv/bin/activate
python -m pip install .

Windows (PowerShell):

py -m venv .venv
.venv\Scripts\Activate.ps1
py -m pip install .

This installs the caia command inside that environment. From then on it behaves exactly like the uv install — use it as described in Run it and Configuration (caia --config, then caia from your inventory folder).

Notes for this path:

  • Activate first, every session. In each new terminal, re-run the activation command (source .venv/bin/activate on macOS/Linux, .venv\Scripts\Activate.ps1 on Windows) before using the command. If you don't want to activate, call it by full path — e.g. .venv/bin/caia — or run python -m caia.cli.
  • Updating is manual here. --update is uv-specific and will report that uv wasn't found. To update, download the newer ZIP and re-run python -m pip install . in your environment. Pass --no-update-check to skip the start-of-run version check.
  • Everything else — --config, inventory discovery, the output/ folder, and the check itself — works identically to the uv install.

Run it

cd into the folder holding your inventory .xlsx, then:

caia

It finds your inventory in that folder (if there's more than one candidate, it lists them and lets you pick), then asks for the advisory bundle to check it against:

Cisco advisory bundle URL (press Enter for a whole-fleet report) ›

Paste the Event Response link Cisco published — for example https://sec.cloudapps.cisco.com/security/center/viewErp.x?alertId=ERP-75736. A bare identifier works too: just ERP-75736.

Supply it up front to skip the prompt entirely, which is what makes a scheduled review possible:

caia --erp ERP-75736

The tool tells you which bundle it found and how many advisories it lists before it starts asking Cisco about your inventory, so a wrong link costs you nothing. It then writes impact_report_<bundle>_<date>_<time>.html into an output folder inside your current folder (created automatically). Open it in any browser.

The report is one row per advisory in the bundle — each showing the advisory, its publication date, and which of your devices and releases it impacts along with the release that fixes them. Advisories in the bundle that hit nothing still get a row; a clean result you can point at is worth more than a gap.

Two things worth knowing:

  • It is scoped on purpose. Advisories affecting your inventory from other publications are deliberately absent, and the report says how many it left out. It is not a whole-inventory assessment.
  • Firewalls that couldn't be checked appear in every row they might belong to, marked in yellow and labelled undetermined. They are never counted as unaffected. If a row says "0 impacted" it also tells you how many devices nobody could answer for.

Each run adds a new timestamped file, so previous reports are kept. Press Ctrl+C at any time to cancel cleanly.

Pressing Enter at the prompt instead produces the older whole-inventory report — one row per firewall, listing every advisory Cisco holds against it, from any publication. It remains available for a broad sweep, but the bundle check above is what the tool is for.

Understanding the output

Every firewall gets exactly one status:

Status Meaning
Impacted Cisco lists at least one advisory for that release on that model. The report shows each advisory's severity, its CVEs, a link to Cisco, and the release that fixes it.
Not impacted Cisco confirmed it recognises both the release and the model, and lists no advisories.
Needs review No trustworthy answer was possible — and the report says why. Never a claim of safety.

Needs review is the one to pay attention to. It usually means the inventory disagrees with Cisco's data — a release Cisco has retired as end-of-support, or a model/release pair Cisco never shipped (e.g. ASA 9.18 on 5500-X hardware). The tool names which, so the row becomes a data-quality fix rather than a mystery. A device that could not be checked is never reported as safe.

The report is a single self-contained file: it filters, sorts and expands with no network access, so it survives being emailed or archived.

Two limits are stated in every report, and worth knowing up front:

  • Cisco's data holds no advisories published before January 2022 for ASA, FMC, FTD and FXOS. An Impacted list is not exhaustive, and Not impacted is narrower than it looks.
  • A verdict does not know whether a vulnerable feature is switched on. Impacted means "listed as affected", not "exploitable".

A run paces itself to stay inside Cisco's request quota, so a large inventory takes a few minutes; progress is printed as it goes.

Coming from version 3.x? The advisory-driven mode — caia --url <ERP-link>, which used AI to read one advisory and produced an Excel file — has been removed in 4.0.0, along with --api, --dry-run, --keep-temp and --no-keep-temp. What --api used to do is now what plain caia does. If you pass any of the removed options the tool tells you so and what to run instead. You no longer need a FueliX API key; any leftover FueliX settings in your config file are ignored and disappear the next time you run --config.


Keeping it up to date

New releases improve the matching logic, so it's worth staying current:

caia --version   # print your version; note if a newer one exists
caia --update    # update to the latest release via uv

--update checks the latest published release and, if newer, reinstalls the tool with uv (pinned to that release). When you start a normal run and a newer version is available, the tool also offers to update first — answer yes to update (it then asks you to re-run) or no to continue on your current version. The version check is best-effort and time-bounded, so it never blocks a run; skip it with --no-update-check or CAIA_NO_UPDATE_CHECK=1.

On Windows, the running command can't overwrite itself while it's open. If an in-place update reports the file is in use, close the terminal and run the uv tool install … --force command it prints from a fresh shell.


Uninstall

Remove the tool the same way it was installed — with one command:

caia --uninstall          # asks you to confirm first
caia --uninstall --yes    # skip the prompt (for scripts / fleet cleanup)

This removes the caia command and its uv-managed environment. Your saved settings are kept — the per-user config file (see Configuration for its exact location) is never touched, so your Cisco credentials are preserved; on success the tool prints the path and you can delete that file yourself if you want it gone.

  • --uninstall is uv-specific. If you installed without uv, it reports there's nothing to uninstall — remove it with the Python tooling you used (e.g. pip uninstall caia-cli).
  • If uv can't be found, it prints the exact manual command: uv tool uninstall caia-cli.
  • On Windows, if the running command can't remove itself, close the terminal and run that manual command from a fresh shell.
  • The exit status is script-friendly: 0 once the tool is gone (including when it was already not installed), non-zero when action is still needed (declined, uv missing, or removal failed).

Configuration

caia --config stores your credentials in a per-user file, so every run in any folder can find them:

OS Config file
macOS ~/Library/Application Support/caia/config
Linux ${XDG_CONFIG_HOME:-~/.config}/caia/config
Windows %APPDATA%\caia\config

The file is KEY=value and looks like this (you may edit it by hand):

CISCO_CLIENT_ID=your-cisco-client-id
CISCO_CLIENT_SECRET=your-cisco-client-secret
  • CISCO_CLIENT_ID — identifies the calling application. Not a secret.
  • CISCO_CLIENT_SECRET — authenticates it. Handled as a secret throughout: never displayed, logged, or written anywhere but this file.

Every prompt is skippable — press Enter to leave a value alone. Two details worth knowing:

  • The client ID is shown as you type or paste it (it identifies the application; it isn't a secret), while the client secret is never displayed. After you enter the secret the tool confirms how many characters it captured — so a truncated paste is obvious without the value ever appearing on screen.
  • --config then checks the credentials against Cisco before saving and tells you whether they work. If Cisco rejects them you see Cisco's own reason and nothing is saved. If Cisco simply can't be reached — a proxy or connectivity problem — it says so plainly and offers to save them unverified, so an outage never blocks setup.

Precedence for every setting is environment variable → config file, so the same names in the environment still work for automation. On macOS/Linux the config file is created owner-only (chmod 600). Keep it private — it contains your client secret.

Getting your Cisco PSIRT API credentials

  1. Go to https://developer.cisco.com/psirt/ and sign in with your Cisco account.
  2. Register an application to get a client ID and client secret for the openVuln API.
  3. Copy both, and paste them when caia --config asks.

Each person uses their own credentials — don't share them.


Troubleshooting

  • caia: command not found — the tool's bin directory isn't on your PATH. Run uv tool update-shell (or restart your terminal), or check uv tool list.
  • No Cisco API credentials are configured — run caia --config to set your client ID and client secret, or export CISCO_CLIENT_ID and CISCO_CLIENT_SECRET.
  • No valid inventory .xlsx found — run the tool from the folder that holds your inventory .xlsx (sheet FW_List), or pass --inventory PATH.
  • --update says uv wasn't found — uv isn't on the tool's PATH. Re-run the uv tool install … --from git+… command from a shell where uv works.
  • Everything comes back Needs review — the report says why for each row. Most often the inventory's release strings or models don't match what Cisco publishes; fix those and re-run.
  • Cisco rejected the configured credentials — re-check them with caia --config; it verifies them against Cisco and shows Cisco's own reason for a rejection.
  • Could not reach Cisco — check your connection or proxy. The tool needs apix.cisco.com and id.cisco.com for advisory data, and sec.cloudapps.cisco.com to read the bundle page. A bundle failure says whether it was connectivity or a bad link, and writes no report either way.
  • --api has been removed (or --url, --dry-run, --keep-temp, --no-keep-temp) — these options were withdrawn in 4.0.0. Just run caia.

Advanced / automation

The check is fully flag-driven for scripting — supplying an input skips its prompt, and a non-interactive run has no prompts at all (no update offer, no confirmation):

Flag Purpose
--erp <URL|ERP-#####> the advisory bundle to check against — supplying it skips the prompt entirely, which is what makes a scheduled review possible
--inventory <PATH> use a specific inventory file, bypassing folder discovery
--sheet <NAME> inventory sheet name (default FW_List)
--output-dir <DIR> where to write the report (default: ./output)
--no-update-check skip the start-of-run version check
--yes skip the uninstall confirmation

So a nightly or post-publication review is just:

caia --erp ERP-75736 --inventory /path/to/inventory.xlsx --no-update-check

Exit codes are script-friendly: 0 success, 1 a declined confirmation or a bundle that could not be resolved (Cisco unreachable, or a page yielding no advisories), 2 an argument error (including a removed option, or a value that is not a Cisco Event Response address), 3 a failed version lookup, 4 a failed update or uninstall, 130 interrupted. No report is written on any non-zero bundle outcome.

On startup the tool prints a splash banner. It is shown only on an interactive terminal, so piped or redirected output (e.g. caia --version > file) stays clean; set CAIA_NO_BANNER=1 to suppress it everywhere.


For maintainers

How it works & how to hand it out

Package layout (caia/)

Module Responsibility Shared surface
assessment.py the assessment itself — analyze(), with no user interface attached
matching.py pure verdict logic: release normalisation, model→platform mapping, verdicts
psirt.py the only module that talks to Cisco's advisory API
inventory.py read the inventory workbook into per-firewall records
erp.py resolve a published bundle page to the advisory identifiers it contains — membership only, never an advisory fact
bundle.py pure per-advisory aggregation: covered families, filtering, row construction
csaf.py the only module that reads Cisco's published advisory documents, for an advisory's two product-scope statements and nothing else
cli.py single entry point; refuses removed options, then dispatches --help/--version/--update/--uninstall/--config and the run
analyzer.py terminal run flow + interactive prompts (working-folder inventory + output/)
html_report.py render the self-contained HTML report, one row per firewall
bundle_report.py render the bundle-scoped report, one row per advisory
config.py per-user configuration (paths, precedence, --config)
version.py installed version, GitHub release discovery, uv-based update
ui.py terminal colors + prompts (no dependencies)

Shared surface. The seven modules marked above are the ones a consumer other than this CLI is expected to import; the rest are the terminal program built on top of them. The entry point is assessment.analyze(inventory, client) — it takes a list of per-device dicts (whatever produced them) plus a psirt.Client the caller owns, prints nothing, writes nothing, and never exits the process. analyzer.run() is one caller of it; the terminal output you see is supplied by the on_plan and on_progress callbacks it passes in. The split exists so that a second consumer cannot drift from this tool's verdicts: the rules that read Cisco's data live in one place, and anything that reimplements them reimplements the safety property in Constitution IV.

For a bundle-scoped run, erp.resolve(reference) returns the bundle and bundle.build_rows(bundle.advisory_ids, devices) turns a completed assessment into one row per advisory. Both are headless for the same reason: the bundle-page parse and the covered-family derivation are the fragile parts, and a second consumer that reimplemented them would produce a second, divergent answer about which advisories are even in scope.

One rule about the bundle page. It is a scope selector, never an authority. It supplies which advisories a bundle contains and the bundle's own identity — nothing else. Every advisory fact in the report (title, severity, score, CVEs, publication date, fixing release) comes from Cisco's API, even though the page displays all of them too. The constitution states this directly under Technology & Data Constraints → Scope selectors.

The version is single-sourced from the committed VERSION file into the package metadata and reported at runtime via importlib.metadata. Devices sharing a product family, hardware platform and software release collapse into one question to Cisco, so a large inventory costs far fewer requests than it has rows.

Handing it to teammates: point them at the install command above. Each teammate supplies their own Cisco credentials (--config) and inventory.

Local development: install from a checkout with uv tool install --from . caia-cli --force, and run the tests with python -m unittest discover -s tests. See CONTRIBUTING.md for the release process.

About

Fleet-wide firewall impact analysis, straight from Cisco. Checks every firewall in your inventory against Cisco's published advisory data and writes one HTML impact report.

Topics

Resources

Contributing

Stars

Watchers

Forks

Releases

Used by

Contributors

Languages