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:
2026-02-07 16:55:49 -06:00
parent 33dc746714
commit 73074932f6
7 changed files with 959 additions and 0 deletions

View File

@@ -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
*/