Loading auth config...
Skip to main content
Lokker
A visual representation of web privacy engineering practices, illustrating the balance between user privacy and tracking technologies, highlighting key concepts like referrer policies, consent management, GDPR, CCPA, and the prevention of data leakage through technical measures such as URL parameters and cross-origin resource sharing.

Web Privacy Engineering Practices

Beyond consent management platforms, web engineers can implement numerous HTML and technical practices to protect user privacy. These practices prevent data leakage through referrer headers, link tracking, and other technical mechanisms that expose user browsing behavior to third parties.

Table of Contents


Why HTML-Level Privacy Matters

The Referrer Problem

When users click links on your website, browsers send referrer headers that reveal:

  • Source website: Where the user came from
  • Page URL: The specific page they were on
  • Context: Information about their browsing behavior

Example Referrer Leakage:

User visits: https://healthcare.com/patient-portal/diabetes-treatment
Clicks link to: https://external-site.com
Referrer sent: https://healthcare.com/patient-portal/diabetes-treatment

Privacy Impact:

  • Third parties learn users visited healthcare pages
  • Sensitive page URLs exposed (medical conditions, financial info)
  • Cross-site tracking enabled through referrer data
  • Compliance violations (HIPAA, GLBA, privacy regulations)

Consent management platforms control what scripts run, but don't prevent:

  • Referrer header leakage
  • Link tracking through URL parameters
  • Cross-site data exposure
  • Browser fingerprinting through technical attributes

Privacy engineering practices address these gaps at the HTML and technical level.

Referrer Policy Implementation

Understanding Referrer Policies

Referrer policies control what referrer information is sent with requests:

PolicyBehaviorUse Case
no-referrerNo referrer sentMaximum privacy
no-referrer-when-downgradeNo referrer on HTTPS→HTTPDefault browser behavior
originOnly origin (domain) sentBalance privacy/functionality
origin-when-cross-originFull URL same-origin, origin only cross-originRecommended default
same-originReferrer only for same-origin requestsInternal sites
strict-originOrigin only, never on downgradeGood balance
strict-origin-when-cross-originFull URL same-origin, origin cross-origin, never on downgradeModern default
unsafe-urlAlways send full URLNot recommended

Meta Tag Referrer Policy

Set a site-wide referrer policy using a meta tag:

<!-- Set site-wide referrer policy -->
<meta name="referrer" content="strict-origin-when-cross-origin">

Recommended Settings:

For Healthcare/Financial Sites (Maximum Privacy):

<meta name="referrer" content="no-referrer">

For General Sites (Balanced):

<meta name="referrer" content="strict-origin-when-cross-origin">

For Internal Sites:

<meta name="referrer" content="same-origin">

HTTP Header Referrer Policy

Set referrer policy via HTTP headers (takes precedence over meta tags):

Nginx Configuration:

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Apache Configuration:

Header always set Referrer-Policy "strict-origin-when-cross-origin"

Express.js (Node.js):

app.use((req, res, next) => {
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
});

Override site-wide policy for specific links:

<!-- No referrer for this specific link -->
<a href="https://external-site.com"
rel="noreferrer">
External Link
</a>

<!-- Origin-only referrer for this link -->
<a href="https://external-site.com"
referrerpolicy="origin">
External Link
</a>

rel="noreferrer"

Prevents referrer from being sent with the request:

<!-- No referrer sent -->
<a href="https://external-site.com" rel="noreferrer">
External Link
</a>

When to Use:

  • Links to external third-party sites
  • Links from sensitive pages (healthcare, financial)
  • Links to social media platforms
  • Any link where referrer leakage is a privacy concern

rel="noopener"

Prevents the new page from accessing the window.opener property:

<!-- Prevents opener access -->
<a href="https://external-site.com"
target="_blank"
rel="noopener">
External Link
</a>

Security Benefit:

  • Prevents new page from accessing window.opener
  • Prevents potential security vulnerabilities
  • Should always be used with target="_blank"

Combined: rel="noopener noreferrer"

Use both attributes together for maximum privacy and security:

<!-- Maximum privacy and security -->
<a href="https://external-site.com"
target="_blank"
rel="noopener noreferrer">
External Link
</a>

