← Back to Home
✍️ Yantratmika Solutions 📅 2025-11-21 ⏱️ 23 min read

From LLM Prompt to Production (7/9) - Security and Compliance

This is part of a series of blogs:

  1. Introduction
  2. Choosing the Right Technology
  3. Architecture Patterns
  4. Multi-Prompt Chaining
  5. Additional Complexity
  6. Redundancy & Scaling
  7. Security & Compliance
  8. Performance Optimization
  9. Observability & Monitoring

“We just need to add HTTPS and we’re secure, right?” If you’ve heard this in a planning meeting for an LLM API that will handle sensitive data, you probably felt that familiar chill down your spine. The security challenges of production LLM APIs extend far beyond transport encryption—they encompass data classification, regulatory compliance, prompt injection attacks, and the fundamental question of whether sending sensitive data to third-party LLM providers is acceptable at all.

When your LLM API needs to process financial records, healthcare data, or personally identifiable information, the security requirements multiply exponentially. A single data breach can result in millions in fines, lost customer trust, and regulatory scrutiny that can cripple an organization.

In this section, we’ll explore the complex security and compliance landscape for LLM APIs handling sensitive data, using AWS serverless architecture with GoLang and CDK. We’ll examine real-world threat models, regulatory requirements, and practical solutions that go far beyond basic security checklists.

Understanding the Sensitive Data Challenge

The moment your LLM API touches sensitive data, you enter a world where traditional API security patterns are insufficient. Consider a healthcare AI assistant that helps doctors analyze patient records, or a financial advisor bot that processes transaction histories. These aren’t just APIs—they’re systems that must comply with HIPAA, PCI DSS, SOX, GDPR, and numerous other regulations simultaneously.

The fundamental challenge is that LLMs, by design, need context to be effective. This creates an inherent tension between functionality and security—the more context you provide, the better the responses, but the greater the risk if that data is compromised or misused.

The Hidden Compliance Complexity

Most organizations underestimate the scope of compliance requirements. It’s not just about encrypting data—it’s about data lineage tracking, audit trails, breach notification procedures, data retention policies, and demonstrating compliance to auditors who may not understand AI systems.

Here’s how to establish a compliance foundation with proper data classification and encryption controls:

// infrastructure/compliance-foundation.ts - Basic stack setup
export class ComplianceFoundationStack extends Stack {
  constructor(scope: Construct, id: string, props: ComplianceProps) {
    super(scope, id, props);

    // Data classification tags that drive security policies
    const dataClassification = {
      'data-classification': props.dataClassification, // PUBLIC, INTERNAL, CONFIDENTIAL, RESTRICTED
      'compliance-scope': props.complianceScope,       // HIPAA, PCI, SOX, GDPR
      'retention-period': props.retentionPeriod,       // Legal retention requirements
      'data-residency': props.dataResidency,           // Geographic restrictions
    };

Next, we establish enterprise-grade encryption with automated key rotation:

    // KMS key with compliance-grade configuration
    const encryptionKey = new Key(this, 'ComplianceKey', {
      description: `Encryption key for ${props.dataClassification} data`,
      enableKeyRotation: true,
      rotationSchedule: RotationSchedule.rate(Duration.days(90)), // Quarterly rotation
      policy: new PolicyDocument({
        statements: [
          new PolicyStatement({
            sid: 'Enable compliance team access',
            principals: [new ArnPrincipal(props.complianceRoleArn)],
            actions: ['kms:*'],
            resources: ['*'],
          }),

Finally, we implement geographic restrictions to ensure data sovereignty compliance:

          // Deny access from non-compliant regions
          new PolicyStatement({
            sid: 'DenyNonCompliantRegions',
            effect: Effect.DENY,
            principals: [new AnyPrincipal()],
            actions: ['kms:*'],
            resources: ['*'],
            conditions: {
              StringNotEquals: {
                'aws:RequestedRegion': props.allowedRegions,
              },
            },
          }),
        ],
      }),
    });

    // Apply tags to all resources for compliance tracking
    Tags.of(this).add('data-classification', props.dataClassification);
    Tags.of(this).add('compliance-scope', props.complianceScope);
  }
}

This foundation demonstrates how compliance requirements drive architectural decisions from day one. The KMS key rotation, regional restrictions, and comprehensive tagging aren’t optional extras—they’re requirements that auditors will specifically look for.

Data Classification and Intelligent Handling

Not all data is equally sensitive, but most LLM implementations treat everything the same way. This creates unnecessary complexity and cost. The solution is implementing intelligent data classification that automatically applies appropriate security controls.

Solution: Dynamic Security Controls Based on Data Classification

First, let’s establish the core data classification framework with pattern detection:

// pkg/security/data_classifier.go - Core classification types
type DataClassifier struct {
    patterns map[DataType][]Pattern
    policies map[DataType]SecurityPolicy
    auditor  *AuditLogger
}

type DataType int
const (
    PUBLIC DataType = iota
    INTERNAL
    CONFIDENTIAL
    RESTRICTED // PII, PHI, Financial data
)

type Pattern struct {
    Regex       *regexp.Regexp
    Confidence  float64
    Description string
}

Next, we define security policies that automatically apply based on data classification:

