Skip to content

Technical improvment - #37

Merged
ellipse2v merged 3 commits into
mainfrom
technical_improvment
Aug 29, 2026
Merged

Technical improvment#37
ellipse2v merged 3 commits into
mainfrom
technical_improvment

Conversation

@ellipse2v

Copy link
Copy Markdown
Owner

add performance update, hardening and multi user capabilities

…port, docker-compose IaC plugin

Performance
    - data_loader.py: @functools.cache on all 9 zero-argument loaders — the 43 MB
      enterprise-attack.json (and CAPEC/D3FEND/STRIDE/NIST/CIS corpora) now parse
      once per process instead of once per MitreMapping instance (4 instantiation
      sites). Fixed a mutation-in-place bug in mitre_mapping_module.map_threat_to_mitre()
      that would have corrupted the now-shared cache across requests/tests.
    - tooling/build_attack_techniques_cache.py (new): derives external_data/attack_techniques.json
      (823 techniques, 1.2 MB) from the full STIX bundle, same pattern as the other
      precomputed mappings — load_attack_techniques() reads only the derived file.
    - models_module.py: ThreatModel(name) no longer requires a CVEService — lazy
      default fallback (same as ReportGenerator's), CVEService already degrades
      gracefully on missing cve_definitions.yml. Closes tech-debt #9.
    - generation/report_generator.py: split 2296 → 1027 lines into report_scoring.py
      (ScoringMixin + unified score_threat() for pytm/AI threats), report_ai_analysis.py
      (AIAnalysisMixin: CISO triage, path narratives, GDAF debate), report_project.py
      (ProjectReportMixin: multi-model project mode). ReportGenerator composes all
      three via mixins; public API and self.xxx access patterns unchanged.
    - server.py: waitress replaces Werkzeug's dev server in production (kept only
      for FLASK_DEBUG=true on loopback).

    Server security hardening
    - server.py: _reject_cross_origin_mutations() — Host/Origin allowlist check on
      every mutating request, defeats both cross-origin drive-by and DNS rebinding
      (no CSRF token: no authenticated session exists by default to protect).
    - server.py: CSP script-src uses a per-request nonce instead of
      'unsafe-inline'/'unsafe-eval' — audited every page (same-origin vendored
      scripts only, no eval()/new Function() anywhere); verified with a real
      headless-Chromium run (Playwright): zero CSP violations, editor fully
      interactive.
    - server.py: opt-in bearer auth (SECOPSTM_REQUIRE_AUTH + SECOPSTM_API_TOKEN),
      off by default so the documented Docker onboarding stays frictionless;
      refuses to start if enabled without a token.
    - utils.py: minimal_subprocess_env() — allowlist (PATH/HOME/LANG/...) passed to
      every `dot` subprocess call so LLM API keys held in os.environ are never
      inherited by Graphviz.
    - architecture.md tech-debt #13 (XSS via innerHTML) verified already mitigated
      (html.escape/_sanitize_name/ElementTree escaping already in place) — no code
      change, note corrected.

    CI/CD gate, diff, SARIF (action.yml, __main__.py)
    - Fixed 4 real bugs found wiring the existing --gate/--diff into action.yml:
      * run_gate_check()/diff_threat_reports() treated severity as a plain string
        (.upper()) — it's {score, level, formatted_score}; crashed on any real report.
      * compare_threat_reports() keyed threats on a "name" field the real schema
        never populates, silently colliding distinct threats (8/22 lost on a real
        test report). Now keys on "description".
      * action.yml's jq queries read .statistics.total_threats/.by_severity.X —
        the real schema is .statistics.total/.by_severity_level.X. Gate, PR
        comment and step summary had shown 0 for every count since the action
        was written.
    - action.yml: gate now calls `secopstm --gate --fail-on --baseline
      --accepted-risks` for real instead of a bash reimplementation that ignored
      baseline/accepted-risks entirely; new "Diff against baseline" step in the
      PR comment + step summary.
    - generation/sarif_generator.py (new): SARIF 2.1.0 export, wired via --sarif
      (CLI) and sarif: true (action.yml, auto-uploads to Security > Code scanning).

    Docker Compose IaC plugin
    - iac_plugins/docker_compose_plugin.py (new): services→Servers (type inferred
      from image), networks→Boundaries, depends_on + shared-network→Dataflows,
      per-service BOM generation. Auto-discovered, no manual wiring.
    - Fixed a real dispatch bug found integrating it: __main__.py reconstructed
      the argparse attribute from plugin.name without converting hyphens
      ("docker-compose" → "docker-compose_path" instead of "docker_compose_path")
      — any hyphenated plugin name was silently never dispatched.
    - Kubernetes/Helm scoped out for a follow-up session (bigger: multiple
      resource kinds + Helm templating).
Lightweight multi-user workspaces:
  - pytm_build_lock() (RLock) in core/model_factory.py serializes every
    create_threat_model()+process_threats() pair. pytm >=1.4 keeps TM's
    element/flow registry at class level, so one request's TM.reset() could
    wipe another's in-flight state. Wired into report_project.py and every
    server service (diagram/export/model_management).
  - GET /api/workspaces + workspace picker in simple_mode.html, gated on
    SECOPSTM_WORKSPACES_DIR (feature invisible when the var is unset).
  - /api/set_project_path now rejects paths outside SECOPSTM_WORKSPACES_DIR
    when that var is set; a session could otherwise point at another user's
    project directory and read it via BOM/context auto-discovery.

  Server bug fixes:
  - 4 routes used `await request.get_json()`; this is Flask, not Quart, so
    awaiting the dict raised TypeError and /api/export_json,
    /api/validate_markdown, /api/set_project_path and /api/diff_reports
    returned 500 on every call. Now synchronous like every other route.
  - Revert CSP script-src to 'self' 'unsafe-inline'. The per-request nonce
    introduced in f1c2bcd does not cover inline on*= event handlers, which
    silently disabled every toolbar button (Load Project, Generate All, all
    Export buttons, the Component Panel). 'unsafe-eval' stays out; g.csp_nonce
    is still generated but is no longer a source in the header.

  Docs (audit follow-up):
  - prompts.yaml path corrected everywhere to
    threat_analysis/config/prompts.yaml (README, DOCKER_HUB, docs/, docstrings).
  - docs/user_context.md rewritten: the documented --ai-context-file /
    rename-to-user_context.json activation workflow never worked
    (RAGThreatGenerator is always constructed with no path override).
It's a copy-into-your-repo example: it references model files that only
  exist in a consumer repo (threat_model.md, ansible/, *.tf) and the
  ellipse2v/SecOpsTM@v1 tag, so it failed on every PR here. Moved to
  examples/; docs updated to point there
@ellipse2v
ellipse2v merged commit 9046a1a into main Aug 29, 2026
1 check passed
@ellipse2v
ellipse2v deleted the technical_improvment branch August 29, 2026 16:28
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