Skip to content

JavaScript Injection

Ghost can inject JavaScript code into any HTML page passing through the proxy. When you create an injection rule with a URL pattern, Ghost automatically modifies matching HTML responses to include your script — along with a built-in helper API (__ghost) that gives your injected code superpowers: cross-origin HTTP requests (bypassing CORS), AI analysis of page content, and DOM mutation observation.

This is different from the browser extension’s inject layer (which overlays UI on top of pages). JavaScript injection modifies the actual HTML response at the proxy level, so the browser executes your code as if it were part of the original page. This means you have full access to the page’s JavaScript context, DOM, cookies, localStorage, and everything else.

What this diagram shows — step by step:

  1. The browser requests an HTML page through Ghost’s proxy
  2. Ghost forwards the request to the real server and gets the HTML response
  3. Ghost checks the response URL against all enabled injection rules
  4. If a rule matches:
    • The response body is decompressed (if gzip, deflate, or brotli compressed) — up to 5 MB. If the body exceeds 5 MB, injection is skipped entirely to avoid working with truncated HTML.
    • Content Security Policy headers are stripped — four variants are removed (Content-Security-Policy, Content-Security-Policy-Report-Only, X-Content-Security-Policy, X-WebKit-CSP) so your injected scripts can execute without being blocked by the page’s security policy
    • The __ghost helper script is injected first (provides fetch, analyze, and observe functions)
    • Your user scripts are injected after the helper
    • The Content-Length header is updated to reflect the larger body size
    • Content-Encoding and Transfer-Encoding headers are removed (since the body is now uncompressed)
  5. The browser receives the modified HTML and executes your scripts as part of the page
  6. Ghost stores the original (pre-injection) body in the flow database — so when you inspect the flow in Ghost’s traffic list, you see the real server response, not the injected version

Each rule defines what JavaScript to inject and which pages to target.

FieldDescription
NameA human-readable label for the rule (e.g., “DOM Logger”, “Auth Token Monitor”)
URL PatternA glob or substring pattern that determines which HTML responses get the script injected (see matching details below)
ScriptThe JavaScript code to inject, up to 64 KB in size
Positionhead or body — a label for organization. Note: all scripts are currently appended at the end of the HTML body regardless of this setting.
EnabledToggle on/off without deleting the rule

Rules are scoped to the active session — each session has its own set of injection rules. When a session is deleted, its injection rules are automatically cascade-deleted from the database.

Ghost matches the rule’s URL pattern against the full request URL using a cascading strategy:

