Skip to content

Instantly share code, notes, and snippets.

@z0rs
Created March 7, 2026 09:34
Show Gist options
  • Select an option

  • Save z0rs/a395aedf95732267b3d87a923751dd7f to your computer and use it in GitHub Desktop.

Select an option

Save z0rs/a395aedf95732267b3d87a923751dd7f to your computer and use it in GitHub Desktop.

Repository Audit Report β€” z0rs.github.io

Date: 2026-03-07
Reviewer: SWE / DevOps / Security


PROJECT OVERVIEW

This is a Gatsby 5 static blog / personal site for a security researcher ("Eno Leriand"). It publishes MDX-based articles and CTF write-ups, rendered with React 18 and styled with Tailwind CSS v3 + the typography plugin. Content is stored under content/ (articles, CTFs, pages) and source code lives in src/. The site is deployed automatically to GitHub Pages via a GitHub Actions workflow on every push to master. Serverless Gatsby Functions in src/api/ provide a reaction system (FaunaDB), newsletter subscription (ConvertKit via axios), and a Google Analytics UA visitor-map endpoint.


ISSUES FOUND

πŸ”΄ CRITICAL β€” Build will fail / Security risk

# Location Issue
1 package.json gatsby-plugin-mdx@^3.20.0 is incompatible with gatsby@^5.x. The v3 plugin targets Gatsby 4. Gatsby 5 requires gatsby-plugin-mdx@^5.x. This mismatch causes a hard build failure.
2 .gitignore .env is tracked by git β€” the gitignore only excludes .env.development and .env.production. The base .env (with real credentials in production) will be committed to the repository.
3 .github/workflows/gatsby.yml Double build: after the artifact upload step, a second yarn run build is unconditionally run, rebuilding the entire site after it has already been uploaded.
4 .github/workflows/gatsby.yml NODE_TLS_REJECT_UNAUTHORIZED: 0 disables TLS certificate verification for the entire build, creating an MITM attack surface in CI.

🟠 HIGH β€” Broken functionality / Missing dependencies

# Location Issue
5 package.json dotenv is missing from dependencies. gatsby-config.js calls require('dotenv').config(…) at the top level. Relying on it as an undeclared transitive dependency is fragile.
6 package.json axios is missing from dependencies. src/api/newsletter.js does const axios = require('axios') but axios is nowhere in package.json. The newsletter endpoint will throw at runtime.
7 src/api/ua-analytics.js Uses the deprecated Universal Analytics (UA) v3 API (google.analytics('v3')). Google shut down UA data access on July 1, 2024. Every call to this endpoint returns an error. Needs migration to the GA4 Data API (@google-analytics/data).
8 src/api/fauna-add-reaction.js No input validation: JSON.parse(req.body) throws an unhandled exception on malformed input. Field values are written to FaunaDB without sanitisation.

🟑 MEDIUM β€” Incorrect configuration / Bad patterns

# Location Issue
9 gatsby-config.js pathPrefix: "/z0rs.github.io" is wrong for a user page. The CNAME file contains z0rs.github.io, indicating this is a GitHub user site (not a project site). With the prefix set, all internal links resolve to https://z0rs.github.io/z0rs.github.io/… β€” a double-path URL.
10 .github/workflows/gatsby.yml Ineffective cache key: hashFiles('public') hashes the public/ directory, which does not exist before the build runs. The cache will never produce a warm hit on a clean runner. Should hash the lockfile instead.
11 src/api/*.js (all 5 files) Mixed CJS require() and ESM export default in the same file. This works today because Gatsby transpiles them, but it is an inconsistent anti-pattern that will break if the code is ever moved out of the Gatsby Functions pipeline.
12 package.json react and react-dom pinned to exact 18.0.0 (no caret). Patch and security releases (e.g. 18.3.x) are silently excluded. Should be ^18.0.0.
13 gatsby-config.js siteUrl: process.env.URL but URL is not defined in .env or the CI env vars, so gatsby-plugin-sitemap generates an invalid sitemap with an undefined base URL.

🟑 MEDIUM β€” Typos causing silent failures

# Location Issue
14 tailwind.config.js (Γ—7) fontWieght typo (should be fontWeight) in the typography plugin configuration β€” h1 through h6 and a elements. Tailwind silently ignores the unknown key, so no heading is actually bold.

πŸ”΅ LOW β€” Unused / outdated packages

# Location Issue
15 package.json gatsby-plugin-gatsby-cloud@^5.0.0 is a deployment adapter for Gatsby Cloud, which is unrelated to GitHub Pages and adds unnecessary install/build overhead.
16 package.json gatsby-plugin-google-analytics@^5.0.0 is listed as a dependency but is not included in the plugins array in gatsby-config.js. It is dead weight.
17 package.json @mdx-js/mdx@^1.6.22 and @mdx-js/react@^1.6.22 are MDX v1 packages. gatsby-plugin-mdx@3 uses MDX v2 internally β€” these v1 packages are version-mismatched peer dependencies.
18 package.json cmdk@^0.1.18 β€” latest stable is 1.x. The 0.1.x API is substantially different; upgrading is a minor breaking change, but the outdated version should be noted.
19 .nvmrc Pins v18.0.0 exactly. CI uses "18" (resolves to latest 18.x). They should match.

FIXES

Fix 1 β€” gatsby-plugin-mdx version (CRITICAL)

Upgrade from v3 to v5 to match Gatsby 5. Also remove the mismatched @mdx-js/mdx and @mdx-js/react v1 packages; gatsby-plugin-mdx@5 bundles its own MDX v3.

Fix 2 β€” .gitignore β€” stop tracking .env (CRITICAL)

Uncomment (or add) the .env line so the base env file is not committed.

Fix 3 β€” CI workflow β€” remove double build, fix cache key, remove TLS bypass (CRITICAL)

Merge the memory flag into the single build step; fix the cache key to hash yarn.lock; remove NODE_TLS_REJECT_UNAUTHORIZED.

Fix 4 β€” package.json β€” add missing dotenv and axios, fix react version range, remove unused plugins (HIGH)

Fix 5 β€” tailwind.config.js β€” fix fontWieght β†’ fontWeight (MEDIUM)

Fix 6 β€” gatsby-config.js β€” remove incorrect pathPrefix, add SITE_URL note (MEDIUM)

Fix 7 β€” src/api/fauna-add-reaction.js β€” add basic input validation (HIGH)


PATCH

--- a/package.json
+++ b/package.json
@@ -10,6 +10,7 @@
   "scripts": {
     "develop": "gatsby develop",
     "clean": "gatsby clean",
     "start": "gatsby develop",
     "build": "gatsby build && yarn robots",
     "serve": "gatsby serve",
     "robots": "cp robots.txt public/",
     "prettier": "prettier --config ./.prettierrc.js --ignore-path ./.prettierignore --write \"**/*.{json,js,ts,tsx,mdx}\"",
     "prepare": "husky install"
   },
