Skip to content

joeseverino/jseverino.com

Repository files navigation

jseverino.com

build codeql dependency review scorecard lighthouse

Personal cybersecurity portfolio for Joe Severino, built with Astro, sourced from a private Obsidian vault, and deployed as static output on Cloudflare Pages.

The repository is the public, sanitized build source. The private vault is the editorial source of truth. Cloudflare builds only from committed files in this repo; it does not need access to the vault.

Private Obsidian vault -> sanitized repo snapshot -> Astro build -> Cloudflare Pages

What This Repo Does

  • Builds a static personal site with Astro 6.4.
  • Syncs public pages, portfolio writeups, and technology taxonomy from a private vault.
  • Rewrites local image references into public asset paths.
  • Generates AVIF, WebP, and optimized fallback image variants.
  • Records image dimensions in src/lib/image-manifest.json so rendered images include stable width and height attributes.
  • Emits canonical metadata, Open Graph/Twitter metadata, JSON-LD, sitemap, RSS, and robots.txt.
  • Uses Cloudflare Pages Functions only where dynamic behavior is required: CSP nonce injection, CSP violation reporting, contact form submission handling, and the scoped read-only preview review proxy.
  • Wraps non-production Cloudflare Pages deployments with sitedrift, providing a compact DEV-versus-LIVE review toolbar with synchronized navigation, visual comparison, response deltas, and per-page SEO checks. Production output is not wrapped.

Repository Map

