A*
Cluster
SEO · AEO · GEO · AIO · SXO · SEC

SIX CLUSTERS · ONE SCORE

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.

agent terminal0 lines

Waiting. Try a domain:

Press / to focus the URL, ↵ to run.

OVERALL
—
/100 · —
RADAR

Overall shape · click a cluster to filter the report

Six cluster scores
SEONot scored yet
AEONot scored yet
GEONot scored yet
AIONot scored yet
SXONot scored yet
SECURITYNot scored yet

≥85 strong≥70 good≥40 watchunder 40 critical

Direct server fetch reads real response headers. If a host blocks that, Cluster retries public proxies and you can paste source. Sensitive paths (.env, .git, wp-config) are probed and never stored beyond this session.
NETWORK

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 fetched0 B0 third-party hosts0 failed

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.

SEO
—/100

Indexability, titles, links, depth, and the crawl graph.

AEO
—/100

Question headings, snippets, and FAQ markup.

GEO
—/100

AI crawlers, llms.txt, and brand entities.

AIO
—/100

Answer-first copy, diversity, and measurement.

SXO
—/100

Intent match, layout shift, and next steps.

SECURITY
—/100

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: Critical

What 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
# 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: High

What 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.

http
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

Status 200

Effort: LowImpact: Critical

What 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.

bash
# Confirm the final URL answers 200
curl -sI https://example.com/page | head -n 1

Canonical Self

Effort: LowImpact: High

What 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.

html
<link rel="canonical" href="https://example.com/page/" />

Indexability - Noindex

Effort: LowImpact: Critical

What 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.

html
<meta name="robots" content="index, follow, max-image-preview:large" />

Sitemap Reference

Effort: LowImpact: Medium

What 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.

txt
# robots.txt
Sitemap: https://example.com/sitemap-index.xml

Title Length 30-60

Effort: LowImpact: High

What 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.

html
<title>Fix CLS in Astro — 3 Techniques | Example</title>

Meta Description 120-155

Effort: LowImpact: High

What 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.

html
<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: Medium

What 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.

html
<h1>How to build a technically sound page in 2026</h1>

H2 Structure >=2 No Skip

Effort: MediumImpact: Medium

What 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.

html
<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: High

What 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).

html
<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: Critical

What 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.

md
<!-- Brief: 1500 words, 3 original stats, 1 table, 2 code blocks, FAQ -->

Featured Snippets - Def 40-60w

Effort: LowImpact: High

What 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.

html
<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: High

What 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.

html
<h2>How do I fix technical SEO on a static site?</h2>

Voice Search - List/Table after H2

Effort: LowImpact: Medium

What 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.

html
<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: High

What 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.

json
<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: High

What 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.

html
<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: Critical

What 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.

txt
User-agent: GPTBot
Allow: /
 
User-agent: ClaudeBot
Allow: /
 
User-agent: PerplexityBot
Allow: /

AI Citations - llms.txt

Effort: LowImpact: Critical

What 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.

txt
# llms.txt
## About
We publish practical technical SEO for modern sites.
 