@@ -20,16 +21,13 @@
   "dependencies": {
     "@google-analytics/data": "^3.0.0",
-    "@mdx-js/mdx": "^1.6.22",
-    "@mdx-js/react": "^1.6.22",
+    "axios": "^1.6.0",
     "@react-three/drei": "^9.22.4",
     "@react-three/fiber": "^8.3.1",
     "cmdk": "^0.1.18",
     "country-flag-icons": "^1.5.5",
     "d3-geo": "^3.0.1",
+    "dotenv": "^16.0.0",
     "dotted-map": "^2.2.3",
     "faunadb": "^4.6.0",
     "gatsby": "^5.0.0",
-    "gatsby-plugin-gatsby-cloud": "^5.0.0",
-    "gatsby-plugin-google-analytics": "^5.0.0",
     "gatsby-plugin-image": "^3.0.0",
-    "gatsby-plugin-mdx": "^3.20.0",
+    "gatsby-plugin-mdx": "^5.0.0",
     "gatsby-plugin-sharp": "^5.0.0",
     "gatsby-plugin-sitemap": "^6.0.0",
     "gatsby-source-filesystem": "^5.0.0",
     "gatsby-transformer-json": "^5.0.0",
     "gatsby-transformer-sharp": "^5.0.0",
     "googleapis": "^105.0.0",
     "prism-react-renderer": "^1.3.5",
-    "react": "18.0.0",
-    "react-dom": "18.0.0",
+    "react": "^18.0.0",
+    "react-dom": "^18.0.0",
     "rehype-autolink-headings": "6.1.1",
     "rehype-slug": "5.0.1",
     "three": "^0.143.0",
     "three-geojson-geometry": "^1.1.7"
   },
--- a/.gitignore
+++ b/.gitignore
@@ -80,7 +80,7 @@
 # dotenv environment variables file
-#.env
+.env
 .env.test
 .env.development
 .env.production
--- a/.github/workflows/gatsby.yml
+++ b/.github/workflows/gatsby.yml
@@ -43,7 +43,7 @@
       - name: Setup Node
         uses: actions/setup-node@v4
         with:
-          node-version: "18"
+          node-version-file: ".nvmrc"
           cache: ${{ steps.detect-package-manager.outputs.manager }}
@@ -53,7 +53,7 @@
       - name: Restore cache
         uses: actions/cache@v4
         with:
           path: |
             public
             .cache
-          key: ${{ runner.os }}-gatsby-build-${{ hashFiles('public') }}
+          key: ${{ runner.os }}-gatsby-build-${{ hashFiles('**/yarn.lock') }}
           restore-keys: |
             ${{ runner.os }}-gatsby-build-
       - name: Install dependencies
         run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
       - name: Build with Gatsby
         env:
           PREFIX_PATHS: 'true'
