Skip to content

security

4 posts with the tag “security”

Content Security Policy (CSP) Guide for Web Developers

Content Security Policy is one of the most effective defenses against XSS attacks, but it’s also one of the most confusing security headers to configure. Get it wrong and your site breaks. Get it right and an entire class of attacks becomes impossible.

Here’s a practical guide to CSP — what it does, how to build one, and how to use Gasoline to generate a policy from your actual traffic.

CSP tells the browser which sources are allowed to load resources on your page. If a script tries to load from an origin not in your policy, the browser blocks it.

Without CSP, an XSS vulnerability means an attacker can:

  • Load scripts from any domain (<script src="https://evil.com/steal.js">)
  • Execute inline JavaScript (<script>document.cookie</script>)
  • Inject styles that hide or modify content

With CSP, even if an attacker injects HTML, the browser refuses to execute scripts or load resources from unauthorized origins.

CSP is delivered as an HTTP response header:

Content-Security-Policy: default-src 'self'; script-src 'self' https://cdn.example.com; style-src 'self' 'unsafe-inline'

Or as a <meta> tag (with some limitations):

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' https://cdn.example.com">

Each directive controls a resource type:

DirectiveControlsExamples
default-srcFallback for all resource types'self'
script-srcJavaScript'self' https://cdn.jsdelivr.net
style-srcCSS'self' 'unsafe-inline'
img-srcImages'self' https: data:
font-srcWeb fonts'self' https://fonts.gstatic.com
connect-srcXHR, fetch, WebSocket'self' https://api.example.com wss://ws.example.com
frame-srcIframes'self' https://www.youtube.com
media-srcAudio and video'self'
worker-srcWeb Workers, Service Workers'self'
object-srcPlugins (Flash, Java)'none'
base-uri<base> element'self'
form-actionForm submission targets'self'

If a specific directive isn’t set, default-src is used as the fallback.

ValueMeaning
'self'Same origin as the page
'none'Block everything
'unsafe-inline'Allow inline scripts/styles (weakens XSS protection)
'unsafe-eval'Allow eval(), new Function() (weakens XSS protection)
https:Any HTTPS origin
data:Data URIs (data:image/png;base64,...)
https://cdn.example.comSpecific origin
*.example.comWildcard subdomain
'nonce-abc123'Scripts/styles with matching nonce attribute
'sha256-...'Scripts/styles with matching hash
Content-Security-Policy: default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; font-src 'self'; connect-src 'self'

This allows only same-origin resources. Everything else is blocked.

Using a CDN for scripts? Add it:

script-src 'self' https://cdn.jsdelivr.net

Using Google Fonts? Add both origins:

style-src 'self' https://fonts.googleapis.com
font-src 'self' https://fonts.gstatic.com

API on a different domain? Add it to connect-src:

connect-src 'self' https://api.myapp.com

Not sure your policy is correct? Use Content-Security-Policy-Report-Only instead:

Content-Security-Policy-Report-Only: default-src 'self'; script-src 'self'; report-uri /csp-reports

The browser logs violations but doesn’t block anything. Review the reports, adjust the policy, then switch to enforcement.

The biggest challenge with CSP is knowing which origins your page actually loads resources from. A modern web application might use:

  • Your own CDN for static assets
  • Google Fonts for typography
  • A JavaScript CDN (jsdelivr, unpkg, cdnjs)
  • An analytics service (Google Analytics, Segment)
  • A payment processor (Stripe)
  • An error tracker (Sentry)
  • Social media embeds
  • Ad networks

You could audit every <script>, <link>, <img>, and fetch() call in your codebase. Or you could let Gasoline do it.

Gasoline observes all network traffic during your browsing session and generates a CSP from what it sees:

generate({format: "csp"})

Strict — only high-confidence origins (observed 3+ times from 2+ pages):

generate({format: "csp", mode: "strict"})

This gives you the tightest possible policy. If a script was only loaded once, it might be ad injection or a browser extension — strict mode excludes it.

Moderate — includes medium-confidence origins:

generate({format: "csp", mode: "moderate"})

Good for most production use cases.

Report-Only — generates a Content-Security-Policy-Report-Only header:

generate({format: "csp", mode: "report_only"})

Deploy this first to find violations before enforcing.

Gasoline automatically excludes:

  • Browser extension origins (chrome-extension://, moz-extension://) — these shouldn’t be in your CSP
  • Development server origins — localhost on a different port than your app
  • Low-confidence origins — observed only once on one page (likely noise)

Don’t want analytics in your CSP? Exclude it:

generate({format: "csp", mode: "strict",
exclude_origins: ["https://analytics.google.com", "https://www.googletagmanager.com"]})

The output includes:

  • Ready-to-use header string — copy-paste into your server config
  • Meta tag equivalent — for static sites
  • Per-origin details — which directive each origin maps to, confidence level, observation count
  • Filtered origins — what was excluded and why
  • Warnings — e.g., “only 3 pages observed — visit more pages for broader coverage”
script-src 'self' 'unsafe-inline'

This defeats the purpose of CSP for scripts. Inline scripts are the primary XSS vector. Use nonces or hashes instead:

<!-- Server generates a unique nonce per request -->
<script nonce="abc123">
// This script is allowed
</script>
script-src 'self' 'nonce-abc123'

Your page loads fine, but all API calls fail. connect-src controls fetch/XHR destinations — if your API is on a different origin, you need to allow it.

script-src 'self' https:

This allows scripts from any HTTPS origin. An attacker can host a script on any HTTPS server and your CSP won’t block it. Be specific about which origins you allow.

Deploying a new CSP without testing breaks things. Always start with Content-Security-Policy-Report-Only, check for violations, then switch to enforcement.

Even in 2026, you should explicitly block plugins:

object-src 'none'

This prevents Flash and Java plugin exploitation (still a vector in some corporate environments).

Next.js uses inline scripts for hydration. You’ll need nonce-based CSP:

middleware.ts
export function middleware(request) {
const nonce = crypto.randomUUID();
const csp = `script-src 'self' 'nonce-${nonce}'; style-src 'self' 'unsafe-inline';`;
// Pass nonce to components via headers
}

CRA inlines a runtime chunk. Either:

  • Disable inline runtime: INLINE_RUNTIME_CHUNK=false
  • Use hash-based CSP for the known inline script

Vite’s dev server uses inline scripts and HMR WebSocket. Dev CSP will differ from production.

  1. Browse your app through its main flows with Gasoline connected
  2. Generate a CSP: generate({format: "csp", mode: "report_only"})
  3. Deploy in report-only mode and monitor for violations
  4. Adjust — add any legitimate origins that were missed
  5. Switch to enforcement once violations are resolved
  6. Regenerate periodically as your dependencies change

Gasoline takes the guesswork out of step 1 — you don’t have to audit your codebase manually. It sees every origin your page communicates with and builds the policy from observation.

How Gasoline MCP Improves Your Application Security

Most developers discover security issues in production. A penetration test finds exposed credentials in an API response. A security review flags missing headers. A breach notification reveals that a third-party script was exfiltrating form data.

Gasoline MCP flips the timeline. Your AI assistant audits security while you develop, catching issues before they ship.

In the typical development cycle, security checks happen late:

  1. Development — features built, tested, deployed
  2. Security review — weeks later, if at all
  3. Penetration test — quarterly, expensive, findings arrive after context is lost
  4. Incident — the worst time to learn about a vulnerability

Every step between writing the code and finding the issue adds cost. A missing HttpOnly flag caught during development takes 30 seconds to fix. The same flag caught in a pen test takes a meeting, a ticket, a sprint, and a deploy.

Real-Time Security Auditing During Development

Section titled “Real-Time Security Auditing During Development”

Gasoline gives your AI assistant six categories of security checks that run against live browser traffic:

Your AI can scan every network request and response for exposed secrets:

observe({what: "security_audit", checks: ["credentials"]})

This catches:

  • AWS Access Keys (AKIA...) in API responses
  • GitHub PATs (ghp_..., ghs_...) in console logs
  • Stripe keys (sk_test_..., sk_live_...) in client-side code
  • JWTs in URL parameters (a common mistake)
  • Bearer tokens in responses that shouldn’t contain them
  • Private keys accidentally bundled in source maps

Every detection runs regex plus validation (Luhn algorithm for credit cards, structure checks for JWTs) to minimize false positives.

observe({what: "security_audit", checks: ["pii"]})

Finds personal data flowing through your application:

  • Social Security Numbers
  • Credit card numbers (with Luhn validation — not just pattern matching)
  • Email addresses in unexpected API responses
  • Phone numbers in contexts where they shouldn’t appear

This matters for GDPR, CCPA, and HIPAA compliance. If your user list API is returning full SSNs when the frontend only needs names, your AI catches it during development.

observe({what: "security_audit", checks: ["headers"]})

Validates that your responses include critical security headers:

HeaderWhat It Prevents
Strict-Transport-SecurityDowngrade attacks, cookie hijacking
X-Content-Type-OptionsMIME sniffing attacks
X-Frame-OptionsClickjacking
Content-Security-PolicyXSS, injection attacks
Referrer-PolicyReferrer leakage to third parties
Permissions-PolicyUnauthorized browser feature access

Missing any of these? Your AI knows immediately — and can fix it.

observe({what: "security_audit", checks: ["cookies"]})

Session cookies without HttpOnly are accessible to XSS attacks. Cookies without Secure can be intercepted over HTTP. Missing SameSite enables CSRF. Gasoline checks every cookie against every flag and rates severity based on whether it’s a session cookie.

observe({what: "security_audit", checks: ["transport"]})

Detects:

  • HTTP usage on non-localhost origins (unencrypted traffic)
  • Mixed content (HTTPS page loading HTTP resources)
  • HTTPS downgrade patterns
observe({what: "security_audit", checks: ["auth"]})

Identifies API endpoints that return PII without requiring authentication. If /api/users/123 returns a full user profile without an Authorization header, that’s a finding.

Third-party scripts are one of the largest attack surfaces in modern web applications. Every <script src="..."> from an external CDN is a trust decision.

observe({what: "third_party_audit"})

Gasoline classifies every third-party origin by risk:

  • Critical risk — scripts from suspicious domains, data exfiltration patterns
  • High risk — scripts from unknown origins, data sent to third parties with POST requests
  • Medium risk — non-essential third-party resources, suspicious TLDs (.xyz, .top, .click)
  • Low risk — fonts and images from known CDNs

It detects domain generation algorithm (DGA) patterns — high-entropy hostnames that indicate malware communication. It flags when your application sends PII-containing form data to third-party origins.

And it’s configurable. Specify your first-party origins and custom allow/block lists:

observe({what: "third_party_audit",
first_party_origins: ["https://api.myapp.com"],
custom_lists: {
allowed: ["https://cdn.mycompany.com"],
blocked: ["https://suspicious-tracker.xyz"]
}})

Security isn’t just about finding issues — it’s about making sure fixes stay fixed.

// Before your deploy
configure({action: "diff_sessions", session_action: "capture", name: "before-deploy"})
// After
configure({action: "diff_sessions", session_action: "capture", name: "after-deploy"})
// Compare
configure({action: "diff_sessions",
session_action: "compare",
compare_a: "before-deploy",
compare_b: "after-deploy"})

The security_diff mode specifically tracks:

  • Headers removed — did someone drop the CSP header?
  • Cookie flags removed — did HttpOnly get lost in a refactor?
  • Authentication removed — did an endpoint become public?
  • Transport downgrades — did something switch from HTTPS to HTTP?

Each change is severity-rated. A removed CSP header is high severity. A transport downgrade is critical.

Gasoline doesn’t just find problems — it generates the artifacts you need to fix and prevent them.

generate({format: "csp", mode: "strict"})

Gasoline observes which origins your page actually loads resources from during development and generates a CSP that allows exactly those origins — nothing more. It uses a confidence scoring system (3+ observations from 2+ pages = high confidence) to filter out extension noise and ad injection.

generate({format: "sri"})

Every third-party script and stylesheet gets a SHA-384 hash. If a CDN is compromised and serves modified JavaScript, the browser refuses to execute it.

The output includes ready-to-paste HTML tags:

<script src="https://cdn.example.com/lib.js"
integrity="sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8w"
crossorigin="anonymous"></script>

Even before auditing, Gasoline protects against accidental data exposure. The redaction engine automatically scrubs sensitive data from all MCP tool responses before they reach the AI:

  • AWS keys become [REDACTED:aws-key]
  • Bearer tokens become [REDACTED:bearer-token]
  • Credit card numbers become [REDACTED:credit-card]
  • SSNs become [REDACTED:ssn]

This is a double safety net. The extension strips auth headers before data reaches the server. The server’s redaction engine catches anything else before it reaches the AI. Two layers, zero configuration.

Here’s the workflow that makes Gasoline transformative for security:

  1. Develop normally — write code, test features
  2. AI audits continuously — security checks run against live traffic
  3. Issues found immediately — in the same terminal where you’re coding
  4. Fix in context — the AI has the code open and the finding in hand
  5. Verify the fix — re-run the audit, confirm the finding is gone
  6. Prevent regression — capture a security snapshot, compare after future changes

The entire cycle takes minutes, not months. No separate tool. No context switch. No ticket in a backlog that nobody reads.

For developers: Security becomes part of your flow, not an interruption to it. The AI catches what you’d need a security expert to find — and you fix it while the code is still fresh in your mind.

For security teams: Shift-left isn’t a buzzword anymore. Developers arrive at security review with most issues already caught and fixed. Reviews focus on architecture and design, not missing headers.

For compliance: Every audit finding is captured with timestamp, severity, and evidence. SARIF export integrates directly with GitHub Code Scanning. The audit log records every security check the AI performed.

For enterprises: Zero data egress. All security scanning happens on the developer’s machine. No credentials sent to cloud services. No browser traffic leaving the network. Localhost only, zero dependencies, open source.

Install Gasoline, open your application, and ask your AI:

“Run a full security audit of this page and tell me what you find.”

You might be surprised what’s been hiding in plain sight.

Subresource Integrity (SRI) Explained: Protect Your Site from CDN Compromise

Every <script src="https://cdn.example.com/library.js"> on your page is a trust decision. You’re trusting that the CDN will always serve the exact file you expect. If the CDN is compromised, hacked, or serves a corrupted file, your users execute the attacker’s code.

Subresource Integrity (SRI) eliminates this risk. Here’s how it works and how to implement it.

In 2018, a cryptocurrency mining script was injected into the British Airways website through a compromised third-party script. In 2019, Magecart attacks hit thousands of e-commerce sites through CDN compromises. In 2021, the ua-parser-js npm package was hijacked to serve malware.

The attack pattern is always the same:

  1. Attacker compromises a CDN, package registry, or hosting provider
  2. The script content changes (malware added, data exfiltration code injected)
  3. Every website loading that script from that CDN now serves the attacker’s code
  4. Users’ data is stolen, credentials harvested, or cryptocurrency mined

SRI prevents step 3. Even if the CDN is compromised, the browser refuses to execute the modified script.

SRI adds a cryptographic hash to your <script> and <link> tags:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"
crossorigin="anonymous"></script>

When the browser downloads the file, it computes the SHA-384 hash of the content and compares it to the integrity attribute. If they don’t match — because the file was modified, corrupted, or replaced — the browser refuses to execute it.

Format: algorithm-base64hash

integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"

Supported algorithms:

  • SHA-256sha256-...
  • SHA-384sha384-... (recommended — good balance of security and performance)
  • SHA-512sha512-...

You can include multiple hashes for fallback:

integrity="sha384-abc... sha256-xyz..."

The browser accepts the resource if any hash matches.

SRI requires CORS. Add crossorigin="anonymous" to cross-origin scripts:

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

Without crossorigin, the browser can’t compute the hash for cross-origin resources and SRI silently fails.

ThreatSRI Protection
CDN compromise (attacker modifies hosted files)Blocks modified scripts
Man-in-the-middle attacks (on HTTP resources)Blocks tampered resources
CDN serving wrong versionBlocks unexpected content
Package registry hijacking (modified npm package)Blocks modified bundles
DNS hijacking (CDN domain points to attacker)Blocks attacker’s response
ThreatWhy SRI Doesn’t Help
First-party script compromiseSRI is for third-party resources
XSS via inline scriptsUse CSP for inline script protection
Version pinningSRI verifies content, not version semantics
AvailabilityIf the CDN is down, SRI can’t make it work
Terminal window
# Generate a hash for a local file
cat library.js | openssl dgst -sha384 -binary | openssl base64 -A
# Generate a hash for a remote file
curl -s https://cdn.example.com/library.js | openssl dgst -sha384 -binary | openssl base64 -A

Gasoline observes which third-party scripts and stylesheets your page loads and generates SRI hashes automatically:

generate({format: "sri"})

Output per resource:

  • URL — the resource location
  • Hashsha384-... in browser-standard format
  • Ready-to-use HTML tag<script> or <link> with integrity and crossorigin attributes
  • File size — for reference
  • Already protected — flags resources that already have SRI

Filter to specific resource types or origins:

generate({format: "sri", resource_types: ["script"]})
generate({format: "sri", origins: ["https://cdn.jsdelivr.net"]})

The output is copy-paste ready:

<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js"
integrity="sha384-OYp56H6p7T3JKjPdRfL7gAHEdJ7yrfCCe3Ew5EHouvE7qdLCHs6PGoGMOQ/pA6j"
crossorigin="anonymous"></script>

Replace your existing tags with integrity-protected versions:

<!-- Before -->
<script src="https://cdn.example.com/chart.js"></script>
<!-- After -->
<script src="https://cdn.example.com/chart.js"
integrity="sha384-abc123..."
crossorigin="anonymous"></script>

Use the webpack-subresource-integrity plugin:

const { SubresourceIntegrityPlugin } = require('webpack-subresource-integrity');
module.exports = {
output: { crossOriginLoading: 'anonymous' },
plugins: [new SubresourceIntegrityPlugin()]
};

Vite doesn’t generate SRI natively. Use a plugin or add integrity attributes to your HTML template for third-party CDN scripts.

For scripts loaded via <Script> component, add the integrity prop:

<Script
src="https://cdn.example.com/analytics.js"
integrity="sha384-abc123..."
crossOrigin="anonymous"
/>

If a CDN updates the file content (even whitespace changes), the hash won’t match and the browser blocks the script.

Fix: Pin to specific versions (lodash@4.17.21 not lodash@latest) and update hashes when you upgrade versions.

Some CDNs serve different content based on the User-Agent header (e.g., minified vs unminified). This means the hash differs across browsers.

Fix: Use CDNs that serve consistent content regardless of User-Agent. Gasoline warns you when it detects Vary: User-Agent on a resource.

If your framework dynamically creates <script> elements, you’ll need to add integrity attributes programmatically:

const script = document.createElement('script');
script.src = 'https://cdn.example.com/lib.js';
script.integrity = 'sha384-abc123...';
script.crossOrigin = 'anonymous';
document.head.appendChild(script);

Service workers can modify responses, which breaks SRI. If you’re using a service worker that caches CDN resources, ensure it passes through the original response without modification.

SRI and CSP work together:

  • CSP restricts which origins can load resources
  • SRI verifies the content of resources from allowed origins

CSP alone: an attacker compromises an allowed CDN → your CSP still allows the compromised script.

SRI alone: an attacker injects a <script> from a new origin → SRI doesn’t help because the new tag doesn’t have an integrity attribute.

Both together: CSP blocks scripts from unauthorized origins, and SRI blocks modified scripts from authorized origins. The attacker needs to compromise your specific CDN and produce content that matches the hash — which is cryptographically impossible.

  1. Browse your app with Gasoline connected
  2. Generate SRI hashes: generate({format: "sri"})
  3. Add integrity attributes to your third-party script and link tags
  4. Test — verify all resources load correctly
  5. Regenerate when you update third-party library versions

Yes, if you load scripts or stylesheets from third-party CDNs. The implementation cost is minimal (add two attributes to each tag) and the protection against supply chain attacks is significant.

Skip it for first-party resources served from your own domain. SRI adds value when you don’t control the server — if you control both the page and the resource server, CSP provides sufficient protection.

The combination of SRI for third-party resources and CSP for all resources gives you defense in depth against the most common web supply chain attacks.

Gasoline v5.1.0: Single-Tab Tracking Isolation

Gasoline v5.1.0 is a security-focused release that fixes a critical privacy vulnerability in how the extension captures browser telemetry. If you’re running any previous version, upgrade immediately.

Prior to v5.1.0, the extension captured console logs, network requests, and other telemetry from every open browser tab — regardless of whether tracking was enabled for that tab. If you had 40 tabs open and clicked “Track This Page” on one of them, data from all 40 tabs was forwarded to the MCP server.

This was a privacy vulnerability. Tabs containing banking sites, personal email, or other sensitive sessions would leak telemetry into the AI assistant’s context.

v5.1.0 introduces tab-scoped filtering in the content script. The extension now:

  1. Only captures from the explicitly tracked tab. All other tabs are completely isolated.
  2. Attaches a tabId to every forwarded message for data attribution.
  3. Blocks Chrome internal pages (chrome://, about://, devtools://) from being tracked.
  4. Clears tracking state on browser restart — no stale tab references.

The button has been renamed from “Track This Page” to “Track This Tab” to reflect the actual behavior.

When no tab is tracked, the MCP server now prepends a warning to all observe() responses:

WARNING: No tab is being tracked. Data capture is disabled. Ask the user to click ‘Track This Tab’ in the Gasoline extension popup.

This prevents the AI assistant from silently operating on stale or missing data.

API responses from network_waterfall and network_bodies now include:

  • Unit suffixes: durationMs, transferSizeBytes instead of ambiguous duration, size
  • compressionRatio: Computed field showing transfer efficiency
  • capturedAt timestamps on all entries
  • limitations array explaining what the data can and can’t tell you

These changes help LLMs interpret network data without guessing units.

Gasoline is now available on PyPI alongside NPM:

Terminal window
pip install gasoline-mcp
gasoline-mcp

Same binary, same behavior. Platform-specific wheels for macOS (arm64, x64), Linux (arm64, x64), and Windows (x64).

Five issues are deferred to v5.2. See KNOWN-ISSUES.md for details:

  • query_dom not yet implemented
  • Accessibility audit runtime error
  • network_bodies returns no data in some cases
  • Extension timeouts after several operations
  • observe() responses missing tabId metadata
Terminal window
npx gasoline-mcp@5.1.0

Or update your .mcp.json:

{
"mcpServers": {
"gasoline": {
"type": "stdio",
"command": "npx",
"args": ["-y", "gasoline-mcp@5.1.0", "--port", "7890", "--persist"]
}
}
}

GitHub Release · CHANGELOG.md