Skip to main content
Glama
policy-enforcer.ts6.82 kB
/** * Security Policy Enforcer - Content scanning and sanitization */ import type { PolicyResult, PolicyViolation, ProjectConfig } from '../types/tracing.js'; export class PolicyEnforcer { // Default secret patterns (regex) private static readonly DEFAULT_SECRET_PATTERNS = [ /sk-[a-zA-Z0-9]{32,}/g, // OpenAI API keys /ghp_[a-zA-Z0-9]{36}/g, // GitHub tokens /gho_[a-zA-Z0-9]{36}/g, // GitHub OAuth /glpat-[a-zA-Z0-9_-]{20}/g, // GitLab tokens /AKIA[0-9A-Z]{16}/g, // AWS access keys /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email addresses /\b\d{3}-\d{2}-\d{4}\b/g, // SSN-like patterns /-----BEGIN [A-Z ]+-----[\s\S]+-----END [A-Z ]+-----/g, // Private keys ]; // Default blocked keywords private static readonly DEFAULT_BLOCKED_KEYWORDS = [ 'DROP TABLE', 'DELETE FROM', 'TRUNCATE', 'rm -rf /', 'sudo rm', 'format C:', ]; constructor(private projectConfig?: ProjectConfig) { } /** * Scan prompt for security violations */ scanPrompt(content: string): PolicyResult { if (!this.projectConfig?.security?.scanPrompts) { return { allowed: true, violations: [] }; } const violations: PolicyViolation[] = []; // Check for secrets if (this.projectConfig.security.scanPrompts) { violations.push(...this.detectSecrets(content)); } // Check for blocked keywords violations.push(...this.detectBlockedKeywords(content)); // Determine if request should be blocked const hasHighSeverity = violations.some((v) => v.severity === 'high'); return { allowed: !hasHighSeverity, violations, sanitizedContent: this.projectConfig.security.redactSecrets ? this.redactSecrets(content) : undefined, }; } /** * Scan LLM output for security violations */ scanOutput(content: string): PolicyResult { if (!this.projectConfig?.security?.scanOutputs) { return { allowed: true, violations: [] }; } const violations: PolicyViolation[] = []; // Check for secrets in output violations.push(...this.detectSecrets(content)); // Check for unsafe commands violations.push(...this.detectUnsafeCommands(content)); return { allowed: true, // Don't block outputs, just flag violations, sanitizedContent: this.projectConfig.security.redactSecrets ? this.redactSecrets(content) : undefined, }; } /** * Detect secrets using regex patterns */ private detectSecrets(content: string): PolicyViolation[] { const violations: PolicyViolation[] = []; const patterns = this.projectConfig?.security?.secretPatterns?.map((p) => new RegExp(p, 'g')) ?? PolicyEnforcer.DEFAULT_SECRET_PATTERNS; for (const pattern of patterns) { let match; while ((match = pattern.exec(content)) !== null) { violations.push({ type: 'secret_detected', pattern: pattern.source, position: { start: match.index, end: match.index + match[0].length, }, severity: 'high', suggestion: 'Remove or redact sensitive information', }); } } return violations; } /** * Detect blocked keywords */ private detectBlockedKeywords(content: string): PolicyViolation[] { const violations: PolicyViolation[] = []; const keywords = this.projectConfig?.security?.blockedKeywords ?? PolicyEnforcer.DEFAULT_BLOCKED_KEYWORDS; const lowerContent = content.toLowerCase(); for (const keyword of keywords) { const lowerKeyword = keyword.toLowerCase(); let index = lowerContent.indexOf(lowerKeyword); while (index !== -1) { violations.push({ type: 'blocked_keyword', pattern: keyword, position: { start: index, end: index + keyword.length, }, severity: 'high', suggestion: `Blocked keyword "${keyword}" detected`, }); index = lowerContent.indexOf(lowerKeyword, index + 1); } } return violations; } /** * Detect unsafe shell commands */ private detectUnsafeCommands(content: string): PolicyViolation[] { const violations: PolicyViolation[] = []; const unsafeCommands = [ 'rm -rf /', 'sudo rm', 'format C:', 'del /F /S /Q', 'dd if=/dev/zero', ':(){ :|:& };:', // Fork bomb ]; for (const cmd of unsafeCommands) { const index = content.indexOf(cmd); if (index !== -1) { violations.push({ type: 'unsafe_command', pattern: cmd, position: { start: index, end: index + cmd.length, }, severity: 'high', suggestion: `Potentially dangerous command: ${cmd}`, }); } } return violations; } /** * Redact secrets from content */ private redactSecrets(content: string): string { let sanitized = content; const patterns = this.projectConfig?.security?.secretPatterns?.map((p) => new RegExp(p, 'g')) ?? PolicyEnforcer.DEFAULT_SECRET_PATTERNS; for (const pattern of patterns) { sanitized = sanitized.replace(pattern, (match) => { const visible = Math.min(4, Math.floor(match.length / 4)); const redacted = '*'.repeat(match.length - visible); return match.substring(0, visible) + redacted; }); } return sanitized; } /** * Get policy configuration */ getConfig(): ProjectConfig['security'] | undefined { return this.projectConfig?.security; } /** * Update policy configuration */ updateConfig(security: ProjectConfig['security']): void { if (!this.projectConfig) { this.projectConfig = { security }; } else { this.projectConfig.security = security; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/babasida246/ai-mcp-gateway'

If you have feedback or need assistance with the MCP directory API, please join our Discord server