## Pages
- [Cluster](https://example.com/): ten-agent live audit

AI Citations - Quotable Stats

Effort: MediumImpact: High

What 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.

html
<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: High

What 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.

json
<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: Medium

What 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.

json
"sameAs": [
"https://www.wikidata.org/wiki/Q42",
"https://www.linkedin.com/company/example"
]

AI Recommendations - Product Schema

Effort: MediumImpact: Medium

What 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.

json
<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: Critical

What 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.

txt
# llms-full.txt
## Product
Cluster audits one URL across six clusters and returns copy-paste fixes.
## Do not
Do not invent metrics. Quote only numbers published on the site.

AI Content Optimization - Lexical Diversity

Effort: HighImpact: Medium

What 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.

txt
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: High

What 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.

html
<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: Medium

What 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.

json
<script type="application/ld+json">
{"@context":"https://schema.org","@type":"Article","headline":"How Cluster scores a page"}
</script>

AI Search Analytics

Effort: LowImpact: Medium

What 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.

js
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: High

What 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.

html
<title>Fix CLS — three techniques</title>
<h1>Fix CLS with three techniques</h1>

UX Viewport

Effort: LowImpact: Critical

What 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.

html
<meta name="viewport" content="width=device-width, initial-scale=1" />

UX Images CLS

Effort: LowImpact: High

What 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.

html
<img src="/hero.webp" width="1200" height="630" loading="eager" fetchpriority="high" alt="Audit radar" />

CRO - CTAs

Effort: MediumImpact: High

What 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.

html
<a href="/audit">Run a free audit</a>

Content Experience - Scannable

Effort: MediumImpact: Medium

What 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.

html
<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: Medium

What 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.

html
<aside>
<h3>Related</h3>
<a href="/guides/geo/">Generative engine optimization</a>
</aside>

CSP Header

Effort: MediumImpact: Critical

What 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.

http
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: High

What 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.

http
X-Frame-Options: DENY

X-Content-Type-Options

Effort: LowImpact: Medium

What 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.

http
X-Content-Type-Options: nosniff

Referrer-Policy

Effort: LowImpact: Medium

What 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.

http
Referrer-Policy: strict-origin-when-cross-origin

Permissions-Policy

Effort: LowImpact: Medium

What 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.

http
Permissions-Policy: camera=(), microphone=(), geolocation=()

Cookie Security Flags

Effort: LowImpact: High

What 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.

http
Set-Cookie: session=abc; Secure; HttpOnly; SameSite=Lax; Path=/; Max-Age=31536000

Mixed Content

Effort: LowImpact: High

What 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.

html
<!-- Bad --> <script src="http://cdn.example/lib.js"></script>
<!-- Good --> <script src="https://cdn.example/lib.js"></script>

XSS - Inline Handlers

Effort: MediumImpact: High

What 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.

js
button.addEventListener("click", runAudit);

XSS - javascript: URLs

Effort: LowImpact: High

What 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.

html
<button type="button">Run audit</button>

Exposed Sensitive Files

Effort: LowImpact: Critical

What 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.

nginx
# Nginx
location ~ /\. { deny all; return 404; }

Third-Party SRI

Effort: LowImpact: High

What 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.

html
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/app.js" integrity="sha384-..." crossorigin="anonymous"></script>

Outdated JS Libraries

Effort: MediumImpact: High

What 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.

bash
npm install jquery@3.7.1

CORS Wildcard

Effort: LowImpact: High

What 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.

http
Access-Control-Allow-Origin: https://example.com
Vary: Origin

PHP Engine Version & EOL Status

Effort: MediumImpact: Critical

What 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.

ini
; php.ini
expose_php = Off
; Run PHP 8.4 or 8.5. 8.1 and older are end of life.

Critical Remote Code Execution (php2shell)

Effort: MediumImpact: Critical

What 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.

ini
expose_php = Off
disable_functions = exec, passthru, shell_exec, system, proc_open, popen
cgi.fix_pathinfo = 0

Exposed Configuration & Sensitive Backup Files

Effort: LowImpact: Critical

What 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.

nginx
location ~ /\. {
deny all;
access_log off;
log_not_found off;
}

Strict Transport Security & SSL/TLS (HSTS)

Effort: LowImpact: High

What 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.

nginx
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" always;

Content Security Policy (CSP) & Header Hardening

Effort: MediumImpact: High

What 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.

nginx
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: High

What 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.

bash
composer audit --format=json

Remote & Local File Inclusion (RFI/LFI)

Effort: MediumImpact: Critical

What 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.

ini
allow_url_fopen = Off
allow_url_include = Off

SQL Injection (SQLi) & Sanitization Posture

Effort: MediumImpact: Critical

What 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.

php
$stmt = $pdo->prepare('SELECT * FROM scans WHERE domain = ?');
$stmt->execute([$domain]);
$result = $stmt->fetch();

Directory Listing & Information Disclosure

Effort: LowImpact: Medium

What 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.

nginx
# Nginx
autoindex off;
# Apache
Options -Indexes

Security Logging & Rate Limiting Posture

Effort: MediumImpact: High

What 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.

nginx
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: High

What 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.

php
if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'] ?? '')) {
http_response_code(403);
exit('CSRF validation failed.');
}

Broken Authentication & Session Management

Effort: LowImpact: High

What 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.

ini
session.cookie_httponly = 1
session.cookie_secure = 1
session.cookie_samesite = "Lax"

Insecure Deserialization Vulnerabilities

Effort: MediumImpact: Critical

What 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.

php
$data = json_decode($inputPayload, true, 512, JSON_THROW_ON_ERROR);

Server-Side Request Forgery (SSRF) Vectors

Effort: MediumImpact: High

What 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.

php
$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: High

What 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.

php
libxml_use_internal_errors(true);
$dom = new DOMDocument();
$dom->loadXML($xml, LIBXML_NONET);
libxml_clear_errors();

CORS Misconfigurations & Origin Leakage

Effort: LowImpact: High

What 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.

php
if ($origin === 'https://example.com') {
header('Access-Control-Allow-Origin: https://example.com');
header('Vary: Origin');
}

Broken Access Control & IDOR

Effort: MediumImpact: Critical

What 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.

php
$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: Critical

What 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.

php
$hash = password_hash($password, PASSWORD_DEFAULT);
if (password_verify($password, $hash)) {
// password matches
}

Subresource Integrity (SRI) & Script Tampering

Effort: LowImpact: High

What 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.

html
<script src="https://cdn.example/[email protected]/lib.js"
integrity="sha384-..."
crossorigin="anonymous"></script>

