

ICARAX Tech Blog
TL;DR – This guide shows how to programmatically check whether your Atlassian stack (Jira, Confluence, Bitbucket, Bamboo, Fisheye, Crucible, Statuspage, and Opsgenie) is running a version prior to the patched release for the recently disclosed critical file‑access flaw (CVE‑2024‑XXXXX). The snippets are ready‑to‑copy, include proper error handling, and follow modern best‑practices for both Python and JavaScript/TypeScript.
<a name="step-1-prerequisites"></a>
| Item | Why you need it | Recommended version |
|---|---|---|
Atlassian product admin access (or a token with read scope) | To call the REST /rest/api/2/serverInfo (Jira) or equivalent endpoints. | Any recent version; token must be generated from Account Settings → Security → API token (or personal access token for Bitbucket). |
| Python 3.9+ | For the Python example. | python --version |
| Node.js 18+ (or LTS) | For the JS/TS example. | node --version |
| Git (optional) | To clone a sample repo or manage your own code. | git --version |
| dotenv (or similar) | To keep secrets out of source control. | Built‑in for Node (dotenv package) or use python-dotenv. |
Note – The code only reads server information; it does not modify any data, so it is safe to run in staging or production environments.
<a name="step-2-installation-and-setup"></a>
# Create a virtual environment (recommended)
python -m venv venv
source venv/bin/activate # on Windows: venv\Scripts\activate
# Install dependencies
pip install --upgrade pip
pip install requests python-dotenv
# Initialize a new npm project (if you don't have one)
npm init -y
# Install core libraries
npm install axios dotenv
# If you prefer TypeScript, also install:
npm install --save-dev typescript @types/node ts-node
npx tsc --init # creates a basic tsconfig.json
Tip – Keep a
.envfile at the project root (see Step 4) and never commit it. Add it to.gitignore.
<a name="step-3-basic-implementation"></a>
The core idea is simple:
version string (e.g., "8.13.0").Below are complete, copy‑paste‑ready snippets for Python and JavaScript/TypeScript. They are deliberately generic – you only need to change the BASE_URL and the PRODUCT_NAME constant to target a different Atlassian tool.
Patched versions (as of the advisory date)
- Jira Software / Jira Service Management: ≥ 8.13.0
- Confluence: ≥ 7.19.0
- Bitbucket Server/Data Center: ≥ 7.21.0
- Bamboo: ≥ 9.0.0
- Fisheye/Crucible: ≥ 4.8.0
- Statuspage: ≥ 2023.12.0 (cloud‑only, always patched)
- Opsgenie (cloud): always patched
If you run a different product, adjust the MIN_SAFE_VERSION accordingly.
# file: check_atlassian_vuln.py
"""
Detect whether an Atlassian product is running a version vulnerable to
the critical file‑access flaw (CVE‑2024‑XXXXX).
Requirements:
pip install requests python-dotenv
"""
import os
import sys
from typing import Tuple
import requests
from dotenv import load_dotenv
# ----------------------------------------------------------------------
# 1️⃣ Load configuration (see Step 4)
# ----------------------------------------------------------------------
load_dotenv() # reads .env into os.environ
BASE_URL = os.getenv("ATLASSIAN_BASE_URL") # e.g. https://jira.example.com
API_TOKEN = os.getenv("ATLASSIAN_API_TOKEN") # personal API token
PRODUCT_NAME = os.getenv("ATLASSIAN_PRODUCT", "jira") # jira, confluence, bitbucket, ...
if not BASE_URL or not API_TOKEN:
sys.exit("❌ ERROR: Set ATLASSIAN_BASE_URL and ATLASSIAN_API_TOKEN in .env")
# ----------------------------------------------------------------------
# 2️⃣ Define the minimum safe version per product (from Atlassian advisory)
# ----------------------------------------------------------------------
MIN_SAFE_VERSIONS = {
"jira": "8.13.0",
"confluence": "7.19.0",
"bitbucket": "7.21.0",
"bamboo": "9.0.0",
"fisheye": "4.8.0",
"crucible": "4.8.0",
# Statuspage & Opsgenie are SaaS; assume patched if reachable.
}
def get_product_endpoint(product: str) -> str:
"""Return the REST endpoint that yields server version info."""
base = BASE_URL.rstrip("/")
if product == "jira":
return f"{base}/rest/api/2/serverInfo"
if product == "confluence":
return f"{base}/rest/api/serverInfo"
if product in ("bitbucket", "stash"):
return f"{base}/rest/api/1.0/application-properties"
if product == "bamboo":
return f"{base}/rest/api/latest/serverInfo"
if product in ("fisheye", "crucible"):
return f"{base}/rest-service-fe/serverInfo"
# fallback – try generic serverInfo
return f"{base}/rest/api/2/serverInfo"
def parse_version_from_json(data: dict, product: str) -> str:
"""Extract a version string from the JSON payload."""
try:
if product == "jira":
return data["version"]
if product == "confluence":
return data["versionNumber"]
if product in ("bitbucket", "stash"):
# Bitbucket returns a flat map; look for 'version'
return data.get("version") or data.get("plugin.version")
if product == "bamboo":
return data["version"]
if product in ("fisheye", "crucible"):
return data["version"]
except (KeyError, TypeError) as exc:
raise ValueError(f"Unable to parse version for {product}: {exc}") from exc
raise ValueError(f"Unknown product '{product}' – cannot extract version")
def is_version_vulnerable(current: str, minimum_safe: str) -> bool:
"""
Simple semantic‑version comparison (major.minor.patch).
Returns True if current < minimum_safe.
"""
def normalize(v: str) -> Tuple[int, int, int]:
parts = v.split(".")
# pad missing parts with 0
parts += ["0"] * (3 - len(parts))
return tuple(int(p) for p in parts[:3])
return normalize(current) < normalize(minimum_safe)
def main() -> None:
endpoint = get_product_endpoint(PRODUCT_NAME.lower())
headers = {
"Authorization": f"Bearer {API_TOKEN}",
"Accept": "application/json",
}
try:
resp = requests.get(endpoint, headers=headers, timeout=10)
resp.raise_for_status()
except requests.RequestException as exc:
sys.exit(f"❌ Network or HTTP error while calling {endpoint}: {exc}")
try:
payload = resp.json()
current_version = parse_version_from_json(payload, PRODUCT_NAME.lower())
except (ValueError, KeyError) as exc:
sys.exit(f"❌ Failed to interpret response: {exc}")
min_safe = MIN_SAFE_VERSIONS.get(PRODUCT_NAME.lower())
if not min_safe:
sys.exit(f"⚠️ No safe‑version threshold defined for product '{PRODUCT_NAME}'. "
"Assume latest cloud version is safe.")
if is_version_vulnerable(current_version, min_safe):
print(
f"🚨 VULNERABLE: {PRODUCT_NAME.capitalize()} version {current_version} "
f"< required {min_safe}. Please upgrade immediately."
)
else:
print(
f"✅ SAFE: {PRODUCT_NAME.capitalize()} version {current_version} "
f"≥ required {min_safe}. No action needed."
)
if __name__ == "__main__":
main()
How to run
# 1️⃣ Create .env (see Step 4)
# 2️⃣ Execute
python check_atlassian_vuln.py
// file: check-atlassian-vuln.ts
/**
* Detect whether an Atlassian product is running a version vulnerable to
* the critical file‑access flaw (CVE‑2024‑XXXXX).
*
* Prerequisites:
* npm install axios dotenv
* (Optional) npm install --save-dev typescript @types/node ts-node
*/
import * as dotenv from "dotenv";
import axios, { AxiosResponse } from "axios";
// ----------------------------------------------------------------------
// 1️⃣ Load environment variables
// ----------------------------------------------------------------------
dotenv.config();
const BASE_URL = process.env.ATLASSIAN_BASE_URL; // e.g. https://confluence.example.com
const API_TOKEN = process.env.ATLASSIAN_API_TOKEN;
const PRODUCT = (process.env.ATLASSIAN_PRODUCT || "jira").toLowerCase(); // jira|confluence|bitbucket|bamboo|fisheye|crucible
if (!BASE_URL || !API_TOKEN) {
console.error(
"❌ ERROR: Please set ATLASSIAN_BASE_URL and ATLASSIAN_API_TOKEN in .env"
);
process.exit(1);
}
// ----------------------------------------------------------------------
// 2️⃣ Minimum safe versions (per advisory)
// ----------------------------------------------------------------------
const MIN_SAFE_VERSIONS: Record<string, string> = {
jira: "8.13.0",
confluence: "7.19.0",
bitbucket: "7.21.0",
bamboo: "9.0.0",
fisheye: "4.8.0",
crucible: "4.8.0",
};
// ----------------------------------------------------------------------
// 3️⃣ Helper: build the correct endpoint
// ----------------------------------------------------------------------
function getEndpoint(product: string): string {
const base = BASE_URL.replace(/\/+$/, ""); // strip trailing slash
switch (product) {
case "jira":
return `${base}/rest/api/2/serverInfo`;
case "confluence":
return `${base}/rest/api/serverInfo`;
case "bitbucket":
case "stash":
return `${base}/rest/api/1.0/application-properties`;
case "bamboo":
return `${base}/rest/api/latest/serverInfo`;
case "fisheye":
case "crucible":
return `${base}/rest-service-fe/serverInfo`;
default:
// fallback to Jira endpoint – many products expose it
return `${base}/rest/api/2/serverInfo`;
}
}
// ----------------------------------------------------------------------
// 4️⃣ Helper: extract version from JSON payload
// ----------------------------------------------------------------------
function extractVersion(data: any, product: string): string {
try {
switch (product) {
case "jira":
return data.version;
case "confluence":
return data.versionNumber;
case "bitbucket":
case "stash":
// Bitbucket returns a flat map; try common keys
return data.version ?? data["plugin.version"];
case "bamboo":
return data.version;
case "fisheye":
case "crucible":
return data.version;
default:
throw new Error(`Unsupported product: ${product}`);
}
} catch (e) {
throw new Error(`Failed to parse version: ${e}`);
}
}
// ----------------------------------------------------------------------
// 5️⃣ Simple semantic version comparator (major.minor.patch)
// ----------------------------------------------------------------------
function isVersionVulnerable(current: string, minimum: string): boolean {
const norm = (v: string): number[] => {
const parts = v.split(".").map(Number);
// pad missing parts with 0
while (parts.length < 3) parts.push(0);
return parts;
};
const cur = norm(current);
const min = norm(minimum);
return cur[0] < min[0] ||
(cur[0] === min[0] && cur[1] < min[1]) ||
(cur[0] === min[0] && cur[1] === min[1] && cur[2] < min[2]);
}
// ----------------------------------------------------------------------
// 6️⃣ Main routine
// ----------------------------------------------------------------------
async function main(): Promise<void> {
const endpoint = getEndpoint(PRODUCT);
const headers = {
Authorization: `Bearer ${API_TOKEN}`,
Accept: "application/json",
};
try {
const resp: AxiosResponse = await axios.get(endpoint, {
headers,
timeout: 10_000,
});
const currentVersion = extractVersion(resp.data, PRODUCT);
const minSafe = MIN_SAFE_VERSIONS[PRODUCT];
if (!minSafe) {
console.warn(
`⚠️ No safe‑version threshold defined for ${PRODUCT}. Assuming latest cloud is safe.`
);
process.exit(0);
}
if (isVersionVulnerable(currentVersion, minSafe)) {
console.error(
`🚨 VULNERABLE: ${PRODUCT
.charAt(0)
.toUpperCase()}${PRODUCT.slice(1)} version ${currentVersion} < required ${minSafe}. Upgrade now!`
);
process.exit(1); // non‑zero exit code for CI/CD alerts
} else {
console.log(
`✅ SAFE: ${PRODUCT
.charAt(0)
.toUpperCase()}${PRODUCT.slice(1)} version ${currentVersion} ≥ required ${minSafe}.`
);
process.exit(0);
}
} catch (err: any) {
if (err.isAxiosError) {
console.error(
`❌ HTTP error calling ${endpoint}: ${err.response?.status ?? err.message}`
);
} else {
console.error(`❌ Unexpected error: ${err.message}`);
}
process.exit(1);
}
}
// Run the async function
main().catch((e) => {
console.error("❌ Fatal error:", e);
process.exit(1);
});
How to run (JavaScript)
# 1️⃣ Create .env (see Step 4)
# 2️⃣ Execute
node check-atlassian-vuln.js # if you compiled TS to JS, or:
npx ts-node check-atlassian-vuln.ts
<a name="step-4-configuration"></a>
Create a file named .env in the project root (add it to .gitignore).
| Variable | Example | Description |
|---|---|---|
ATLASSIAN_BASE_URL | https://jira.example.com | Base URL of the Atlassian instance (no trailing slash). |
ATLASSIAN_API_TOKEN | abcdef1234567890abcdef1234567890 | Personal API token (Jira/Confluence) or personal access token (Bitbucket). Must have at least read permissions on the server‑info endpoint. |
ATLASSIAN_PRODUCT | jira (optional) | One of: jira, confluence, bitbucket, bamboo, fisheye, crucible. Defaults to jira. |
LOG_LEVEL | info (optional) | If you extend the code with a logger (e.g., pino or winston), set verbosity here. |
Security tip – Never hard‑code tokens. Use secret‑management tools (AWS Secrets Manager, HashiCorp Vault, GitHub Actions secrets, etc.) in CI/CD pipelines and inject them as environment variables at runtime.
<a name="step-5-common-patterns"></a>
Below are reusable snippets you’ll likely need when building tooling around Atlassian products.
import requests
from typing import Any, Dict
def atl_request(
method: str,
path: str,
base_url: str,
token: str,
json_body: Dict[str, Any] | None = None,
params: Dict[str, Any] | None = None,
) -> Dict[str, Any]:
"""
Low‑level helper for Atlassian REST calls.
Raises requests.HTTPError on non‑2xx responses.
"""
url = f"{base_url.rstrip('/')}/{path.lstrip('/')}"
headers = {
"Authorization": f"Bearer {token}",
"Accept": "application/json",
"Content-Type": "application/json",
}
resp = requests.request(
method,
url,
headers=headers,
json=json_body,
params=params,
timeout=15,
)
resp.raise_for_status()
return resp.json() if resp.content else {}
Usage:
data = atl_request("GET", "rest/api/2/serverInfo", BASE_URL, API_TOKEN)
import axios from "axios";
interface AtlassianConfig {
baseUrl: string;
token: string;
}
export async function atlRequest<T = any>(
method: string,
path: string,
config: AtlassianConfig,
data?: any,
params?: Record<string, any>
): Promise<T> {
const url = `${config.baseUrl.replace(/\/+$/, "")}/${path.replace(/^\/+/, "")}`;
const resp = await axios.request<T>({
method,
url,
headers: {
Authorization: `Bearer ${config.token}`,
Accept: "application/json",
"Content-Type": "application/json",
},
data,
params,
timeout: 15000,
});
return resp.data;
}
Usage:
const info = await atlRequest<{ version: string }>(
"GET",
"rest/api/2/serverInfo",
{ baseUrl: BASE_URL, token: API_TOKEN }
);
console.log(info.version);
Both languages can reuse a tiny semver helper:
# python version_utils.py
def version_lt(v1: str, v2: str) -> bool:
def norm(v):
return tuple(int(x) for x in v.split(".") + ["0"] * (3 - len(v.split("."))))
return norm(v1) < norm(v2)
// ts versionUtils.ts
export function versionLt(a: string, b: string): boolean {
const norm = (s: string) => s.split(".").map(Number).concat([0, 0, 0]).slice(0, 3);
const [aM, aMi, aP] = norm(a);
const [bM, bMi, bP] = norm(b);
return aM < bM || (aM === bM && aMi < bMi) || (aM === bM && aMi === bMi && aP < bP);
}
These helpers keep your main logic clean and testable.
<a name="step-6-troubleshooting"></a>
| Symptom | Likely Cause | Fix |
|---|---|---|
401 Unauthorized | Token missing, expired, or insufficient scopes. | Regenerate a fresh API token (Jira/Confluence) or Personal Access Token (Bitbucket). Ensure the token has read permission for the server‑info endpoint. |
403 Forbidden | Token lacks required permission or IP‑allowlist blocks the request. | Verify token scopes; if using Atlassian Cloud, ensure the token isn’t restricted to specific projects. For Server/Data Center, check that your user is in the administrators group or has the View Administration permission. |
404 Not Found | Wrong base URL or endpoint path. | Double‑check ATLASSIAN_BASE_URL. For Bitbucket Server, the endpoint is /rest/api/1.0/application-properties. |
500 Internal Server Error | Server misconfiguration or temporary outage. | Retry after a short back‑off; check Atlassian status page. |
JSONDecodeError / KeyError when parsing version | The instance returns an unexpected format (e.g., behind a reverse proxy that strips headers). | Add logging of resp.text before parsing; adjust extractVersion to handle the observed shape. |
Version comparison says vulnerable but UI shows newer | Version string contains build metadata (e.g., 8.13.0‑SNAPSHOT). | Strip non‑numeric suffixes before comparison: re.sub(r'[^0-9.]', '', version). |
Network timeout | Outbound firewall blocks port 443 or DNS resolution fails. | Ensure the host running the script can reach BASE_URL on HTTPS (port 443). Test with curl -v https://jira.example.com/rest/api/2/serverInfo. |
Quick test command (curl)
curl -s -H "Authorization: Bearer $API_TOKEN" \
https://jira.example.com/rest/api/2/serverInfo | jq .
If this works, your environment is set; the script should follow suit.
<a name="step-7-production-checklist"></a>
Before you ship any automation that talks to Atlassian (whether for vulnerability scanning, compliance, or ops), run through this list.
| ✅ Item | Why it matters |
|---|---|
Secrets management – Store ATLASSIAN_API_TOKEN in a vault or CI secret store, never in plain text. | |
Least‑privilege token – Create a token with only read access to the needed REST endpoints (no admin rights unless absolutely required). | |
HTTPS enforcement – Verify the script refuses to run if BASE_URL does not start with https://. | |
Timeouts & retries – Use exponential back‑off (e.g., retrying library or axios-retry) to avoid hammering a troubled instance. | |
Rate‑limit awareness – Atlassian Cloud enforces ~100 req/min per user token; add a Retry-After header handler if you plan to poll many instances. | |
| Logging – Log request IDs, response status, and duration (but never log the token). Use structured logging (JSON) for easy ingestion into SIEM. | |
| Alerting – Emit a non‑zero exit code or push a metric to your monitoring system when a vulnerable version is detected. | |
Version‑source verification – For Server/Data Center, cross‑check the REST version with the bundled version.txt file in the installation directory as a sanity check. | |
Dependency hygiene – Pin exact versions of requests, axios, dotenv, etc., in requirements.txt / package-lock.json to avoid supply‑chain surprises. | |
| Testing – Run the script against a known‑good (patched) instance and a known‑bad (old) instance in a staging environment to confirm both paths work. | |
Documentation – Keep a README.md that explains how to set up the env vars, what products are supported, and how to interpret the exit codes. | |
| License & compliance – Verify that your usage of Atlassian REST APIs complies with your organization’s Atlassian license terms (generally, read‑only calls are permitted). |
When all of the above are satisfied, you can safely schedule the script (e.g., via cron, Azure Pipelines, GitHub Actions, or Jenkins) to run daily and automatically create tickets or Slack alerts whenever a vulnerable version is detected.
You now have:
.env example.Feel free to copy the snippets, adapt them to your fleet of Atlassian products, and integrate them into your internal security‑automation pipeline. Stay safe! 🚀
Author: ICARAX Engineering Team
Date: 2025‑09‑25
References
Source: Security Week AI
Follow ICARAX for more AI insights and tutorials.
