Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ bin
obj
**/bin
**/obj
**/node_modules
**/dist
**/coverage
src/SecureFix.Api/wwwroot
*.db
*.sqlite
*.sqlite3
Expand Down
9 changes: 9 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ AUTH_MODE=demo
# Used only by the demo authentication handler (AUTH_MODE=demo). Never set in Production.
SECUREFIX_DEMO_TOKEN=securefix-demo-token

# Dashboard build configuration (public values; never place secrets in VITE_* variables)
VITE_DATA_MODE=live
VITE_AUTH_MODE=demo
# Required for a production Entra dashboard build:
# VITE_ENTRA_CLIENT_ID=<single-page-application-client-id>
# VITE_ENTRA_TENANT_ID=<entra-tenant-id>
# VITE_ENTRA_API_SCOPE=api://<api-client-id>/access_as_user
# VITE_ENTRA_REDIRECT_URI=https://securefix.example.com

# Database
DATABASE_URL=sqlite:securefix.db
DATABASE_PROVIDER=sqlite
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/dotnet-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,32 @@ jobs:
with:
dotnet-version: '10.0.x'

- name: Setup Node.js
uses: actions/setup-node@v5
with:
node-version: '22'
cache: npm
cache-dependency-path: src/SecureFix.Web/package-lock.json

- name: Install dashboard dependencies
working-directory: src/SecureFix.Web
run: npm ci

- name: Test dashboard
working-directory: src/SecureFix.Web
run: npm test

- name: Build dashboard
working-directory: src/SecureFix.Web
env:
VITE_DATA_MODE: live
VITE_AUTH_MODE: demo
run: npm run build

- name: Audit dashboard production dependencies
working-directory: src/SecureFix.Web
run: npm audit --omit=dev --audit-level=high

- name: Restore dependencies
run: dotnet restore SecureFix.slnx

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ logs/
artifacts/
build-output/
publish/
src/SecureFix.Api/wwwroot/
demo-results-*
demo-security-*

Expand Down
29 changes: 25 additions & 4 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
# Build stage
# Dashboard build stage
FROM node:22-alpine AS web-build
WORKDIR /src/SecureFix.Web

COPY ["src/SecureFix.Web/package.json", "src/SecureFix.Web/package-lock.json", "./"]
RUN npm ci
COPY ["src/SecureFix.Web/", "./"]

ARG VITE_DATA_MODE=live
ARG VITE_AUTH_MODE=demo
ARG VITE_ENTRA_CLIENT_ID=
ARG VITE_ENTRA_TENANT_ID=
ARG VITE_ENTRA_API_SCOPE=
ARG VITE_ENTRA_REDIRECT_URI=
ENV VITE_DATA_MODE=$VITE_DATA_MODE \
VITE_AUTH_MODE=$VITE_AUTH_MODE \
VITE_ENTRA_CLIENT_ID=$VITE_ENTRA_CLIENT_ID \
VITE_ENTRA_TENANT_ID=$VITE_ENTRA_TENANT_ID \
VITE_ENTRA_API_SCOPE=$VITE_ENTRA_API_SCOPE \
VITE_ENTRA_REDIRECT_URI=$VITE_ENTRA_REDIRECT_URI
RUN npm run build

# API build stage
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
WORKDIR /src

# Copy solution and projects
COPY ["SecureFix.slnx", "."]
COPY ["src/SecureFix.Core/", "src/SecureFix.Core/"]
COPY ["src/SecureFix.Api/", "src/SecureFix.Api/"]
COPY ["tests/SecureFix.Tests/", "tests/SecureFix.Tests/"]
COPY --from=web-build /src/SecureFix.Api/wwwroot/ src/SecureFix.Api/wwwroot/

# Restore and build
RUN dotnet restore SecureFix.slnx
RUN dotnet build SecureFix.slnx -c Release -o /app/build

