Tar Slip (CWE-22) — 3 import services:
threat_actor, lolbas, caldera: add path validation before extractall()
to prevent malicious zip members with ../ escaping the target directory.
(sigma, elastic, atomic already had this protection)
Path Traversal (CWE-23) — professional_reports.py:
Add _assert_safe_report_path() check on all 5 report endpoints to
verify the generated filepath stays within REPORT_OUTPUT_DIR.
Open Redirect (CWE-601) — sso.py:
Validate IdP redirect URL scheme (must be http/https) before
issuing RedirectResponse, blocking javascript: and data: redirects.
DOM XSS (CWE-79) — 4 frontend pages:
Create src/utils/url.ts with safeUrl() that rejects non-http/https
protocols; apply to actor.mitre_url, ref.url, intel.url.
Sanitize framework name to alphanumeric-only before DOM insertion.
Restrict evidence MIME types to an explicit safe allowlist (png/jpg/gif/webp).
Hardcoded credentials (CWE-798):
verify_gaps.py, create_wiki.py: replace literal passwords with
environment variable reads (AEGIS_ADMIN_PASSWORD, GITEA_PASSWORD).
- config.py: move REPORT_OUTPUT_DIR from /tmp (world-writable) to /app/reports
to prevent CWE-377 symlink attack vector (B108, only real security issue)
- main.py: log startup seed failures instead of silently swallowing them (B110)
- Add # nosec annotations to intentional try/except patterns that are by design:
Jira integration errors, email failures, DetachedInstanceError, storage errors,
and Jira session timeout (all B110/B112 false positives)
- Add # nosec B105 to false positives where bandit misidentifies config key
names and masking strings as hardcoded passwords
- Add .bandit config to skip B311 in seed_demo.py (random used for fake
demo data generation, not cryptographic purposes)
Picks up Debian security fixes for systemd (257.13), sqlite3 (3.46.1-7+deb13u1),
sed (4.9-2+deb13u1) and other packages flagged by Snyk. All Docker image CVEs
were Low severity; Snyk CI threshold is set to high so none blocked builds.
- .github/workflows/snyk.yml: scans backend (Python), frontend (npm)
and backend Docker image on every push/PR and weekly schedule.
Uses continue-on-error during initial cleanup phase.
Requires SNYK_TOKEN secret in GitHub repo settings.
- backend/requirements-lock.txt: exact pip freeze from production
container for accurate Snyk CVE scanning (no version ambiguity).
To enable: add SNYK_TOKEN to GitHub repo secrets (get token from
app.snyk.io -> Account Settings -> API Token).
npm ci installs exact versions from package-lock.json with no implicit
resolution, making builds fully reproducible and guaranteed to use the
audited safe dependency versions.
Group 1 - Dual validation rejection (9 tests):
_check_dual_validation: any single rejection is a veto (r or b == rejected
-> rejected). Removes the disputed state transition that broke tests expecting
immediate rejection when one lead rejects.
Group 2 - Reopen clears notes (2 tests):
reopen_test service was intentionally keeping red/blue validation notes but
tests (and TestEntity.reopen domain method) expect them cleared. Align service
with domain entity behavior.
Group 3 - Audit integrity hash (2 tests):
log_action: call db.refresh(entry) after initial flush and before computing
the HMAC hash. Without this, a DB round-trip (commit + refresh in tests)
retrieves a timestamp with different string representation, causing mismatch.
Group 4 - Tempo service API (3 tests):
- auto_log_test_worklog: make duration_seconds optional (default None) and
compute from test.red_started_at -> updated_at when not supplied.
- Add get_tempo_client() that raises InvalidOperationError when disabled,
matching what tests expect.
- test_tempo_service: set tempo_api_token/jira_account_id on admin_user so
the service proceeds past the has_tempo_configured guard.
Coverage threshold: change min_validated_for_full from 2 to 1 so that a single
fully dual-validated detected test yields TechniqueStatus.validated, matching
test_coverage_correct_after_dual_validation expectations.
When the wizard reconfigures and generates a new DB_PASSWORD, the existing
Postgres volume retains the old password (Docker only initializes credentials
on a fresh empty volume). The backend then fails to connect because .env
has the new password but Postgres still uses the old one.
Fix: run 'docker compose down -v' before 'up --build' whenever the wizard
reconfigures (SKIP_CONFIG=false), so Postgres always initializes with the
current .env credentials. Also add a pre-confirmation warning when existing
volumes are detected.
Snyk scan found 3 High severity vulns: two in ecdsa (pulled by python-jose)
and one in diskcache (pulled by pySigma, never imported). Remove both
vulnerable dependencies and migrate JWT handling to PyJWT. Fix
test_logout_revokes_token which broke because test stubs sys.modules[jose]
with a MagicMock at collection time; test now uses PyJWT directly.
Task D — Google-style docstrings (Args/Returns) on every public function,
method, and class across all 158 Python files in the backend. Zero ruff D
violations (pydocstyle Google convention).
Task E — Explanatory one-line comment before every code line (~11600 new
comments). ruff check passes clean after isort re-sort.
SystemPage now only shows operational content: MITRE Sync, Intel Scan,
ATT&CK Evaluations, Scheduled Jobs, and Template Management.
Settings gets two new admin-only tabs:
- "SSO / Azure AD": full SAML 2.0 wizard (5-step setup for Azure AD)
- "System": System Status + Version Info + Configuration Export/Import
- sso_service: fix process_callback for Azure AD claim URIs (email, role)
- Default role_attr to full Azure role claim URI
- Fallback email resolution via Azure email claim URI + NameID
- Username defaults to full email (prevents collision with local accounts)
- User lookup also tries email field for existing local accounts
- Logs warning when unknown role received from IdP
- frontend/api/sso.ts: new API module with getSsoStatus, getSsoConfig, updateSsoConfig
- LoginPage: redesigned for SSO-first flow
- Shows Azure SSO button as primary when SSO enabled+configured
- Local login collapsed under "Emergency admin access" section
- Falls back to normal local login form when SSO is disabled
- SystemPage: new SsoConfigSection component (guided 5-step wizard)
- Step 1: Copy SP Entity ID and ACS URL for IT team + metadata XML download
- Step 2: Azure App Roles reference table (6 roles with exact values)
- Step 3: Tenant ID field auto-fills idp_entity_id and idp_sso_url
- Step 4: X.509 certificate paste field
- Step 5: Attribute mapping pre-filled with Azure AD claim URIs
- Enable/disable toggle + save
- Capture Step.Description (HTML stripped), step name/number, substep ref,
criteria, and data sources from MITRE ATT&CK Evaluations API
- _aggregate_by_technique() now accumulates ALL occurrences per technique
(multiple substep refs, criteria, step contexts) instead of keeping only
the best-scoring one
- New helper functions _build_procedure_text(), _build_description(),
_build_red_summary() generate rich narratives from accumulated occurrences
- New re_enrich_evaluation_round() service function + POST endpoint
/system/attck-evaluations/re-enrich to update already-imported tests
without changing detection results or validation state
- Frontend: Re-enrich button per imported round + result banner in SystemPage
The /api/results/ endpoint returns a LIST: [{name: crowdstrike, adversaries: [...]}]
Previous code called data.get() on the list → AttributeError crash on every import.
Fix: detect list vs dict response, extract the crowdstrike vendor entry first,
then get its adversaries list. Keeps legacy dict fallback just in case.
- Fallback names now use hyphens matching live API (carbanak-fin7, wizard-spider-sandworm)
- Add APT3 (R1) and Enterprise 2025/er7 (R7) to fallback - verified from live API
- Remove OilRig (R6) from fallback - CrowdStrike did not participate in Round 6
- Orange fallback banner only shows when NO rounds are available at all
- Soft gray note when rounds are loaded but API had transient error
- Check-new and import errors: detect 502/Cloudflare messages and show user-friendly text
instead of raw Cloudflare HTML error messages
- Add browser User-Agent and Referer headers to all evals.mitre.org requests
- fetch_rounds_with_status() returns api_reachable flag + rounds list
- Fallback to 5 known public CrowdStrike rounds (APT29/R2 through OilRig/R6)
when live API is blocked, so UI always shows something actionable
- Router returns {rounds, api_reachable, api_error} instead of plain array
- Frontend shows orange warning banner when using fallback data
- Remove 502 HTTPException - rounds are always returned (live or fallback)
Replaces the minimal bottom legend with a full coverage legend panel
placed above the filters. Each status shows a cell mock matching the
exact colors used in the matrix, a color-coded label, and a short
description of what it means. Includes review_required with its
orange alert-triangle badge. Removes the old minimal bottom legend.
New drag-and-drop section at the bottom of the Import RT page so operators
can convert screenshots to base64 without leaving the page. Includes
thumbnail preview, copy-base64 and copy-JSON-snippet buttons with
2s feedback, per-image delete and clear-all.
Each technique in the RT import JSON now requires at least one evidence
image (PNG/JPG/GIF/WebP/BMP, max 10 MB decoded) embedded as base64.
Backend:
- RTEvidenceEntry model: filename, data (base64), caption (optional)
- RTTechniqueEntry.evidence is now required
- Pre-validation raises 422 if any technique is missing evidence
- After test creation, images are decoded and stored in MinIO as
Evidence records (team=red) linked to the test
Frontend:
- RTEvidenceEntry type added to api/tests.ts
- parseJson() validates evidence presence and structure per technique
- Preview table shows base64 thumbnails (up to 3 + overflow count)
- Format reference updated: evidence fields moved to Required section
- Import result shows total evidence images attached
The previous name implied data from a dedicated threat intelligence team.
The feature actually monitors public RSS feeds and security blogs for
ATT&CK technique mentions, so Security Feed Monitor is more accurate.
Updated description and all references across SystemPage and ReviewQueuePage.