Add red-team shadow dossiers site

This commit is contained in:
danny 2025-12-25 12:06:50 +00:00
parent cb928248cb
commit 17f096cc3e
33 changed files with 3854 additions and 2 deletions

10
.gitignore vendored
View file

@ -1,14 +1,20 @@
.DS_Store
.env
.env.*
.env.local
.env.*.local
.env.development
.env.test
.env.production
.env.staging
.venv
__pycache__/
*.pyc
dist/
build/
.pytest_cache/
node_modules/
.vite/
# Re-voice workspace
tmp/
*.log

View file

@ -0,0 +1,2 @@
VITE_PUBLIC_BASE_URL=
VITE_ENABLE_ROAST=false

View file

@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View file

@ -0,0 +1,46 @@
import React from 'react';
import { Header } from './components/Header';
import { Hero } from './components/Hero';
import { LeakViewer } from './components/LeakViewer';
import { PricingTiers } from './components/PricingTiers';
import { RoastGenerator } from './components/RoastGenerator';
import { Footer } from './components/Footer';
const App: React.FC = () => {
return (
<div className="min-h-screen selection:bg-red-ink selection:text-white overflow-x-hidden">
<Header />
<main>
<Hero />
<LeakViewer />
<RoastGenerator />
<PricingTiers />
{/* Evidence CTA */}
<section className="py-16 md:py-20 px-6 border-t hairline">
<div className="max-w-6xl mx-auto">
<div className="rounded-xl border hairline bg-white p-8 md:p-10 flex flex-col md:flex-row md:items-center md:justify-between gap-6">
<div>
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Verification</div>
<h2 className="text-2xl md:text-3xl font-extrabold tracking-tight text-slate-900">Evidence index & trace verification</h2>
<p className="text-slate-700 mt-2 max-w-2xl">
Browse hashes, trace bundles, and reproducibility notes. If you want the bite, keep the proof.
</p>
</div>
<a
href="#dossiers"
target="_blank" rel="noopener noreferrer"
className="inline-flex items-center justify-center rounded-md bg-slate-900 text-white px-6 py-3 text-sm font-semibold hover:bg-slate-800 transition-colors"
>
Evidence index (staged)
</a>
</div>
</div>
</section>
</main>
<Footer />
</div>
);
};
export default App;

View file

@ -0,0 +1,75 @@
# CODEX Implementation Guide (Server + Launch Wiring)
This repo is a **pre-launch staging front-end**. It is intentionally safe:
- No “live” PDF/evidence links are active unless `dossier.status === 'live'`
- No model API keys are shipped to the browser
- The roast generator (if enabled) calls a server endpoint
## 1) Publish static artifacts
Target paths (recommended):
- PDF: `/static/hosted/snyk-shadow-dossier.pdf`
- Evidence index: `/static/hosted/evidence/index.html`
### What to ship
- The free public PDF
- Evidence index HTML + any referenced assets (bundles, hashes, trace files)
## 2) Flip dossiers to LIVE
Edit `constants.tsx`:
- Set:
- `status: 'live'`
- `pdfPath: '/static/hosted/<file>.pdf'`
- `evidencePath: '/static/hosted/evidence/index.html'`
Optional:
- Add `sha256` and `bytes` (use `node scripts/hash.mjs path/to.pdf`)
- Add `gumroadUrl` for classified edition
- Confirm `contactEmail`
## 3) Configure base URL (optional)
If you deploy under a domain/subdomain and want absolute links, set:
- `VITE_PUBLIC_BASE_URL=https://infrafabric.io` (or your subdomain)
If unset, the app uses relative links (works fine for same-origin hosting).
## 4) Roast Generator (optional)
### Current behavior
- UI is hidden unless: `VITE_ENABLE_ROAST=true`
- Client calls: `POST /api/roast` with JSON `{ "content": "..." }`
- Expected response: `{ "text": "..." }`
### Implementing the endpoint
You can implement this in whichever server environment you use. Examples are in `server_stubs/`.
Required behavior:
- Validate request body
- Rate limit (recommended)
- Call your LLM provider using a **server-side** secret
- Return JSON `{ text }`
### Provider dependency
If you want Gemini, add `@google/genai` server-side and use `server_stubs/gemini_client_example.ts` as a starting point.
## 5) Replace staged UI labels (optional)
Staged labels are intentional pre-launch.
Once live, you can:
- Rename “Evidence (staged)” back to “Evidence”
- Replace “Request early access” with “Download PDF”
- Enable premium tier by adding Gumroad URL in `constants.tsx`
## 6) Final checklist
- [ ] PDF link works (no 404)
- [ ] Evidence index link works
- [ ] `status` set to `live`
- [ ] Optional: SHA-256 + bytes present and correct
- [ ] No secret keys shipped to client bundles
- [ ] OpenGraph image exists at `/og-dossier.jpg`
- [ ] Favicon present at `/favicon.svg`

View file

