Secure, interactive client reports for managed service providers.
Send a normal PDF where a reader can click an approved figure and inspect the precise evidence behind it. Delivery is recipient-bound, time-bound, revocable, and auditable.
RipoDoc preserves the client reporting workflow MSPs already use while adding a defensible evidence layer. It supports two complementary paths: render a report from a validated manifest, or overlay link annotations onto an existing PDF without redrawing its pages.
| Capability | What RipoDoc provides |
|---|---|
| Familiar client experience | A standard PDF that opens in common viewers and keeps the report format your clients know. |
| Evidence on demand | One click opens only the rows and provenance approved for that figure. |
| Controlled delivery | Signed evidence claims bind the report, version, recipient hash, expiry, and revocation identifier. |
| Accountable publication | Report manifests validate before storage; every figure has approved provenance. |
| Auditability | Granted and denied access can be recorded in a SHA-256 hash chain. |
| MSP-ready workspace | A customer dashboard, security-control view, downloadable report registry, and private administrator workspace. |
| Route | Audience | Purpose |
|---|---|---|
/ |
Prospects and MSP teams | Outcome-led product page with an interactive sample report backed by real fixture data. |
/upload |
Verified self-hosted operators | Propose overlays and create a downloadable clickable PDF through the same-origin authoring gateway and a private RipoDoc worker. |
/api/health |
Deployment monitoring | No-store operational health response with safe configuration indicators only; never returns tenants, reports, recipients, sessions, or secrets. |
/dashboard |
Verified MSP workspace users | Review only reports owned by the authenticated tenant; report manifests require tenant-scoped session authorization. |
/signup, /login, /verify-email, /forgot-password, /reset-password |
MSP workspace users | Complete account lifecycle with email activation, one-time codes, private sessions, rate limits, and CAPTCHA protection. |
/security |
Operators and security reviewers | Inspect safe, live operational posture without exposing secrets, recipient identities, or audit records. |
/e/[token]/[elementId] |
Intended report recipient | Open the evidence authorized by a signed link. |
/admin |
Private operators only | Protected report and recipient operations workspace. It is intentionally excluded from public navigation. |
RipoDoc requires Node.js 22.6 or newer because the toolchain uses native TypeScript stripping. The following sequence runs the public app and produces a locally clickable sample report.
git clone https://github.com/thecelestialmismatch/RipoDoc.git
cd RipoDoc
# Install the frontend and project dependencies.
npm install
npm install --prefix web/frontend
# Use a strong server-only signing secret for evidence links.
export RIPODOC_EVIDENCE_SECRET="$(openssl rand -base64 32)"
# Start the customer-facing application.
npm --prefix web/frontend run dev
# In a second terminal, generate a PDF whose evidence links point at localhost.
npm run build:pdf:localOpen build/demo-report.local.pdf and select a report figure. The local application will show the approved evidence rows, provenance, and review metadata for that figure.
RipoDoc is downloadable as a complete MSP deployment bundle, not merely a public marketing site. The bundle starts PostgreSQL, the protected workspace application, and a loopback-only PDF worker together. Copy the repository, create the required environment values, then run:
git clone https://github.com/thecelestialmismatch/RipoDoc.git
cd RipoDoc
export POSTGRES_PASSWORD="$(openssl rand -base64 32)"
export RIPODOC_AUTH_SECRET="$(openssl rand -base64 32)"
export RIPODOC_EVIDENCE_SECRET="$(openssl rand -base64 32)"
export RIPODOC_WORKER_AUTH_SECRET="$(openssl rand -base64 32)"
# Set RIPODOC_APP_ORIGIN, RIPODOC_RESEND_API_KEY, RIPODOC_EMAIL_FROM,
# RIPODOC_TURNSTILE_SECRET, and NEXT_PUBLIC_RIPODOC_TURNSTILE_SITE_KEY.
docker compose -f docker-compose.selfhost.yml up --buildThe browser-facing app listens on ${RIPODOC_PORT:-3000}. PostgreSQL remains internal to the compose network, while the PDF worker binds its host port to 127.0.0.1 and accepts only short-lived server-issued authoring capabilities. The browser never receives a worker credential or direct worker URL. The bundle runs the web and worker containers as non-root with read-only root filesystems, temporary tmpfs mounts, dropped Linux capabilities, health checks, and conservative CPU/memory limits. Deploy the web service behind an HTTPS reverse proxy using the canonical origin configured in RIPODOC_APP_ORIGIN.
The /upload route intentionally uses a private self-hosted processing worker. A public Vercel site must not receive an MSP's client report by default. Every upload, proposal download, review, publish, and artifact download starts with a verified workspace session. The web server issues a single-use, short-lived, tenant-bound capability to the worker; browser CORS and draft IDs are never treated as authorization. The authoring sequence is explicit: select a validated RipoDoc manifest, upload its matching PDF, review the proposed click zones, record a server-bound reviewer decision, then publish the downloadable PDF. Publication rechecks the exact source PDF, manifest, and reviewed-proposal digests.
# The worker signs evidence links and verifies a separate server-only
# authoring credential. Do not place either value in NEXT_PUBLIC_ variables.
export RIPODOC_EVIDENCE_SECRET="$(openssl rand -base64 32)"
export RIPODOC_WORKER_AUTH_SECRET="$(openssl rand -base64 32)"
export RIPODOC_LOCAL_WORKER_URL="http://127.0.0.1:4000/api/local"
# Start the private PDF worker, then run the Next.js app with the same
# RIPODOC_WORKER_AUTH_SECRET and RIPODOC_LOCAL_WORKER_URL values.
npm run dev:local-server
npm --prefix web/frontend run devThe upload page uses same-origin /api/authoring/* routes. Configure RIPODOC_LOCAL_WORKER_URL only in the trusted web-server environment; never publish it as NEXT_PUBLIC_*, and never expose RIPODOC_WORKER_AUTH_SECRET to the browser. The worker rejects browser-originated requests, unsigned requests, expired/replayed capabilities, wrong-purpose capabilities, and cross-tenant draft access.
| Mode | Use it when | Command |
|---|---|---|
| Overlay | You want to retain the existing PDF your MSP already sends. | npm run overlay:propose then npm run overlay:sample |
| Generate | You want RipoDoc to render a complete PDF from a report manifest. | npm run build:pdf or npm run build:pdf:local |
| Local authoring UI | You want a browser interface connected to your self-hosted worker. | npm run docker:run and npm --prefix web/frontend run dev |
Store all values below in the relevant server environment. Do not expose RIPODOC_EVIDENCE_SECRET, RIPODOC_AUTH_SECRET, or RIPODOC_WORKER_AUTH_SECRET to the browser.
| Variable | Required | Purpose |
|---|---|---|
RIPODOC_EVIDENCE_SECRET |
Yes for evidence delivery | A 32-character-or-longer secret used to sign recipient-bound evidence claims. |
RIPODOC_DATABASE_URL |
Yes for workspace authentication | PostgreSQL connection URL for tenant users, opaque sessions, one-time codes, rate-limit counters, and tenant report records. |
RIPODOC_AUTH_SECRET |
Yes for workspace authentication | A separate 32-character-or-longer server-only HMAC secret for session, privacy, and one-time-code hashing. |
RIPODOC_APP_ORIGIN |
Yes for workspace authentication | Canonical HTTPS application origin used in email messages and CSRF checks. |
RIPODOC_RESEND_API_KEY |
Yes for workspace authentication | Server-only Resend API key used for verification, reset, and recipient-proof codes. |
RIPODOC_EMAIL_FROM |
Yes for workspace authentication | Verified sender address for security notices. |
RIPODOC_TURNSTILE_SECRET |
Yes in production | Server-only Cloudflare Turnstile secret used by every authentication action. |
NEXT_PUBLIC_RIPODOC_TURNSTILE_SITE_KEY |
Yes in production | Public Cloudflare Turnstile site key for the authentication forms. |
RIPODOC_AUDIT_LOG |
Recommended on durable infrastructure | File path for a persisted, locally verifiable hash-chained audit log. Without it, events are emitted as structured stdout. |
RIPODOC_REVOKED_JTIS |
Optional | Comma-separated claim identifiers that must no longer authorize evidence access. |
RIPODOC_ALLOW_EXPORT |
Optional | Set to false to disable evidence CSV export. The default is enabled. |
RIPODOC_CONTENT_ROOT |
Optional | Filesystem root for published report manifests in a self-hosted runtime. Defaults to content/reports under the active working directory. |
RIPODOC_LOCAL_WORKER_URL |
Yes for private authoring | Server-only URL for the private worker. Compose sets this to http://worker:4000/api/local; local development may use http://127.0.0.1:4000/api/local. |
RIPODOC_WORKER_AUTH_SECRET |
Yes for private authoring | A separate 32-character-or-longer server-only HMAC secret shared only by the web service and worker to issue and verify single-use, 90-second authoring capabilities. |
RIPODOC_WORKER_MAX_CONCURRENT_JOBS |
Optional | Private worker concurrency cap. Defaults to 2 and is constrained to a maximum of 4. |
RipoDoc does not treat possession of a PDF URL as permanent authorization. Its evidence token contains the report identifier, version, recipient email hash, expiry, and revocation identifier; the email address itself is not embedded in the token. Before rendering scoped evidence, the recipient must complete a one-time code challenge delivered to the address whose hash is embedded in the signed claim. One-time codes are stored only as keyed hashes, expire quickly, are consumed atomically, and are never placed in links or audit logs.
The workspace identity service is deliberately implemented as a small, server-side TypeScript boundary rather than a browser-side identity shim. Next.js 16 App Router supplies the request and response surfaces; Node.js crypto provides random generation, HMAC, constant-time comparison, and scrypt; PostgreSQL is the durable authority for users, sessions, one-time codes, rate limits, audit events, tenants, and report ownership; the pg client uses parameterized statements and transactions; Resend delivers verification, recovery, and recipient-proof codes through HTTPS; and Cloudflare Turnstile is verified server-side for production anti-automation. The Next.js middleware sets the browser security headers and CSRF cookie.
| Required authentication control | Shipped implementation | Primary source |
|---|---|---|
| Enumeration-resistant lifecycle | Signup and recovery return one generic message, and login performs a dummy scrypt verification for unknown accounts before a fixed 900 ms response floor. | service.ts, primitives.ts |
| Rate limiting and CAPTCHA | PostgreSQL rate-limit buckets enforce address and IP limits; every production auth action verifies Turnstile before it invokes the auth service. | service.ts, captcha.ts, [action]/route.ts |
| Verified account activation | New tenants are inactive until a one-time verification code is consumed; sign-in excludes unverified or disabled users. | service.ts |
| Slow password hashing | Passwords use Node.js scrypt with a random 16-byte salt and a 64-byte derived key; no plaintext, MD5, or raw SHA-256 password storage is used. | primitives.ts |
| Secure reset and proof codes | Codes are cryptographically random, HMAC-hashed before storage, single-use through an atomic transaction, delivered outside URLs, and expire after 30 minutes. | service.ts, recipient-proof.ts |
| Session and CSRF controls | Opaque session secrets are stored only as keyed hashes; session cookies use HttpOnly, strict same-site handling, and production-only Secure; state-changing actions require same-origin plus double-submit CSRF validation. |
server.ts, middleware.ts |
A deeper test and control inventory is maintained in docs/05-security/authentication-and-test-coverage.md. The historical read-only 20-control assessment is maintained in docs/05-security/self-hosted-deployment-and-read-only-audit.md. The current private-worker threat model is maintained in docs/05-security/worker-hardening-threat-model.md, and the complete self-hosted operational procedure is maintained in docs/05-engineering/self-hosted-operations-runbook.md. PR #9 supply-chain evidence, reproducible final-image scanning instructions, and CycloneDX SBOM files are maintained in docs/05-security/container-sbom-and-vulnerability-scan.md; customer-facing self-hosted upgrade instructions are maintained in docs/05-security/pr9-self-hosted-security-advisory-and-migration-guide.md.
RipoDoc fails closed for hosted account actions until PostgreSQL, Resend, the canonical app origin, an authentication secret, and the production Turnstile configuration are present. It never substitutes ephemeral Vercel filesystem state or silent email failure for a durable identity deployment.
The evidence rows are not embedded in the PDF. Forwarding the document does not automatically forward the underlying dataset.
Published manifests are schema-validated and immutable at the file-store boundary. When a durable RIPODOC_AUDIT_LOG is configured, audit events can be verified with:
npm run audit:verify -- --path /secure/audit/evidence-access.jsonlThe /security page provides operator-safe indicators for this posture. It never displays signing secrets, token identifiers, recipient identities, or audit-log contents.
The repository uses the built-in Node.js test runner for evidence and application-runtime boundaries. The current regression suite completes 135 tests with no failures, including an end-to-end local worker test that verifies the manifest requirement, review gate, signed publication, and PDF download, plus dashboard/download route-contract coverage, followed by clean linting, type checking, and a production build. See docs/05-security/authentication-and-test-coverage.md for the full file-by-file coverage map.
npm test # evidence, audit, runtime-posture, and admin-key tests
npm run lint # ESLint for the Next.js frontend
npm run typecheck # strict TypeScript checks for web and tools
npm run verify # typecheck + test + production frontend buildThe public product site is designed for Vercel deployment from the main branch. Before enabling workspace accounts on Vercel, provision PostgreSQL and configure RIPODOC_DATABASE_URL, RIPODOC_AUTH_SECRET, RIPODOC_APP_ORIGIN, Resend, and Turnstile variables listed above. The application fails closed for account actions until that durable authentication configuration exists.
The document processor is intentionally not bundled into the public Vercel application. Deploy the worker on infrastructure you control and configure its URL only through server-side RIPODOC_LOCAL_WORKER_URL, paired with RIPODOC_WORKER_AUTH_SECRET. This separation prevents report files from being silently routed through a public marketing deployment and prevents a browser from directly calling the worker.
| Release gate | Required result |
|---|---|
| Automated checks | npm run verify exits successfully. |
| Public route review | /, /upload, /dashboard, /security, and the safe /api/health deployment probe render without broken navigation. |
| Private operations review | /admin redirects unauthenticated visitors to /login; verified owner and admin users see only their tenant-scoped operational summary. |
| Worker review | A verified tenant session completes upload, review, and publication through /api/authoring; direct browser, unsigned, replayed, wrong-purpose, and cross-tenant worker requests fail without disclosing artifacts. |
| Environment review | Server-only variables are present in the deployment environment and never committed. |
web/frontend/ Next.js App Router application
src/app/ Public, customer, evidence, and admin routes
src/lib/evidence/ Manifest validation, tokens, audit, storage, posture
src/lib/admin/ HMAC session helpers for private operations
content/reports/ Validated published report manifests
tools/evidence-pdf/ PDF generator, overlay engine, fixtures, and tests
docker/local/ Local self-hosted authoring worker
var/msp_pitch_deck/ 12-slide MSP sales and marketing pitch deck
DESIGN.md RipoDoc visual system and interface standards
The MSP sales deck is maintained in var/msp_pitch_deck/. It explains the reporting problem, the RipoDoc evidence workflow, integration posture, security approach, onboarding path, and commercial positioning.
The product design system is maintained in DESIGN.md. It documents the RipoDoc navy, blue, and teal visual language; layout principles; component behavior; accessibility expectations; and responsive constraints.
Keep trust boundaries explicit. Any change that affects evidence authorization, recipient binding, link expiry, revocation, report manifest validation, or audit behavior must include focused tests. Keep the public website truthful about deployment capabilities: a public preview is not a substitute for an explicitly configured self-hosted document processor.
See LICENSE for the repository license terms.