@flamesshield/rules-engine
Version:
Independent rules engine for security analysis of Firebase
240 lines • 10.4 kB
JavaScript
/**
* Computed Properties Utility Functions
*
* This module provides utility functions for calculating computed properties
* from ProjectInformation objects, replacing the old fact-transformer approach.
*/
/**
* Calculates the enforcement ratio for App Check services
* @param projectInfo - The project information object
* @returns A number between 0 and 1 representing the ratio of enforced services
*/
export function calculateEnforcementRatio(projectInfo) {
if (!projectInfo.app_check || !projectInfo.app_check.services) {
return 0;
}
const totalServices = projectInfo.app_check.services.length;
if (totalServices === 0) {
return 0;
}
const enforcedServices = projectInfo.app_check.services.filter(service => service.is_enforced).length;
return enforcedServices / totalServices;
}
/**
* Counts the number of apps without attestation providers
* @param projectInfo - The project information object
* @returns The count of apps without attestation providers
*/
export function countAppsWithoutAttestationProviders(projectInfo) {
if (!projectInfo.app_check || !projectInfo.app_check.apps) {
return 0;
}
return projectInfo.app_check.apps.filter(app => !app.attestation_providers || app.attestation_providers.length === 0).length;
}
/**
* Calculates a simple security score based on various factors
* @param projectInfo - The project information object
* @returns A number between 0 and 1 representing the security score
*/
export function calculateSecurityScore(projectInfo) {
let score = 0;
const weights = {
auth: 0.3,
app_check: 0.3,
enforcement: 0.2,
mfa: 0.1,
emailVerification: 0.1
};
// Auth configuration score
if (projectInfo.auth) {
const authScore = projectInfo.auth.enforced ? 1 : 0;
score += authScore * weights.auth;
}
// App Check score
if (projectInfo.app_check) {
const appCheckScore = projectInfo.app_check.app_check_enabled ? 1 : 0;
score += appCheckScore * weights.app_check;
}
// Enforcement ratio score
const enforcementRatio = calculateEnforcementRatio(projectInfo);
score += enforcementRatio * weights.enforcement;
// MFA score
if (projectInfo.auth?.mfa_enabled) {
score += weights.mfa;
}
// Email verification score
if (projectInfo.auth?.email_verification_enabled) {
score += weights.emailVerification;
}
return Math.min(score, 1); // Cap at 1
}
/**
* Determines if the project has secrets using the function_secrets scan results
* @param projectInfo - The project information object
* @returns True if secrets are detected, false otherwise
*/
export function hasSecrets(projectInfo) {
// Primary check: Use function_secrets scan results (authoritative source)
if (projectInfo.function_secrets && projectInfo.function_secrets.length > 0) {
return projectInfo.function_secrets.some(scanResult => scanResult.secretsCount > 0);
}
return false;
}
/**
* Determines if the project has HTTP triggered functions
* @param projectInfo - The project information object
* @returns True if HTTP triggered functions are found, false otherwise
*/
export function hasHttpTriggeredFunctions(projectInfo) {
// Check functionsv1 for HTTP triggers
if (projectInfo.functionsv1 && projectInfo.functionsv1.length > 0) {
const hasHttpV1 = projectInfo.functionsv1.some((func) => func.trigger === 'HTTP' || func.trigger === 'CALLABLE');
if (hasHttpV1)
return true;
}
// Check functionsv2 for HTTP triggers (CALLABLE type)
if (projectInfo.functionsv2 && projectInfo.functionsv2.length > 0) {
const hasCallableV2 = projectInfo.functionsv2.some((func) => func.trigger === 'CALLABLE' || func.trigger === 'HTTP');
if (hasCallableV2)
return true;
}
return false;
}
/**
* Determines if this is a multi-region deployment
* @param projectInfo - The project information object
* @returns True if deployed across multiple regions, false otherwise
*/
export function isMultiRegionDeployment(projectInfo) {
const regions = new Set();
// Check functionsv1 regions
if (projectInfo.functionsv1) {
projectInfo.functionsv1.forEach((func) => {
if (func.location) {
regions.add(func.location);
}
});
}
// Check functionsv2 regions
if (projectInfo.functionsv2) {
projectInfo.functionsv2.forEach((func) => {
if (func.location) {
regions.add(func.location);
}
});
}
// Check database regions
if (projectInfo.databases) {
projectInfo.databases.forEach(db => {
if (db.location) {
regions.add(db.location);
}
});
}
// Check storage bucket regions
if (projectInfo.storage_buckets) {
projectInfo.storage_buckets.forEach(bucket => {
if (bucket.location) {
regions.add(bucket.location);
}
});
}
return regions.size > 1;
}
import { SecurityAnalysisClient } from '../api/SecurityAnalysisClient.js';
/**
* Creates a vulnerability lookup object that can be used to check if specific vulnerability IDs exist
* @param vulnerabilities - The array of vulnerability findings
* @returns An object with vulnerability IDs as keys and boolean values
*/
export function createVulnerabilityLookup(vulnerabilities) {
if (!vulnerabilities || vulnerabilities.length === 0) {
return {};
}
const lookup = {};
vulnerabilities.forEach(vulnerability => {
if (vulnerability.vulnerabilityId) {
lookup[vulnerability.vulnerabilityId] = true;
}
});
return lookup;
}
/**
* Creates an enriched ProjectInformation object with all computed properties
* @param projectInfo - The base project information object
* @returns An enriched project information object with computed properties
*/
export async function enrichProjectInformation(projectInfo) {
const totalServices = projectInfo.app_check?.services?.length || 0;
const enforcedServices = projectInfo.app_check?.services?.filter(s => s.is_enforced).length || 0;
const unenforced = totalServices - enforcedServices;
const totalApps = projectInfo.app_check?.apps?.length || 0;
const appsWithoutProviders = countAppsWithoutAttestationProviders(projectInfo);
const appsWithProviders = totalApps - appsWithoutProviders;
// Helper function to check if a specific service is unenforced
const isServiceUnenforced = (serviceName) => {
const service = projectInfo.app_check?.services?.find(s => s.name === serviceName);
return service ? !service.is_enforced : false;
};
let firestore_vulnerabilities = [];
if (projectInfo.firestore_rules) {
try {
const client = new SecurityAnalysisClient();
firestore_vulnerabilities = await client.analyzeFirestoreRules(projectInfo.firestore_rules);
}
catch (e) {
firestore_vulnerabilities = [];
}
}
const computed = {
enforcement_ratio: calculateEnforcementRatio(projectInfo),
apps_without_attestation_providers: appsWithoutProviders,
security_score: calculateSecurityScore(projectInfo),
has_secrets: hasSecrets(projectInfo),
multi_region_deployment: isMultiRegionDeployment(projectInfo),
has_http_triggered_functions: hasHttpTriggeredFunctions(projectInfo),
total_services: totalServices,
enforced_services: enforcedServices,
unenforced_services: unenforced,
total_apps: totalApps,
apps_with_attestation_providers: appsWithProviders,
firestore_vulnerabilities: firestore_vulnerabilities || [],
fails_rule: createVulnerabilityLookup(firestore_vulnerabilities || []),
// Service-specific enforcement flags
auth_service_unenforced: isServiceUnenforced('identitytoolkit.googleapis.com'),
functions_service_unenforced: isServiceUnenforced('cloudfunctions.googleapis.com'),
dataconnect_service_unenforced: isServiceUnenforced('firebasedataconnect.googleapis.com'),
ailogic_service_unenforced: isServiceUnenforced('firebaseailogic.googleapis.com'),
google_identity_ios_service_unenforced: isServiceUnenforced('googleidentity.googleapis.com'),
maps_js_service_unenforced: isServiceUnenforced('maps-js'),
places_api_service_unenforced: isServiceUnenforced('places-backend.googleapis.com'),
rtdb_service_unenforced: isServiceUnenforced('firebasedatabase.googleapis.com'),
storage_service_unenforced: isServiceUnenforced('storage.googleapis.com'),
};
return {
...projectInfo,
has_secrets: hasSecrets(projectInfo),
computed,
// Add direct access properties for test compatibility
total_apps: totalApps,
total_services: totalServices,
unenforced_services: unenforced,
enforcement_coverage_ratio: totalServices > 0 ? enforcedServices / totalServices : 0,
security_summary: projectInfo.app_check?.security_summary,
apps_without_attestation_providers: appsWithoutProviders,
has_http_triggered_functions: hasHttpTriggeredFunctions(projectInfo),
firestore_vulnerabilities: firestore_vulnerabilities || [],
fails_rule: createVulnerabilityLookup(firestore_vulnerabilities || []),
// Service-specific enforcement flags
auth_service_unenforced: isServiceUnenforced('identitytoolkit.googleapis.com'),
functions_service_unenforced: isServiceUnenforced('cloudfunctions.googleapis.com'),
dataconnect_service_unenforced: isServiceUnenforced('firebasedataconnect.googleapis.com'),
ailogic_service_unenforced: isServiceUnenforced('firebaseailogic.googleapis.com'),
google_identity_ios_service_unenforced: isServiceUnenforced('googleidentity.googleapis.com'),
maps_js_service_unenforced: isServiceUnenforced('maps-js'),
places_api_service_unenforced: isServiceUnenforced('places-backend.googleapis.com'),
rtdb_service_unenforced: isServiceUnenforced('firebasedatabase.googleapis.com'),
storage_service_unenforced: isServiceUnenforced('storage.googleapis.com'),
};
}
//# sourceMappingURL=computed-properties.js.map