@ -0,0 +1,64 @@
# InfraFabric Red Team — Shadow Dossiers (Front-End)
A minimalist React + Vite publication hub for InfraFabrics “Shadow Dossiers” drops:
- serious, paper/ink layout
- staged/publication statuses (draft/scheduled/live)
- verifiability hooks (evidence index + optional SHA-256 + size)
- **no client-side model keys** (roast generator is feature-flagged and calls a server endpoint)
## Local dev
Prereqs: Node.js 18+
```bash
npm install
npm run dev
```
Open: http://localhost:3000
## Production (self-host)
```bash
npm install
npm run build
PORT=8080 npm run start
```
## Environment variables
Create `.env.local` from `.env.example`:
- `VITE_PUBLIC_BASE_URL`
Optional. When set (e.g. `https://infrafabric.io`) it will prefix dossier `pdfPath`/`evidencePath`.
- `VITE_ENABLE_ROAST`
Defaults to `false`. When `true`, renders the Roast Generator UI which calls `POST /api/roast`.
## Staging behavior (important)
This bundle is **pre-launch**. Dossier buttons are gated by `dossier.status`:
- `draft` / `scheduled`: download + evidence links remain disabled, and the UI shows “staged.”
- `live`: links activate (and optional SHA-256/bytes render if populated).
Update dossier status and paths in `constants.tsx`.
## Codex/server implementation checklist
See `CODEX_IMPLEMENTATION.md` for the complete checklist and stubs.
Quick summary:
1. Publish PDFs and evidence pages at:
- `/static/hosted/<dossier>.pdf`
- `/static/hosted/evidence/index.html`
2. Set `VITE_PUBLIC_BASE_URL` in the deployment environment if needed.
3. (Optional) Implement `POST /api/roast` server-side and set `VITE_ENABLE_ROAST=true`.
## Hash helper
Compute SHA-256 + bytes for a PDF and paste into the dossier entry:
```bash
node scripts/hash.mjs /path/to/file.pdf
```

View file

@ -0,0 +1,18 @@
import React from 'react';
export const Disclaimer: React.FC = () => (
<div id="disclaimer" className="rounded-lg border hairline bg-white p-5">
<div className="flex items-center justify-between gap-3 mb-3">
<span className="mono text-[11px] uppercase tracking-wide text-slate-500">Legal notice</span>
<span className="inline-flex items-center rounded-full border hairline px-2.5 py-1 mono text-[11px] text-slate-600">
Satire / parody
</span>
</div>
<p className="text-sm leading-relaxed text-slate-700">
This site contains satirical critical commentary. The Shadow Dossiers are performance art intended to highlight
systemic issues in governance, compliance, and security tooling. Nothing here is presented as a statement of fact
about any specific organization. Verification artifacts (hashes, traces, evidence index) are provided to support
reproducibility of the published materials.
</p>
</div>
);

View file

@ -0,0 +1,58 @@
import React from 'react';
export const Footer: React.FC = () => {
return (
<footer className="py-12 px-6 border-t hairline bg-white">
<div className="max-w-6xl mx-auto">
<div className="grid md:grid-cols-12 gap-10">
<div className="md:col-span-5">
<div className="flex items-center gap-3 mb-3">
<div className="w-9 h-9 rounded-md border hairline bg-slate-900 text-white flex items-center justify-center font-extrabold text-sm">
IF
</div>
<div>
<div className="font-semibold text-slate-900">InfraFabric Red Team</div>
<div className="mono text-[11px] text-slate-500">Shadow Dossiers</div>
</div>
</div>
<p className="text-sm text-slate-700 leading-relaxed max-w-md">
Satirical critical commentary, published alongside verification artifacts (hashes, traces, evidence index).
</p>
</div>
<div className="md:col-span-7 grid sm:grid-cols-3 gap-8">
<div>
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-3">Links</div>
<ul className="space-y-2 text-sm">
<li><a className="underline hover:no-underline" href="#dossiers">Dossiers</a></li>
<li><a className="underline hover:no-underline" href="#editions">Editions</a></li>
<li><a className="underline hover:no-underline" href="#dossiers" target="_blank" rel="noopener noreferrer">Evidence index</a></li>
</ul>
</div>
<div>
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-3">Contact</div>
<ul className="space-y-2 text-sm">
<li><a className="underline hover:no-underline" href="mailto:dave@infrafabric.io">dave@infrafabric.io</a></li>
<li><a className="underline hover:no-underline" href="mailto:redteam@infrafabric.io">redteam@infrafabric.io</a></li>
<li><a className="underline hover:no-underline" href="https://x.com/IF_Dave" target="_blank" rel="noopener noreferrer">@IF_Dave</a></li>
</ul>
</div>
<div>
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-3">Notice</div>
<p className="text-sm text-slate-700 leading-relaxed">
This is satire/parody and critical commentary; not affiliated with any vendor. Nothing here is presented as a statement of fact.
</p>
<a className="mt-3 inline-block text-sm font-semibold underline hover:no-underline" href="#disclaimer">Read the disclaimer</a>
</div>
</div>
</div>
<div className="mt-10 pt-6 border-t hairline">
<p className="mono text-[11px] text-slate-500">
© 2025 InfraFabric. Shadow Dossiers.
</p>
</div>
</div>
</footer>
);
};

View file

