Indexability, titles, links, depth, and the crawl graph.
Audit a live page
before the models do.
Cluster fetches the document, robots, and llms.txt, then scores technical SEO, answer engines, generative search, experience, and security. Every miss includes a fix you can paste.
Recent scans · 0
No saved scans in this browser.
Waiting. Try a domain:
Press / to focus the URL, ↵ to run.
Overall shape · click a cluster to filter the report
| SEO | Not scored yet |
|---|---|
| AEO | Not scored yet |
| GEO | Not scored yet |
| AIO | Not scored yet |
| SXO | Not scored yet |
| SECURITY | Not scored yet |
≥85 strong≥70 good≥40 watchunder 40 critical
Traffic this scan actually made
Each row is a request Cluster sent. A 404 or other error includes what it means and what to do. This is not your router, and it is not traffic the page makes only after JavaScript runs.
0 requests, 0 B, 0 failed.
Waiting for a scan. Rows appear as each fetch finishes.
HOSTS IN THE HTML
No scripts, images, frames, or stylesheets yet.
Question headings, snippets, and FAQ markup.
AI crawlers, llms.txt, and brand entities.
Answer-first copy, diversity, and measurement.
Intent match, layout shift, and next steps.
Headers, injection sinks, and a 20-point extended audit.
Ten agents, one crawl
Pass means 85 or above. The spiders move while the crawl is running. The times are the measured fetch and the one shared scoring pass.
Fix library · 74
Every check, in plain language, with something you can paste.
HTTPS Enforced
Effort: LowImpact: CriticalWhat this means. If the address can open as http, the page is traveling unlocked. Anyone on the same cafe Wi-Fi can read or change it, browsers show "Not secure," and Google treats that as a reason to rank the page lower. HTTPS is the lock icon: it proves the visitor reached your server, not someone in the middle.
How to fix it. Turn on a free certificate from your host or Let's Encrypt. Then permanently redirect every http visit to the https address, so there is only one URL. After that has been live for a while, add the HSTS header in the snippet so browsers remember to use the lock and skip the insecure address entirely.
# Nginx — port 80 only redirects. HSTS is ignored on plain HTTP, so it belongs on the HTTPS server.server {listen 80;return 301 https://$host$request_uri;}server {listen 443 ssl;add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;}
HSTS Preload Strong
Effort: LowImpact: HighWhat this means. If someone types the domain without https, or follows an old http link on public Wi-Fi, an attacker on that network can strip the redirect and read or change the traffic before it is encrypted. This is an SSL-stripping man-in-the-middle. It is not, by itself, a way into the database.
How to fix it. Return Strict-Transport-Security: max-age=31536000; includeSubDomains; preload on HTTPS responses, and only after every subdomain really serves HTTPS. Then submit the domain at hstspreload.org. The Security cluster also has one nginx block that sets this with the other browser headers.
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
Status 200
Effort: LowImpact: CriticalWhat this means. 200 means "here is the page." Anything else — a 404, a 500, or a chain of redirects — tells Google the page is missing, broken, or not worth the crawl. Visitors land on an error, and the ranking URL never settles.
How to fix it. Open the final address and make sure the server answers 200, not another redirect. Collapse chains so the first hop lands on the real page. If something was moved, keep one permanent (301) redirect and delete the extras.
# Confirm the final URL answers 200curl -sI https://example.com/page | head -n 1
Canonical Self
Effort: LowImpact: HighWhat this means. A canonical tag is you pointing at the one URL that should get the credit. If it is missing, or it points at a different page, Google may split your ranking between copies (with and without a slash, or http and https) and none of them rank as well as they could.
How to fix it. On this exact page, add a link rel=canonical whose href is the preferred address of this page, including https and the trailing slash you actually use. It should match the URL in the browser, not the homepage, unless this page really is a duplicate of another.
<link rel="canonical" href="https://example.com/page/" />
Indexability - Noindex
Effort: LowImpact: CriticalWhat this means. noindex is an instruction that says "do not show this page in Google." That is correct for a checkout or a thank-you page. On a page you want people to find, it hides the page from search and from rich results, no matter how good the content is.
How to fix it. Remove noindex from the robots meta tag and from the X-Robots-Tag header unless the page is meant to stay private. Use index, follow, and max-image-preview:large so Google can show the page and a large image preview.
<meta name="robots" content="index, follow, max-image-preview:large" />
Sitemap Reference
Effort: LowImpact: MediumWhat this means. A sitemap is a list of the URLs you want discovered. If robots.txt never mentions it, Google has to find every new page by following links, which is slower and easy to miss on a new or quiet site.
How to fix it. Publish an XML sitemap (or a sitemap index if you have several). Add one line to robots.txt: Sitemap: followed by the full https address. Then submit that same address in Google Search Console.
# robots.txtSitemap: https://example.com/sitemap-index.xml
Title Length 30-60
Effort: LowImpact: HighWhat this means. The title is the blue link in Google. Under about 30 characters it looks thin and wastes the space. Over about 60 characters Google cuts it off, so the words you cared about never get seen and fewer people click.
How to fix it. Rewrite the title to roughly 30–60 characters. Put the phrase someone would search first, then the brand at the end. Read it out loud: it should say what the page is, not a pile of keywords.
<title>Fix CLS in Astro — 3 Techniques | Example</title>
Meta Description 120-155
Effort: LowImpact: HighWhat this means. The meta description is the gray paragraph under the blue link. If it is missing or the wrong length, Google writes its own snippet from random sentences on the page, and that usually gets fewer clicks.
How to fix it. Write 120–155 characters in plain speech. Say what the visitor gets and why it is worth opening. Do not repeat the title word for word. One concrete outcome beats a list of adjectives.
<meta name="description" content="Audit a live page with 10 agents. Get SEO, AEO, GEO, and security scores plus copy-paste fixes." />
H1 Single
Effort: LowImpact: MediumWhat this means. The H1 is the page's headline, the thing a person and a search engine both treat as the subject. Zero H1s leave the subject unclear. Several H1s argue with each other, so neither readers nor Google know which idea the page is actually about.
How to fix it. Keep exactly one H1, and make it match what someone came to learn. Smaller sections belong in H2s. If a template is printing the logo or a menu item as an H1, change that to a paragraph or a span.
<h1>How to build a technically sound page in 2026</h1>
H2 Structure >=2 No Skip
Effort: MediumImpact: MediumWhat this means. H2s are the table of contents of the page. With fewer than two, the page is one undifferentiated block. Jumping from an H1 straight to an H3 is like skipping a chapter number: screen readers and Google both lose the outline.
How to fix it. Add at least two H2s, ideally three to six, written as the questions a visitor would ask. Never skip a level: H1, then H2, then H3 only inside that H2. Do not use headings just to make text big.
<h2>What is technical SEO?</h2><h2>Why does page experience matter?</h2><h2>How do you fix CLS?</h2>
On-Page Assets - Alt OG LD+JSON
Effort: MediumImpact: HighWhat this means. Three separate things are easy to forget, and each one costs you. Alt text is how images are understood when they do not load and how Google Images reads them. Open Graph tags are the title, description, and picture people see when the link is pasted into Slack or iMessage. JSON-LD is the structured label that can earn stars, FAQs, or a logo in search.
How to fix it. Give every meaningful image a short alt that describes it, plus width and height. Add og:title, og:description, and a real og:image of about 1200 by 630. Add one JSON-LD block that matches what the page actually is (an article, a product, an organization).
<meta property="og:title" content="Cluster audit" /><meta property="og:image" content="https://example.com/og.png" /><img src="/hero.webp" alt="Score radar across six clusters" width="1200" height="630" />
Topical Authority - Depth
Effort: HighImpact: CriticalWhat this means. A thin page says a little about a topic and stops. People do not link to it, and answer engines do not quote it, because there is nothing specific to quote. Depth here means enough explanation, an example, and a number you measured, not filler.
How to fix it. If the page is short, expand it past roughly 1,500 words only where you have something real to add: a worked example, a small table, two or three original figures, and a short FAQ. Cut repeated sentences instead of padding.
<!-- Brief: 1500 words, 3 original stats, 1 table, 2 code blocks, FAQ -->
Featured Snippets - Def 40-60w
Effort: LowImpact: HighWhat this means. The paragraph Google lifts into the box above the links is usually 40 to 60 words sitting directly under a heading. If the answer is buried, too long, or starts with "it depends" without saying what the thing is, someone else's sentence gets the box.
How to fix it. Under the question heading, write one definition of about 40–60 words. Start with the name of the thing plus "is" or "means," then the outcome. Do not put an image or a huge intro between the heading and that paragraph.
<h2>What is GEO?</h2><p>Generative Engine Optimization is the practice of making a page easy for ChatGPT, Perplexity, and Gemini to quote. It favors original numbers, an llms.txt file, and clear entity markup so models cite the brand instead of a competitor.</p>
Question-Based H2s
Effort: LowImpact: HighWhat this means. People type questions, and so do the tools that summarize the web. A heading that is already the question ("How do I fix this?") is much easier to match than a clever label like "The hard part."
How to fix it. Rewrite the H2s as What, Why, or How questions a customer would actually ask. Keep the answer in the first sentences under that heading. You do not need every heading to be a question, but the important ones should be.
<h2>How do I fix technical SEO on a static site?</h2>
Voice Search - List/Table after H2
Effort: LowImpact: MediumWhat this means. Voice assistants and snippet boxes prefer a list or a table that starts immediately after the question. If the next thing is a long story, they skip the page and read someone who put the steps in order.
How to fix it. Put a short numbered list, a bullet list, or a table as the very next block after the H2. One item per step. Save the commentary for after the list.
<h2>How do you audit a page in three steps?</h2><ol><li>Fetch the HTML and response headers.</li><li>Parse the document.</li><li>Score each cluster and apply the fixes.</li></ol>
FAQPage Schema
Effort: LowImpact: HighWhat this means. A styled accordion that looks like an FAQ is not enough. Google only treats it as an FAQ if the questions and answers are also marked up as FAQPage structured data. Without that, you miss the expandable questions under your search result.
How to fix it. Add a JSON-LD FAQPage with three to five real questions you already answer on the page. The text in the markup must match the visible text. Do not invent questions that are not on the page, and do not mark up the whole article as an FAQ.
<script type="application/ld+json">{"@context": "https://schema.org","@type": "FAQPage","mainEntity": [{"@type": "Question","name": "What does Cluster score?","acceptedAnswer": {"@type": "Answer","text": "SEO, AEO, GEO, AIO, experience, and security on one live URL."}}]}</script>
AI Overview - TLDR Top 15%
Effort: LowImpact: HighWhat this means. AI Overviews and similar summaries read the top of the page first. If the useful conclusion is at the bottom, the model summarizes your introduction instead, or quotes a competitor who said the point up front.
How to fix it. In the first 15 percent of the page, add a short "Key takeaways" or TL;DR: three bullets that state the result. A person skimming should get the answer without scrolling.
<div id="tldr"><strong>TL;DR</strong><ul><li>Ten agents score one URL.</li><li>Six clusters, one overall grade.</li><li>Every miss ships a copy-paste fix.</li></ul></div>
LLM Visibility - Bot Access
Effort: LowImpact: CriticalWhat this means. ChatGPT, Claude, Perplexity, and similar products only cite pages their crawlers were allowed to read. A robots.txt rule that blocks GPTBot, ClaudeBot, or PerplexityBot (or a blanket Disallow for everyone) quietly removes you from those answers.
How to fix it. In robots.txt, allow the major AI crawlers, or at least do not disallow the whole site for them. Keep blocking paths that are private, such as checkout or admin. Recheck after any "block AI" plugin, because those often add the disallow for you.
User-agent: GPTBotAllow: /User-agent: ClaudeBotAllow: /User-agent: PerplexityBotAllow: /
AI Citations - llms.txt
Effort: LowImpact: CriticalWhat this means. llms.txt is a plain-text cover sheet at /llms.txt. It tells a model, in a few lines, who you are and which URLs matter. Without it, the model has to guess from whatever page it landed on.
How to fix it. Publish https://your-domain/llms.txt. Start with a one-paragraph description of the company or the site, then a short list of the canonical pages you want quoted, each with a one-line note. Link the real pages, not drafts.
# llms.txt## AboutWe publish practical technical SEO for modern sites.## Pages- [Cluster](https://example.com/): ten-agent live audit
AI Citations - Quotable Stats
Effort: MediumImpact: HighWhat this means. Models prefer a sentence with a number they can repeat: a percent, a count, a price, a year. Vague lines like "many customers love it" give them nothing safe to cite, so they cite someone who published a figure.
How to fix it. Add one or two original numbers you can stand behind, with the sample or the date attached ("in our 2025 review of 400 pages..."). Do not borrow a statistic you cannot source. Put the number in a normal sentence, not only inside an image.
<p>Pages with a self-canonical and FAQ schema were 22% more likely to appear in AI Overviews in our 2025 sample of 400 URLs.</p>
Brand Mentions - Org Schema
Effort: LowImpact: HighWhat this means. To a model, an untagged brand is just a word that might belong to several companies. Organization schema says "this name, this logo, this official site are the same entity," which is how you get named correctly instead of mixed up with a namesake.
How to fix it. Add Organization JSON-LD with the legal or public name, the homepage URL, a logo URL that actually loads, and sameAs links to profiles you control (Wikidata, LinkedIn, Wikipedia if you have one). Put it on the homepage at least.
<script type="application/ld+json">{"@context": "https://schema.org","@type": "Organization","name": "Example","url": "https://example.com","logo": "https://example.com/logo.png","sameAs": ["https://www.wikidata.org/wiki/Q42"]}</script>
Entity Signals - sameAs
Effort: LowImpact: MediumWhat this means. sameAs is the list of "this is also us" links. Wikidata and your official social profiles are how a model checks that your brand is that specific company, not a different one with the same name.
How to fix it. Inside the Organization block, add sameAs URLs for Wikidata and the social profiles you actually update. Use the canonical profile URL, not a redirect or a personal account. Skip networks you do not use.
"sameAs": ["https://www.wikidata.org/wiki/Q42","https://www.linkedin.com/company/example"]
AI Recommendations - Product Schema
Effort: MediumImpact: MediumWhat this means. When someone asks an assistant for a product, it looks for a Product label: the name, the offer, and often a rating. A normal product page with none of that is easy to skip in favor of a retailer who marked it up.
How to fix it. If this page sells or describes one product, add Product JSON-LD with the real name and, if you have genuine reviews, an aggregateRating that matches the reviews on the page. Never invent a rating. Include price and currency if there is an offer.
<script type="application/ld+json">{"@context": "https://schema.org","@type": "Product","name": "Cluster Pro","aggregateRating": {"@type": "AggregateRating","ratingValue": "4.9","reviewCount": "128"}}</script>
AI Search Visibility - llms.txt Full
Effort: LowImpact: CriticalWhat this means. llms.txt is the short map. llms-full.txt is the longer briefing a model can read when it wants detail: what you do, what you refuse to claim, and the pages that explain it. Missing the long file means the model only gets the short map.
How to fix it. Publish both /llms.txt and /llms-full.txt. Keep the long file factual and current. State what the product does and what it does not do, so a model does not invent features.
# llms-full.txt## ProductCluster audits one URL across six clusters and returns copy-paste fixes.## Do notDo not invent metrics. Quote only numbers published on the site.
AI Content Optimization - Lexical Diversity
Effort: HighImpact: MediumWhat this means. If the same few words repeat in a loop, the page reads as filler, whether a person or a detector is skimming it. Varied, specific nouns ("the hero image shifted 0.18") are more believable than "this great tool helps you."
How to fix it. Read the page aloud and replace repeated verbs and empty praise with concrete nouns: what was fetched, what broke, what the number was. One first-hand detail is worth more than another adjective. Cut sentences that restate the previous one.
Before: "This tool is a great tool that helps you with your tool needs."After: "Cluster fetched the live HTML, found three images without dimensions, and the layout shifted 0.18."
AI Overviews - Answer-First
Effort: LowImpact: HighWhat this means. Answer-first means the result is in the opening, not after three screens of scene-setting. Summarizers quote the opening. If the opening is a story about the industry, that story is what gets quoted, not your conclusion.
How to fix it. Open with one or two sentences that state the result, or a TL;DR line. Then explain. You can still tell the story; just do not make the reader hunt for the point.
<div data-tldr>TL;DR: Force HTTPS, one H1, a 40-word definition under each question H2, and allow AI crawlers.</div>
AI Mode Optimization
Effort: LowImpact: MediumWhat this means. Google's AI Mode leans on pages that already say what kind of thing they are: an article, a product, a how-to. Without that label, and without question headings, the page is harder to place next to a specific question.
How to fix it. Add Article (or the more specific type that fits) as JSON-LD, with a headline that matches the H1. Keep the important H2s phrased as questions, and answer each one immediately underneath.
<script type="application/ld+json">{"@context":"https://schema.org","@type":"Article","headline":"How Cluster scores a page"}</script>
AI Search Analytics
Effort: LowImpact: MediumWhat this means. Visits from ChatGPT, Perplexity, and Gemini show up as referral traffic, but only if you look. If you do not separate them, you cannot tell whether the llms.txt work or the clearer answers changed anything.
How to fix it. In your analytics, group referrals whose host is chatgpt.com, perplexity.ai, gemini.google.com, claude.ai, and similar. A one-line check on document.referrer is enough to tag them. Review it monthly, the same way you review Google traffic.
const ref = document.referrer;if (/chatgpt\.com|perplexity\.ai|gemini\.google/.test(ref)) {// send to your analytics as ai_referral}
Search Intent - Title/H1 Match
Effort: LowImpact: HighWhat this means. The title is the promise in search. The H1 is the promise on the page. When they talk about different things, the visitor feels misled and bounces, and Google sees a page that does not match the query it ranked for.
How to fix it. Use the same primary phrase in the title and the H1. The wording can differ slightly ("Fix CLS — three techniques" and "Fix CLS with three techniques") as long as a person would say they are the same topic.
<title>Fix CLS — three techniques</title><h1>Fix CLS with three techniques</h1>
UX Viewport
Effort: LowImpact: CriticalWhat this means. The viewport tag tells a phone how wide the layout is. Without it, the browser pretends the screen is a desktop, shrinks the page to fit, and text becomes tiny. Mobile Core Web Vitals and basic usability both fail from this one missing line.
How to fix it. Add the viewport meta tag with width=device-width and initial-scale=1. Do not disable zoom (do not set user-scalable=no or a maximum-scale of 1). One tag in the head is the whole fix.
<meta name="viewport" content="width=device-width, initial-scale=1" />
UX Images CLS
Effort: LowImpact: HighWhat this means. If an image has no width and height, the browser does not know how much space to reserve. The text jumps down when the image arrives. That jump is layout shift, and it is both annoying and a Core Web Vitals failure.
How to fix it. Set width and height on every image to its real pixel size. The browser will reserve the box and scale it with CSS. For the first picture on the page, use loading="eager" and fetchpriority="high" so it arrives sooner. Leave lazy loading for images below the fold.
<img src="/hero.webp" width="1200" height="630" loading="eager" fetchpriority="high" alt="Audit radar" />
CRO - CTAs
Effort: MediumImpact: HighWhat this means. A call to action is the next step: buy, try, book, read the guide. A page with none of them can only be bounced from. Two clear actions, one early and one after you have made the case, is enough. Ten identical buttons is not better.
How to fix it. Add at least two obvious links or buttons: one near the top, one after the proof or the explanation. Say what happens when they are clicked ("Run a free audit") rather than "Click here." Make them real links, not divs pretending to be buttons.
<a href="/audit">Run a free audit</a>
Content Experience - Scannable
Effort: MediumImpact: MediumWhat this means. Most people skim. A wall of equal paragraphs makes them leave before the useful part. Short blocks, a list, and one figure give the eye somewhere to land and make the page feel possible to finish.
How to fix it. Break anything longer than about four lines. Turn steps into a list. Add one image, chart, or code block with a caption. You are not dumbing the page down; you are showing its structure.
<p>Two lines of context.</p><ul><li>One concrete point</li></ul><figure><img src="/chart.webp" alt="Score distribution" width="800" height="450" /></figure>
Engagement - Related Search
Effort: MediumImpact: MediumWhat this means. When the page ends, the visit ends, unless you offer a next page or a way to search the site. Related links keep someone in your explanations instead of sending them back to Google, where a competitor is waiting.
How to fix it. Add a small "Related" group of two or three real pages on the same topic, plus a site search box if you have more than a handful of pages. Do not link to drafts or to the same URL they are already on.
<aside><h3>Related</h3><a href="/guides/geo/">Generative engine optimization</a></aside>
CSP Header
Effort: MediumImpact: CriticalWhat this means. Without a Content-Security-Policy, the browser trusts and runs any script injected into the page. If an attacker finds a reflection bug, or a third-party plugin is compromised, that script can skim form inputs, steal session tokens, or redirect visitors to a phishing page. The missing header is a browser safeguard, not a direct breach of the database.
How to fix it. Restrict scripts, styles, and connections to 'self' and verified third parties such as https://fonts.googleapis.com. Roll it out as Content-Security-Policy-Report-Only first so you can see blocked resources without breaking the site, then enforce the real header. Avoid unsafe-inline and unsafe-eval on scripts.
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com; img-src 'self' data: https:; font-src 'self' https://fonts.gstatic.com; object-src 'none'; base-uri 'self'; frame-ancestors 'none'
X-Frame-Options
Effort: LowImpact: HighWhat this means. Without a framing restriction, another site can embed yours in a transparent frame and line decoy buttons up underneath. A logged-in visitor thinks they are clicking the attacker's page. They are clicking yours: pay, change a setting, or submit a form. That is clickjacking, a browser trick, not a server login.
How to fix it. Send X-Frame-Options: DENY, or SAMEORIGIN if the site legitimately frames its own pages. Also set frame-ancestors 'none' or 'self' in the Content-Security-Policy. A star in frame-ancestors does not count as a fix.
X-Frame-Options: DENY
X-Content-Type-Options
Effort: LowImpact: MediumWhat this means. Browsers may sniff file bytes and ignore the Content-Type you sent. In a MIME confusion attack, someone uploads a file that looks like a harmless image but contains HTML and script. If the browser renders it as a page, that script runs as your site.
How to fix it. Send X-Content-Type-Options: nosniff on every response, including uploads and APIs, so the browser obeys the declared Content-Type and does not guess.
X-Content-Type-Options: nosniff
Referrer-Policy
Effort: LowImpact: MediumWhat this means. When a visitor clicks an outbound link, the browser can send the full previous URL in the Referer header. If that URL holds a private path, a password-reset token, or a search query, the next site and its logs receive it. This leaks data from the browser. It does not open the database.
How to fix it. Send Referrer-Policy: strict-origin-when-cross-origin. Other sites then receive only your origin, not the path or the query string. Same-site clicks can still see the path, which analytics usually want. Do not use unsafe-url.
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy
Effort: LowImpact: MediumWhat this means. An embedded widget or an injected script can ask for the camera, microphone, or location under your site's name. If you never use those devices, leaving them available only gives a compromised script a prompt to abuse.
How to fix it. Send Permissions-Policy: camera=(), microphone=(), geolocation=(). Empty parentheses mean nobody, including your own pages. If one path truly needs a device, allow it only there.
Permissions-Policy: camera=(), microphone=(), geolocation=()
Cookie Security Flags
Effort: LowImpact: HighWhat this means. A session cookie missing these flags is exposed three ways, all in the browser. Without Secure it can travel in cleartext if the browser ever hits http. Without HttpOnly, script can read it with document.cookie, so one XSS bug is an account takeover. Without SameSite, another site can attach it to a forged request.
How to fix it. Where the cookie is issued, set Secure, HttpOnly, and SameSite=Lax. Use Strict only when the cookie is never needed on a cross-site return such as an OAuth redirect. If nginx proxies the app, proxy_cookie_flags can add the same flags on the way out. A meta tag cannot do this.
Set-Cookie: session=abc; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=31536000
Mixed Content
Effort: LowImpact: HighWhat this means. The page is HTTPS, but something on it — a script, a stylesheet, an image — still loads over http. Browsers block the scripts (so features silently break) and warn on the rest. The lock icon goes away or shows as broken.
How to fix it. Search the HTML and CSS for http:// and change those addresses to https://. Check third-party widgets too. If a vendor has no HTTPS, drop them. A content-security-policy upgrade-insecure-requests directive can rewrite leftovers, but fixing the source is better.
<!-- Bad --> <script src="http://cdn.example/lib.js"></script><!-- Good --> <script src="https://cdn.example/lib.js"></script>
XSS - Inline Handlers
Effort: MediumImpact: HighWhat this means. Attributes like onclick, onload, and onerror are inline scripts. They are a favorite place to inject code, and a strict Content-Security-Policy cannot allow them without also allowing other inline scripts. One poisoned attribute runs as you.
How to fix it. Remove the on* attributes. Give the element an id or a data attribute and bind the behavior with addEventListener in a script file you control. The snippet shows the pattern. Frameworks that compile these away are fine; raw onclick in the HTML is not.
button.addEventListener("click", runAudit);
XSS - javascript: URLs
Effort: LowImpact: HighWhat this means. A link whose href starts with javascript: runs code when clicked. Attackers use that pattern because it looks like a URL. If any of that address comes from a comment, a query string, or a CMS field, they choose the code.
How to fix it. Delete every javascript: link. If you need a control that does not go anywhere, use a button. If it navigates, use a normal https URL. A Content-Security-Policy that blocks unsafe inline scripts is the safety net, not a substitute for removing these.
<button type="button">Run audit</button>
Exposed Sensitive Files
Effort: LowImpact: CriticalWhat this means. We requested paths like /.env and /.git/HEAD. If the server answers with the real file, your secrets, database password, or source history are public. That is not a style issue. Anyone can download them.
How to fix it. At the web server or CDN, deny dotfiles and common backup names and return 404. Then rotate every key that may have been in a file that was reachable: database URLs, API tokens, private keys. Confirm with a private request that /.env no longer returns content.
# Nginxlocation ~ /\. { deny all; return 404; }
Third-Party SRI
Effort: LowImpact: HighWhat this means. A script loaded from a CDN is only as trustworthy as that CDN today. If the file is swapped, your visitors run the new file. Subresource Integrity is a checksum: the browser refuses the script unless the bytes match what you expected.
How to fix it. On each third-party script and stylesheet, add an integrity hash and crossorigin="anonymous". Generate the hash from the exact file you intend to load (srihash.org or openssl). Pin a versioned URL, not "latest," or the hash will break on every upstream change.
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/app.js" integrity="sha384-..." crossorigin="anonymous"></script>
Outdated JS Libraries
Effort: MediumImpact: HighWhat this means. Old copies of libraries, especially jQuery 1.x through 3.3, contain cross-site scripting bugs that are already written up and easy to use. Leaving them in the page is leaving a known hole open.
How to fix it. Upgrade to a current release (jQuery 3.7.1 or newer if you still need it) or delete the library if the page no longer uses it. Check vendor bundles and old WordPress plugins, which often hide a second, older copy. Run npm audit or Retire.js so the next outdated file is caught in CI.
npm install jquery@3.7.1
CORS Wildcard
Effort: LowImpact: HighWhat this means. Access-Control-Allow-Origin: * means any website can read this response from a visitor's browser. That is fine for a truly public asset. Combined with cookies or other credentials, it lets another site read private data while the visitor is logged in.
How to fix it. If the response is private, echo one specific origin (https://your-app.example) instead of *. Add Vary: Origin so caches do not mix users. Only send Access-Control-Allow-Credentials when you truly need cookies, and never together with a star.
Access-Control-Allow-Origin: https://example.comVary: Origin
PHP Engine Version & EOL Status
Effort: MediumImpact: CriticalWhat this means. PHP only patches a branch for a few years. On November 5, 2026, 8.1 and anything older are already end of life. 8.2 gets security fixes only until December 31, 2026. An unpatched runtime is a standing invitation for bugs that are already written up.
How to fix it. Move the site to PHP 8.4 or 8.5, which still receive fixes. Then set expose_php off so the version is not advertised in X-Powered-By. Do this on the host, container, or control panel that actually runs PHP, then reload the page and confirm the header is gone.
; php.iniexpose_php = Off; Run PHP 8.4 or 8.5. 8.1 and older are end of life.
Critical Remote Code Execution (php2shell)
Effort: MediumImpact: CriticalWhat this means. Remote code execution means a stranger can run commands on your server. This scan does not fire exploit payloads. It only flags a public phpinfo page or an end-of-life PHP version, which is how those bugs get confirmed and used.
How to fix it. Upgrade PHP to a supported release and delete every public phpinfo page. In php.ini, turn off URL wrappers you do not need and disable shell functions. eval cannot be disabled this way, so do not pass visitor text to it. Restart PHP after the change.
expose_php = Offdisable_functions = exec, passthru, shell_exec, system, proc_open, popencgi.fix_pathinfo = 0
Exposed Configuration & Sensitive Backup Files
Effort: LowImpact: CriticalWhat this means. We request paths such as /.env, composer.lock, phpinfo.php, and common backups. If the server answers with the real file, passwords, source history, or exact dependency versions are public. Anyone can download them.
How to fix it. Deny dotfiles, backups, and lockfiles at the web server or CDN and return 404. Remove phpinfo.php. Then rotate every key that may have been in a reachable file: database URLs, API tokens, private keys. Confirm with a private request that /.env no longer returns content.
location ~ /\. {deny all;access_log off;log_not_found off;}
Strict Transport Security & SSL/TLS (HSTS)
Effort: LowImpact: HighWhat this means. A missing or short Strict-Transport-Security header lets the first visit stay on http. On a shared network an attacker can strip the upgrade to HTTPS and read the session in transit. That is a browser and network attack, not a SQL login.
How to fix it. Send max-age of at least one year, includeSubDomains, and preload. Confirm every subdomain serves HTTPS first, or visitors can get locked out. Submit the domain at hstspreload.org. The grouped nginx block in the Security cluster sets this with the other headers.
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;
Content Security Policy (CSP) & Header Hardening
Effort: MediumImpact: HighWhat this means. Without an allow-list, the browser executes any injected script or third-party file. A comment field, a poisoned ad, or a compromised plugin can then skim inputs and steal the session. The header is what stops that in the browser. It does not patch the database.
How to fix it. Start from default-src 'self', name the exact https hosts you use, and ban object-src. Do not add unsafe-inline. Use Content-Security-Policy-Report-Only until the reports are clean, then switch to the enforcing header.
add_header Content-Security-Policy "default-src 'self'; script-src 'self'; object-src 'none'; base-uri 'self'; frame-ancestors 'none'" always;
Outdated Third-Party CMS & Plugin Dependencies
Effort: MediumImpact: HighWhat this means. The generator tag and composer.lock tell the world which WordPress, Drupal, or library versions you run. Attackers match those numbers to published bugs. As of late 2026 the WordPress line that is still maintained is 7.1.4.
How to fix it. Update the CMS core, then themes and plugins, and remove the generator tag. Run composer audit in CI so a known-bad library fails the build. If composer.lock was reachable, treat those versions as public and patch them.
composer audit --format=json
Remote & Local File Inclusion (RFI/LFI)
Effort: MediumImpact: CriticalWhat this means. File inclusion means the server loads a path that a visitor chose. Local inclusion reads your files. Remote inclusion can fetch an attacker's script and run it. This scan does not send traversal payloads. It only flags PHP source that includes a query or form value.
How to fix it. Never pass visitor input to include or require. Set allow_url_include and allow_url_fopen off unless a specific feature needs the latter. Keep the list of allowed files in code, not in the query string.
allow_url_fopen = Offallow_url_include = Off
SQL Injection (SQLi) & Sanitization Posture
Effort: MediumImpact: CriticalWhat this means. A public database error usually means a query was glued together from visitor text and then failed. The error also leaks table names. This scan does not send SQL payloads. It only reports an error the page already printed.
How to fix it. Build every query with placeholders and bind the values, as in the snippet. Turn display_errors off in production so a failed query becomes a generic message, not a stack trace. Escape output separately. Quoting the input yourself is not a substitute.
$stmt = $pdo->prepare('SELECT * FROM scans WHERE domain = ?');$stmt->execute([$domain]);$result = $stmt->fetch();
Directory Listing & Information Disclosure
Effort: LowImpact: MediumWhat this means. If a folder has no index page and autoindex is on, the server prints every filename. That list becomes a map of backups, uploads, and old copies.
How to fix it. Turn directory indexes off. In nginx that is autoindex off, which is the default. In Apache use Options -Indexes. Add an index file only where you truly want a listing, and never on upload folders.
# Nginxautoindex off;# ApacheOptions -Indexes
Security Logging & Rate Limiting Posture
Effort: MediumImpact: HighWhat this means. A stack trace or PHP warning on a public page means display_errors is on. It names files and versions. Rate limits cannot be proven from one page load, so this check does not pretend to measure them. It only flags errors the page already showed.
How to fix it. Set display_errors off and log_errors on. Add a request limit on login and API routes so one client cannot guess passwords all day. Return a short generic message to the visitor and keep the detail in the log.
limit_req_zone $binary_remote_addr zone=scan_limit:10m rate=5r/s;# Inside the login location:limit_req zone=scan_limit burst=10 nodelay;
Cross-Site Request Forgery (CSRF) Protection
Effort: MediumImpact: HighWhat this means. A POST form changes something: a password, an email, a payment. If it has no secret token, another site can submit it while the visitor is logged in, and the browser will attach the session cookie.
How to fix it. Put a long random token in the session and in a hidden field on every POST form. Reject the request when they differ, using a constant-time compare. SameSite cookies help, but they are not a replacement for the token.
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {http_response_code(403);exit('CSRF validation failed.');}
Broken Authentication & Session Management
Effort: LowImpact: HighWhat this means. Session cookies without Secure, HttpOnly, and SameSite are stolen in the browser, not by guessing the database. Secure stops cleartext on http. HttpOnly stops document.cookie from reading the token after XSS. SameSite stops other sites from attaching the cookie to a forged request.
How to fix it. Set Secure, HttpOnly, and SameSite=Lax on every session cookie where it is created. Use Strict only if a login never has to survive a cross-site return. nginx can add the same flags with proxy_cookie_flags when it sits in front of the app.
session.cookie_httponly = 1session.cookie_secure = 1session.cookie_samesite = "Lax"
Insecure Deserialization Vulnerabilities
Effort: MediumImpact: CriticalWhat this means. unserialize turns a string into live objects. If that string comes from a visitor, they can choose the objects, and some of those run code when they are created. This is not visible on a normal public page, so the check passes unless the source itself shows the sink.
How to fix it. Do not unserialize visitor input. Accept JSON and decode it to arrays, as in the snippet. If you must keep old serialized sessions, sign them and refuse anything that fails the signature.
$data = json_decode($inputPayload, true, 512, JSON_THROW_ON_ERROR);
Server-Side Request Forgery (SSRF) Vectors
Effort: MediumImpact: HighWhat this means. Server-side request forgery is when your server fetches a URL a visitor supplied, including addresses that only the server can reach, such as cloud metadata or localhost. That cannot be seen from one public page. This scan does not ask your site to fetch those addresses.
How to fix it. If a feature must fetch a URL, allow only known hosts. Resolve the name and refuse private, loopback, and link-local addresses. Do this again on the connection, because the name can change between the check and the request.
$ip = gethostbyname($host);$flags = FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE;if (!filter_var($ip, FILTER_VALIDATE_IP, $flags)) {throw new RuntimeException('That address is not allowed.');}
XML External Entity (XXE) Injection Controls
Effort: MediumImpact: HighWhat this means. An XML parser that loads external entities will fetch or read files the attacker names, including local secrets. Parser settings are not visible on the public page, so this check passes unless you later expose them. Do not turn entity substitution on.
How to fix it. Load XML with external network access disabled. Do not pass LIBXML_NOENT or LIBXML_DTDLOAD. Those flags enable the substitution you are trying to prevent. Prefer JSON for anything a visitor sends.
libxml_use_internal_errors(true);$dom = new DOMDocument();$dom->loadXML($xml, LIBXML_NONET);libxml_clear_errors();
CORS Misconfigurations & Origin Leakage
Effort: LowImpact: HighWhat this means. Access-Control-Allow-Origin decides which other websites may read this response from a visitor's browser. A star means all of them. The value null is also unsafe, because a sandboxed frame can send it.
How to fix it. If the response is private, echo one specific origin you own. Never combine a star with credentials. Add Vary: Origin so caches do not mix users. A public file that truly has no secrets can stay a star.
if ($origin === 'https://example.com') {header('Access-Control-Allow-Origin: https://example.com');header('Vary: Origin');}
Broken Access Control & IDOR
Effort: MediumImpact: CriticalWhat this means. Insecure direct object reference means a URL contains an id, and the server returns that record without checking that this user owns it. Changing the id then reads someone else's data. A public homepage cannot show whether those checks exist.
How to fix it. On every read or write, look the record up by its id and by the signed-in user. If no row matches both, return 404. Do not rely on hiding the id. Do the check on the server, not only by disabling a button.
$stmt = $pdo->prepare('SELECT * FROM scans WHERE id = ? AND user_id = ?');$stmt->execute([$scanId, $_SESSION['user_id']]);if (!$stmt->fetch()) {http_response_code(404);exit;}
Weak Cryptographic Password Storage
Effort: MediumImpact: CriticalWhat this means. MD5 and SHA1 are not password hashes. They are fast, so a leaked database is cracked with a word list. Password hashes are not in the public HTML, so this check passes unless the page source itself shows a weak hash of a password.
How to fix it. Store passwords with password_hash, which uses bcrypt or Argon2. Check them with password_verify. When you next see an old hash at login, re-hash it with password_hash and save the new value.
$hash = password_hash($password, PASSWORD_DEFAULT);if (password_verify($password, $hash)) {// password matches}
Subresource Integrity (SRI) & Script Tampering
Effort: LowImpact: HighWhat this means. A script loaded from another site is only as trustworthy as that site today. If the file is swapped, your visitors run the new file. An integrity hash makes the browser refuse the script when the bytes change.
How to fix it. On each third-party script and stylesheet, add an integrity hash and crossorigin="anonymous". Hash the exact file you intend to load. Pin a versioned URL, not latest, or the hash breaks on the next upstream change.
integrity="sha384-..."crossorigin="anonymous"></script>
Clickjacking & Frame-Ancestors Protection
Effort: LowImpact: HighWhat this means. If another site can frame this page, it can align decoy buttons over your real ones. The visitor's click hits your account action while they look at the attacker's page. X-Frame-Options and frame-ancestors are the browser's refusal. They are not a server patch.
How to fix it. Send X-Frame-Options: DENY, or SAMEORIGIN when you frame your own pages. Also send frame-ancestors 'none' or 'self' in the content policy. A star allows every site and does not count.
add_header X-Frame-Options "DENY" always;add_header Content-Security-Policy "frame-ancestors 'none';" always;
Internal Links >=3
Effort: LowImpact: HighWhat this means. A page with no path to the rest of the site is an orphan. Search engines discover pages by following links, and a visitor who finishes reading has nowhere useful to go. One or two links is a start. Three puts the page in a real neighborhood.
How to fix it. Add at least three contextual links to other pages on this same site, inside sentences, not only in the footer. Point each one at the next question a reader would ask. Use the destination's topic as the link text, not "click here."
<a href="/guides/technical-seo/">Technical SEO guide</a><a href="/guides/aeo/">Answer-engine guide</a><a href="/about/">Who writes this</a>
Open Graph
Effort: LowImpact: MediumWhat this means. When someone pastes this URL into Slack, iMessage, LinkedIn, or X, those apps read og:title, og:description, and og:image. If any tag is missing, the preview is a blank box or a random sentence, and fewer people open the link.
How to fix it. Add all three tags in the head. Use the page title, a one-sentence description, and a real picture about 1200 by 630 pixels. The image address must be a full https URL, not a relative path.
<meta property="og:title" content="Page title people should see" /><meta property="og:description" content="One sentence that says what the page is for." /><meta property="og:image" content="https://example.com/og.png" /><meta property="og:url" content="https://example.com/page/" />
WordCount >1500
Effort: HighImpact: HighWhat this means. Short pages can rank when the question is tiny. Most topics are not. Under about 1,500 words there is usually not enough explanation, example, or evidence for a search result or an answer engine to prefer this page over a fuller one.
How to fix it. Expand only where you have something true to add: a worked example, a small table, two or three numbers you measured, and a short FAQ. Do not paste filler just to hit a word count.
<!-- Brief: 1500 words, 3 original stats, 1 table, 2 code blocks, FAQ -->
Has Table Code Img
Effort: MediumImpact: MediumWhat this means. A wall of paragraphs is hard to scan and hard to quote. A table, a code sample, or a second image gives people a place to land and gives other sites something specific worth linking to.
How to fix it. Add whichever of the three this page is missing. Use a table for a comparison, a code block for a snippet you would actually paste, and at least two images with alt text, width, and height. Skip a stock photo that says nothing.
<table><caption>What to add</caption><tr><th>Piece</th><th>Why</th></tr><tr><td>Table</td><td>A comparison someone can scan</td></tr></table><pre><code>curl -I https://example.com</code></pre>
E-E-A-T - About Author
Effort: MediumImpact: HighWhat this means. Pages that never say who is speaking are easy to ignore. A name, a credential, and a page about the organization are how readers, and the systems that quote the web, decide the advice is accountable to someone.
How to fix it. Link to an About page, and put the author's name on the article with one credential. A rel="author" link, an itemprop="author" byline, or a meta author tag all count. The about page should say who you are in plain language.
<a href="/about" rel="author">About the author</a><meta name="author" content="Ada Ortega" /><p itemprop="author">Ada Ortega, 10 years in technical SEO</p>
Freshness Signal
Effort: LowImpact: MediumWhat this means. A page with no date looks abandoned. Search and answer engines use a visible updated date when they decide whether this is still the advice to quote.
How to fix it. Show a time element with a machine-readable datetime where people can see it, and add article:modified_time in the head. Change the date only when the content actually changed.
<time datetime="2026-11-05">Updated Nov 5, 2026</time><meta property="article:modified_time" content="2026-11-05T12:00:00Z" />
Outbound Authority >=2
Effort: LowImpact: MediumWhat this means. A page that never cites anyone else looks like it has no sources. Two links to trustworthy sites, such as official docs or a standard, show a reader where to check the claim.
How to fix it. From the body, link at least two sources a skeptical reader would trust. Open them in a new tab only when you mean to, and then add rel="noopener". Do not link to random directories just to raise the count.
<p>Per <a href="https://web.dev/articles/vitals" rel="noopener">Web Vitals</a>,an LCP under 2.5 seconds is the good range. The<a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" rel="noopener">header reference</a>lists the ones this page should send.</p>
Run an audit to attach these notes to the checks that failed on a real page.
What this scan can and cannot see
Fifty notes. A colored number appears only when that check ran. Blue is strong, green is good, amber is watch, red is critical.
Nothing is filled in yet. Run a scan and these notes leave Pending.
One-page fetch
This scan reads the URL you entered, not every internal link.
Pending
Final status
The status after redirects. 200 means the document arrived.
Pending
Canonical
The page should name its own preferred URL.
Pending
Indexability
noindex hides the page from search.
Pending
Sitemap line
robots.txt should point at the sitemap.
Pending
FAQ schema
A FAQPage block is how an answer engine lifts a question.
Pending
Question headings
H2s phrased as questions are easier to cite.
Pending
Snippet length
A 40–60 word definition under the title is the snippet.
Pending
Lists after questions
A list or table after a question heading is easy to read aloud.
Pending
Answer at the top
The first 15% of the page should state the point.
Pending
llms.txt
A short markdown file at /llms.txt for agents.
Pending
Full llms file
llms-full.txt is the long version of the same idea.
Pending
Bot access
robots.txt should not block the common AI crawlers by accident.
Pending
Organization schema
The brand should be named as an Organization.
Pending
sameAs links
sameAs ties the brand to profiles you control.
Pending
Answer first
The opening should answer, then explain.
Pending
Quotable numbers
A statistic with a source is easier to cite than a vague claim.
Pending
Word count
Thin pages are harder to cite. The bar used here is 1,500 words.
Pending
Tables, code, images
A table, a code sample, or a real figure gives the page something to quote.
Pending
Outbound sources
At least two links to somewhere other than yourself.
Pending
Viewport
A missing viewport meta makes the phone layout guess.
Pending
Image space
Images without a size reserve push the page down as they load.
Pending
Next step
A clear action tells a person, and a model, what the page is for.
Pending
Scannable copy
Short blocks, a list, and a figure are easier to finish.
Pending
Title matches H1
The title and the H1 should promise the same thing.
Pending
HTTPS
The final URL should be https.
Pending
HSTS
A year-long HSTS header, with preload only when every subdomain is https.
Pending
Clickjacking
frame-ancestors or X-Frame-Options.
Pending
CORS
A star in Allow-Origin lets any site read the response.
Pending
Cookies
Secure, HttpOnly, and SameSite on cookies this response sets.
Pending
Open findings
Every check under 85 stays open. There is no accordion.
Pending
Copy a fix
Each snippet has a Copy button.
Pending
Paste HTML
If the host blocks the crawl, paste the source.
Pending
A thin black-on-white PDF of the misses.
Pending
JSON
The audit object, for your own notes.
Pending
Share link
The URL is put in ?url= so someone else can run the same page.
Pending
Markdown
A plain markdown summary of the clusters and the misses.
Pending
Title length
About 30 to 60 characters.
Pending
Meta description
About 120 to 155 characters.
Pending
One H1
A single H1 that matches the page.
Pending
Heading order
H2s without skipping a level.
Pending
Product schema
A product page should say so in JSON-LD.
Pending
Freshness
A visible date tells a reader, and a model, when this was true.
Pending
Author
An about link or an author name is the experience signal.
Pending
PHP version
Only if the server announces a PHP version.
Pending
Exposed files
.env, .git, and backups are probed and not stored.
Pending
Equal cluster weight
The overall is the average of the six clusters, not a hidden formula.
Pending
Auth header
You can send Authorization, Cookie, or X-Api-Key on the document fetch only.
Pending
Open Graph
Title, description, and image for link previews.
Pending
Warning note
If the overall is under 70, the alarm desk can preview a note.
Pending
Warning note
Save who should hear about a drop. Cluster cannot send the mail from here. The preview is the text. Copy it into your own mail or webhook.
Nothing is emailed. A saved row stays in this browser, and the preview below is the note you would send yourself.
Engine Optimization Matrix
Three clusters already scored above. These rows are those checks, not a live feed from Google, Perplexity, or an LLM.
SEO Telemetry
GEO Telemetry
AI Optimization
CRAWLER OBSERVATIONS
Google, Perplexity, and LLM crawlers
These lines are what this scan read in robots.txt, the page status, and llms.txt. They are not a private feed from those companies.
- Waiting for a scan.
CONTENT-SECURITY-POLICY
What the browser is allowed to run
Each directive below is explained on its own. The number is from the policy on this response.
Run a scan. The policy is read from the response header, then from a meta tag if the header is missing.
20-vector takeover resilience
Separate from the six-cluster grade. DNS checks leave this browser encrypted to Google or Cloudflare DNS-over-HTTPS, then fall back to the other if the first does not answer. That hides the lookup from the local network. It does not mean your visitors use DoH, and a resolver can still see the name. The page, headers, and secret-path probes come from the scan above. Nothing here sends exploit payloads, TRACE, or login attempts.
Waiting for a page scan. These 20 checks do not change the six-cluster score.
| AT-01 Subdomain takeover (dangling CNAME) | Not run |
|---|---|
| AT-02 Exposed .git and .env | Not run |
| AT-03 Third-party scripts without SRI | Not run |
| AT-04 CORS origin reflection | Not run |
| AT-05 Session cookie flags | Not run |
| AT-06 Content-Security-Policy | Not run |
| AT-07 Clickjacking (frame-ancestors) | Not run |
| AT-08 SPF and DMARC | Not run |
| AT-09 DNS CAA | Not run |
| AT-10 DNSSEC | Not run |
| AT-11 Exposed backups and wp-config | Not run |
| AT-12 Version fingerprints | Not run |
| AT-13 MIME sniffing | Not run |
| AT-14 Referrer policy | Not run |
| AT-15 Permissions-Policy | Not run |
| AT-16 Open redirect parameters | Not run |
| AT-17 Production source maps | Not run |
| AT-18 Public API schema links | Not run |
| AT-19 DOM XSS sinks | Not run |
| AT-20 Public admin and phpinfo | Not run |
Each row is one encrypted lookup. AD means the resolver vouched that DNSSEC checked out. An empty answer is still an answer. No response means both public resolvers failed, so the related vector stays partial instead of being marked clean.
No lookups yet.
Subdomain takeover (dangling CNAME)
CWE-350 · 8 ptsWhat an attacker can do
A CNAME left pointing at a cloud host you no longer own lets someone else claim that host and serve pages under your name.
How to fix it
Delete the DNS record before you delete the app. A live CNAME is only a lead until you confirm the resource is still yours.
; Remove the alias when the app is gone
Exposed .git and .env
CWE-538 · 8 ptsWhat an attacker can do
If /.git/HEAD or /.env answers with the real file, source history and secrets are public.
How to fix it
Deny dotfiles except /.well-known/, then rotate any key that was in a file you could fetch.
location ~ /\.(?!well-known).* {
deny all;
return 404;
}Third-party scripts without SRI
CWE-353 · 6 ptsWhat an attacker can do
A script from another host runs as your page. Without an integrity hash, a swapped file on that host runs for every visitor.
How to fix it
Add integrity and crossorigin="anonymous" on each third-party script. Pin a version, not latest.
<script src="https://cdn.example/[email protected]" integrity="sha384-..." crossorigin="anonymous"></script>
CORS origin reflection
CWE-942 · 6 ptsWhat an attacker can do
Access-Control-Allow-Origin: * lets any site read this response from a visitor's browser.
How to fix it
Echo one specific https origin, never a star, and send Vary: Origin.
add_header Access-Control-Allow-Origin "https://example.com" always; add_header Vary "Origin" always;
Session cookie flags
CWE-614 · 7 ptsWhat an attacker can do
A cookie without Secure can leak on plain http. Without HttpOnly, script can read it. Without SameSite, another site can send it.
How to fix it
Set Secure, HttpOnly, and SameSite=Lax or Strict where the cookie is created.
Set-Cookie: __Host-sid=abc; Path=/; Secure; HttpOnly; SameSite=Lax
Content-Security-Policy
CWE-693 · 7 ptsWhat an attacker can do
Without a CSP, injected markup can run script in your origin and read the session.
How to fix it
Send default-src 'self' and frame-ancestors 'none'. Add script sources only for hosts you mean to run.
add_header Content-Security-Policy "default-src 'self'; frame-ancestors 'none';" always;
Clickjacking (frame-ancestors)
CWE-1021 · 5 ptsWhat an attacker can do
Without a frame ban, another site can put yours in a transparent frame and steal the click.
How to fix it
Send X-Frame-Options: DENY and frame-ancestors 'none'.
add_header X-Frame-Options "DENY" always;
SPF and DMARC
CWE-290 · 6 ptsWhat an attacker can do
Without a hard-fail SPF and a DMARC reject or quarantine policy, anyone can send mail that claims to be from your domain.
How to fix it
End SPF with -all. Publish _dmarc with p=reject or p=quarantine.
_dmarc.example.com. IN TXT "v=DMARC1; p=reject; rua=mailto:[email protected]"
DNS CAA
CWE-295 · 4 ptsWhat an attacker can do
Without CAA, any certificate authority may issue a certificate for your name.
How to fix it
Publish CAA records that name only the authorities you use.
example.com. IN CAA 0 issue "letsencrypt.org"
DNSSEC
CWE-345 · 4 ptsWhat an attacker can do
An unsigned zone lets a resolver on the path forge the address. This check reads the authenticated-data bit from DNS-over-HTTPS. It does not prove every registrar setting.
How to fix it
Turn on DNSSEC at the DNS host and publish the DS record at the parent.
; Enable DNSSEC at the registrar and publish the DS record
Exposed backups and wp-config
CWE-530 · 6 ptsWhat an attacker can do
A leftover .bak, zip, or wp-config.php that answers 200 with the real file hands over the database password.
How to fix it
Remove those files from the web root and deny the extensions at the proxy.
location ~* \.(bak|sql|zip|tar|gz|save)$ { return 404; }Version fingerprints
CWE-200 · 3 ptsWhat an attacker can do
A generator tag or a versioned Server or X-Powered-By line tells scanners which bug list to use.
How to fix it
Turn server tokens off and remove the generator meta tag.
server_tokens off;
MIME sniffing
CWE-430 · 4 ptsWhat an attacker can do
Without nosniff, a browser may treat an uploaded text file as HTML and run it as your site.
How to fix it
Send X-Content-Type-Options: nosniff on every response.
add_header X-Content-Type-Options "nosniff" always;
Referrer policy
CWE-200 · 4 ptsWhat an attacker can do
A full Referer can carry reset tokens in the query string to the next site.
How to fix it
Send Referrer-Policy: strict-origin-when-cross-origin.
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
Permissions-Policy
CWE-276 · 3 ptsWhat an attacker can do
A frame or third-party script can ask for the camera or microphone unless you say no.
How to fix it
Send Permissions-Policy that disables the hardware you do not use.
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
Open redirect parameters
CWE-601 · 5 ptsWhat an attacker can do
A ?next= or ?redirect= that is not checked can send someone from your login URL to a lookalike.
How to fix it
Allow only a path on your own origin.
const safe = raw.startsWith("/") && !raw.startsWith("//") ? raw : "/";Production source maps
CWE-540 · 4 ptsWhat an attacker can do
A sourceMappingURL comment points at the original source, including comments and hidden routes.
How to fix it
Turn source maps off in the production build and deny .map on the public host.
location ~* \.map$ { return 404; }Public API schema links
CWE-200 · 4 ptsWhat an attacker can do
A link to GraphQL or Swagger on the public page is a map of your mutations. This scan does not send an introspection query.
How to fix it
Turn introspection off in production and require a login for API docs.
introspection: process.env.NODE_ENV !== "production"
DOM XSS sinks
CWE-79 · 3 ptsWhat an attacker can do
innerHTML, document.write, and eval turn text into code when that text comes from the URL or a message.
How to fix it
Use textContent for plain text. Sanitize anything that must be HTML.
element.textContent = untrustedInput;
Public admin and phpinfo
CWE-306 · 3 ptsWhat an attacker can do
A public phpinfo page or a linked admin path shows the stack and invites credential stuffing. This scan does not try passwords.
How to fix it
Remove phpinfo. Put admin routes behind a login or an allowlist.
location = /phpinfo.php { return 404; }DEEP CODE OBFUSCATION & CLOAKED INJECTOR AUDIT
Uncovering invisible parasites, zero-size frames, and unauthorized black-hat storefronts living inside your DOM.
Compromised pages often host a second business: an illicit casino, a rogue pharmacy, a CSS rule that parks links off-screen, a base64 loader, or a script that only fires for some visitors. This sweep walks the sitemap and every same-origin link it finds, including subfolders, up to 240 pages. It does not run those scripts, submit forms, or change the site. It cannot see localStorage, sessionStorage, or IndexedDB. A hit is something to remove, not proof of who put it there.
Idle. Nothing has been read yet.
VOLUME
| Hidden link | 0 |
|---|---|
| Hidden frame | 0 |
| Gambling | 0 |
| Unrelated spam | 0 |
| Injected loader | 0 |
| Sends people away | 0 |
- Hidden link0
- Hidden frame0
- Gambling0
- Unrelated spam0
- Injected loader0
- Sends people away0
Each arm is how many hits of that type. Zero stays at the center.
Every page that gets read shows up here. Only pages with a real hit open in full.