diff --git a/.gitignore b/.gitignore index 453533d..2ae698b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,16 @@ .vscode/ .DS_Store Thumbs.db + +# mdBook output. Cloudflare Pages builds it on demand from src/. +book/ + +# Local mdBook binary cache if a contributor installs into the repo. +.mdbook-bin/ + +# Chapter copies in src/ are generated by scripts/build.sh from the +# canonical top-level Markdown files. They exist locally after the +# first build, but they are not source-of-truth and should not be +# committed. The only file in src/ that is tracked is SUMMARY.md. +src/*.md +!src/SUMMARY.md diff --git a/DEPLOY_CLOUDFLARE.md b/DEPLOY_CLOUDFLARE.md new file mode 100644 index 0000000..16df5ac --- /dev/null +++ b/DEPLOY_CLOUDFLARE.md @@ -0,0 +1,112 @@ +# Deploying to Cloudflare Pages + +This is the exact configuration the documentation site uses on +Cloudflare Pages. It is reproduced here so a future deployer can +rebuild the project from scratch without guessing at settings. + +## Project settings + +In the Cloudflare dashboard under Pages > Create a project > Connect +to Git, point at the `vyrox-security/vyrox-docs` repository and set +the following build configuration. + +| Field | Value | +|---|---| +| Production branch | `main` | +| Build command | `bash scripts/build.sh` | +| Build output directory | `book` | +| Root directory | `/` | +| Environment variable | `MDBOOK_VERSION=v0.4.40` | + +The build command downloads a pinned mdBook release into +`.mdbook-bin/` if the binary is not already on PATH, syncs the +top-level Markdown into `src/`, and runs `mdbook build`. Output goes +to `book/`, which is what Cloudflare Pages publishes. + +You can change the pinned mdBook version by updating +`MDBOOK_VERSION` either in the Cloudflare environment variables or +inline in `scripts/build.sh`. We keep one source of truth so a +release bump is one edit. + +## Custom domain + +Cloudflare Pages serves the project at `.pages.dev` by +default. The production deployment maps to `docs.vyrox.dev` via a +CNAME under the `vyrox.dev` zone. Configure that under Pages > the +project > Custom domains. + +## Headers and redirects + +Both files live at the repo root and are copied into `book/` by the +build script: + +- `_headers` carries the security headers (CSP, HSTS, frame-ancestors, + referrer policy, permissions policy) plus aggressive caching for + hashed assets and short revalidation for HTML. +- `_redirects` maps the friendly path aliases (`/architecture`, + `/api`, etc) to the mdBook-generated chapter URLs. + +The header CSP allows Google Fonts because the site loads Fraunces, +Geist, and JetBrains Mono from `fonts.googleapis.com` and +`fonts.gstatic.com`. Removing those allowances breaks typography. + +## Preview deployments + +Cloudflare Pages builds every branch automatically. A pull request +gets a preview URL of the form +`..pages.dev`. The default behaviour is fine +for our use case; we do not gate previews behind authentication +because the docs are public anyway. + +If a contributor adds private documentation in the future, gate the +preview environments via Cloudflare Access. The setting lives under +Pages > the project > Settings > Access policy. + +## Local reproduction + +Run the exact build that Cloudflare Pages runs: + +```bash +bash scripts/build.sh +``` + +That fetches mdBook into `.mdbook-bin/` (so your global install is +not touched), syncs the top-level Markdown into `src/`, and writes +the site into `book/`. Serve it with anything that serves static +files: + +```bash +python -m http.server -d book 8080 +``` + +Or use `mdbook serve` for the watch loop while editing. + +## Troubleshooting + +**Build fails with "mdbook not found".** The script tries to fetch a +binary from GitHub Releases. Cloudflare Pages build hosts have +outbound HTTPS so this works by default. If you see a download +failure, check the GitHub Releases page for the pinned tag and +verify the URL pattern. Newer mdBook releases sometimes change the +archive naming. + +**Build succeeds but the deploy 404s.** The output directory is +`book`, not `dist` or `public`. Double-check the Cloudflare project +settings. + +**Headers are not applied.** Cloudflare Pages reads `_headers` from +the root of the published directory. If you do not see your CSP on +production, confirm that `_headers` ended up inside `book/` after the +build. The build script copies it; if you ran `mdbook build` +directly without the script, the copy will not have happened. + +**Fonts render as the system default.** Either the CSP is blocking +the Google Fonts CDN, or the build is missing the Google Fonts link +tag. The link tag is injected by `theme/head.hbs`. Confirm with +`grep fonts.googleapis.com book/index.html` that the link is present. + +## Cross-references + +- [`README.md`](README.md) for the project overview. +- [`CONTRIBUTING.md`](CONTRIBUTING.md) for the docs contribution + workflow. diff --git a/_headers b/_headers new file mode 100644 index 0000000..06ea672 --- /dev/null +++ b/_headers @@ -0,0 +1,33 @@ +# Cloudflare Pages headers. +# +# This file is copied into the build output at deploy time. The hosting +# layer applies these headers to every matched request. Reference: +# https://developers.cloudflare.com/pages/configuration/headers/ +# +# We keep the rule set tight. Defaults are deny-by-default for the +# things that should never be there; explicit allows where the site +# legitimately reaches off-host (Google Fonts only). + +/* + X-Content-Type-Options: nosniff + X-Frame-Options: DENY + Referrer-Policy: strict-origin-when-cross-origin + Permissions-Policy: accelerometer=(), camera=(), geolocation=(), gyroscope=(), magnetometer=(), microphone=(), payment=(), usb=() + Strict-Transport-Security: max-age=31536000; includeSubDomains; preload + Content-Security-Policy: default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self' + +# Aggressive caching on hashed asset files. mdBook fingerprints every +# CSS, JS, and font filename so this is safe for them. +/css/* + Cache-Control: public, max-age=31536000, immutable + +/fonts/* + Cache-Control: public, max-age=31536000, immutable + +/FontAwesome/* + Cache-Control: public, max-age=31536000, immutable + +# HTML revalidates fast so a docs update lands within minutes of a +# Cloudflare Pages deploy without users seeing a stale page. +/*.html + Cache-Control: public, max-age=0, must-revalidate diff --git a/_redirects b/_redirects new file mode 100644 index 0000000..374e8ac --- /dev/null +++ b/_redirects @@ -0,0 +1,29 @@ +# Cloudflare Pages redirects. +# +# Lines are ` `. mdBook publishes every +# chapter at /.html where NAME is the source filename without +# the extension. We redirect the friendlier / paths (and the +# .md form a developer might type) to the canonical .html URL. +# +# Reference: https://developers.cloudflare.com/pages/configuration/redirects/ + +# Root convenience aliases. +/architecture /ARCHITECTURE.html 301 +/threat-model /THREAT_MODEL.html 301 +/audit-chain /AUDIT_CHAIN.html 301 +/api /API_REFERENCE.html 301 +/api-reference /API_REFERENCE.html 301 +/adapters /ADAPTERS.html 301 +/quickstart /QUICKSTART.html 301 +/contributing /CONTRIBUTING.html 301 +/security /SECURITY.html 301 +/roadmap /ROADMAP.html 301 +/code-of-conduct /CODE_OF_CONDUCT.html 301 + +# Anyone typing the canonical filename with .md gets the rendered page. +/*.md /:splat.html 301 + +# Legacy SETUP_GUIDE.md is moved to vyrox-design-partners. Redirect the +# old public URL to a friendly explainer page. +/SETUP_GUIDE.html /QUICKSTART.html 308 +/setup /QUICKSTART.html 308 diff --git a/book.toml b/book.toml new file mode 100644 index 0000000..de87da4 --- /dev/null +++ b/book.toml @@ -0,0 +1,63 @@ +# mdBook configuration for the Vyrox public documentation site. +# +# The canonical source for every chapter is a top-level Markdown file +# in the repo root (README.md, ARCHITECTURE.md, etc). `scripts/build.sh` +# copies them into src/ before invoking `mdbook build`, so editing the +# top-level file is the only place a contributor needs to touch. +# +# Output goes to `book/`. Cloudflare Pages serves the contents of +# `book/` as the static site. + +[book] +title = "Vyrox Security" +description = "Engineering documentation for the Vyrox security platform: architecture, API contracts, threat model, audit-log spec, EDR adapter guide." +authors = ["Vyrox Security"] +language = "en" +src = "src" + +[build] +build-dir = "book" +create-missing = false + +[output.html] +# Use the brand colour for the in-page accent and the website meta tags. +# Light theme is the daylight reading mode (bone palette). Dark theme +# is the void palette. Both are styled by theme/css/variables.css. +default-theme = "light" +preferred-dark-theme = "navy" + +# Smarter punctuation handling. mdBook would otherwise turn the "--" +# sequences that appear in CLI examples into en-dashes, which breaks +# copy-paste. Leaving curly-quotes on for prose readability. +smart-punctuation = false + +additional-css = ["theme/css/variables.css", "theme/css/typography.css"] +additional-js = [] + +# Edit-this-page link on every chapter. Points at the top-level file +# the chapter mirrors, so an external contributor who clicks "edit" +# lands on the canonical source. +git-repository-url = "https://github.com/vyrox-security/vyrox-docs" +git-repository-icon = "fab-github" +edit-url-template = "https://github.com/vyrox-security/vyrox-docs/edit/main/{path}" + +# Site-wide footer text. Single line because mdBook expects it. +[output.html.print] +enable = true + +[output.html.search] +enable = true +limit-results = 30 +teaser-word-count = 30 +use-boolean-and = true +boost-title = 2 +boost-hierarchy = 1 +boost-paragraph = 1 +expand = true +heading-split-level = 3 +copy-js = true + +# Custom hook into the document head: pulls in the three Google Fonts +# (Fraunces, Geist, JetBrains Mono) and adds the favicon. +[preprocessor.links] +[preprocessor.index] diff --git a/index.html b/index.html deleted file mode 100644 index 633e0c3..0000000 --- a/index.html +++ /dev/null @@ -1,1005 +0,0 @@ - - - - - - Vyrox Documentation | AI Security Operations Platform - - - - - - - -
- - -
-
- -
-
v1.0.0 Now Available
-

