Security & Performance
When to Use
Use this when you need to secure image derivatives against attacks and optimize image delivery performance.
Security Concerns
1. Image Derivative Token (itok)
Threat: Unauthenticated users could generate unlimited derivatives by crafting URLs, causing denial-of-service through storage/CPU exhaustion.
Protection: Drupal adds ?itok=HASH token to derivative URLs. Server validates token before generating derivative.
Config:
// sites/default/settings.php
// Default: Require token (SECURE)
$config['image.settings']['allow_insecure_derivatives'] = FALSE;
// Allow insecure (ONLY if CDN handles security)
$config['image.settings']['allow_insecure_derivatives'] = TRUE;
// Suppress token from URLs (only with allow_insecure OR CDN)
$config['image.settings']['suppress_itok_output'] = TRUE;
Decision:
| If you have... | Set... | Why |
|---|---|---|
| Standard Drupal, no CDN | allow_insecure_derivatives: FALSE |
Prevents DoS via arbitrary derivative requests |
| CDN with signed URLs | allow_insecure_derivatives: TRUE, suppress_itok_output: TRUE |
CDN handles security, cleaner URLs |
| CDN without security | Keep secure (FALSE) | Drupal token is only protection |
Attack scenario prevented:
# Without token protection
GET /styles/custom_9999x9999/public/image.jpg
→ Server generates 9999x9999 derivative
→ Attacker loops, fills disk, crashes server
# With token protection
GET /styles/custom_9999x9999/public/image.jpg
→ 403 Forbidden (missing valid token)
2. Directory Traversal
Threat: Crafted URIs could access files outside public files directory.
Protection: Drupal validates stream wrapper scheme and normalizes paths before derivative generation.
Example attack prevented:
// Malicious attempt
$uri = 'public://../../private/secret.pdf';
$style->buildUri($uri);
// Drupal normalizes to
// public://private/secret.pdf (within public)
// OR rejects entirely
3. Source Image Validation
Threat: Uploaded files could contain malicious code disguised as images.
Protection: - File module validates MIME type - Image toolkit validates image structure - GD/ImageMagick reject non-image files
Best practice:
// In custom upload handling
$image = \Drupal::service('image.factory')->get($uri);
if (!$image->isValid()) {
// Reject file, not a real image
}
Performance Optimization
1. Derivative Generation Strategy
Lazy generation (default): - Derivatives created on first request - Pro: No wasted processing for unused images - Con: First user experiences delay (200-500ms)
Pre-generation (optional):
// In cron or post-save hook
$image_style = ImageStyle::load('large');
$image_style->createDerivative($source_uri, $derivative_uri);
Recommendation: Use lazy for most cases. Pre-generate for critical above-fold images only.
2. CDN Integration
Without CDN:
Browser → Drupal → Generate derivative → Serve file
With CDN:
Browser → CDN (cache hit) → Return file
Browser → CDN (cache miss) → Drupal → Generate → CDN caches → Return file
Config for CDN:
// settings.php
$config['image.settings']['allow_insecure_derivatives'] = TRUE;
$config['image.settings']['suppress_itok_output'] = TRUE;
// Ensure CDN is configured in settings
$settings['file_public_base_url'] = 'https://cdn.example.com';
3. Storage Management
Derivatives location:
public://styles/{style_name}/{scheme}/{original_path}
Flush strategies:
| When to flush... | Command | Impact |
|---|---|---|
| After style config change | drush image:flush {style} |
Regenerates only that style |
| After bulk image updates | drush image:flush --all |
Regenerates all, may take hours |
| After disk full | drush image:flush --all then drush cr |
Cleans space, regenerates on demand |
Storage estimate:
Average source image: 2 MB
Image styles per image: 5
Responsive multipliers: 2
Total derivatives: 10
Storage per image = 2 MB × 0.3 (WebP compression) × 10 = 6 MB
For 1000 images = 6 GB derivatives storage
4. Caching
Render cache:
// Image render element includes cache metadata
$image = [
'#theme' => 'image_style',
'#style_name' => 'large',
'#uri' => $uri,
'#cache' => [
'tags' => ['image_style:large'],
'contexts' => ['url.site'],
],
];
Clear cache on style change:
# Automatic on style save
drush cr
# Manual if needed
drush cache:rebuild
5. Batch Processing
For large derivative generation:
// In batch operation
$batch = [
'operations' => [
['my_module_generate_derivatives', [$image_uri]],
],
];
batch_set($batch);
function my_module_generate_derivatives($uri, &$context) {
$styles = ImageStyle::loadMultiple();
foreach ($styles as $style) {
$derivative_uri = $style->buildUri($uri);
$style->createDerivative($uri, $derivative_uri);
}
}
Prevents timeout/memory errors on large imagesets.
Monitoring
Derivative storage size:
du -sh sites/default/files/styles/
Derivative count:
find sites/default/files/styles/ -type f | wc -l
Failed derivatives (check logs):
drush watchdog:show --type=image
Common Mistakes
- Disabling token protection without CDN security → DoS vulnerability, arbitrary derivative generation
- Not flushing after style changes → Serving outdated derivatives, user confusion
- Pre-generating all derivatives → Wastes storage and processing, most unused
- Allowing insecure derivatives on public internet → Attacker can exhaust disk/CPU
- Not monitoring derivative storage → Disk fills silently, site crashes
- Using token URLs with aggressive CDN caching → Cache miss on every token change, poor hit rate
See Also
- WebP & AVIF Optimization
- Best Practices & Patterns
- Reference: core/modules/image/src/Entity/ImageStyle.php (getPathToken method)
- Reference: https://www.drupal.org/docs/security-in-drupal