- 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.
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 | 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 |
GdprService.AnonymizeUserAsync(username) replaces all user PII with [anonymized]:
HitlCheckpoint.DecisionByMcpServerInfo.ApprovedBySkill.ApprovedByProjectMemoryentries whereAgentIdmatches
Records are retained (not deleted) to preserve audit integrity. Only the personal content is scrubbed.
PiiScrubberPolicy (Serilog IDestructuringPolicy) masks email addresses in all log messages as [email-redacted]. Applied globally at LoggerConfiguration level.
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.
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)
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.
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)
Location: AcpRoutingMiddleware — POST /acp/message
Fix: Returns HTTP 401 if User.Identity.IsAuthenticated != true.
Location: AcpRoutingMiddleware catch block
Fix: Generic "Internal error processing ACP message" replaces ex.Message in responses. Full exception is still logged server-side.
Fix: [Authorize(Policy = NoxPolicies.AnyUser)] on both HitlHub and AgentMonitorHub.
Location: PostgresHitlQueue.SubmitDecisionAsync
Fix: FirstOrDefaultAsync(h => h.Id == id && h.Status == Pending) — returns HTTP 409 if checkpoint already resolved.
Fix: app.MapMcp("/mcp").RequireAuthorization(NoxPolicies.AnyUser)
Location: FlowTools.SubmitCheckpointDecision
Fix: decidedBy is extracted from IHttpContextAccessor → JWT sub / Identity.Name. Not accepted from tool call arguments.
Fix: 300 req/min per user via AddRateLimiter (fixed window).
Fix: Kestrel MaxRequestBodySize = 1 MB.
Location: ACP memory.query.request handler
Fix: Math.Clamp(topK, 1, 100).
- Dev: self-signed certificate generated by
infra/certs/generate-dev-certs.sh - Production: use a CA-signed certificate; set
RequireHttpsMetadata: truein Keycloak auth config