feat(#360): Add federation credential isolation
Implement explicit deny-lists in QueryService and CommandService to prevent user credentials from leaking across federation boundaries. ## Changes ### Core Implementation - QueryService: Block all credential-related queries with keyword detection - CommandService: Block all credential operations (create/update/delete/read) - Case-insensitive keyword matching for both queries and commands ### Security Features - Deny-list includes: credential, api_key, secret, token, password, oauth - Errors returned for blocked operations - No impact on existing allowed operations (tasks, events, projects, agent commands) ### Testing - Added 2 unit tests to query.service.spec.ts - Added 3 unit tests to command.service.spec.ts - Added 8 integration tests in credential-isolation.integration.spec.ts - All 377 federation tests passing ### Documentation - Created comprehensive security doc at docs/security/federation-credential-isolation.md - Documents 4 security guarantees (G1-G4) - Includes testing strategy and incident response procedures ## Security Guarantees 1. G1: Credential Confidentiality - Credentials never leave instance in plaintext 2. G2: Cross-Instance Isolation - Compromised key on one instance doesn't affect others 3. G3: Query/Command Isolation - Federated instances cannot query/modify credentials 4. G4: Accidental Exposure Prevention - Credentials cannot leak via messages ## Defense-in-Depth This implementation adds application-layer protection on top of existing: - Transit key separation (mosaic-credentials vs mosaic-federation) - Per-instance OpenBao servers - Workspace-scoped credential access Fixes #360 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -375,6 +375,12 @@ export class QueryService {
|
||||
throw new Error("workspaceId is required in query context");
|
||||
}
|
||||
|
||||
// SECURITY: Block all credential-related queries
|
||||
// Credentials must never be exposed via federation
|
||||
if (this.isCredentialQuery(query)) {
|
||||
throw new Error("Credential queries are not allowed via federation");
|
||||
}
|
||||
|
||||
// Parse query to determine type and parameters
|
||||
const queryType = this.parseQueryType(query);
|
||||
const queryParams = this.parseQueryParams(query, context);
|
||||
@@ -392,6 +398,29 @@ export class QueryService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if query is attempting to access credential data
|
||||
* Returns true if query contains credential-related keywords
|
||||
*/
|
||||
private isCredentialQuery(query: string): boolean {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
// Deny-list of credential-related keywords
|
||||
const credentialKeywords = [
|
||||
"credential",
|
||||
"user_credential",
|
||||
"api_key",
|
||||
"api key",
|
||||
"secret",
|
||||
"token",
|
||||
"password",
|
||||
"oauth",
|
||||
"access_token",
|
||||
];
|
||||
|
||||
return credentialKeywords.some((keyword) => lowerQuery.includes(keyword));
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse query string to determine query type
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user