Expand All @@ -26,6 +47,6 @@ RUN (id -u app >/dev/null 2>&1 || useradd -m -u 1000 app) && chown -R app:app /a
USER app

EXPOSE 5000
ENV ASPNETCORE_URLS=http://+:5000
ENV ASPNETCORE_URLS=http://0.0.0.0:5000

ENTRYPOINT ["dotnet", "SecureFix.Api.dll"]
36 changes: 27 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ Dependabot or JSON alert

### How would this be deployed?

Hackathon demo: containerized FastAPI app with SQLite and local/mock providers.
Hackathon demo: a React dashboard and ASP.NET Core API shipped as one container, with SQLite and local/mock providers.

Production path: separately scalable API and worker services on Azure Container Apps or AKS, backed by managed messaging, PostgreSQL, managed identity, secrets management, and centralized observability.
Production path: the integrated dashboard/API container can run on Azure Container Apps or AKS, with workers scaled separately and backed by managed messaging, PostgreSQL, managed identity, secrets management, and centralized observability.

### How would this be secured?

Expand Down Expand Up @@ -67,8 +67,8 @@ AI timeout or invalid output falls back to a rules-based recommendation and requ
- automatic merge
- automatic production deployment
- autonomous approval
- full enterprise identity integration
- complex front-end UI
- automatic production deployment from the dashboard
- direct merge or release actions

## Repository guide

Expand All @@ -94,12 +94,23 @@ The intended demo should show:
7. a blocked prompt-injection or unauthorized-action attempt
8. a fallback path when AI is unavailable

## Running the local demo
## Running the dashboard locally

Start the API, then run the authenticated end-to-end validation script in a second terminal:
Build the integrated dashboard, then start the API:

```bash
cd src/SecureFix.Web
npm ci
VITE_DATA_MODE=live VITE_AUTH_MODE=demo npm run build
cd ../..
dotnet run --project src/SecureFix.Api --urls http://127.0.0.1:5000
```

Open `http://127.0.0.1:5000`. The development role switcher uses the existing demo bearer token and headers; it must never be enabled in Production. For frontend hot reload, run `npm run dev` and let Vite proxy API calls to the configured ASP.NET development URL.

To run the authenticated API validation script in a second terminal:

```bash
bash ./demo-end-to-end.sh
```