@ -0,0 +1,29 @@
import React from 'react';
export const Header: React.FC = () => {
return (
<header className="sticky top-0 z-50 bg-[rgba(247,245,240,0.92)] backdrop-blur border-b hairline px-6 py-4">
<div className="max-w-6xl mx-auto flex justify-between items-center">
<div className="flex items-center gap-3">
<div className="w-8 h-8 rounded-md border hairline bg-white flex items-center justify-center font-extrabold text-slate-900 text-sm">
IF
</div>
<div className="leading-tight">
<div className="text-sm font-semibold text-slate-900">InfraFabric Red Team</div>
<div className="mono text-[11px] text-slate-500">Shadow Dossiers</div>
</div>
</div>
<nav className="flex items-center gap-6">
<div className="hidden md:flex gap-6 text-sm text-slate-700">
<a href="#dossiers" className="hover:text-slate-900">Dossiers</a>
<a href="#editions" className="hover:text-slate-900">Editions</a>
<a href="#dossiers" target="_blank" rel="noopener noreferrer" className="hover:text-slate-900">Evidence (staged)</a>
<a href="mailto:dave@infrafabric.io" className="hover:text-slate-900">Contact</a>
</div>
<a href="https://x.com/IF_Dave" target="_blank" rel="noopener noreferrer" className="mono text-[11px] text-slate-500 hover:text-slate-800">@IF_Dave</a>
</nav>
</div>
</header>
);
};

View file

@ -0,0 +1,50 @@
import React from 'react';
import { Disclaimer } from './Disclaimer';
export const Hero: React.FC = () => {
return (
<section className="pt-14 pb-16 px-6 border-b hairline">
<div className="max-w-6xl mx-auto">
<div className="grid lg:grid-cols-12 gap-10 items-start">
<div className="lg:col-span-7">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-4">
Publication · Red Team Commentary · Verifiable Artifacts
</div>
<h1 className="text-5xl md:text-6xl font-extrabold tracking-tight text-slate-900 leading-[1.05] mb-5">
Shadow Dossiers
</h1>
<p className="text-lg text-slate-700 leading-relaxed max-w-2xl mb-8">
Satirical, operatorgrade commentary on governance and compliance theaterpublished alongside an evidence index
so readers can verify whats being referenced.
</p>
<div className="flex flex-wrap gap-3">
<a
href="#dossiers"
className="inline-flex items-center justify-center rounded-md bg-slate-900 text-white px-5 py-3 text-sm font-semibold hover:bg-slate-800 transition-colors"
>
Browse dossiers
</a>
<a
href="#dossiers"
target="_blank" rel="noopener noreferrer" className="inline-flex items-center justify-center rounded-md border hairline bg-white px-5 py-3 text-sm font-semibold text-slate-900 hover:bg-slate-50 transition-colors"
>
Evidence index
</a>
<a
href="mailto:dave@infrafabric.io"
className="inline-flex items-center justify-center rounded-md border hairline bg-transparent px-5 py-3 text-sm font-semibold text-slate-700 hover:bg-white transition-colors"
>
Contact
</a>
</div>
</div>
<div className="lg:col-span-5">
<Disclaimer />
</div>
</div>
</div>
</section>
);
};

View file

