Security
ANCP enforces security at four layers: transport encryption, connection authentication, tenant isolation, and (planned) message-level signing — so every message is verified before it reaches a handler.
Security Layers Overview
Transport Encryption
All transports require TLS 1.2 or higher. Plaintext connections are rejected at the server level.
Connection Authentication
SignalR connections require a signed JWT. HTTP calls require a bearer token or API key. Unauthenticated connections are refused before any message is processed.
Tenant Isolation
Every message's tenantId is verified against the authenticated credential. Cross-tenant messages are silently discarded and audited.
Message Signing
Planned for ANCP 1.1 — Ed25519 signatures on the envelope header prevent message tampering in transit.
Transport Encryption (TLS)
All ANCP transports operate over TLS. The platform enforces TLS 1.2 as the minimum version, with TLS 1.3 preferred. Certificate validation is not optional — self-signed certificates are rejected in production deployments.
// ASP.NET Core — enforce HTTPS on the SignalR hub
app.UseHttpsRedirection();
app.UseHsts();
// In Kestrel configuration (appsettings.json)
{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://+:443",
"Certificate": {
"Subject": "*.bizfirst.io",
"Store": "My",
"Location": "LocalMachine"
}
}
}
}
}
Authentication
SignalR — JWT Bearer
Browser clients and server-side node hosts authenticate to the SignalR hub using a JWT bearer token. The token is attached during connection negotiation and validated before the connection is accepted. Because WebSockets cannot send HTTP headers after the upgrade, the token is passed as a query parameter during negotiation:
// Client-side connection with JWT
const connection = new HubConnectionBuilder()
.withUrl('https://api.bizfirst.io/ancp-hub', {
accessTokenFactory: () => getAccessToken() // Called on each connect/reconnect
})
.withAutomaticReconnect([1000, 2000, 5000, 10000, 30000])
.build();
await connection.start();
// ASP.NET Core — JWT validation for SignalR
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => {
options.TokenValidationParameters = new TokenValidationParameters {
ValidateIssuer = true,
ValidIssuer = "https://identity.bizfirst.io",
ValidateAudience = true,
ValidAudience = "ancp-api",
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
IssuerSigningKey = signingKey
};
// Allow JWT in query string for SignalR WebSocket upgrade
options.Events = new JwtBearerEvents {
OnMessageReceived = ctx => {
var token = ctx.Request.Query["access_token"];
if (!string.IsNullOrEmpty(token))
ctx.Token = token;
return Task.CompletedTask;
}
};
});
HTTP — Bearer Token and API Keys
Server-to-server HTTP calls use a bearer JWT in the Authorization header. External integrations may use API keys scoped to a specific tenant and permission set:
POST /api/ancp/messages HTTP/1.1
Authorization: Bearer eyJhbGciOiJSUzI1NiJ9...
X-Tenant-Id: tenant-acme
Content-Type: application/json
Tenant Isolation
After authentication succeeds, the router performs tenant isolation checks on every incoming message. This is enforced at the protocol layer — it does not depend on application code to remember to check.
| Check | Failure Action | Audit Event |
|---|---|---|
Envelope tenantId matches JWT tenantId claim |
Message discarded | ANCP_TENANT_MISMATCH |
Source URI tenantId matches envelope tenantId |
Message discarded | ANCP_SOURCE_TENANT_MISMATCH |
Destination URI tenantId matches envelope tenantId |
Message discarded | ANCP_DEST_TENANT_MISMATCH |
| Topic subscription belongs to same tenant | Subscription refused | ANCP_CROSS_TENANT_SUBSCRIBE |
Cross-tenant messages are not returned to the sender with an error message. This prevents an attacker from probing which tenants exist by watching for error vs. no-response. The message is dropped silently; only the audit log records the violation.
Node Capability Authorization
Beyond tenant isolation, the ProcessSecurity layer checks that the source node or agent has the capability to send the requested message type to the destination. Capabilities are defined in the NodeCapabilityPolicy table.
// Example: Node capability check (C# — ProcessSecurity)
public async Task<bool> CanSendAsync(string sourceAddress, string destAddress, string messageType)
{
var sourceNodeId = AncpAddress.Parse(sourceAddress).NodeId;
var policy = await _nodeCapabilityRepo.GetAsync(sourceNodeId);
return policy.AllowedDestinations.Contains(destAddress)
&& policy.AllowedMessageTypes.Contains(messageType);
}
Message Signing (ANCP 1.1 — Planned)
ANCP 1.1 will introduce optional message signing using Ed25519 asymmetric keys. The signature covers the canonical form of the envelope header fields and prevents tampering in transit.
// Planned signature header format (ANCP 1.1)
{
"id": "msg-uuid",
"type": "Command",
// ... other fields ...
"signature": {
"algorithm": "Ed25519",
"keyId": "node-signing-key-v1",
"value": "base64url-encoded-signature"
}
}
For high-trust agent-to-agent communication, ANCP supports carrying DIDComm v2 encrypted payloads inside the payload field. The outer ANCP envelope handles routing and tenant isolation; the DIDComm layer provides end-to-end encryption between specific agents. See the DIDComm v2 documentation for key exchange and envelope format details.
Security Audit Trail
All security events are written to the SecurityAuditLog table in the ProcessSecurity database. Each audit entry records:
- Message ID and timestamp
- Source and destination addresses
- tenantId (from envelope and from JWT)
- Event type (authentication, tenant check, capability check)
- Outcome (allowed/denied)
- Actor IP address and session ID
Security Checklist for Node Authors
| Requirement | Status |
|---|---|
Always set tenantId from authenticated context — never from request input | Mandatory |
Never log the full payload in production — it may contain PII | Mandatory |
| Use short-lived JWT tokens (15 min TTL) with refresh | Mandatory |
Set ttl on time-sensitive Commands to prevent replay | Recommended |
Set traceId from incoming request context for end-to-end tracing | Recommended |
Validate correlationId on Responses before trusting the data | Recommended |