Expand All @@ -114,8 +125,15 @@ writes evidence to `demo-security-results-*`.
bash ./demo-security-controls.sh
```

## Status
## Dashboard capabilities

This repository is organized around the implementation roadmap and supporting docs for the hackathon MVP.
- operational overview with workflow and severity distributions
- searchable, filterable vulnerability work queue
- workflow detail with risk, approval, remediation, proposal, audit, and governance views
- validated alert ingestion and sample payloads
- role-aware human approval and rejection actions
- demo identity switching for local development and an MSAL/Entra production adapter

## Status

The preferred delivery approach is a reproducible, production-minded demo that favors security, governance, traceability, and human oversight over UI polish.
The repository provides a reproducible, production-minded demo that combines a governed security workflow with an operator dashboard while preserving security, traceability, and human oversight.
14 changes: 14 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,27 @@
services:
securefix-api:
env_file:
- .env
Comment on lines +3 to +4
build:
context: .
dockerfile: Dockerfile
args:
VITE_DATA_MODE: ${VITE_DATA_MODE:-live}
VITE_AUTH_MODE: ${VITE_AUTH_MODE:-demo}
VITE_ENTRA_CLIENT_ID: ${VITE_ENTRA_CLIENT_ID:-}
VITE_ENTRA_TENANT_ID: ${VITE_ENTRA_TENANT_ID:-}
VITE_ENTRA_API_SCOPE: ${VITE_ENTRA_API_SCOPE:-}
VITE_ENTRA_REDIRECT_URI: ${VITE_ENTRA_REDIRECT_URI:-}
ports:
- "8888:5000"
environment:
ASPNETCORE_ENVIRONMENT: ${ASPNETCORE_ENVIRONMENT:-Development}
ASPNETCORE_URLS: ${ASPNETCORE_URLS:-http://+:5000}
AUTH_MODE: ${AUTH_MODE:-entra}
AzureAd__TenantId: ${AzureAd__TenantId:-}
AzureAd__ClientId: ${AzureAd__ClientId:-}
AzureAd__Audience: ${AzureAd__Audience:-}
SECUREFIX_DEMO_TOKEN: ${SECUREFIX_DEMO_TOKEN:-securefix-demo-token}
DATABASE_URL: ${DATABASE_URL:-sqlite:securefix.db}
DATABASE_PROVIDER: ${DATABASE_PROVIDER:-sqlite}
AI_PROVIDER: ${AI_PROVIDER:-mock}
Expand Down
15 changes: 9 additions & 6 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,18 @@ SecureFix AI uses a controlled workflow:

## Target shape

- FastAPI API for ingestion and workflow actions
- service layer for business logic
- React and TypeScript dashboard built with Vite and served by the API as static assets
- ASP.NET Core API for ingestion, dashboard queries, and workflow actions
- service layer for business logic and read-optimized dashboard aggregation
- repositories for persistence abstraction
- security layer for identity and authorization
- governance layer for approval and reporting
- observability layer for logs, metrics, and traces
- security layer for demo or Microsoft Entra identity and role authorization
- governance layer for approval, audit, and reporting
- observability layer for logs, health, readiness, and process metrics

The dashboard and API use same-origin requests in the integrated deployment. UI role gating improves usability, but API authorization remains authoritative. Durable dashboard counts come from persisted workflow records; `/metrics` remains Admin-only process telemetry.

## Deployment path

Hackathon demo: containerized app with SQLite and local/mock providers.
Hackathon demo: one container serving the compiled dashboard and API, with SQLite and local/mock providers.

Production target: separately scalable containers with managed identity, managed queueing, PostgreSQL, secrets management, and centralized telemetry.
15 changes: 15 additions & 0 deletions docs/runbook.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Common operations

- build and start the integrated dashboard/API
- start the service in Docker
- run unit and security tests
- submit a sample alert
Expand All @@ -10,6 +11,20 @@
- inspect audit and governance output
- provision or update the Entra ID app registration and its roles

## Dashboard operations

- **Integrated local build**: run `npm ci` and `VITE_DATA_MODE=live VITE_AUTH_MODE=demo npm run build`
from `src/SecureFix.Web`, then start `src/SecureFix.Api`. The generated assets are written
to `src/SecureFix.Api/wwwroot` and served at `/`.
- **Hot reload**: run `npm run dev` in `src/SecureFix.Web`; Vite proxies API calls to the
configured ASP.NET development URL.
- **Production Entra build**: pass `VITE_AUTH_MODE=entra`, `VITE_ENTRA_CLIENT_ID`,
`VITE_ENTRA_TENANT_ID`, `VITE_ENTRA_API_SCOPE`, and the optional redirect URI as Docker
build arguments. These are public browser configuration, not secrets.
- **Dashboard unavailable**: verify that `wwwroot/index.html` exists in the published image,
request `/health` to separate static-hosting failures from API failures, and check the
browser console for CSP or authentication errors.

## Identity operations

- **Provision app registration and roles**: `./infra/entra-app-registration.sh "SecureFix-AI-API"`.
Expand Down
16 changes: 15 additions & 1 deletion docs/security-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,29 @@ App roles are defined once on the Entra ID app registration (see
`infra/entra-app-registration.sh`) and assigned to users/groups from the Enterprise
Application's "Users and groups" blade — never granted by the application itself.

The integrated dashboard uses an MSAL browser adapter when built with `VITE_AUTH_MODE=entra`.
Only public identifiers and API scopes are compiled into browser assets; client secrets are
never used by or exposed to the dashboard. In demo mode, the visible role switcher is a local
UX aid backed by the intentionally weak demo headers. Hiding or disabling a UI action is not
a security boundary: every operation remains protected by API role authorization.

## Roles

- Admin
- SecurityReviewer
- Developer
- Viewer

## Browser controls

The API serves the dashboard with a restrictive Content Security Policy, clickjacking
protection, MIME-sniffing protection, and a strict referrer policy. Static dashboard routes
are anonymous so the browser can load the application shell; protected data and mutations
still require authenticated API calls.

## Secrets

Secrets must come from environment variables or managed secret storage in production.
No credentials are committed to source control.
No credentials are committed to source control. `VITE_*` values are public build-time
configuration and must never contain secrets.

28 changes: 28 additions & 0 deletions src/SecureFix.Api/Controllers/DashboardController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace SecureFix.Api.Controllers;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SecureFix.Core.Models;
using SecureFix.Core.Services;

[ApiController]
[Route("api/v1/dashboard")]
[Produces("application/json")]
public sealed class DashboardController : ControllerBase
{
private readonly IDashboardQueryService _dashboardQueryService;

public DashboardController(IDashboardQueryService dashboardQueryService)
{
_dashboardQueryService = dashboardQueryService ??
throw new ArgumentNullException(nameof(dashboardQueryService));
}

[Authorize(Roles = "Viewer,Developer,SecurityReviewer,Admin")]
[HttpGet("summary")]
[ProducesResponseType(typeof(DashboardSummaryDto), StatusCodes.Status200OK)]
public async Task<ActionResult<DashboardSummaryDto>> GetSummary(CancellationToken cancellationToken)
{
return Ok(await _dashboardQueryService.GetSummaryAsync(cancellationToken));
}
}
29 changes: 28 additions & 1 deletion src/SecureFix.Api/Controllers/WorkflowsController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,16 +16,43 @@ namespace SecureFix.Api.Controllers;
public class WorkflowsController : ControllerBase
{
private readonly IApprovalService _approvalService;
private readonly IDashboardQueryService _dashboardQueryService;
private readonly ILogger<WorkflowsController> _logger;

public WorkflowsController(
IApprovalService approvalService,
IDashboardQueryService dashboardQueryService,
ILogger<WorkflowsController> logger)
{
_approvalService = approvalService ?? throw new ArgumentNullException(nameof(approvalService));
_dashboardQueryService = dashboardQueryService ??
throw new ArgumentNullException(nameof(dashboardQueryService));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}

[Authorize(Roles = "Viewer,Developer,SecurityReviewer,Admin")]
[HttpGet]
[ProducesResponseType(typeof(WorkflowListResponseDto), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
public async Task<ActionResult<WorkflowListResponseDto>> GetWorkflows(
[FromQuery] WorkflowListQueryDto query,
CancellationToken cancellationToken)
{
try
{
return Ok(await _dashboardQueryService.GetWorkflowsAsync(query, cancellationToken));
}
catch (ArgumentException exception)
{
ModelState.AddModelError(exception.ParamName ?? "query", exception.Message);
return BadRequest(new ValidationProblemDetails(ModelState)
{
Status = StatusCodes.Status400BadRequest,
Title = "Invalid workflow query"
});
}
}

/// <summary>
/// Get workflow status for an alert.
/// </summary>
Expand All @@ -34,7 +61,7 @@ public WorkflowsController(
/// <response code="200">Workflow found and returned.</response>
/// <response code="404">Workflow not found.</response>
/// <response code="500">Server error during lookup.</response>
[Authorize(Roles = "Developer,SecurityReviewer,Admin")]
[Authorize(Roles = "Viewer,Developer,SecurityReviewer,Admin")]
[HttpGet("{id}")]
[ProducesResponseType(typeof(WorkflowStatusResponse), StatusCodes.Status200OK)]
[ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status404NotFound)]
Expand Down
Loading
Loading