@ -0,0 +1,169 @@
import React from 'react';
import { DOSSIERS } from '../constants';
import { resolvePublicUrl } from '../lib/urls';
const formatBytes = (bytes?: number) => {
if (!bytes) return null;
const units = ['B', 'KB', 'MB', 'GB'];
let b = bytes;
let i = 0;
while (b >= 1024 && i < units.length - 1) {
b /= 1024;
i += 1;
}
return `${b.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
};
const statusLabel = (status: string) => {
switch (status) {
case 'live':
return 'LIVE';
case 'scheduled':
return 'SCHEDULED';
case 'draft':
default:
return 'DRAFT';
}
};
export const LeakViewer: React.FC = () => {
return (
<section id="dossiers" className="py-16 md:py-20 px-6">
<div className="max-w-6xl mx-auto">
<div className="mb-10">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Dossiers</div>
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight text-slate-900">Current publications</h2>
</div>
<div className="space-y-8">
{DOSSIERS.map((d) => {
const isLive = d.status === 'live';
const pdfUrl = resolvePublicUrl(d.pdfPath);
const evidenceUrl = resolvePublicUrl(d.evidencePath);
return (
<article key={d.id} className="rounded-2xl border hairline bg-white shadow-sm overflow-hidden">
<div className="grid md:grid-cols-12">
<div className="md:col-span-4 p-6 md:p-8 bg-paper">
<div className="rounded-xl border hairline bg-white overflow-hidden">
<img
src={d.previewImageUrl}
alt={`Cover for Shadow Dossier: ${d.title}`}
className="w-full h-auto block"
loading="lazy"
/>
</div>
</div>
<div className="md:col-span-8 p-6 md:p-8">
<div className="flex flex-wrap items-center gap-2 mb-3">
<span className="mono text-[11px] text-slate-500">{d.leakDate}</span>
<span className="text-slate-300"></span>
<span className="inline-flex items-center rounded-full border hairline px-2.5 py-1 mono text-[11px] text-slate-600">
{d.classification}
</span>
<span className="inline-flex items-center rounded-full border hairline px-2.5 py-1 mono text-[11px] text-slate-600">
{statusLabel(d.status)}
</span>
<span className="text-slate-300"></span>
<span className="mono text-[11px] text-slate-500">{d.id}</span>
</div>
<h3 className="text-2xl md:text-3xl font-extrabold tracking-tight text-slate-900 mb-3">
{d.title}
</h3>
<p className="text-base md:text-lg text-slate-700 leading-relaxed mb-6">{d.summary}</p>
<div className="grid md:grid-cols-2 gap-6">
<div className="rounded-xl border hairline bg-paper p-4">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Verification</div>
{d.sha256 && isLive ? (
<div className="space-y-1">
<div className="mono text-[11px] text-slate-500">SHA-256</div>
<div className="mono text-[12px] text-slate-700 break-all">{d.sha256}</div>
{d.bytes ? (
<div className="mono text-[11px] text-slate-500">Size: {formatBytes(d.bytes)}</div>
) : null}
</div>
) : (
<div className="text-sm text-slate-600 leading-relaxed">
Integrity details will publish with the drop. Until then, link verification remains staged.
</div>
)}
{evidenceUrl && isLive ? (
<div className="mt-3">
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={evidenceUrl}
target="_blank"
rel="noopener noreferrer"
>
Verify (evidence index)
</a>
</div>
) : null}
</div>
<div className="rounded-xl border hairline bg-paper p-4">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Links</div>
<div className="flex flex-wrap gap-3 items-center">
{pdfUrl && isLive ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={pdfUrl}
target="_blank"
rel="noopener noreferrer"
>
Download PDF
</a>
) : (
<span className="text-sm font-semibold text-slate-400 cursor-not-allowed" title="Not published yet">
PDF not published
</span>
)}
{d.gumroadUrl ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={d.gumroadUrl}
target="_blank"
rel="noopener noreferrer"
>
Classified edition
</a>
) : null}
{d.contactEmail ? (
<a
className="text-sm font-semibold underline underline-offset-4 hover:no-underline"
href={`mailto:${d.contactEmail}?subject=${encodeURIComponent(`Shadow Dossier: ${d.title}`)}`}
>
Contact
</a>
) : null}
</div>
{!isLive ? (
<div className="mt-3 text-sm text-slate-600 leading-relaxed">
This dossier is staged. Links will activate when the drop is published.
</div>
) : null}
</div>
</div>
</div>
</div>
</article>
);
})}
</div>
</div>
</section>
);
};

View file

@ -0,0 +1,80 @@
import React from 'react';
import { PRICING_TIERS } from '../constants';
export const PricingTiers: React.FC = () => {
return (
<section id="editions" className="py-16 md:py-20 px-6 border-t hairline bg-white">
<div className="max-w-6xl mx-auto">
<div className="mb-10">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Editions & services</div>
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight text-slate-900">Choose an engagement</h2>
<p className="text-slate-700 mt-3 max-w-2xl">
The public drops are free. Paid editions fund ongoing work and include the working files. Custom dossiers are limited in capacity.
</p>
</div>
<div className="grid md:grid-cols-3 gap-6">
{PRICING_TIERS.map((tier) => (
<div
key={tier.name}
className={`rounded-xl border hairline p-7 flex flex-col ${
tier.variant === 'premium' ? 'bg-slate-900 text-white shadow-sm' : 'bg-white text-slate-900'
}`}
>
<div className="mb-6">
<div className={`mono text-[11px] uppercase tracking-[0.18em] ${tier.variant === 'premium' ? 'text-white/70' : 'text-slate-500'}`}>
{tier.name}
</div>
<div className="mt-3 text-3xl font-extrabold">{tier.price}</div>
<p className={`mt-3 text-sm leading-relaxed ${tier.variant === 'premium' ? 'text-white/80' : 'text-slate-700'}`}>
{tier.description}
</p>
</div>
<ul className={`flex-1 space-y-3 text-sm ${tier.variant === 'premium' ? 'text-white/85' : 'text-slate-700'}`}>
{tier.features.map((f, i) => (
<li key={i} className="flex items-start gap-3">
<span className={`${tier.variant === 'premium' ? 'text-white' : 'text-red-ink'} mt-0.5 font-bold`}></span>
<span>{f}</span>
</li>
))}
</ul>
{(() => {
const enabled = (tier.enabled !== false) && !!tier.actionUrl;
const isMailto = (tier.actionUrl || '').startsWith('mailto');
return (
<div className="mt-8">
<button
type="button"
disabled={!enabled}
onClick={() => {
if (!enabled || !tier.actionUrl) return;
window.open(tier.actionUrl, isMailto ? '_self' : '_blank', isMailto ? undefined : 'noopener,noreferrer');
}}
className={`w-full rounded-md py-3 text-sm font-semibold transition-colors ${
!enabled
? 'bg-slate-200 text-slate-500 cursor-not-allowed'
: tier.variant === 'premium'
? 'bg-white text-slate-900 hover:bg-slate-100'
: 'bg-slate-900 text-white hover:bg-slate-800'
}`}
title={!enabled ? (tier.disabledHint || 'Not available yet') : undefined}
>
{tier.cta}
</button>
{!enabled && tier.disabledHint ? (
<div className="mt-2 text-xs text-slate-500 leading-relaxed">{tier.disabledHint}</div>
) : null}
</div>
);
})()}
</div>
))}
</div>
</div>
</section>
);
};

View file

@ -0,0 +1,75 @@
import React, { useState } from 'react';
import { generateRoast } from '../services/roastClient';
import { ENABLE_ROAST_GENERATOR } from '../lib/flags';
export const RoastGenerator: React.FC = () => {
if (!ENABLE_ROAST_GENERATOR) return null;
const [input, setInput] = useState('');
const [output, setOutput] = useState('');
const [loading, setLoading] = useState(false);
const handleCritique = async () => {
if (!input.trim()) return;
setLoading(true);
try {
const result = await generateRoast(input);
setOutput(result);
} finally {
setLoading(false);
}
};
return (
<section id="review" className="py-16 md:py-20 px-6 bg-[rgba(247,245,240,0.6)] border-t hairline">
<div className="max-w-6xl mx-auto">
<div className="mb-10">
<div className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500 mb-2">Quick critique</div>
<h2 className="text-3xl md:text-4xl font-extrabold tracking-tight text-slate-900">Preliminary truth audit (AI-assisted)</h2>
<p className="text-slate-700 mt-3 max-w-3xl">
Paste a short excerpt from a policy, whitepaper, control description, or vendor claim. This produces a rough critique to help you
decide whether a custom dossier is worthwhile. Do not paste secrets.
</p>
</div>
<div className="grid md:grid-cols-2 gap-6">
<div className="rounded-xl border hairline bg-white p-6">
<label className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500">Input</label>
<textarea
value={input}
onChange={(e) => setInput(e.target.value)}
className="mt-3 w-full h-48 rounded-lg border hairline bg-slate-50 p-4 text-sm focus:outline-none focus:ring-2 focus:ring-[rgba(185,28,28,0.25)] resize-none"
placeholder="Paste an excerpt here…"
/>
<button
onClick={handleCritique}
disabled={loading}
className="mt-4 w-full rounded-md bg-slate-900 text-white py-3 text-sm font-semibold hover:bg-slate-800 disabled:opacity-50 transition-colors"
>
{loading ? 'Generating…' : 'Generate critique'}
</button>
<p className="mt-3 text-xs text-slate-500">
Output is best-effort commentary. For a real engagement, request availability via email.
</p>
</div>
<div className="rounded-xl border hairline bg-white p-6 relative">
<div className="flex items-center justify-between mb-3">
<span className="mono text-[11px] uppercase tracking-[0.18em] text-slate-500">Output</span>
<a href="mailto:dave@infrafabric.io" className="text-xs font-semibold underline hover:no-underline">Request custom dossier</a>
</div>
<div className="h-52 md:h-[19.5rem] overflow-auto rounded-lg border hairline bg-slate-50 p-4 text-sm leading-relaxed text-slate-800">
{output ? output : <span className="text-slate-400">No output yet.</span>}
</div>
{loading && (
<div className="absolute inset-0 bg-white/60 backdrop-blur-sm flex items-center justify-center rounded-xl">
<div className="w-8 h-8 border-2 border-slate-900 border-t-transparent rounded-full animate-spin"></div>
</div>
)}
</div>
</div>
</div>
</section>
);
};

View file

@ -0,0 +1,123 @@
import React from 'react';
import { Dossier, PricingTier } from './types';
const coverSvg = (title: string, id: string, classification: string) => {
// Inline SVG cover to avoid stock-photo vibe and keep distribution simple.
const svg = `
<svg xmlns="http://www.w3.org/2000/svg" width="800" height="1000" viewBox="0 0 800 1000">
<rect width="800" height="1000" fill="#f7f5f0"/>
<rect x="50" y="60" width="700" height="880" fill="#ffffff" stroke="#0f172a" stroke-opacity="0.12"/>
<rect x="50" y="60" width="700" height="70" fill="#0b0d10"/>
<text x="80" y="105" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial" font-size="22" fill="#ffffff" letter-spacing="1">
INFRAFABRIC · RED TEAM
</text>
<text x="80" y="205" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial" font-size="46" font-weight="800" fill="#0f172a">
Shadow Dossier
</text>
<text x="80" y="255" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial" font-size="24" fill="#334155">
${title.replace(/&/g, '&amp;')}
</text>
<rect x="80" y="320" width="250" height="34" rx="17" fill="#ffffff" stroke="#0f172a" stroke-opacity="0.18"/>
<text x="98" y="343" font-family="JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas" font-size="14" fill="#334155">
CLASSIFICATION: ${classification}
</text>
<text x="80" y="410" font-family="JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas" font-size="14" fill="#64748b">
DOSSIER_ID: ${id}
</text>
<line x1="80" y1="440" x2="720" y2="440" stroke="#0f172a" stroke-opacity="0.10"/>
<g opacity="0.35">
<text x="80" y="520" font-family="JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas" font-size="12" fill="#64748b">
This document is satirical/parody and critical commentary.
</text>
<text x="80" y="545" font-family="JetBrains Mono, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas" font-size="12" fill="#64748b">
Verification artifacts (hashes, traces) are provided via the evidence index.
</text>
</g>
<g opacity="0.18">
<text x="420" y="920" font-family="Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial" font-size="120" font-weight="900" fill="#b91c1c"
transform="rotate(-12 420 920)">SATIRE</text>
</g>
</svg>
`.trim();
return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
};
export const DOSSIERS: Dossier[] = [
{
id: 'snyk-mirror-01',
title: 'The Snyk-Mirror Anomaly',
leakDate: 'Dec 25, 2025',
summary:
'A satirical red-team dossier on compliance theater around AI code guardrails—where policy meets production and loses. Includes diagrams, failure modes, and a verifiability trail.',
classification: 'EYES-ONLY',
status: 'scheduled',
previewImageUrl: coverSvg('The Snyk-Mirror Anomaly', 'snyk-mirror-01', 'EYES-ONLY'),
// These are paths (not live yet). Codex/server will publish them when ready.
pdfPath: '/static/hosted/snyk-shadow-dossier.pdf',
evidencePath: '/static/hosted/evidence/index.html',
// Optional: set once sales are live.
// gumroadUrl: 'https://gumroad.com/l/your-product',
contactEmail: 'redteam@infrafabric.io',
// Optional: add sha256/bytes once you compute them at build time.
// sha256: '…',
// bytes: 1234567,
}
];
export const PRICING_TIERS: PricingTier[] = [
{
name: 'Public Drop (Free)',
price: '$0',
variant: 'free',
description: 'The public PDF drop. Satire/parody + critical commentary, published with a verification trail when live.',
features: [
'Full PDF drop (public)',
'Diagrams & artifacts intact',
'Evidence index link for verification',
'No email required'
],
cta: 'Request early access',
enabled: true,
actionUrl: 'mailto:redteam@infrafabric.io?subject=Shadow%20Dossier%20Early%20Access',
disabledHint: 'The public drop is not published yet.'
},
{
name: 'Classified Edition (PWYW)',
price: '$29+',
variant: 'premium',
description: 'Annotated PDF + sources (raw Markdown + Mermaid). Sales go live when the first drop publishes.',
features: [
'Annotated PDF (redink marginalia)',
'Raw Markdown source',
'Mermaid diagram sources',
'Personalized classification ID'
],
cta: 'Sales open soon',
enabled: false,
actionUrl: '',
disabledHint: 'Gumroad link will be added at launch.'
},
{
name: 'Custom Dossier (Service)',
price: '$499',
variant: 'enterprise',
description: 'A bespoke premortem/critique of your governance or security material, delivered as a dossier.',
features: [
'Review of your deck/report/controls',
'Findings + failure modes',
'Actionable mitigation notes',
'Delivery via email'
],
cta: 'Request availability',
enabled: true,
actionUrl: 'mailto:dave@infrafabric.io?subject=Custom%20Shadow%20Dossier%20Inquiry'
}
];

View file

@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>InfraFabric Red Team — Shadow Dossiers</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
<meta name="description" content="InfraFabric Red Team Shadow Dossiers: satirical critical commentary with verifiable artifacts (hashes, traces, evidence index).">
<meta property="og:title" content="InfraFabric Red Team — Shadow Dossiers">
<meta property="og:description" content="Satirical commentary on governance & compliance theater, published with verifiable artifacts.">
<meta property="og:image" content="/og-dossier.jpg">
<meta property="og:type" content="website">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@IF_Dave">
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;600;700&family=Inter:wght@400;500;600;700;800&display=swap" rel="stylesheet">
<style>
:root{
--paper:#f7f5f0;
--ink:#0f172a;
--muted:#334155;
--border:rgba(15,23,42,.12);
--accent:#b91c1c; /* subdued red */
}
body{
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: var(--paper);
color: var(--ink);
}
.mono{ font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; }
.red-ink{ color: var(--accent); }
.bg-red-ink{ background-color: var(--accent); }
.border-red-ink{ border-color: var(--accent); }
/* subtle, serious defaults */
a{ text-underline-offset: 3px; }
.hairline{ border-color: var(--border); }
.bg-paper{ background: var(--paper); }
/* keep scrollbars unobtrusive */
::-webkit-scrollbar{ width: 10px; height: 10px; }
::-webkit-scrollbar-track{ background: transparent; }
::-webkit-scrollbar-thumb{ background: rgba(15,23,42,.18); border-radius: 999px; border: 3px solid transparent; background-clip: padding-box; }
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/index.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,16 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
const rootElement = document.getElementById('root');
if (!rootElement) {
throw new Error("Could not find root element to mount to");
}
const root = ReactDOM.createRoot(rootElement);
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);

View file

@ -0,0 +1,2 @@
export const ENABLE_ROAST_GENERATOR =
((import.meta as any).env?.VITE_ENABLE_ROAST as string | undefined) === 'true';

View file

@ -0,0 +1,15 @@
export const getPublicBaseUrl = (): string => {
const raw = (import.meta as any).env?.VITE_PUBLIC_BASE_URL as string | undefined;
if (!raw) return '';
return raw.replace(/\/$/, '');
};
export const resolvePublicUrl = (path?: string): string | null => {
if (!path) return null;
// If already absolute, return as-is.
if (/^https?:\/\//i.test(path)) return path;
const base = getPublicBaseUrl();
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return `${base}${normalizedPath}`;
};

View file

@ -0,0 +1,5 @@
{
"name": "InfraFabric Red Team Shadow Dossiers",
"description": "A high-stakes, satirical front-end for the InfraFabric Red Team leaks, featuring classified dossiers, custom roasts, and operational realism theater.",
"requestFramePermissions": []
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,23 @@
{
"name": "infrafabric-red-team-shadow-dossiers",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview",
"start": "node server/server.mjs"
},
"dependencies": {
"express": "^4.21.2",
"react": "^19.2.3",
"react-dom": "^19.2.3"
},
"devDependencies": {
"@types/node": "^22.14.0",
"@vitejs/plugin-react": "^5.0.0",
"typescript": "~5.8.2",
"vite": "^6.2.0"
}
}

View file

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect x="2" y="2" width="60" height="60" rx="12" fill="#0b0d10"/>
<path d="M18 18h28v6H36v22h-6V24H18v-6z" fill="#ffffff"/>
<rect x="2" y="2" width="60" height="60" rx="12" fill="none" stroke="rgba(255,255,255,0.18)" stroke-width="2"/>
</svg>

After

Width:  |  Height:  |  Size: 310 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

View file

@ -0,0 +1,14 @@
import { createHash } from 'node:crypto';
import { readFileSync, statSync } from 'node:fs';
const path = process.argv[2];
if (!path) {
console.error('Usage: node scripts/hash.mjs /absolute/or/relative/path/to/file.pdf');
process.exit(1);
}
const buf = readFileSync(path);
const sha256 = createHash('sha256').update(buf).digest('hex');
const bytes = statSync(path).size;
console.log(JSON.stringify({ sha256, bytes }, null, 2));

View file

@ -0,0 +1,93 @@
import fs from "node:fs";
import path from "node:path";
import url from "node:url";
import express from "express";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const projectRoot = path.resolve(__dirname, "..");
const distDir = path.join(projectRoot, "dist");
const indexHtmlPath = path.join(distDir, "index.html");
function pickPhrases(input) {
const text = String(input || "").replace(/\r\n?/g, "\n");
const lines = text
.split("\n")
.map((l) => l.trim())
.filter(Boolean)
.slice(0, 200);
const interesting = [];
const needles = ["must", "should", "require", "required", "ensure", "enforce", "policy", "control", "audit", "compliance"];
for (const line of lines) {
const lower = line.toLowerCase();
if (needles.some((n) => lower.includes(n))) interesting.push(line);
if (interesting.length >= 4) break;
}
if (interesting.length) return interesting;
return lines.slice(0, 3);
}
function generateRoastText(content) {
const trimmed = String(content || "").trim();
const phrases = pickPhrases(trimmed);
const bullets = phrases.map((p) => `- ${p.length > 120 ? `${p.slice(0, 117)}` : p}`).join("\n");
return [
"We love the ambition here and are directionally aligned with the idea of \"secure rollout\" as long as we define secure as \"documented\" and rollout as \"phased.\"",
"",
"Key risk: this reads like a control narrative optimized for sign-off, not for the Friday-afternoon pull request that actually ships the code.",
"",
"Observed control theater (excerpt):",
bullets ? bullets : "- (no extractable claims detected)",
"",
"Recommendation: convert every \"should\" into an owner, a gate (PR/CI/access), and a stop condition. Otherwise this becomes an alignment session that reproduces itself indefinitely.",
].join("\n");
}
function main() {
const port = Number(process.env.PORT || 8080);
const app = express();
app.disable("x-powered-by");
app.use(express.json({ limit: "256kb" }));
app.get("/healthz", (_req, res) => {
res.status(200).json({ ok: true });
});
app.post("/api/roast", (req, res) => {
const content = String(req.body?.content ?? "");
if (!content.trim()) return res.status(400).json({ text: "Missing content" });
if (content.length > 20_000) return res.status(413).json({ text: "Content too large" });
return res.status(200).json({ text: generateRoastText(content) });
});
if (fs.existsSync(distDir) && fs.existsSync(indexHtmlPath)) {
app.use(express.static(distDir, { fallthrough: true }));
app.get("*", (_req, res) => {
res.setHeader("Content-Type", "text/html; charset=utf-8");
res.status(200).sendFile(indexHtmlPath);
});
} else {
app.get("*", (_req, res) => {
res
.status(503)
.type("text/plain")
.send("red-team site is not built yet. Run `npm install` then `npm run build`.");
});
}
app.listen(port, "0.0.0.0", () => {
// eslint-disable-next-line no-console
console.log(`red-team site listening on http://0.0.0.0:${port}`);
});
}
main();

View file

@ -0,0 +1,6 @@
# Express stub (optional)
If you prefer a small Node server, implement POST /api/roast and reverse-proxy behind your web server.
This is a skeleton only; do not ship without auth/rate limiting.

View file

@ -0,0 +1,16 @@
import express from 'express';
const app = express();
app.use(express.json({ limit: '64kb' }));
app.post('/api/roast', async (req, res) => {
const content = String(req.body?.content ?? '');
if (!content.trim()) return res.status(400).json({ text: 'Missing content' });
// TODO: server-side provider call
return res.status(501).json({ text: 'Not implemented' });
});
app.listen(8080, () => {
console.log('Server listening on http://localhost:8080');
});

View file

@ -0,0 +1,30 @@
import { GoogleGenAI } from "@google/genai";
const API_KEY = process.env.API_KEY || "";
export const generateRoast = async (content: string) => {
const ai = new GoogleGenAI({ apiKey: API_KEY });
try {
const response = await ai.models.generateContent({
model: 'gemini-3-flash-preview',
contents: `You are Dave, a cynical, veteran Red Team operator for InfraFabric. You specialize in "Operational Realism" and loathe "Compliance Theater."
A user has submitted a snippet of a corporate whitepaper or security report.
Your task is to write a short, sharp, satirical "Shadow Dossier" roast of this content.
Keep it brief (under 150 words). Use jargon like "audit trail graveyard", "vaporware shielding", "SOC2 cosplay", "compliance debt", and "vulnerability PR".
Focus on how the provided text is essentially high-effort fiction designed to appease auditors while leaving production wide open.
User content: "${content}"`,
config: {
systemInstruction: "You are 'Dave' from InfraFabric Red Team. Your tone is cynical, sharp, professional but satirical. You are allergic to corporate buzzwords unless used in mockery.",
temperature: 0.9,
}
});
return response.text || "Dave is currently busy burning another audit trail. Try again later.";
} catch (error) {
console.error("Roast error:", error);
return "Transmission failed. The corporate firewalls are thicker than their logic. (Check your API configuration).";
}
};

