Skip to content

Fix: guard Categories access in extract_capec_names (Issue #2487)#2517

Open
immortal71 wants to merge 22 commits intoOWASP:masterfrom
immortal71:fix/capec-categories-2487
Open

Fix: guard Categories access in extract_capec_names (Issue #2487)#2517
immortal71 wants to merge 22 commits intoOWASP:masterfrom
immortal71:fix/capec-categories-2487

Conversation

@immortal71
Copy link
Contributor

Problem: scripts/capec_map_enricher.py raised an unhandled KeyError when the CAPEC JSON lacked the top-level 'Categories' section.\n\nFix: Add defensive checks to ensure 'Categories' and 'Category' exist and that 'Category' is a list before iterating. When absent or malformed, the function logs a warning and continues extracting names from 'Attack_Pattern' entries.\n\nTests: Unit tests covering missing/malformed Categories already exist and passed locally.

Accessing catalog['Categories']['Category'] without guards caused an
unhandled KeyError if the CAPEC JSON had no Categories section.

Added defensive checks consistent with existing guards for
Attack_Patterns and Attack_Pattern. Logs a warning and skips the
categories block if the key is absent or malformed.

Fixes OWASP#2488
Copilot AI review requested due to automatic review settings March 5, 2026 03:21
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds defensive handling in extract_capec_names to avoid crashing when CAPEC catalog JSON is missing or has malformed Categories data, while still extracting names from Attack_Pattern entries.

Changes:

  • Guard access to catalog["Categories"]["Category"] with presence/type checks.
  • Emit warnings (instead of raising) when Categories/Category is missing or malformed, then continue processing.

Comment on lines +67 to +76
elif "Category" not in catalog["Categories"]:
logging.warning("No 'Category' key found in categories section")
elif not isinstance(catalog["Categories"]["Category"], list):
logging.warning("'Category' is not a list")
else:
for category in catalog["Categories"]["Category"]:
if "_ID" in category and "_Name" in category:
capec_id = int(category["_ID"])
capec_name = category["_Name"]
capec_names[capec_id] = capec_name
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

catalog["Categories"] is assumed to be a mapping. If CAPEC JSON includes a malformed Categories value (e.g., null/None), the check "Category" not in catalog["Categories"] will raise TypeError and the function will still crash. Consider validating catalog.get("Categories") is a dict (or at least supports in/get) before checking for "Category", and log a dedicated warning when the section isn’t an object.

Suggested change
elif "Category" not in catalog["Categories"]:
logging.warning("No 'Category' key found in categories section")
elif not isinstance(catalog["Categories"]["Category"], list):
logging.warning("'Category' is not a list")
else:
for category in catalog["Categories"]["Category"]:
if "_ID" in category and "_Name" in category:
capec_id = int(category["_ID"])
capec_name = category["_Name"]
capec_names[capec_id] = capec_name
else:
categories = catalog.get("Categories")
# V2.2: Validate input structure before use to fail safely on malformed data
if not isinstance(categories, dict):
logging.warning("Invalid 'Categories' section in catalog; expected an object")
elif "Category" not in categories:
logging.warning("No 'Category' key found in categories section")
else:
category_entries = categories["Category"]
if not isinstance(category_entries, list):
logging.warning("'Category' is not a list")
else:
for category in category_entries:
if "_ID" in category and "_Name" in category:
capec_id = int(category["_ID"])
capec_name = category["_Name"]
capec_names[capec_id] = capec_name

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +76
if "Categories" not in catalog:
logging.warning("No 'Categories' key found in catalog")
elif "Category" not in catalog["Categories"]:
logging.warning("No 'Category' key found in categories section")
elif not isinstance(catalog["Categories"]["Category"], list):
logging.warning("'Category' is not a list")
else:
for category in catalog["Categories"]["Category"]:
if "_ID" in category and "_Name" in category:
capec_id = int(category["_ID"])
capec_name = category["_Name"]
capec_names[capec_id] = capec_name
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new defensive branches for missing/malformed Categories (Categories missing, Category missing, Category not a list) don’t appear to have unit tests in tests/scripts/capec_map_enricher_utest.py. Please add focused tests asserting the function returns the Attack_Pattern-derived mappings and emits the expected warnings for each of these cases.

Copilot uses AI. Check for mistakes.
Comment on lines +65 to +67
if "Categories" not in catalog:
logging.warning("No 'Categories' key found in catalog")
elif "Category" not in catalog["Categories"]:
Copy link

Copilot AI Mar 5, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description says “Unit tests covering missing/malformed Categories already exist”, but the current test suite (e.g., tests/scripts/capec_map_enricher_utest.py) doesn’t include cases for missing/malformed Categories or assertions for the new warning messages. Either update the PR description to match reality or include the missing tests as part of this PR.

Copilot uses AI. Check for mistakes.
…te_limiter prod bypass, schema changeset tests
@immortal71
Copy link
Contributor Author

@sydseter the pr is ready for review !!

@rewtd
Copy link
Collaborator

rewtd commented Mar 5, 2026

@immortal71 there are a couple of good suggestions by @copilot code review[agent] that are worth implementing. Could you look at expanding the test coverage to cover the abuse cases that your code corrects?

Apply Copilot suggestion: use catalog.get('Categories') + isinstance(dict)
check before accessing nested keys, preventing TypeError when Categories
is null or a non-dict value.

Add unit tests for all missing/malformed Categories edge cases:
- Categories key absent
- Categories is None
- Categories is non-dict (e.g. list)
- Category key missing from dict
- Category value is not a list

Closes OWASP#2487
@immortal71
Copy link
Contributor Author

@rewtd done, is this good to go ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants