@brandlab-digital/conformance 3.3.2 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"rule-registry.js","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabVerificationCheckId } from \"./verification-policy.js\";\n\n/**\n * Every static rule the evaluator can run, the check it evaluates and what it\n * proves. `<placeholder>` segments stand for the concrete package, capability,\n * key or state named in each finding. The generated rule reference is built\n * from this list, and tests keep it identical to the evaluator.\n */\nexport interface BrandlabStaticRule {\n ruleId: string;\n surface: string;\n checkId: BrandlabVerificationCheckId;\n summary: string;\n}\n\nexport const BRANDLAB_STATIC_RULES = [\n { ruleId: \"brandlab-release-package-json-missing\", surface: \"release\", checkId: \"release-consistency\", summary: \"package.json is present and readable.\", },\n { ruleId: \"brandlab-release-installed-version-unresolved\", surface: \"release\", checkId: \"release-consistency\", summary: \"All four Brandlab packages are pinned to one exact release.\", },\n { ruleId: \"brandlab-release-unsupported\", surface: \"release\", checkId: \"release-consistency\", summary: \"The installed release is the one this checker certifies.\", },\n { ruleId: \"brandlab-release-declared-installed-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The declared and detected releases agree.\", },\n { ruleId: \"brandlab-release-package-lock-<state>\", surface: \"release\", checkId: \"release-consistency\", summary: \"A valid npm lockfile v2 or v3 is committed.\", },\n { ruleId: \"brandlab-release-package-pin-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"Each package is declared once and locked at the exact release.\", },\n { ruleId: \"brandlab-release-package-resolution-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The lockfile resolves each package from the canonical npm registry with integrity.\", },\n { ruleId: \"brandlab-release-package-integrity-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"With --release-manifest, lockfile integrity equals the immutable release record.\", },\n { ruleId: \"brandlab-runtime-<package>\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next, react, react-dom and typescript use versions the release permits.\", },\n { ruleId: \"brandlab-runtime-react-pair\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"react and react-dom are declared and locked at the same version.\", },\n { ruleId: \"brandlab-runtime-node\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"engines.node matches the release in package.json and the lockfile.\", },\n { ruleId: \"brandlab-runtime-package-manager\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"packageManager is the release's npm version.\", },\n { ruleId: \"brandlab-runtime-sharp\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"Sharp is pinned only by overrides.sharp with one lockfile resolution.\", },\n { ruleId: \"brandlab-runtime-image-initialisation\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config imports the shared image-runtime guard first.\", },\n { ruleId: \"brandlab-runtime-next-config\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config exports defineBrandlabNextConfig, which owns headers, image formats and Preview noindex.\", },\n { ruleId: \"brandlab-environment-<capability>-<key>\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example documents each variable an enabled capability reads, including the contract's recipient key.\", },\n { ruleId: \"brandlab-environment-retired-contact-state\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example lists no retired contact state variables.\", },\n { ruleId: \"brandlab-source-dangerously-set-inner-html\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No project-owned dangerouslySetInnerHTML; only SchemaScript and SafeRichText inject markup.\", },\n { ruleId: \"brandlab-source-public-secret\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No API key, secret or token behind a NEXT_PUBLIC_ name.\", },\n { ruleId: \"brandlab-config-missing\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts exists.\", },\n { ruleId: \"brandlab-config-invalid\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts is a literal defineBrandlabProject with projectId.\", },\n { ruleId: \"brandlab-routes-catalogue\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the route catalogue with defineRouteCatalog.\", },\n { ruleId: \"brandlab-routes-location-estate\", surface: \"routes\", checkId: \"route-identity\", summary: \"A project that publishes locations declares them in src/data/locations/estate.ts with defineLocationEstate.\", },\n { ruleId: \"brandlab-redirects-table\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the redirect table with defineRedirects.\", },\n { ruleId: \"brandlab-contact-recipient-env\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"The contact contract declares delivery.recipientEnv as a literal key.\", },\n { ruleId: \"brandlab-source-local-contact-delivery\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Email delivery is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-email-renderer\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact email rendering is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-attachments\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Attachment preparation is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-contact-draft-persistence\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact details are never persisted in browser storage.\", },\n { ruleId: \"brandlab-source-retired-contact-state\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"No retired contact state service variables in source.\", },\n { ruleId: \"brandlab-contact-provider-route\", surface: \"contact\", checkId: \"contact-provider-errors\", summary: \"POST /api/contact is created by the shared handler, so provider errors are handled by the runtime.\", },\n { ruleId: \"brandlab-analytics-pii-payload\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"No lead fields, messages or attachment details in analytics payloads, and no project-set user_data.\", },\n { ruleId: \"brandlab-source-retired-analytics-event\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Only current event names are sent: contact_form for accepted enquiries and phone_link_click as a tap diagnostic.\", },\n { ruleId: \"brandlab-source-local-google-tag-bootstrap\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"The Google tag bootstrap is owned by the shared runtime.\", },\n { ruleId: \"brandlab-analytics-google-tag-owner\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one BrandlabGoogleTag, rendered by the root layout.\", },\n { ruleId: \"brandlab-analytics-google-tag-consent\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding: no adUserData attribute.\", },\n { ruleId: \"brandlab-analytics-contract\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one module declares the analytics binding with defineBrandlabAnalytics.\", },\n { ruleId: \"brandlab-analytics-instrumentation\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"src/instrumentation-client.ts calls installBrandlabInstrumentation, which reports one phone_link_click per tap.\", },\n { ruleId: \"brandlab-analytics-retired-phone-api\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No trackPhoneCall; taps are reported once as phone_link_click.\", },\n { ruleId: \"brandlab-analytics-web-vitals\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No web-vitals reporter, web_vital event or webVitals flag.\", },\n { ruleId: \"brandlab-robots-package-route\", surface: \"robots\", checkId: \"search-routing\", summary: \"robots is a thin binding of @brandlab-digital/site-next/robots with no static public/robots.txt.\", },\n { ruleId: \"brandlab-sitemap-package-route\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"The sitemap route uses the shared sitemap API over the route catalogue, with no static public/sitemap.xml.\", },\n { ruleId: \"brandlab-sitemap-split-mode\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"/sitemaps/[chunk] exists exactly when brandlab.config sets sitemap.mode: 'split'.\", },\n { ruleId: \"brandlab-metadata-route-builder\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Page metadata comes from createRouteMetadataBuilder(site, routes).\", },\n { ruleId: \"brandlab-site-identity\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Exactly one module declares the site identity (canonical URL, locale, titles) with defineBrandlabSite.\", },\n { ruleId: \"brandlab-source-raw-json-ld\", surface: \"schema\", checkId: \"structured-data\", summary: \"JSON-LD is rendered only by the shared schema component.\", },\n { ruleId: \"brandlab-source-self-serving-rating-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No self-serving AggregateRating schema.\", },\n { ruleId: \"brandlab-source-synthetic-offer-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No invented Offer markup.\", },\n { ruleId: \"brandlab-source-local-google-reviews-fetch\", surface: \"reviews\", checkId: \"structured-data\", summary: \"Google Places reviews are fetched only by the shared review runtime.\", },\n { ruleId: \"brandlab-business-facts\", surface: \"business\", checkId: \"business-identity\", summary: \"Exactly one module declares the business facts with defineBrandlabBusiness.\", },\n { ruleId: \"brandlab-content-implementation-wording\", surface: \"content\", checkId: \"public-output-safety\", summary: \"Everything that renders publicly (pages, components, metadata, schema, alt text, MDX, Markdown and JSON content) carries finished copy, not template, helper, scaffold or placeholder wording.\", },\n { ruleId: \"brandlab-source-root-missing\", surface: \"source\", checkId: \"source-safety\", summary: \"Project source sits under a recognised root so the source rules can run.\", },\n { ruleId: \"brandlab-source-platform-reimplementation\", surface: \"source\", checkId: \"source-safety\", summary: \"Protected platform surfaces (SchemaScript, SafeRichText, contact, sitemap, analytics APIs) are imported, not redeclared.\", },\n { ruleId: \"brandlab-source-retired-api\", surface: \"source\", checkId: \"source-safety\", summary: \"No API, option or configuration key removed in 3.0 (TrackedTelephoneLink, shouldIndexSite, buildRobotsMetadata, buildSeoMetadata and the duplicate metadata builders, isBrandlabProductionDeployment, resolveBrowserAnalyticsConfig, resolveBrandlabGoogleTagConfig, the contact submission controller and its lead-tracking helpers (use useBrandlabContactForm), header profiles and isProduction, xRobotsTag, contentSecurityPolicyMode, leadDiagnostics, ALLOW_INDEXING, NEXT_PUBLIC_SITE_URL, profile, routes.aliases).\", },\n { ruleId: \"brandlab-ci-workflow-pinned\", surface: \"project-verification\", checkId: \"project-build\", summary: \"Every reused workflow and action is pinned to a 40-character commit SHA.\", },\n { ruleId: \"brandlab-deployment-vercelignore-lockfile\", surface: \"deployment\", checkId: \"deployment-binding\", summary: \".vercelignore never excludes package.json or package-lock.json.\", },\n] as const satisfies readonly BrandlabStaticRule[];\n\n/** Whether a concrete rule ID (from a finding or evaluation) belongs to a registry entry. */\nexport function matchesBrandlabStaticRule(rule: BrandlabStaticRule, ruleId: string): boolean {\n if (rule.ruleId === ruleId) return true;\n if (!rule.ruleId.includes(\"<\")) return false;\n const stem = rule.ruleId.slice(0, rule.ruleId.indexOf(\"-<\"));\n const pattern = new RegExp(`^${rule.ruleId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/<[a-z]+>/g, \"[a-z0-9-]+\")}$`);\n return ruleId === stem || pattern.test(ruleId);\n}\n"]}
1
+ {"version":3,"file":"rule-registry.js","sourceRoot":"","sources":["../src/rule-registry.ts"],"names":[],"mappings":"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","sourcesContent":["import type { BrandlabVerificationCheckId } from \"./verification-policy.js\";\n\n/**\n * Every static rule the evaluator can run, the check it evaluates and what it\n * proves. `<placeholder>` segments stand for the concrete package, capability,\n * key or state named in each finding. The generated rule reference is built\n * from this list, and tests keep it identical to the evaluator.\n */\nexport interface BrandlabStaticRule {\n ruleId: string;\n surface: string;\n checkId: BrandlabVerificationCheckId;\n summary: string;\n}\n\nexport const BRANDLAB_STATIC_RULES = [\n { ruleId: \"brandlab-release-package-json-missing\", surface: \"release\", checkId: \"release-consistency\", summary: \"package.json is present and readable.\", },\n { ruleId: \"brandlab-release-installed-version-unresolved\", surface: \"release\", checkId: \"release-consistency\", summary: \"All four Brandlab packages are pinned to one exact release.\", },\n { ruleId: \"brandlab-release-unsupported\", surface: \"release\", checkId: \"release-consistency\", summary: \"The installed release is the one this checker certifies.\", },\n { ruleId: \"brandlab-release-declared-installed-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The declared and detected releases agree.\", },\n { ruleId: \"brandlab-release-package-lock-<state>\", surface: \"release\", checkId: \"release-consistency\", summary: \"A valid npm lockfile v2 or v3 is committed.\", },\n { ruleId: \"brandlab-release-package-pin-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"Each package is declared once and locked at the exact release.\", },\n { ruleId: \"brandlab-release-package-resolution-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"The lockfile resolves each package from the canonical npm registry with integrity.\", },\n { ruleId: \"brandlab-release-package-integrity-mismatch\", surface: \"release\", checkId: \"release-consistency\", summary: \"With --release-manifest, lockfile integrity equals the immutable release record.\", },\n { ruleId: \"brandlab-runtime-<package>\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next, react, react-dom and typescript use versions the release permits.\", },\n { ruleId: \"brandlab-runtime-react-pair\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"react and react-dom are declared and locked at the same version.\", },\n { ruleId: \"brandlab-runtime-node\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"engines.node matches the release in package.json and the lockfile.\", },\n { ruleId: \"brandlab-runtime-package-manager\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"packageManager is the release's npm version.\", },\n { ruleId: \"brandlab-runtime-sharp\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"Sharp is pinned only by overrides.sharp with one lockfile resolution.\", },\n { ruleId: \"brandlab-runtime-image-initialisation\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config imports the shared image-runtime guard first.\", },\n { ruleId: \"brandlab-runtime-next-config\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"next.config exports defineBrandlabNextConfig, which owns headers, image formats and Preview noindex.\", },\n { ruleId: \"brandlab-runtime-image-remote-patterns\", surface: \"runtime\", checkId: \"runtime-supported\", summary: \"Image remote patterns are https on public hosts (from 3.3.3 a production build refuses http and local ones, and wildcard hosts that admit a local address); any other wildcard host, or every path of another site's host, is a warning.\", },\n { ruleId: \"brandlab-environment-<capability>-<key>\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example documents each variable an enabled capability reads, including the contract's recipient key.\", },\n { ruleId: \"brandlab-environment-retired-contact-state\", surface: \"environment\", checkId: \"runtime-supported\", summary: \".env.example lists no retired contact state variables.\", },\n { ruleId: \"brandlab-source-dangerously-set-inner-html\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No project-owned dangerouslySetInnerHTML; only SchemaScript and SafeRichText inject markup.\", },\n { ruleId: \"brandlab-source-public-secret\", surface: \"security\", checkId: \"runtime-supported\", summary: \"No API key, secret or token behind a NEXT_PUBLIC_ name.\", },\n { ruleId: \"brandlab-config-missing\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts exists.\", },\n { ruleId: \"brandlab-config-invalid\", surface: \"routes\", checkId: \"route-identity\", summary: \"brandlab.config.ts is a literal defineBrandlabProject with projectId.\", },\n { ruleId: \"brandlab-routes-catalogue\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the route catalogue with defineRouteCatalog.\", },\n { ruleId: \"brandlab-routes-location-estate\", surface: \"routes\", checkId: \"route-identity\", summary: \"A project that publishes locations declares them in src/data/locations/estate.ts with defineLocationEstate.\", },\n { ruleId: \"brandlab-redirects-table\", surface: \"routes\", checkId: \"route-identity\", summary: \"Exactly one module declares the redirect table with defineRedirects.\", },\n { ruleId: \"brandlab-contact-recipient-env\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"The contact contract declares delivery.recipientEnv as a literal key.\", },\n { ruleId: \"brandlab-source-local-contact-delivery\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Email delivery is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-email-renderer\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact email rendering is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-local-contact-attachments\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Attachment preparation is left to the shared contact runtime.\", },\n { ruleId: \"brandlab-source-contact-draft-persistence\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"Contact details are never persisted in browser storage.\", },\n { ruleId: \"brandlab-source-retired-contact-state\", surface: \"contact\", checkId: \"contact-runtime\", summary: \"No retired contact state service variables in source.\", },\n { ruleId: \"brandlab-contact-provider-route\", surface: \"contact\", checkId: \"contact-provider-errors\", summary: \"POST /api/contact is created by the shared handler, so provider errors are handled by the runtime.\", },\n { ruleId: \"brandlab-analytics-pii-payload\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"No lead fields, messages or attachment details in analytics payloads, and no project-set user_data.\", },\n { ruleId: \"brandlab-source-retired-analytics-event\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Only current event names are sent: contact_form for accepted enquiries and phone_link_click as a tap diagnostic.\", },\n { ruleId: \"brandlab-source-local-google-tag-bootstrap\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"The Google tag bootstrap is owned by the shared runtime.\", },\n { ruleId: \"brandlab-analytics-google-tag-owner\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one BrandlabGoogleTag, rendered by the root layout.\", },\n { ruleId: \"brandlab-analytics-google-tag-consent\", surface: \"analytics\", checkId: \"analytics-privacy\", summary: \"BrandlabGoogleTag takes its ad_user_data consent default from the analytics binding: no adUserData attribute.\", },\n { ruleId: \"brandlab-analytics-contract\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"Exactly one module declares the analytics binding with defineBrandlabAnalytics.\", },\n { ruleId: \"brandlab-analytics-instrumentation\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"src/instrumentation-client.ts calls installBrandlabInstrumentation, which reports one phone_link_click per tap.\", },\n { ruleId: \"brandlab-analytics-retired-phone-api\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No trackPhoneCall; taps are reported once as phone_link_click.\", },\n { ruleId: \"brandlab-analytics-web-vitals\", surface: \"analytics\", checkId: \"analytics-conversion\", summary: \"No web-vitals reporter, web_vital event or webVitals flag.\", },\n { ruleId: \"brandlab-robots-package-route\", surface: \"robots\", checkId: \"search-routing\", summary: \"robots is a thin binding of @brandlab-digital/site-next/robots with no static public/robots.txt.\", },\n { ruleId: \"brandlab-sitemap-package-route\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"The sitemap route uses the shared sitemap API over the route catalogue, with no static public/sitemap.xml.\", },\n { ruleId: \"brandlab-sitemap-split-mode\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"/sitemaps/[chunk] exists exactly when brandlab.config sets sitemap.mode: 'split'.\", },\n { ruleId: \"brandlab-metadata-route-builder\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Page metadata comes from createRouteMetadataBuilder(site, routes).\", },\n { ruleId: \"brandlab-site-identity\", surface: \"metadata\", checkId: \"search-routing\", summary: \"Exactly one module declares the site identity (canonical URL, locale, titles) with defineBrandlabSite.\", },\n { ruleId: \"brandlab-source-raw-json-ld\", surface: \"schema\", checkId: \"structured-data\", summary: \"JSON-LD is rendered only by the shared schema component.\", },\n { ruleId: \"brandlab-source-self-serving-rating-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No self-serving AggregateRating schema.\", },\n { ruleId: \"brandlab-source-faq-howto-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No FAQPage or HowTo markup.\", },\n { ruleId: \"brandlab-sitemap-build-time-lastmod\", surface: \"sitemap\", checkId: \"search-routing\", summary: \"Sitemap lastmod comes from content dates (lastSignificantChange), never new Date() or Date.now().\", },\n { ruleId: \"brandlab-source-synthetic-offer-schema\", surface: \"schema\", checkId: \"structured-data\", summary: \"No invented Offer markup.\", },\n { ruleId: \"brandlab-source-local-google-reviews-fetch\", surface: \"reviews\", checkId: \"structured-data\", summary: \"Google Places reviews are fetched only by the shared review runtime.\", },\n { ruleId: \"brandlab-business-facts\", surface: \"business\", checkId: \"business-identity\", summary: \"Exactly one module declares the business facts with defineBrandlabBusiness.\", },\n { ruleId: \"brandlab-content-implementation-wording\", surface: \"content\", checkId: \"public-output-safety\", summary: \"Everything that renders publicly (pages, components, metadata, schema, alt text, MDX, Markdown and JSON content) carries finished copy, not template, helper, scaffold or placeholder wording.\", },\n { ruleId: \"brandlab-source-root-missing\", surface: \"source\", checkId: \"source-safety\", summary: \"Project source sits under a recognised root so the source rules can run.\", },\n { ruleId: \"brandlab-source-platform-reimplementation\", surface: \"source\", checkId: \"source-safety\", summary: \"Protected platform surfaces (SchemaScript, SafeRichText, contact, sitemap, analytics APIs) are imported, not redeclared.\", },\n { ruleId: \"brandlab-source-retired-api\", surface: \"source\", checkId: \"source-safety\", summary: \"No API, option or configuration key removed in 3.0 (TrackedTelephoneLink, shouldIndexSite, buildRobotsMetadata, buildSeoMetadata and the duplicate metadata builders, isBrandlabProductionDeployment, resolveBrowserAnalyticsConfig, resolveBrandlabGoogleTagConfig, the contact submission controller and its lead-tracking helpers (use useBrandlabContactForm), header profiles and isProduction, xRobotsTag, contentSecurityPolicyMode, leadDiagnostics, ALLOW_INDEXING, NEXT_PUBLIC_SITE_URL, profile, routes.aliases).\", },\n { ruleId: \"brandlab-ci-workflow-pinned\", surface: \"project-verification\", checkId: \"project-build\", summary: \"Every reused workflow and action is pinned to a 40-character commit SHA.\", },\n { ruleId: \"brandlab-deployment-vercelignore-lockfile\", surface: \"deployment\", checkId: \"deployment-binding\", summary: \".vercelignore never excludes package.json or package-lock.json.\", },\n] as const satisfies readonly BrandlabStaticRule[];\n\n/** Whether a concrete rule ID (from a finding or evaluation) belongs to a registry entry. */\nexport function matchesBrandlabStaticRule(rule: BrandlabStaticRule, ruleId: string): boolean {\n if (rule.ruleId === ruleId) return true;\n if (!rule.ruleId.includes(\"<\")) return false;\n const stem = rule.ruleId.slice(0, rule.ruleId.indexOf(\"-<\"));\n const pattern = new RegExp(`^${rule.ruleId.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\").replace(/<[a-z]+>/g, \"[a-z0-9-]+\")}$`);\n return ruleId === stem || pattern.test(ruleId);\n}\n"]}
@@ -30,6 +30,8 @@ export interface BrandlabProjectCapabilities {
30
30
  }