View file

@ -0,0 +1,27 @@
import type { VercelRequest, VercelResponse } from '@vercel/node';
/**
* POST /api/roast
* Request: { content: string }
* Response: { text: string }
*
* IMPORTANT: keep provider API keys server-side only.
*/
export default async function handler(req: VercelRequest, res: VercelResponse) {
if (req.method !== 'POST') {
res.status(405).json({ text: 'Method Not Allowed' });
return;
}
const content = (req.body?.content ?? '').toString();
if (!content.trim()) {
res.status(400).json({ text: 'Missing content' });
return;
}
// TODO (Codex): Call provider here (Gemini/OpenAI/etc.) using server env vars.
// Example:
// const text = await generateRoastWithGemini(content);
res.status(501).json({ text: 'Not implemented: wire server-side LLM provider here.' });
}

View file

@ -0,0 +1,24 @@
export interface RoastResponse {
text: string;
}
/**
* Client calls a server-side endpoint.
* IMPORTANT: Do not put model API keys in the browser bundle.
* Codex/server should implement POST /api/roast.
*/
export const generateRoast = async (content: string): Promise<string> => {
const resp = await fetch('/api/roast', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ content })
});
if (!resp.ok) {
return 'Roast endpoint unavailable. (Server has not implemented /api/roast yet.)';
}
const data = (await resp.json().catch(() => null)) as RoastResponse | null;
if (!data?.text) return 'No output returned.';
return data.text;
};

