Table of Contents#
- Introduction
- Understanding the Problem
- AMP Security Model Overview
- Input Sanitization Techniques
- Implementing amp-bind-macro with Sanitization
- Best Practices and Common Patterns
- Real-World Example
- Testing and Validation
- Conclusion
- References
Understanding the Problem#
The Risks of Unsanitized Input#
When user input is directly incorporated into URLs without proper sanitization, several security vulnerabilities can arise:
- Open Redirect Attacks: Malicious actors can redirect users to phishing sites
- JavaScript Injection: Despite AMP's restrictions, improper handling can lead to script injection
- Parameter Pollution: Conflicting or malicious parameters can break application logic
- XSS Vulnerabilities: Cross-site scripting attacks through URL parameters
AMP-Specific Challenges#
AMP's security model imposes restrictions that affect URL handling:
- Limited JavaScript execution
- Strict CSP (Content Security Policy) requirements
- Validation rules for
amp-bindexpressions - Restrictions on dynamic URL protocols
AMP Security Model Overview#
AMP Bind Limitations#
AMP's amp-bind component has specific constraints for URL generation:
<!-- Allowed -->
<a [href]="'/search?q=' + encodeURIComponent(searchTerm)">Search</a>
<!-- Potentially problematic without sanitization -->
<a [href]="'javascript:alert(1)' + userInput">Dangerous Link</a>URL Protocol Restrictions#
AMP only allows certain URL protocols in bindings:
http://,https://mailto:,tel:#fragment identifiers- Relative URLs
Input Sanitization Techniques#
1. URL Encoding#
The most fundamental sanitization technique is proper URL encoding:
<amp-state id="app">
<script type="application/json">
{
"userInput": "",
"baseUrl": "https://api.example.com/search"
}
</script>
</amp-state>
<amp-bind-macro id="buildSafeUrl"
arguments="input"
expression="baseUrl + '?query=' + encodeURIComponent(input)">
</amp-bind-macro>
<input type="text" on="input-throttled:AMP.setState({userInput: event.value})">
<a [href]="buildSafeUrl(userInput)" class="button">Search</a>2. Whitelist-Based Validation#
Create macros that validate input against allowed patterns:
<amp-bind-macro id="validateAndSanitizeInput"
arguments="input"
expression="
input.replace(/[^a-zA-Z0-9\s\-_]/g, '').trim().substring(0, 100)
">
</amp-bind-macro>
<amp-bind-macro id="buildSearchUrl"
arguments="searchTerm"
expression="
'/search?q=' + encodeURIComponent(validateAndSanitizeInput(searchTerm))
">
</amp-bind-macro>3. Parameter-Specific Sanitization#
Different URL parameters require different sanitization approaches:
<amp-bind-macro id="sanitizeEmailParam"
arguments="email"
expression="
email.replace(/[^a-zA-Z0-9@._-]/g, '').toLowerCase()
">
</amp-bind-macro>
<amp-bind-macro id="sanitizeIdParam"
arguments="id"
expression="
id.replace(/[^0-9]/g, '')
">
</amp-bind-macro>Implementing amp-bind-macro with Sanitization#
Basic Safe URL Construction Macro#
<!-- State initialization -->
<amp-state id="appState">
<script type="application/json">
{
"searchQuery": "",
"category": "all",
"sortBy": "relevance"
}
</script>
</amp-state>
<!-- Sanitization and URL building macros -->
<amp-bind-macro id="sanitizeInput"
arguments="input,maxLength"
expression="
encodeURIComponent(
input.replace(/[<>\"']/g, '')
.trim()
.substring(0, maxLength || 255)
)
">
</amp-bind-macro>
<amp-bind-macro id="buildSearchUrl"
arguments="query,category,sort"
expression="
'/results?q=' + sanitizeInput(query, 100) +
'&category=' + sanitizeInput(category, 50) +
'&sort=' + sanitizeInput(sort, 20) +
'&source=amp'
">
</amp-bind-macro>
<!-- Usage -->
<input type="text"
placeholder="Search..."
on="input-throttled:AMP.setState({searchQuery: event.value})">
<select on="change:AMP.setState({category: event.value})">
<option value="all">All Categories</option>
<option value="tech">Technology</option>
<option value="news">News</option>
</select>
<a [href]="buildSearchUrl(searchQuery, category, sortBy)"
class="search-button">
Search Now
</a>Advanced Macro with Multiple Validation Layers#
<amp-bind-macro id="validateUrlParams"
arguments="params"
expression="
{
query: params.query ? params.query.replace(/[^\w\s-]/g, '') : '',
page: Math.max(1, Math.min(100, parseInt(params.page) || 1)),
limit: [10, 25, 50].includes(parseInt(params.limit)) ?
parseInt(params.limit) : 25
}
">
</amp-bind-macro>
<amp-bind-macro id="constructApiUrl"
arguments="params"
expression="
'/api/v1/search?q=' +
encodeURIComponent(params.query ? params.query.replace(/[^\w\s-]/g, '') : '') +
'&page=' + Math.max(1, Math.min(100, parseInt(params.page) || 1)) +
'&limit=' + ([10, 25, 50].includes(parseInt(params.limit)) ? parseInt(params.limit) : 25) +
'&format=json'
">
</amp-bind-macro>Best Practices and Common Patterns#
1. Defense in Depth Approach#
Implement multiple layers of sanitization:
<amp-bind-macro id="multiLayerSanitization"
arguments="userInput"
expression="encodeURIComponent(userInput.replace(/[<>\"'&]/g, '').trim().substring(0, 100))">
</amp-bind-macro>2. Use AMP-State for Configuration#
Store sanitization rules in AMP state for maintainability:
<amp-state id="validationRules">
<script type="application/json">
{
"maxLengths": {
"searchQuery": 100,
"username": 50,
"email": 254
},
"allowedPatterns": {
"username": "^[a-zA-Z0-9_-]{3,50}$",
"id": "^[0-9]{1,10}$"
}
}
</script>
</amp-state>3. Centralized Sanitization Macros#
Create reusable sanitization macros for consistent application-wide use:
<amp-bind-macro id="sanitizeForUrl"
arguments="input,paramType"
expression="encodeURIComponent((paramType == 'email' ? input.toString().replace(/[^a-zA-Z0-9@._-]/g, '') : paramType == 'numeric' ? input.toString().replace(/[^0-9]/g, '') : input.toString().replace(/[^a-zA-Z0-9\s_-]/g, '')).substring(0, 255))">
</amp-bind-macro>Real-World Example#
E-commerce Product URL Generation#
<!-- AMP State -->
<amp-state id="productState">
<script type="application/json">
{
"selectedCategory": "electronics",
"searchTerm": "",
"minPrice": "",
"maxPrice": "",
"sortOption": "popularity"
}
</script>
</amp-state>
<!-- Sanitization Macros -->
<amp-bind-macro id="sanitizePrice"
arguments="price"
expression="
Math.max(0, Math.min(10000,
parseInt(price.replace(/[^0-9]/g, '') || 0)
))
">
</amp-bind-macro>
<amp-bind-macro id="sanitizeCategory"
arguments="category"
expression="
['electronics', 'clothing', 'books', 'home'].includes(category) ?
category : 'all'
">
</amp-bind-macro>
<amp-bind-macro id="buildProductSearchUrl"
arguments="term,category,minPrice,maxPrice,sort"
expression="'/products?search=' + encodeURIComponent(term.replace(/[^\w\s-]/g, '').substring(0, 50)) + '&category=' + sanitizeCategory(category) + '&min_price=' + sanitizePrice(minPrice) + '&max_price=' + sanitizePrice(maxPrice) + '&sort=' + (['popularity', 'price_asc', 'price_desc', 'newest'].includes(sort) ? sort : 'popularity') + '&=true'">
</amp-bind-macro>
<!-- UI Implementation -->
<div class="filters">
<input type="text"
placeholder="Search products..."
on="input-throttled:AMP.setState({searchTerm: event.value})">
<input type="number"
placeholder="Min price"
on="input-throttled:AMP.setState({minPrice: event.value})">
<input type="number"
placeholder="Max price"
on="input-throttled:AMP.setState({maxPrice: event.value})">
<select on="change:AMP.setState({sortOption: event.value})">
<option value="popularity">Popularity</option>
<option value="price_asc">Price: Low to High</option>
<option value="price_desc">Price: High to Low</option>
<option value="newest">Newest First</option>
</select>
</div>
<a [href]="buildProductSearchUrl(searchTerm, selectedCategory, minPrice, maxPrice, sortOption)"
class="cta-button">
Apply Filters
</a>Testing and Validation#
AMP Validator Compliance#
Ensure your macros pass AMP validation:
<!-- This will pass validation -->
<amp-bind-macro id="safeUrlBuilder"
arguments="param"
expression="'/path?value=' + encodeURIComponent(param)">
</amp-bind-macro>
<!-- This may cause validation warnings -->
<amp-bind-macro id="unsafeUrlBuilder"
arguments="param"
expression="'javascript:void(' + param + ')'">
</amp-bind-macro>Testing Strategies#
- Boundary Testing: Test with maximum length inputs
- Special Character Testing: Test with HTML, SQL, and script tags
- Encoding Verification: Ensure proper URL encoding occurs
- AMP Validator: Always test with AMP's official validator
<!-- Test your implementation with various inputs -->
<amp-state id="testCases">
<script type="application/json">
{
"tests": [
"normal input",
"input with <script>",
"very-long-input-".repeat(10),
"input with & special?chars"
]
}
</script>
</amp-state>
<div [text]="buildSearchUrl(testCases.tests[0])">Test Result 1</div>
<div [text]="buildSearchUrl(testCases.tests[1])">Test Result 2</div>Conclusion#
Proper input sanitization in AMP pages, especially when using amp-bind-macro for URL generation, is crucial for security and compliance. By implementing the techniques outlined in this guide:
- You protect users from redirect attacks and XSS vulnerabilities
- Ensure AMP validator compliance
- Create maintainable, reusable sanitization logic
- Build robust applications that handle edge cases gracefully
Remember that security is an ongoing process. Regularly review and update your sanitization logic to address new threats and always test your implementation with the AMP validator.