Best Practice: Always use rel="noopener noreferrer" for external links that open in new tabs.

Automatic Application

For All External Links:

// Automatically add rel="noopener noreferrer" to external links
document.querySelectorAll('a[href^="http"]').forEach(link => {
const href = link.getAttribute('href');
const currentDomain = window.location.hostname;
const linkDomain = new URL(href).hostname;

if (linkDomain !== currentDomain) {
// External link - add privacy attributes
const rel = link.getAttribute('rel') || '';
if (!rel.includes('noopener')) {
link.setAttribute('rel', (rel + ' noopener noreferrer').trim());
}
}
});

WordPress Function:

// Automatically add privacy attributes to external links
function add_privacy_to_external_links($content) {
$content = preg_replace_callback(
'/<a\s+([^>]*href=["\']([^"\']*)["\'][^>]*)>/i',
function($matches) {
$href = $matches[2];
$attrs = $matches[1];

// Check if external link
if (strpos($href, 'http') === 0 &&
strpos($href, home_url()) !== 0) {
// Add rel attributes if not present
if (strpos($attrs, 'rel=') === false) {
$attrs .= ' rel="noopener noreferrer"';
} else {
$attrs = preg_replace(
'/rel=["\']([^"\']*)["\']/i',
'rel="$1 noopener noreferrer"',
$attrs
);
}
}

return '<a ' . $attrs . '>';
},
$content
);

return $content;
}
add_filter('the_content', 'add_privacy_to_external_links');

Form Submission Privacy

Preventing Referrer Leakage on Forms

Forms can leak referrer information when submitted to third parties:

<!-- DANGEROUS: Form submits to third party with referrer -->
<form action="https://third-party-service.com/submit" method="post">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>

Privacy-Safe Form Submission:

<!-- SAFE: Form with referrer policy -->
<form action="https://third-party-service.com/submit"
method="post"
referrerpolicy="no-referrer">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>

Server-Side Form Handling

For maximum privacy, handle form submissions server-side:

<!-- Form submits to your server first -->
<form action="/api/submit-form" method="post">
<input type="text" name="data">
<button type="submit">Submit</button>
</form>
// Server-side forwarding without referrer
app.post('/api/submit-form', (req, res) => {
// Forward to third party without referrer
fetch('https://third-party-service.com/submit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
// No Refer header sent
},
referrerPolicy: 'no-referrer',
body: JSON.stringify(req.body)
});
});

Image and Resource Privacy

Image Referrer Policy

Images loaded from external domains can leak referrer information:

<!-- DANGEROUS: Image loads with referrer -->
<img src="https://external-cdn.com/image.jpg" alt="Image">

Privacy-Safe Image Loading:

<!-- SAFE: Image with referrer policy -->
<img src="https://external-cdn.com/image.jpg"
alt="Image"
referrerpolicy="no-referrer">

CSS and JavaScript Resources

External CSS and JavaScript files can also leak referrer:

<!-- DANGEROUS: External script with referrer -->
<script src="https://external-cdn.com/script.js"></script>

Privacy-Safe Resource Loading:

<!-- SAFE: Script with referrer policy -->
<script src="https://external-cdn.com/script.js"
referrerpolicy="no-referrer"></script>

Or use CSP (Content Security Policy):

<meta http-equiv="Content-Security-Policy" 
content="referrer no-referrer">

Subresource Integrity (SRI)

The Domain Hijacking Risk

When you load external JavaScript or CSS files directly from third-party domains, you risk:

Domain Hijacking Scenario:

  1. You load a polyfill from cdn.example.com/polyfill.js
  2. Domain expires or is acquired by malicious actor
  3. Malicious actor stands up the same script name
  4. Injects malicious code alongside or instead of legitimate code
  5. Your site loads malicious script - security breach occurs

Real-World Example:

  • Popular JavaScript library hosted on external CDN
  • Domain registration expires
  • Malicious party acquires domain
  • Serves malicious version of the script
  • All sites loading from that domain are compromised

What is Subresource Integrity (SRI)?

Subresource Integrity (SRI) is a security feature that allows browsers to verify that resources they fetch haven't been tampered with. It uses cryptographic hashes to ensure the integrity of external resources.

