Exposure of Sensitive Information to an Unauthorized Actor
The product exposes sensitive information to an actor that is not explicitly authorized to have access to that information.
There are many different kinds of mistakes that introduce information exposures. The severity of the error can range widely, depending on the context in which the product operates, the type of sensitive information that is revealed, and the benefits it may provide to an attacker.
How to fix this vulnerability
Prevention strategies for Information Exposure based on 14 Shoulder detection rules.
Use environment variables for configuration only; never log or return their values
package main import ( "log" "os" ) func main() { apiKey := os.Getenv("API_KEY") - log.Printf("API Key: %s", apiKey) + if apiKey == "" { + log.Fatal("API_KEY not configured") + } + log.Println("API key configured:", len(apiKey) > 0) + // Use apiKey internally, never log or return it }
Store API keys in environment variables, never log them, and protect model endpoints with authentication
- client := openai.NewClient("sk-proj-1234567890abcdefghijklmnop") - log.Printf("Using key: %s", apiKey) - http.Handle("/models/", http.FileServer(http.Dir("./models"))) + client := openai.NewClient(os.Getenv("OPENAI_API_KEY")) + log.Printf("Request completed: model=%s tokens=%d", model, usage.TotalTokens) + + modelsHandler := http.FileServer(http.Dir("./models")) + http.Handle("/models/", authMiddleware(rateLimiter(modelsHandler)))
Mask PII and redact credentials before sending data to LLM APIs, and use structured logging
- resp, _ := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ - Messages: []openai.ChatCompletionMessage{{ - Content: fmt.Sprintf("User SSN: %s, Password: %s", user.SSN, password), - }}, - }) - log.Printf("Request: %v", messages) + safeMessage := maskPII(userInput) + safeMessage = redactCredentials(safeMessage) + + resp, _ := client.CreateChatCompletion(ctx, openai.ChatCompletionRequest{ + Messages: []openai.ChatCompletionMessage{{Content: safeMessage}}, + }) + log.Printf("Completed: model=%s tokens=%d", model, resp.Usage.TotalTokens)
Use secrets internally without exposing them in logs, responses, or client-side code
- app.get('/config', (req, res) => { - res.json({ apiKey: process.env.API_KEY }); - }); + const apiKey = process.env.API_KEY; + // Use the key server-side only + const result = await externalApi.call({ key: apiKey }); + res.json({ data: result });
Load API keys from environment variables and proxy LLM calls through your server
const openai = new OpenAI({ - apiKey: 'sk-proj-1234567890abcdefghijklmnop' + apiKey: process.env.OPENAI_API_KEY });
Mask or redact PII and credentials before sending data to LLM APIs
- const response = await openai.chat.completions.create({ - messages: [{ role: 'user', content: `Process: ${userRecord}` }] + const masked = maskPII(userRecord); + const response = await openai.chat.completions.create({ + messages: [{ role: 'user', content: `Process: ${masked}` }] });
Return generic error messages to users; log detailed errors server-side only
- from flask import jsonify - - @app.route('/api/data') - def handler(): - try: - return jsonify(process()) - except Exception as e: - return jsonify({'error': str(e), 'trace': traceback.format_exc()}), 500 + import logging + from flask import jsonify + + logger = logging.getLogger(__name__) + + @app.route('/api/data') + def handler(): + try: + return jsonify(process()) + except Exception as e: + logger.error(f"Error: {e}", exc_info=True) + return jsonify({'error': 'Internal server error'}), 500
Use Presidio or similar libraries to anonymize PII before sending data to LLM APIs
- messages = [{ - 'role': 'user', - 'content': f"User SSN: {user.ssn}, email: {user.email}. Summarize profile." - }] - logging.info(f"Request: {messages}") - response = openai.chat.completions.create(model='gpt-4', messages=messages) + from presidio_analyzer import AnalyzerEngine + from presidio_anonymizer import AnonymizerEngine + + analyzer = AnalyzerEngine() + anonymizer = AnonymizerEngine() + + def anonymize_text(text: str) -> str: + results = analyzer.analyze(text=text, language='en') + return anonymizer.anonymize(text=text, analyzer_results=results).text + + safe_message = anonymize_text(user_message) + messages = [{'role': 'user', 'content': safe_message}] + response = openai.chat.completions.create(model='gpt-4', messages=messages) + logger.info('Completed', extra={'model': 'gpt-4', 'tokens': response.usage.total_tokens})
Use explicit field selection or serializer schemas to exclude sensitive fields from responses
from flask import jsonify from models import User @app.route('/api/users') def get_users(): users = User.query.all() - return jsonify([u.__dict__ for u in users]) + return jsonify([{ + 'id': u.id, + 'email': u.email, + 'name': u.name + } for u in users])
Find vulnerabilities in your code
Use Shoulder to scan your codebase for Exposure of Sensitive Information to an Unauthorized Actor patterns. 14 rules.
# Scan with Shoulder CLI npx @shoulderdev/cli trust --cwe=200 # Or scan entire project npx @shoulderdev/cli trust .
Detection Rules (14)
What to watch for in code reviews
These patterns indicate potential Exposure of Sensitive Information to an Unauthorized Actor vulnerabilities. Look for these during code reviews and security audits.
Scan your codebase for Exposure of Sensitive Information to an Unauthorized Actor
Shoulder CLI finds vulnerable patterns across your entire codebase.