Skip to content

[high] fix: [ipqs_fraud_and_risk_scoring] raise a real exception on API failure - #827

Open
elhoim wants to merge 1 commit into
MISP:mainfrom
elhoim:fix/010-ipqs-bare-raise
Open

elhoim wants to merge 1 commit into
MISP:mainfrom
elhoim:fix/010-ipqs-bare-raise

Conversation

@elhoim

@elhoim elhoim commented Aug 31, 2026 •

Copy link
Copy Markdown
Contributor

BLUF — An IPQualityScore API failure raises an unhandled RuntimeError instead of a usable error message.

  • Problem — RequestHandler.get() in expansion/ipqs_fraud_and_risk_scoring.py uses a bare raise inside a try body when the API returns success: false, producing RuntimeError: No active exception to reraise, which the surrounding except (ConnectTimeout, ProxyError, InvalidURL) does not cover.
  • Fix — adds a module-level IPQualityScoreError carrying the API message, raises that instead, and wraps the ip/url/email/phone/username/password dispatch in handler() with a try/except returning misperrors.
  • Effect — a bad key, exhausted quota or malformed IOC now surfaces the API's own reason rather than a traceback; the file-type branch is unchanged in behaviour.

The defect

In RequestHandler.get(), when the IPQualityScore API responds with success: false, the handler sets the error message and then executes a bare raise inside the try body — not inside an except clause:

if str(response["success"]) != "True":
    msg = response["message"]
    logger.error("Error: %s", {msg})
    misperrors["error"] = msg
    raise

A bare raise with no exception currently being handled produces RuntimeError: No active exception to reraise. That RuntimeError is not a member of the except (ConnectTimeout, ProxyError, InvalidURL) tuple that wraps this code, so it propagates straight out of get(), out of handler(), and out of the module entirely.

Impact

Whenever the IPQualityScore API reports a failure for an enrichment lookup (bad/expired API key, over quota, malformed IOC, etc.), the module crashes with an unhandled RuntimeError instead of returning the informative error MISP module callers expect. An analyst enriching an attribute sees a generic module crash/traceback instead of the actual reason the IPQS lookup failed, which is unhelpful for triage and makes the failure harder to diagnose.

The fix

Introduced a module-level IPQualityScoreError exception carrying the API's error message and raised that instead of the bare raise. handler()'s ip/url/email/phone/username/password lookup dispatch is now wrapped in a try/except IPQualityScoreError: return misperrors, so an API-reported failure now returns the standard MISP module error response instead of crashing. The file_query_input_type branch, which is mutually exclusive with the wrapped branches, was changed from elif to a separate if to accommodate the new try block; behavior for file-type attributes is unchanged.

The two other raise statements the finding also flagged (:158, :183) were left as-is: they sit inside except HTTPError as error: blocks where an exception is genuinely active, so they are correct re-raises, not the described defect.

Behaviour change

Before this fix, an IPQS API failure crashed the module with an unhandled RuntimeError. After this fix, it returns misperrors with the API's error message, which is the module's documented error-reporting contract — this brings the failure path in line with how every other error in this module is already reported, rather than introducing new behavior.

Verification

  • flake8 --extend-exclude=misp_modules/lib/,tests/,website/ on the changed file: clean, no output.
  • python -m py_compile misp_modules/modules/expansion/ipqs_fraud_and_risk_scoring.py: succeeds.
  • Full module test suite: 161 passed, 4 skipped, 5 subtests passed in 150.44s (0:02:30).
  • Verified the module still imports cleanly on the live test server (log line: MISP module ipqs_fraud_and_risk_scoring (type=expansion) imported).

Found during a review of the repository; other findings are being submitted as separate PRs.

🤖 Generated with Claude Code

When the IPQualityScore API answers with success: false — an invalid API
key, an exhausted quota, an unsupported value — RequestHandler.get()
stored the API message in misperrors and then executed a bare raise from
inside the try block, where no exception is active. Python turns that
into RuntimeError("No active exception to reraise"), which the
except (ConnectTimeout, ProxyError, InvalidURL) clause does not match, so
it escaped handler() entirely: the analyst saw an opaque module crash
instead of the message IPQS actually returned. The bare raise now becomes
a module-specific IPQualityScoreError carrying that message, and handler()
catches it around the lookup dispatch and returns misperrors, which is the
error contract MISP expects.

Verified with flake8 on the changed file and the full test suite against a
local modules server: 161 passed, 4 skipped, 5 subtests passed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018dfYpyaSZd1nxSRLr8suj8
@elhoim elhoim changed the title fix: [ipqs_fraud_and_risk_scoring] raise a real exception on API failure [high] fix: [ipqs_fraud_and_risk_scoring] raise a real exception on API failure Sep 3, 2026

This branch has not been deployed

No deployments
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.

1 participant