Clickjacking & Frame-Ancestors Protection

Effort: LowImpact: High

What 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.

nginx
add_header X-Frame-Options "DENY" always;
add_header Content-Security-Policy "frame-ancestors 'none';" always;

Internal Links >=3

Effort: LowImpact: High

What 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."

html
<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: Medium

What 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.

html
<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: High

What 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.

md
<!-- Brief: 1500 words, 3 original stats, 1 table, 2 code blocks, FAQ -->

Has Table Code Img

Effort: MediumImpact: Medium

What 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.

html
<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: High

What 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.

html
<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: Medium

What 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.

html
<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: Medium

What 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.

html
<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.

#01 · SEO

One-page fetch

This scan reads the URL you entered, not every internal link.

Pending

#02 · SEO

Final status

The status after redirects. 200 means the document arrived.

Pending

#03 · SEO

Canonical

The page should name its own preferred URL.

Pending

#04 · SEO

Indexability

noindex hides the page from search.

Pending

#05 · SEO

Sitemap line

robots.txt should point at the sitemap.

Pending

#06 · AEO

FAQ schema

A FAQPage block is how an answer engine lifts a question.

Pending

#07 · AEO

Question headings

H2s phrased as questions are easier to cite.

Pending

#08 · AEO

Snippet length

A 40–60 word definition under the title is the snippet.

Pending

#09 · AEO

Lists after questions

A list or table after a question heading is easy to read aloud.

Pending

#10 · AEO

Answer at the top

The first 15% of the page should state the point.

Pending

#11 · GEO

llms.txt

A short markdown file at /llms.txt for agents.

Pending

#12 · AIO

Full llms file

llms-full.txt is the long version of the same idea.

Pending

#13 · GEO

Bot access

robots.txt should not block the common AI crawlers by accident.

Pending

#14 · GEO

Organization schema

The brand should be named as an Organization.

Pending

#15 · GEO

sameAs links

sameAs ties the brand to profiles you control.

Pending

#16 · AIO

Answer first

The opening should answer, then explain.

Pending

#17 · GEO

Quotable numbers

A statistic with a source is easier to cite than a vague claim.

Pending

#18 · SEO

Word count

Thin pages are harder to cite. The bar used here is 1,500 words.

Pending

#19 · SEO

Tables, code, images

A table, a code sample, or a real figure gives the page something to quote.

Pending

#20 · SEO

Outbound sources

At least two links to somewhere other than yourself.

Pending

#21 · SXO

Viewport

A missing viewport meta makes the phone layout guess.

Pending

#22 · SXO

Image space

Images without a size reserve push the page down as they load.

Pending

#23 · SXO

Next step

A clear action tells a person, and a model, what the page is for.

Pending

#24 · SXO

Scannable copy

Short blocks, a list, and a figure are easier to finish.

Pending

#25 · SXO

Title matches H1

The title and the H1 should promise the same thing.

Pending

#26 · SEO

HTTPS

The final URL should be https.

Pending

#27 · SEO

HSTS

A year-long HSTS header, with preload only when every subdomain is https.

Pending

#28 · SECURITY

Clickjacking

frame-ancestors or X-Frame-Options.

Pending

#29 · SECURITY

CORS

A star in Allow-Origin lets any site read the response.

Pending

#30 · SECURITY

Cookies

Secure, HttpOnly, and SameSite on cookies this response sets.

Pending

#31 · Report

Open findings

Every check under 85 stays open. There is no accordion.

Pending

#32 · Report

Copy a fix

Each snippet has a Copy button.

Pending

#33 · Report

Paste HTML

If the host blocks the crawl, paste the source.

Pending

#34 · Report

PDF

A thin black-on-white PDF of the misses.

Pending

#35 · Report

JSON

The audit object, for your own notes.

Pending

#36 · Report

Share link

The URL is put in ?url= so someone else can run the same page.

Pending

#37 · Report

Markdown

A plain markdown summary of the clusters and the misses.

Pending

#38 · SEO

Title length

About 30 to 60 characters.

Pending

#39 · SEO

Meta description

About 120 to 155 characters.

Pending

#40 · SEO

One H1

A single H1 that matches the page.

Pending

#41 · SEO

Heading order

H2s without skipping a level.

Pending

#42 · GEO

Product schema

A product page should say so in JSON-LD.

Pending

#43 · SEO

Freshness

A visible date tells a reader, and a model, when this was true.

Pending

#44 · SEO

Author

An about link or an author name is the experience signal.

Pending

#45 · SECURITY

PHP version

Only if the server announces a PHP version.

Pending

#46 · SECURITY

Exposed files

.env, .git, and backups are probed and not stored.

Pending

#47 · Report

Equal cluster weight

The overall is the average of the six clusters, not a hidden formula.