View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "ES2022",
"experimentalDecorators": true,
"useDefineForClassFields": false,
"module": "ESNext",
"lib": [
"ES2022",
"DOM",
"DOM.Iterable"
],
"skipLibCheck": true,
"types": [
"node"
],
"moduleResolution": "bundler",
"isolatedModules": true,
"moduleDetection": "force",
"allowJs": true,
"jsx": "react-jsx",
"paths": {
"@/*": [
"./*"
]
},
"allowImportingTsExtensions": true,
"noEmit": true
}
}

View file

@ -0,0 +1,44 @@
export type DossierStatus = 'draft' | 'scheduled' | 'live';
export interface Dossier {
id: string;
title: string;
leakDate: string; // display date
summary: string;
classification: 'UNCLASSIFIED' | 'CLASSIFIED' | 'EYES-ONLY';
status: DossierStatus;
previewImageUrl: string; // cover/thumbnail (data URI recommended)
/**
* Paths are preferred over absolute URLs for pre-launch staging.
* When deployed, VITE_PUBLIC_BASE_URL can be set to prefix these paths.
*/
pdfPath?: string; // e.g. /static/hosted/snyk-shadow-dossier.pdf
evidencePath?: string; // e.g. /static/hosted/evidence/index.html
/** Optional, for live deployments or external destinations (e.g. Gumroad). */
gumroadUrl?: string;
contactEmail?: string;
/** Optional verifiability fields; only render when populated with real values. */
sha256?: string; // preferred hash
bytes?: number; // file size
}
export interface PricingTier {
name: string;
price: string;
description: string;
features: string[];
cta: string;
variant: 'free' | 'premium' | 'enterprise';
/**
* If actionUrl is empty or enabled=false, the CTA renders disabled.
* Use this for pre-launch staging so the site does not feel like a mockup.
*/
actionUrl?: string;
enabled?: boolean;
disabledHint?: string;
}

View file

@ -0,0 +1,18 @@
import path from 'path';
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
export default defineConfig(() => {
return {
server: {
port: 3000,
host: '0.0.0.0',
},
plugins: [react()],
resolve: {
alias: {
'@': path.resolve(__dirname, '.'),
}
}
};
});