Implemented comprehensive URL validation to prevent SSRF attacks: - Created URL validator utility with protocol whitelist (http/https only) - Blocked access to private IP ranges (10.x, 192.168.x, 172.16-31.x) - Blocked loopback addresses (127.x, localhost, 0.0.0.0) - Blocked link-local addresses (169.254.x) - Blocked IPv6 localhost (::1, ::) - Allow localhost in development/test environments only - Added structured audit logging for invalid URL attempts - Comprehensive test coverage (37 tests for URL validator) Security Impact: - Prevents attackers from redirecting agent spawn requests to internal services - Blocks data exfiltration via malicious orchestrator URL - All agent operations now validated against SSRF Files changed: - apps/api/src/federation/utils/url-validator.ts (new) - apps/api/src/federation/utils/url-validator.spec.ts (new) - apps/api/src/federation/federation-agent.service.ts (validation integration) - apps/api/src/federation/federation-agent.service.spec.ts (test updates) - apps/api/src/federation/audit.service.ts (audit logging) - apps/api/src/federation/federation.module.ts (service exports) Fixes #279 Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
225 lines
5.8 KiB
TypeScript
225 lines
5.8 KiB
TypeScript
/**
|
|
* Federation Audit Service
|
|
*
|
|
* Logs security-sensitive operations for compliance and monitoring.
|
|
* Uses application logger since ActivityLog requires workspace context.
|
|
*/
|
|
|
|
import { Injectable, Logger } from "@nestjs/common";
|
|
|
|
@Injectable()
|
|
export class FederationAuditService {
|
|
private readonly logger = new Logger(FederationAuditService.name);
|
|
|
|
/**
|
|
* Log instance keypair regeneration (system-level operation)
|
|
* Logged to application logs for security audit trail
|
|
*/
|
|
logKeypairRegeneration(userId: string, instanceId: string): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_KEYPAIR_REGENERATED",
|
|
userId,
|
|
instanceId,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log instance configuration update (system-level operation)
|
|
* Logged to application logs for security audit trail
|
|
*/
|
|
logInstanceConfigurationUpdate(
|
|
userId: string,
|
|
instanceId: string,
|
|
updates: Record<string, unknown>
|
|
): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_INSTANCE_CONFIG_UPDATED",
|
|
userId,
|
|
instanceId,
|
|
updates,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log federated authentication initiation
|
|
*/
|
|
logFederatedAuthInitiation(userId: string, remoteInstanceId: string): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_AUTH_INITIATED",
|
|
userId,
|
|
remoteInstanceId,
|
|
timestamp: new Date().toISOString(),
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log federated identity linking
|
|
*/
|
|
logFederatedIdentityLinked(userId: string, remoteInstanceId: string): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_IDENTITY_LINKED",
|
|
userId,
|
|
remoteInstanceId,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log federated identity revocation
|
|
*/
|
|
logFederatedIdentityRevoked(userId: string, remoteInstanceId: string): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_IDENTITY_REVOKED",
|
|
userId,
|
|
remoteInstanceId,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log identity verification attempt
|
|
*/
|
|
logIdentityVerification(userId: string, remoteInstanceId: string, success: boolean): void {
|
|
const level = success ? "log" : "warn";
|
|
this.logger[level]({
|
|
event: "FEDERATION_IDENTITY_VERIFIED",
|
|
userId,
|
|
remoteInstanceId,
|
|
success,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log identity linking (create mapping)
|
|
*/
|
|
logIdentityLinking(localUserId: string, remoteInstanceId: string, remoteUserId: string): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_IDENTITY_LINKED",
|
|
localUserId,
|
|
remoteUserId,
|
|
remoteInstanceId,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log identity revocation (remove mapping)
|
|
*/
|
|
logIdentityRevocation(localUserId: string, remoteInstanceId: string): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_IDENTITY_REVOKED",
|
|
localUserId,
|
|
remoteInstanceId,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log capability denial (security event)
|
|
* Logged when remote instance attempts operation without required capability
|
|
*/
|
|
logCapabilityDenied(
|
|
remoteInstanceId: string,
|
|
requiredCapability: string,
|
|
requestedUrl: string
|
|
): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_CAPABILITY_DENIED",
|
|
remoteInstanceId,
|
|
requiredCapability,
|
|
requestedUrl,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log incoming connection attempt
|
|
* Logged for all incoming connection requests (security monitoring)
|
|
*/
|
|
logIncomingConnectionAttempt(data: {
|
|
workspaceId: string;
|
|
remoteInstanceId: string;
|
|
remoteUrl: string;
|
|
timestamp: number;
|
|
}): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_INCOMING_CONNECTION_ATTEMPT",
|
|
workspaceId: data.workspaceId,
|
|
remoteInstanceId: data.remoteInstanceId,
|
|
remoteUrl: data.remoteUrl,
|
|
requestTimestamp: new Date(data.timestamp).toISOString(),
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log incoming connection created
|
|
* Logged when an incoming connection is successfully created
|
|
*/
|
|
logIncomingConnectionCreated(data: {
|
|
workspaceId: string;
|
|
connectionId: string;
|
|
remoteInstanceId: string;
|
|
remoteUrl: string;
|
|
}): void {
|
|
this.logger.log({
|
|
event: "FEDERATION_INCOMING_CONNECTION_CREATED",
|
|
workspaceId: data.workspaceId,
|
|
connectionId: data.connectionId,
|
|
remoteInstanceId: data.remoteInstanceId,
|
|
remoteUrl: data.remoteUrl,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log incoming connection rejected
|
|
* Logged when an incoming connection is rejected (security event)
|
|
*/
|
|
logIncomingConnectionRejected(data: {
|
|
workspaceId: string;
|
|
remoteInstanceId: string;
|
|
remoteUrl?: string;
|
|
reason: string;
|
|
error?: string;
|
|
}): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_INCOMING_CONNECTION_REJECTED",
|
|
workspaceId: data.workspaceId,
|
|
remoteInstanceId: data.remoteInstanceId,
|
|
remoteUrl: data.remoteUrl,
|
|
reason: data.reason,
|
|
error: data.error,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Log invalid orchestrator URL configuration attempt
|
|
* Logged when orchestrator URL validation fails (SSRF prevention)
|
|
*/
|
|
logInvalidOrchestratorUrl(url: string, error: string): void {
|
|
this.logger.warn({
|
|
event: "FEDERATION_INVALID_ORCHESTRATOR_URL",
|
|
url,
|
|
error,
|
|
timestamp: new Date().toISOString(),
|
|
securityEvent: true,
|
|
});
|
|
}
|
|
}
|