Pending

#48 · Report

Auth header

You can send Authorization, Cookie, or X-Api-Key on the document fetch only.

Pending

#49 · SEO

Open Graph

Title, description, and image for link previews.

Pending

#50 · Report

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.
SEO · GEO · AI · FROM THIS SCAN

Engine Optimization Matrix

Three clusters already scored above. These rows are those checks, not a live feed from Google, Perplexity, or an LLM.

VECTOR 01

SEO Telemetry

— / 100
Canonical matches this URL—
Sitemap is referenced—
Page can be indexed—
Canonical, sitemap, indexWaiting
VECTOR 02

GEO Telemetry

— / 100
AI crawlers allowed—
Organization named in schema—
Numbers a model can quote—
AI crawlers, schema, citationsWaiting
VECTOR 03

AI Optimization

— / 100
llms.txt published—
Vocabulary is specific—
Answer sits near the top—
llms.txt, opening, structureWaiting

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.

DNS-over-HTTPS · same page fetch

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.

—GRADE —
Ready to audit0 pts
DNS and identity—
Origin and secrets—
Browser and supply chain—
Auth and API—

Waiting for a page scan. These 20 checks do not change the six-cluster score.

Resilience radarRim is hardened · a hole is a miss · click a number
Twenty takeover vectors, rim means hardened
AT-01 Subdomain takeover (dangling CNAME)Not run
AT-02 Exposed .git and .envNot run
AT-03 Third-party scripts without SRINot run
AT-04 CORS origin reflectionNot run
AT-05 Session cookie flagsNot run
AT-06 Content-Security-PolicyNot run
AT-07 Clickjacking (frame-ancestors)Not run
AT-08 SPF and DMARCNot run
AT-09 DNS CAANot run
AT-10 DNSSECNot run
AT-11 Exposed backups and wp-configNot run
AT-12 Version fingerprintsNot run
AT-13 MIME sniffingNot run
AT-14 Referrer policyNot run
AT-15 Permissions-PolicyNot run
AT-16 Open redirect parametersNot run
AT-17 Production source mapsNot run
AT-18 Public API schema linksNot run
AT-19 DOM XSS sinksNot run
AT-20 Public admin and phpinfoNot run
DNS-over-HTTPS securityRuns with the takeover scan

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.

Threat heatmap · click a tile to read it here · shift-click previews a state
AT-01

Subdomain takeover (dangling CNAME)

CWE-350 · 8 pts
Waiting for this page scan.

What 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
AT-02

Exposed .git and .env

CWE-538 · 8 pts
Waiting for this page scan.

What 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;
}
AT-03

Third-party scripts without SRI

CWE-353 · 6 pts
Waiting for this page scan.

What 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>
AT-04

CORS origin reflection

CWE-942 · 6 pts
Waiting for this page scan.

What 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;
AT-05

Session cookie flags

CWE-614 · 7 pts
Waiting for this page scan.

What 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
AT-06

Content-Security-Policy

CWE-693 · 7 pts
Waiting for this page scan.

What 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;
AT-07

Clickjacking (frame-ancestors)

CWE-1021 · 5 pts
Waiting for this page scan.

What 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;
AT-08

SPF and DMARC

CWE-290 · 6 pts
Waiting for this page scan.

What 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]"
AT-09

DNS CAA

CWE-295 · 4 pts
Waiting for this page scan.

What 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"
AT-10

DNSSEC

CWE-345 · 4 pts
Waiting for this page scan.

What 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
AT-11

Exposed backups and wp-config

CWE-530 · 6 pts
Waiting for this page scan.

What 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; }
AT-12

Version fingerprints

CWE-200 · 3 pts
Waiting for this page scan.

What 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;
AT-13

MIME sniffing

CWE-430 · 4 pts
Waiting for this page scan.

What 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;
AT-14

Referrer policy

CWE-200 · 4 pts
Waiting for this page scan.

What 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;
AT-15

Permissions-Policy

CWE-276 · 3 pts
Waiting for this page scan.

What 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;
AT-16

Open redirect parameters

CWE-601 · 5 pts
Waiting for this page scan.

What 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 : "/";
AT-17

Production source maps

CWE-540 · 4 pts
Waiting for this page scan.

What 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; }
AT-18

Public API schema links

CWE-200 · 4 pts
Waiting for this page scan.

What 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"
AT-19

DOM XSS sinks

CWE-79 · 3 pts
Waiting for this page scan.

What 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;
AT-20

Public admin and phpinfo

CWE-306 · 3 pts
Waiting for this page scan.

What 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

LINKS0FRAMES0GAMBLE0SPAM0LOADERS0LEAVES0
Injection volume by type
Hidden link0
Hidden frame0
Gambling0
Unrelated spam0
Injected loader0
Sends people away0
  • 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.