AI Security Operations,
On-Demand

-

Vyrox is an AI-native Managed Detection and Response (MDR) service that delivers enterprise-grade security at a fraction of traditional MSSP costs.

- -
- - -
-
-
๐ŸŽฏ
-

The Problem

-
-

Every company with CrowdStrike or SentinelOne faces the same gap: their EDR detects everything, but no one triages it at night.

-
-
- โš  -
-

500+ alerts per week

-

Hit a 200-person company's EDR

-
-
-
- ๐Ÿ‘ค -
-

IT Manager is the only analyst

-

Nights, weekends, holidays

-
-
-
- ๐Ÿ”ด -
-

Real attacks happen at 2am

-

When no one's watching

-
-
-
- ๐Ÿ”ฅ -
-

False positives burn trust

-

After 50 "scheduled task" alerts

-
-
-
-
- - -
-
-
๐Ÿš€
-

Quickstart

-
-

Get Vyrox running in ~15 minutes. No log management, no query language, no data lake required.

- -

Step 1: Get Your API Keys

-
    -
  1. Log into your Vyrox dashboard at app.vyrox.dev
  2. -
  3. Navigate to Settings โ†’ API Keys
  4. -
  5. Create a new key pair: Ingestion Key and HMAC Secret
  6. -
- -

Step 2: Configure EDR Webhook

