Skip to content

Security: coding-with-nox/nox

Security

docs/security.md

Security

Authentication

  • Provider: Keycloak 26, realm nox
  • Protocol: OpenID Connect / OAuth2 — JWT Bearer tokens
  • Validation: Authority (issuer), Audience (nox-api), signature

The KeycloakRolesTransformer (IClaimsTransformation) reads realm_access.roles from the Keycloak JWT and maps them to ClaimTypes.Role so standard ASP.NET Core role-based authorization works.

RBAC

Three roles, defined in NoxPolicies:

Role Policy constant Permitted actions
viewer NoxAnyUser Read all data, subscribe to SignalR
manager NoxManagerOrAdmin Start flows, submit HITL decisions
admin NoxAdminOnly Create/approve/reject flows, skills, agents, GDPR erasure

All endpoints require at minimum NoxAnyUser. Anonymous access is rejected with HTTP 401.

Endpoint Authorization Matrix

Endpoint Min role
GET /api/* (read) viewer
POST /api/flows/*/runs manager
POST /api/hitl/*/decide manager
POST /api/skills/{id}/approve admin
POST /api/gdpr/anonymize/* admin
/hubs/hitl, /hubs/agents viewer
/mcp viewer
POST /acp/message any authenticated user

GDPR Compliance (EU Regulation 2016/679)

Art. 17 — Right to Erasure (Right to be Forgotten)

GdprService.AnonymizeUserAsync(username) replaces all user PII with [anonymized]:

  • HitlCheckpoint.DecisionBy
  • McpServerInfo.ApprovedBy
  • Skill.ApprovedBy
  • ProjectMemory entries where AgentId matches

Records are retained (not deleted) to preserve audit integrity. Only the personal content is scrubbed.

PII in Logs

PiiScrubberPolicy (Serilog IDestructuringPolicy) masks email addresses in all log messages as [email-redacted]. Applied globally at LoggerConfiguration level.

Data Retention

AiAuditEntry records are retained for 6 months from CreatedAt. A scheduled job (or EF query) should purge entries older than 180 days. SHA-256 hashes are stored, never raw PII.

EU AI Act (Regulation 2024/1689)

Nox is classified as Limited Risk (Art. 52 transparency obligations):

  • Internal tooling for software development lifecycle
  • Not used in high-risk domains (healthcare, law enforcement, credit scoring)
  • Humans retain final decision authority (all agent outputs reviewed via HITL checkpoints)

Art. 12 — Audit Logging

AiAuditEntry captures:

  • AgentId, ActionType, InputHash (SHA-256), OutputHash (SHA-256)
  • ModelUsed, TokensUsed, HitlCheckpointId, Timestamp

No raw prompt/response content is stored — only hashes, preventing GDPR conflicts.

Security Fixes (Pen-Test 2026-03-23)

SSRF — Server-Side Request Forgery (CRITICAL)

Location: NoxMcpClientManager.SendJsonRpcAsync

Fix: ValidateEndpointUrl() blocks:

  • Non-HTTP/HTTPS schemes
  • Loopback (localhost, 127.x.x.x, ::1)
  • RFC-1918 private ranges (10.x, 172.16-31.x, 192.168.x)
  • Link-local (169.254.x.x)

ACP Endpoint Authentication (CRITICAL)

Location: AcpRoutingMiddlewarePOST /acp/message

Fix: Returns HTTP 401 if User.Identity.IsAuthenticated != true.

Exception Leakage (HIGH)

Location: AcpRoutingMiddleware catch block

Fix: Generic "Internal error processing ACP message" replaces ex.Message in responses. Full exception is still logged server-side.

SignalR Hub Authorization (HIGH)

Fix: [Authorize(Policy = NoxPolicies.AnyUser)] on both HitlHub and AgentMonitorHub.

Race Condition — Double Decision (HIGH)

Location: PostgresHitlQueue.SubmitDecisionAsync

Fix: FirstOrDefaultAsync(h => h.Id == id && h.Status == Pending) — returns HTTP 409 if checkpoint already resolved.

MCP Endpoint Authorization (HIGH)

Fix: app.MapMcp("/mcp").RequireAuthorization(NoxPolicies.AnyUser)

decidedBy from Caller Input (HIGH)

Location: FlowTools.SubmitCheckpointDecision

Fix: decidedBy is extracted from IHttpContextAccessor → JWT sub / Identity.Name. Not accepted from tool call arguments.

Rate Limiting (MEDIUM)

Fix: 300 req/min per user via AddRateLimiter (fixed window).

Body Size Limit (MEDIUM)

Fix: Kestrel MaxRequestBodySize = 1 MB.

Unbounded topK (MEDIUM)

Location: ACP memory.query.request handler

Fix: Math.Clamp(topK, 1, 100).

TLS

  • Dev: self-signed certificate generated by infra/certs/generate-dev-certs.sh
  • Production: use a CA-signed certificate; set RequireHttpsMetadata: true in Keycloak auth config

There aren't any published security advisories