Skip to content

fix(simulation): stop returning Python tracebacks in API error responses - #745

Open
andesyteoss wants to merge 1 commit into
666ghj:mainfrom
andesyteoss:fix/cwe209-simulation-stack-902f
Open

fix(simulation): stop returning Python tracebacks in API error responses#745
andesyteoss wants to merge 1 commit into
666ghj:mainfrom
andesyteoss:fix/cwe209-simulation-stack-902f

Conversation

@andesyteoss

@andesyteoss andesyteoss commented Jul 27, 2026

Copy link
Copy Markdown

fix(simulation): stop returning Python tracebacks in API error responses

Summary

The Flask blueprint backend/app/api/simulation.py currently returns traceback.format_exc() inside the JSON body of every 500 response (around 20 endpoints). Any client that triggers a handled Exception gets back the full Python stack trace, which discloses:

  • absolute filesystem paths of the deployment (e.g. install directory, virtualenv location),
  • exact versions and internal module layout of third-party libraries in use,
  • internal function names, file structure, and line numbers of MiroFish's own code.

This is a textbook CWE-209: Generation of Error Message Containing Sensitive Information. On its own the impact is information disclosure (Low/Info), but it's a useful recon primitive for an attacker planning a follow-up (dependency-CVE targeting, path guessing for other file endpoints, etc.).

Why this is reachable by an unauthenticated attacker

  • The backend has no authentication middleware on the simulation blueprint (grep -n "before_request\|@login_required" backend/app/api/simulation.py returns nothing).
  • CORS is configured with origins: "*", so any origin can call the API from a browser.
  • Every affected endpoint takes user-controlled path or JSON parameters (e.g. graph_id, simulation_id, entity_uuid) that are easy to make invalid, which is enough to trip the except Exception branch and produce the leaky response.

Fix

Replace the leaking pattern

except Exception as e:
    logger.error(f"...: {str(e)}")
    return jsonify({
        "success": False,
        "error": str(e),
        "traceback": traceback.format_exc(),
    }), 500

with

except Exception as e:
    logger.error(f"...: {str(e)}", exc_info=True)
    return jsonify({
        "success": False,
        "error": str(e),
    }), 500

The full traceback is still captured server-side via logger.error(..., exc_info=True) so operators keep the debugging signal; only the client-facing JSON is stripped. The import traceback line is also removed since it's no longer used.

Scope: backend/app/api/simulation.py only, ~20 handlers. Diff is +62/-93 in a single file — no behavior change beyond the response body shape.

Proof of concept

With the backend running locally (python backend/run.py or however you normally start it):

# Any invalid graph_id trips the except branch.
curl -s http://127.0.0.1:5000/api/simulation/entities/does-not-exist | jq .

Before the fix, the JSON response contains a "traceback" field with the full Python stack, absolute paths, and library internals. After the fix, the response is {"success": false, "error": "..."} with no stack trace, and the traceback is written to the server log instead.

Every route mentioned above exists in backend/app/api/simulation.py (e.g. line 77 @simulation_bp.route('/entities/<graph_id>', ...)).

Testing

  • Ran the backend test suite: 130/130 passing after the change.
  • Manually triggered several of the patched handlers with bad IDs and confirmed the response no longer includes traceback, while exc_info=True produces the full trace in the server log.

Adversarial review

Before submitting we tried to disprove this. Things we considered:

  • Is Flask running with DEBUG=False enough? No — DEBUG only controls Flask's own default 500 page and the interactive debugger. These handlers explicitly build the JSON body themselves and include traceback.format_exc() regardless of debug mode.
  • Is there an auth layer we missed? We grepped for before_request, login_required, JWT/session middleware, and router-level auth dependencies. None are applied to the simulation blueprint. Combined with CORS(origins="*"), the endpoints are reachable by any network-adjacent attacker.
  • Is the information really sensitive? Filesystem paths and dependency internals on their own aren't catastrophic, but they're clearly beyond what a public API should hand out, and CWE-209 exists precisely for this. Removing the field is a straightforward hardening step with no downside.

Note for maintainers (out of scope for this PR)

The same "traceback": traceback.format_exc() pattern exists in a few other files (backend/app/api/graph.py, backend/app/api/report.py). We kept this PR narrowly scoped to simulation.py to keep the diff reviewable, but you may want to apply the same treatment there in a follow-up.

Return only the exception message to clients; log full traceback
server-side via logger.error(..., exc_info=True). Prevents disclosure
of internal paths, library versions, and code structure (CWE-209).
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