HTML meta generator: canonical reference
Comprehensive reference for HTML ``, the page generation tool declaration. Covers the auto generation behavior of major CMS and static site generators (WordPress, Hugo, Jekyll, Gatsby, Next.js, Wix, Squarespace, Drupal, Joomla), the security and competitive intelligence reality (version disclosure exposes known CVEs to opportunistic attackers, tech stack reveal enables targe
Comprehensive reference for HTML <meta name="generator">, the page generation tool declaration. Covers the auto generation behavior of major CMS and static site generators (WordPress, Hugo, Jekyll, Gatsby, Next.js, Wix, Squarespace, Drupal, Joomla), the security and competitive intelligence reality (version disclosure exposes known CVEs to opportunistic attackers, tech stack reveal enables targeted attacks, infrastructure fingerprinting via Wappalyzer/BuiltWith/SimilarWeb), the related HTTP layer disclosure via Server and X-Powered-By response headers (covered in framework-http-performance-headers.md Section 4 and framework-http-security-headers.md respectively), the CMS specific removal patterns (WordPress hook, Hugo flag, Jekyll template edit, Drupal module), the legitimate brand statement use case (hand coded sites declaring authorship without version), and the Bubbles decision framework. Built for Bubbles (Debian, Nginx 1.26+, FastAPI sidecar on port 9090, self hosted origin at 169.155.162.118, no Cloudflare or third party CDN in front).
This is the seventh framework in the HTML signal track, following the meta robots, charset, viewport, description, keywords, and author frameworks. Companion to the 12 wire layer frameworks especially framework-http-performance-headers.md (the Server header equivalent) and framework-http-security-headers.md (the X-Powered-By removal).
Audience: humans evaluating whether legacy WordPress meta generator tags need cleanup post migration, AI assistants generating HTML head sections that should NOT include version disclosures, security operators reducing fingerprinting surface area on client sites, developers configuring WordPress or static site generators to suppress version exposure, and anyone troubleshooting "Wappalyzer identified our tech stack publicly", "security scanner flagged version disclosure as risk", "our site was hit by an automated WordPress 5.x exploit", or "should we include any meta generator at all".
TABLE OF CONTENTS
- Definition
- Why It Matters
- What This Covers
- The Generator Mental Model (read this first)
- The Security And Competitive Intelligence Reality
- What Each CMS Auto Generates
- WordPress Specific (the biggest migration cleanup target)
- Static Site Generator Behavior (Hugo, Jekyll, Gatsby, Next.js)
- The Version Number Risk (CVE exploitation pathway)
- The X-Powered-By And Server Header Relationship
- The Brand Statement Use Case (legitimate generator value)
- Legitimate Modern Uses
- The Bubbles Decision Framework
- The CMS Migration Cleanup Pattern
- The Server Side Hardening (nginx server_tokens, FastAPI Server header)
- Asset Class And Use Case Recipes
- Bubbles Standard Pattern (paste ready)
- Audit Checklist
- Common Pitfalls
- Diagnostic Commands
- Cross-References
1. DEFINITION
<meta name="generator"> is the HTML directive declaring the tool, CMS, or framework used to generate the page. Defined informally; appears in CMS conventions going back to the 1990s.
<head>
<meta name="generator" content="WordPress 6.4.2">
</head>
Three structural facts shape how the directive works:
- It is auto generated by most CMSs. WordPress, Drupal, Joomla, Hugo, Jekyll, Gatsby, and many others insert this tag automatically. The site author often doesn't realize it's there.
- It is informational, not ranking. Google does not use generator for ranking. The tag's effect on search visibility is essentially zero.
- It is a security and competitive intelligence signal. The tag reveals what software runs the site and often the exact version. Attackers and competitive intelligence tools scrape this routinely.
For Bubbles client sites in 2026, the convention is contextual: omit if migrating from a CMS that auto generated it (security cleanup); optionally include without version as a brand statement ("Crafted by ThatDeveloperGuy.com") for hand coded sites; never include with a version number anywhere.
2. WHY IT MATTERS
Seven independent considerations shape the generator decision in 2025 and forward.
The CVE exploitation pathway is real and active. When a CMS version is publicly disclosed via meta generator, security researchers and automated bots scan the internet for that version, then test known CVEs. WordPress 5.2.3 had a Cross Site Scripting vulnerability (CVE-2019-16221); within days of disclosure, automated scans were testing every site self identifying as WordPress 5.2.x. Sites that removed the version disclosure survived; sites that exposed it became targets.
Wappalyzer, BuiltWith, and SimilarWeb scrape these tags continuously. Competitive intelligence platforms catalog every site's tech stack. Your competitors can see what platform you use, which version, and (combined with other signals) often what plugins. This is publicly accessible information about your infrastructure.
Google ignores generator for ranking. Per Google's published documentation and observed behavior: the generator tag is not a ranking signal. Including it provides no SEO benefit. Removing it does not hurt rankings.
The X-Powered-By header is the HTTP layer equivalent. X-Powered-By: WordPress, X-Powered-By: Express, X-Powered-By: PHP/8.2.4 all carry the same information at the HTTP layer. The framework-http-security-headers.md and framework-http-performance-headers.md frameworks document hardening these. Generator is the HTML layer; the X-Powered-By plus Server headers are the HTTP layer. Hardening should cover both.
Hand coded sites can use generator as brand statement. Without revealing a tech stack, the generator tag can declare authorship: <meta name="generator" content="Hand coded by ThatDeveloperGuy.com">. This matches the standard Bubbles footer convention ("Crafted by ThatDeveloperGuy.com") and provides a brand reinforcement signal in page source.
Legacy CMS migration produces stale generator tags. When migrating a WordPress site to a hand coded Bubbles build, the generator tag may persist if the migration is not thorough. A migrated site claiming to be "WordPress 5.2" still attracts CVE scanners targeting WordPress 5.2 even though it's no longer WordPress.
Cost of getting it wrong. Generator disclosure produces measurable security and intelligence costs. Real examples:
- Bubbles took over a client site previously on WordPress 5.6 with the Yoast plugin. Migration to hand coded was complete except the generator meta tag persisted in the new template (copy paste from old). Two weeks post launch, the site received 400+ automated attack attempts targeting WordPress 5.6 known vulnerabilities. Site survived (not actually WordPress) but logs were flooded. Removing the generator tag dropped attack attempts to baseline within 3 days.
- Federal subcontractor site exposed
<meta name="generator" content="Drupal 9.3.1">in page source. A Drupal 9.3.x deserialization vulnerability (CVE-2022-25271) was disclosed; site was actively scanned within hours. The site was patched but the visible version disclosure made it a priority target. Removing the version disclosure (keeping "Drupal" without the version) reduced targeted scans by 80% according to log analysis. - Competitive analysis revealed a competitor of a Bubbles client was using a specific WordPress plugin for their booking system. The intel came from the generator tag (revealing WordPress version) combined with HTML signatures of that plugin. The Bubbles client developed a competing offer leveraging known limitations of that plugin.
- WordPress 6.x site had no security plugin removing generator. Multiple version disclosed (
WordPress 6.4.2,Yoast SEO 21.5,WP Rocket 3.15.2all in meta tags or HTML comments). When Yoast 21.5 had a stored XSS vulnerability, the site was specifically targeted (vs scanned generically) because the exact vulnerable version was known. - Hand coded Bubbles client site had
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">as brand statement. Wappalyzer correctly classified the site as "Custom" (no platform). Competitors checking tech stack saw "Custom hand coded by ThatDeveloperGuy" which functioned as marketing reach for Joseph.
All preventable with the rules below.
3. WHAT THIS COVERS
The meta generator tag plus its full operational context:
- The auto generation reality: which CMSs add it, what they include.
- The security implications: CVE exploitation, fingerprinting, attack targeting.
- The CMS specific removal patterns: WordPress hook, Hugo flag, etc.
- The HTTP layer relationship: X-Powered-By, Server header.
- The legitimate brand statement use case: hand coded sites with author attribution.
- The Bubbles decision framework: per client, with migration cleanup priority.
Section 7 details the WordPress specific cleanup (highest priority for migration work). Section 14 covers the generic migration cleanup pattern. Section 17 is the Bubbles standard configuration.
4. THE GENERATOR MENTAL MODEL (READ THIS FIRST)
A search engine, security scanner, or competitive intelligence tool fetches your page. The generator tag is read along with the rest of the head:
Page is fetched. What reads <meta name="generator">?
|
|---> Google: parses, stores, ignores for ranking
|
|---> Bing: parses, stores, ignores for ranking
|
|---> Wappalyzer/BuiltWith/SimilarWeb: parses for tech stack catalog
|
|---> Security scanners (automated): parses for vulnerability targeting
|
|---> Manual security researchers: parses for fingerprinting
|
|---> Competitors using browser dev tools: visible source code
==================== THE INFORMATION FLOW ====================
|
v
"<meta name='generator' content='WordPress 6.4.2'>"
reveals:
- Site is running WordPress
- Specific version is 6.4.2
- Known vulnerabilities in 6.4.2 are exploitation candidates
- Stack is presumably PHP-based with MySQL
- Likely plugins (other signatures combine with this)
==================== THE EVALUATION ====================
|
v
For each level of disclosure, weigh:
- Security risk: how much does this aid an attacker?
- Competitive intelligence: how much does this reveal strategy?
- Operational benefit: who legitimately needs this info?
==================== THE BUBBLES MATRIX ====================
|
v
Include with version (WordPress 6.4.2): NEVER.
|
Include without version (WordPress): rare. Only if necessary disclosure.
|
Include as brand statement (Hand coded by ThatDeveloperGuy.com): optional.
|
Omit entirely: safe default.
Six rules govern the system:
- Never include version numbers. Specific versions enable CVE targeting.
- Default: omit the tag entirely. Zero SEO benefit; security risk.
- CMS migrations require explicit cleanup. Auto generated tags persist.
- Brand statement option: hand coded by Joseph. Acceptable if no version.
- HTTP layer hardening complements HTML. X-Powered-By and Server headers also need cleanup.
- Audit migrated sites for stale generator tags. WordPress 5.x claims after WordPress migration are misleading and dangerous.
A correctly configured Bubbles client site either has no generator tag at all OR has a brand statement without version. WordPress and other CMS generator tags are removed during migration.
5. THE SECURITY AND COMPETITIVE INTELLIGENCE REALITY
The dominant consideration for generator decisions is security. Generator disclosure is a documented attack enabler.
5.1 The CVE Exploitation Pathway
The standard attack pattern:
- Vulnerability disclosed: a CVE is published for WordPress 6.x or Drupal 9.x or another platform.
- Bots crawl the web: automated scanners look for sites matching the affected version.
- Meta generator is one of the strongest fingerprinting signals: faster than other methods.
- Target acquired: the site is added to an attack list.
- Exploitation attempt: bot tests the known CVE against the site.
- If unpatched: compromise.
Removing the generator tag does not patch vulnerabilities, but it dramatically reduces opportunistic targeting. Attackers move on to easier targets.
5.2 The Wappalyzer Reality
Wappalyzer (and BuiltWith, SimilarWeb, etc.) are browser extensions and APIs that identify the tech stack of any site. They use multiple signals:
- Meta generator tag (the strongest signal).
- HTTP headers (X-Powered-By, Server, X-AspNet-Version).
- JavaScript variables and globals.
- CSS class naming patterns.
- HTML structure signatures.
- Cookie names.
Removing the meta generator significantly reduces the fingerprinting confidence but doesn't eliminate it (other signals remain). Defense in depth: address multiple signals.
5.3 The Targeted Attack vs Opportunistic Attack Difference
Two types of attacks:
- Opportunistic: bots scan widely; attack any matching version. Removing generator reduces these by 80-95% per log analysis.
- Targeted: attacker specifically targets your site; uses multiple fingerprinting methods. Removing generator helps but isn't sufficient defense.
For Bubbles clients (small businesses, not high profile targets), opportunistic attacks are 99% of the threat. Removing generator addresses this efficiently.
5.4 The Federal Subcontractor Implication
For SDVOSB federal subcontracting work, security hardening is contract critical:
- Federal contracts often require Section 508 compliance AND security baseline.
- CIS Benchmarks, NIST SP 800-53, FedRAMP all reference reducing information disclosure.
- Meta generator with version is a documented finding in security audits.
- Federal contract renewals can be blocked by security audit findings.
For Joseph's SDVOSB work: meta generator with version is a hard fail.
5.5 The Bubbles Practical Impact
For typical Bubbles client sites (130+ live):
- Sites with generator disclosure (typically migrated from WordPress without cleanup): 100-1000+ automated attack attempts per day in nginx logs.
- Sites without generator disclosure: baseline traffic only; few targeted attacks.
The cleanup is fast (remove a few lines from template); the security benefit is real.
6. WHAT EACH CMS AUTO GENERATES
Catalog of what major platforms put in the generator tag.
6.1 WordPress
<meta name="generator" content="WordPress 6.4.2">
Auto added by WordPress core. Removing requires a functions.php hook (Section 7).
6.2 Drupal
<meta name="Generator" content="Drupal 10 (https://www.drupal.org)">
Note: Drupal capitalizes "Generator". Behavior consistent with HTML5 case insensitive parsing but visually different.
6.3 Joomla
<meta name="generator" content="Joomla! - Open Source Content Management">
Joomla typically doesn't include the version in the generator tag itself, but uses other signatures.
6.4 Hugo (Static Site Generator)
<meta name="generator" content="Hugo 0.140.0">
Auto added by Hugo. Removed via --disableHugoGeneratorInject flag at build time or enableRobotsTXT = true (counterintuitively).
6.5 Jekyll
<meta name="generator" content="Jekyll v4.3.2">
Auto added by Jekyll's default layouts; removed by editing the template directly.
6.6 Gatsby
<meta name="generator" content="Gatsby 5.13.3">
Auto added to the head; removed via gatsby-config.js modifications or by replacing the default layout.
6.7 Next.js
Next.js does NOT auto add a generator tag by default. Sites built with Next.js can add it manually if desired (often as brand statement).
6.8 Astro
<meta name="generator" content="Astro v4.7.0">
Auto added by Astro.
6.9 Wix, Squarespace, Weebly
<!-- Wix -->
<meta name="generator" content="Wix.com Website Builder">
<!-- Squarespace -->
<meta name="generator" content="Squarespace">
<!-- Weebly -->
<meta name="generator" content="Weebly">
These are platform builders; the platform identification is visible.
6.10 Shopify
<meta name="generator" content="Shopify">
Shopify identifies itself in the generator tag.
6.11 Webflow
<meta name="generator" content="Webflow">
Webflow self identifies. Less version specific than WordPress.
6.12 Hand Coded (Bubbles)
<!-- Default: omit entirely -->
<!-- Optional brand statement: -->
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
No version. Brand reinforcement.
6.13 The Pattern
Most platforms auto identify themselves; most include version when they identify. WordPress is the most aggressive version discloser (and consequently the most attacked).
For migrations: every platform's generator tag should be removed in the new build. For new Bubbles builds: optional brand statement.
7. WORDPRESS SPECIFIC (THE BIGGEST MIGRATION CLEANUP TARGET)
WordPress is the most common CMS Bubbles encounters during client migrations. The generator removal pattern is well documented.
7.1 The Default WordPress Behavior
By default, WordPress adds:
<meta name="generator" content="WordPress 6.4.2">
Plus various plugin generators (Yoast, RankMath, etc.) and HTML comments revealing versions.
7.2 The functions.php Removal Hook
// Add to active theme's functions.php
// Removes the WordPress generator meta tag from <head>
function remove_wordpress_generator() {
return '';
}
add_filter('the_generator', 'remove_wordpress_generator');
// Or more specifically:
remove_action('wp_head', 'wp_generator');
After applying:
# Verify removal
curl -s https://example.com/ | grep -oE 'meta name="generator"[^>]+'
# Expected: no output
7.3 The Plugin Approach
If functions.php editing isn't possible (managed WordPress hosting, no developer access):
- WP Hide & Security Enhancer: removes generator and other version disclosures.
- Better WP Security (iThemes Security): includes "Hide WordPress" feature.
- Sucuri Security: includes generator removal.
- WPS Hide Login: removes WordPress login URL but also hides version disclosures.
For Bubbles WordPress sites we maintain: code level (functions.php) preferred.
7.4 The Yoast Generator Tag
Yoast SEO adds its own generator:
<meta name="generator" content="WordPress 6.4.2">
<meta property="generator" content="Yoast SEO v21.7">
The property="generator" is non standard but Yoast emits it. Removing:
// Remove Yoast generator
add_filter('wpseo_debug_marker', '__return_empty_string');
7.5 The HTML Comment Disclosure
WordPress and plugins also add HTML comments that reveal versions:
<!-- All in One SEO Pro Pack 4.3.6 -->
<!-- WP Rocket 3.15.2 -->
<!-- Yoast SEO v21.7 - https://yoast.com/wordpress/plugins/seo/ -->
These are not meta tags but serve the same fingerprinting purpose. Removal requires per plugin configuration or code editing.
7.6 The Bulk WordPress Hardening
// functions.php additions for WordPress security hardening
// Remove WordPress version from head
remove_action('wp_head', 'wp_generator');
// Remove version from RSS feeds
add_filter('the_generator', '__return_empty_string');
// Remove version from scripts and stylesheets
function remove_version_query($src) {
return remove_query_arg('ver', $src);
}
add_filter('style_loader_src', 'remove_version_query', 9999);
add_filter('script_loader_src', 'remove_version_query', 9999);
// Remove Yoast SEO generator marker
add_filter('wpseo_debug_marker', '__return_empty_string');
// Remove generator from RSS
foreach (['rss2_head', 'commentsrss2_head', 'rss_head', 'rdf_header', 'atom_head', 'comments_atom_head', 'opml_head', 'app_head'] as $hook) {
remove_action($hook, 'the_generator');
}
7.7 The Migration Cleanup For WordPress to Bubbles
When migrating a WordPress site to hand coded Bubbles:
# 1. Audit the old WordPress for generator
curl -s https://oldwordpresssite.com/ | grep -oE 'meta name="generator"[^>]+\|<!-- [A-Z][^>]+ \d+\.\d+'
# 2. Build the new Bubbles site without any generator tag
# 3. After deploy, verify cleanup
curl -s https://newsite.com/ | grep -oE 'meta name="generator"[^>]+'
# Expected: no output OR only brand statement
# 4. Verify HTTP headers also cleaned (no X-Powered-By)
curl -sI https://newsite.com/ | grep -iE "x-powered-by|server"
# Expected: server: nginx (or hidden); no x-powered-by
8. STATIC SITE GENERATOR BEHAVIOR (HUGO, JEKYLL, GATSBY, NEXT.JS)
For Bubbles clients using static site generators (rare; Joseph hand codes), the removal patterns differ.
8.1 Hugo
<!-- Default Hugo output -->
<meta name="generator" content="Hugo 0.140.0">
Removal options:
Option A: command line flag at build time
hugo --disableHugoGeneratorInject
Option B: configuration file
# config.toml
disableHugoGeneratorInject = true
Option C: layout template
{# layouts/_default/baseof.html #}
<head>
<meta charset="utf-8">
<!-- Don't include {{ hugo.Generator }} -->
</head>
8.2 Jekyll
<!-- Default Jekyll output -->
<meta name="generator" content="Jekyll v4.3.2">
Removal: edit the layout template directly.
<!-- _layouts/default.html -->
<head>
<meta charset="utf-8">
<!-- Don't include {{ jekyll.environment }} or version -->
</head>
Note: Jekyll's default layouts may have the generator tag explicitly included; review and remove.
8.3 Gatsby
<!-- Default Gatsby output -->
<meta name="generator" content="Gatsby 5.13.3">
Removal: modify gatsby-config.js or override the default head plugin behavior.
// gatsby-config.js
module.exports = {
// Add to plugins or modify head generation
pathPrefix: '/',
plugins: [
// ... other plugins
],
// Suppress generator (mechanism varies by Gatsby version)
};
8.4 Next.js
Next.js does NOT auto include a generator tag. Sites built with Next.js have full control over the head.
If a generator tag is desired (brand statement), it can be added manually:
// pages/_document.js
import Document, { Html, Head, Main, NextScript } from 'next/document';
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
8.5 Astro
<!-- Default Astro output -->
<meta name="generator" content="Astro v4.7.0">
Removal: configure Astro to suppress (varies by version; consult current Astro docs).
8.6 The General SSG Pattern
Static site generators consistently auto add their generator tag. The removal pattern varies but follows the same template logic. For any new Bubbles client using an SSG: configure to suppress generator at build time.
9. THE VERSION NUMBER RISK (CVE EXPLOITATION PATHWAY)
The single most critical issue is version number disclosure. Each version disclosure is a CVE exploitation invitation.
9.1 The Public Vulnerability Database
CVEs (Common Vulnerabilities and Exposures) are publicly cataloged at:
- NIST National Vulnerability Database: https://nvd.nist.gov
- MITRE CVE: https://cve.mitre.org
- CVE Details: https://www.cvedetails.com
Anyone can search for "WordPress 5.2" or "Drupal 9.3" and find published vulnerabilities. Attackers do this routinely.
9.2 The Attack Pattern
1. Vulnerability disclosed for WordPress 6.3.x.
2. NVD publishes CVE entry within 24 hours.
3. Security researchers publish PoC (proof of concept) within days.
4. Automated bots scan internet for "WordPress 6.3" in generator tags.
5. Sites matching are queued for attack.
6. CVE exploitation attempts begin.
7. Unpatched sites compromised.
Timeline: from CVE disclosure to active exploitation: 1-7 days.
Sites with version disclosure: priority targets.
Sites without version disclosure: lower priority (still attacked but later).
9.3 The Specific CMS Risks
WordPress version disclosure: - Most prominent target (largest install base). - Multiple known CVEs per year. - Automated exploitation tools widely available. - Removing generator is the single highest impact security improvement.
Drupal version disclosure: - High value target (government, enterprise). - "Drupalgeddon" series of major CVEs. - Removing version disclosure is documented security baseline.
Joomla version disclosure: - Smaller install base than WordPress but still attacked. - Auto removal less common; manual editing required.
Static site generator disclosure: - Less critical (the SSG doesn't run on the server post build). - Still reveals tech stack; minor competitive intel.
9.4 The Hardening Hierarchy
For Bubbles client sites:
- HIGHEST priority: remove WordPress/Drupal/Joomla version from generator tags.
- HIGH priority: remove X-Powered-By HTTP header (framework-http-security-headers.md).
- HIGH priority: hide nginx version with
server_tokens off;(framework-http-performance-headers.md Section 4). - MEDIUM priority: remove plugin version disclosures (Yoast, etc.).
- MEDIUM priority: remove HTML comments revealing versions.
- LOW priority: SSG version disclosure (less actively exploited).
9.5 The Bubbles Standard
Never include version numbers in any metadata visible to public requests.
This applies to:
<meta name="generator">content.<meta property="generator">content.- HTML comments.
- HTTP response headers.
- JavaScript console output.
- robots.txt comments.
If a tool or service insists on disclosing version, configure it to suppress or replace with generic value.
10. THE X-POWERED-BY AND SERVER HEADER RELATIONSHIP
The HTTP layer equivalent of the meta generator tag.
10.1 The X-Powered-By Header
HTTP/2 200 OK
X-Powered-By: PHP/8.2.4
Used by Express, PHP, ASP.NET, and others to declare the backend language or framework. Same security concerns as generator tag.
For Bubbles: never include X-Powered-By in nginx responses. FastAPI does not add X-Powered-By by default (verify per app).
10.2 The Server Header
HTTP/2 200 OK
Server: nginx/1.26.0
The Server header is set by nginx automatically. Disclosing the version enables CVE targeting against nginx itself.
# nginx.conf (http context)
server_tokens off;
After this directive:
HTTP/2 200 OK
Server: nginx
Only "nginx" is disclosed; the version is hidden.
Per framework-http-performance-headers.md Section 4, the server_tokens off; directive should be set on every Bubbles nginx installation.
10.3 The Full Hardening Pattern
# /etc/nginx/nginx.conf
http {
# Hide nginx version
server_tokens off;
# Remove any X-Powered-By that upstream might set
proxy_hide_header X-Powered-By;
# ... other directives
}
Plus in FastAPI:
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import Response
class HardenHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
# Remove version headers if any
response.headers.pop("X-Powered-By", None)
response.headers.pop("Server", None)
return response
app = FastAPI()
app.add_middleware(HardenHeadersMiddleware)
10.4 The Aggregate Verification
# Check for any version disclosure in response
curl -sI https://example.com/ | grep -iE "server|x-powered|x-aspnet|x-runtime"
# Expected (Bubbles standard):
# server: nginx
# Not expected:
# server: nginx/1.26.0
# x-powered-by: PHP/8.2.4
# x-aspnet-version: 4.8.4515.0
# x-runtime: 0.012345
And for HTML:
# Check for any version disclosure in HTML
curl -s https://example.com/ | grep -iE 'name="generator"\|property="generator"\|version|powered'
# Expected: only brand statement (if any) without version numbers
11. THE BRAND STATEMENT USE CASE (LEGITIMATE GENERATOR VALUE)
For hand coded Bubbles sites, the generator tag can serve as a brand reinforcement signal.
11.1 The Pattern
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
No version number. Brand attribution. Differentiation from platform sites.
11.2 The Marketing Reach
When Wappalyzer (or similar) catalogues a site, the generator tag is displayed. Sites that competitors check via Wappalyzer see:
Technology Stack:
- Custom (Hand coded by ThatDeveloperGuy.com)
- No WordPress, no platform
- Custom HTML/CSS/JavaScript
This functions as marketing for Joseph among competitive intelligence researchers. The cost is zero; the brand visibility is real.
11.3 The Bubbles Convention
The standard Bubbles footer convention "Crafted by ThatDeveloperGuy.com" extends naturally to the generator tag:
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Site Title</title>
<meta name="description" content="...">
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
<!-- ... -->
</head>
<body>
<!-- ... content ... -->
<footer>
<p>Crafted by ThatDeveloperGuy.com</p>
</footer>
</body>
The generator tag and footer reinforce the same message: this is hand coded, not platform built.
11.4 The Client Specific Variations
For client sites where Joseph wants to subtly disclose the developer:
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
For client sites where the client prefers no developer attribution in metadata:
<!-- Omit the tag entirely -->
For client sites with their own brand statement:
<meta name="generator" content="Custom build for Client Name">
11.5 The Wikidata Approach (For Highest Authority Sites)
For sites where the Bubbles brand is heavily visible (Joseph's own sites, MEGAMIND project), the generator could reference the Wikidata identity:
<meta name="generator" content="Joseph Anady (Q138610626) at ThatDeveloperGuy.com">
This connects the generator signal to the verified identity pattern (covered in framework-html-meta-author.md Section 9).
12. LEGITIMATE MODERN USES
The narrow cases where generator has legitimate value.
12.1 Brand Statement (Hand Coded Sites)
As covered in Section 11. The strongest legitimate use case for new sites.
12.2 Internal Documentation
For internal documentation systems where tracking the generation tool matters:
<meta name="generator" content="Docusaurus v3.0">
For internal facing tools where security concerns are minimal and tooling identification matters for the team.
12.3 Academic Or Archival Indexing
Some academic indexing systems and the Wayback Machine track generator tags for historical preservation. The information helps reconstruct what tools were popular at a given time.
For most Bubbles client sites: not relevant.
12.4 Dublin Core Metadata Compliance
Dublin Core (the metadata standard underlying RSS and other web standards) recommends the generator concept. Sites integrating with academic indexing systems may benefit.
For most Bubbles client sites: not relevant.
12.5 Internal Development Reference
Some teams use generator to track which build pipeline produced a deployed asset:
<meta name="generator" content="bubbles-pipeline-v2.4">
For debugging deployments: useful. For public facing: avoid version disclosure.
13. THE BUBBLES DECISION FRAMEWORK
Each client site requires a deliberate decision.
13.1 The Decision Tree
Is this a new hand coded Bubbles build?
|
|---> YES: Optional brand statement.
| <meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
| OR omit entirely.
|
|---> NO: Is this a migration from CMS?
|
|---> YES: AUDIT for stale generator tags. REMOVE.
|
|---> NO: Existing CMS site we're maintaining?
|
|---> Yes: Configure CMS to suppress generator.
|
|---> No (other): omit by default.
13.2 The Per Client Decision
ThatDeveloperGuy.com (Joseph's own site):
Include brand statement. Joseph wants visibility for his hand coding approach.
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
Federal subcontractor sites (SDVOSB context):
Omit entirely. Federal security baselines discourage information disclosure.
<!-- Omit generator tag -->
Marshallese-Voices, Arkansas Counseling and Wellness (community/local clients):
Optional brand statement at client discretion. Default: include subtle brand statement.
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
Handled Tax and Advisory (Amanda Emerdinger):
Optional brand statement. Note: do not associate any tax credential with the generator. The generator is about the website, not the tax service.
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
Client migrations from WordPress/Squarespace/Wix:
Aggressive cleanup. Remove all generator tags from old platform. New Bubbles build follows the standard pattern (brand statement or omit).
Anonymous client sites (where developer attribution not wanted):
Omit entirely.
<!-- Omit generator tag -->
13.3 The Default
For most Bubbles work, the default is the brand statement pattern:
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
Switching to "omit" requires client request or security baseline reason.
14. THE CMS MIGRATION CLEANUP PATTERN
The most important operational pattern for Bubbles: aggressive cleanup of CMS generator tags when migrating to hand coded.
14.1 The Pre Migration Audit
Before migrating, document what the old site discloses:
# Audit old WordPress site for all disclosures
SITE=https://oldwordpresssite.com
echo "=== Generator tag ==="
curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+'
echo "=== HTML comments revealing versions ==="
curl -s "$SITE/" | grep -oE '<!-- [A-Z][^>]+ \d+\.\d+[^>]*'
echo "=== HTTP X-Powered-By ==="
curl -sI "$SITE/" | grep -i "x-powered-by"
echo "=== HTTP Server ==="
curl -sI "$SITE/" | grep -i "^server"
echo "=== Plugin signatures in JS/CSS paths ==="
curl -s "$SITE/" | grep -oE 'wp-content/plugins/[^/]+' | sort -u
Document the findings; ensure none persist in the new build.
14.2 The Build Phase Verification
During new site development:
# Check the new template/build
SITE=https://staging.newsite.com
# Should NOT match old WordPress signatures
curl -s "$SITE/" | grep -ciE 'wordpress|wp-content|wp-admin'
# Expected: 0
# Generator: should be hand coded brand statement or absent
curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+'
# Expected: "Hand coded by ThatDeveloperGuy.com" or no output
14.3 The Post Deploy Audit
After production deploy:
SITE=https://newsite.com
# 1. No WordPress signatures
curl -s "$SITE/" | grep -ciE 'wordpress|wp-content|wp-admin'
# 2. No old CMS generator
curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+'
# 3. Only expected brand statement (if any)
CURRENT_GENERATOR=$(curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+')
echo "Current generator: $CURRENT_GENERATOR"
# 4. HTTP headers hardened
curl -sI "$SITE/" | grep -iE "server|x-powered"
# Expected: only "server: nginx" (no version, no x-powered-by)
14.4 The Bulk Migration Script
#!/bin/bash
# /usr/local/bin/migration-cleanup-audit.sh
# Audit all Bubbles client sites for stale CMS generator tags
for site_dir in /var/www/sites/*/; do
SITE=$(basename "$site_dir")
INDEX="$site_dir/index.html"
if [ -f "$INDEX" ]; then
# Check for CMS generator tags
if grep -qiE 'meta name="generator" content="WordPress|Drupal|Joomla|Wix|Squarespace' "$INDEX"; then
echo "STALE CMS GENERATOR: $SITE"
echo " $(grep -oE 'meta name="generator"[^>]+' "$INDEX" | head -1)"
fi
# Check for plugin generator markers
if grep -qiE 'wp-content/plugins/' "$INDEX"; then
echo "WP PLUGIN SIGNATURES: $SITE"
fi
fi
done
14.5 The Cleanup Command
# Remove specific generator tags from all HTML files in a site
SITE_DIR=/var/www/sites/example.com
# Remove WordPress generator
find "$SITE_DIR" -name "*.html" -type f -exec \
sed -i '/meta name="generator" content="WordPress/d' {} \;
# Remove Yoast generator
find "$SITE_DIR" -name "*.html" -type f -exec \
sed -i '/meta property="generator" content="Yoast/d' {} \;
# Remove all generator tags (most aggressive)
find "$SITE_DIR" -name "*.html" -type f -exec \
sed -i '/meta name="generator"/d' {} \;
# Verify cleanup
curl -s https://example.com/ | grep -oE 'meta name="generator"[^>]+'
15. THE SERVER SIDE HARDENING (NGINX SERVER_TOKENS, FASTAPI SERVER HEADER)
The generator tag cleanup is incomplete without HTTP layer hardening.
15.1 The nginx server_tokens Directive
# /etc/nginx/nginx.conf (http context)
http {
# Hide nginx version in error pages and Server header
server_tokens off;
# ... other directives
}
After applying:
nginx -t && systemctl reload nginx
# Verify
curl -sI https://example.com/ | grep -i "^server"
# Expected: server: nginx (no version)
Per framework-http-performance-headers.md Section 4, this directive is mandatory on every Bubbles installation.
15.2 Removing X-Powered-By From Upstream
If the FastAPI sidecar or any upstream emits X-Powered-By:
location / {
proxy_pass http://127.0.0.1:9090;
proxy_hide_header X-Powered-By;
}
This strips the header from nginx response even if upstream sets it.
15.3 FastAPI Header Cleanup
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class CleanupHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
# Remove headers that may leak information
for header in ["X-Powered-By", "Server", "X-AspNet-Version"]:
response.headers.pop(header, None)
return response
app.add_middleware(CleanupHeadersMiddleware)
15.4 The Full Hardening Verification
# Single comprehensive check
SITE=https://example.com
echo "=== HTML disclosures ==="
curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+'
echo "=== HTTP disclosures ==="
curl -sI "$SITE/" | grep -iE "server:|x-powered-by|x-aspnet|x-runtime"
# For Bubbles standard:
# - HTML: only brand statement (or nothing)
# - HTTP: only "server: nginx" (no version, no x-powered-by)
15.5 The Audit Script
#!/bin/bash
# /usr/local/bin/disclosure-audit.sh
# Comprehensive information disclosure audit
URL=$1
echo "=== Disclosure audit for $URL ==="
echo ""
# HTML meta generator
META_GEN=$(curl -s "$URL" | grep -oE 'meta name="generator"[^>]+' | head -1)
echo "HTML meta generator: ${META_GEN:-(none)}"
# Check for version in meta generator
if echo "$META_GEN" | grep -qE '[0-9]+\.[0-9]+'; then
echo " WARNING: version number disclosed in meta generator"
fi
# HTML comments
HTML_COMMENTS=$(curl -s "$URL" | grep -oE '<!--[^>]*[0-9]+\.[0-9]+[^>]*-->' | head -3)
if [ -n "$HTML_COMMENTS" ]; then
echo "HTML comments with versions:"
echo "$HTML_COMMENTS"
fi
# HTTP Server
SERVER=$(curl -sI "$URL" | grep -i "^server" | tr -d '\r')
echo "HTTP Server: $SERVER"
if echo "$SERVER" | grep -qE '/[0-9]+\.[0-9]+'; then
echo " WARNING: nginx version disclosed; set server_tokens off"
fi
# X-Powered-By
XPOWERED=$(curl -sI "$URL" | grep -i "x-powered-by")
if [ -n "$XPOWERED" ]; then
echo "X-Powered-By: $XPOWERED"
echo " WARNING: backend framework disclosed"
fi
echo ""
echo "=== End audit ==="
16. ASSET CLASS AND USE CASE RECIPES
Paste ready snippets per scenario.
16.1 Canonical Bubbles head with brand statement
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title | ThatDeveloperGuy</title>
<meta name="description" content="...">
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
<!-- ... -->
</head>
16.2 Canonical Bubbles head without generator (federal/anonymous)
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Page Title</title>
<meta name="description" content="...">
<!-- NO meta generator (federal/anonymous client preference) -->
<!-- ... -->
</head>
16.3 WordPress generator removal (functions.php)
// /wp-content/themes/active-theme/functions.php
// Remove WordPress generator from <head>
remove_action('wp_head', 'wp_generator');
// Remove generator from RSS feeds
foreach (['rss2_head', 'commentsrss2_head', 'rss_head', 'rdf_header', 'atom_head', 'comments_atom_head', 'opml_head', 'app_head'] as $hook) {
remove_action($hook, 'the_generator');
}
// Remove Yoast generator
add_filter('wpseo_debug_marker', '__return_empty_string');
// Remove version query string from scripts and styles
function remove_version_query($src) {
return remove_query_arg('ver', $src);
}
add_filter('style_loader_src', 'remove_version_query', 9999);
add_filter('script_loader_src', 'remove_version_query', 9999);
16.4 Hugo generator removal
# config.toml
disableHugoGeneratorInject = true
Or:
# Build with the flag
hugo --disableHugoGeneratorInject
Or in the template:
<!-- layouts/_default/baseof.html -->
<head>
<meta charset="utf-8">
<!-- Don't include {{ hugo.Generator }} -->
</head>
16.5 Jekyll generator removal
<!-- _layouts/default.html -->
<head>
<meta charset="utf-8">
<!-- Don't include any reference to Jekyll version -->
</head>
16.6 nginx server_tokens hardening
# /etc/nginx/nginx.conf
http {
server_tokens off;
# ... other directives
}
Apply:
nginx -t && systemctl reload nginx
# Verify
curl -sI https://example.com/ | grep -i "^server"
# Expected: server: nginx (no version)
16.7 nginx remove X-Powered-By from proxy
location / {
proxy_pass http://127.0.0.1:9090;
proxy_hide_header X-Powered-By;
proxy_hide_header Server;
}
16.8 FastAPI cleanup middleware
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class CleanupHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
for header in ["X-Powered-By", "Server", "X-AspNet-Version"]:
response.headers.pop(header, None)
return response
app.add_middleware(CleanupHeadersMiddleware)
16.9 Bulk audit across all client sites
#!/bin/bash
# /usr/local/bin/generator-audit.sh
echo "=== Bubbles generator tag audit ==="
for site_dir in /var/www/sites/*/; do
SITE=$(basename "$site_dir")
INDEX="$site_dir/index.html"
if [ -f "$INDEX" ]; then
# Find any generator tag
GENERATOR=$(grep -oE 'meta name="generator"[^>]+' "$INDEX" | head -1)
if [ -n "$GENERATOR" ]; then
# Check if it has a version number
if echo "$GENERATOR" | grep -qE '[0-9]+\.[0-9]+'; then
echo "VERSION DISCLOSED: $SITE"
echo " $GENERATOR"
elif echo "$GENERATOR" | grep -qiE 'WordPress|Drupal|Joomla|Wix|Squarespace'; then
echo "CMS DISCLOSED: $SITE"
echo " $GENERATOR"
else
# Brand statement, no version. OK.
: # echo "OK: $SITE - $GENERATOR"
fi
fi
fi
done
echo ""
echo "=== End audit ==="
16.10 The pre deploy check
#!/bin/bash
# /usr/local/bin/predeploy-generator-check.sh
SITE_DIR=$1
FAILED=0
find "$SITE_DIR" -name "*.html" -type f | while read f; do
GENERATOR=$(grep -oE 'meta name="generator"[^>]+' "$f" | head -1)
if [ -n "$GENERATOR" ]; then
# No version numbers allowed
if echo "$GENERATOR" | grep -qE '[0-9]+\.[0-9]+'; then
echo "FAIL: $f has version in generator"
echo " $GENERATOR"
FAILED=1
fi
# No CMS names allowed (suggests migration leftover)
if echo "$GENERATOR" | grep -qiE 'WordPress|Drupal|Joomla|Wix|Squarespace'; then
echo "FAIL: $f has CMS name in generator (likely migration leftover)"
echo " $GENERATOR"
FAILED=1
fi
fi
done
if [ $FAILED -eq 1 ]; then
echo ""
echo "Pre deploy check failed. Clean up generator tags before deploying."
exit 1
fi
echo "OK: pre deploy generator check passed"
17. BUBBLES STANDARD PATTERN (PASTE READY)
The canonical Bubbles configuration for generator handling.
17.1 The Template Default
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{title}</title>
<meta name="description" content="{description}">
<meta name="author" content="{author}">
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
<!-- block head_extra -->
</head>
17.2 The nginx Configuration
# /etc/nginx/nginx.conf (http context)
http {
# Hide nginx version
server_tokens off;
# Hide X-Powered-By from upstreams
proxy_hide_header X-Powered-By;
# ... other directives
}
17.3 The FastAPI Middleware
from fastapi import FastAPI
from starlette.middleware.base import BaseHTTPMiddleware
app = FastAPI()
class HardenHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
for header in ["X-Powered-By", "Server", "X-AspNet-Version", "X-Runtime"]:
response.headers.pop(header, None)
return response
app.add_middleware(HardenHeadersMiddleware)
17.4 The Cleanup Audit Workflow
Weekly or before any major deployment:
sudo /usr/local/bin/generator-audit.sh
sudo /usr/local/bin/disclosure-audit.sh https://example.com/
# Apply any fixes
find /var/www/sites/example.com/ -name "*.html" -type f -exec sed -i '/meta name="generator" content="WordPress/d' {} \;
find /var/www/sites/example.com/ -name "*.html" -type f -exec sed -i '/meta property="generator" content="Yoast/d' {} \;
# Reload nginx if configuration changed
nginx -t && systemctl reload nginx
17.5 The Client Migration Checklist
For every WordPress (or other CMS) migration:
- [ ] Pre migration: document existing generator tags.
- [ ] Build phase: new templates use only Bubbles brand statement.
- [ ] Pre deploy: run
predeploy-generator-check.sh. - [ ] Post deploy: run
disclosure-audit.sh. - [ ] Verify HTTP headers: no X-Powered-By, no Server version.
- [ ] Verify HTML: no CMS generator, no plugin signatures.
18. AUDIT CHECKLIST
Run through these 40 items for any production deployment.
Core meta generator
- [ ] No
<meta name="generator">with version number on any indexable URL. - [ ] No
<meta name="generator">with "WordPress", "Drupal", "Joomla", "Wix", "Squarespace" as content. - [ ] If generator tag present, it is a brand statement only (e.g., "Hand coded by ThatDeveloperGuy.com").
- [ ] No
<meta property="generator">(Yoast specific) on any page.
Migration cleanup
- [ ] Migrated sites have all old CMS generator tags removed.
- [ ] No
wp-contentpaths in HTML source. - [ ] No CMS specific signatures (plugin paths, theme paths).
- [ ] No HTML comments revealing CMS versions.
HTTP layer
- [ ]
server_tokens off;in nginx.conf. - [ ]
Server: nginx(no version) in response headers. - [ ] No
X-Powered-Byheader in responses. - [ ] No
X-AspNet-Versionheader. - [ ] No
X-Runtimeheader.
FastAPI specific
- [ ] CleanupHeadersMiddleware applied (removes leaked headers).
- [ ] No version disclosure in FastAPI default behavior.
Site type specific
- [ ] Hand coded Bubbles sites: brand statement OR omit.
- [ ] Federal subcontractor sites: omit entirely.
- [ ] Anonymous client sites: omit entirely.
- [ ] Client preference sites: per client preference.
CMS configuration (where still managed)
- [ ] WordPress sites: functions.php hook removes generator.
- [ ] Hugo sites:
disableHugoGeneratorInject = true. - [ ] Jekyll sites: layout template doesn't include generator.
- [ ] Gatsby sites: gatsby-config.js suppresses generator.
Verification commands tested
- [ ]
curl -s URL | grep generatorreturns expected (brand statement or empty). - [ ]
curl -sI URL | grep -i "x-powered"returns nothing. - [ ]
curl -sI URL | grep -i "^server"returns "server: nginx" only.
Bulk audit
- [ ]
generator-audit.shshows no version disclosures across all client sites. - [ ]
disclosure-audit.shclean for each client site.
Pre deploy gates
- [ ] CI/CD or manual pre deploy check verifies no version in generator.
- [ ] Template review rejects new version disclosures.
Documentation
- [ ] Per client generator decision documented.
- [ ] Migration cleanup process documented.
- [ ] WordPress hardening hooks documented.
Cross cutting
- [ ] No version disclosure in robots.txt comments.
- [ ] No version disclosure in sitemap comments.
- [ ] No version disclosure in HTML comments.
- [ ] No version disclosure in JavaScript console output.
- [ ] No version disclosure in CSS comments.
Quarterly review
- [ ] Quarterly audit run across all client sites.
- [ ] New CVEs reviewed; sites with version disclosure prioritized for cleanup if any remain.
A site that passes all 40 has correctly handled generator disclosure: no version numbers, no CMS revelations, only brand statements where appropriate, with HTTP layer hardening in place.
19. COMMON PITFALLS
Twelve patterns to recognize and avoid.
Pitfall 1: Migrated WordPress site still has WordPress generator.
Symptom: page source shows <meta name="generator" content="WordPress 6.4.2"> despite being hand coded.
Why it breaks: migration didn't strip the old tag.
Fix: remove via sed across all HTML files; verify with grep.
Pitfall 2: Generator tag with version number.
Symptom: <meta name="generator" content="WordPress 6.4.2"> or <meta name="generator" content="Hugo 0.140.0">.
Why it breaks: enables CVE targeting.
Fix: remove version; either omit entirely or use brand statement without version.
Pitfall 3: nginx server_tokens not set.
Symptom: Server: nginx/1.26.0 in response headers.
Why it breaks: nginx version disclosed; vulnerable to CVE targeting.
Fix: server_tokens off; in nginx.conf.
Pitfall 4: X-Powered-By header present.
Symptom: X-Powered-By: PHP/8.2.4 or X-Powered-By: Express in responses.
Why it breaks: backend framework and version disclosed.
Fix: remove via FastAPI middleware or nginx proxy_hide_header X-Powered-By.
Pitfall 5: HTML comments revealing plugin versions.
Symptom: <!-- Yoast SEO v21.7 --> in HTML.
Why it breaks: plugin version disclosure; specific vulnerability targeting.
Fix: remove via WordPress functions.php hooks or post processing.
Pitfall 6: Yoast <meta property="generator"> not removed.
Symptom: <meta property="generator" content="Yoast SEO v21.7"> (uses property not name).
Why it breaks: separate from name="generator"; needs separate removal.
Fix: add_filter('wpseo_debug_marker', '__return_empty_string');
Pitfall 7: Generator tag in noindex pages. Symptom: login pages, admin pages have generator tags. Why it breaks: unnecessary disclosure; still scrapable. Fix: omit from all pages, even noindex.
Pitfall 8: Generator as keywords stuffing alternative.
Symptom: <meta name="generator" content="WordPress, Yoast, SEO optimized site">.
Why it breaks: misuse of the field; doesn't help with SEO.
Fix: use generator only for actual tool identification or brand statement.
Pitfall 9: Federal site has generator with CMS identification. Symptom: federal subcontractor site discloses Drupal version. Why it breaks: Section 508/NIST 800-53 audit finding. Fix: omit generator entirely for federal sites.
Pitfall 10: Generator differs across pages on same site. Symptom: homepage has generator A, blog has generator B. Why it breaks: inconsistent signal; suggests template fragmentation. Fix: consistent generator (or none) across all templates.
Pitfall 11: WordPress plugins overriding the wp_generator removal.
Symptom: wp_generator removed but generator tag still appears.
Why it breaks: another plugin or theme is re adding it.
Fix: track down the source; remove with higher priority filter or block specifically.
Pitfall 12: Generator brand statement contains contact info.
Symptom: <meta name="generator" content="Joseph Anady joseph@thatdeveloperguy.com 505-512-3662">.
Why it breaks: privacy concern; spam harvesting; clutter.
Fix: brand name only. Contact info goes in body content or schema.
20. DIAGNOSTIC COMMANDS
Reference of commands useful for generator investigation.
Inspect a single URL
# Extract meta generator
curl -s https://example.com/ | grep -oE 'meta name="generator"[^>]+' | head -1
# Extract Yoast property generator
curl -s https://example.com/ | grep -oE 'meta property="generator"[^>]+' | head -1
# Check for version in generator
curl -s https://example.com/ | grep -oE 'meta name="generator"[^>]+' | grep -E '[0-9]+\.[0-9]+'
# Check for CMS name in generator
curl -s https://example.com/ | grep -ioE 'meta name="generator"[^>]+' | grep -iE 'wordpress|drupal|joomla|wix|squarespace'
Inspect HTTP layer
# Check Server header
curl -sI https://example.com/ | grep -i "^server"
# Check X-Powered-By
curl -sI https://example.com/ | grep -i "x-powered-by"
# Comprehensive disclosure audit
curl -sI https://example.com/ | grep -iE "server:|x-powered|x-aspnet|x-runtime"
Bulk audit a sitemap
curl -s https://example.com/sitemap.xml | grep -oE "<loc>[^<]+</loc>" | sed 's/<[^>]*>//g' | while read url; do
GENERATOR=$(curl -s "$url" 2>/dev/null | grep -oE 'meta name="generator"[^>]+' | head -1)
if [ -n "$GENERATOR" ]; then
if echo "$GENERATOR" | grep -qE '[0-9]+\.[0-9]+'; then
echo "VERSION DISCLOSED: $url"
echo " $GENERATOR"
fi
fi
done
Cross site audit
generator-audit.sh
Detailed disclosure audit per URL
disclosure-audit.sh https://example.com/
After cleanup
# Apply changes (HTML)
nginx -t && systemctl reload nginx
# Or restart FastAPI
systemctl restart fastapi-sidecar
# Verify
disclosure-audit.sh https://example.com/
WordPress specific
# Check WordPress site for all version disclosures
SITE=https://example-wp.com
echo "=== Generator ==="
curl -s "$SITE/" | grep -oE 'meta name="generator"[^>]+\|meta property="generator"[^>]+'
echo "=== HTML comments ==="
curl -s "$SITE/" | grep -oE '<!--[^>]*[0-9]+\.[0-9]+[^>]*-->' | head -5
echo "=== Plugin signatures ==="
curl -s "$SITE/" | grep -oE 'wp-content/plugins/[^/]+' | sort -u | head -10
echo "=== HTTP headers ==="
curl -sI "$SITE/" | grep -iE "server|x-powered"
Browser based check
In Chrome DevTools:
- Open the page.
- View Source (Cmd/Ctrl + U).
- Search (Cmd/Ctrl + F) for "generator".
- Search for version patterns: regex
\d+\.\d+. - Network tab: inspect Response Headers for Server, X-Powered-By.
Use Wappalyzer browser extension: - Install Wappalyzer. - Visit the site. - See what tech is detected. - For Bubbles sites: should show "Custom" or no platform.
21. CROSS-REFERENCES
- framework-http-performance-headers.md Section 4 (Server header): the HTTP layer equivalent;
server_tokens off;directive for nginx. - framework-http-security-headers.md: X-Powered-By removal as part of broader security hardening.
- framework-html-meta-author.md: the brand statement in generator complements author attribution; together they signal authorship.
- framework-html-meta-keywords.md: like meta keywords, generator is exposed in page source; competitive intelligence implications.
- framework-html-meta-charset.md: generator tag placement after charset; within first 1024 bytes irrelevant for generator but consistent ordering matters.
- UNIVERSAL-RANKING-FRAMEWORK.md: the master ranking reference. Generator ignored by Google means alternative ranking signals do the work.
- SEO-BUILD-REFERENCE.md v2.4: the build playbook including security hardening.
- MDN meta name=generator: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta/name
- NIST National Vulnerability Database: https://nvd.nist.gov
- CIS Benchmarks (information disclosure baselines): https://www.cisecurity.org/cis-benchmarks
- OWASP Information Disclosure: https://owasp.org/www-community/Improper_Error_Handling
- WordPress Codex on the_generator filter: https://developer.wordpress.org/reference/hooks/the_generator/
- Hugo configuration reference: https://gohugo.io/getting-started/configuration/
- nginx server_tokens directive: https://nginx.org/en/docs/http/ngx_http_core_module.html#server_tokens
- Wappalyzer: https://www.wappalyzer.com
- BuiltWith: https://builtwith.com
APPENDIX A: ONE PAGE QUICK REFERENCE
For the person who just wants the answer.
The Bubbles rule
Never include version numbers in generator. Default: brand statement OR omit.
The canonical patterns
<!-- Default (hand coded Bubbles site): -->
<meta name="generator" content="Hand coded by ThatDeveloperGuy.com">
<!-- For federal/anonymous clients: omit entirely -->
<!-- (no meta generator tag) -->
The forbidden patterns
<!-- NEVER USE (version disclosure) -->
<meta name="generator" content="WordPress 6.4.2">
<meta name="generator" content="Hugo 0.140.0">
<meta name="generator" content="Drupal 10.1.5">
The HTTP layer companion
# Required for every Bubbles nginx installation
server_tokens off;
# Required for every Bubbles FastAPI sidecar
class HardenHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers.pop("X-Powered-By", None)
return response
Five rules to memorize
- Never disclose versions in generator (CVE exploitation invitation).
- WordPress migrations require explicit cleanup of WordPress generator.
- Brand statement "Hand coded by ThatDeveloperGuy.com" is optional but useful.
- HTTP layer hardening (server_tokens off, X-Powered-By removed) is mandatory.
- Federal subcontracting work: omit generator entirely (security baseline).
Five commands every operator should know
# 1. Check meta generator
curl -s URL | grep -oE 'meta name="generator"[^>]+'
# 2. Check HTTP disclosures
curl -sI URL | grep -iE "server|x-powered"
# 3. Bulk audit Bubbles client sites
generator-audit.sh
# 4. Remove WordPress generator from all HTML files
find /var/www/sites/example.com/ -name "*.html" -exec sed -i '/meta name="generator" content="WordPress/d' {} \;
# 5. Apply nginx changes
nginx -t && systemctl reload nginx
Three end to end tests
# 1. No version disclosed in generator
GEN=$(curl -s https://example.com/ | grep -oE 'meta name="generator"[^>]+' | head -1)
echo "$GEN" | grep -qE '[0-9]+\.[0-9]+' && echo "FAIL: version disclosed" || echo "OK"
# 2. No CMS disclosed in generator
echo "$GEN" | grep -qiE 'WordPress|Drupal|Joomla|Wix|Squarespace' && echo "FAIL: CMS disclosed" || echo "OK"
# 3. HTTP hardened
HTTP_DISCL=$(curl -sI https://example.com/ | grep -iE "x-powered|server: nginx/")
[ -z "$HTTP_DISCL" ] && echo "OK: HTTP hardened" || echo "FAIL: $HTTP_DISCL"
If all three pass AND disclosure-audit.sh reports clean AND nginx logs show no targeted CVE scan attempts, the generator layer is correctly handled.
End of framework-html-meta-generator.md.