How SRI Works:

  1. Generate hash of the legitimate resource file
  2. Include hash in the integrity attribute
  3. Browser verifies downloaded resource matches hash
  4. Resource blocked if hash doesn't match

Implementation

Basic SRI Implementation:

<!-- DANGEROUS: External script without integrity check -->
<script src="https://cdn.example.com/polyfill.js"></script>
<!-- SAFE: External script with Subresource Integrity -->
<script src="https://cdn.example.com/polyfill.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous"></script>

Why crossorigin="anonymous" is Required:

  • SRI requires CORS to work properly
  • crossorigin="anonymous" enables CORS for integrity checking
  • Without it, SRI validation may fail

Generating SRI Hashes

Using OpenSSL:

# Generate SHA-384 hash (recommended)
cat polyfill.js | openssl dgst -sha384 -binary | openssl base64 -A

# Output: sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC

Using Online Tools:

Using Node.js:

const crypto = require('crypto');
const fs = require('fs');

const fileBuffer = fs.readFileSync('polyfill.js');
const hash = crypto.createHash('sha384').update(fileBuffer).digest('base64');
console.log(`sha384-${hash}`);

CSS Resources with SRI

SRI also works for CSS files:

<!-- DANGEROUS: External CSS without integrity -->
<link rel="stylesheet" href="https://cdn.example.com/styles.css">
<!-- SAFE: External CSS with Subresource Integrity -->
<link rel="stylesheet"
href="https://cdn.example.com/styles.css"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC"
crossorigin="anonymous">

Multiple Hash Algorithms

You can provide multiple hash algorithms for compatibility:

<script src="https://cdn.example.com/polyfill.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC
sha512-abc123def456..."
crossorigin="anonymous"></script>

Hash Algorithm Priority:

  • Browser uses the strongest algorithm it supports
  • Falls back to weaker algorithms if need
  • SHA-384 is recommended (balance of security and compatibility)

Common CDN Libraries with SRI

Bootstrap:

<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"
integrity="sha384-geWF76RCwLtnZ8qwWowPQNguL3RmwHVBC9FhGdlKrxdiJJigb/j/68SIy3Te4Bkz"
crossorigin="anonymous"></script>

jQuery:

<script src="https://code.jquery.com/jquery-3.7.0.min.js"
integrity="sha384-NXgwF8Kv9SSAr+7KKbfCOf+QqXuNSLQqIvokz8qU7lPfiU/TWuCMEmZNvXGkfNg"
crossorigin="anonymous"></script>

React:

<script src="https://unpkg.com/react@18/umd/react.production.min.js"
integrity="sha384-..."
crossorigin="anonymous"></script>

Updating SRI Hashes

When to Update:

  • When updating library versions
  • When CDN changes file content
  • When security patches are applied

Update Process:

  1. Download new version of the resource
  2. Generate new SRI hash
  3. Update integrity attribute
  4. Test that resource loads correctly

Browser Support

SRI Browser Support:

  • ✅ Chrome 45+
  • ✅ Firefox 43+
  • ✅ Safari 11+
  • ✅ Edge 17+
  • ✅ Opera 32+

Fallback Behavior:

  • Browsers without SRI support ignore the integrity attribute
  • Resource loads normally (no integrity check)
  • Consider this when deciding whether to use SRI

Best Practices

1. Always Use SRI for External Scripts

<!-- Always include integrity for external scripts -->
<script src="https://cdn.example.com/script.js"
integrity="sha384-..."
crossorigin="anonymous"></script>

2. Use SRI for External CSS

<!-- Include integrity for external stylesheets -->
<link rel="stylesheet"
href="https://cdn.example.com/styles.css"
integrity="sha384-..."
crossorigin="anonymous">

3. Prefer Self-Hosted Resources

When Possible:

  • Download and host resources on your own domain
  • Eliminates domain hijacking risk entirely
  • Full control over resource integrity

When External is Necessary:

  • Use SRI to verify integrity
  • Monitor for domain changes
  • Have fallback self-hosted versions ready

4. Monitor for Hash Mismatches