-
-
-
๐Ÿ›ก๏ธ
-

CrowdStrike Falcon

-

Add webhook to Falcon console with HMAC-SHA256 signature

-
-
-
๐ŸŽฏ
-

SentinelOne

-

Configure Bearer token authentication

-
-
-
๐Ÿ”ท
-

Microsoft Defender

-

Connect via Azure Logic Apps

-
-
- -

Step 3: Install Discord Bot

-
    -
  1. Open Vyrox dashboard โ†’ Settings โ†’ Discord
  2. -
  3. Click Add to Server
  4. -
  5. Run /vyrox register in your server
  6. -
- -
-
- โœ“ Integration Complete -
-

Within 30 seconds, your first alert will appear in Discord with triage verdict and recommended action.

-
-
- - -
-
-
๐Ÿ—๏ธ
-

Architecture

-
-

Vyrox uses a two-stage triage pipeline: deterministic heuristics first, then LLM for ambiguous cases.

- -
-
-
EDR Webhooks
- โ†’ -
Ingestion
- โ†’ -
Redis Queue
- โ†’ -
AI Worker
- โ†’ -
Heuristics
- โ†’ -
LLM (ambiguous)
- โ†’ -
Discord
- โ†’ -
Human Approve
- โ†’ -
vyrox-proxy
-
-
- -

Stage 1: Heuristics Engine

-

Deterministic pattern-based triage in under 5ms. Covers 80%+ of alert volume with zero cost. Every verdict is explainable.

-
    -
  • Pattern matching against MITRE ATT&CK TTPs
  • -
  • No LLM dependency
  • -
  • Full audit trail
  • -
- -

Stage 2: LLM Triage

-

Ambiguous alerts escalate to a small language model with structured prompts. Returns verdict, confidence, and reasoning.

-
    -
  • Only ~20% of alerts reach LLM
  • -
  • Human-readable reasoning
  • -
  • Human approval required
  • -
-
- - -
-
-
๐Ÿ“ก
-

API Reference

-
- -

POST /webhook/crowdstrike

-

Receives detection events from CrowdStrike Falcon. Requires HMAC-SHA256 signature.

-
curl -X POST https://api.vyrox.dev/webhook/crowdstrike \
-  -H "Content-Type: application/json" \
-  -H "X-Vyrox-Signature: sha256=<signature>" \
-  -d '{"detect_id":"evt:123","timestamp":1704067200,"severity":"high"}'
- -

POST /webhook/sentinelone

-

Receives threat events from SentinelOne. Requires Bearer token.

-
curl -X POST https://api.vyrox.dev/webhook/sentinelone \
-  -H "Content-Type: application/json" \
-  -H "Authorization: Bearer <ingestion_key>" \
-  -d '{"id":"thrt_123","createdAt":1704067200,"severity":"high"}'
- -

Response Format

-
{
-  "status": "queued",
-  "alert_id": "550e8400-e29b-41d4-a716-446655440000"
-}
-
- - -
-
-
๐Ÿ”’
-

Security Model

-
- -

HMAC-SHA256 Request Signing

-

Every request between Vyrox services is signed with HMAC-SHA256. Unsigned requests are rejected at the boundary.

- -

Replay Protection

-

The proxy rejects requests with approval timestamps older than 30 seconds. Captured requests cannot be replayed.

- -

Human-in-the-Loop

-

Every CRITICAL and HIGH verdict requires human approval. No autonomous containment.

- -

Severity Workflow

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
SeverityDiscord EmbedAction
CRITICALYesRequire approval
HIGHYesRequire approval
MEDIUMYesNotify only
LOWNo (configurable)Auto-dismiss
-
- - -
-
-
๐Ÿ“‹
-

Audit Logging

-
-

Every action is written to an append-only JSONL audit log with SHA-256 chaining.

- -

Log Entry Fields

-
    -
  • timestamp - Unix timestamp
  • -
  • tenant_id - Tenant isolation
  • -
  • action_type - HOST_ISOLATION, KILL_PROCESS, etc.
  • -
  • host - Target endpoint
  • -
  • approved_by - Discord user
  • -
  • dry_run - Whether action was simulated
  • -
- -

Export API

