Files
Portfolio/scripts/smoke-production.mjs

288 lines
18 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
import { pathToFileURL } from 'node:url';
import { PERFORMANCE_BUDGETS } from './lib/bundle-budgets.mjs';
const sha256Pattern = /^[a-f0-9]{64}$/u;
const privateIdentifiers = /(?:simpastil|simpasteel|neon[ -]?debugger|yourhealth)/iu;
const sourceMapReference = /(?:sourceMappingURL\s*=|["'(]\/?[^"')\s]+\.map(?:[?"')\s]|$))/iu;
const publicMetadata = Object.freeze({
name: 'Luca Pîslaru',
role: 'System Administrator & Full-Stack Developer',
title: 'Luca Pîslaru — System Administrator & Full-Stack Developer',
description: 'I’m Luca Pîslaru, a System Administrator & Full-Stack Developer based in Bucharest. Explore my work in Linux infrastructure and end-to-end application development.',
sameAs: Object.freeze([
'https://linkedin.com/in/luca-pisl',
'https://github.com/LucaPisl',
'https://gitlab.com/luca_pisl',
]),
location: Object.freeze({ city: 'Bucharest', countryCode: 'RO' }),
socialImagePath: '/assets/project-social-preview.webp',
socialImageType: 'image/webp',
});
function rootHttpsUrl(value, label) {
let url;
try { url = new URL(value); } catch { throw new Error(`${label} must be a valid URL.`); }
if (url.protocol !== 'https:' || url.username || url.password || url.port || url.pathname !== '/' || url.search || url.hash) {
throw new Error(`${label} must be one canonical root HTTPS origin without credentials, port, query, or fragment.`);
}
return url;
}
function requireHeader(response, name, expected) {
const value = response.headers.get(name);
if (value === null || (expected instanceof RegExp ? !expected.test(value) : value !== expected)) throw new Error(`${name} response header is missing or invalid.`);
return value;
}
function allMatches(source, expression) {
return [...source.matchAll(expression)].map((match) => match[1]);
}
function decodedText(value) {
return value.replace(/&amp;/gu, '&').replace(/&lt;/gu, '<').replace(/&gt;/gu, '>').replace(/&quot;/gu, '"').replace(/&#(?:39|x27);/giu, "'");
}
function visibleText(html) {
return decodedText(html.replace(/<script\b[\s\S]*?<\/script>/giu, ' ').replace(/<style\b[\s\S]*?<\/style>/giu, ' ').replace(/<[^>]+>/gu, ' ').replace(/\s+/gu, ' ').trim());
}
export function verifyNoJavaScriptDocument(html) {
if (typeof html !== 'string' || sourceMapReference.test(html)) throw new Error('JavaScript-disabled document contains a source map reference.');
if (privateIdentifiers.test(html)) throw new Error('JavaScript-disabled document contains a private identifier.');
const text = visibleText(html);
const headings = allMatches(html, /<h[1-6]\b[^>]*>([\s\S]*?)<\/h[1-6]>/giu).length;
const links = allMatches(html, /<a\b[^>]*href=["'][^"']+["'][^>]*>([\s\S]*?)<\/a>/giu).length;
if (!/Luca Pîslaru/u.test(text) || !/System Administrator\s*&\s*Full-Stack Developer/u.test(text) ||
!/Selected builds/iu.test(text) || !/Contact/iu.test(text) || !/mailto:/iu.test(html) || headings < 3 || links < 1 || text.length < 120) {
throw new Error('JavaScript-disabled document lacks substantive semantic identity, work evidence, or contact content.');
}
return { headings, links, visibleCharacters: text.length };
}
function verifySecurityHeaders(response, expectedDigest) {
requireHeader(response, 'x-portfolio-manifest-digest', expectedDigest);
const csp = requireHeader(response, 'content-security-policy', /default-src 'self'/u);
for (const directive of ["connect-src 'self'", "object-src 'none'", "base-uri 'none'", "frame-ancestors 'none'", "form-action 'self'", 'upgrade-insecure-requests']) {
if (!csp.includes(directive)) throw new Error(`Content-Security-Policy lacks ${directive}.`);
}
if (/unsafe-inline|unsafe-eval|\*/u.test(csp)) throw new Error('Content-Security-Policy contains an unsafe source expression.');
requireHeader(response, 'strict-transport-security', /^max-age=31536000(?:; preload)?$/u);
requireHeader(response, 'x-content-type-options', 'nosniff');
requireHeader(response, 'referrer-policy', 'no-referrer');
requireHeader(response, 'permissions-policy', /camera=\(\).*microphone=\(\).*geolocation=\(\)/u);
requireHeader(response, 'x-frame-options', 'DENY');
}
function exactTagContent(html, expression, label) {
const values = allMatches(html, expression);
if (values.length !== 1) throw new Error(`Expected exactly one ${label}.`);
return decodedText(values[0]);
}
function exactObjectKeys(value, expectedKeys, label) {
if (typeof value !== 'object' || value === null || Array.isArray(value) ||
JSON.stringify(Object.keys(value).sort()) !== JSON.stringify([...expectedKeys].sort())) {
throw new Error(`${label} has missing or forbidden fields.`);
}
}
function verifyMetadata(html, canonicalUrl) {
const title = exactTagContent(html, /<title\b[^>]*>([\s\S]*?)<\/title>/giu, 'title');
const canonicalLink = exactTagContent(html, /<link\b(?=[^>]*\brel=["']canonical["'])(?=[^>]*\bhref=["']([^"']+)["'])[^>]*>/giu, 'canonical link');
const description = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']description["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'description meta');
const robots = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']robots["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'robots meta');
const ogType = exactTagContent(html, /<meta\b(?=[^>]*\bproperty=["']og:type["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'og:type');
const ogTitle = exactTagContent(html, /<meta\b(?=[^>]*\bproperty=["']og:title["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'og:title');
const ogDescription = exactTagContent(html, /<meta\b(?=[^>]*\bproperty=["']og:description["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'og:description');
const ogUrl = exactTagContent(html, /<meta\b(?=[^>]*\bproperty=["']og:url["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'og:url');
const ogImage = exactTagContent(html, /<meta\b(?=[^>]*\bproperty=["']og:image["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'og:image');
const twitterCard = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']twitter:card["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'twitter:card');
const twitterTitle = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']twitter:title["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'twitter:title');
const twitterDescription = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']twitter:description["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'twitter:description');
const twitterImage = exactTagContent(html, /<meta\b(?=[^>]*\bname=["']twitter:image["'])(?=[^>]*\bcontent=["']([^"']+)["'])[^>]*>/giu, 'twitter:image');
if (title !== publicMetadata.title || description !== publicMetadata.description ||
canonicalLink !== canonicalUrl || !/^index\s*,\s*follow$/iu.test(robots) ||
ogType !== 'profile' || ogTitle !== title || ogDescription !== description || ogUrl !== canonicalLink ||
twitterCard !== 'summary_large_image' || twitterTitle !== title || twitterDescription !== description ||
twitterImage !== ogImage) {
throw new Error('Canonical, title, description, Open Graph, Twitter, or robots metadata is inconsistent.');
}
let socialImage;
try { socialImage = new URL(ogImage); } catch { throw new Error('Social image URL is invalid.'); }
const canonicalOrigin = new URL(canonicalUrl);
if (socialImage.origin !== canonicalOrigin.origin || socialImage.username || socialImage.password || socialImage.port ||
socialImage.pathname !== publicMetadata.socialImagePath || socialImage.search || socialImage.hash) {
throw new Error('Social image must use the expected path on the canonical host.');
}
const jsonLdText = exactTagContent(html, /<script\b(?=[^>]*\btype=["']application\/ld\+json["'])[^>]*>([\s\S]*?)<\/script>/giu, 'JSON-LD block');
let jsonLd;
try { jsonLd = JSON.parse(jsonLdText); } catch { throw new Error('JSON-LD is not valid JSON.'); }
exactObjectKeys(jsonLd, ['@context', '@type', 'name', 'description', 'url', 'mainEntity'], 'ProfilePage JSON-LD');
const person = jsonLd.mainEntity;
exactObjectKeys(person, ['@type', 'name', 'jobTitle', 'description', 'url', 'homeLocation', 'sameAs'], 'Person JSON-LD');
exactObjectKeys(person.homeLocation, ['@type', 'address'], 'homeLocation JSON-LD');
exactObjectKeys(person.homeLocation.address, ['@type', 'addressLocality', 'addressCountry'], 'PostalAddress JSON-LD');
if (jsonLd['@context'] !== 'https://schema.org' || jsonLd['@type'] !== 'ProfilePage' ||
jsonLd.name !== publicMetadata.name || jsonLd.description !== description || jsonLd.url !== canonicalUrl ||
person['@type'] !== 'Person' || person.name !== publicMetadata.name || person.jobTitle !== publicMetadata.role ||
person.description !== description || person.url !== canonicalUrl ||
person.homeLocation['@type'] !== 'Place' || person.homeLocation.address['@type'] !== 'PostalAddress' ||
person.homeLocation.address.addressLocality !== publicMetadata.location.city ||
person.homeLocation.address.addressCountry !== publicMetadata.location.countryCode ||
JSON.stringify(person.sameAs) !== JSON.stringify(publicMetadata.sameAs)) {
throw new Error('ProfilePage and Person JSON-LD do not match the exact public identity metadata.');
}
return {
assetPaths: allMatches(html, /<(?:script|link)\b(?=[^>]*(?:src|href)=["'](\/assets\/[^"']+)["'])[^>]*>/giu),
socialImage,
};
}
async function request(fetchImpl, url, init = {}) {
const response = await fetchImpl(url, { ...init, redirect: 'manual', signal: init.signal ?? AbortSignal.timeout(15_000) });
return response;
}
async function cancelResponseBody(response) {
try { await response.body?.cancel(); } catch { /* Preserve the validation error that required cancellation. */ }
}
async function countBoundedResponseBody(response, label, maxBytes) {
const contentLengthHeader = response.headers.get('content-length');
let declaredBytes = null;
if (contentLengthHeader !== null) {
if (!/^(?:0|[1-9][0-9]*)$/u.test(contentLengthHeader)) {
await cancelResponseBody(response);
throw new Error(`${label} content-length is malformed.`);
}
declaredBytes = Number(contentLengthHeader);
if (!Number.isSafeInteger(declaredBytes)) {
await cancelResponseBody(response);
throw new Error(`${label} content-length is outside the safe integer range.`);
}
if (declaredBytes === 0) {
await cancelResponseBody(response);
throw new Error(`${label} response is empty.`);
}
if (declaredBytes > maxBytes) {
await cancelResponseBody(response);
throw new Error(`${label} exceeds the ${maxBytes}-byte project-still limit.`);
}
}
if (response.body === null) throw new Error(`${label} response body is missing.`);
const reader = response.body.getReader();
let receivedBytes = 0;
try {
while (true) {
const { done, value } = await reader.read();
if (done) break;
receivedBytes += value.byteLength;
if (receivedBytes > maxBytes) {
try { await reader.cancel(); } catch { /* Preserve the byte-limit validation error. */ }
throw new Error(`${label} exceeds the ${maxBytes}-byte project-still limit.`);
}
}
} finally {
reader.releaseLock();
}
if (receivedBytes === 0) throw new Error(`${label} response is empty.`);
if (declaredBytes !== null && receivedBytes !== declaredBytes) {
throw new Error(`${label} content-length does not match the streamed byte count.`);
}
return receivedBytes;
}
export async function verifyProductionSmoke(options) {
const canonical = rootHttpsUrl(options.canonicalUrl, 'Canonical URL');
if (!sha256Pattern.test(options.expectedManifestDigest)) throw new Error('Expected signed manifest digest must be lowercase SHA-256.');
const fetchImpl = options.fetchImpl ?? fetch;
for (const alternateValue of options.alternateUrls ?? []) {
let alternate;
try { alternate = new URL(alternateValue); } catch { throw new Error('Alternate URL is invalid.'); }
if (alternate.username || alternate.password || alternate.search || alternate.hash || alternate.pathname !== '/' || alternate.origin === canonical.origin) throw new Error('Alternate URL must be a distinct credential-free root origin.');
const response = await request(fetchImpl, alternate);
if (response.status !== 308 || response.headers.get('location') !== canonical.href) throw new Error('Alternate host must redirect to the canonical HTTPS URL in one permanent hop.');
}
const rootResponse = await request(fetchImpl, canonical, { headers: { 'accept-encoding': 'br, gzip' } });
if (rootResponse.status !== 200) throw new Error('Canonical HTTPS URL did not return 200.');
verifySecurityHeaders(rootResponse, options.expectedManifestDigest);
requireHeader(rootResponse, 'content-type', /^text\/html(?:;|$)/u);
requireHeader(rootResponse, 'cache-control', /max-age=0.*must-revalidate/u);
requireHeader(rootResponse, 'content-encoding', /^(?:br|gzip)$/u);
const html = await rootResponse.text();
if (privateIdentifiers.test(html) || sourceMapReference.test(html)) throw new Error('Production HTML exposes a private identifier or source map.');
const noJs = verifyNoJavaScriptDocument(html);
const { assetPaths, socialImage } = verifyMetadata(html, canonical.href);
if (assetPaths.length === 0 || assetPaths.some((path) => path.endsWith('.map'))) throw new Error('Production HTML lacks a safe hashed asset reference.');
const assetResponse = await request(fetchImpl, new URL(assetPaths[0], canonical), { headers: { 'accept-encoding': 'br, gzip' } });
if (assetResponse.status !== 200) throw new Error('Production asset did not return 200.');
verifySecurityHeaders(assetResponse, options.expectedManifestDigest);
requireHeader(assetResponse, 'cache-control', /max-age=31536000.*immutable/u);
requireHeader(assetResponse, 'content-type', /^(?:text\/javascript|application\/javascript|text\/css)(?:;|$)/u);
requireHeader(assetResponse, 'content-encoding', /^(?:br|gzip)$/u);
const assetText = await assetResponse.text();
if (privateIdentifiers.test(assetText) || sourceMapReference.test(assetText)) throw new Error('Production asset exposes a private identifier or source map.');
const socialImageResponse = await request(fetchImpl, socialImage);
if (socialImageResponse.status !== 200) throw new Error('Production social image did not return 200.');
verifySecurityHeaders(socialImageResponse, options.expectedManifestDigest);
requireHeader(socialImageResponse, 'cache-control', /max-age=31536000.*immutable/u);
const socialImageType = socialImageResponse.headers.get('content-type');
if (socialImageType === null || !new RegExp(`^${publicMetadata.socialImageType}(?:;|$)`, 'u').test(socialImageType)) {
throw new Error('Production social image content-type is missing or unsupported.');
}
await countBoundedResponseBody(
socialImageResponse,
'Production social image',
PERFORMANCE_BUDGETS.projectStill,
);
for (const path of ['/robots.txt', '/sitemap.xml']) {
const response = await request(fetchImpl, new URL(path, canonical));
if (response.status !== 200) throw new Error(`${path} did not return 200.`);
verifySecurityHeaders(response, options.expectedManifestDigest);
requireHeader(response, 'cache-control', /max-age=0.*must-revalidate/u);
const text = await response.text();
if (path === '/robots.txt' && (!/Allow:\s*\//iu.test(text) || !text.includes(`${canonical.href}sitemap.xml`))) throw new Error('robots.txt is inconsistent with the canonical sitemap.');
if (path === '/sitemap.xml' && !text.includes(`<loc>${canonical.href}</loc>`)) throw new Error('sitemap.xml is inconsistent with the canonical URL.');
}
let videoRangeVerified = false;
if (options.videoPath !== undefined) {
if (!/^\/assets\/[a-z0-9][a-z0-9._-]*\.(?:mp4|webm)$/u.test(options.videoPath)) throw new Error('Video path must be one safe public MP4/WebM asset.');
const response = await request(fetchImpl, new URL(options.videoPath, canonical), { headers: { range: 'bytes=0-1023' } });
if (response.status !== 206) throw new Error('Production video range request did not return 206.');
verifySecurityHeaders(response, options.expectedManifestDigest);
requireHeader(response, 'accept-ranges', 'bytes'); requireHeader(response, 'content-range', /^bytes 0-1023\/\d+$/u);
requireHeader(response, 'content-type', /^video\/(?:mp4|webm)$/u); requireHeader(response, 'cache-control', /max-age=31536000.*immutable/u);
videoRangeVerified = true;
}
return { schema: 'portfolio-production-smoke/v1', canonicalUrl: canonical.href, manifestDigest: options.expectedManifestDigest,
jsDisabledContentVerified: true, headings: noJs.headings, assetCompressionVerified: true,
socialImageVerified: true, videoRangeVerified };
}
function parseArguments(args) {
const values = { alternateUrls: [] };
for (let index = 0; index < args.length; index += 2) {
const key = args[index]; const value = args[index + 1];
if (!key?.startsWith('--') || !value) throw new Error('Smoke arguments must be complete name/value pairs.');
const name = key.slice(2);
if (name === 'alternate-url') values.alternateUrls.push(value);
else if (Object.hasOwn(values, name)) throw new Error(`Duplicate smoke argument: ${key}`);
else values[name] = value;
}
if (!values['canonical-url'] || !values['manifest-digest']) throw new Error('Usage: smoke-production.mjs --canonical-url <https-root> --manifest-digest <sha256> [--alternate-url <url>] [--video-path <path>].');
return values;
}
async function main() {
const values = parseArguments(process.argv.slice(2));
const result = await verifyProductionSmoke({ canonicalUrl: values['canonical-url'], expectedManifestDigest: values['manifest-digest'],
alternateUrls: values.alternateUrls, videoPath: values['video-path'] });
process.stdout.write(`${JSON.stringify(result)}\n`);
}
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
main().catch((error) => { process.stderr.write(`Production smoke failed: ${error instanceof Error ? error.message : 'unknown error'}\n`); process.exitCode = 1; });
}