PatternMatch StrategyExampleWhat It Matches
* or emptyMatch all*Every HTML response passing through the proxy
Contains **Multi-segment glob**example.com**/app/**Matches across / separators — https://www.example.com/app/dashboard/settings
Contains *, ?, or [Single-segment glob*.example.com/app/*Standard glob — api.example.com/app/home but NOT api.example.com/app/sub/page
Plain textSubstring contains (fallback)example.comAny URL containing “example.com” anywhere

The pattern is checked against both the full URL (with scheme) and the host+path (without scheme), so example.com/page works even without specifying https://.

Every injection automatically includes the Ghost helper script before your code. This gives your injected JavaScript three powerful functions that wouldn’t normally be available in a browser context.

Makes HTTP requests without CORS restrictions. When you call __ghost.fetch(), the request is sent from Ghost’s backend server — not from the browser. This means you can make API calls to any server from any page, regardless of CORS headers.

// Fetch data from any API — no CORS restrictions
const response = await window.__ghost.fetch('https://api.example.com/users', {
method: 'GET',
headers: { 'Authorization': 'Bearer my-token' }
});
console.log(response.status, response.body);

How it works under the hood: your call is sent to POST /api/v1/script/fetch on Ghost’s backend, which makes the actual HTTP request using Go’s HTTP client and returns the response. The backend includes SSRF protection (blocks requests to private/loopback IP addresses) and a 10-second timeout. Response bodies are capped at 5 MB (1 MB for text content), and binary responses are base64-encoded.

Sends page data to Ghost’s AI agent for analysis. This lets your injected scripts leverage the configured LLM (Anthropic, OpenAI, or Ollama) to analyze DOM content, find security issues, or extract structured data from pages.

// Send page content to AI for analysis
const dashboard = document.querySelector('.dashboard').innerHTML;
const analysis = await window.__ghost.analyze(
dashboard,
'Find any sensitive data exposed in this HTML'
);
console.log(analysis);

How it works: your call is sent to POST /api/v1/script/analyze on Ghost’s backend, which forwards the data to the configured LLM. Rate-limited to 10 concurrent requests globally and 3 per IP address to prevent abuse. Data payload is capped at 32 KB, prompts at 1 KB, and the entire request at 256 KB. 30-second timeout.

window.__ghost.observe(selector, callback)

Section titled “window.__ghost.observe(selector, callback)”

A convenient wrapper around MutationObserver that watches for DOM elements matching a CSS selector. Processes existing elements immediately and monitors for new ones as they’re added to the page.

// Watch for new notification elements appearing in the DOM
const cleanup = window.__ghost.observe('.notification-item', (element) => {
console.log('New notification:', element.textContent);
});
// Later, stop observing
cleanup();

This is purely client-side (no server communication). It uses a WeakSet to deduplicate already-processed elements and handles both the case where the body already exists (processes immediately) and where the script runs before DOMContentLoaded (defers until ready). Returns a cleanup function that disconnects the observer.

The __ghost helper connects to Ghost’s API in two ways:

  1. Primary: Through http://ghost.proxy — Ghost’s proxy intercepts requests to this special hostname and routes them to the API server internally
  2. Fallback: Direct connection to http://{host}:{port} if ghost.proxy is unreachable

On initialization, the helper sends a GET /verify request to confirm connectivity before making any other calls.

Content Security Policy (CSP) is a browser security feature that restricts which scripts can run on a page. Since Ghost’s injected scripts aren’t listed in the page’s CSP, they would normally be blocked. Ghost strips all four CSP header variants when injection rules match:

  • Content-Security-Policy
  • Content-Security-Policy-Report-Only
  • X-Content-Security-Policy (legacy)
  • X-WebKit-CSP (legacy)

This is necessary for injected scripts to execute, but it does weaken the page’s security posture during testing. CSP is only stripped for pages that match an injection rule — other pages are unaffected.

Injected script content is escaped to prevent two dangerous scenarios:

  1. Script tag breakout</script (case-insensitive) is replaced with <\/script, preventing an attacker from crafting script content that prematurely closes the script tag and injects arbitrary HTML
  2. HTML comment injection<!-- is replaced with <\!--, preventing HTML comment state transitions that could break the page structure

Each injected script is wrapped in a <script> tag with a data-ghost-inject attribute set to the rule’s ID (HTML-escaped to prevent XSS via crafted IDs):

<script data-ghost-inject="01HWRFQP3K5M9TGWQX7Z">
// Your injected code here
</script>

The __ghost helper uses data-ghost-inject="__ghost-helper" as its identifier.

The original (pre-injection) response body is stored in Ghost’s flow database. When you inspect a flow in the traffic list, you see the real server response — not the version with injected scripts. This keeps your traffic analysis clean and accurate.

Compressed response bodies (gzip, deflate, brotli) are decompressed before injection, with a strict 5 MB limit (maxInjectionDecompressSize). If the decompressed body exceeds 5 MB, Ghost checks by reading one extra byte — if data remains, it returns the original compressed body unchanged and skips injection entirely. This prevents memory issues from decompressing extremely large HTML pages.

The injection rules panel opens as a slide-over from the command bar (code block icon).

Each rule appears as a row showing:

  • Color-coded icon — cyan for head position, purple for body position
  • Rule name (or “Unnamed rule” if no name was set)
  • Position badge — shows </head> or </body>
  • URL pattern in monospace with a globe icon
  • Hover actions — toggle (green/grey) and delete (red) buttons

Click a rule to edit or click “Add” to create:

  • Name — text input for the rule label
  • URL Pattern — text input with placeholder showing glob syntax
  • Position — toggle between head and body
  • Script — monospace textarea (10 rows, resizable) for the JavaScript code
  • Helper API hint — reminder text about window.__ghost.fetch, analyze, and observe
  • Enable/Disable toggle
  • Cancel and Save buttons

When no rules exist, shows an explanation of what injection does and a “Create your first rule” button.

The proxy_inject_script agent tool lets the AI agent manage injection rules programmatically:

ActionWhat It Does
addCreates a new injection rule with URL pattern and script. Validates glob pattern syntax and enforces the 64 KB script size limit. Persists to the database and adds to the in-memory injector.
removeDeletes a rule by ID. Removes from database and memory.
listLists all rules for the current session, showing ID, name, URL pattern, enabled status, and a truncated (80-character) script preview.
clearRemoves all rules for the current session in a single atomic database operation, then clears the in-memory rule set.

Injection rules are stored in SQLite (injection_rules table) with a foreign key reference to the session:

session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE

When a session is deleted, all its injection rules are automatically removed. Rules are re-fetched from the database when you switch sessions.

EventWhen It Fires
injection_rule.createdA new rule was saved (from UI or agent tool)
injection_rule.updatedA rule was modified or toggled
injection_rule.deletedA rule was removed or cleared
MethodEndpointDescription
GET/api/v1/injection-rulesList all rules for the active session
POST/api/v1/injection-rulesCreate a new rule
GET/api/v1/injection-rules/{id}Get a single rule
PUT/api/v1/injection-rules/{id}Update a rule
DELETE/api/v1/injection-rules/{id}Delete a rule
POST/api/v1/injection-rules/{id}/toggleToggle enabled/disabled

Script bridge endpoints (used by the __ghost helper, no authentication required):

MethodEndpointDescription
POST/api/v1/script/fetchProxy an HTTP request (CORS bypass). SSRF-protected, 10s timeout, 5 MB response cap.
POST/api/v1/script/analyzeSend data to the AI agent for analysis. Rate-limited (10 global, 3/IP), 30s timeout, 32 KB data cap.

DOM inspection — Inject a script that highlights all interactive elements, logs their accessibility attributes, and reports which ones are missing ARIA labels. Useful for accessibility auditing without installing browser extensions.

API monitoring from inside the page — Hook window.fetch and XMLHttpRequest to log all client-side API calls with their parameters. See requests that your app makes directly (not through the proxy) alongside the proxied traffic.

Test data injection — Inject a script that modifies the page’s state: set feature flags in localStorage, override API responses, or add mock data to the DOM. Test how your app behaves with different configurations without touching the server.

Security testing — Inject DOM XSS probes, extract cookies and tokens, test for DOM clobbering, or use __ghost.fetch to make cross-origin API calls that test for IDOR or authorization bypass from within the page context.

Performance monitoring — Inject a PerformanceObserver to capture Core Web Vitals (LCP, FID, CLS) and send them to Ghost via __ghost.fetch. Monitor real performance metrics during manual testing sessions.

Live page augmentation — Inject a floating panel that shows the current user’s session data, feature flags, or A/B test variant in real-time. Useful for QA engineers who need to verify which experiment group they’re in.