-
GET /audit/export?tenant_id=<tenant_id>
-
- - -
-
-
๐Ÿข
-

Tenant Isolation

-
-
-
- โš  Critical Security Requirement -
-

Every database query, Redis key, and audit entry includes tenant_id. A misconfiguration in one tenant cannot affect another.

-
- -

Isolation Rules

-
    -
  • Database: All queries include WHERE tenant_id = ?
  • -
  • Redis: Keys are namespaced vyrox:alerts:{tenant_id}
  • -
  • API Keys: Per-tenant, non-transferable
  • -
  • Discord Channels: Per-tenant, private
  • -
-
- - -
-
-
๐Ÿ”“
-

Open-Core Model

-
-

Vyrox follows an intentional open-core model. The containment proxy is MIT licensed and publicly available.

- -
-
-
๐Ÿ“ฆ
-

Open Source (MIT)

-

vyrox-proxy - the execution layer that touches your endpoints

-
-
-
๐Ÿ”’
-

Proprietary

-

Heuristics patterns, triage logic, detection intelligence

-
-
-
โœ…
-

Auditable

-

Read the code that executes containment on your infrastructure

-
-
-
๐ŸŽฏ
-

Defensible

-

Detection moat that enables small team to operate at MSSP scale

-
-
-
- - -
-
-
๐Ÿ’ฐ
-

Pricing

-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
TierPriceWhat's Included
Sentinel$499/moUp to 50 endpoints, auto-triage, Discord approval, monthly digest
SOC Team$2K/mo + $3/endpointUnlimited endpoints, priority Discord, weekly briefing
EnterpriseCustomSSO, custom SLAs, dedicated support
- -
-
- โ„น Why endpoint-based pricing? -
-

Per-alert pricing creates an adversarial relationship. Endpoint-based pricing aligns incentives: we earn more as you grow, and we're incentivized to resolve alerts efficiently.

-
-
- - -
-

Vyrox Security © 2026. Built for IT managers who own security but aren't CISOs.

-

- GitHub ยท - Contact ยท - Website -

