Install
$ agentstack add skill-trebormc-drupal-ai-agents-twig-audit ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Twig Audit
Principle: Twig is for presentation ONLY. Preprocess handles logic.
Audit Workflow
- Run the detection greps below on the theme/module templates.
- Read EVERY flagged template in full (grep hits need context to confirm).
- Classify each confirmed hit against the anti-pattern list (severity: drilling/raw = CRITICAL, logic/isolation = HIGH, attributes = MEDIUM).
- Produce the report using the Audit Report Format at the end.
Detection Commands
Replace TPL with the templates directory, e.g. $DDEV_DOCROOT/themes/custom/*/templates/:
# Anti-pattern 1: render array drilling — matches content.field_x[0]['#markup'] and ['#items']
grep -rn "\[.['\"]#" TPL ; grep -rn "\['#" TPL
# Anti-pattern 2: business logic — comparisons/calculations on .value inside if/set
grep -rnE "\{% *(if|set) .*\.value" TPL
# Anti-pattern 3: raw filter usage — every hit must be justified
grep -rn "|raw" TPL
# Anti-pattern 4: non-isolated includes — check each hit for "with_context = false" or "only"
grep -rn "include(" TPL ; grep -rn "{% embed" TPL
# Anti-pattern 6: direct entity access bypassing the render system
grep -rn "\.entity\." TPL
# Anti-pattern 7: attributes built as strings
grep -rnE "class=\"[^\"]*\{\{" TPL
A grep hit is a CANDIDATE, not a confirmed issue — always read the template before reporting.
Anti-Patterns to Detect
1. Render Array Drilling (CRITICAL — Breaks Cache)
{# BAD — loses cache metadata, security risk #}
{{ content.field_image[0]['#markup'] }}
{{ content.field_body['#items'][0]['value'] }}
{# GOOD #}
{{ content.field_image }}
{{ content.body }}
2. Business Logic in Templates
{# BAD #}
{% if user.field_role.value == 'premium' and user.field_expiration.value|date('U') > 'now'|date('U') %}
{% set discount = product.price.value * 0.2 %}
{# GOOD — variable from preprocess #}
{% if has_premium_discount %}
{{ formatted_discounted_price }}
3. Unsafe |raw Usage
{# BAD — XSS vulnerability #}
{{ node.field_user_content.value|raw }}
{# GOOD — use processed field #}
{{ content.field_user_content }}
4. Missing Component Isolation
{# BAD — variable contamination #}
{{ include('mytheme:card', {heading: title}) }}
{# GOOD #}
{{ include('mytheme:card', {heading: title}, with_context = false) }}
{% embed 'mytheme:card' with {heading: title} only %}
Use include (+ with_context = false) when only passing simple values; use embed (+ only) when the component has blocks/slots to fill. Both isolate the component from the parent template's variables.
5. Content Not Rendered (Loses Cache Metadata)
{# BAD — cache metadata lost #}
{{ label }}
{{ content.body }}
{# GOOD — render remaining with without #}
{{ label }}
{{ content.body }}
{{ content|without('body') }}
6. Direct Entity Access (Bypasses Render System)
{# BAD — bypasses formatters and cache #}
{{ file_url(node.field_image.entity.uri.value) }}
{# GOOD — render the field properly #}
{{ content.field_image }}
{# Or if you need just the URL, use preprocess #}
{{ image_url }}
7. Attributes as Strings
{# BAD #}
{# GOOD #}
{% set classes = ['node', 'node--' ~ node.bundle|clean_class] %}
Component Props vs Slots
| Props (simple values) | Slots (complex content) | |----------------------|------------------------| | Titles, labels | Formatted text fields | | Numbers, booleans | Images, media | | URLs | Entity references | | CSS variants | Nested components |
Correct Adapter Pattern
{# node--article--teaser.html.twig #}
{% embed 'mytheme:card' with {
heading: label,
url: url,
variant: node.isPromoted ? 'featured' : 'default',
} only %}
{% block media %}{{ content.field_image }}{% endblock %}
{% block body %}{{ content.body }}{% endblock %}
{% endembed %}
{# CRITICAL: Render remaining for cache bubbling #}
{{ content|without('field_image', 'body') }}
Move to Preprocess
Flag these as preprocess candidates:
- Entity queries
- Service calls
- Complex calculations
- Permission checks
- Date formatting beyond simple
|date
Audit Report Format
Issues Detected
| Severity | Line | Issue | Risk | |----------|------|-------|------| | CRITICAL | 12 | Render array drilling | Broken cache |
Refactored Code
Complete template with fixes and comments.
Preprocess Changes
function mytheme_preprocess_node(&$variables) {
// Required PHP code for logic moved from template
}
Final Checklist
- [ ] No render array drilling
- [ ] content rendered or
|withoutused - [ ] No business logic
- [ ]
|rawonly with trusted data - [ ] Components isolated
- [ ] Attributes via object
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: trebormc
- Source: trebormc/drupal-ai-agents
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.