ahmed-bhs/doctrine-doctor
Doctrine Doctor is a runtime analysis tool for Doctrine ORM integrated into the Symfony Web Profiler. It detects real-world issues like N+1 queries, slow queries, missing indexes, hydration overhead, and injection risks, with actionable backtraces and suggestions.
This document describes the security features implemented in the PHP template rendering system to prevent XSS (Cross-Site Scripting) and other injection attacks.
The SafeContext class provides automatic HTML escaping for all template variables, similar to Twig's auto-escaping feature.
Security Benefits:
Basic Usage (Auto-Escaped):
// Template context:
// ['username' => '<script>alert("XSS")</script>']
// SAFE - Auto-escaped
echo $context->username;
// Output: <script>alert("XSS")</script>
// SAFE - Array access also auto-escapes
echo $context['username'];
// Output: <script>alert("XSS")</script>
Raw Output (Intentional Unescaped):
// UNSAFE - Use only for pre-sanitized content
echo $context->raw('formatted_sql');
// Example: SQL syntax highlighting is already sanitized
echo $context->raw('highlighted_code');
Conditional Checks:
// Check if variable exists
if ($context->has('suggestion')) {
echo $context->suggestion;
}
// Get all available keys
$keys = $context->keys();
Arrays are recursively escaped:
// Context: ['items' => ['<script>', 'safe', '<img>']]
foreach ($context->items as $item) {
echo $item; // Each item is auto-escaped
}
// Output:
// <script>
// safe
// <img>
Non-string types are preserved:
// Context:
// [
// 'count' => 42,
// 'price' => 19.99,
// 'active' => true,
// 'data' => null,
// ]
echo $context->count; // 42 (int)
echo $context->price; // 19.99 (float)
echo $context->active; // true (bool)
echo $context->data; // null
For advanced use cases, the escapeContext() helper provides context-specific escaping.
| Context | Use Case | Example |
|---|---|---|
html |
HTML content (default) | <div><?php echo escapeContext($text, 'html'); ?></div> |
attr |
HTML attributes | <div class="<?php echo escapeContext($class, 'attr'); ?>"> |
js |
JavaScript strings | var name = <?php echo escapeContext($name, 'js'); ?>; |
css |
CSS identifiers | .<?php echo escapeContext($class, 'css'); ?> { } |
url |
URL parameters | ?param=<?php echo escapeContext($value, 'url'); ?> |
HTML Context (Default):
<p><?php echo escapeContext($userInput, 'html'); ?></p>
Attribute Context:
<div class="user-<?php echo escapeContext($userId, 'attr'); ?>">
<a href="/profile/<?php echo escapeContext($username, 'url'); ?>">
Profile
</a>
</div>
JavaScript Context:
<script>
var config = {
username: <?php echo escapeContext($username, 'js'); ?>,
message: <?php echo escapeContext($message, 'js'); ?>
};
</script>
CSS Context:
<style>
.severity-<?php echo escapeContext($severity, 'css'); ?> {
color: red;
}
</style>
GOOD:
// Auto-escaped by default
<h3><?php echo $context->title; ?></h3>
<p><?php echo $context->description; ?></p>
📢 BAD:
// Manual extraction bypasses auto-escaping
extract($context);
<h3><?php echo $title; ?></h3> <!-- NOT ESCAPED! -->
Only use raw() for:
GOOD - Pre-sanitized:
// formatSqlWithHighlight() already escapes and adds HTML
<div class="query-item">
<?php echo $context->raw('formatted_sql'); ?>
</div>
📢 BAD - User input:
// NEVER use raw() on user input
<div>
<?php echo $context->raw('user_comment'); ?> <!-- XSS VULNERABILITY! -->
</div>
For existing templates using extract(), variables are still available but NOT auto-escaped:
// Old style (still works but NOT auto-escaped)
extract($context);
echo htmlspecialchars($username, ENT_QUOTES, 'UTF-8'); // Manual escape required
// New style (auto-escaped)
echo $context->username; // Safe by default
Migration Recommendation: Update templates to use $context-> for new code.
Before:
<?php
// Old template using extract()
extract($context);
$e = fn(string $str): string => htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
?>
<div class="alert">
<strong><?php echo $e($title); ?></strong>
<p><?php echo $e($message); ?></p>
</div>
After:
<?php
// New template using SafeContext
// No need for manual escaping function
?>
<div class="alert">
<strong><?php echo $context->title; ?></strong>
<p><?php echo $context->message; ?></p>
</div>
$e($variable) with $context->variablehtmlspecialchars() calls$context->raw() for pre-sanitized contentraw() usage with security teamTemplates can use both styles during migration:
// Safe: Both work together
echo $context->username; // Auto-escaped
echo htmlspecialchars($oldVar, ENT_QUOTES, 'UTF-8'); // Manual escape
Problem:
// 📢 BAD - Double-escaped
echo htmlspecialchars($context->username, ENT_QUOTES, 'UTF-8');
// Output: &lt;script&gt; (double-encoded)
Solution:
// GOOD - Use raw() to get unescaped value, then escape once
echo htmlspecialchars($context->raw('username'), ENT_QUOTES, 'UTF-8');
// BETTER - Just use auto-escaping
echo $context->username;
Problem:
// 📢 VULNERABLE - Attribute injection
<div class="user-<?php echo $context->raw('userId'); ?>">
Solution:
// SAFE - Auto-escaped
<div class="user-<?php echo $context->userId; ?>">
// SAFE - Context-aware escaping
<div class="user-<?php echo escapeContext($context->raw('userId'), 'attr'); ?>">
Problem:
// 📢 WRONG - HTML escaping in JavaScript breaks syntax
<script>
var name = "<?php echo $context->name; ?>";
// Output: var name = "<script>"; (breaks JS)
</script>
Solution:
// CORRECT - Use JS context escaping
<script>
var name = <?php echo escapeContext($context->raw('name'), 'js'); ?>;
// Output: var name = "\u003Cscript\u003E"; (safe and valid JS)
</script>
Problem:
// 📢 DANGEROUS ASSUMPTION
// "Admin users are trusted, so we can use raw()"
if ($user->isAdmin()) {
echo $context->raw('comment'); // STILL VULNERABLE!
}
Solution:
// PRINCIPLE: Never trust ANY user input
echo $context->comment; // Always escape, regardless of user role
Attack:
Input: <script>alert('XSS')</script>
Defense:
echo $context->input;
// Output: <script>alert('XSS')</script>
// Browser displays: <script>alert('XSS')</script> (as text, not executed)
Attack:
Input: " onclick="alert('XSS')
Defense:
<button class="<?php echo $context->input; ?>">Click</button>
// Output: <button class="" onclick="alert('XSS')">Click</button>
// Quote is escaped, attribute injection prevented
Attack:
Input: javascript:alert('XSS')
Defense:
<a href="<?php echo escapeContext($context->raw('url'), 'url'); ?>">Link</a>
// Output: <a href="javascript%3Aalert%28%27XSS%27%29">Link</a>
// URL is encoded, script execution prevented
All security features are covered by unit tests in tests/Unit/Template/Security/SafeContextTest.php.
Run security tests:
vendor/bin/phpunit --testsuite unit --filter SafeContext
Test XSS Prevention:
// Create test context with XSS payloads
$context = new SafeContext([
'test1' => '<script>alert(1)</script>',
'test2' => '<img src=x onerror=alert(1)>',
'test3' => 'javascript:alert(1)',
]);
// Verify all are escaped
var_dump($context->test1); // Should NOT contain executable script
var_dump($context->test2); // Should NOT contain onerror handler
var_dump($context->test3); // Should NOT contain javascript: protocol
[← Back to Main Documentation]({{ site.baseurl }}/) | Architecture →
How can I help you explore Laravel packages today?