// Monitor for SRI failures
window.addEventListener('error', function(e) {
if (e.target.tagName === 'SCRIPT' || e.target.tagName === 'LINK') {
if (e.target.hasAttribute('integrity')) {
console.error('SRI integrity check failed for:', e.target.src || e.target.href);
// Alert security team
}
}
}, true);

Common Mistakes

❌ Mistake: Missing crossorigin Attribute

Problem: SRI requires crossorigin="anonymous" to work properly.

Solution: Always include crossorigin="anonymous" with SRI.

<!-- WRONG: Missing crossorigin -->
<script src="https://cdn.example.com/script.js"
integrity="sha384-..."></script>

<!-- CORRECT: Includes crossorigin -->
<script src="https://cdn.example.com/script.js"
integrity="sha384-..."
crossorigin="anonymous"></script>

❌ Mistake: Outdated Hash Values

Problem: Hash doesn't match updated resource, causing resource to be blocked.

Solution: Update hash values when resources are updated.

❌ Mistake: Trusting CDN Without Verification

Problem: Assuming CDN-provided hashes are correct without verification.

Solution: Generate your own hashes from downloaded files.

Content Security Policy (CSP)

Referrer Policy in CSP

Set referrer policy via Content Security Policy:

<meta http-equiv="Content-Security-Policy" 
content="referrer no-referrer">

CSP Header (Recommended):

add_header Content-Security-Policy "referrer no-referrer" always;

Comprehensive CSP

Combine referrer policy with other CSP directives:

<meta http-equiv="Content-Security-Policy" 
content="default-src 'self';
script-src 'self' 'unsafe-inline';
style-src 'self' 'unsafe-inline';
referrer no-referrer">

URL Parameter Privacy

Removing Sensitive URL Parameters

URL parameters can leak sensitive information:

<!-- DANGEROUS: Sensitive data in URL -->
<a href="https://external-site.com?source=healthcare&page=diabetes-treatment">
External Link
</a>

Privacy-Safe URLs:

<!-- SAFE: No sensitive parameters -->
<a href="https://external-site.com" rel="noreferrer">
External Link
</a>

Server-Side URL Sanitization

Sanitize URLs before redirecting or linking:

// Remove sensitive parameters before redirect
function sanitizeUrl(url) {
const urlObj = new URL(url);

// Remove sensitive parameters
const sensitiveParams = ['source', 'ref', 'utm_source', 'page', 'id'];
sensitiveParams.forEach(param => {
urlObj.searchParams.delete(param);
});

return urlObj.toString();
}

Cross-Origin Resource Sharing (CORS) and Privacy

CORS Headers and Referrer

CORS policies can affect referrer handling:

// Server-side CORS configuration
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Referrer-Policy', 'no-referrer');
next();
});

Best Practices Summary

1. Site-Wide Referrer Policy

Set a default referrer policy for your entire site:

<!-- In <head> section -->
<meta name="referrer" content="strict-origin-when-cross-origin">

Or via HTTP header:

add_header Referrer-Policy "strict-origin-when-cross-origin" always;

Always use privacy attributes on external links:

<a href="https://external-site.com" 
target="_blank"
rel="noopener noreferrer">
External Link
</a>

3. Form Submission Privacy

Prevent referrer leakage on forms:

<form action="https://external-service.com/submit" 
referrerpolicy="no-referrer">
<!-- Form fields -->
</form>

4. Image and Resource Privacy

Set referrer policy on external resources:

<img src="https://external-cdn.com/image.jpg" 
referrerpolicy="no-referrer">

5. Subresource Integrity (SRI)

Always use SRI for external JavaScript and CSS:

<script src="https://cdn.example.com/script.js"
integrity="sha384-..."
crossorigin="anonymous"></script>

<link rel="stylesheet"
href="https://cdn.example.com/styles.css"
integrity="sha384-..."
crossorigin="anonymous">

6. Sensitive Page Protection

For healthcare, financial, or sensitive pages:

<!-- Maximum privacy for sensitive pages -->
<meta name="referrer" content="no-referrer">

Implementation Checklist

HTML-Level Privacy

  • Set site-wide referrer policy meta tag
  • Add rel="noopener noreferrer" to all external links
  • Set referrer policy on forms submitting to third parties
  • Set referrer policy on external images and resources
  • Add Subresource Integrity (SRI) to all external scripts and CSS
  • Include crossorigin="anonymous" with SRI attributes
  • Remove sensitive parameters from URLs