31
31
  /** Capabilities declared by brandlab.config.ts, the contact route and the location data. */
32
32
  export declare function describeCapabilities(root: string, config: string | null): BrandlabProjectCapabilities;
33
+ /** Loopback, unspecified, private, shared and link-local hosts in any URL spelling, and wildcard hosts that admit one, as defineBrandlabNextConfig refuses them. */
34
+ export declare function isLocalImageHost(hostname: string): boolean;
33
35
  /** Evaluate gitignore-style patterns (last match wins) for a root-level file. */
34
36
  export declare function isIgnored(patterns: string, file: string): boolean;
35
37
  export interface ContactContractRecipient {
@@ -1 +1 @@
1
- {"version":3,"file":"static-rules.d.ts","sourceRoot":"","sources":["../src/static-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AASpD,OAAO,EAcL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,sBAAsB,EAAE,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;CACxC,GAAG,eAAe,CAalB;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,sBAAsB,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,GAAG,OAAO,EAAE,MAAM,EAAE,QAGrB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,QAgB5D;AAED,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;CACxC;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,2BAA2B,CAgBrG;AAssBD,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAajE;AAID,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,sFAAsF;AACtF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,mBAAmB,GAC1B,wBAAwB,EAAE,CAU5B;AA0BD,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBnE"}
1
+ {"version":3,"file":"static-rules.d.ts","sourceRoot":"","sources":["../src/static-rules.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AASpD,OAAO,EAcL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EAEzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAOvE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,mBAAmB,CAAC;IAC5B,2DAA2D;IAC3D,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,WAAW,EAAE,sBAAsB,EAAE,CAAC;CACvC;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,eAAe,CAAC,UAAU,CAAC,CAAC;CACxC,GAAG,eAAe,CAalB;AAED,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,sBAAsB,EAAE,EACrC,OAAO,EAAE,MAAM,EACf,GAAG,OAAO,EAAE,MAAM,EAAE,QAGrB;AAED,0FAA0F;AAC1F,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,iBAAiB,QAiB5D;AAED,MAAM,WAAW,2BAA2B;IAC1C,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,WAAW,EAAE,QAAQ,GAAG,OAAO,GAAG,IAAI,CAAC;CACxC;AAED,4FAA4F;AAC5F,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,2BAA2B,CAgBrG;AA0xBD,oKAAoK;AACpK,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAoC1D;AA+CD,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAajE;AAID,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,yFAAyF;IACzF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,sFAAsF;AACtF,wBAAgB,6BAA6B,CAC3C,MAAM,EAAE,mBAAmB,GAC1B,wBAAwB,EAAE,CAU5B;AA0BD,0EAA0E;AAC1E,wBAAgB,aAAa,CAAC,IAAI,EAAE,kBAAkB,GAAG,GAAG,CAAC,MAAM,CAAC,CAgBnE"}
@@ -43,6 +43,7 @@ export function inspectStaticRules(context) {
43
43
  inspectRetiredConfiguration(context);
44
44
  inspectWorkflowPins(context);
45
45
  inspectVercelIgnore(context);
46
+ inspectImageRemotePatterns(context);
46
47
  }
47
48
  /** Capabilities declared by brandlab.config.ts, the contact route and the location data. */
48
49
  export function describeCapabilities(root, config) {
@@ -725,6 +726,181 @@ function inspectVercelIgnore(context) {
725
726
  remediation: "Remove the package manifest and lockfile patterns from .vercelignore.",
726
727
  }));
727
728
  }
729
+ // --- runtime: image remote patterns (3.3.3) -----------------------------------
730
+ const NEXT_CONFIG_FILES = ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js"];
731
+ /**
732
+ * next.config's images.remotePatterns, as the optimiser will fetch them: from 3.3.3 a
733
+ * production build refuses an http, protocol-less or local source, or a wildcard host that
734
+ * admits a local address (blocker: next build would fail), and any other wildcard host or a
735
+ * pattern for every path of a third-party host is
736
+ * reported as a warning (the optimiser fetches such images for anyone who asks:
737
+ * GHSA-cjq9-62q9-8jv4, usage cost). Spreads and conditional entries are the site's own
738
+ * gating and are left alone; images.developmentRemotePatterns is for next dev only.
739
+ */
740
+ function inspectImageRemotePatterns(context) {
741
+ const path = NEXT_CONFIG_FILES.find((candidate) => context.source.byPath.has(candidate));
742
+ const file = path ? context.source.byPath.get(path) : undefined;
743
+ if (!path || !file)
744
+ return;
745
+ const calls = findImportedCalls(file, SITE_NEXT("next-config"), "defineBrandlabNextConfig");
746
+ if (calls.length === 0)
747
+ return;
748
+ const ruleId = "brandlab-runtime-image-remote-patterns";
749
+ recordEvaluation(context.evaluations, "runtime", ruleId);
750
+ const ownHosts = canonicalHosts(context);
751
+ for (const call of calls) {
752
+ const images = sameFileObject(file, propertyExpression(unwrapExpression(call.arguments[0]), "images"));
753
+ const patterns = sameFileArray(file, propertyExpression(images, "remotePatterns"));
754
+ if (!patterns)
755
+ continue;
756
+ patterns.elements.forEach((element, index) => {
757
+ const pattern = unwrapExpression(element);
758
+ if (!pattern || !ts.isObjectLiteralExpression(pattern))
759
+ return;
760
+ const protocolNode = unwrapExpression(objectPropertyValue(pattern, "protocol"));
761
+ const hostname = stringLiteralValue(unwrapExpression(objectPropertyValue(pattern, "hostname")));
762
+ const pathnameNode = unwrapExpression(objectPropertyValue(pattern, "pathname"));
763
+ const pathname = pathnameNode ? stringLiteralValue(pathnameNode) : undefined;
764
+ // A computed host, protocol or path cannot be judged here; defineBrandlabNextConfig checks it at build time.
765
+ if (hostname === null || (protocolNode && stringLiteralValue(protocolNode) === null) || pathname === null)
766
+ return;
767
+ const protocol = protocolNode ? stringLiteralValue(protocolNode) : undefined;
768
+ const shown = `${protocol ?? "any protocol"}://${hostname}${pathname === undefined ? "/**" : pathname.startsWith("/") ? pathname : `/${pathname}`}`;
769
+ const at = { ruleId, surface: "runtime", path, line: lineOfNode(file, pattern) };
770
+ if (protocol !== "https" || isLocalImageHost(hostname)) {
771
+ context.findings.push(staticFinding({
772
+ ...at,
773
+ expected: "Remote image sources a production build fetches are https on public hosts",
774
+ actual: `images.remotePatterns[${index}] allows ${shown}: from 3.3.3 a production build refuses an image source that uses http, names no protocol, or names or admits a local address, so next build fails.`,
775
+ remediation: "Remove the pattern, name the exact public hosts instead of a wildcard that admits local addresses, or move a development-only source (such as http://localhost) to images.developmentRemotePatterns, which only next dev uses.",
776
+ }));
777
+ return;
778
+ }
779
+ const wildcardHost = hostname.includes("*");
780
+ const everyPath = pathname === undefined || /^\/?\*\*$/.test(pathname);
781
+ if (!wildcardHost && (!everyPath || ownHosts.has(hostname.toLowerCase())))
782
+ return;
783
+ context.findings.push(staticFinding({
784
+ ...at,
785
+ severity: "warning",
786
+ expected: "Remote image patterns name the hosts and paths the site's images come from",
787
+ actual: wildcardHost
788
+ ? `images.remotePatterns[${index}] (${shown}) lets the image optimiser fetch from every host matching ${hostname}, for anyone who asks.`
789
+ : `images.remotePatterns[${index}] (${shown}) lets the image optimiser fetch any path on ${hostname}, for anyone who asks.`,
790
+ remediation: wildcardHost
791
+ ? "List the exact hosts the site's images come from, or serve local copies under public/."
792
+ : "Narrow pathname to the folders the site's images use (for example /vi/** or /images/**), or serve local copies under public/.",
793
+ }));
794
+ });
795
+ }
796
+ }
797
+ /** The site's canonical host and its www twin, from the defineBrandlabSite call in src/config/site.ts. */
798
+ function canonicalHosts(context) {
799
+ const hosts = new Set();
800
+ for (const file of context.source.files) {
801
+ for (const call of findImportedCalls(file, SITE_CORE("site"), "defineBrandlabSite")) {
802
+ const url = stringLiteralValue(unwrapExpression(objectPropertyValue(unwrapExpression(call.arguments[0]), "canonicalUrl")));
803
+ if (!url)
804
+ continue;
805
+ try {
806
+ const host = new URL(url).hostname.toLowerCase();
807
+ hosts.add(host);
808
+ hosts.add(host.startsWith("www.") ? host.slice(4) : `www.${host}`);
809
+ }
810
+ catch {
811
+ // not a URL: nothing to exempt
812
+ }
813
+ }
814
+ }
815
+ return hosts;
816
+ }
817
+ /** Loopback, unspecified, private, shared and link-local hosts in any URL spelling, and wildcard hosts that admit one, as defineBrandlabNextConfig refuses them. */
818
+ export function isLocalImageHost(hostname) {
819
+ const host = hostname.trim().toLowerCase().replace(/^\[(.*)\]$/, "$1").replace(/\.$/, "");
820
+ // Other picomatch syntax (?, braces, classes, extglobs) cannot be judged here, so it is refused.
821
+ if (/[?{}[\]()!@+]/.test(host))
822
+ return true;
823
+ // A wildcard host is local when it admits a local address: Next matches hostnames with picomatch, whose `*` stops
824
+ // only at a slash, so `*` and `**` both admit `localhost`, and `10.*.*.*` admits 10.0.0.1. Any wildcard address
825
+ // (`192.168.1.*`, `fd12:*`) counts as local.
826
+ if (host.includes("*")) {
827
+ if (/^[\d.*]+$/.test(host) || host.includes(":"))
828
+ return true;
829
+ const source = host.split(/(\*\*|\*)/).map((part) => (part === "**" ? ".*" : part === "*" ? "[^/]*" : part.replace(/[.+?^$()|{}[\]\\]/g, "\\$&"))).join("");
830
+ const glob = new RegExp("^" + source + "$");
831
+ return LOCAL_HOST_SAMPLES.some((sample) => glob.test(sample));
832
+ }
833
+ // The address a URL reads it as: 127.1, 0x7f.0.0.1 and [::ffff:127.0.0.1] all name 127.0.0.1.
834
+ let named = host;
835
+ try {
836
+ named = new URL("https://" + (host.includes(":") ? "[" + host + "]" : host)).hostname.replace(/^\[(.*)\]$/, "$1");
837
+ }
838
+ catch {
839
+ // not a host a URL can name: judged as written
840
+ }
841
+ if (named === "localhost" || named.endsWith(".localhost"))
842
+ return true;
843
+ const hex = named.match(/^(?:0{0,4}:){0,5}:?ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/);
844
+ const dotted = hex ? [parseInt(hex[1], 16) >> 8, parseInt(hex[1], 16) & 255, parseInt(hex[2], 16) >> 8, parseInt(hex[2], 16) & 255].join(".") : undefined;
845
+ const mapped = named.match(/^(?:0{0,4}:){0,5}:?ffff:(\d{1,3}(?:\.\d{1,3}){3})$/);
846
+ const ipv4 = (dotted ?? mapped?.[1] ?? named).match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/);
847
+ if (ipv4) {
848
+ const [a, b] = [Number(ipv4[1]), Number(ipv4[2])];
849
+ return a === 0 || a === 10 || a === 127 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) ||
850
+ (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127);
851
+ }
852
+ if (!named.includes(":"))
853
+ return false;
854
+ const groups = named.split(":");
855
+ const allZero = (parts) => parts.every((part) => /^0*$/.test(part));
856
+ // :: and ::1 in any spelling (0:0:0:0:0:0:0:1), then the private and link-local prefixes.
857
+ if (allZero(groups) || (allZero(groups.slice(0, -1)) && /^0*1$/.test(groups.at(-1) ?? "")))
858
+ return true;
859
+ return /^f[cd][0-9a-f]{0,2}:/.test(named) || /^fe[89ab][0-9a-f]?:/.test(named);
860
+ }
861
+ /** One address in each local range, to judge a wildcard host by. */
862
+ const LOCAL_HOST_SAMPLES = ["localhost", "a.localhost", "0.0.0.0", "10.0.0.1", "100.64.0.1", "127.0.0.1", "169.254.169.254", "172.16.0.1", "192.168.0.1", "::1", "fc00::1", "fe80::1"];
863
+ function unwrapExpression(node) {
864
+ let current = node;
865
+ while (current && (ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isParenthesizedExpression(current) || ts.isNonNullExpression(current))) {
866
+ current = current.expression;
867
+ }
868
+ return current;
869
+ }
870
+ /** A property's value, including shorthand (`{ remotePatterns }` gives the identifier). */
871
+ function propertyExpression(node, name) {
872
+ if (!node || !ts.isObjectLiteralExpression(node))
873
+ return undefined;
874
+ for (const property of node.properties) {
875
+ if (ts.isShorthandPropertyAssignment(property) && property.name.text === name)
876
+ return property.name;
877
+ }
878
+ return objectPropertyValue(node, name);
879
+ }
880
+ /** The initializer of a top-level `const name = …` in the same file, unwrapped. */
881
+ function sameFileInitializer(file, name) {
882
+ for (const statement of parseBrandlabSourceFile(file).statements) {
883
+ if (!ts.isVariableStatement(statement))
884
+ continue;
885
+ for (const declaration of statement.declarationList.declarations) {
886
+ if (ts.isIdentifier(declaration.name) && declaration.name.text === name)
887
+ return unwrapExpression(declaration.initializer);
888
+ }
889
+ }
890
+ return undefined;
891
+ }
892
+ /** An array literal, directly or through a top-level `const name = [...]` in the same file. */
893
+ function sameFileArray(file, node) {
894
+ const value = unwrapExpression(node);
895
+ const resolved = value && ts.isIdentifier(value) ? sameFileInitializer(file, value.text) : value;
896
+ return resolved && ts.isArrayLiteralExpression(resolved) ? resolved : undefined;
897
+ }
898
+ /** An object literal, directly or through a top-level `const name = {...}` in the same file. */
899
+ function sameFileObject(file, node) {
900
+ const value = unwrapExpression(node);
901
+ const resolved = value && ts.isIdentifier(value) ? sameFileInitializer(file, value.text) : value;
902
+ return resolved && ts.isObjectLiteralExpression(resolved) ? resolved : undefined;
903
+ }
728
904
  /** Evaluate gitignore-style patterns (last match wins) for a root-level file. */
729
905
  export function isIgnored(patterns, file) {
730
906
  let ignored = false;