

ICARAX Tech Blog
This guide shows how to query Cisco’s Security Advisory API for the latest critical vulnerabilities (the “dozen” Cisco just patched) and integrate the data into your own tooling or dashboards.
All code snippets are copy‑and‑paste ready, include error handling, and follow modern best‑practice patterns.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | How to obtain |
|---|---|---|
| Cisco DevNet account | Access to Cisco’s Security Advisory API (OAuth2 protected) | Sign up at https://developer.cisco.com/ |
OAuth2 client credentials (client_id & client_secret) | Used to fetch an access token | In DevNet → My Apps → create a new app → select “Security Advisory” API |
| Python ≥ 3.9 (or Node ≥ 14) | Runtime for the examples | https://www.python.org/downloads/ / https://nodejs.org/ |
Package manager (pip for Python, npm/yarn for JS) | Install dependencies | Comes with Python/Node |
| Git (optional) | Clone the example repo if you prefer | https://git-scm.com/ |
Tip: Keep your
client_idandclient_secretout of source control – use a.envfile (see Step 4).
<a name="step-2-installation-and-setup"></a>
# Create a virtual environment (recommended)
python -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
# Install required packages
pip install --upgrade pip
pip install requests python-dotenv tenacity
# Initialize a new Node project (if you don't have one)
npm init -y
# Install dependencies
npm install axios dotenv
# For TypeScript add:
npm install --save-dev typescript @types/node ts-node
npx tsc --init # creates tsconfig.json
<a name="step-3-basic-implementation"></a>
Both examples follow the same flow:
severity=Critical published in the last 30 days.The API endpoint used here is the public Cisco Security Advisory API (version 1).
Docs: https://developer.cisco.com/docs/security-advisories/
#!/usr/bin/env python3
"""
fetch_cisco_critical.py
Retrieves Cisco Security Advisories marked as Critical from the last 30 days.
"""
import os
import sys
import time
from datetime import datetime, timedelta
import requests
from dotenv import load_dotenv
from tenacity import retry, stop_after_attempt, wait_exponential, retry_if_exception_type
# ----------------------------------------------------------------------
# Load environment variables from .env (if present)
# ----------------------------------------------------------------------
load_dotenv()
CLIENT_ID = os.getenv("CISCO_CLIENT_ID")
CLIENT_SECRET = os.getenv("CISCO_CLIENT_SECRET")
if not CLIENT_ID or not CLIENT_SECRET:
sys.exit("❌ Missing CISCO_CLIENT_ID or CISCO_CLIENT_SECRET in environment.")
# ----------------------------------------------------------------------
# Constants
# ----------------------------------------------------------------------
TOKEN_URL = "https://cloudsso.cisco.com/as/token.oauth2"
ADVISORY_URL = "https://api.cisco.com/security/advisories"
# Ask for advisories published within the last 30 days
START_DATE = (datetime.utcnow() - timedelta(days=30)).strftime("%Y-%m-%d")
# ----------------------------------------------------------------------
# Helper: fetch OAuth2 token (with retry)
# ----------------------------------------------------------------------
@retry(
reraise=True,
stop=stop_after_attempt(3),
wait=wait_exponential(multiplier=1, min=2, max=10),
retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout)),
)
def get_access_token() -> str:
"""Exchange client credentials for a Bearer token."""
payload = {
"grant_type": "client_credentials",
"client_id": CLIENT_ID,
"client_secret": CLIENT_SECRET,
}
headers = {"Content-Type": "application/x-www-form-urlencoded"}
resp = requests.post(TOKEN_URL, data=payload, headers=headers, timeout=10)
resp.raise_for_status()
data = resp.json()
return data["access_token"]
# ----------------------------------------------------------------------
# Helper: fetch advisories (with retry & basic rate‑limit handling)
# ----------------------------------------------------------------------
@retry(
reraise=True,
stop=stop_after_attempt(4),
wait=wait_exponential(multiplier=2, min=3, max=30),
retry=retry_if_exception_type((requests.ConnectionError, requests.Timeout)),
)
def fetch_critical_advisories(token: str):
"""Return a list of Critical advisories published after START_DATE."""
headers = {"Authorization": f"Bearer {token}", "Accept": "application/json"}
params = {
"publishedStartDate": START_DATE,
"severity": "Critical",
# The API supports pagination; we request a reasonable page size.
"pageSize": 100,
}
advisories = []
url = ADVISORY_URL
while url:
resp = requests.get(url, headers=headers, params=params, timeout=15)
# Handle 429 (Too Many Requests) by respecting Retry-After header
if resp.status_code == 429:
retry_after = int(resp.headers.get("Retry-After", "5"))
time.sleep(retry_after)
continue # retry the same request
resp.raise_for_status()
data = resp.json()
advisories.extend(data.get("advisories", []))
# Pagination: the API returns a `next` link if more pages exist
url = data.get("metadata", {}).get("links", {}).get("next")
# After first request we don't need to resend params (they're in the link)
params = None
return advisories
# ----------------------------------------------------------------------
# Main routine
# ----------------------------------------------------------------------
def main():
try:
token = get_access_token()
except Exception as exc:
sys.exit(f"❌ Failed to obtain OAuth token: {exc}")
try:
criticals = fetch_critical_advisories(token)
except Exception as exc:
sys.exit(f"❌ Error while fetching advisories: {exc}")
if not criticals:
print("✅ No Critical Cisco advisories in the last 30 days.")
return
print(f"🔎 Found {len(criticals)} Critical Cisco advisory(ies) (last 30 days):\n")
for adv in criticals:
adv_id = adv.get("advisoryId", "N/A")
title = adv.get("title", "N/A")
cvss = adv.get("cvss", {}).get("baseScore", "N/A")
pub = adv.get("publicationDate", "N/A")
print(f"- [{adv_id}] {title}")
print(f" CVSS: {cvss} Published: {pub}")
print()
if __name__ == "__main__":
main()
What the script does
| Step | Description |
|---|---|
Load .env | Reads CISCO_CLIENT_ID & CISCO_CLIENT_SECRET. |
get_access_token() | Performs the OAuth2 client‑credentials flow, retrying on transient network errors. |
fetch_critical_advisories() | Calls /security/advisories with severity=Critical and a date filter, handles pagination and 429 rate‑limit responses. |
| Output | Prints a readable list: Advisory ID, title, CVSS score, publication date. |
Security note: Never commit your
.envfile. Add it to.gitignore.
// fetch_cisco_critical.ts
// Retrieves Cisco Security Advisories marked as Critical from the last 30 days.
import * as dotenv from "dotenv";
import axios, { AxiosInstance } from "axios";
dotenv.config();
const CLIENT_ID = process.env.CISCO_CLIENT_ID ?? "";
const CLIENT_SECRET = process.env.CISCO_CLIENT_SECRET ?? "";
if (!CLIENT_ID || !CLIENT_SECRET) {
console.error("❌ Missing CISCO_CLIENT_ID or CISCO_CLIENT_SECRET in environment.");
process.exit(1);
}
// ---------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------
const TOKEN_URL = "https://cloudsso.cisco.com/as/token.oauth2";
const ADVISORY_URL = "https://api.cisco.com/security/advisories";
const START_DATE = new Date(
Date.now() - 30 * 24 * 60 * 60 * 1000
).toISOString().split("T")[0]; // YYYY-MM-DD
// ---------------------------------------------------------------------
// Create an axios instance with default timeout
// ---------------------------------------------------------------------
const http: AxiosInstance = axios.create({
timeout: 15000,
});
// ---------------------------------------------------------------------
// Helper: obtain OAuth2 token (with simple retry)
// ---------------------------------------------------------------------
async function getAccessToken(): Promise<string> {
const payload = new URLSearchParams({
grant_type: "client_credentials",
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
});
try {
const { data } = await http.post(TOKEN_URL, payload.toString(), {
headers: { "Content-Type": "application/x-www-form-urlencoded" },
});
return data.access_token;
} catch (err: any) {
if (err.response?.status === 429) {
const retryAfter = err.response.headers["retry-after"] ?? 5;
await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
return getAccessToken(); // retry once after back‑off
}
throw new Error(`Token request failed: ${err.message}`);
}
}
// ---------------------------------------------------------------------
// Helper: fetch advisories with pagination & rate‑limit handling
// ---------------------------------------------------------------------
async function fetchCriticalAdvisories(token: string) {
const headers = { Authorization: `Bearer ${token}`, Accept: "application/json" };
let url = ADVISORY_URL;
let params = {
publishedStartDate: START_DATE,
severity: "Critical",
pageSize: 100,
};
const advisories: any[] = [];
while (url) {
try {
const { data } = await http.get(url, { headers, params });
// First request uses `params`; subsequent pages use the `next` link.
params = undefined;
advisories.push(...(data.advisories ?? []));
// Pagination: Cisco API returns a `next` link under metadata.links
url = data.metadata?.links?.next ?? null;
} catch (err: any) {
if (err.response?.status === 429) {
const retryAfter = err.response.headers["retry-after"] ?? 5;
await new Promise((r) => setTimeout(r, Number(retryAfter) * 1000));
continue; // retry same request
}
throw new Error(`Advisory request failed: ${err.message}`);
}
}
return advisories;
}
// ---------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------
(async () => {
try {
const token = await getAccessToken();
const criticals = await fetchCriticalAdvisories(token);
if (criticals.length === 0) {
console.log("✅ No Critical Cisco advisories in the last 30 days.");
return;
}
console.log(
`🔎 Found ${criticals.length} Critical Cisco advisory(ies) (last 30 days):\n`
);
for (const adv of criticals) {
const id = adv.advisoryId ?? "N/A";
const title = adv.title ?? "N/A";
const cvss = adv.cvss?.baseScore ?? "N/A";
const pub = adv.publicationDate ?? "N/A";
console.log(`- [${id}] ${title}`);
console.log(` CVSS: ${cvss} Published: ${pub}`);
console.log();
}
} catch (e: any) {
console.error(`❌ Fatal error: ${e.message}`);
process.exit(1);
}
})();
To run the TypeScript version
# Compile & run (ts-node handles it)
npx ts-node fetch_cisco_critical.ts
Or compile first:
npm run build # if you added a "build": "tsc" script
node dist/fetch_cisco_critical.js
Both scripts produce output similar to:
🔎 Found 12 Critical Cisco advisory(ies) (last 30 days):
- [cisco-sa-20240915-iosxe-rce] Multiple Vulnerabilities in Cisco IOS XE Software
CVSS: 9.8 Published: 2024-09-15
- [cisco-sa-20240914-asa-auth-bypass] Authentication Bypass in Cisco ASA
CVSS: 9.1 Published: 2024-09-14
...
<a name="step-4-configuration"></a>
Create a .env file in the project root (add it to .gitignore):
# .env – never commit this file!
CISCO_CLIENT_ID=your-client-id-here
CISCO_CLIENT_SECRET=your-client-secret-here
How to get the values
- Log in to https://developer.cisco.com/
- Navigate to My Apps → Create a New App
- Select the Security Advisory API, choose OAuth2 client credentials grant type.
- Copy the generated Client ID and Client Secret into the
.envfile.
You can also export the variables directly in your shell:
export CISCO_CLIENT_ID=...
export CISCO_CLIENT_SECRET=...
<a name="step-5-common-patterns"></a>
| Pattern | Why it’s useful | Example (Python) |
|---|---|---|
| Retry with exponential back‑off | Handles transient network glitches & 429 responses | tenacity decorator (@retry) |
| Centralised HTTP client | Consistent timeout, headers, logging | requests.Session() or axios.create() |
| Pagination loop | APIs often return limited pages; follow next link | while url: loop in both snippets |
| Environment‑driven config | Keeps secrets out of code, enables different environments (dev/stage/prod) | python-dotenv / dotenv package |
| Structured logging | Easier to ingest in ELK, Splunk, etc. | Use logging (Python) or pino/winston (Node) |
| Rate‑limit respect | Avoid being blocked; read Retry-After header | if resp.status_code == 429: sleep(retry_after) |
| Typed responses (TS) | Catch shape mismatches early | Define interfaces for advisory objects |
<a name="step-6-troubleshooting"></a>
| Symptom | Likely cause | Fix |
|---|---|---|
401 Unauthorized when fetching token | Wrong/expired client_id/client_secret | Verify credentials in DevNet; regenerate if needed |
403 Forbidden on advisory call | Token lacks required scope (Security Advisory) | Ensure the app is subscribed to the Security Advisory API |
429 Too Many Requests | Exceeded rate limit (default 5 req/sec) | Respect Retry-After; add back‑off; consider caching results for a few minutes |
| Empty result list | Date filter too restrictive or no critical advisories in window | Increase look‑back window (e.g., 90 days) or drop date filter temporarily |
JSON decode error (JSONDecodeError) | Received HTML error page (often due to network issue) | Check response status before .json(); log resp.text for debugging |
ModuleNotFoundError: dotenv | .env loader not installed | Run pip install python-dotenv or npm install dotenv |
TS2304: Cannot find name 'process' | Running plain Node without dotenv config | Ensure dotenv.config() is called before accessing process.env |
Debug tip: Add a temporary print(resp.text) (Python) or console.log(resp.data) (Node) right after a request to see the raw server reply.
<a name="step-7-production-checklist"></a>
| ✅ Item | Description |
|---|---|
| Secrets management | Use a vault (AWS Secrets Manager, HashiCorp Vault, Azure Key Vault) or CI/CD injected env vars – never hard‑code. |
| HTTPS enforcement | All calls to Cisco endpoints are HTTPS; verify TLS certificates (default in libraries). |
| Rate‑limit handling | Implement the Retry-After logic globally; consider a token‑bucket or leaky‑bucket limiter for burst traffic. |
| Caching | Cache the advisory list for a short TTL (e.g., 5 min) to reduce API calls and smooth spikes. |
| Observability | Emit structured logs (request ID, latency, status) and metrics (call count, error rate) to your monitoring stack. |
| Timeouts & retries | Set sensible connect/read timeouts (5‑15 s) and retry only on idempotent GETs (advisory list). |
| Version pinning | Lock dependencies (requirements.txt / package-lock.json) to avoid surprise breaking changes. |
| Input validation | Validate any user‑supplied date or filter parameters before sending to the API (prevent injection‑style misuse). |
| Error reporting | Capture unhandled exceptions and send to an error‑tracking service (Sentry, Rollbar). |
| Documentation | Keep a README.md with setup instructions, required scopes, and example output. |
| Testing | Write unit tests that mock the HTTP layer (e.g., responses for Python, nock for Node) to verify retry & pagination logic. |
| Compliance | Ensure usage aligns with Cisco’s API Terms of Service (no redistribution of raw advisories without attribution). |
With the snippets above you can:
Happy coding, and stay secure! 🚀
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