Server Configuration

  • Configure HTTP referrer policy header
  • Set Content Security Policy with referrer policy
  • Configure CORS headers appropriately
  • Sanitize URLs before redirects

Code-Level Implementation

  • Automatically add privacy attributes to external links
  • Sanitize form data before forwarding to third parties
  • Remove sensitive URL parameters
  • Implement server-side form handling for third-party submissions

Industry-Specific Considerations

Healthcare Websites

Maximum Privacy Required:

<!-- No referrer for healthcare sites -->
<meta name="referrer" content="no-referrer">

Why: Prevents leakage of medical page URLs, patient portal access, and health condition information.

Financial Services Websites

Strict Privacy Required:

<!-- Strict referrer policy for financial sites -->
<meta name="referrer" content="no-referrer">

Why: Prevents leakage of account information, transaction pages, and financial data.

General Websites

Balanced Approach:

<!-- Balanced privacy for general sites -->
<meta name="referrer" content="strict-origin-when-cross-origin">

Why: Maintains functionality while protecting privacy.

Testing Your Implementation

Verify Referrer Policy

Browser Console Test:

// Check current referrer policy
console.log('Referrer Policy:', document.referrerPolicy);

// Test link referrer
const link = document.createElement('a');
link.href = 'https://example.com';
link.rel = 'noreferrer';
link.click();
// Check Network tab - no Refer header should be sent

Network Tab Verification

  1. Open Browser DevTools → Network tab
  2. Click external link with rel="noreferrer"
  3. Check Request Headers → Should not include Refer header
  4. Verify Privacy → No source URL information leaked

Automated Testing

// Test that external links have privacy attributes
function testExternalLinkPrivacy() {
const externalLinks = document.querySelectorAll('a[href^="http"]');
const currentDomain = window.location.hostname;

externalLinks.forEach(link => {
const linkDomain = new URL(link.href).hostname;

if (linkDomain !== currentDomain) {
const rel = link.getAttribute('rel') || '';
if (!rel.includes('noreferrer')) {
console.warn('External link missing noreferrer:', link.href);
}
if (!rel.includes('noopener') && link.target === '_blank') {
console.warn('External link missing noopener:', link.href);
}
}
});
}

Common Mistakes

❌ Mistake: Only Using rel="noopener"

Problem: noopener prevents opener access but doesn't prevent referrer leakage.

Solution: Always use rel="noopener noreferrer" together.

❌ Mistake: Inconsistent Referrer Policies

Problem: Different pages have different referrer policies, creating confusion.

Solution: Set site-wide policy and override only when necessary.

❌ Mistake: Forgetting Form Referrer Policy

Problem: Forms submit to third parties with referrer information.

Solution: Set referrerpolicy="no-referrer" on forms submitting externally.

Problem: URL parameters contain sensitive information.

Solution: Remove sensitive parameters or use server-side URL sanitization.

❌ Mistake: Loading External Scripts Without SRI

Problem: External scripts loaded without integrity checks can be compromised if domain is hijacked.

Solution: Always include integrity and crossorigin="anonymous" attributes on external scripts and CSS.

Conclusion

Privacy engineering practices at the HTML and technical level complement consent management by preventing data leakage through referrer headers, link tracking, and other technical mechanisms. These practices are essential for protecting user privacy, especially for healthcare and financial services websites.

Key Takeaways:

  1. Set Referrer Policy: Use meta tags or HTTP headers to control referrer behavior
  2. External Link Privacy: Always use rel="noopener noreferrer" on external links
  3. Form Privacy: Prevent referrer leakage on forms submitting to third parties
  4. Resource Privacy: Set referrer policy on external images and scripts
  5. Subresource Integrity: Always use SRI for external JavaScript and CSS to prevent domain hijacking
  6. Sensitive Pages: Use no-referrer policy for healthcare and financial pages

Rember: These practices work alongside consent management platforms—they don't replace consent management, but they add an essential layer of privacy protection at the technical level.



Note: These practices are technical privacy controls that complement consent management. They don't replace the need for proper consent management platforms, but they add essential privacy protection at the HTML and technical level.