Admin still bypassed require_any_role (non-strict) on the pure
operator actions: start-execution, submit-red, start-blue-work,
submit-blue, pause/resume-timer, and the red/blue field-edit + general
test create/update/remediation/import-rt/template endpoints all used
the loose dependency even where admin wasn't in the role tuple.
Switched every one of these to require_any_role_strict, matching the
review/validate/dispute/hold/assign endpoints already fixed earlier.
Frontend: removed every remaining role === "admin" disjunct gating
Start Execution, Submit to Blue Team, blue evaluating actions, timer
control, red/blue field editing, test creation, and template creation.
Team-blindness visibility (isBlind) deliberately keeps the admin
bypass — admin still sees both sides unmasked for oversight, since
that's visibility, not operating.
Updated ~20 tests whose fixtures used the admin account as a
convenience shortcut to create/drive tests through the workflow —
switched them to a red_lead account, fixing an incidental
fixture-resolution-order cookie bug along the way (client's cookie
jar prefers the last-logged-in role's cookie over any Bearer header
explicitly passed to a later request).
Leads get the same two-queue view operators already have: 'Available
to Review' (pending reviews currently assigned to a peer lead) and
'My Assigned Reviews' (assigned to me), replacing the old single
'My Reviews' toggle. Since the load-balanced auto-assignment always
picks a reviewer immediately, 'available' means peer-assigned reviews
a lead could pick up if the assignee can't get to them, not unclaimed
ones (there aren't any).
POST /tests/{id}/assign now also accepts red_reviewer_assignee /
blue_reviewer_assignee, validated against the matching lead role and
synced to Jira the same way operator assignment already is. The
AssigneeControl UI gained a 'reviewer' kind (leads-only picker) shown
on the test detail header while a test sits in red_review/blue_review
— this also fixes the reviewer assignment being invisible in Aegis
even though it was already being pushed to Jira correctly.
- Fix the red/blue draft form losing unsaved fields (e.g. execution_start_time) whenever an evidence upload refetches the test — the hydration effect now runs once per test, not on every refetch.
- Add color-scheme: dark so native date/time picker popups match the app theme instead of rendering white.
- Fix _STATE_TO_JIRA_STATUS: real Jira statuses are 'To Do' and 'Red Team test review', not 'To-Do' and 'RT Test Review' — confirmed live against the actual workflow transitions, which is why Submit to Blue Team never moved the ticket past 'In Progress'.
- Tempo worklog sync was silently blocked by TEMPO_ENABLED defaulting to False with no admin-facing toggle, even after configuring a Tempo token via Settings. Now bypassed once an admin or personal token is actually configured, mirroring Jira's DB-backed enabled flag.
- Hold now actually pauses the running phase timer (paused_at), and Resume accumulates the held duration into red/blue_paused_seconds — previously the timer kept counting through a hold.
Shrinks padding, icon size, and font sizes, and bumps the grid to 9
columns at the lg breakpoint so Draft through Disputed no longer wrap
to a second row.
The previous fix for the literal '\u2014' JSX text bug only replaced
the escape sequence with a real em dash but left the leading backslash
character behind, so it rendered as '\—' instead of '—'.
Admin administers the site — it no longer has any override on
validate-red/blue, review-red/blue, resolve-dispute, request-discussion,
reopen, hold, resume, assign-operators, or classification-update.
Introduces require_any_role_strict(), a role dependency without the
global admin bypass, so these specific endpoints truly exclude admin
instead of only removing it from the (redundant) role tuple.
Managers gain the ability to assign red_tech/blue_tech operators
(POST /tests/{id}/assign, GET /users/operators) alongside leads, since
that's coordination, not resolving a ticket.
Also enlarges and repositions the operator-assignment controls next
to the Start Execution button, and fixes a literal '\u2014' rendering
as text instead of an em dash in the test detail technique line.
GET /system/jira-config was admin-only, so non-admin users hit a 403
and the frontend silently fell back to a hardcoded jira.atlassian.com
base URL when building /browse/{key} links. Opened it to any
authenticated user (still no secrets returned).
Also drops the redundant 'security-test' Jira label, and adds a
page-size selector (10/25/50/100) and a 'no existing test yet' filter
to the Test Catalog.
Adds GET /users/operators (leads+admin) and wires a lead-only assign
control into the test detail header, calling the existing but
previously unreachable POST /tests/{id}/assign endpoint. Manual
assignment now also pushes the Jira ticket assignee immediately
instead of waiting for the operator to start execution.
Also adds test.platform as a kebab-case label on Jira ticket creation.
Techniques (/techniques) and Coverage Matrix (/matrix) showed the same
underlying data (Technique.status_global) with completely different
visual styles — discrete Tailwind status colors + ID+name cells on
Techniques, vs. a continuous score-gradient hex color + ID-only cells
(name via tooltip) on the heatmap. Coverage Matrix also has 3 layers
(Threat Actor, Detection Rules, Campaign) with no Techniques equivalent,
so Techniques was the strict subset — merged into Coverage Matrix rather
than the other way around.
- HeatmapCell now uses the same discrete status-color palette as the old
TechniqueCell (cellColors.ts), shows the technique name inline, and
keeps zoom/virtualization/hover-tooltip untouched.
- HeatmapLegend restyled to match (swatch + label + description per
tier, review_required as an overlay badge) instead of the old
hex-gradient bar.
- MatrixPage gains a Matrix/List view toggle and a status filter
(coverage/threat-actor layers only) — both ported from the old
Techniques page — so nothing it offered is lost.
- /techniques now redirects to /matrix; removed TechniquesPage,
AttackMatrix, TechniqueCell (fully superseded). /techniques/:mitreId
(detail) and /techniques/review-queue are untouched.
Known pre-existing limitation carried over, not introduced or fixed
here: the heatmap only places a multi-tactic technique under its first
tactic column (_format_tactic takes tactic_str.split(",")[0]), while the
old Techniques matrix placed it under every tactic it belongs to.
The "200" badge on the Validated Tests page was a hardcoded query limit,
not the real total — silently hiding results beyond the 200th once there
were more validated tests than that. Now shows the true total via the new
/tests/count endpoint, and warns when the fetched page doesn't cover all
matches.
Adds filter controls beyond the existing name/technique text search:
technique (MITRE ID or name), platform, attack outcome, detection
outcome, and a validated-date range — all pushed down to the server via
the new GET /tests filters instead of only ever searching the current
page client-side.
- TestsPage gains a "My Reviews" toggle for red_lead/blue_lead, separate
from "My Tasks" — filters by reviewer_id at the red_review/blue_review
gate instead of the unrelated in_review validation stage
- Test Catalog cards now warn when a technique already has existing
tests, using the backend's new existing_test_count
- ExecutiveDashboardPage drops its duplicate client-side tactic-order
sort now that the backend returns canonical order; time-range filter
now notes which sections it actually scopes
- HeatmapLegend adds the missing review_required swatch (as an overlay
indicator, matching how it's actually rendered on cells) and hover
tooltips reusing StatusBadge's copy instead of being unexplained
static color chips
- Fix a real React key bug in ControlsTable: rows were grouped in a
keyless shorthand fragment, which can misreconcile when the table is
filtered/sorted; switched to a keyed Fragment
- Minor: CompliancePage summary-card grid no longer strands a lone card
on tablet widths
- Wrap 8 previously-raw description fields in MarkdownText so imported
markdown (links, bold, lists, MITRE citations) renders instead of
showing literal syntax: campaign cards, D3FEND countermeasure
descriptions, compliance framework/control descriptions, detection
rule descriptions, threat-actor and technique reference descriptions,
and the RT-import preview description
- Fix the "Validated" status tooltip claiming a "≥2 tests" threshold
when the real rule is ≥1 validated test with full detection; reworded
the adjacent "Partial" tooltip for the same clarity
- Generalize the technique review_required banner text away from
"MITRE ATT&CK sync" — review can also be triggered by Atomic/Caldera/
Elastic/Sigma/LOLBAS imports, intel scans, and stale-detection checks
- Executive Dashboard no longer auto-triggers POST /risk/compute or shows
the Refresh scores button for roles the backend rejects (viewer), which
was causing a 403 on load
- AuthContext now proactively renews the session cookie every 20 minutes
while the app is open, so an actively-used session doesn't rely solely
on the reactive on-401 refresh racing against its own token's expiry
Direct test removal is now gated to draft-only at the service layer
(active campaigns must go through a modification request that a
manager approves). The trash-icon button on the tests table still
showed for active campaigns and would always fail with a 400 —
restrict it to draft, matching the backend gate.
Adds the 4 backend-logged campaign actions missing from ACTION_LABELS
(delete_campaign, generate_campaign, schedule_campaign,
recurring_campaign_run), and wires campaign-timeline invalidation into
the remaining audit-logging mutations on the page (complete, schedule,
approve/reject modification request) so the timeline panel updates
live instead of only on next reload, consistent with submit/approve/
reject campaign.
- Single admin Jira account stored in system_configs (jira.admin_email, jira.admin_api_token)
- Admin Tempo token in system_configs (tempo.admin_token)
- get_admin_jira_client() + has_admin_jira_configured() replace per-user auth in all lifecycle hooks
- lookup_user_jira_account_id() auto-discovers each user's Atlassian accountId by email on login
- auto_log_test_worklog() now uses admin Tempo token and logs both red+blue team time
- Settings > Jira: admin credentials fields + test buttons moved to admin Jira tab
- Profile section simplified: jira_account_id shown read-only (auto-detected)
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).
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
- 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.
Overall Security Score renamed to Overall Programme Score. Descriptions across
Executive Dashboard and Dashboard page now clarify scores reflect Red/Blue Team
exercise maturity and coverage breadth, not the organisation real-world security
state, to avoid overstating what ATT&CK simulation tests can guarantee.
client.ts: when FastAPI detail is an object, extract .message for the error
string and preserve the full detail on enhancedError.detail so consumers
can inspect structured error payloads (e.g. 409 start_date_in_future).
CampaignDetailPage: use enhancedErr.status (not response.status) and
enhancedErr.detail (not response.data.detail) to detect 409 and show
the confirmation modal instead of the toast.
FastAPI wraps error bodies as {detail: string | object}, not at the
top level. Was reading data.message instead of data.detail.message,
causing [object Object] in the toast for all non-409 errors.
Now correctly extracts:
- 409 with object detail -> start_date warning modal
- Other errors with string detail -> readable toast message
- Other errors with object detail -> detail.message in toast
Adds a Generate Campaign button (purple, visible to leads/admin) in the
threat actor header. Opens a modal with:
- Actor name shown as context
- Start date picker (required — validated: must be today or future)
- Warning message showing when tests will be queued
- Error display for API failures
- On success: redirects to the new campaign detail page
Start date is mandatory here (unlike the CampaignsPage flow where it
is optional) to enforce scheduling discipline when generating from actors.
Backend: activate endpoint returns 409 with structured warning when
start_date is in the future; accepts force=true to bypass.
test_crud_service: always excludes tests from draft campaigns with future
start_date so they do not appear in the team queue prematurely.
Frontend: catches 409 on activate and shows amber confirmation modal
with Keep scheduled / Activate now anyway options.