type SecurityPolicy struct {
    EncryptionRequired bool
    AuditingLevel     AuditLevel
    RetentionPeriod   time.Duration
    AllowedRegions    []string
    RequiresMFA       bool
    AllowThirdParty   bool  // Can this data be sent to external LLM providers?
}

Now let’s implement the main classification logic that scans content and applies appropriate policies:

func (dc *DataClassifier) ClassifyContent(content string, userContext *UserContext) (*ClassificationResult, error) {
    var highestClassification DataType = PUBLIC
    var detectedPatterns []DetectedPattern

    // Scan content for sensitive data patterns
    for dataType, patterns := range dc.patterns {
        for _, pattern := range patterns {
            if matches := pattern.Regex.FindAllStringSubmatch(content, -1); len(matches) > 0 {
                detectedPatterns = append(detectedPatterns, DetectedPattern{
                    Type:       dataType,
                    Pattern:    pattern.Description,
                    Confidence: pattern.Confidence,
                    Matches:    len(matches),
                    Locations:  matches,
                })

                if dataType > highestClassification {
                    highestClassification = dataType
                }
            }
        }
    }

Finally, we create the classification result with audit logging:

    result := &ClassificationResult{
        Classification:    highestClassification,
        DetectedPatterns: detectedPatterns,
        ApplicablePolicy: dc.policies[highestClassification],
        Timestamp:       time.Now(),
        UserID:         userContext.UserID,
        SessionID:      userContext.SessionID,
    }

    // Audit the classification decision
    dc.auditor.LogClassification(result)

    return result, nil
}

Here’s how we initialize the sensitive data detection patterns:

func (dc *DataClassifier) initializePatterns() {
    // Social Security Numbers
    dc.patterns[RESTRICTED] = append(dc.patterns[RESTRICTED], Pattern{
        Regex:       regexp.MustCompile(`\b\d{3}-\d{2}-\d{4}\b`),
        Confidence:  0.95,
        Description: "US Social Security Number",
    })

    // Credit Card Numbers (Luhn algorithm validation would be added in real implementation)
    dc.patterns[RESTRICTED] = append(dc.patterns[RESTRICTED], Pattern{
        Regex:       regexp.MustCompile(`\b(?:\d{4}[-\s]?){3}\d{4}\b`),
        Confidence:  0.85,
        Description: "Credit Card Number",
    })

    // Email addresses (GDPR PII)
    dc.patterns[CONFIDENTIAL] = append(dc.patterns[CONFIDENTIAL], Pattern{
        Regex:       regexp.MustCompile(`[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}`),
        Confidence:  0.90,
        Description: "Email Address",
    })
}

This classification system automatically detects sensitive data and applies appropriate security policies. The key insight is that different data types require fundamentally different handling—you can’t apply the same security controls to a weather query and a medical record.

Encryption: Beyond the Basics

“We encrypt everything” sounds reassuring, but encryption in LLM systems is complex. You need encryption in transit, at rest, in memory, and crucially, you need to maintain usability for AI processing while keeping data secure.

Solution: Multi-Layer Encryption Strategy

Let’s start with the core encryption framework that handles different security levels:

// pkg/encryption/layered_encryption.go - Core structure
type LayeredEncryption struct {
    transitEncryption *TLSConfig
    atRestEncryption  *AESEncryption
    fieldEncryption   *FieldLevelEncryption
    tokenizer        *DataTokenizer
    keyRotator       *KeyRotationManager
}

type FieldLevelEncryption struct {
    kmsClient *kms.Client
    keyID     string
    cache     map[string][]byte // Cached data encryption keys
    mutex     sync.RWMutex
}

Now let’s implement the main encryption logic that applies different strategies based on data classification:

