Fix CSP violations from Cloudflare Radar scan

Two duplicate <meta> CSP tags in index.html conflicted with the
_headers HTTP header, and browsers enforce the intersection of all
active policies. That blocked the profile photo, the inline nav-
highlight script, and the Cloudflare Insights beacon.

Consolidate to a single CSP in _headers, allow the actual external
resources the page needs (ki5bhv.com image, cloudflareinsights.com
beacon), and move the inline script/onerror handler into js/main.js
so script-src no longer needs 'unsafe-inline'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Justin 2026-08-30 16:28:33 -05:00
parent 5d6eae87bf
commit c7a10ae719
3 changed files with 26 additions and 23 deletions

View File

@ -1,5 +1,5 @@
/* /*
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests Content-Security-Policy: default-src 'self'; script-src 'self' https://static.cloudflareinsights.com; style-src 'self' https://fonts.googleapis.com 'unsafe-inline'; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://ki5bhv.com data:; connect-src 'self' https://cloudflareinsights.com; object-src 'none'; base-uri 'none'; frame-ancestors 'none'; form-action 'self'; upgrade-insecure-requests
X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff
X-Frame-Options: DENY X-Frame-Options: DENY
Referrer-Policy: strict-origin-when-cross-origin Referrer-Policy: strict-origin-when-cross-origin

View File

@ -6,9 +6,6 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="referrer" content="strict-origin-when-cross-origin" /> <meta name="referrer" content="strict-origin-when-cross-origin" />
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' https://ki5bhv.com data:; connect-src 'none'; frame-src 'none'; object-src 'none'; base-uri 'self'; form-action 'none'; upgrade-insecure-requests" />
<meta http-equiv="Content-Security-Policy"
content="default-src 'self'; script-src 'self' https://static.cloudflareinsights.com; connect-src 'self' https://cloudflareinsights.com; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; object-src 'none'; base-uri 'none'; form-action 'self'">
<title>Justin Frasier | Infrastructure Administrator</title> <title>Justin Frasier | Infrastructure Administrator</title>
<link rel="sitemap" type="application/xml" href="/sitemap.xml" /> <link rel="sitemap" type="application/xml" href="/sitemap.xml" />
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='4' fill='%230f1218'/%3E%3Ctext x='3' y='11' font-family='IBM Plex Mono,Courier New,monospace' font-size='6' fill='%234a5568'%3E~$%3C/text%3E%3Ctext x='2' y='27' font-family='IBM Plex Mono,Courier New,monospace' font-size='19' font-weight='600' fill='%2322c55e'%3Ejf%3C/text%3E%3C/svg%3E" /> <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' rx='4' fill='%230f1218'/%3E%3Ctext x='3' y='11' font-family='IBM Plex Mono,Courier New,monospace' font-size='6' fill='%234a5568'%3E~$%3C/text%3E%3Ctext x='2' y='27' font-family='IBM Plex Mono,Courier New,monospace' font-size='19' font-weight='600' fill='%2322c55e'%3Ejf%3C/text%3E%3C/svg%3E" />
@ -27,8 +24,7 @@
<!-- ── Sidebar ── --> <!-- ── Sidebar ── -->
<aside> <aside>
<img class="profile-photo" src="https://ki5bhv.com/photo/justin1.jpg" alt="Justin Frasier" <img class="profile-photo" src="https://ki5bhv.com/photo/justin1.jpg" alt="Justin Frasier">
onerror="this.style.display='none'">
<div class="profile-name">Justin Frasier</div> <div class="profile-name">Justin Frasier</div>
<div class="profile-title">Infrastructure Administrator</div> <div class="profile-title">Infrastructure Administrator</div>
<div class="profile-loc">📍 Bossier City, LA</div> <div class="profile-loc">📍 Bossier City, LA</div>
@ -370,23 +366,7 @@
</main> </main>
</div> </div>
<script> <script src="js/main.js" defer></script>
// Highlight active nav link on scroll
const sections = document.querySelectorAll('section[id]');
const links = document.querySelectorAll('nav a');
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
links.forEach(l => l.classList.remove('active'));
const active = document.querySelector(`nav a[href="#${e.target.id}"]`);
if (active) active.classList.add('active');
}
});
}, { threshold: 0.3 });
sections.forEach(s => observer.observe(s));
</script>
</body> </body>

23
js/main.js Normal file
View File

@ -0,0 +1,23 @@
// Highlight active nav link on scroll
const sections = document.querySelectorAll('section[id]');
const links = document.querySelectorAll('nav a');
const observer = new IntersectionObserver(entries => {
entries.forEach(e => {
if (e.isIntersecting) {
links.forEach(l => l.classList.remove('active'));
const active = document.querySelector(`nav a[href="#${e.target.id}"]`);
if (active) active.classList.add('active');
}
});
}, { threshold: 0.3 });
sections.forEach(s => observer.observe(s));
// Hide the profile photo if it fails to load
const profilePhoto = document.querySelector('.profile-photo');
if (profilePhoto) {
profilePhoto.addEventListener('error', () => {
profilePhoto.style.display = 'none';
});
}