23 Commits
Author SHA1 Message Date
Samir KhanandClaude Sonnet 4.6 742a915947 docs(readme): fix install bugs blocking new users
- Correct clone URL (YOUR_ORG placeholder -> sameerk27/vigil365)
- Remove non-existent 'dist' copy step (vite outDir already targets wwwroot) — this
  was failing for everyone trying the manual build
- Production connection string Encrypt=True;TrustServerCertificate=True (match shipped appsettings)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-27 14:56:18 +05:30
Samir KhanandClaude Sonnet 4.6 74a3efaf91 docs: expand SECURITY.md posture + add threat model
- SECURITY.md: disclosure policy, design/deployment model, current controls,
  hardening-in-progress, operator responsibilities (keeps Graph API stability notes)
- docs/THREAT_MODEL.md: assets, trust boundaries, data flows, STRIDE mitigations

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 18:32:37 +05:30
Samir KhanandClaude Sonnet 4.6 daa2008851 docs: add project summary overview
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-24 18:27:31 +05:30
sameerk27andGitHub b47ed33d5f feat(alerting): per-alert snooze and silent auto-resolve (#2)
feat(alerting): per-alert snooze and silent auto-resolve
2026-06-23 10:16:24 +05:30
AnandSundar 3c52f8d2ae feat(ui): snooze controls in alert center
Wires the Alert Center to the new per-alert snooze endpoints and renders
the new server-side fields (snoozedUntil, lastEvaluatedAt).

- TriggeredAlert interface extended with status union members
  ('snoozed' | 'auto_resolved') and the four new fields.
- acApi gains snooze(id, durationHours) and unsnooze(id).
- statusTone maps the new statuses to neutral (snoozed) and info
  (auto_resolved) tones so they read distinctly from new / acknowledged.
- Status filter dropdown gets two new options so users can isolate
  snoozed or auto-resolved rows.
- Action cell gains a snooze <select> with 4h / 24h / 7d presets and an
  Unsnooze button that appears when snoozedUntil is in the future.
- Detail modal surfaces Snoozed until and Last evaluated rows when
  populated.
- Mini-list and active-alerts table both render a muted 'snoozed until'
  indicator so the state is visible at a glance.
2026-06-22 13:36:37 -06:00
AnandSundar ac685b4712 test(alerting): add xUnit project for auto-resolve path
Adds M365SecurityDashboard.Api.Tests with seven focused tests for the
new auto-resolve loop in AlertEvaluator:

- Streak increments on a below-threshold observation
- Auto-resolve after AutoResolveDebounceCycles consecutive observations
- Streak resets to zero on any above-threshold observation
- Debounce-of-one auto-resolves on the first observation
- Terminal-state alerts (resolved, auto_resolved) are skipped
- No notification dispatch on auto-resolve
- AcknowledgedAt and AcknowledgedBy survive an auto-resolve transition

Uses Microsoft.EntityFrameworkCore.InMemory for the test DbContext.
NotificationSender is constructed but never invoked by the loop under
test, so no HTTP/DPAPI dependencies are exercised.
2026-06-22 13:29:42 -06:00
AnandSundar ea5ad21e47 feat(alerting): add per-alert snooze and silent auto-resolve
Per-alert snooze (4h / 24h / 7d presets) plus silent auto-resolve on metric
recovery. Snooze data lives on TriggeredAlert and composes with the existing
per-policy SuppressionMinutes.

Schema: adds SnoozedUntil, SnoozedBy, BelowThresholdStreakCount, and
LastEvaluatedAt columns to TriggeredAlerts via idempotent IF COL_LENGTH
ALTER blocks, mirroring the existing SourceFailureDetails pattern.

API: POST /api/triggered-alerts/{id}/snooze accepts an absolute timestamp
or a duration in hours; POST .../unsnooze clears the state. Snoozing a
terminal-state alert returns 400.

Evaluator: after the existing dispatch loop, scans non-terminal alerts,
increments or resets a per-alert streak counter on each evaluation, and
transitions to Status="auto_resolved" after AutoResolveDebounceCycles
(default 2) consecutive below-threshold observations. Resolution is silent
- no notification dispatch, no NotificationLog write. Manual resolve
remains the user-driven terminal state; the first transition wins.

Config: new Alerting section in appsettings.json; bound via the existing
IOptions pattern.
2026-06-22 13:24:46 -06:00
Samir KhanandClaude Sonnet 4.6 5a241cde00 fix(install): add -I flag and SET QUOTED_IDENTIFIER ON for filtered index
sqlcmd without -I causes the filtered index on ExternalId to fail silently,
leaving the schema half-applied. Reported by Firdous Parray.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 10:40:26 +05:30
Samir KhanandClaude Sonnet 4.6 18b6383b9c fix(security): harden API against information disclosure
- SQL connection: Encrypt=True (was Encrypt=False)
- Generic error responses — ex.Message no longer returned to clients;
  full error logged server-side via app.Logger.LogError
- Swagger only enabled in Development environment
- CORS restricted to explicit methods (GET POST PUT DELETE OPTIONS)
- Security headers added: X-Frame-Options DENY, X-Content-Type-Options
  nosniff, Referrer-Policy no-referrer
- Microsoft.Identity.Web + Azure.Identity packages added (prep for
  certificate auth)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-22 10:33:28 +05:30
Samir KhanandClaude Sonnet 4.6 a3bfc53a9e fix(ui): make auto-refresh fire reliably
The countdown ticker called setRefreshKey() inside the setCountdown()
updater. React state updaters must be pure — side effects inside them
are unreliable, so the countdown reached 0 but the refresh often never
triggered.

Split into two effects: the ticker now only decrements (pure), and a
separate effect fires the refresh when countdown hits 0 (guarded by
!loading to avoid double-fire). load() resets the countdown on completion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 14:51:58 +05:30
Samir KhanandClaude Sonnet 4.6 16a66724e0 feat(health): surface collection health + per-source failures
Silent collector failures (caught and only logged) are now visible.

Backend:
- Capture which sources failed and why in CollectionRun.SourceFailureDetails
  (JSON), not just a count; idempotent column add for existing DBs
- /api/collector/runs returns the new field

Frontend:
- New "Collection Health" card on Overview: last-run status, alerts
  collected, duration, source-failure count, and an expandable list of
  failing sources with their error — or "all sources collecting normally"
- Refreshes with the dashboard (and after Run Collection)

Turns the previously-invisible SourceFailures signal into something the
operator can actually see.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 13:17:38 +05:30
Samir KhanandClaude Sonnet 4.6 4f6e185ebf fix(collector): guard JSON traversal against non-object elements
Mail flow issues collection threw "requires an element of type Object,
but the target element has type Array" because Get(e,"details","url")
traversed into "details", which is an array in serviceAnnouncement/issues.
TryGetProperty throws on non-object elements.

Guard the element kind before each nested TryGetProperty so any path that
hits an array/primitive returns null instead of throwing. Fixes the
recurring 1-source-failure per collection run; mail-flow advisories now
collect (run upserts 83 vs 81).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 13:05:07 +05:30
Samir KhanandClaude Sonnet 4.6 cc542eba73 perf(api): stop paginating entire audit/sign-in history
The identity endpoint hung 90s+ because directoryAudits used
GetCollectionAsync, which follows @odata.nextLink through every page —
walking the whole audit history even though only the latest 10 are
wanted ($top is page size, not a limit). Same issue in sign-in-locations.

- directoryAudits and signIns now use GetSinglePageAsync (latest page only)
- Time-box the identity endpoint best-effort Graph calls to 10s as a
  safety net under throttling
- Guard SecretProtector DPAPI calls with OperatingSystem.IsWindows()
  to clear CA1416 and run cleanly off-Windows

identity 90s+ -> 3.2s; signin-locations now 2.8s.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:59:39 +05:30
Samir KhanandClaude Sonnet 4.6 ed7378aebe fix(ui): resolve invisible titles + full design-system overhaul
- Fix flexbox collapse hiding alert titles in Overview mini-lists
  (missing min-width:0 on .mini-row/.mr-user collapsed them to 0 width
  in both light and dark mode)
- Establish token-based color system: 3-tier surface elevation, unified
  severity/status scales, text scale — all with proper light/dark parity
- Replace scattered hardcoded hex values across CSS and JSX inline styles
  with design tokens so both themes stay consistent
- Full dark-mode contrast audit (WCAG AA): badges, KPI tiles/icons,
  source badges, tone classes, charts/gauges, form inputs, focus rings
- sevColor() now returns CSS vars so severity colors adapt per theme

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:52:46 +05:30
Samir KhanandClaude Sonnet 4.6 ee9c8ded02 security: encrypt notification secrets at rest + honest security docs
Addresses external review feedback on the project.

- Add SecretProtector (Windows DPAPI, machine scope) and encrypt SMTP
  password and Teams/Slack/generic webhook URLs at rest in the database.
  Values are decrypted only in memory at send time; SMTP password is
  never returned by the API. Legacy plaintext rows are read transparently.
- Rewrite README "Security & Maturity" section: honest beta positioning,
  read-only/least-privilege scope, credential handling, and a host
  hardening checklist (dedicated low-priv host, BitLocker, no public
  exposure, rotation). Notes certificate auth as recommended next step.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:20:39 +05:30
Samir KhanandClaude Sonnet 4.6 8730c0fa38 fix(alert-center): QA fixes for severity rendering and types
- Normalize sevColor() input so lowercase backend severities map to
  correct colors (were all falling back to grey)
- Add missing .sev-dot.sev-* CSS rules so Recent Alerts severity dots
  are visible
- Add suppressionMinutes to AlertPolicy TS interface (was silently dropped)
- Correct misleading "no email sent" label on the policy modal

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 12:09:22 +05:30
Samir KhanandClaude Opus 4.8 8c42489c88 feat(alerting): move Alert Center to backend with real notification delivery
Previously the alert engine was browser-only (localStorage) and only ran
while the dashboard was open. It now runs server-side on the 15-minute
collection cycle and actually delivers notifications.

Backend:
- New EF entities: AlertPolicy, TriggeredAlert, NotificationSettings, NotificationLog
- AlertEvaluator computes metrics from SecurityAlerts and fires policies with
  per-policy suppression windows to prevent alert fatigue
- NotificationSender delivers to Teams/Slack incoming webhooks, SMTP email,
  and a generic JSON webhook (SIEM/Power Automate); every attempt is logged
- Evaluation hooked into GraphCollectionWorker so alerts fire with no browser open
- Idempotent table creation + default policy seeding (works on existing DBs)
- REST endpoints for policies CRUD, triggered alerts ack/resolve, settings, test, log

Frontend:
- Alert Center now reads/writes via the API instead of localStorage
- New Notifications tab: Teams/webhook/SMTP config, send-test, delivery history
- Acknowledge/resolve/policy edits persist to the database

Verified against live tenant: 5 policies fired (MFA 7, risky users 1,
non-compliant 2, high alerts 10, service health 15); dedup and ack confirmed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 18:16:22 +05:30
Samir KhanandClaude Sonnet 4.6 5a7dd86a3b chore: add SECURITY.md and broken API issue template
- SECURITY.md documents endpoint stability per area, how to report
  broken Graph endpoints, and links to the MS Graph changelog
- New issue template for broken API reports with structured fields
  (page, error, date, endpoint, license tier)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-18 17:07:45 +05:30
sameerk27andGitHub 748f7ccb29 Update README.md 2026-06-17 15:00:12 +05:30
Samir KhanandClaude Sonnet 4.6 f5f3e7e6c9 chore: add contributing guide and issue templates
- CONTRIBUTING.md with plain-English rules for contributors
- Bug report issue template
- Feature request issue template

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 22:10:50 +05:30
sameerk27andGitHub 1405d9aec7 Create SECURITY.md 2026-06-16 22:01:25 +05:30
Samir KhanandClaude Sonnet 4.6 a1c38f6140 rebrand: rename to Vigil365
- New name: Vigil365 (M365 Security Operations)
- New sidebar logo: shield + pulse SVG icon
- Updated browser tab title and header subtitle

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:51:33 +05:30
Samir KhanandClaude Sonnet 4.6 54ad152f31 Initial commit: M365 Security Alert Dashboard
Full-stack Microsoft 365 security monitoring dashboard built with
ASP.NET Core 8 + React 18 + TypeScript. Aggregates security signals
from Microsoft Graph API across Defender XDR, Entra ID Protection,
Intune, Exchange Online, and M365 Compliance into a single
self-hosted dashboard.

Features:
- 13 monitoring pages: Identity, Devices, Email, Incidents, Compliance,
  Service Health, Licenses, Conditional Access, Audit Log, Sign-in Locations,
  M365 Connectivity, Alert Center, Overview
- Alert Policy Engine with 9 pre-built templates and custom policy builder
- Detail modals with direct M365 portal deep links per item type
- Per-page search, filter, sort, CSV export, saved filter presets
- Dark/light mode, collapsible sidebar, toast notifications
- Responsive layout, sticky filter bars, sortable table columns

All credentials must be supplied via .NET User Secrets (dev) or
appsettings.Production.json (prod) — never committed to source.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-16 21:41:48 +05:30