Lossless Changelog
product

Private GitHub Content Fetching for Confidential Memos

Confidential memos now stream from a private GitHub repository through a fine-grained token, with the file path derived automatically from the URL slug. No memo content lives in this repo at all.

Michael StatonMusic Editor

Changelog - 2025-12-12 (#3)

Hypernova Site: Private GitHub Content Fetching for Confidential Memos

Overview

Implemented a secure content fetching system that retrieves confidential investment memos from a private GitHub repository (lossless-group/hypernova-secure-data) at runtime. This keeps sensitive content out of deployed static assets while allowing authorized users to view memos after passcode authentication.

Key Achievement: Content is now fetched directly from a private GitHub repo using a fine-grained PAT, with automatic path derivation from URL slugs to GitHub file paths.


Architecture

User Request                    GitHub Private Repo
     │                               │
     ▼                               │
/memos/Example-Company-v002-draft              │
     │                               │
     ▼                               ▼
[Auth Check] ──────────────► [fetchMemoBySlug()]
     │                               │
     │                               ▼
     │                    [deriveGitHubPathFromSlug()]
     │                               │
     │                               ▼
     │                    deals/Example-Company/outputs/Example-Company-v0.0.2/
     │                         Example-Company-v0.0.2-draft.md
     │                               │
     ▼                               ▼
[Render Memo] ◄───────────── [Raw GitHub Content]

Changes by Area

1. New Library: src/lib/github-content.ts

Core library for fetching content from private GitHub repositories:

Features:

  • fetchGitHubContent() — Fetch raw markdown from GitHub via PAT authentication

  • listGitHubDirectory() — List files in a GitHub directory

  • fetchMemoBySlug() — High-level function to fetch memos by URL slug

  • deriveGitHubPathFromSlug() — Convert URL slugs to GitHub file paths

  • parseFrontmatter() — Extract YAML frontmatter from markdown

  • isLocalDemoMode() — Check if running in local fallback mode

  • 5-minute in-memory cache to reduce API calls

URL-to-Path Conversion:

URL Slug: Example-Company-v002-draft
GitHub Path: deals/Example-Company/outputs/Example-Company-v0.0.2/Example-Company-v0.0.2-draft.md

URL Slug: Second-Example-v003-draft
GitHub Path: deals/Second-Example/outputs/Second-Example-v0.0.3/Second-Example-v0.0.3-draft.md

The system converts URL-safe versions (v002) to dotted versions (v0.0.2) automatically.

2. Updated Memo Page: src/pages/memos/[slug].astro

SSR page that fetches memo content at runtime:

ASTRO
import { fetchMemoBySlug, isLocalDemoMode } from '@lib/github-content';

export const prerender = false;

// Auth check
const accessCookie = Astro.cookies.get('universal_portfolio_access');
if (!accessCookie?.value) {
  return Astro.redirect(`/portfolio-gate?redirect=${redirectPath}`);
}

// Fetch from GitHub (or local fallback)
const memo = await fetchMemoBySlug(slug);

Features:

  • Requires passcode authentication via /portfolio-gate

  • Shows "Local Demo Mode" banner when PAT not configured

  • Logs fetch mode to server console for debugging

3. Local Demo Mode

When GITHUB_CONTENT_PAT is empty, the system falls back to reading from src/content/markdown-memos/ for local testing:

  • Yellow banner: "Local Demo Mode — Content loaded from local files"

  • Server logs: [memos] LOCAL DEMO MODE - fetching "slug" from src/content/markdown-memos/

4. Configuration

Environment Variables (.env):

BASH
GITHUB_CONTENT_PAT=github_pat_xxxxx  # Fine-grained PAT with Contents read-only
GITHUB_CONTENT_OWNER=lossless-group
GITHUB_CONTENT_REPO=hypernova-secure-data
GITHUB_CONTENT_BRANCH=main

TypeScript Path Alias (tsconfig.json):

JSON
"@lib/*": ["src/lib/*"]

Files Changed Summary

New

  • src/lib/github-content.ts — GitHub content fetching library with caching, path derivation, and local fallback

Modified

  • src/pages/memos/[slug].astro — Now uses fetchMemoBySlug() from the new library

  • tsconfig.json — Added @lib/* path alias

  • .env — Added GitHub content configuration variables


Security Considerations

  1. Fine-grained PAT: Only needs Contents: Read-only permission on the single repository

  2. Server-side only: PAT is never exposed to client-side code

  3. SSR required: Page uses export const prerender = false to fetch at runtime

  4. Auth gated: Memo pages require passcode authentication before content loads


Testing

Local Demo Mode:

BASH
# Leave GITHUB_CONTENT_PAT empty in .env
pnpm dev
# Visit: http://localhost:4321/memos/Example-Company-v002-draft
# Should see yellow "Local Demo Mode" banner

GitHub Mode:

BASH
# Add PAT to .env
pnpm dev
# Visit: http://localhost:4321/memos/Second-Example-v005-draft
# No banner, content from GitHub
# Server console: [memos] GITHUB MODE - fetching...

Notes / Follow-Ups

  • For production (Vercel), add GITHUB_CONTENT_PAT as an environment variable

  • PAT expiration: Set reminders to rotate before expiry

  • Consider adding a cache-busting mechanism for content updates

  • The path derivation assumes the standard structure: deals/{Company}/outputs/{Company}-{version}/{slug}.md