-          NODE_TLS_REJECT_UNAUTHORIZED: 0
+          NODE_OPTIONS: "--max-old-space-size=4096"
         run: ${{ steps.detect-package-manager.outputs.runner }} gatsby build
       - name: Upload artifact
         uses: actions/upload-pages-artifact@v3
         with:
           path: ./public
-      - name: Increase memory limit and run build
-        run: |
-          export NODE_OPTIONS="--max-old-space-size=4096"
-          yarn run build
--- a/tailwind.config.js
+++ b/tailwind.config.js
@@ -106,7 +106,7 @@
             h1: {
               color: theme('colors.text'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.text')
               }
             },
             h2: {
               color: theme('colors.salmon'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.salmon')
               }
             },
             h3: {
               color: theme('colors.salmon'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.salmon')
               }
             },
             h4: {
               color: theme('colors.salmon'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.salmon')
               }
             },
             h5: {
               color: theme('colors.salmon'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.salmon')
               }
             },
             h6: {
               color: theme('colors.salmon'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
               a: {
                 color: theme('colors.salmon')
               }
             },
             strong: {
               color: theme('colors.text')
             },
             a: {
               color: theme('colors.secondary'),
-              fontWieght: theme('font-bold'),
+              fontWeight: '700',
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -6,8 +6,8 @@
 module.exports = {
-  pathPrefix: "/z0rs.github.io",
-  // flags: {
+  // pathPrefix removed: CNAME sets domain to z0rs.github.io (user page β€” no prefix needed).
+  // Re-enable only if deploying as a project page under a different username.
+  // flags: {
   //   FAST_DEV: true
   // },
   trailingSlash: 'always',
   siteMetadata: {
     name: 'Eno Leriand',
     description: 'Security & Research',
     ...
-    siteUrl: process.env.URL,
+    siteUrl: process.env.SITE_URL || 'https://z0rs.github.io',
     defaultImage: '/images/76135196.jpeg'
   },
   plugins: [
-    'gatsby-plugin-gatsby-cloud',
     'gatsby-plugin-image',
     ...
--- a/src/api/fauna-add-reaction.js
+++ b/src/api/fauna-add-reaction.js
@@ -1,9 +1,22 @@
 const faunadb = require('faunadb');
 
+const ALLOWED_REACTIONS = ['heart', 'thumbsup', 'fire', 'party'];
+const MAX_STRING_LEN = 500;
+
 export default async function handler(req, res) {
-  const { title, slug, reaction, date } = JSON.parse(req.body);
+  let body;
+  try {
+    body = typeof req.body === 'string' ? JSON.parse(req.body) : req.body;
+  } catch {
+    return res.status(400).json({ message: 'Invalid JSON body' });
+  }
+
+  const { title, slug, reaction, date } = body || {};
+
+  if (!slug || typeof slug !== 'string' || slug.length > MAX_STRING_LEN) {
+    return res.status(400).json({ message: 'Invalid or missing slug' });
+  }
+  if (!reaction || !ALLOWED_REACTIONS.includes(reaction)) {
+    return res.status(400).json({ message: `reaction must be one of: ${ALLOWED_REACTIONS.join(', ')}` });
+  }
+  if (title && (typeof title !== 'string' || title.length > MAX_STRING_LEN)) {
+    return res.status(400).json({ message: 'Invalid title' });
+  }
 
   const q = faunadb.query;
   const client = new faunadb.Client({ secret: process.env.FAUNA_KEY });
--- a/.env
+++ b/.env.example
@@ -1,6 +1,9 @@
+# Copy this file to .env and fill in your values.
+# .env is git-ignored. Never commit real credentials.
 GATSBY_API_URL=
 GATSBY_TWITTER_USERNAME=
 GATSBY_GA_MEASUREMENT_ID=
 CK_FORM_ID=
 CK_API_KEY=
+SITE_URL=https://z0rs.github.io
 FAUNA_KEY=

SUMMARY TABLE

Severity Count Fixed in patch
πŸ”΄ Critical 4 4
🟠 High 4 3 (UA analytics requires full rewrite)
🟑 Medium 6 4
πŸ”΅ Low 5 3

Manual action required (not auto-patchable)

  1. src/api/ua-analytics.js β€” UA (v3) API is permanently shut down. Must rewrite to use the GA4 Data API (@google-analytics/data, already in package.json). This is a significant API change and needs the GA4 property ID from your Google Analytics account.
  2. src/api/*.js β€” Standardise on ESM (import/export) or CJS (require/module.exports) β€” do not mix them in the same file.
  3. content/articles/*.mdx β€” 19 of 19 articles are missing a status field in frontmatter. The GraphQL query in gatsby-node.js filters { status: { ne: "draft" } }. Without the field, articles rely on null β‰  "draft" being truthy, which works but is fragile. Add status: published to each frontmatter block.
  4. package.json β€” After applying the gatsby-plugin-mdx@^5 upgrade, run yarn install and verify that the wrapESMPlugin shim in gatsby-config.js is no longer needed (v5 handles ESM plugins natively).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment