diff --git a/client/blog-astro/src/pages/index.astro b/client/blog-astro/src/pages/index.astro index 0e6cbe9..4390500 100644 --- a/client/blog-astro/src/pages/index.astro +++ b/client/blog-astro/src/pages/index.astro @@ -8,8 +8,54 @@ const blogPosts = Array.isArray(blogIndex?.posts) ? blogIndex.posts : []; +const isCybersecurityWriteup = (item) => { + if (!item || typeof item !== 'object') { + return false; + } + + const kind = String(item.kind || '') + .trim() + .toLowerCase(); + + if (kind === 'blog' || kind === 'webcomic') { + return false; + } + + if (kind === 'ctf') { + return true; + } + + if ( + item.ctf && + typeof item.ctf === 'object' && + ( + item.ctf.event || + item.ctf.category || + item.ctf.difficulty || + item.ctf.points + ) + ) { + return true; + } + + const haystack = [ + item.title, + item.excerpt, + item.description, + item.category, + ...(Array.isArray(item.tags) ? item.tags : []), + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + + return /\b(ctf|cyber|cybersecurity|security|dfir|forensic|forensics|osint|reverse engineering|reversing|binary exploitation|pwn|web exploitation|web security|cryptography|crypto challenge|malware|incident response|soc|vulnerability|pentest|penetration testing|network security|packet|threat intel|threat intelligence)\b/.test( + haystack + ); +}; + const ctfWriteups = Array.isArray(ctfIndex?.writeups) - ? ctfIndex.writeups + ? ctfIndex.writeups.filter(isCybersecurityWriteup) : []; const comics = Array.isArray(webcomicIndex?.comics) diff --git a/client/src/App.jsx b/client/src/App.jsx index d7b2070..490a9d3 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -3769,6 +3769,54 @@ function WebcomicWildCard({ ); } +function isCybersecurityWriteup(item) { + if (!item || typeof item !== 'object') { + return false; + } + + const kind = + String(item.kind || '') + .trim() + .toLowerCase(); + + if (kind === 'webcomic' || kind === 'blog') { + return false; + } + + if (kind === 'ctf') { + return true; + } + + if ( + item.ctf && + typeof item.ctf === 'object' && + ( + item.ctf.event || + item.ctf.category || + item.ctf.difficulty || + item.ctf.points + ) + ) { + return true; + } + + const haystack = [ + item.title, + item.excerpt, + item.description, + item.category, + item.issuer, + ...(Array.isArray(item.tags) ? item.tags : []), + ] + .filter(Boolean) + .join(' ') + .toLowerCase(); + + return /\b(ctf|cyber|cybersecurity|security|dfir|forensic|forensics|osint|reverse engineering|reversing|binary exploitation|pwn|web exploitation|web security|cryptography|crypto challenge|malware|incident response|soc|vulnerability|pentest|penetration testing|network security|packet|threat intel|threat intelligence)\b/.test( + haystack + ); +} + function CTFBlogIndexView() { const [ctfItems, setCtfItems] = useState([]); @@ -4106,6 +4154,231 @@ function CTFBlogIndexView() { ); } +function CTFCatalogView() { + const [items, setItems] = + useState([]); + const [loading, setLoading] = + useState(true); + + useEffect(() => { + let active = true; + + async function loadCatalog() { + const candidates = [ + BASE_PATH + + 'ctf_blog/index.json', + BASE_PATH + + 'ctf-blog/index.json', + ]; + + for (const url of candidates) { + try { + const response = + await fetch(url); + + if (!response.ok) { + continue; + } + + const data = + await response.json(); + + const writeups = + Array.isArray(data?.writeups) + ? data.writeups + : []; + + if (active) { + setItems( + writeups.filter( + isCybersecurityWriteup + ) + ); + setLoading(false); + } + + return; + } catch { + // Try the legacy path next. + } + } + + if (active) { + setItems([]); + setLoading(false); + } + } + + loadCatalog(); + + return () => { + active = false; + }; + }, []); + + const heroImage = blogAsset( + 'images/blog-hero-sakura-catgirl.webp' + ); + + const cards = + items.length + ? items + : Array.from( + { length: 6 }, + (_, index) => ({ + __placeholder: true, + id: + 'ctf-catalog-placeholder-' + + index, + }) + ); + + return ( + <> + + +
+
+ + +
+ +
+ + CYBERSECURITY / CTF ARCHIVE + + +

+ Challenge notes, +
+ + evidence & exploits. + +

+ +

+ This archive intentionally shows + only cybersecurity and CTF-related + writeups. General blog posts and + webcomics stay under /blog. +

+ +
+ DFIR + OSINT + WEB + REV + PWN + CRYPTO +
+
+
+ +
+
+
+ + SECURITY-ONLY INDEX + + +

+ CTF & cybersecurity +

+ +

+ Filtered from the published CTF + index using document type, CTF + metadata, tags and security + keywords. +

+
+ + + {items.length} published + +
+ + {loading ? ( +
+ + Loading security archive... + +
+ ) : ( +
+ {cards.map((item, index) => ( + + ))} +
+ )} +
+ +
+
+ + SCOPE BOUNDARY + + +

+ Security work only. +

+ +

+ Personal notes, general systems + articles and webcomics remain on + the main blog instead of leaking + into the CTF archive. +

+
+ + +
+
+ + ); +} + function BlogArticleView({ slug }) { const [post, setPost] = useState(null); @@ -4690,12 +4963,15 @@ export default function App({ initialContent = null }) { CTF BLOG ROUTES ======================================================= */ + if (ctfPath === '/blog') { + return ; + } + if ( - ctfPath === '/blog' || ctfPath === '/ctf_blog' || ctfPath === '/ctf-blog' ) { - return ; + return ; } if (