Path Purpose
src/pages/ Astro routes for pages, portfolio, tags, RSS, robots.txt, and errors.
src/layouts/BaseLayout.astro Shared document shell, preload, header, footer, and SEO head.
src/components/ Reusable UI and metadata components.
src/lib/content.ts Markdown rendering, custom directive transforms, content loading, taxonomy lookup, and site chrome parsing.
src/content/pages/ Sanitized synced page Markdown.
src/content/writeups/ Sanitized synced portfolio Markdown.
src/content/technology-groups.md Synced public taxonomy for technology labels and groups.
src/lib/site.ts Site identity, social links, and navigation (typed config, derived from src/lib/site.mjs).
public/assets/ Static site assets organized by bucket: docs/ (downloadable documents), fonts/, icons/, og/ (Open Graph cards), pages/<slug>/ and writeups/<slug>/ (vault-synced page and writeup assets). See Architecture §11 Asset Organization for the convention.
public/_headers Static Cloudflare security headers. CSP is issued per-request by the middleware (not set here).
public/_redirects Static Cloudflare redirects.
functions/_middleware.ts Per-request HTML CSP nonce generation and script nonce injection.
functions/api/contact.ts Contact form endpoint with Turnstile, validation, rate limiting, and D1 storage.
functions/api/csp-report.ts CSP violation report receiver with noise filtering and D1 storage.
functions/__sitedrift/[[path]].ts Read-only preview-review proxy scoped to /__sitedrift/*.
db/schema.sql D1 schema for contact submissions and CSP reports.
public/schemas/cordon-v4.json Hosted JSON Schema for the Cordon command-surface contract, served at /schemas/ (its $id). Published copy; canonical source lives in the cordon repo.
bin/sync-content.mjs Vault-to-repo sync, metadata allowlisting, asset copy, image optimization, and manifest generation.
bin/publish-check.mjs Local release gate: clean, sync, the registry audits, build, and the post-build checks (assets, links, page weight, SEO). Also run by CI on every push.

Content Model

The private vault is organized as:

06 Pages/
  _technology-groups.md
  home/index.md
  about/index.md
  contact/index.md
  portfolio/index.md
  privacy/index.md
  resume/index.md

05 Writeups/
  project-slug/
    index.md
    images/

bin/sync-content.mjs copies only published content and only allowed frontmatter fields. Vault-only fields such as internal IDs, systems, related projects, sensitivity, and operator notes are dropped by omission. Local assets are resolved against their source directory and refused if they escape that directory.

Page frontmatter may include an explicit path. If omitted, the site falls back to / for home and /<slug>/ for other pages. Writeup URLs come from their folder slug. An optional intro field renders as the on-page subtitle below the H1; pages without one fall back to description, so SEO meta and visible subtitle stay coupled by default.

Image Pipeline

During sync, image references are collected from Markdown and frontmatter. Optimizable images are processed into:

  • AVIF at 512, 1024, and 1600 px widths.
  • WebP at 512, 1024, and 1600 px widths.
  • An optimized fallback file.

The generated paths and intrinsic dimensions are written to src/lib/image-manifest.json. Picture.astro uses that manifest to output responsive <picture> markup with stable dimensions, which prevents layout shift without hand-maintained image metadata.

Image encodes are cached under node_modules/.cache/jseverino-img by source-content hash. The cache speeds local syncs but is not part of the public source of truth.

Brand

The favicons, HD marks, and social cards are generated, not hand-drawn. src/lib/brand.mjs holds the site's identity (navy #1E3A8A plus the JS glyph); the rendering logic lives in a standalone, public package, branding-engine (npm). The site is just a consumer: bin/make-icons.mjs, bin/make-og-image.mjs, and bin/make-github-social.mjs pass BRAND to the engine and write to the repo's own paths.

branding-engine is an optionalDependency pinned to a published, provenance-attested npm version. Because the generated assets in public/assets/ are committed, the production build never runs the engine — if install can't fetch it, the optional install is skipped and the static build is unchanged. The engine runs only locally, on demand, to regenerate. The full story (one navy identity, then a shared engine) is in docs/Brand-System.md.

Metadata And SEO

SeoHead.astro emits:

  • Canonical URL.
  • Open Graph and Twitter card metadata.
  • JSON-LD for WebSite, Person, Article, and BreadcrumbList where applicable.
  • robots noindex where requested.

The Person schema reads from src/lib/site.ts, so the displayed identity, social links, and structured data stay aligned. Portfolio writeups pass published and reviewed dates into Article schema.

Every non-production Pages deployment also includes sitedrift's SEO inspection panel. It renders DEV and LIVE snippets together, compares title, description, and canonical metadata, and checks headings, viewport, language, Open Graph, indexing directives, favicon, and image alt coverage.

Deployment Preview Review

Cloudflare branch and version previews open in compact sitedrift Solo mode, showing the preview deployment as DEV and https://jseverino.com as LIVE. Reviewers can switch to Split or Overlay/Diff, mirror links and scrolling, inspect response timing deltas, open the SEO comparison, and keep notes in that browser's localStorage.

Red DEV and navy LIVE compared in sitedrift Split view

This comparison connects two tools I built. branding-engine turned one temporary source-of-truth change from navy to red into a coordinated favicon, wordmark, theme, Open Graph card, and social preview. sitedrift then wrapped the immutable branch deployment and compared it directly with the unchanged production site. The result proves both sides of the workflow: generate a consistent brand from one decision, then review the complete deployed effect before merging it.

DEV and LIVE SEO checks in sitedrift

The review goes beyond appearance. It compares metadata and SEO checks, response timing and transfer deltas, pixel differences, and browser-local notes while keeping production untouched.

The integration is deliberately preview-only:

  • sitedrift cloudflare activates only when CF_PAGES=1 and CF_PAGES_BRANCH is not main.
  • Production remains ordinary Astro output.
  • /__sitedrift/* accepts only GET and HEAD.
  • The LIVE proxy is fixed to https://jseverino.com.
  • Contact and CSP-report routes are not modified.
  • Preview responses remain noindex.

See Deployment Preview Review for the workflow, architecture, security boundary, verification steps, and the frozen red-brand comparison.

Security Model

The public site is static HTML, CSS, JavaScript, and assets. There is no WordPress runtime, no public database-backed page renderer, no admin panel, no comments, no uploads, and no account system.

Dynamic behavior is intentionally narrow:

  • functions/_middleware.ts runs for HTML responses, generates a nonce, adds it to every <script>, emits a nonce-bearing CSP, and advertises the CSP report endpoint. public/_headers carries the other security headers; CSP is issued only per-request by the middleware.
  • functions/api/contact.ts accepts contact submissions, verifies Turnstile server-side, validates input, applies a per-IP hourly limit, and stores accepted messages in Cloudflare D1 with parameterized SQL.
  • functions/api/csp-report.ts receives browser CSP violation reports, drops extension/off-site noise, and stores compact records in the same D1 database for review.

Local Commands

The ones that matter day to day:

# Daily
npm run dev                # Start the Astro dev server
npm run sync:content       # Sync published vault content into the repo
npm run diagnose           # Run every check; writes .validation-report.md on failure
npm run diff:build         # Build HEAD vs the working tree and report any change to shipped output

# Release
npm run publish:check      # Fast local build gate (add -- --no-sync for code-only changes)
npm run publish:check:ci   # Rehearse the CI gate: CI=1 + a scratch keyring, before pushing workflow changes
npm run release:check      # Trusted deterministic gate; also fails if validation changes repo state
npm run deploy:verify      # After push: verify remote checks and the deployed production artifact

Every other script — asset generation, scaffolding, the individual audits the gates compose — is in docs/Commands.md, an overview by role followed by the detail per command. npm run help prints the live grouped list straight from package.json, and a unit test asserts the reference covers every script, so neither can drift.

The personal site CLI wraps these commands for day-to-day publishing, but the npm scripts are the canonical repo-local interface. Its site manage TUI puts the whole surface on one screen — the featured order and publish state of every writeup on one tab, and an operations dashboard (servers, git, build, security signature, live-site probe, publish-gate summary) with the doctor/diagnose/build/test/publish commands runnable in place on the other. See docs/Site-CLI.md.

site manage Site tab: status dashboard with the dev server running, content counts, and the inline action list

The testing suite, local quality audits, repository policies, and visual baselines are toured in tests/README.md and documented in full in tests/ARCHITECTURE.md.

Validation & Testing

Every change is verified across four layers: local Node audits that assert invariants about the source, unit tests for the pure logic (the markdown DSL, the Cloudflare Pages functions, the gate harness itself), Playwright specs that drive the built site in a real browser, and post-push probes that re-check the live deploy. Together they cover signed security metadata, WCAG contrast and an axe accessibility sweep, schema parity on both boundaries (vault/Zod/MCP and handler/OpenAPI/D1), strict types on the serverless functions, serverless request handling, internal link integrity, structural HTML, page-weight budgets, cross-browser functional flows, pixel baselines, and live header/CSP checks.

The everyday entry point is npm run diagnose — the collect-all gate. It runs every check in the inventory without stopping at the first failure: a green run prints a single line, a red run writes .validation-report.md with each failure, its remediation, and the exact command to rerun that one check. --fast runs only the static checks, --no-tests skips the browser suite, and --json emits a machine-readable result for agents and CI.

Start with the tour in tests/README.md; the full reference, with every script, spec, code example, and the troubleshooting tree, is tests/ARCHITECTURE.md.

Validation flow from source change through local gates, push, and deployment verification

Diagram source: docs/diagrams/validation-flow.mmd, pre-rendered with diagram.

GitHub Actions Continuous Integration

In addition to local pre-commit checks, continuous security and build audits are run on every push and pull request to main:

Workflow Purpose Verified Result
build Runs the registry publish gate (publish:check --no-sync) in a clean container — the committed tree must pass everything the local gate passes, minus the vault parity check (its sources live only on the authoring machine). Green gate on the committed tree & CycloneDX SBOM artifact.
codeql Scans JavaScript and TypeScript source files for semantic vulnerabilities. Clean GitHub code scanning dashboard (zero open alerts).
dependency review Audits manifest package updates for high-severity advisories. Pull request status validation.
scorecard Computes OpenSSF security scorecard health metrics. Weekly SARIF supply-chain reports.
workflow lint Lints GitHub Action runner steps using actionlint. PR/push syntax validation.
link check Audits all Markdown documentation and public links via Lychee. Detailed connectivity report artifacts.
lighthouse Benchmarks performance, accessibility, and SEO using Lighthouse CI. 100/100/100/100 score compliance.

Workflow dependencies are pinned to immutable SHAs or container digests. Every workflow declares a top-level permissions: contents: read and scopes any security-events: write to the specific job that uploads SARIF. Dependabot still checks npm weekly and GitHub Actions monthly via .github/dependabot.yml.

The GitHub code-scanning dashboard is kept at zero open alerts. CodeQL findings are fixed at the source; OpenSSF Scorecard findings that do not apply to a solo personal repo (Branch-Protection, Code-Review, Fuzzing, CII-Best-Practices, Maintained until the repo turns 90 days old) are dismissed with a "won't fix — solo personal repo" reason and an inline explanation. The current local Scorecard aggregate is 6.4 / 10 (2026-05-29) — failing checks are structural to a one-person project and are not real security gaps.

Preview deployments (*.pages.dev) carry an X-Robots-Tag: noindex from public/_headers so only the canonical custom domain ever lands in search results.

Current PageSpeed Snapshot

Google PageSpeed Insights reported a clean 100 across every scored category for the live homepage on May 27, 2026 at 9:14 PM CDT.

Mode Performance Accessibility Best Practices SEO FCP LCP TBT CLS Speed Index
Mobile, emulated Moto G Power / Slow 4G 100 100 100 100 0.9 s 1.8 s 0 ms 0 1.4 s
Desktop, emulated desktop / custom throttling 100 100 100 100 0.3 s 0.5 s 0 ms 0 0.4 s

The PDFs used as evidence were exported from PageSpeed Insights for https://jseverino.com/ with Lighthouse 13.3.0. The run also passed the trust-and-safety checks for effective CSP, strong HSTS, and Trusted Types mitigation.

Cloudflare Operations

The Pages project owns runtime bindings in the Cloudflare dashboard; this repo intentionally has no wrangler.toml. The shared D1 binding is named DB and points at jseverino-contact.

Apply the D1 schema after any change to db/schema.sql:

wrangler d1 execute jseverino-contact --remote --file=./db/schema.sql

Check CSP reports after deployment:

wrangler d1 execute jseverino-contact --remote --command "SELECT created_at, effective_directive, blocked_uri, document_uri FROM csp_reports ORDER BY created_at DESC LIMIT 20;"

Generated And Local Files

Do not commit:

  • node_modules/, node_modules.*
  • .astro/, .vite/
  • dist/, dist.nosync/
  • .env*, .dev.vars*
  • .claude/, .gemini/
  • .DS_Store
  • iCloud conflict copies such as home 2.md

bin/clean-generated.mjs removes generated output and resolves iCloud conflict copies before publish checks.

Documentation

  • docs/Architecture.md explains the build, content, rendering, image, and edge architecture.
  • docs/Brand-System.md tells how the site's brand became one navy identity rendered by the standalone branding-engine.
  • docs/Vault-Workflow.md explains the private-to-public sync contract.
  • docs/Blueprint-Setup.md inventories every instance-specific value (identity, brand, edge config, dashboard) for reuse as a blueprint.
  • docs/Authoring-Guide.md documents supported Markdown extensions.
  • docs/Commands.md is the full command reference — every npm script by role, with detail per command.
  • docs/Site-CLI.md documents the personal site CLI and the site manage TUI that drive publishing day to day.
  • docs/SEO.md documents canonical URLs, structured data, discovery files, and metadata flow.
  • docs/Deployment-Preview-Review.md documents the sitedrift-powered Cloudflare preview review workflow and production guard.
  • docs/Accessibility.md documents landmarks, skip navigation, alt text, focus behavior, reduced motion, keyboard coverage, and contrast posture.
  • docs/WordPress-To-Astro-Migration.md documents the platform migration decision and performance comparison.
  • docs/Release-Checklist.md documents preflight, publish, signed tag, deploy, header, SEO, and accessibility checks.
  • SECURITY.md documents the security posture and vulnerability reporting process.
  • LICENSE covers the original source code, written content, and images in this repository. The repo is published for transparency and review; no rights are granted to copy, modify, or redistribute without prior written permission.

History

This site moved from WordPress to Astro in early 2026. The main reason was architectural simplification: remove the public origin runtime, remove plugin/admin attack surface, and make the shipped site a reviewable static artifact. The migration rationale and measured comparison are documented in docs/WordPress-To-Astro-Migration.md.

About

Personal cybersecurity portfolio at jseverino.com, built with Astro and published to Cloudflare Pages from a private notes vault.

Topics

Resources

License

Security policy

Stars

Watchers

Forks

Contributors