Technical improvment - #37
Merged
Merged
Conversation
…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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
add performance update, hardening and multi user capabilities