func (le *LayeredEncryption) EncryptSensitiveData(data *SensitiveData) (*EncryptedData, error) {
    result := &EncryptedData{
        Metadata: data.Metadata,
        Timestamp: time.Now(),
    }

    // For different sensitivity levels, apply different encryption strategies
    switch data.Classification {
    case RESTRICTED:
        // Highest security: field-level encryption + tokenization
        encryptedFields, err := le.encryptFields(data.Fields)
        if err != nil {
            return nil, fmt.Errorf("field encryption failed: %w", err)
        }

        // Tokenize the encrypted data for additional protection
        tokens, err := le.tokenizer.TokenizeData(encryptedFields)
        if err != nil {
            return nil, fmt.Errorf("tokenization failed: %w", err)
        }

        result.EncryptedContent = tokens
        result.EncryptionMethod = "AES256-GCM + Tokenization"

For field-level encryption, we implement secure key management with AWS KMS:

func (fle *FieldLevelEncryption) encryptFields(fields map[string]interface{}) (map[string]interface{}, error) {
    result := make(map[string]interface{})

    for fieldName, fieldValue := range fields {
        // Convert field value to bytes
        valueBytes, err := json.Marshal(fieldValue)
        if err != nil {
            return nil, fmt.Errorf("marshaling field %s: %w", fieldName, err)
        }

        // Generate or retrieve data encryption key
        dataKey, err := fle.getDataEncryptionKey(fieldName)
        if err != nil {
            return nil, fmt.Errorf("getting encryption key for field %s: %w", fieldName, err)
        }

        // Encrypt the field value
        encryptedValue, err := fle.encryptWithKey(valueBytes, dataKey)
        if err != nil {
            return nil, fmt.Errorf("encrypting field %s: %w", fieldName, err)
        }

The key management function ensures proper key rotation and caching:

func (fle *FieldLevelEncryption) getDataEncryptionKey(fieldName string) ([]byte, error) {
    fle.mutex.RLock()
    if key, exists := fle.cache[fieldName]; exists {
        fle.mutex.RUnlock()
        return key, nil
    }
    fle.mutex.RUnlock()

    // Generate new data encryption key using KMS
    input := &kms.GenerateDataKeyInput{
        KeyId:   aws.String(fle.keyID),
        KeySpec: types.DataKeySpecAes256,
    }

    result, err := fle.kmsClient.GenerateDataKey(context.Background(), input)
    if err != nil {
        return nil, fmt.Errorf("generating data key: %w", err)
    }

    // Cache the plaintext key (in production, implement proper memory protection)
    fle.mutex.Lock()
    fle.cache[fieldName] = result.Plaintext
    fle.mutex.Unlock()

    return result.Plaintext, nil
}

This layered approach provides defense in depth. Even if one layer is compromised, the data remains protected by additional layers. The field-level encryption is particularly important for LLM applications because it allows you to selectively encrypt only the sensitive parts of a prompt while leaving non-sensitive context unencrypted for AI processing.

Access Control: Zero Trust for LLM APIs

Traditional perimeter security doesn’t work for LLM APIs that need to integrate with multiple external services and handle diverse data types. The solution is implementing zero-trust architecture where every request is authenticated, authorized, and audited.

Solution: Comprehensive Zero-Trust Implementation

First, let’s establish the core zero-trust gateway structure:

// pkg/auth/zero_trust_gateway.go - Core components
type ZeroTrustGateway struct {
    authenticator     *MultiFactorAuth
    authorizer       *PolicyBasedAuthorizer
    auditor          *ComplianceAuditor
    riskAssessment   *RiskAssessmentEngine
    sessionManager   *SessionManager
    rateLimiter      *AdaptiveRateLimiter
}

type AuthenticationContext struct {
    UserID           string
    Roles           []string
    SecurityClearance SecurityLevel
    MFAVerified     bool
    DeviceFingerprint string
    IPAddress       string
    GeographicLocation string
    RiskScore       float64
}

Now let’s implement the main authorization logic with risk-based decision making:

func (ztg *ZeroTrustGateway) AuthorizeRequest(req *AuthorizationRequest) (*AuthorizationResult, error) {
    // Step 1: Verify authentication
    authResult, err := ztg.authenticator.VerifyAuthentication(req.Context)
    if err != nil {
        return nil, fmt.Errorf("authentication failed: %w", err)
    }

    // Step 2: Assess risk based on context
    riskAssessment, err := ztg.riskAssessment.AssessRequest(req)
    if err != nil {
        return nil, fmt.Errorf("risk assessment failed: %w", err)
    }

For high-risk scenarios, we implement adaptive controls:

    // Step 3: Apply adaptive controls based on risk
    if riskAssessment.RiskLevel > RiskHigh {
        // High risk - require additional verification
        if !req.Context.MFAVerified {
            return &AuthorizationResult{
                Authorized:     false,
                RequiredActions: []string{"MFA_REQUIRED"},
                Message:        "Multi-factor authentication required for high-risk request",
            }, nil
        }

        // For restricted data, require supervisor approval
        if req.DataClassification == RESTRICTED {
            approvalRequired, err := ztg.checkSupervisorApproval(req.Context.UserID, req.RequestedResource)
            if err != nil || !approvalRequired {
                return &AuthorizationResult{
                    Authorized:     false,
                    RequiredActions: []string{"SUPERVISOR_APPROVAL_REQUIRED"},
                    Message:        "Supervisor approval required for restricted data access",
                }, nil
            }
        }
    }

We then check fine-grained permissions and apply rate limiting:

    // Step 4: Check fine-grained permissions
    permissionResult, err := ztg.authorizer.CheckPermissions(req)
    if err != nil {
        return nil, fmt.Errorf("permission check failed: %w", err)
    }

    // Step 5: Apply rate limiting based on user and risk profile
    rateLimitResult, err := ztg.rateLimiter.CheckRateLimit(req.Context, riskAssessment.RiskLevel)
    if err != nil {
        return nil, fmt.Errorf("rate limit check failed: %w", err)
    }

    if rateLimitResult.Exceeded {
        return &AuthorizationResult{
            Authorized:    false,
            RequiredActions: []string{"RATE_LIMITED"},
            Message:       fmt.Sprintf("Rate limit exceeded. Try again in %v", rateLimitResult.RetryAfter),
            RetryAfter:    rateLimitResult.RetryAfter,
        }, nil
    }

Finally, we create a time-limited session with appropriate constraints based on data classification:

    // Step 6: Create session with appropriate constraints
    session, err := ztg.sessionManager.CreateSession(&SessionConfig{
        UserID:           req.Context.UserID,
        DataClassification: req.DataClassification,
        ExpiresAt:        time.Now().Add(ztg.getSessionDuration(req.DataClassification)),
        Permissions:      permissionResult.GrantedPermissions,
        RiskLevel:        riskAssessment.RiskLevel,
    })

    return &AuthorizationResult{
        Authorized:    true,
        SessionToken:  session.Token,
        ExpiresAt:    session.ExpiresAt,
        Permissions:  permissionResult.GrantedPermissions,
    }, nil
}

func (ztg *ZeroTrustGateway) getSessionDuration(classification DataType) time.Duration {
    switch classification {
    case RESTRICTED:
        return 15 * time.Minute // Short sessions for sensitive data
    case CONFIDENTIAL:
        return 1 * time.Hour
    default:
        return 8 * time.Hour
    }
}

This zero-trust implementation considers multiple factors for authorization decisions: user identity, risk assessment, data classification, and contextual factors like location and device.

Audit Logging: Building Compliance-Grade Trails

Compliance regulations require detailed audit trails that can withstand scrutiny from auditors and regulators. Traditional application logs aren’t sufficient—you need structured, tamper-evident, and comprehensive logging.

Solution: Compliance-Grade Audit System

Let’s start with the audit event structure that captures all necessary compliance information:

// pkg/audit/compliance_auditor.go - Audit event structure
type ComplianceAuditor struct {
    loggers        []AuditLogger
    encryptor      *AuditLogEncryption
    digitalSigner  *DigitalSigner
    retention      *RetentionManager
    alerting       *SecurityAlertManager
}

type AuditEvent struct {
    EventID        string                 `json:"event_id"`
    Timestamp      time.Time             `json:"timestamp"`
    EventType      AuditEventType        `json:"event_type"`
    UserID         string                `json:"user_id"`
    SessionID      string                `json:"session_id"`
    ResourceAccessed string              `json:"resource_accessed"`
    DataClassification DataType          `json:"data_classification"`
    Action         string                `json:"action"`
    Result         string                `json:"result"`
    RiskScore      float64              `json:"risk_score"`
    Details        map[string]interface{} `json:"details"`
    Signature      string                `json:"digital_signature"`
}

Now let’s implement the main LLM query logging function with comprehensive audit details:

func (ca *ComplianceAuditor) LogLLMQuery(userID, sessionID string, query *LLMQuery, response *LLMResponse, risk float64) error {
    // Create base audit event
    event := &AuditEvent{
        EventID:        ca.generateEventID(),
        Timestamp:      time.Now().UTC(),
        EventType:      EventLLMQuery,
        UserID:         userID,
        SessionID:      sessionID,
        ResourceAccessed: "LLM_API",
        DataClassification: query.DataClassification,
        Action:         "QUERY_LLM",
        Result:         ca.getResultStatus(response),
        RiskScore:      risk,
        Details: map[string]interface{}{
            "prompt_length":      len(query.Prompt),
            "response_length":    len(response.Text),
            "model_used":         response.ModelName,
            "processing_time_ms": response.ProcessingTime.Milliseconds(),
            "tokens_used":        response.TokensUsed,
            "cost_usd":          response.Cost,
            "sensitive_data_detected": query.ContainsSensitiveData,
            "external_provider": response.ExternalProvider,
        },
    }

For sensitive data, we add additional compliance-specific audit details:

    // For sensitive data, add additional audit details
    if query.DataClassification >= CONFIDENTIAL {
        event.Details["data_patterns_detected"] = query.DetectedPatterns
        event.Details["encryption_used"] = query.EncryptionMethod
        event.Details["compliance_frameworks"] = query.ApplicableCompliance

        // Hash the prompt for audit purposes without storing sensitive data
        event.Details["prompt_hash"] = ca.hashSensitiveContent(query.Prompt)
    }

    // Add geographic and compliance context
    event.Details["data_residency_region"] = ca.getDataResidencyRegion(query)
    event.Details["compliance_violations"] = ca.checkComplianceViolations(query, response)

We then apply digital signatures and encryption for tamper evidence:

    // Digital signature for tamper evidence
    signature, err := ca.digitalSigner.SignEvent(event)
    if err != nil {
        return fmt.Errorf("failed to sign audit event: %w", err)
    }
    event.Signature = signature

    // Encrypt sensitive audit data
    encryptedEvent, err := ca.encryptor.EncryptAuditEvent(event)
    if err != nil {
        return fmt.Errorf("failed to encrypt audit event: %w", err)
    }

    // Log to multiple destinations for redundancy
    var logErrors []error
    for _, logger := range ca.loggers {
        if err := logger.LogEvent(encryptedEvent); err != nil {
            logErrors = append(logErrors, err)
        }
    }

    return nil
}

Here’s the completion of the compliance violation detection logic:

func (ca *ComplianceAuditor) checkComplianceViolations(query *LLMQuery, response *LLMResponse) []string {
    var violations []string

    // HIPAA violation check
    if query.DataClassification == RESTRICTED && query.ContainsHealthData {
        if response.ExternalProvider {
            violations = append(violations, "HIPAA_POTENTIAL_VIOLATION: Health data sent to external provider")
        }
        if query.Region != "us-east-1" && query.Region != "us-west-2" {
            violations = append(violations, "HIPAA_POTENTIAL_VIOLATION: Health data processed outside BAA regions")
        }
    }

    // GDPR violation check
    if query.ContainsPII && !query.UserConsent {
        violations = append(violations, "GDPR_POTENTIAL_VIOLATION: PII processed without explicit consent")
    }

    // PCI DSS violation check
    if query.ContainsPaymentData && !query.PCICompliantEnvironment {
        violations = append(violations, "PCI_POTENTIAL_VIOLATION: Payment data in non-compliant environment")
    }

    return violations
}

Finally, let’s implement suspicious activity detection for potential security threats:

func (ca *ComplianceAuditor) analyzeForSuspiciousActivity(event *AuditEvent) error {
    // Check for unusual access patterns
    recentEvents := ca.getRecentEventsForUser(event.UserID, time.Hour)

    // Detect rapid-fire queries (potential data exfiltration)
    if len(recentEvents) > 100 { // More than 100 queries in an hour
        return fmt.Errorf("potential data exfiltration: user %s made %d queries in 1 hour", event.UserID, len(recentEvents))
    }

    // Detect access to sensitive data from unusual locations
    if event.DataClassification >= CONFIDENTIAL {
        userLocations := ca.getUserHistoricalLocations(event.UserID)
        if !ca.isLocationWithinExpectedRegions(event.IPAddress, userLocations) {
            return fmt.Errorf("potential unauthorized access: user %s accessing sensitive data from unusual location", event.UserID)
        }
    }

    // Detect unusual query patterns
    if event.EventType == EventLLMQuery {
        if promptLength, ok := event.Details["prompt_length"].(int); ok {
            if promptLength > 10000 { // Unusually long prompt
                return fmt.Errorf("potential data dumping: unusually long prompt from user %s", event.UserID)
            }
        }
    }

    return nil
}

This comprehensive audit system creates compliance-grade logs that include not just what happened, but the full context needed for regulatory investigations. The digital signatures ensure tamper evidence, while the multi-destination logging provides redundancy.

Preventing Prompt Injection and LLM-Specific Attacks

Traditional API security doesn’t account for prompt injection, model extraction, or other LLM-specific attack vectors. These attacks can bypass traditional security controls and directly compromise AI behavior.

Solution: LLM-Aware Security Controls

Let’s start with the core security scanner structure that handles multiple types of LLM-specific threats:

// pkg/security/llm_security_scanner.go - Core scanner structure
type LLMSecurityScanner struct {
    injectionDetector    *PromptInjectionDetector
    extractionDetector   *ModelExtractionDetector
    dataLeakDetector     *DataLeakageDetector
    manipulationDetector *ResponseManipulationDetector
    rateLimiter         *PromptRateLimiter
    sanitizer           *PromptSanitizer
}

type SecurityScanResult struct {
    ThreatLevel        ThreatLevel
    DetectedThreats    []DetectedThreat
    RecommendedActions []string
    SanitizedPrompt   string
    ShouldBlock       bool
    Explanation       string
}

Now let’s implement the main prompt scanning logic that checks for multiple threat types:

func (lss *LLMSecurityScanner) ScanPrompt(prompt string, userContext *UserContext) (*SecurityScanResult, error) {
    result := &SecurityScanResult{
        ThreatLevel: ThreatNone,
        DetectedThreats: []DetectedThreat{},
    }

    // 1. Check for prompt injection attempts
    injectionThreats, err := lss.injectionDetector.DetectInjection(prompt, userContext)
    if err != nil {
        return nil, fmt.Errorf("injection detection failed: %w", err)
    }
    result.DetectedThreats = append(result.DetectedThreats, injectionThreats...)

    // 2. Check for model extraction attempts
    extractionThreats, err := lss.extractionDetector.DetectExtraction(prompt, userContext)
    if err != nil {
        return nil, fmt.Errorf("extraction detection failed: %w", err)
    }
    result.DetectedThreats = append(result.DetectedThreats, extractionThreats...)

    // 3. Determine overall threat level and decide on blocking
    result.ThreatLevel = lss.calculateOverallThreatLevel(result.DetectedThreats)
    result.ShouldBlock = lss.shouldBlockRequest(result.ThreatLevel, userContext)

    return result, nil
}

Here’s the prompt injection detection logic that combines pattern matching with ML models:

func (pid *PromptInjectionDetector) DetectInjection(prompt string, userContext *UserContext) ([]DetectedThreat, error) {
    var threats []DetectedThreat

    // Check against known injection patterns
    for _, pattern := range pid.patterns {
        if matches := pattern.Pattern.FindAllStringSubmatch(prompt, -1); len(matches) > 0 {
            threat := DetectedThreat{
                Type:        ThreatPromptInjection,
                Severity:    pattern.Severity,
                Description: pattern.Description,
                Evidence:    matches[0][0], // First match as evidence
                Confidence:  0.8, // Pattern-based detection confidence
                Mitigation:  pattern.Mitigation,
            }
            threats = append(threats, threat)
        }
    }

    // Use ML model for more sophisticated detection
    mlPrediction, confidence, err := pid.mlModel.PredictInjection(prompt, userContext)
    if err != nil {
        return threats, fmt.Errorf("ML injection detection failed: %w", err)
    }

    if mlPrediction && confidence > 0.7 {
        threat := DetectedThreat{
            Type:        ThreatPromptInjection,
            Severity:    pid.getSeverityFromConfidence(confidence),
            Description: "ML-detected potential prompt injection",
            Confidence:  confidence,
            Mitigation:  "Review prompt for injection patterns and sanitize",
        }
        threats = append(threats, threat)
    }

    return threats, nil
}

Let’s implement specific detection for role manipulation attempts:

func (pid *PromptInjectionDetector) detectRoleManipulation(prompt string) bool {
    rolePatterns := []string{
        `(?i)ignore\s+previous\s+instructions`,
        `(?i)you\s+are\s+now\s+(a|an)\s+`,
        `(?i)forget\s+everything\s+above`,
        `(?i)system\s*:\s*`,
        `(?i)assistant\s*:\s*`,
        `(?i)override\s+your\s+instructions`,
        `(?i)act\s+as\s+if\s+you\s+are`,
    }

    for _, pattern := range rolePatterns {
        if matched, _ := regexp.MatchString(pattern, prompt); matched {
            return true
        }
    }
    return false
}

And here’s the jailbreaking detection that identifies attempts to bypass AI safety controls:

func (pid *PromptInjectionDetector) detectJailbreaking(prompt string) bool {
    jailbreakPatterns := []string{
        `(?i)DAN\s+mode`,  // "Do Anything Now" jailbreak
        `(?i)developer\s+mode`,
        `(?i)simulation\s+mode`,
        `(?i)hypothetically`,
        `(?i)for\s+educational\s+purposes\s+only`,
        `(?i)this\s+is\s+just\s+a\s+test`,
        `(?i)pretend\s+that`,
    }

    for _, pattern := range jailbreakPatterns {
        if matched, _ := regexp.MatchString(pattern, prompt); matched {
            return true
        }
    }
    return false
}

Finally, the blocking decision logic that considers user context and data sensitivity:

func (lss *LLMSecurityScanner) shouldBlockRequest(threatLevel ThreatLevel, userContext *UserContext) bool {
    // Always block critical threats
    if threatLevel >= ThreatCritical {
        return true
    }

    // Block high threats for users accessing sensitive data
    if threatLevel >= ThreatHigh && userContext.DataClassification >= CONFIDENTIAL {
        return true
    }

    // Check user's security clearance and trust level
    if userContext.SecurityClearance < SecurityClearanceHigh && threatLevel >= ThreatMedium {
        return true
    }

    // Consider recent user behavior
    if userContext.RecentSuspiciousActivity && threatLevel >= ThreatLow {
        return true
    }

    return false
}

This LLM-aware security system addresses attack vectors specific to AI systems. The multi-layered detection approach combines pattern matching, machine learning, and heuristic analysis to identify sophisticated attacks that traditional security tools would miss.

Data Residency and Cross-Border Compliance

Many organizations discover too late that their LLM implementation violates data residency requirements by processing European citizen data in US data centers or sending healthcare data to regions without appropriate compliance certifications.

Solution: Intelligent Data Routing Based on Compliance Requirements

Let’s start with the infrastructure setup for compliance-aware routing:

// infrastructure/compliance-routing.ts - Basic routing infrastructure
export class ComplianceRoutingStack extends Stack {
  constructor(scope: Construct, id: string, props: ComplianceRoutingProps) {
    super(scope, id, props);

    // Create region-specific deployments based on compliance requirements
    const complianceRegions = this.createComplianceRegions(props.complianceRequirements);

    // API Gateway with intelligent routing
    const api = new RestApi(this, 'ComplianceAwareAPI', {
      restApiName: 'LLM Compliance API',
      description: 'LLM API with data residency compliance',
      endpointConfiguration: {
        types: [EndpointType.REGIONAL],
      },
      policy: this.createRegionalAccessPolicy(),
    });

Next, we set up the routing Lambda function with compliance region configuration:

// Lambda function for routing decisions
const routingFunction = new Function(this, "ComplianceRoutingFunction", {
  runtime: Runtime.PROVIDED_AL2,
  handler: "bootstrap",
  code: Code.fromAsset("dist/routing-lambda.zip"),
  environment: {
    COMPLIANCE_REGIONS: JSON.stringify(complianceRegions),
    GDPR_REGIONS: "eu-west-1,eu-central-1",
    HIPAA_REGIONS: "us-east-1,us-west-2",
    CCPA_REGIONS: "us-west-1,us-west-2",
  },
  timeout: Duration.seconds(30),
});

Now let’s implement the compliance router logic that determines appropriate regions based on data classification:

// pkg/routing/compliance_router.go - Core routing logic
type ComplianceRouter struct {
    regionConfigs map[string]RegionConfig
    geolocator   *GeoLocationService
    classifier   *DataClassifier
    auditor      *ComplianceAuditor
}

type RegionConfig struct {
    Region            string
    ComplianceFrameworks []string
    SupportedDataTypes   []DataType
    Certifications      []string
    EndpointURL         string
    BackupRegions       []string
}

Here’s the main routing decision logic that considers geography, data classification, and compliance frameworks:

func (cr *ComplianceRouter) RouteRequest(req *LLMRequest) (*RoutingDecision, error) {
    // 1. Determine user's geographic location
    userLocation, err := cr.geolocator.GetLocation(req.ClientIP)
    if err != nil {
        return nil, fmt.Errorf("geolocation failed: %w", err)
    }

    // 2. Classify the data in the request
    classification, err := cr.classifier.ClassifyContent(req.Prompt, req.UserContext)
    if err != nil {
        return nil, fmt.Errorf("data classification failed: %w", err)
    }

    // 3. Determine applicable compliance frameworks
    applicableFrameworks := cr.getApplicableFrameworks(userLocation, classification)

    // 4. Find compliant regions
    eligibleRegions := cr.findCompliantRegions(applicableFrameworks, classification.Classification)
    if len(eligibleRegions) == 0 {
        return nil, fmt.Errorf("no compliant regions found for request from %s with data classification %v",
            userLocation.Country, classification.Classification)
    }

    // 5. Select optimal region based on latency and load
    selectedRegion, err := cr.selectOptimalRegion(eligibleRegions, userLocation)
    if err != nil {
        return nil, fmt.Errorf("region selection failed: %w", err)
    }

    return &RoutingDecision{
        TargetRegion:        selectedRegion.Region,
        EndpointURL:         selectedRegion.EndpointURL,
        BackupEndpoints:     selectedRegion.BackupRegions,
        ComplianceFrameworks: applicableFrameworks,
    }, nil
}

Let’s implement the compliance framework detection based on user location and data types:

func (cr *ComplianceRouter) getApplicableFrameworks(location *GeoLocation, classification *ClassificationResult) []string {
    var frameworks []string

    // GDPR applies to EU residents and their data
    if cr.isEULocation(location) || classification.ContainsPII {
        frameworks = append(frameworks, "GDPR")
    }

    // HIPAA applies to US healthcare data
    if location.Country == "US" && classification.ContainsHealthData {
        frameworks = append(frameworks, "HIPAA")
    }

    // CCPA applies to California residents
    if location.Country == "US" && location.Region == "CA" {
        frameworks = append(frameworks, "CCPA")
    }

    // PCI DSS applies to payment data regardless of location
    if classification.ContainsPaymentData {
        frameworks = append(frameworks, "PCI_DSS")
    }

    return frameworks
}

Finally, let’s implement the routing validation to ensure legal compliance:

func (cr *ComplianceRouter) validateRoutingDecision(req *LLMRequest, region RegionConfig, frameworks []string) error {
    // Validate that the selected region can legally process this request
    for _, framework := range frameworks {
        if !cr.regionSupportsFramework(region, framework) {
            return fmt.Errorf("region %s does not support required framework %s", region.Region, framework)
        }
    }

    // Check for data sovereignty violations
    if req.UserContext.DataSovereigntyRequirements != nil {
        allowedRegions := req.UserContext.DataSovereigntyRequirements.AllowedRegions
        if len(allowedRegions) > 0 && !contains(allowedRegions, region.Region) {
            return fmt.Errorf("region %s not in allowed regions for user %s",
                region.Region, req.UserContext.UserID)
        }
    }

    // Validate data export restrictions
    if req.UserContext.DataExportRestrictions != nil {
        if req.UserContext.DataExportRestrictions.ProhibitCrossBorder &&
           !cr.isSameJurisdiction(req.UserLocation, region.Region) {
            return fmt.Errorf("cross-border data transfer prohibited for user %s",
                req.UserContext.UserID)
        }
    }

    return nil
}

This intelligent routing system ensures that data never crosses inappropriate boundaries and automatically selects the most appropriate processing region based on real-time compliance analysis.

Incident Response and Breach Management

When security incidents occur in LLM systems, traditional incident response procedures are often inadequate. LLM breaches can involve model extraction, training data exposure, or prompt injection that causes inappropriate responses—all requiring specialized response procedures.

Solution: LLM-Specific Incident Response Framework

Let’s start with the incident response structure and types specific to LLM systems:

// pkg/incident/llm_incident_response.go - Core incident response structure
type LLMIncidentResponse struct {
    detector        *IncidentDetector
    classifier      *IncidentClassifier
    containment     *ContainmentManager
    forensics       *LLMForensics
    notification    *NotificationManager
    recovery        *RecoveryManager
    compliance      *ComplianceReporting
}

type IncidentType string
const (
    IncidentDataBreach        IncidentType = "DATA_BREACH"
    IncidentPromptInjection   IncidentType = "PROMPT_INJECTION"
    IncidentModelExtraction   IncidentType = "MODEL_EXTRACTION"
    IncidentUnauthorizedAccess IncidentType = "UNAUTHORIZED_ACCESS"
    IncidentDataLeakage       IncidentType = "DATA_LEAKAGE"
    IncidentComplianceViolation IncidentType = "COMPLIANCE_VIOLATION"
    IncidentServiceCompromise IncidentType = "SERVICE_COMPROMISE"
)

Now let’s implement the main incident handling workflow:

func (lir *LLMIncidentResponse) HandleIncident(alert *SecurityAlert) error {
    // 1. Create incident record
    incident, err := lir.createIncidentRecord(alert)
    if err != nil {
        return fmt.Errorf("failed to create incident record: %w", err)
    }

    // 2. Classify the incident and determine severity
    classification, err := lir.classifier.ClassifyIncident(incident)
    if err != nil {
        return fmt.Errorf("failed to classify incident: %w", err)
    }
    incident.Type = classification.Type
    incident.Severity = classification.Severity
    incident.ComplianceImpact = classification.ComplianceImpact

    // 3. Immediate containment actions
    if err := lir.performImmediateContainment(incident); err != nil {
        return fmt.Errorf("immediate containment failed: %w", err)
    }

    // 4. Assess impact and determine notification requirements
    impactAssessment, err := lir.assessImpact(incident)
    if err != nil {
        return fmt.Errorf("impact assessment failed: %w", err)
    }

    return nil
}

Let’s implement immediate containment actions specific to different LLM incident types:

func (lir *LLMIncidentResponse) performImmediateContainment(incident *LLMIncident) error {
    var actions []ContainmentAction

    switch incident.Type {
    case IncidentPromptInjection:
        // Immediately block the attacking user and similar patterns
        actions = append(actions, ContainmentAction{
            Type:        "BLOCK_USER",
            Description: "Block user account pending investigation",
            Target:      incident.AffectedSystems,
        })

        // Update prompt filtering rules
        actions = append(actions, ContainmentAction{
            Type:        "UPDATE_FILTERS",
            Description: "Deploy enhanced prompt injection filters",
            Target:      []string{"prompt_filter_service"},
        })

    case IncidentDataBreach:
        // Isolate affected systems
        actions = append(actions, ContainmentAction{
            Type:        "ISOLATE_SYSTEMS",
            Description: "Isolate compromised systems from network",
            Target:      incident.AffectedSystems,
        })

        // Rotate all potentially compromised credentials
        actions = append(actions, ContainmentAction{
            Type:        "ROTATE_CREDENTIALS",
            Description: "Emergency rotation of all system credentials",
            Target:      []string{"all_systems"},
        })

For model extraction incidents, we implement specific rate limiting and pattern blocking:

    case IncidentModelExtraction:
        // Implement rate limiting to prevent further extraction
        actions = append(actions, ContainmentAction{
            Type:        "EMERGENCY_RATE_LIMIT",
            Description: "Implement emergency rate limiting",
            Target:      []string{"llm_api"},
        })

        // Block suspicious query patterns
        actions = append(actions, ContainmentAction{
            Type:        "BLOCK_PATTERNS",
            Description: "Block query patterns indicative of model extraction",
            Target:      []string{"api_gateway"},
        })
    }

    // Execute containment actions
    for _, action := range actions {
        if err := lir.containment.ExecuteAction(action); err != nil {
            return fmt.Errorf("containment action %s failed: %w", action.Type, err)
        }
        incident.ContainmentActions = append(incident.ContainmentActions, action)
    }

    return nil
}

Here’s the notification requirement determination based on regulatory compliance:

func (lir *LLMIncidentResponse) determineNotificationRequirements(assessment *ImpactAssessment) []NotificationRequirement {
    var requirements []NotificationRequirement

    // GDPR notification requirements (72 hours for authority, immediate for high-risk to individuals)
    if assessment.ComplianceImpact.GDPRBreach {
        requirements = append(requirements, NotificationRequirement{
            Recipient:    "data_protection_authority",
            Timeline:     72 * time.Hour,
            Required:     true,
            Content:      "GDPR breach notification to supervisory authority",
        })

        if assessment.DataImpact.HighRiskToIndividuals {
            requirements = append(requirements, NotificationRequirement{
                Recipient: "affected_individuals",
                Timeline:  time.Hour, // As soon as reasonably possible
                Required:  true,
                Content:   "GDPR high-risk breach notification to data subjects",
            })
        }
    }

    // HIPAA breach notification (60 days to OCR, 60 days to individuals)
    if assessment.ComplianceImpact.HIPAABreach {
        requirements = append(requirements, NotificationRequirement{
            Recipient: "hhs_ocr",
            Timeline:  60 * 24 * time.Hour,
            Required:  true,
            Content:   "HIPAA breach notification to Office for Civil Rights",
        })
    }

    return requirements
}

This specialized incident response framework addresses the unique challenges of LLM security incidents. Unlike traditional breaches that might involve stolen files, LLM incidents can involve model behavior modification, prompt injection attacks, or subtle data leakage that’s difficult to detect and quantify.

Conclusion: Security as a Foundation, Not an Afterthought

Securing LLM APIs that handle sensitive data is fundamentally different from traditional API security. The challenges extend far beyond network security and authentication—they encompass AI-specific attack vectors, complex regulatory compliance, and the inherent tension between AI functionality and data protection.

The solutions we’ve explored address the full spectrum of LLM security challenges:

The fundamental insight is that LLM security requires a security-first approach to architecture. Organizations that try to “add security later” discover that their design decisions have created insurmountable compliance and security challenges. Data flows, model interactions, and external dependencies must all be designed with security and compliance as primary constraints.

Recent regulatory developments have only increased the stakes. The EU AI Act, emerging state privacy laws, and sector-specific regulations like HIPAA and PCI DSS are all adapting to address AI-specific risks. Organizations that build comprehensive security frameworks now will be well-positioned for these evolving requirements.

Perhaps most importantly, security in LLM systems isn’t just about preventing breaches—it’s about maintaining trust. When users share sensitive information with AI systems, they’re placing extraordinary trust in the organization’s ability to protect that data. A single security incident can destroy years of trust-building and permanently damage an organization’s reputation.

Building truly secure LLM APIs requires expertise that spans traditional cybersecurity, AI/ML engineering, regulatory compliance, and incident response. The technical complexity is matched by operational complexity—monitoring systems need to understand AI behavior patterns, incident response teams need to understand model extraction attacks, and compliance teams need to understand the implications of sending data to external LLM providers.


Building production-ready LLM systems requires navigating dozens of architectural decisions, each with far-reaching implications. At Yantratmika Solutions, we’ve helped organizations avoid the common pitfalls and build systems that scale. The devil, as always, is in the implementation details.