-
-
-
-
- - - - \ No newline at end of file diff --git a/justfile b/justfile index b146639..1322737 100644 --- a/justfile +++ b/justfile @@ -1,145 +1,72 @@ -# Vyrox Documentation Justfile -# ===================================================================== -# Production-grade task runner for documentation. -# Public docs: ARCHITECTURE.md, API_REFERENCE.md, QUICKSTART.md +# Vyrox Documentation task runner. # -# The docs site can be served as static HTML (index.html) or as -# markdown files. Both are maintained in sync. +# The docs site is an mdBook compiled from the top-level Markdown +# files. `book.toml` is the configuration, `src/SUMMARY.md` is the +# chapter order, and `theme/` holds the brand CSS and the Google +# Fonts loader. The actual `mdbook build` invocation lives in +# `scripts/build.sh` so Cloudflare Pages can call it directly. # # Usage: -# just # Show all commands -# just # Run specific command -# ===================================================================== +# just list every recipe +# just build produce ./book/ +# just serve local preview on http://localhost:3000 +# just lint line-length, trailing whitespace, broken links +# just clean remove ./book/ and the local mdbook cache -set shell := ["zsh", "-cu"] - -# ===================================================================== -# DEFAULT -# ===================================================================== +set shell := ["bash", "-cu"] default: @just --list -# ===================================================================== -# DOCUMENTATION -# ===================================================================== - -# View ARCHITECTURE.md -view-architecture: - @cat ARCHITECTURE.md - -# View API reference -view-api: - @cat API_REFERENCE.md - -# View quickstart -view-quickstart: - @cat QUICKSTART.md - -# View all docs -view-all: - @echo "=== ARCHITECTURE.md ===" - @cat ARCHITECTURE.md - @echo "" - @echo "=== API_REFERENCE.md ===" - @cat API_REFERENCE.md - @echo "" - @echo "=== QUICKSTART.md ===" - @cat QUICKSTART.md - -# ===================================================================== -# LINK CHECKING -# ===================================================================== - -# Check for broken links (requires markdown-link-check) -check-links: - @echo "Checking markdown links..." - @grep -rh "\[.*\](.*\.md)" . --include="*.md" | grep -v "^\s*#" | while read line; do target=$(echo "$line" | sed 's/.*](\([^)]*\)).*/\1/'); if [ -n "$target" ] && [ ! -f "$target" ]; then echo "Broken link: $target"; fi; done || true - -# ===================================================================== -# LINTING -# ===================================================================== - -# Lint markdown files -lint: - @echo "Linting markdown files..." - @for f in *.md; do if [ -f "$f" ]; then echo "Checking $f..."; grep -n "[[:space:]]$" "$f" || true; grep -En "\]\([a-zA-Z_]*\.md\)" "$f" | while read line; do file=$(echo "$line" | sed 's/:.*//'); link=$(echo "$line" | sed 's/.*](\([^)]*\)).*/\1/'); if [ ! -f "$link" ]; then echo "$file: broken link [$link]"; fi; done; fi; done - -# Check for TODO markers -check-todos: - @grep -rn "TODO\|FIXME\|XXX\|HACK" . --include="*.md" || echo "No TODOs found" - -# ===================================================================== -# FORMATTING -# ===================================================================== - -# Format markdown (basic cleanup) -format: - @echo "Formatting markdown files..." - @for f in *.md; do if [ -f "$f" ]; then sed -i 's/[[:space:]]*$//' "$f"; perl -i -pe 's/\n*\z/\n/' "$f"; fi; done - -# ===================================================================== -# WORD COUNT -# ===================================================================== - -# Count words in documentation -words: - @echo "Word count by file:"; @for f in *.md; do if [ -f "$f" ]; then echo " $f: $(wc -w < "$f") words"; fi; done; echo ""; echo "Total: $(cat *.md | wc -w) words" - -# Line count -lines: - @echo "Line count by file:"; @for f in *.md; do if [ -f "$f" ]; then echo " $f: $(wc -l < "$f") lines"; fi; done - -# ===================================================================== -# WEBSITE -# ===================================================================== - -# Serve the documentation website locally -serve: - @echo "Starting local docs server..." - @npx serve . -l 3000 - -# Serve with live reload -dev: - @echo "Starting development server with live reload..." - @npx serve . -l 3000 -c -1 - -# Build the static site (for deployment) +# Build the static site into ./book/. Idempotent. build: - @echo "Building static site..." - @if [ -f index.html ]; then \ - echo "index.html exists - static site ready"; \ - else \ - echo "No index.html found - run 'just website' first"; \ - fi + bash scripts/build.sh -# Validate website HTML -validate: - @echo "Validating HTML..." - @grep -c "" index.html || echo "WARNING: index.html not found or missing DOCTYPE" - @grep -c "/dev/null; then + echo "[lint] trailing whitespace in $f:" && grep -nE "[[:space:]]+$" "$f" + fail=1 + fi + if grep -nE "[โ€”โ€“]" "$f" >/dev/null; then + echo "[lint] em or en dash in $f (house style is plain ASCII):" + grep -nE "[โ€”โ€“]" "$f" + fail=1 + fi + done + exit $fail + +# Run a strict broken-link check across the public Markdown files. +links: + #!/usr/bin/env bash + set -euo pipefail + for f in *.md; do + grep -oE "\]\([A-Z_]+\.md\)" "$f" 2>/dev/null \ + | sed -E "s/\]\(([A-Z_]+\.md)\)/\1/" | sort -u \ + | while read target; do + if [[ ! -f "$target" ]]; then + echo "[links] $f references missing chapter: $target" + fi + done + done + +# Word and line counts per chapter. Useful for tracking doc growth. +stats: + @for f in *.md; do printf "%-22s %5d lines %6d words\n" "$f" "$(wc -l < $f)" "$(wc -w < $f)"; done + +# Remove the build output and the local mdbook binary cache. clean: - rm -f *.tmp - rm -f *~ - -# ===================================================================== -# CI/CD -# ===================================================================== - -# Check docs for CI -ci: - just lint - just check-todos - -# ===================================================================== -# HELP -# ===================================================================== + rm -rf book/ .mdbook-bin/ -help: - @just --list --unsorted \ No newline at end of file +# Run lint + links. Mirrored by CI. +ci: lint links diff --git a/package.json b/package.json index 3f1788f..319fd0d 100644 --- a/package.json +++ b/package.json @@ -1,19 +1,20 @@ { "name": "vyrox-docs", "version": "1.0.0", - "description": "Vyrox Documentation Website", + "description": "Vyrox Security engineering documentation site, built with mdBook", "private": true, "scripts": { - "dev": "npx serve .", - "build": "echo 'Static site - no build required'", - "preview": "npx serve . -l 3000" + "build": "bash scripts/build.sh", + "dev": "bash scripts/build.sh && npx serve book -l 3000", + "serve": "npx mdbook serve --open" }, "keywords": [ "documentation", "security", - "mdr", - "edr" + "edr", + "soc", + "mdbook" ], "author": "Vyrox Security", "license": "MIT" -} \ No newline at end of file +} diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..61fd10b --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,134 @@ +#!/usr/bin/env bash +# build.sh โ€” produce the static documentation site. +# +# Used by: +# - Cloudflare Pages (build command: `bash scripts/build.sh`) +# - Local contributors via `just build` or `npm run build` +# - CI in `.github/workflows/build-docs.yml` +# +# What it does: +# 1. Make sure the chapter files in src/ are in sync with the canonical +# top-level Markdown files. Top-level wins. We copy on every build +# so a contributor who only edits the top-level file does not need +# to remember src/. +# 2. Install mdbook if the host does not already have it. On Cloudflare +# Pages the workspace is ephemeral so we always end up here. +# 3. Run `mdbook build`. Output lands in `book/`. +# +# Exit codes: +# 0 build succeeded +# 1 sync or build failed +# 2 environment is missing a required tool + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +cd "$REPO_ROOT" + +# --------------------------------------------------------------------- +# 1. Sync canonical top-level Markdown into src/ +# --------------------------------------------------------------------- +# These are the files we publish. SUMMARY.md and the theme/ directory +# are mdBook-only and live in src/ + theme/ already. +CHAPTERS=( + "README.md" + "ARCHITECTURE.md" + "THREAT_MODEL.md" + "AUDIT_CHAIN.md" + "API_REFERENCE.md" + "ADAPTERS.md" + "QUICKSTART.md" + "CONTRIBUTING.md" + "CODE_OF_CONDUCT.md" + "SECURITY.md" + "ROADMAP.md" +) + +echo "[build] syncing $((${#CHAPTERS[@]})) chapters into src/" +for f in "${CHAPTERS[@]}"; do + if [[ ! -f "$f" ]]; then + echo "[build] missing top-level chapter: $f" >&2 + exit 1 + fi + cp "$f" "src/$f" +done + +# --------------------------------------------------------------------- +# 2. Make sure mdbook is available +# --------------------------------------------------------------------- +# Cloudflare Pages does not ship mdbook. We fetch the pinned release +# tarball if the binary is not on PATH. Pinning the version means a +# release-channel change on the upstream cannot break this build. +MDBOOK_VERSION="${MDBOOK_VERSION:-v0.4.40}" +LOCAL_MDBOOK_BIN="$REPO_ROOT/.mdbook-bin/mdbook" + +mdbook_cmd() { + if command -v mdbook >/dev/null 2>&1; then + echo "mdbook" + return + fi + if [[ -x "$LOCAL_MDBOOK_BIN" ]]; then + echo "$LOCAL_MDBOOK_BIN" + return + fi + echo "" +} + +if [[ -z "$(mdbook_cmd)" ]]; then + echo "[build] mdbook not found, installing ${MDBOOK_VERSION} locally" + mkdir -p "$REPO_ROOT/.mdbook-bin" + + # Pick the right tarball for the current platform. Cloudflare Pages + # runs Ubuntu x86_64, but a local contributor might run macOS. We + # cover both and fail loudly on anything else. + OS="$(uname -s)" + ARCH="$(uname -m)" + case "$OS-$ARCH" in + Linux-x86_64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-unknown-linux-gnu.tar.gz" + ;; + Darwin-arm64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-aarch64-apple-darwin.tar.gz" + ;; + Darwin-x86_64) + URL="https://github.com/rust-lang/mdBook/releases/download/${MDBOOK_VERSION}/mdbook-${MDBOOK_VERSION}-x86_64-apple-darwin.tar.gz" + ;; + *) + echo "[build] unsupported platform: $OS-$ARCH" >&2 + echo "[build] install mdbook manually and put it on PATH" >&2 + exit 2 + ;; + esac + + echo "[build] fetching $URL" + curl -sSL "$URL" | tar -xz -C "$REPO_ROOT/.mdbook-bin/" + chmod +x "$REPO_ROOT/.mdbook-bin/mdbook" +fi + +MDBOOK="$(mdbook_cmd)" +if [[ -z "$MDBOOK" ]]; then + echo "[build] mdbook still not available after install attempt" >&2 + exit 2 +fi + +# --------------------------------------------------------------------- +# 3. Build +# --------------------------------------------------------------------- +echo "[build] running $MDBOOK build" +"$MDBOOK" build + +# --------------------------------------------------------------------- +# 4. Copy Cloudflare Pages metadata into the build output +# --------------------------------------------------------------------- +# `_headers` and `_redirects` belong at the root of the deployed +# directory. They live at the repo root so a contributor reading the +# repo can see them, and we copy them in at the end of the build so +# Cloudflare Pages sees them in `book/`. +for f in _headers _redirects; do + if [[ -f "$f" ]]; then + cp "$f" "book/$f" + echo "[build] copied $f into book/" + fi +done + +echo "[build] done; output in book/" diff --git a/src/SUMMARY.md b/src/SUMMARY.md new file mode 100644 index 0000000..6e457cd --- /dev/null +++ b/src/SUMMARY.md @@ -0,0 +1,25 @@ +# Summary + +[Vyrox Security](./README.md) + +# Getting started + +- [Quickstart](./QUICKSTART.md) +- [Contributing](./CONTRIBUTING.md) +- [Code of Conduct](./CODE_OF_CONDUCT.md) + +# Architecture + +- [System architecture](./ARCHITECTURE.md) +- [Threat model](./THREAT_MODEL.md) +- [Audit chain specification](./AUDIT_CHAIN.md) + +# Reference + +- [API reference](./API_REFERENCE.md) +- [EDR adapter guide](./ADAPTERS.md) + +# Project + +- [Roadmap](./ROADMAP.md) +- [Security policy](./SECURITY.md) diff --git a/theme/css/typography.css b/theme/css/typography.css new file mode 100644 index 0000000..5000084 --- /dev/null +++ b/theme/css/typography.css @@ -0,0 +1,263 @@ +/* + * Vyrox docs typography. + * + * Visual target is the Rust book: clean reference-doc feel, sans-serif + * body and headings, generous line-height, content centered in a + * readable column. We use Geist for body, Fraunces only for the page + * H1 as a tasteful brand accent, and JetBrains Mono for all code. + * + * Font scale is rem-based on top of mdBook's 62.5% root, so 1.6rem + * resolves to ~16px on default browser settings. + */ + +/* Body */ +html, +body, +.content { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-feature-settings: "kern", "liga", "calt"; + font-optical-sizing: auto; +} + +body { + font-size: 1.6rem; + line-height: 1.6; + font-weight: 400; +} + +.content { + line-height: 1.6; +} + +/* Headings. + * + * Geist for h2 and below so the page reads as a reference doc, not a + * magazine. H1 keeps Fraunces because the brand spec calls for the + * variable serif and the H1 is the one place a touch of identity does + * not fight the prose. */ +.content h1 { + font-family: "Fraunces", Georgia, "Times New Roman", serif; + font-weight: 600; + font-size: 2.8rem; + line-height: 1.2; + letter-spacing: -0.01em; + margin-block-start: 0.4em; + margin-block-end: 0.6em; + font-variation-settings: "opsz" 96, "SOFT" 0, "WONK" 0; +} + +.content h2, +.content h3, +.content h4, +.content h5, +.content h6 { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-weight: 600; + letter-spacing: -0.005em; + line-height: 1.3; +} + +.content h2 { + font-size: 2rem; + margin-block-start: 2.2em; + margin-block-end: 0.6em; +} +.content h3 { + font-size: 1.7rem; + margin-block-start: 1.8em; + margin-block-end: 0.5em; +} +.content h4 { + font-size: 1.4rem; + margin-block-start: 1.6em; + margin-block-end: 0.4em; +} +.content h5 { + font-size: 1.2rem; + margin-block-start: 1.4em; +} +.content h6 { + font-size: 1.1rem; + margin-block-start: 1.2em; + color: var(--searchresults-header-fg); +} + +/* Paragraphs and lists */ +.content p, +.content li { + font-size: 1.6rem; +} +.content p { + margin-block-start: 0; + margin-block-end: 1em; +} +.content ul, +.content ol { + padding-inline-start: 1.8em; + margin-block-end: 1em; +} +.content li { + margin-block-end: 0.35em; +} +.content li > p { + margin-block-end: 0.5em; +} + +/* Links. Underline on hover keeps the page calm while still feeling + responsive. */ +.content a:link, +.content a:visited { + text-decoration: none; +} +.content a:hover { + text-decoration: underline; + text-underline-offset: 2px; +} + +/* Blockquotes. Plain background tint, thin border, no italics. */ +.content blockquote { + background: var(--quote-bg); + border-inline-start: 3px solid var(--quote-border); + padding: 0.6em 1em; + margin: 1.2em 0; + color: var(--fg); +} +.content blockquote p { + margin: 0.2em 0; +} + +/* Code. JetBrains Mono everywhere, ligatures off so == reads as = =. */ +code, +pre, +pre code, +.hljs, +.menu-title code { + font-family: "JetBrains Mono", "SF Mono", "Cascadia Code", + "Source Code Pro", Consolas, "Liberation Mono", monospace; + font-feature-settings: "liga" 0, "calt" 0; + font-variant-ligatures: none; +} + +pre { + padding: 0.85em 1em; + border-radius: 4px; + overflow-x: auto; + line-height: 1.55; + font-size: 1.4rem; +} + +/* Inline code chip. Subtle border so it reads as an identifier + without becoming a coloured tag. */ +.content :not(pre) > code { + color: var(--inline-code-color); + background: var(--quote-bg); + border: 1px solid var(--quote-border); + border-radius: 3px; + padding: 0 0.3em; + font-size: 0.88em; +} + +/* Tables. Used heavily in the API reference. */ +.content table { + font-size: 1.5rem; + border-collapse: collapse; + margin: 1.4em 0; + width: 100%; +} +.content table th { + font-weight: 600; + text-align: left; + background: var(--table-header-bg); + padding: 0.5em 0.7em; + border: 1px solid var(--table-border-color); +} +.content table td { + padding: 0.5em 0.7em; + border: 1px solid var(--table-border-color); + vertical-align: top; +} +.content table tr:nth-child(2n) td { + background: var(--table-alternate-bg); +} + +/* Horizontal rule. Lighter than mdBook's default so it does not slice + the page. */ +.content hr { + border: 0; + border-top: 1px solid var(--quote-border); + margin: 2.2em 0; +} + +/* Sidebar. Geist with a calm 1.4 line-height. */ +.sidebar, +.sidebar .chapter, +.menu-title { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; +} +.sidebar .chapter { + font-size: 1.4rem; + line-height: 1.45; +} +.sidebar .chapter li.chapter-item { + padding: 0.18em 0; +} +.menu-title { + font-family: "Fraunces", Georgia, "Times New Roman", serif; + font-weight: 600; + font-size: 1.9rem; + letter-spacing: -0.01em; + font-variation-settings: "opsz" 96; +} + +/* SUMMARY part titles (`# Heading` lines in SUMMARY.md) read as + small uppercase labels above each section so the nav stays + scannable. */ +.chapter > .part-title { + font-family: "JetBrains Mono", "SF Mono", Consolas, monospace; + font-size: 1.05rem; + text-transform: uppercase; + letter-spacing: 0.14em; + color: var(--searchresults-header-fg); + margin-block-start: 1.4em; + margin-block-end: 0.4em; + padding-inline-start: 0.6em; +} + +/* Search */ +#searchbar, +.searchbar-outer input { + font-family: "Geist", "Helvetica Neue", Arial, sans-serif; + font-size: 1.45rem; +} + +/* Active sidebar item gets a thin ember rule and the accent colour + without changing background. Reads as a calm pointer instead of a + highlight bar. */ +.chapter li.chapter-item.expanded > a.active, +.chapter li.chapter-item > a.active { + color: var(--sidebar-active); + font-weight: 500; + border-inline-start: 2px solid var(--sidebar-active); + margin-inline-start: -2px; + padding-inline-start: 8px; +} + +/* Print niceties */ +@media print { + html, body { background: white; color: black; } + .content { max-width: 100%; } + pre, code { font-size: 9pt; } + .content a:link, + .content a:visited { color: black; } +} + +/* Small screens. Tighten the scale, drop max-width so the column + fills the viewport. */ +@media (max-width: 640px) { + body { font-size: 1.55rem; line-height: 1.55; } + .content h1 { font-size: 2.2rem; } + .content h2 { font-size: 1.7rem; } + .content h3 { font-size: 1.45rem; } + .content h4 { font-size: 1.25rem; } + .content table { font-size: 1.35rem; } +} diff --git a/theme/css/variables.css b/theme/css/variables.css new file mode 100644 index 0000000..753e2f0 --- /dev/null +++ b/theme/css/variables.css @@ -0,0 +1,131 @@ +/* + * Vyrox docs theme variables. + * + * The target visual is a clean reference doc in the style of the Rust + * book (https://doc.rust-lang.org/stable/book/). Plain near-white + * background, high-contrast text, a single accent colour for links + * and active sidebar items, no decorative borders. + * + * The brand still shows up through the ember accent and the heading + * face, but it does not fight the prose. + * + * mdBook ships several built-in themes. We override only the + * variables we need; everything else falls through to the upstream + * defaults. + */ + +:root { + /* Slightly wider than mdBook's default to fit the longer tables + in the API reference without horizontal scroll. The Rust book + uses 750px; we go to 820px because our API tables are wider. */ + --content-max-width: 820px; + --sidebar-target-width: 290px; +} + +/* ------------------------------------------------------------------- + * Light theme. The default and the recommended reading experience. + * Near-white background, near-black text, ember accent. + * ------------------------------------------------------------------- */ +.light, html:not(.js) { + --bg: hsl(0, 0%, 100%); + --fg: hsl(0, 0%, 12%); + + --sidebar-bg: #fafafa; + --sidebar-fg: hsl(0, 0%, 12%); + --sidebar-non-existant: #aaa; + --sidebar-active: #c93a25; + --sidebar-spacer: #f0f0f0; + + --scrollbar: #8f8f8f; + + --icons: #747474; + --icons-hover: #c93a25; + + --links: #c93a25; + --inline-code-color: #6b1a08; + + --theme-popup-bg: #fafafa; + --theme-popup-border: #d8d8d8; + --theme-hover: #ececec; + + --quote-bg: #f6f6f6; + --quote-border: #e5e5e5; + + --warning-border: #c9892f; + + --table-border-color: hsl(0, 0%, 92%); + --table-header-bg: hsl(0, 0%, 96%); + --table-alternate-bg: hsl(0, 0%, 98%); + + --searchbar-border-color: #cccccc; + --searchbar-bg: #ffffff; + --searchbar-fg: #0e0a05; + --searchbar-shadow-color: rgba(0, 0, 0, 0.08); + --searchresults-header-fg: #777; + --searchresults-border-color: #e5e5e5; + --searchresults-li-bg: #fafafa; + --search-mark-bg: #ffe6b0; + + --color-scheme: light; +} + +/* ------------------------------------------------------------------- + * Dark theme (navy). Selected automatically for users whose OS + * prefers dark. Pitch black background, warm-cream text, brighter + * ember accent for AA contrast. + * ------------------------------------------------------------------- */ +.navy { + --bg: #0a0a0b; + --fg: #ece3cc; + + --sidebar-bg: #050505; + --sidebar-fg: #ece3cc; + --sidebar-non-existant: #6b5e48; + --sidebar-active: #ff6a3d; + --sidebar-spacer: #14141a; + + --scrollbar: #2a2a30; + + --icons: #8a8a90; + --icons-hover: #ff6a3d; + + --links: #ff9156; + --inline-code-color: #ffe6b0; + + --theme-popup-bg: #0a0a0b; + --theme-popup-border: #1f1f26; + --theme-hover: #14141a; + + --quote-bg: #14141a; + --quote-border: #1f1f26; + + --warning-border: #c9892f; + + --table-border-color: #1f1f26; + --table-header-bg: #14141a; + --table-alternate-bg: #0e0e12; + + --searchbar-border-color: #1f1f26; + --searchbar-bg: #14141a; + --searchbar-fg: #ece3cc; + --searchbar-shadow-color: rgba(255, 106, 61, 0.18); + --searchresults-header-fg: #c9bfa8; + --searchresults-border-color: #1f1f26; + --searchresults-li-bg: #0a0a0b; + --search-mark-bg: #8b2a12; + + --color-scheme: dark; +} + +/* ------------------------------------------------------------------- + * Selection colour. Subtle ember tint on light, slightly stronger on + * dark so the highlight reads through the lower-contrast surface. + * ------------------------------------------------------------------- */ +.light ::selection { + background: rgba(232, 70, 46, 0.16); + color: #0e0a05; +} +.navy ::selection { + background: rgba(255, 106, 61, 0.32); + color: #ece3cc; +} diff --git a/theme/head.hbs b/theme/head.hbs new file mode 100644 index 0000000..ec6f513 --- /dev/null +++ b/theme/head.hbs @@ -0,0 +1,25 @@ + + + + + + + + + +