Skip to content

Instantly share code, notes, and snippets.

@l-portet
Created December 12, 2025 02:35
Show Gist options
  • Select an option

  • Save l-portet/845ab6bd953e36f9996d5fb1d0487bc9 to your computer and use it in GitHub Desktop.

Select an option

Save l-portet/845ab6bd953e36f9996d5fb1d0487bc9 to your computer and use it in GitHub Desktop.
const TIMEOUT_MS = 5000;
const CHECK_INTERVAL_MS = 500;
let startTime = Date.now();
const parseVersion = (versionStr) => {
if (!versionStr) return null;
const [main, pre] = versionStr.split('-');
const [major, minor, patch] = main.split('.').map(Number);
let canaryVal = 0;
if (pre && pre.includes('canary')) {
const parts = pre.split('.');
canaryVal = parts.length > 1 ? Number(parts[1]) : 0;
}
return {
major, minor, patch,
isCanary: !!pre,
canaryVal,
raw: versionStr
};
};
const isVulnerable = (v) => {
if (!v) return false;
// Next.js 14: Only Canary 14.3.0-canary.77+ is affected. Stable is safe.
if (v.major === 14) {
if (!v.isCanary) return false; // Stable 14 is safe
if (v.minor < 3) return false;
if (v.minor > 3) return true;
// Minor is 3
if (v.patch > 0) return true;
// Patch is 0, check canary build
return v.canaryVal >= 77;
}
// Next.js 15: Fixed in specific patch versions
if (v.major === 15) {
const fixes = { 0: 5, 1: 9, 2: 6, 3: 6, 4: 8, 5: 7 };
// If exact minor is known, check patch level
if (fixes.hasOwnProperty(v.minor)) {
return v.patch < fixes[v.minor];
}
// If minor is older than checked versions (unlikely for v15), vuln.
// If minor is newer (e.g. 15.6), assume fix carried over (Safe).
return v.minor < 0;
}
// Next.js 16: Fixed in 16.0.7
if (v.major === 16) {
if (v.minor === 0) return v.patch < 7;
return false; // Assume 16.1+ is safe
}
return false;
};
const showWarning = (version) => {
const el = document.createElement('div');
el.innerHTML = `
<div style="font-weight: 700; margin-bottom: 4px;">⚠️ Security Warning</div>
Next.js v${version} is vulnerable.<br/>
<span style="font-size: 0.85em; opacity: 0.9;">Update to a patched release.</span>
`;
Object.assign(el.style, {
position: 'fixed', top: '20px', right: '20px',
background: '#fff3cd', color: '#856404', border: '1px solid #ffeeba',
padding: '12px 16px', borderRadius: '6px',
fontFamily: 'system-ui, sans-serif', fontSize: '14px',
zIndex: '9999999', boxShadow: '0 4px 12px rgba(0,0,0,0.1)',
pointerEvents: 'none'
});
document.body.appendChild(el);
console.warn(`[Security] Next.js v${version} is vulnerable.`);
};
const check = () => {
const versionStr = window?.next?.version;
if (!versionStr) return;
const v = parseVersion(versionStr);
if (isVulnerable(v)) {
showWarning(v.raw);
} else {
console.log(`[Security] Next.js v${v.raw} is safe.`);
}
};
const interval = setInterval(() => {
// 1. Check if ready
if (window?.next?.version) {
clearInterval(interval);
check();
return;
}
// 2. Check timeout (5 seconds)
if (Date.now() - startTime > TIMEOUT_MS) {
clearInterval(interval);
console.log('[Security] Timeout: Next.js version not detected within 5s.');
}
}, CHECK_INTERVAL_MS);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment