From 2e6edcb24e74146849b32a4f436dfca8b8884c10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Fernandes=20=E2=80=94=20Systems=20=26=20AI=20Engi?= =?UTF-8?q?neer?= Date: Fri, 20 Feb 2026 16:38:41 -0300 Subject: [PATCH 1/2] chore: add local validation flow and npm 403 troubleshooting path --- .github/dependabot.yml | 11 + .github/workflows/ci.yml | 18 ++ .github/workflows/release-security.yml | 55 ++++ .meridian/policies/branch_protection.rego | 8 + .npmrc | 3 + .well-known/security.txt | 5 + Makefile | 28 ++ README.md | 55 +++- SECURITY.md | 11 + apps/cli/meridian.js | 66 ++++ apps/cli/package.json | 8 + apps/collector/Dockerfile | 12 + apps/collector/package.json | 25 ++ apps/collector/sql/001_init.sql | 37 +++ apps/collector/src/config.ts | 27 ++ apps/collector/src/db.ts | 87 ++++++ apps/collector/src/hashing.ts | 41 +++ apps/collector/src/index.ts | 177 +++++++++++ apps/collector/src/signing.ts | 46 +++ apps/collector/src/types.ts | 9 + apps/collector/test/hashing.test.ts | 31 ++ apps/collector/tsconfig.json | 13 + apps/policy-engine/Dockerfile | 12 + apps/policy-engine/package.json | 22 ++ apps/policy-engine/src/index.ts | 50 +++ apps/policy-engine/tsconfig.json | 13 + docker-compose.minimal.yml | 51 ++++ docker-compose.yml | 97 ++++++ docs/adr/README.md | 9 + docs/api/openapi.yaml | 59 ++++ docs/architecture/collector-blueprint.md | 284 ++++++++++++++++++ docs/architecture/deployment-tiers.md | 13 + .../meridian-reference-architecture.svg | 53 ++++ docs/architecture/disaster-recovery-plan.md | 23 ++ docs/architecture/enterprise-identity.md | 20 ++ docs/architecture/kubernetes-hardening.md | 35 +++ docs/architecture/multi-tenancy-model.md | 16 + docs/architecture/reference-deployment-aws.md | 7 + .../reference-deployment-azure.md | 7 + docs/architecture/sla-slo.md | 21 ++ docs/architecture/zero-trust-service-auth.md | 16 + docs/compliance/mapping.md | 22 ++ docs/compliance/soc2-readiness-roadmap.md | 20 ++ .../npm-registry-troubleshooting.md | 24 ++ docs/engineering/performance-model.md | 30 ++ docs/engineering/sbom-and-release-security.md | 12 + docs/engineering/secure-sdlc.md | 18 ++ docs/engineering/supply-chain-security.md | 13 + docs/executive/board-one-pager.md | 17 ++ docs/executive/competitive-positioning.md | 9 + docs/executive/investor-summary.md | 12 + docs/executive/roi-model.md | 15 + docs/governance/data-retention-policy.md | 17 ++ docs/governance/product-governance-charter.md | 46 +++ docs/governance/risk-register.md | 12 + docs/governance/third-party-risk.md | 17 ++ docs/integrations/integration-architecture.md | 18 ++ docs/meridian-codex-package.md | 148 +++++++++ docs/pilot/30-day-evaluation-plan.md | 16 + docs/pilot/integration-checklist.md | 8 + docs/pilot/kpi-model.md | 5 + docs/pilot/security-validation-steps.md | 6 + docs/pilot/success-metrics.md | 6 + docs/procurement/architecture-overview.md | 14 + docs/procurement/data-handling-summary.md | 6 + docs/procurement/deployment-models.md | 10 + .../security-questionnaire-template.md | 9 + docs/procurement/subprocessors.md | 7 + docs/prompts/codex-bootstrap.md | 44 +++ docs/security/adversarial-scenarios.md | 21 ++ docs/security/cryptographic-model.md | 27 ++ docs/security/data-flow-diagram.md | 31 ++ docs/security/dpia.md | 19 ++ docs/security/external-validation-plan.md | 18 ++ docs/security/integrity-proof.md | 21 ++ docs/security/key-management-runbook.md | 25 ++ docs/security/security-whitepaper.md | 33 ++ docs/security/threat-model-stride.md | 74 +++++ docs/support/api-deprecation-policy.md | 6 + docs/support/backward-compatibility-policy.md | 6 + docs/support/support-policy.md | 15 + docs/support/upgrade-path.md | 7 + index.html | 150 +++++++++ package.json | 15 + reports/failover-test.md | 12 + reports/load-test-baseline.md | 18 ++ scripts/bootstrap.sh | 4 + scripts/validate-local.sh | 88 ++++++ tests/load/k6-script.js | 28 ++ tests/resilience/chaos-checklist.md | 7 + 90 files changed, 2786 insertions(+), 1 deletion(-) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/release-security.yml create mode 100644 .meridian/policies/branch_protection.rego create mode 100644 .npmrc create mode 100644 .well-known/security.txt create mode 100644 Makefile create mode 100644 SECURITY.md create mode 100755 apps/cli/meridian.js create mode 100644 apps/cli/package.json create mode 100644 apps/collector/Dockerfile create mode 100644 apps/collector/package.json create mode 100644 apps/collector/sql/001_init.sql create mode 100644 apps/collector/src/config.ts create mode 100644 apps/collector/src/db.ts create mode 100644 apps/collector/src/hashing.ts create mode 100644 apps/collector/src/index.ts create mode 100644 apps/collector/src/signing.ts create mode 100644 apps/collector/src/types.ts create mode 100644 apps/collector/test/hashing.test.ts create mode 100644 apps/collector/tsconfig.json create mode 100644 apps/policy-engine/Dockerfile create mode 100644 apps/policy-engine/package.json create mode 100644 apps/policy-engine/src/index.ts create mode 100644 apps/policy-engine/tsconfig.json create mode 100644 docker-compose.minimal.yml create mode 100644 docker-compose.yml create mode 100644 docs/adr/README.md create mode 100644 docs/api/openapi.yaml create mode 100644 docs/architecture/collector-blueprint.md create mode 100644 docs/architecture/deployment-tiers.md create mode 100644 docs/architecture/diagrams/meridian-reference-architecture.svg create mode 100644 docs/architecture/disaster-recovery-plan.md create mode 100644 docs/architecture/enterprise-identity.md create mode 100644 docs/architecture/kubernetes-hardening.md create mode 100644 docs/architecture/multi-tenancy-model.md create mode 100644 docs/architecture/reference-deployment-aws.md create mode 100644 docs/architecture/reference-deployment-azure.md create mode 100644 docs/architecture/sla-slo.md create mode 100644 docs/architecture/zero-trust-service-auth.md create mode 100644 docs/compliance/mapping.md create mode 100644 docs/compliance/soc2-readiness-roadmap.md create mode 100644 docs/engineering/npm-registry-troubleshooting.md create mode 100644 docs/engineering/performance-model.md create mode 100644 docs/engineering/sbom-and-release-security.md create mode 100644 docs/engineering/secure-sdlc.md create mode 100644 docs/engineering/supply-chain-security.md create mode 100644 docs/executive/board-one-pager.md create mode 100644 docs/executive/competitive-positioning.md create mode 100644 docs/executive/investor-summary.md create mode 100644 docs/executive/roi-model.md create mode 100644 docs/governance/data-retention-policy.md create mode 100644 docs/governance/product-governance-charter.md create mode 100644 docs/governance/risk-register.md create mode 100644 docs/governance/third-party-risk.md create mode 100644 docs/integrations/integration-architecture.md create mode 100644 docs/meridian-codex-package.md create mode 100644 docs/pilot/30-day-evaluation-plan.md create mode 100644 docs/pilot/integration-checklist.md create mode 100644 docs/pilot/kpi-model.md create mode 100644 docs/pilot/security-validation-steps.md create mode 100644 docs/pilot/success-metrics.md create mode 100644 docs/procurement/architecture-overview.md create mode 100644 docs/procurement/data-handling-summary.md create mode 100644 docs/procurement/deployment-models.md create mode 100644 docs/procurement/security-questionnaire-template.md create mode 100644 docs/procurement/subprocessors.md create mode 100644 docs/prompts/codex-bootstrap.md create mode 100644 docs/security/adversarial-scenarios.md create mode 100644 docs/security/cryptographic-model.md create mode 100644 docs/security/data-flow-diagram.md create mode 100644 docs/security/dpia.md create mode 100644 docs/security/external-validation-plan.md create mode 100644 docs/security/integrity-proof.md create mode 100644 docs/security/key-management-runbook.md create mode 100644 docs/security/security-whitepaper.md create mode 100644 docs/security/threat-model-stride.md create mode 100644 docs/support/api-deprecation-policy.md create mode 100644 docs/support/backward-compatibility-policy.md create mode 100644 docs/support/support-policy.md create mode 100644 docs/support/upgrade-path.md create mode 100644 index.html create mode 100644 package.json create mode 100644 reports/failover-test.md create mode 100644 reports/load-test-baseline.md create mode 100755 scripts/bootstrap.sh create mode 100755 scripts/validate-local.sh create mode 100644 tests/load/k6-script.js create mode 100644 tests/resilience/chaos-checklist.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..4f74123 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 +updates: + - package-ecosystem: npm + directory: "/" + schedule: + interval: weekly + open-pull-requests-limit: 10 + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: weekly diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..d35cc1a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,18 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + collector: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - run: npm install + - run: npm run lint + - run: npm run test diff --git a/.github/workflows/release-security.yml b/.github/workflows/release-security.yml new file mode 100644 index 0000000..05beeff --- /dev/null +++ b/.github/workflows/release-security.yml @@ -0,0 +1,55 @@ +name: Release Security + +on: + push: + tags: + - 'v*.*.*' + +jobs: + supply-chain: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write + packages: write + attestations: write + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '20' + - name: Generate SBOM (CycloneDX) + uses: CycloneDX/gh-node-module-generatebom@v1 + with: + path: . + output: sbom.cdx.json + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: sbom.cdx.json + - name: Build container + run: docker build -f apps/collector/Dockerfile -t ghcr.io/${{ github.repository }}/collector:${{ github.ref_name }} . + - name: Trivy scan image + uses: aquasecurity/trivy-action@0.28.0 + with: + image-ref: ghcr.io/${{ github.repository }}/collector:${{ github.ref_name }} + severity: CRITICAL,HIGH + exit-code: '1' + - name: Login GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Push image + run: docker push ghcr.io/${{ github.repository }}/collector:${{ github.ref_name }} + - name: Install cosign + uses: sigstore/cosign-installer@v3.7.0 + - name: Sign image with Cosign keyless + run: cosign sign --yes ghcr.io/${{ github.repository }}/collector:${{ github.ref_name }} + - name: Attest build provenance + uses: actions/attest-build-provenance@v1 + with: + subject-name: ghcr.io/${{ github.repository }}/collector + subject-digest: sha256:${{ github.sha }} diff --git a/.meridian/policies/branch_protection.rego b/.meridian/policies/branch_protection.rego new file mode 100644 index 0000000..bd868c0 --- /dev/null +++ b/.meridian/policies/branch_protection.rego @@ -0,0 +1,8 @@ +package meridian.branch + +deny[msg] { + input.action == "push" + input.branch == "main" + not input.metadata.via_pull_request + msg := "Direct push to main is not allowed" +} diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..77a0265 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +registry=https://registry.npmjs.org/ +fund=false +audit=false diff --git a/.well-known/security.txt b/.well-known/security.txt new file mode 100644 index 0000000..3b7a9f4 --- /dev/null +++ b/.well-known/security.txt @@ -0,0 +1,5 @@ +Contact: mailto:security@meridian.io +Expires: 2027-01-01T00:00:00.000Z +Preferred-Languages: en, pt-BR +Policy: https://meridian.io/security +Hiring: https://meridian.io/careers diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2b7c102 --- /dev/null +++ b/Makefile @@ -0,0 +1,28 @@ +.PHONY: setup dev test lint up down load-test up-minimal validate-local + +setup: + npm install + +dev: + npm run dev + +test: + npm run test + +lint: + npm run lint + +up: + docker compose up -d --build + +down: + docker compose down -v + +load-test: + k6 run tests/load/k6-script.js + +up-minimal: + docker compose -f docker-compose.minimal.yml up -d --build + +validate-local: + ./scripts/validate-local.sh diff --git a/README.md b/README.md index c4ad275..801c9db 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,55 @@ # Meridian -The Fixed Point of Truth for Enterprise Engineering Governance + +**The Fixed Point of Truth for Enterprise Engineering** + +Meridian é uma plataforma de governança e compliance para Git enterprise com trilha de auditoria imutável, policy-as-code e inteligência de conformidade. + +## Estado atual (entregável executável) + +- Collector funcional em TypeScript/Fastify (`apps/collector`) +- Policy Engine inicial (`apps/policy-engine`) com avaliação de violações críticas +- Persistência de eventos em PostgreSQL append-only com hash chain e RLS por tenant +- Endpoints ativos: + - `POST /webhooks/github` + - `POST /webhooks/gitlab` + - `GET /audit/integrity?tenant_id=...` + - `GET /audit/export?tenant_id=...&from=...&to=...` (includes `key_id`, signature metadata) + - `GET /metrics` +- Verificação offline de export via CLI: + - `node apps/cli/meridian.js verify --bundle audit-export.json --key ` + - Ed25519 mode: `node apps/cli/meridian.js verify --bundle audit-export.json --public-key ./export-public.pem` + +## Quickstart + +```bash +make setup +make up +``` + +Collector em `http://localhost:8080` e Policy Engine em `http://localhost:8081`. + +## Segurança e Trust Signals + +- Row-Level Security por tenant no banco +- Chaves/segredos por tenant via configuração (`TENANT_GITHUB_SECRETS`, `TENANT_GITLAB_TOKENS`, `TENANT_API_KEYS`) +- Release security pipeline com SBOM, Trivy, Cosign e provenance attestation +- Dependabot semanal para npm e GitHub Actions +- Responsible disclosure policy + `.well-known/security.txt` + +## Estrutura-chave + +- Código: `apps/collector/`, `apps/policy-engine/`, `apps/cli/` +- SQL append-only + RLS: `apps/collector/sql/001_init.sql` +- CI/CD: `.github/workflows/ci.yml`, `.github/workflows/release-security.yml` +- OpenAPI: `docs/api/openapi.yaml` +- Governança: `docs/governance/` +- Segurança: `docs/security/` +- Compliance: `docs/compliance/` +- Support/LTS: `docs/support/` +- Procurement: `docs/procurement/` +- Pilot package: `docs/pilot/` +- Integrações enterprise: `docs/integrations/` +- Arquitetura de referência: `docs/architecture/reference-deployment-aws.md`, `docs/architecture/reference-deployment-azure.md` +- Performance e resiliência: `tests/load/k6-script.js`, `tests/resilience/`, `reports/` +- Validação local ponta a ponta: `make validate-local` (gera `reports/local-validation-report.md`) +- Stack mínima (Postgres + Collector): `make up-minimal` diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..a0e9b80 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,11 @@ +# Security Policy + +## Responsible Disclosure +- Contact: security@meridian.io +- Preferred encryption: PGP on request +- Acknowledgement target: 72 hours +- Initial triage target: 5 business days + +## Bug Bounty (Pilot) +- Scope: Collector API endpoints and authentication/tenant-isolation controls +- Safe harbor for good-faith research within scope diff --git a/apps/cli/meridian.js b/apps/cli/meridian.js new file mode 100755 index 0000000..e4d5345 --- /dev/null +++ b/apps/cli/meridian.js @@ -0,0 +1,66 @@ +#!/usr/bin/env node +import fs from 'node:fs'; +import crypto from 'node:crypto'; + +function canonicalize(input) { + if (input === null || typeof input !== 'object') return JSON.stringify(input); + if (Array.isArray(input)) return `[${input.map((v) => canonicalize(v)).join(',')}]`; + const keys = Object.keys(input).sort(); + return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(input[k])}`).join(',')}}`; +} + +function sha256(value) { + return crypto.createHash('sha256').update(value).digest('hex'); +} + +function hmacSha256(value, key) { + return crypto.createHmac('sha256', key).update(value).digest('hex'); +} + +function usage() { + console.log('Usage: meridian verify --bundle [--key ] [--public-key ]'); +} + +const args = process.argv.slice(2); +if (args[0] !== 'verify') { + usage(); + process.exit(1); +} + +const bundleFlag = args.indexOf('--bundle'); +if (bundleFlag === -1 || !args[bundleFlag + 1]) { + usage(); + process.exit(1); +} + +const path = args[bundleFlag + 1]; +const keyFlag = args.indexOf('--key'); +const publicKeyFlag = args.indexOf('--public-key'); +const key = (keyFlag !== -1 ? args[keyFlag + 1] : undefined) || process.env.EXPORT_SIGNING_KEY || 'dev-export-signing-key'; +const publicKeyPath = publicKeyFlag !== -1 ? args[publicKeyFlag + 1] : undefined; + +const data = JSON.parse(fs.readFileSync(path, 'utf8')); +const { bundle_hash, signature, signature_algorithm, key_id, key_provider, key_reference, public_key, ...unsignedBundle } = data; +const recomputedHash = sha256(canonicalize(unsignedBundle)); +const hashValid = recomputedHash === bundle_hash; + +let signatureValid = false; +if ((signature_algorithm ?? '').toUpperCase() === 'ED25519') { + const providedPublic = publicKeyPath ? fs.readFileSync(publicKeyPath, 'utf8') : undefined; + const publicKey = providedPublic || data.public_key || process.env.EXPORT_PUBLIC_KEY_PEM; + if (publicKey) { + signatureValid = crypto.verify(null, Buffer.from(recomputedHash), publicKey, Buffer.from(signature, 'base64')); + } +} else { + signatureValid = hmacSha256(recomputedHash, key) === signature; +} + +console.log(JSON.stringify({ + format_version: data.format_version, + key_id, + hash_valid: hashValid, + signature_valid: signatureValid, + signature_algorithm: signature_algorithm ?? 'HMAC-SHA256' +}, null, 2)); + +if (!hashValid || !signatureValid) process.exit(2); diff --git a/apps/cli/package.json b/apps/cli/package.json new file mode 100644 index 0000000..78d72a6 --- /dev/null +++ b/apps/cli/package.json @@ -0,0 +1,8 @@ +{ + "name": "@meridian/cli", + "version": "0.1.0", + "type": "module", + "bin": { + "meridian": "./meridian.js" + } +} diff --git a/apps/collector/Dockerfile b/apps/collector/Dockerfile new file mode 100644 index 0000000..0ecd36a --- /dev/null +++ b/apps/collector/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +COPY apps/collector/package.json apps/collector/package.json +COPY apps/policy-engine/package.json apps/policy-engine/package.json +COPY apps/cli/package.json apps/cli/package.json +RUN npm install +COPY . . +RUN npm run -w apps/collector build +USER node +EXPOSE 8080 +CMD ["npm", "run", "-w", "apps/collector", "start"] diff --git a/apps/collector/package.json b/apps/collector/package.json new file mode 100644 index 0000000..84a9db3 --- /dev/null +++ b/apps/collector/package.json @@ -0,0 +1,25 @@ +{ + "name": "@meridian/collector", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js", + "test": "vitest run", + "lint": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "@fastify/sensible": "^5.6.0", + "fastify": "^4.28.1", + "pg": "^8.12.0", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "tsx": "^4.19.2", + "typescript": "^5.7.2", + "vitest": "^2.1.8" + } +} diff --git a/apps/collector/sql/001_init.sql b/apps/collector/sql/001_init.sql new file mode 100644 index 0000000..b67ec15 --- /dev/null +++ b/apps/collector/sql/001_init.sql @@ -0,0 +1,37 @@ +CREATE TABLE IF NOT EXISTS audit_events ( + id UUID PRIMARY KEY, + tenant_id TEXT NOT NULL, + provider TEXT NOT NULL, + event_type TEXT NOT NULL, + previous_hash TEXT NOT NULL, + event_hash TEXT NOT NULL, + payload JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_audit_events_tenant_created_at + ON audit_events (tenant_id, created_at DESC); + +CREATE OR REPLACE FUNCTION forbid_audit_events_mutation() +RETURNS trigger AS $$ +BEGIN + RAISE EXCEPTION 'audit_events is append-only'; +END; +$$ LANGUAGE plpgsql; + +DROP TRIGGER IF EXISTS trg_no_update_delete ON audit_events; +CREATE TRIGGER trg_no_update_delete +BEFORE UPDATE OR DELETE ON audit_events +FOR EACH ROW EXECUTE FUNCTION forbid_audit_events_mutation(); + +ALTER TABLE audit_events ENABLE ROW LEVEL SECURITY; + +DROP POLICY IF EXISTS tenant_isolation_select ON audit_events; +CREATE POLICY tenant_isolation_select ON audit_events +FOR SELECT +USING (tenant_id = current_setting('app.tenant_id', true)); + +DROP POLICY IF EXISTS tenant_isolation_insert ON audit_events; +CREATE POLICY tenant_isolation_insert ON audit_events +FOR INSERT +WITH CHECK (tenant_id = current_setting('app.tenant_id', true)); diff --git a/apps/collector/src/config.ts b/apps/collector/src/config.ts new file mode 100644 index 0000000..fa337ad --- /dev/null +++ b/apps/collector/src/config.ts @@ -0,0 +1,27 @@ +function parseJsonMap(input: string | undefined): Record { + if (!input) return {}; + try { + const parsed = JSON.parse(input) as Record; + return parsed ?? {}; + } catch { + return {}; + } +} + +export const config = { + port: Number(process.env.PORT ?? 8080), + host: process.env.HOST ?? '0.0.0.0', + databaseUrl: process.env.DATABASE_URL ?? 'postgres://meridian:meridian@localhost:5432/meridian', + githubSecret: process.env.GITHUB_WEBHOOK_SECRET ?? 'dev-github-secret', + gitlabToken: process.env.GITLAB_WEBHOOK_TOKEN ?? 'dev-gitlab-token', + exportSigningAlgorithm: process.env.EXPORT_SIGNING_ALGORITHM ?? 'ed25519', + exportSigningKeyId: process.env.EXPORT_SIGNING_KEY_ID ?? 'local-dev-key-v1', + exportSigningKey: process.env.EXPORT_SIGNING_KEY ?? 'dev-export-signing-key', + exportPrivateKeyPem: process.env.EXPORT_PRIVATE_KEY_PEM, + exportPublicKeyPem: process.env.EXPORT_PUBLIC_KEY_PEM, + keyProvider: process.env.KEY_PROVIDER ?? 'env', + keyReference: process.env.KEY_REFERENCE ?? 'local://export-signing-key', + tenantGithubSecrets: parseJsonMap(process.env.TENANT_GITHUB_SECRETS), + tenantGitlabTokens: parseJsonMap(process.env.TENANT_GITLAB_TOKENS), + tenantApiKeys: parseJsonMap(process.env.TENANT_API_KEYS) +}; diff --git a/apps/collector/src/db.ts b/apps/collector/src/db.ts new file mode 100644 index 0000000..2743621 --- /dev/null +++ b/apps/collector/src/db.ts @@ -0,0 +1,87 @@ +import { Pool, PoolClient } from 'pg'; +import { config } from './config.js'; +import { CanonicalEvent } from './types.js'; + +const pool = new Pool({ connectionString: config.databaseUrl }); + +async function withTenantClient(tenantId: string, fn: (client: PoolClient) => Promise): Promise { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + await client.query("SELECT set_config('app.tenant_id', $1, true)", [tenantId]); + const result = await fn(client); + await client.query('COMMIT'); + return result; + } catch (error) { + await client.query('ROLLBACK'); + throw error; + } finally { + client.release(); + } +} + +export async function getPreviousHash(tenantId: string): Promise { + return withTenantClient(tenantId, async (client) => { + const result = await client.query( + 'SELECT event_hash FROM audit_events WHERE tenant_id = $1 ORDER BY created_at DESC LIMIT 1', + [tenantId] + ); + return result.rows[0]?.event_hash ?? 'GENESIS'; + }); +} + +export async function appendEvent(event: CanonicalEvent): Promise { + await withTenantClient(event.tenant_id, async (client) => { + await client.query( + `INSERT INTO audit_events + (id, tenant_id, provider, event_type, previous_hash, event_hash, payload) + VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb)`, + [event.id, event.tenant_id, event.provider, event.event_type, event.previous_hash, event.event_hash, JSON.stringify(event.payload)] + ); + }); +} + +export async function verifyChainIntegrity(tenantId: string): Promise<{ valid: boolean; checked: number; breakAt?: string }> { + return withTenantClient(tenantId, async (client) => { + const result = await client.query( + `SELECT id, previous_hash, event_hash, created_at + FROM audit_events + WHERE tenant_id = $1 + ORDER BY created_at ASC, id ASC`, + [tenantId] + ); + + let prev = 'GENESIS'; + for (const row of result.rows) { + if (row.previous_hash !== prev) { + return { valid: false, checked: result.rows.length, breakAt: row.id }; + } + prev = row.event_hash; + } + + return { valid: true, checked: result.rows.length }; + }); +} + +export async function getEventsForExport(params: { + tenantId: string; + from?: string; + to?: string; +}): Promise> { + return withTenantClient(params.tenantId, async (client) => { + const result = await client.query( + `SELECT id, created_at, event_type, provider, previous_hash, event_hash, payload + FROM audit_events + WHERE tenant_id = $1 + AND ($2::timestamptz IS NULL OR created_at >= $2::timestamptz) + AND ($3::timestamptz IS NULL OR created_at <= $3::timestamptz) + ORDER BY created_at ASC, id ASC`, + [params.tenantId, params.from ?? null, params.to ?? null] + ); + return result.rows; + }); +} + +export async function closeDb(): Promise { + await pool.end(); +} diff --git a/apps/collector/src/hashing.ts b/apps/collector/src/hashing.ts new file mode 100644 index 0000000..0896bc9 --- /dev/null +++ b/apps/collector/src/hashing.ts @@ -0,0 +1,41 @@ +import crypto from 'node:crypto'; + +export function canonicalize(input: unknown): string { + if (input === null || typeof input !== 'object') { + return JSON.stringify(input); + } + + if (Array.isArray(input)) { + return `[${input.map((v) => canonicalize(v)).join(',')}]`; + } + + const obj = input as Record; + const keys = Object.keys(obj).sort(); + const body = keys + .map((k) => `${JSON.stringify(k)}:${canonicalize(obj[k])}`) + .join(','); + return `{${body}}`; +} + +export function sha256(value: string): string { + return crypto.createHash('sha256').update(value).digest('hex'); +} + +export function computeEventHash(params: { + tenantId: string; + provider: 'github' | 'gitlab'; + eventType: string; + occurredAt: string; + payload: unknown; + previousHash: string; +}): string { + const canonical = canonicalize(params.payload); + const base = [params.tenantId, params.provider, params.eventType, params.occurredAt, canonical, params.previousHash].join('|'); + return sha256(base); +} + +export function validateGithubSignature(rawBody: Buffer, signatureHeader: string | undefined, secret: string): boolean { + if (!signatureHeader?.startsWith('sha256=')) return false; + const expected = `sha256=${crypto.createHmac('sha256', secret).update(rawBody).digest('hex')}`; + return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signatureHeader)); +} diff --git a/apps/collector/src/index.ts b/apps/collector/src/index.ts new file mode 100644 index 0000000..6d5f8ab --- /dev/null +++ b/apps/collector/src/index.ts @@ -0,0 +1,177 @@ +import Fastify from 'fastify'; +import sensible from '@fastify/sensible'; +import crypto from 'node:crypto'; +import { z } from 'zod'; +import { appendEvent, getEventsForExport, getPreviousHash, verifyChainIntegrity } from './db.js'; +import { config } from './config.js'; +import { canonicalize, computeEventHash, sha256, validateGithubSignature } from './hashing.js'; +import { signBundleHash } from './signing.js'; + +const app = Fastify({ logger: true }); +await app.register(sensible); + +app.addContentTypeParser('*', { parseAs: 'buffer' }, (_, body, done) => done(null, body)); + +const tenantQuery = z.object({ tenant_id: z.string().min(1) }); +const exportQuery = z.object({ + tenant_id: z.string().min(1), + from: z.string().optional(), + to: z.string().optional() +}); +const metrics = { + webhookRequestsTotal: 0, + githubSignatureFailures: 0, + gitlabTokenFailures: 0, + eventsPersisted: 0 +}; + +function parseJsonBody(body: unknown): unknown { + if (Buffer.isBuffer(body)) return JSON.parse(body.toString('utf8')); + return body; +} + +function resolveTenantSecret(tenantId: string, provider: 'github' | 'gitlab'): string { + if (provider === 'github') { + return config.tenantGithubSecrets[tenantId] ?? config.githubSecret; + } + return config.tenantGitlabTokens[tenantId] ?? config.gitlabToken; +} + +function validateTenantApiKey(tenantId: string, apiKey: string | undefined): boolean { + const expected = config.tenantApiKeys[tenantId]; + if (!expected) return true; + return apiKey === expected; +} + + +app.addHook('onRequest', async (req, reply) => { + const tenantId = (req.headers['x-tenant-id'] as string | undefined) ?? 'default'; + const correlationId = (req.headers['x-correlation-id'] as string | undefined) ?? crypto.randomUUID(); + req.headers['x-correlation-id'] = correlationId; + reply.header('x-correlation-id', correlationId); + + const apiKey = req.headers['x-api-key'] as string | undefined; + if (!validateTenantApiKey(tenantId, apiKey)) { + return reply.unauthorized('invalid tenant api key'); + } +}); + +app.post('/webhooks/github', async (req, reply) => { + metrics.webhookRequestsTotal += 1; + const raw = req.body as Buffer; + const payload = parseJsonBody(raw) as Record; + const tenantId = String(req.headers['x-tenant-id'] ?? payload.organization ?? 'default'); + const secret = resolveTenantSecret(tenantId, 'github'); + const signature = req.headers['x-hub-signature-256'] as string | undefined; + const eventType = (req.headers['x-github-event'] as string | undefined) ?? 'unknown'; + + if (!validateGithubSignature(raw, signature, secret)) { + metrics.githubSignatureFailures += 1; + return reply.unauthorized('invalid signature'); + } + + const previousHash = await getPreviousHash(tenantId); + const occurredAt = new Date().toISOString(); + const eventHash = computeEventHash({ tenantId, provider: 'github', eventType, occurredAt, payload, previousHash }); + + await appendEvent({ + id: crypto.randomUUID(), + tenant_id: tenantId, + provider: 'github', + event_type: eventType, + previous_hash: previousHash, + event_hash: eventHash, + payload + }); + + metrics.eventsPersisted += 1; + return reply.code(202).send({ status: 'accepted', provider: 'github' }); +}); + +app.post('/webhooks/gitlab', async (req, reply) => { + metrics.webhookRequestsTotal += 1; + const payload = parseJsonBody(req.body) as Record; + const tenantId = String(req.headers['x-tenant-id'] ?? payload.organization ?? payload.group ?? 'default'); + const token = req.headers['x-gitlab-token'] as string | undefined; + const expected = resolveTenantSecret(tenantId, 'gitlab'); + + if (!token || token !== expected) { + metrics.gitlabTokenFailures += 1; + return reply.unauthorized('invalid token'); + } + + const eventType = (req.headers['x-gitlab-event'] as string | undefined) ?? 'unknown'; + const previousHash = await getPreviousHash(tenantId); + const occurredAt = new Date().toISOString(); + const eventHash = computeEventHash({ tenantId, provider: 'gitlab', eventType, occurredAt, payload, previousHash }); + + await appendEvent({ + id: crypto.randomUUID(), + tenant_id: tenantId, + provider: 'gitlab', + event_type: eventType, + previous_hash: previousHash, + event_hash: eventHash, + payload + }); + + metrics.eventsPersisted += 1; + return reply.code(202).send({ status: 'accepted', provider: 'gitlab' }); +}); + +app.get('/audit/integrity', async (req, reply) => { + const parsed = tenantQuery.safeParse(req.query); + if (!parsed.success) { + return reply.badRequest(parsed.error.message); + } + const result = await verifyChainIntegrity(parsed.data.tenant_id); + return reply.send({ tenant_id: parsed.data.tenant_id, ...result }); +}); + +app.get('/audit/export', async (req, reply) => { + const parsed = exportQuery.safeParse(req.query); + if (!parsed.success) { + return reply.badRequest(parsed.error.message); + } + + const events = await getEventsForExport({ + tenantId: parsed.data.tenant_id, + from: parsed.data.from, + to: parsed.data.to + }); + + const unsignedBundle = { + format_version: '1.0.0', + tenant_id: parsed.data.tenant_id, + from: parsed.data.from ?? null, + to: parsed.data.to ?? null, + generated_at: new Date().toISOString(), + events + }; + + const bundleHash = sha256(canonicalize(unsignedBundle)); + const signatureInfo = signBundleHash(bundleHash); + + return reply.send({ + ...unsignedBundle, + bundle_hash: bundleHash, + ...signatureInfo + }); +}); + +app.get('/metrics', async () => { + return [ + '# TYPE collector_webhook_requests_total counter', + `collector_webhook_requests_total ${metrics.webhookRequestsTotal}`, + '# TYPE collector_signature_failures_total counter', + `collector_signature_failures_total ${metrics.githubSignatureFailures + metrics.gitlabTokenFailures}`, + '# TYPE collector_events_persisted_total counter', + `collector_events_persisted_total ${metrics.eventsPersisted}` + ].join('\n'); +}); + +if (process.env.NODE_ENV !== 'test') { + await app.listen({ host: config.host, port: config.port }); +} + +export default app; diff --git a/apps/collector/src/signing.ts b/apps/collector/src/signing.ts new file mode 100644 index 0000000..2101b8c --- /dev/null +++ b/apps/collector/src/signing.ts @@ -0,0 +1,46 @@ +import crypto from 'node:crypto'; +import { config } from './config.js'; + +export type SignatureInfo = { + signature: string; + signature_algorithm: 'HMAC-SHA256' | 'ED25519'; + key_id: string; + key_provider: string; + key_reference: string; + public_key?: string; +}; + +function signWithHmac(bundleHash: string): SignatureInfo { + const signature = crypto.createHmac('sha256', config.exportSigningKey).update(bundleHash).digest('hex'); + return { + signature, + signature_algorithm: 'HMAC-SHA256', + key_id: config.exportSigningKeyId, + key_provider: config.keyProvider, + key_reference: config.keyReference + }; +} + +function signWithEd25519(bundleHash: string): SignatureInfo { + if (!config.exportPrivateKeyPem) { + return signWithHmac(bundleHash); + } + + const signature = crypto.sign(null, Buffer.from(bundleHash), config.exportPrivateKeyPem).toString('base64'); + return { + signature, + signature_algorithm: 'ED25519', + key_id: config.exportSigningKeyId, + key_provider: config.keyProvider, + key_reference: config.keyReference, + public_key: config.exportPublicKeyPem + }; +} + +export function signBundleHash(bundleHash: string): SignatureInfo { + if (config.exportSigningAlgorithm.toLowerCase() === 'hmac-sha256') { + return signWithHmac(bundleHash); + } + + return signWithEd25519(bundleHash); +} diff --git a/apps/collector/src/types.ts b/apps/collector/src/types.ts new file mode 100644 index 0000000..5f5e61d --- /dev/null +++ b/apps/collector/src/types.ts @@ -0,0 +1,9 @@ +export interface CanonicalEvent { + id: string; + tenant_id: string; + provider: 'github' | 'gitlab'; + event_type: string; + previous_hash: string; + event_hash: string; + payload: unknown; +} diff --git a/apps/collector/test/hashing.test.ts b/apps/collector/test/hashing.test.ts new file mode 100644 index 0000000..85e27f0 --- /dev/null +++ b/apps/collector/test/hashing.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest'; +import { canonicalize, computeEventHash, validateGithubSignature } from '../src/hashing.js'; +import crypto from 'node:crypto'; + +describe('hashing', () => { + it('canonicalize sorts object keys', () => { + const a = canonicalize({ b: 1, a: 2 }); + const b = canonicalize({ a: 2, b: 1 }); + expect(a).toBe(b); + }); + + it('computeEventHash is deterministic', () => { + const params = { + tenantId: 'acme', + provider: 'github' as const, + eventType: 'push', + occurredAt: '2025-01-01T00:00:00Z', + payload: { x: 1 }, + previousHash: 'GENESIS' + }; + expect(computeEventHash(params)).toBe(computeEventHash(params)); + }); + + it('validates github hmac signature', () => { + const raw = Buffer.from('{"hello":"world"}'); + const secret = 'test-secret'; + const sig = `sha256=${crypto.createHmac('sha256', secret).update(raw).digest('hex')}`; + expect(validateGithubSignature(raw, sig, secret)).toBe(true); + expect(validateGithubSignature(raw, sig, 'wrong')).toBe(false); + }); +}); diff --git a/apps/collector/tsconfig.json b/apps/collector/tsconfig.json new file mode 100644 index 0000000..7c07c23 --- /dev/null +++ b/apps/collector/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node", "vitest/globals"] + }, + "include": ["src", "test"] +} diff --git a/apps/policy-engine/Dockerfile b/apps/policy-engine/Dockerfile new file mode 100644 index 0000000..48f663a --- /dev/null +++ b/apps/policy-engine/Dockerfile @@ -0,0 +1,12 @@ +FROM node:20-alpine +WORKDIR /app +COPY package*.json ./ +COPY apps/collector/package.json apps/collector/package.json +COPY apps/policy-engine/package.json apps/policy-engine/package.json +COPY apps/cli/package.json apps/cli/package.json +RUN npm install +COPY . . +RUN npm run -w apps/policy-engine build +USER node +EXPOSE 8081 +CMD ["npm", "run", "-w", "apps/policy-engine", "start"] diff --git a/apps/policy-engine/package.json b/apps/policy-engine/package.json new file mode 100644 index 0000000..f9cddb1 --- /dev/null +++ b/apps/policy-engine/package.json @@ -0,0 +1,22 @@ +{ + "name": "@meridian/policy-engine", + "version": "0.1.0", + "type": "module", + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc -p tsconfig.json", + "start": "node dist/index.js", + "test": "echo \"no tests yet\"", + "lint": "tsc -p tsconfig.json --noEmit" + }, + "dependencies": { + "fastify": "^4.28.1", + "zod": "^3.23.8" + }, + "devDependencies": { + "@types/node": "^22.10.2", + "tsx": "^4.19.2", + "typescript": "^5.7.2" + } +} diff --git a/apps/policy-engine/src/index.ts b/apps/policy-engine/src/index.ts new file mode 100644 index 0000000..ee1ccae --- /dev/null +++ b/apps/policy-engine/src/index.ts @@ -0,0 +1,50 @@ +import Fastify from 'fastify'; +import { z } from 'zod'; + +const app = Fastify({ logger: true }); +const port = Number(process.env.PORT ?? 8081); +const host = process.env.HOST ?? '0.0.0.0'; + +const InputSchema = z.object({ + tenant_id: z.string(), + action: z.string(), + branch: z.string().optional(), + metadata: z.record(z.any()).optional() +}); + +app.post('/evaluate', async (req, reply) => { + const parsed = InputSchema.safeParse(req.body); + if (!parsed.success) { + return reply.code(400).send({ error: parsed.error.message }); + } + + const input = parsed.data; + const violations: string[] = []; + + if (input.action === 'push' && (input.branch === 'main' || input.branch === 'master')) { + const viaPr = Boolean(input.metadata?.via_pull_request); + if (!viaPr) violations.push('Direct push to protected branch is not allowed'); + } + + const approvedReviews = Number(input.metadata?.approved_reviews ?? 0); + if (input.action === 'pr_merge' && (input.branch === 'main' || input.branch === 'master') && approvedReviews < 2) { + violations.push('Protected branch merge requires at least 2 approvals'); + } + + const hasSecrets = Boolean(input.metadata?.secret_exposure_detected); + if (hasSecrets) violations.push('Secret exposure detected in commit payload'); + + return reply.send({ + tenant_id: input.tenant_id, + status: violations.length ? 'fail' : 'pass', + violations + }); +}); + +app.get('/health', async () => ({ status: 'ok' })); + +if (process.env.NODE_ENV !== 'test') { + await app.listen({ host, port }); +} + +export default app; diff --git a/apps/policy-engine/tsconfig.json b/apps/policy-engine/tsconfig.json new file mode 100644 index 0000000..4ff54c1 --- /dev/null +++ b/apps/policy-engine/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "outDir": "dist", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "types": ["node"] + }, + "include": ["src"] +} diff --git a/docker-compose.minimal.yml b/docker-compose.minimal.yml new file mode 100644 index 0000000..40d4ebc --- /dev/null +++ b/docker-compose.minimal.yml @@ -0,0 +1,51 @@ +version: '3.9' +services: + postgres: + image: postgres:16 + environment: + POSTGRES_USER: meridian + POSTGRES_PASSWORD: meridian + POSTGRES_DB: meridian + ports: + - '5432:5432' + volumes: + - pgdata_min:/var/lib/postgresql/data + - ./apps/collector/sql/001_init.sql:/docker-entrypoint-initdb.d/001_init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U meridian -d meridian"] + interval: 10s + timeout: 5s + retries: 10 + + collector: + build: + context: . + dockerfile: apps/collector/Dockerfile + depends_on: + postgres: + condition: service_healthy + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + tmpfs: + - /tmp + environment: + PORT: 8080 + DATABASE_URL: postgres://meridian:meridian@postgres:5432/meridian + GITHUB_WEBHOOK_SECRET: dev-github-secret + GITLAB_WEBHOOK_TOKEN: dev-gitlab-token + TENANT_API_KEYS: '{"acme":"acme-key"}' + TENANT_GITHUB_SECRETS: '{"acme":"acme-github-secret"}' + TENANT_GITLAB_TOKENS: '{"acme":"dev-gitlab-token"}' + EXPORT_SIGNING_ALGORITHM: HMAC-SHA256 + EXPORT_SIGNING_KEY_ID: local-dev-key-v1 + KEY_PROVIDER: env + KEY_REFERENCE: local://export-signing-key + EXPORT_SIGNING_KEY: dev-export-signing-key + ports: + - '8080:8080' + +volumes: + pgdata_min: diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..c3317c3 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,97 @@ +version: '3.9' +services: + postgres: + image: postgres:16 + environment: + POSTGRES_USER: meridian + POSTGRES_PASSWORD: meridian + POSTGRES_DB: meridian + ports: + - '5432:5432' + volumes: + - pgdata:/var/lib/postgresql/data + - ./apps/collector/sql/001_init.sql:/docker-entrypoint-initdb.d/001_init.sql:ro + healthcheck: + test: ["CMD-SHELL", "pg_isready -U meridian -d meridian"] + interval: 10s + timeout: 5s + retries: 10 + + redis: + image: redis:7-alpine + ports: + - '6379:6379' + + zookeeper: + image: confluentinc/cp-zookeeper:7.5.3 + environment: + ZOOKEEPER_CLIENT_PORT: 2181 + + kafka: + image: confluentinc/cp-kafka:7.5.3 + depends_on: + - zookeeper + ports: + - '9092:9092' + environment: + KAFKA_BROKER_ID: 1 + KAFKA_ZOOKEEPER_CONNECT: zookeeper:2181 + KAFKA_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092 + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + + opa: + image: openpolicyagent/opa:0.69.0-static + command: ["run", "--server", "/policies"] + volumes: + - ./.meridian/policies:/policies:ro + ports: + - '8181:8181' + + collector: + build: + context: . + dockerfile: apps/collector/Dockerfile + depends_on: + postgres: + condition: service_healthy + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + tmpfs: + - /tmp + environment: + PORT: 8080 + DATABASE_URL: postgres://meridian:meridian@postgres:5432/meridian + GITHUB_WEBHOOK_SECRET: dev-github-secret + GITLAB_WEBHOOK_TOKEN: dev-gitlab-token + TENANT_API_KEYS: '{"acme":"acme-key"}' + TENANT_GITHUB_SECRETS: '{"acme":"acme-github-secret"}' + TENANT_GITLAB_TOKENS: '{"acme":"dev-gitlab-token"}' + EXPORT_SIGNING_ALGORITHM: HMAC-SHA256 + EXPORT_SIGNING_KEY_ID: local-dev-key-v1 + KEY_PROVIDER: env + KEY_REFERENCE: local://export-signing-key + EXPORT_SIGNING_KEY: dev-export-signing-key + ports: + - '8080:8080' + + policy-engine: + build: + context: . + dockerfile: apps/policy-engine/Dockerfile + read_only: true + security_opt: + - no-new-privileges:true + cap_drop: + - ALL + tmpfs: + - /tmp + environment: + PORT: 8081 + ports: + - '8081:8081' + +volumes: + pgdata: diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..a27590e --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,9 @@ +# ADR Index + +| ADR | Title | Status | +|---|---|---| +| ADR-001 | Immutable Event Storage | Accepted | +| ADR-002 | OPA as Policy Engine | Accepted | +| ADR-003 | Multi-tenancy by Schema + RLS | Accepted | + +See `docs/meridian-codex-package.md` for the current ADR summary and decision rationale. diff --git a/docs/api/openapi.yaml b/docs/api/openapi.yaml new file mode 100644 index 0000000..f532cb3 --- /dev/null +++ b/docs/api/openapi.yaml @@ -0,0 +1,59 @@ +openapi: 3.1.0 +info: + title: Meridian API + version: 1.3.0 + description: Versioned API contract for webhook ingestion, audit export verification, integrity verification, metrics, and policy evaluation. +servers: + - url: https://api.meridian.local +paths: + /webhooks/github: + post: + summary: Receive GitHub webhook + responses: + '202': { description: Accepted } + '401': { description: Unauthorized } + /webhooks/gitlab: + post: + summary: Receive GitLab webhook + responses: + '202': { description: Accepted } + '401': { description: Unauthorized } + /audit/integrity: + get: + summary: Verify audit hash chain integrity + parameters: + - in: query + name: tenant_id + schema: { type: string } + required: true + responses: + '200': { description: Integrity verification result } + /audit/export: + get: + summary: Export tamper-proof audit bundle + parameters: + - in: query + name: tenant_id + schema: { type: string } + required: true + - in: query + name: from + schema: { type: string, format: date-time } + required: false + - in: query + name: to + schema: { type: string, format: date-time } + required: false + responses: + '200': { description: Signed export bundle } + /metrics: + get: + summary: Collector Prometheus metrics + responses: + '200': { description: Metrics payload } + /evaluate: + post: + summary: Evaluate compliance policy input (Policy Engine) + responses: + '200': { description: Policy decision result } +components: {} diff --git a/docs/architecture/collector-blueprint.md b/docs/architecture/collector-blueprint.md new file mode 100644 index 0000000..6f81162 --- /dev/null +++ b/docs/architecture/collector-blueprint.md @@ -0,0 +1,284 @@ +# Meridian Event Collector Blueprint (MVP Fase 1) + +## 1. Objetivo + +O **Event Collector** é a porta de entrada de eventos de Git providers (GitHub/GitLab) no Meridian. +Ele precisa garantir: + +- ingestão confiável de webhooks; +- normalização em um formato canônico; +- trilha imutável com hash encadeado; +- persistência append-only; +- verificabilidade criptográfica para auditoria. + +--- + +## 2. Escopo do MVP + +### Incluído + +- `POST /webhooks/github` +- `POST /webhooks/gitlab` +- validação de assinatura dos providers +- normalizador universal (`CanonicalEvent`) +- cálculo de hash de evento +- encadeamento por `previous_hash` +- persistência no `audit_events` +- endpoint de verificação de integridade + +### Fora do escopo inicial + +- Kafka / filas distribuídas +- deduplicação global multi-região +- assinatura assimétrica por HSM +- replay/backfill massivo + +--- + +## 3. Arquitetura lógica + +```text +GitHub/GitLab Webhook + | + v ++--------------------------+ +| FastAPI Collector | +| - Signature Validator | +| - Provider Adapter | +| - Canonical Normalizer | ++--------------------------+ + | + v ++--------------------------+ +| Chain Hasher | +| - previous_hash lookup | +| - event_hash generation | ++--------------------------+ + | + v ++--------------------------+ +| Immutable Audit Store | +| PostgreSQL append-only | ++--------------------------+ + | + v ++--------------------------+ +| Integrity Verifier API | ++--------------------------+ +``` + +--- + +## 4. Contratos de API + +### 4.1 GitHub webhook + +- **Endpoint:** `POST /webhooks/github` +- **Headers obrigatórios:** + - `X-GitHub-Event` + - `X-GitHub-Delivery` + - `X-Hub-Signature-256` +- **Validação:** HMAC SHA-256 com segredo por tenant + +Resposta: + +- `202 Accepted` quando persistido +- `401 Unauthorized` para assinatura inválida +- `422 Unprocessable Entity` para payload inválido + +### 4.2 GitLab webhook + +- **Endpoint:** `POST /webhooks/gitlab` +- **Headers obrigatórios:** + - `X-Gitlab-Event` + - `X-Gitlab-Token` +- **Validação:** token compartilhado por tenant + +Resposta: + +- `202 Accepted` quando persistido +- `401 Unauthorized` para token inválido +- `422 Unprocessable Entity` para payload inválido + +### 4.3 Integridade + +- **Endpoint:** `GET /audit/integrity?tenant_id=...&from=...&to=...` +- **Função:** valida sequência de hashes no intervalo + +Resposta exemplo: + +```json +{ + "tenant_id": "acme", + "checked_events": 12453, + "valid": true, + "first_event_id": "...", + "last_event_id": "..." +} +``` + +--- + +## 5. Modelo canônico de evento + +```json +{ + "event_id": "uuid-v7", + "tenant_id": "string", + "provider": "github|gitlab", + "event_type": "push|pull_request|merge_request|...", + "occurred_at": "RFC3339", + "received_at": "RFC3339", + "repository": { + "id": "string", + "name": "string", + "full_name": "org/repo", + "default_branch": "main" + }, + "actor": { + "id": "string", + "username": "string", + "email": "optional" + }, + "raw_payload": {"...": "original provider payload"}, + "metadata": { + "delivery_id": "provider delivery id", + "source_ip": "ip", + "headers": {"...": "subset non-sensitive"} + } +} +``` + +### Regras de normalização + +1. Preservar payload original (`raw_payload`) para auditoria forense. +2. Converter datas para UTC (`RFC3339`). +3. Definir chaves estáveis para hashing (JSON canônico com ordenação determinística). +4. Não descartar campos críticos de segurança (ator, repo, tipo de evento, origem). + +--- + +## 6. Audit Store imutável + +### 6.1 Tabela base + +```sql +CREATE TABLE audit_events ( + id UUID PRIMARY KEY, + tenant_id TEXT NOT NULL, + provider TEXT NOT NULL, + event_type TEXT NOT NULL, + previous_hash TEXT, + event_hash TEXT NOT NULL, + payload JSONB NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); +``` + +### 6.2 Regras de imutabilidade + +- Sem `UPDATE` e sem `DELETE` para `audit_events` (somente `INSERT`). +- Trigger bloqueando mutações. +- Permissões separadas: + - role `collector_writer`: apenas `INSERT` + - role `auditor_reader`: apenas `SELECT` + +--- + +## 7. Hash chain (block-like) + +### 7.1 Algoritmo + +Para cada novo evento `E_n`: + +1. Buscar o último `event_hash` do tenant (`H_{n-1}`). +2. Canonicalizar payload para string determinística `P_n`. +3. Calcular `H_n = SHA256(tenant_id || provider || event_type || occurred_at || P_n || H_{n-1})`. +4. Persistir `previous_hash = H_{n-1}` e `event_hash = H_n`. + +### 7.2 Verificação + +`verify_chain_integrity(tenant_id, from, to)`: + +- ordena eventos por `created_at, id`; +- recalcula cada hash; +- compara com `event_hash` armazenado; +- valida vínculo com `previous_hash` do próximo. + +Se qualquer divergência: `valid = false` e reporta primeiro ponto de quebra. + +--- + +## 8. Implementação FastAPI (estrutura sugerida) + +```text +apps/collector/ + app/ + main.py + api/ + webhooks.py + integrity.py + domain/ + canonical_event.py + normalizers/ + github.py + gitlab.py + hashing.py + integrity.py + infrastructure/ + db.py + repositories/ + audit_repository.py + security/ + signatures.py +``` + +--- + +## 9. Segurança mínima obrigatória + +- TLS obrigatório em trânsito. +- Segredos por tenant (GitHub/GitLab webhook secret/token). +- Rotação de segredos sem downtime. +- Sanitização de headers sensíveis antes de persistir metadados. +- Rate limiting por tenant e por IP. +- Logs estruturados com `trace_id`. + +--- + +## 10. Observabilidade + +Métricas recomendadas: + +- `collector_webhook_requests_total{provider,status}` +- `collector_webhook_latency_ms{provider}` +- `collector_signature_failures_total{provider}` +- `collector_chain_integrity_failures_total{tenant}` +- `collector_persist_errors_total` + +Tracing: + +- span por requisição de webhook +- span de validação +- span de normalização +- span de persistência + +--- + +## 11. Critérios de aceite do MVP + +1. Receber eventos de GitHub e GitLab com autenticação válida. +2. Persistir todos os eventos aceitos em tabela append-only. +3. Encadear hashes por tenant sem lacunas. +4. Detectar adulteração via `verify_chain_integrity`. +5. Expor evidência auditável do status da cadeia. + +--- + +## 12. Próximo passo recomendado + +Após concluir o Collector MVP: + +1. Implementar `Immutable Audit Store` com hardening SQL (trigger + roles + backup WORM). +2. Integrar `Policy Engine` (OPA) consumindo `CanonicalEvent`. +3. Persistir decisão de policy e alimentar `Compliance Scoring Engine`. diff --git a/docs/architecture/deployment-tiers.md b/docs/architecture/deployment-tiers.md new file mode 100644 index 0000000..f519ca3 --- /dev/null +++ b/docs/architecture/deployment-tiers.md @@ -0,0 +1,13 @@ +# Deployment Tiers + +## Tier A — SaaS Multi-tenant +Shared control plane with tenant isolation enforced by app + DB policies. + +## Tier B — Dedicated Tenant +Dedicated compute and data plane per customer with managed operations. + +## Tier C — Full Isolated (Single-tenant VPC) +Customer-specific VPC/account/subscription with isolated networking and key domains. + +## Tier D — Air-gapped Mode +Offline/on-prem deployment with controlled update channels and local signing/verifier workflows. diff --git a/docs/architecture/diagrams/meridian-reference-architecture.svg b/docs/architecture/diagrams/meridian-reference-architecture.svg new file mode 100644 index 0000000..8c7d175 --- /dev/null +++ b/docs/architecture/diagrams/meridian-reference-architecture.svg @@ -0,0 +1,53 @@ + + + Meridian Reference Architecture + Enterprise governance and compliance control plane + + + GitHub / GitLab / BB + Webhooks + APIs + + + Event Collector + HMAC validation · tenant auth + hash chain seed + append write + + + Policy Engine (OPA) + Rego evaluation + violations + decision logs + + + Compliance Intelligence + scores · anomaly signals + executive risk outputs + + + Immutable Audit Store + PostgreSQL append-only + RLS + tenant isolation + retention + integrity verification endpoint + + + API Gateway + OIDC/SAML authN + RBAC/tenant authZ + audit export endpoints + + + Executive Dashboard + Risk heatmaps + Trend & SLA views + + + Security & Trust Plane + Cosign signed releases · SBOM automation · Provenance attestations · mTLS service auth · Observability + correlation IDs + DR (RPO/RTO) · SOC2 readiness · Procurement package · Responsible disclosure + + + + + + + + diff --git a/docs/architecture/disaster-recovery-plan.md b/docs/architecture/disaster-recovery-plan.md new file mode 100644 index 0000000..f9ee0bc --- /dev/null +++ b/docs/architecture/disaster-recovery-plan.md @@ -0,0 +1,23 @@ +# Disaster Recovery Plan + +## Objectives +- **RPO:** 15 minutes +- **RTO:** 4 hours + +## Backup Policy +- Continuous WAL archiving for PostgreSQL +- Hourly incremental backups +- Daily full backups +- Immutable offsite storage retention policy + +## Recovery Procedures +1. Incident declaration and severity assignment +2. Failover to warm standby region +3. Restore data plane from latest consistent checkpoint +4. Rebuild service plane via IaC + GitOps +5. Verify chain integrity and tenant accessibility + +## Testing Cadence +- Quarterly restore drills +- Semiannual regional failover simulations +- Annual game-day across all critical services diff --git a/docs/architecture/enterprise-identity.md b/docs/architecture/enterprise-identity.md new file mode 100644 index 0000000..440bb95 --- /dev/null +++ b/docs/architecture/enterprise-identity.md @@ -0,0 +1,20 @@ +# Enterprise Identity Federation + +## Supported Federation Patterns +- OIDC (Okta, Azure AD, Ping Identity) +- SAML 2.0 for enterprise SSO compatibility + +## Claim Mapping +- `sub` / `email` -> user identity +- `groups` / `roles` -> Meridian role mapping +- `tenant_id` (or mapped org claim) -> tenant context + +## SCIM Provisioning Model +- SCIM used for user/group lifecycle synchronization +- Deprovisioning SLA: immediate revocation on sync + +## Role Mapping +- `MeridianAdmin`: tenant-wide administration +- `SecurityAnalyst`: read + investigation + export +- `Auditor`: read-only evidence access +- `Operator`: operational actions without policy override diff --git a/docs/architecture/kubernetes-hardening.md b/docs/architecture/kubernetes-hardening.md new file mode 100644 index 0000000..2e6a93e --- /dev/null +++ b/docs/architecture/kubernetes-hardening.md @@ -0,0 +1,35 @@ +# Kubernetes Runtime Hardening + +## Container Baseline +- Distroless base images for production workloads +- Run as non-root (`runAsNonRoot: true`, explicit UID/GID) +- Read-only root filesystem +- Drop all Linux capabilities except explicitly required + +## Kernel / Runtime Controls +- Seccomp profile: `RuntimeDefault` (or stricter custom profile) +- AppArmor profile enforced on all workloads +- Disable privilege escalation + +## Pod Security Context Example +```yaml +securityContext: + runAsNonRoot: true + runAsUser: 10001 + runAsGroup: 10001 + fsGroup: 10001 + seccompProfile: + type: RuntimeDefault +containers: + - name: collector + securityContext: + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + capabilities: + drop: ["ALL"] +``` + +## Network Controls +- Default deny NetworkPolicy per namespace +- Explicit egress allow-list for dependencies +- Ingress restricted to API gateway/load balancer paths diff --git a/docs/architecture/multi-tenancy-model.md b/docs/architecture/multi-tenancy-model.md new file mode 100644 index 0000000..527d55b --- /dev/null +++ b/docs/architecture/multi-tenancy-model.md @@ -0,0 +1,16 @@ +# Multi-Tenancy Model + +## Isolation Strategy +- Primary: schema-per-tenant in PostgreSQL +- Secondary: row-level security enforcement +- Tertiary: tenant-aware service authorization context + +## Data Protection +- Per-tenant encryption key namespaces +- Tenant-scoped object prefixes for exports/backups +- No cross-tenant joins in application query layer + +## Operational Controls +- Tenant-scoped quotas and rate limits +- Tenant-specific audit export pipelines +- Tenant isolation checks in CI and integration tests diff --git a/docs/architecture/reference-deployment-aws.md b/docs/architecture/reference-deployment-aws.md new file mode 100644 index 0000000..f49230b --- /dev/null +++ b/docs/architecture/reference-deployment-aws.md @@ -0,0 +1,7 @@ +# Reference Deployment — AWS + +- VPC with public/private subnets across 2+ AZs +- Private subnets for data plane (PostgreSQL, Kafka, Redis) +- Security groups with least-privilege ingress +- Isolated tenant workloads via namespace and IAM boundaries +- Bastion or SSM Session Manager access model diff --git a/docs/architecture/reference-deployment-azure.md b/docs/architecture/reference-deployment-azure.md new file mode 100644 index 0000000..c6b2033 --- /dev/null +++ b/docs/architecture/reference-deployment-azure.md @@ -0,0 +1,7 @@ +# Reference Deployment — Azure + +- VNet with segmented subnets and NSGs +- Private endpoints for data services +- Workload identity and managed identities for service auth +- Isolation zones for control plane and data plane +- Bastion host access for operational break-glass scenarios diff --git a/docs/architecture/sla-slo.md b/docs/architecture/sla-slo.md new file mode 100644 index 0000000..1e2cd32 --- /dev/null +++ b/docs/architecture/sla-slo.md @@ -0,0 +1,21 @@ +# SLA / SLO Definition + +## External SLA Targets +- Platform availability: **99.9%** (base tier) +- Premium availability option: **99.99%** + +## Service SLOs +- Webhook ingestion success: 99.95% (5-min windows) +- p95 ingest latency: < 2 seconds +- Policy evaluation latency p95: < 3 seconds +- Integrity verification job success: 99.9% daily + +## Incident Severity Model +- **SEV-1:** customer-wide outage or integrity compromise +- **SEV-2:** major feature degradation across tenants +- **SEV-3:** partial degradation with workaround +- **SEV-4:** minor defect with low operational impact + +## Error Budget Policy +- Monthly error budget tracked per SLO +- Freeze non-critical releases if budget exhausted diff --git a/docs/architecture/zero-trust-service-auth.md b/docs/architecture/zero-trust-service-auth.md new file mode 100644 index 0000000..f73f74c --- /dev/null +++ b/docs/architecture/zero-trust-service-auth.md @@ -0,0 +1,16 @@ +# Zero Trust Service Authentication Model + +## Principles +- No implicit trust between internal services +- Every request authenticated and authorized +- Short-lived credentials and automatic rotation + +## Controls +- mTLS between services via service mesh +- Service-to-service JWT with audience and tenant claims +- Workload identity integration with cloud IAM + +## Operational Requirements +- Certificate rotation ≤ 24h +- Revocation propagation < 5 minutes +- Mandatory authN/authZ checks in all internal APIs diff --git a/docs/compliance/mapping.md b/docs/compliance/mapping.md new file mode 100644 index 0000000..d84a6f2 --- /dev/null +++ b/docs/compliance/mapping.md @@ -0,0 +1,22 @@ +# Compliance Mapping + +## SOC 2 +| Control | Meridian Capability | Evidence Artifact | +|---|---|---| +| CC6.1 Logical access | SSO/RBAC + tenant-scoped authorization | Access logs, IAM policy snapshots | +| CC7.2 Monitoring | Telemetry, alerting, integrity checks | Metrics dashboards, incident alerts | +| CC8.1 Change management | Policy-as-code + protected branches + PR approvals | PR history, release logs | + +## ISO 27001 +| Control | Meridian Capability | Evidence Artifact | +|---|---|---| +| A.12.4 Logging & monitoring | Immutable audit trail + centralized logs | Audit event exports, log retention settings | +| A.14.2 Secure development | CI checks, policy tests, signed releases | CI pipeline runs, SBOM, release attestations | +| A.16 Incident management | Severity model + IR workflow + postmortems | Incident tickets, timelines, remediation records | + +## DORA (EU) +| Domain | Meridian Capability | Evidence Artifact | +|---|---|---| +| ICT risk management | Continuous policy evaluation + risk scoring | Score history, control drift reports | +| Incident reporting | Structured incident severity and auditability | Incident reports and export logs | +| Operational resilience | DR plans, RTO/RPO targets, failover testing | DR drill reports and recovery evidence | diff --git a/docs/compliance/soc2-readiness-roadmap.md b/docs/compliance/soc2-readiness-roadmap.md new file mode 100644 index 0000000..71234a8 --- /dev/null +++ b/docs/compliance/soc2-readiness-roadmap.md @@ -0,0 +1,20 @@ +# SOC 2 Type II Readiness Roadmap + +## Phase 1 (0-60 days) +- Finalize control ownership matrix +- Enable evidence collection automation in CI/CD +- Start quarterly access review workflow + +## Phase 2 (60-120 days) +- Execute internal control dry-run +- Close high-priority control gaps +- Engage external audit firm for readiness review + +## Phase 3 (120-240 days) +- Begin Type II observation period +- Run monthly control attestation ceremonies +- Track exceptions and remediation SLAs + +## Phase 4 (240+ days) +- Complete audit fieldwork +- Deliver SOC 2 Type II report package diff --git a/docs/engineering/npm-registry-troubleshooting.md b/docs/engineering/npm-registry-troubleshooting.md new file mode 100644 index 0000000..2f354e1 --- /dev/null +++ b/docs/engineering/npm-registry-troubleshooting.md @@ -0,0 +1,24 @@ +# npm Registry 403 Troubleshooting + +If `npm install` returns `403 Forbidden`, validate in this order: + +1. **Registry source** + - Confirm `.npmrc` points to `https://registry.npmjs.org/` (or your approved internal mirror). +2. **Proxy settings** + - Check `npm config get proxy` and `npm config get https-proxy`. + - Remove stale/invalid proxy config if not required. +3. **Network egress policy** + - Verify environment/firewall allows outbound access to npm registry. +4. **Internal mirror policy** + - If using Artifactory/Nexus, confirm requested package versions are allowed. +5. **Lockfile strategy** + - Run `npm install` locally in a network-enabled environment. + - Commit `package-lock.json` and use `npm ci` in CI. + +## Quick checks + +```bash +npm config get registry +npm ping +npm view fastify version +``` diff --git a/docs/engineering/performance-model.md b/docs/engineering/performance-model.md new file mode 100644 index 0000000..fa382df --- /dev/null +++ b/docs/engineering/performance-model.md @@ -0,0 +1,30 @@ +# Performance & Scalability Model + +## Target Throughput +- Baseline: 200 events/sec sustained +- Burst: 1,000 events/sec for 5-minute windows + +## Latency SLO +- p95 ingest latency < 2s +- p99 policy evaluation latency < 5s + +## Horizontal Scaling +- Collector scales statelessly behind load balancer +- Kafka partitions scale ingestion fan-out +- Read replicas scale reporting workloads + +## Load Testing Methodology +1. Warm-up phase (5 min) with representative payloads +2. Sustained phase (30 min) at baseline load +3. Stress phase (10x spike) for burst validation +4. Cooldown and recovery measurement + +## Required Outputs +- Throughput sustained (events/sec) +- p50/p95/p99 latency +- Error rate and retry rate +- Resource usage profile (CPU/memory) + +## Runbooks +- `k6 run tests/load/k6-script.js` +- Archive run report under `reports/` with timestamp diff --git a/docs/engineering/sbom-and-release-security.md b/docs/engineering/sbom-and-release-security.md new file mode 100644 index 0000000..100ac5a --- /dev/null +++ b/docs/engineering/sbom-and-release-security.md @@ -0,0 +1,12 @@ +# SBOM and Release Security Baseline + +## SBOM +- Generate SBOM on every release build (CycloneDX or SPDX) +- Store SBOM artifact alongside release metadata +- Associate SBOM with commit SHA and container digest + +## Release Security +- Signed release artifacts (Sigstore/Cosign or equivalent) +- Dependency vulnerability scanning gates +- License policy checks in CI +- Provenance attestation attached to release pipeline diff --git a/docs/engineering/secure-sdlc.md b/docs/engineering/secure-sdlc.md new file mode 100644 index 0000000..66eb41c --- /dev/null +++ b/docs/engineering/secure-sdlc.md @@ -0,0 +1,18 @@ +# Secure SDLC + +## Code Review Policy +- Two approvals mandatory for protected branches. +- Security-sensitive changes require Security Engineering approval. + +## Mandatory Security Review +- Required for auth, crypto, tenancy, and audit log changes. + +## Static Analysis +- SAST in CI for all pull requests. + +## Dependency Scanning +- Dependency vulnerability scan on every PR and release. + +## Signed Commits and Releases +- Signed commits required on protected branches. +- Release artifacts must be cryptographically signed. diff --git a/docs/engineering/supply-chain-security.md b/docs/engineering/supply-chain-security.md new file mode 100644 index 0000000..0cff591 --- /dev/null +++ b/docs/engineering/supply-chain-security.md @@ -0,0 +1,13 @@ +# Supply Chain Security Controls + +## Release Integrity +- Container signing with Cosign (keyless) +- Build provenance attestation in release workflow + +## Artifact Transparency +- CycloneDX SBOM generated and uploaded per release +- Vulnerability scanning gate via Trivy + +## Dependency Hygiene +- Dependabot weekly updates for npm and GitHub Actions +- Security triage SLA for HIGH/CRITICAL findings diff --git a/docs/executive/board-one-pager.md b/docs/executive/board-one-pager.md new file mode 100644 index 0000000..ae101a4 --- /dev/null +++ b/docs/executive/board-one-pager.md @@ -0,0 +1,17 @@ +# Meridian Board One-Pager + +## Why now +Regulators and enterprise audit teams require proof of engineering control effectiveness, not policy statements. + +## What Meridian does +Meridian creates a tamper-evident operating record of software delivery controls and turns it into executive-level risk visibility. + +## Business Outcomes +- Reduced audit preparation time +- Lower control failure probability +- Faster executive decision cycles on engineering risk + +## KPI Targets +- 40–60% reduction in manual audit evidence effort +- 25–40% reduction in control drift incidents +- Continuous control visibility across all repositories diff --git a/docs/executive/competitive-positioning.md b/docs/executive/competitive-positioning.md new file mode 100644 index 0000000..e995263 --- /dev/null +++ b/docs/executive/competitive-positioning.md @@ -0,0 +1,9 @@ +# Competitive Positioning + +## vs Native Git provider logs +- Meridian adds normalized cross-provider evidence, control mapping, and executive scoring. +- Native logs are operational; Meridian is governance-grade and auditor-oriented. + +## vs Internal custom tooling +- Meridian provides standardized control framework mapping and productized operating model. +- Internal tools often lack long-term maintainability and audit defensibility. diff --git a/docs/executive/investor-summary.md b/docs/executive/investor-summary.md new file mode 100644 index 0000000..3c02ae5 --- /dev/null +++ b/docs/executive/investor-summary.md @@ -0,0 +1,12 @@ +# Investor Summary (3-slide narrative) + +## Slide 1 — Problem +Large enterprises cannot reliably prove SDLC control effectiveness across thousands of repositories. + +## Slide 2 — Solution +Meridian overlays existing Git systems with immutable audit evidence, policy-as-code enforcement, and compliance scoring. + +## Slide 3 — Economic Value +- Lower audit and compliance labor costs +- Reduced exposure to regulatory findings +- Improved resilience and governance posture diff --git a/docs/executive/roi-model.md b/docs/executive/roi-model.md new file mode 100644 index 0000000..9f1a408 --- /dev/null +++ b/docs/executive/roi-model.md @@ -0,0 +1,15 @@ +# ROI Model + +## Cost Inputs +- Current manual audit evidence collection hours +- Average blended hourly rate (Engineering + GRC + Security) +- Estimated control failure impact cost + +## Value Drivers +1. Audit preparation labor reduction +2. Faster remediation cycle time +3. Reduced probability of compliance exceptions +4. Lower incident-response overhead for governance issues + +## Example Formula +`Annual ROI = (Audit labor savings + Avoided failure costs + Operational savings) - Meridian TCO` diff --git a/docs/governance/data-retention-policy.md b/docs/governance/data-retention-policy.md new file mode 100644 index 0000000..cbdf8da --- /dev/null +++ b/docs/governance/data-retention-policy.md @@ -0,0 +1,17 @@ +# Data Retention & Legal Hold Policy + +## Retention Baseline +- Audit events retained for 7 years by default (SOX-aligned) +- Tenant-configurable retention above baseline where required + +## Legal Hold Mode +- Legal hold can suspend deletion/archival for scoped tenants/time windows +- Activation requires dual authorization (Security + Compliance) + +## Immutability Override +- Any exceptional override requires quorum approval (minimum 2-of-3: CISO, Compliance Lead, VP Engineering) +- All override actions are logged and included in audit evidence + +## Deletion and Archival +- Archival to immutable storage tiers +- Cryptographic chain continuity retained across active/archive boundaries diff --git a/docs/governance/product-governance-charter.md b/docs/governance/product-governance-charter.md new file mode 100644 index 0000000..15ec81a --- /dev/null +++ b/docs/governance/product-governance-charter.md @@ -0,0 +1,46 @@ +# Product Governance Charter + +## 1. Mission +Meridian exists to provide enterprise-grade, tamper-evident governance for software delivery events across Git providers, enabling auditable control evidence for engineering, security, and regulatory stakeholders. + +## 2. Scope +### In scope +- Git event ingestion and normalization +- Immutable audit trail and integrity verification +- Policy-as-code enforcement evidence +- Compliance scoring and executive reporting artifacts + +### Out of scope +- Source-code hosting replacement +- CI/CD orchestration replacement +- GRC suite replacement + +## 3. Governance Principles +1. Evidence over assertion. +2. Security and privacy by default. +3. Tenant isolation as a first-order concern. +4. Change management through version-controlled workflows. +5. Verifiable controls aligned to enterprise frameworks. + +## 4. RACI Matrix +| Domain | Responsible | Accountable | Consulted | Informed | +|---|---|---|---|---| +| Product roadmap | Product Management | CEO/GM | CISO, Engineering | Sales, CS | +| Security controls | Security Engineering | CISO | Platform, Legal | Product | +| Policy library changes | Governance Engineering | Head of Governance | Security, Audit | Customer Success | +| Runtime operations | SRE | VP Engineering | Security, Product | All customers | +| Compliance mappings | Compliance Lead | CISO | External auditor, Legal | GTM leadership | + +## 5. Policy Change Process +1. Propose change via PR with rationale and control mapping. +2. Require two approvals (Security + Governance owner). +3. Run policy tests and impact checks in CI. +4. Release with semantic versioning and changelog entry. +5. Record effective date and affected tenants. +6. Preserve full history for external audit evidence. + +## 6. Governance Cadence +- Weekly risk triage +- Bi-weekly control drift review +- Monthly executive governance review +- Quarterly control effectiveness attestation diff --git a/docs/governance/risk-register.md b/docs/governance/risk-register.md new file mode 100644 index 0000000..35ecb50 --- /dev/null +++ b/docs/governance/risk-register.md @@ -0,0 +1,12 @@ +# Risk Register + +| ID | Risk | Impact | Likelihood | Mitigation | Owner | Status | +|---|---|---|---|---|---|---| +| R-001 | Webhook spoofing bypasses ingestion trust | High | Medium | HMAC/signature validation, source allowlists, replay protection | Security Eng | Open | +| R-002 | Hash-chain tampering in audit store | Critical | Low | Append-only constraints, integrity verifier job, cryptographic signatures | Platform Eng | Open | +| R-003 | Tenant data leakage via query path | Critical | Medium | Schema-per-tenant + RLS + per-tenant encryption keys | Platform Eng | Open | +| R-004 | Policy regression causes false pass | High | Medium | Mandatory policy tests, staged rollout, canary evaluation | Governance Eng | Open | +| R-005 | Event backlog causes delayed evidence | Medium | Medium | Queue backpressure controls, autoscaling, SLO alerts | SRE | Open | +| R-006 | Key compromise for signing/integrity | Critical | Low | KMS/HSM-backed keys, rotation, key usage audit trails | Security Eng | Open | +| R-007 | Insufficient backup/restore readiness | High | Medium | DR drills, immutable backups, cross-region restore tests | SRE | Open | +| R-008 | Compliance claims drift from implementation | High | Medium | Control owner attestations, quarterly mapping review | Compliance Lead | Open | diff --git a/docs/governance/third-party-risk.md b/docs/governance/third-party-risk.md new file mode 100644 index 0000000..f11e70b --- /dev/null +++ b/docs/governance/third-party-risk.md @@ -0,0 +1,17 @@ +# Third-Party Risk Policy + +## Critical Dependencies +- Cloud hosting provider +- Managed database provider +- Identity provider (Okta/Azure AD) +- Logging/monitoring vendors + +## Risk Assessment +- Security posture review before onboarding +- Annual reassessment for critical vendors + +## Vendor Onboarding Process +1. Security questionnaire +2. DPA and legal review +3. Access scope minimization +4. Continuous monitoring registration diff --git a/docs/integrations/integration-architecture.md b/docs/integrations/integration-architecture.md new file mode 100644 index 0000000..bca13a2 --- /dev/null +++ b/docs/integrations/integration-architecture.md @@ -0,0 +1,18 @@ +# Integration Architecture + +## GitHub Enterprise +- Webhooks + REST/GraphQL APIs +- HMAC webhook validation + +## GitLab +- Webhooks + API v4 +- Token-based webhook verification + +## ServiceNow +- Incident and control ticket export via REST API + +## Splunk +- Security/audit log forwarding via HTTP Event Collector + +## Okta +- OIDC/SAML SSO for enterprise authentication diff --git a/docs/meridian-codex-package.md b/docs/meridian-codex-package.md new file mode 100644 index 0000000..a6f3aa4 --- /dev/null +++ b/docs/meridian-codex-package.md @@ -0,0 +1,148 @@ +# Meridian — Codex Package + +> The Fixed Point of Truth for Enterprise Engineering + +## 1) Visão geral + +Meridian é uma camada enterprise de compliance e governança instalada sobre a infraestrutura Git existente da organização. + +Objetivo central: permitir que times de engenharia, CISO, auditoria e board respondam com evidência verificável à pergunta: + +> “Temos controle real e auditável sobre nosso código?” + +## 2) Estrutura de repositório alvo + +```text +meridian/ +├── apps/ +│ ├── collector/ +│ ├── engine/ +│ ├── intelligence/ +│ ├── dashboard/ +│ └── api/ +├── packages/ +│ ├── sdk/ +│ ├── policies/ +│ └── types/ +├── infrastructure/ +│ ├── terraform/ +│ ├── kubernetes/ +│ └── policies/ +├── .meridian/policies/ +├── docs/ +│ ├── architecture/ +│ ├── governance/ +│ ├── adr/ +│ └── executive/ +├── scripts/ +├── Makefile +├── docker-compose.yml +└── README.md +``` + +## 3) Stack técnica recomendada + +### Backend +- Node.js 20 + TypeScript (ou Go 1.22) +- Fastify (REST) + Apollo Server (GraphQL) +- OPA/Rego (policy engine) +- Kafka (streaming) +- PostgreSQL + TimescaleDB +- Redis + +### Frontend +- Next.js 14 (App Router) +- Tailwind + Radix UI +- Recharts + D3 +- NextAuth.js + SSO/SAML + +### Infraestrutura +- Terraform (multi-cloud) +- Kubernetes + Helm +- GitHub Actions (CI/CD) +- OpenTelemetry + Prometheus + Grafana +- HashiCorp Vault + +## 4) Data flow arquitetural + +```text +Git Providers -> Event Collector -> Kafka (git.events.raw) + -> Policy Engine (OPA) -> Kafka (git.events.evaluated) + -> Intelligence + Immutable Store + -> API Gateway -> Dashboard + Audit Exports +``` + +## 5) Modelo de evento canônico (resumo) + +Campos obrigatórios do `MeridianEvent`: + +- `event_id` +- `sequence_id` +- `timestamp` +- `platform` +- `organization` +- `repository` +- `branch` +- `actor` +- `action` +- `metadata` +- `policy_evaluation` (`status`, `violations`) +- `integrity` (`hash`, `previous_hash`, `signature?`) + +## 6) Policy-as-Code + +- Policies versionadas em `.meridian/policies/*.rego` +- Regras mínimas iniciais: + - proteção de branch + - requisito de 2 aprovações para merge em branches protegidas + - bloqueio de dependências críticas + +## 7) Estratégia de Git e qualidade + +- Trunk-based development com feature flags +- Conventional Commits +- Branch protection em `main`: + - PR obrigatório + - 2 aprovações + - checks obrigatórios (CI/security/policy) + - sem force push e sem delete + - commits assinados + +## 8) Compliance scoring + +Score global 0–100 com breakdown por controles: + +- branch protection +- code review +- secret scanning +- dependency security +- SBOM coverage +- signed commits +- audit trail + +Níveis de risco: `low`, `medium`, `high`, `critical`. + +## 9) ADRs mandatórios + +- **ADR-001:** armazenamento imutável append-only com hash encadeado +- **ADR-002:** OPA/Rego como motor de políticas +- **ADR-003:** multi-tenant com isolamento forte (schema-per-tenant + RLS) + +## 10) Roadmap executável + +### Fase 1 +1. Collector +2. Immutable audit store +3. Policy engine +4. Dashboard básico +5. Compliance scoring + +### Fase 2 +1. Anomaly detection +2. Executive reporting +3. Multi-org support + +### Fase 3 +1. GRC integrations +2. Enterprise SSO +3. SLA monitoring diff --git a/docs/pilot/30-day-evaluation-plan.md b/docs/pilot/30-day-evaluation-plan.md new file mode 100644 index 0000000..de43e85 --- /dev/null +++ b/docs/pilot/30-day-evaluation-plan.md @@ -0,0 +1,16 @@ +# 30-Day Enterprise Pilot Plan + +## Week 1 +- Environment setup and identity integration +- Webhook onboarding for pilot repositories + +## Week 2 +- Policy baseline activation and tuning +- Initial score and violation baseline review + +## Week 3 +- Operational validation (alerts, exports, integrity checks) +- Security validation checkpoints + +## Week 4 +- KPI review, stakeholder readout, production go/no-go decision diff --git a/docs/pilot/integration-checklist.md b/docs/pilot/integration-checklist.md new file mode 100644 index 0000000..fb6e731 --- /dev/null +++ b/docs/pilot/integration-checklist.md @@ -0,0 +1,8 @@ +# Pilot Integration Checklist + +- [ ] Git provider webhooks configured +- [ ] Tenant secrets configured +- [ ] Identity provider connected (OIDC/SAML) +- [ ] Policy bundle validated +- [ ] Audit export verification tested +- [ ] Dashboard/API access validated by stakeholders diff --git a/docs/pilot/kpi-model.md b/docs/pilot/kpi-model.md new file mode 100644 index 0000000..9819484 --- /dev/null +++ b/docs/pilot/kpi-model.md @@ -0,0 +1,5 @@ +# Pilot KPI Model + +`Pilot Value Score = (Coverage × Reliability × Detection Improvement) - Operational Friction` + +Track KPIs weekly and compare against pre-pilot baseline. diff --git a/docs/pilot/security-validation-steps.md b/docs/pilot/security-validation-steps.md new file mode 100644 index 0000000..8650db3 --- /dev/null +++ b/docs/pilot/security-validation-steps.md @@ -0,0 +1,6 @@ +# Pilot Security Validation Steps + +1. Validate webhook signature enforcement (positive/negative tests) +2. Validate tenant isolation checks with cross-tenant attempts +3. Validate chain integrity export and offline verification +4. Validate incident escalation workflow for simulated critical alert diff --git a/docs/pilot/success-metrics.md b/docs/pilot/success-metrics.md new file mode 100644 index 0000000..3fe0807 --- /dev/null +++ b/docs/pilot/success-metrics.md @@ -0,0 +1,6 @@ +# Pilot Success Metrics + +- Webhook ingestion reliability > 99.9% +- Policy evaluation coverage across pilot repos > 95% +- Mean time to detect policy violations reduced by > 50% +- Manual evidence collection time reduced by > 40% diff --git a/docs/procurement/architecture-overview.md b/docs/procurement/architecture-overview.md new file mode 100644 index 0000000..6077b83 --- /dev/null +++ b/docs/procurement/architecture-overview.md @@ -0,0 +1,14 @@ +# Architecture Overview (Procurement Package) + +Meridian operates as an overlay control plane on existing Git infrastructure, ingesting events from Git providers, evaluating controls, and producing tamper-evident governance evidence. + +## Deployment Modes +- SaaS managed +- Customer-hosted on-prem +- Hybrid control/data plane split + +## Security Controls Summary +- Tenant isolation (RLS + tenant-bound context) +- Immutable append-only audit store +- Signed release pipeline and SBOM +- SSO-ready integration path (OIDC/SAML) diff --git a/docs/procurement/data-handling-summary.md b/docs/procurement/data-handling-summary.md new file mode 100644 index 0000000..217fe51 --- /dev/null +++ b/docs/procurement/data-handling-summary.md @@ -0,0 +1,6 @@ +# Data Handling Summary + +- Data processed: Git event metadata and policy evaluation outcomes +- Data residency: configurable by deployment model +- Encryption: AES-256 at rest, TLS in transit +- Retention: configurable per customer and regulation diff --git a/docs/procurement/deployment-models.md b/docs/procurement/deployment-models.md new file mode 100644 index 0000000..5952370 --- /dev/null +++ b/docs/procurement/deployment-models.md @@ -0,0 +1,10 @@ +# Deployment Models + +## SaaS +- Managed control plane and managed updates + +## On-Prem +- Customer-managed environment with Meridian deployment artifacts + +## Hybrid +- Customer data plane with managed control and update channel diff --git a/docs/procurement/security-questionnaire-template.md b/docs/procurement/security-questionnaire-template.md new file mode 100644 index 0000000..483a423 --- /dev/null +++ b/docs/procurement/security-questionnaire-template.md @@ -0,0 +1,9 @@ +# Security Questionnaire Template + +## Sections +- Organization security governance +- Data protection and privacy +- Identity and access management +- Infrastructure and operations +- Incident response and BCP/DR +- Application security and SDLC diff --git a/docs/procurement/subprocessors.md b/docs/procurement/subprocessors.md new file mode 100644 index 0000000..c4f9517 --- /dev/null +++ b/docs/procurement/subprocessors.md @@ -0,0 +1,7 @@ +# Subprocessor List + +| Subprocessor | Purpose | Data Type | +|---|---|---| +| Cloud provider | Hosting/infrastructure | Service telemetry + encrypted data at rest | +| Monitoring vendor | Observability | Operational metrics/logs | +| Email provider | Transactional notifications | Contact metadata | diff --git a/docs/prompts/codex-bootstrap.md b/docs/prompts/codex-bootstrap.md new file mode 100644 index 0000000..e4b5e52 --- /dev/null +++ b/docs/prompts/codex-bootstrap.md @@ -0,0 +1,44 @@ +# Prompt — Meridian Bootstrap (Codex) + +```text +Create a production-ready monorepo called "meridian" — an enterprise +Git compliance and governance platform (Fortune 500 tier). + +Tech stack: +- TypeScript throughout +- Node.js 20 (Fastify) for backend services +- Next.js 14 (App Router) for the dashboard +- PostgreSQL + TimescaleDB for immutable event storage +- Apache Kafka for event streaming +- Open Policy Agent (OPA) with Rego for policy engine +- Redis for caching +- Docker Compose for local development +- GitHub Actions for CI/CD +- Helm + Kubernetes for production deployment + +Start with: +1. Root package.json (npm workspaces monorepo) +2. apps/collector — Fastify service that receives GitHub/GitLab webhooks, + validates payload, hashes events with SHA-256 chaining, and publishes + to Kafka topic "git.events.raw" +3. apps/engine — OPA-based policy evaluation service that consumes + "git.events.raw", evaluates policies from .meridian/policies/*.rego, + and publishes to "git.events.evaluated" +4. apps/api — GraphQL + REST gateway with authentication (JWT + API keys) +5. apps/dashboard — Next.js executive dashboard showing compliance scores, + event timeline, and policy violation alerts +6. packages/types — shared TypeScript interfaces for MeridianEvent schema +7. packages/policies — standard OPA policy library (branch protection, + dependency security, approval requirements) +8. docker-compose.yml — full local stack (postgres, kafka, redis, all apps) +9. .github/workflows/ci.yml — CI with lint, test, security scan, OPA validation +10. Makefile with: setup, dev, build, test, security, audit-report targets +11. .meridian/policies/ — Meridian auditing itself (dogfooding) + +The MeridianEvent schema must include: event_id, sequence_id, timestamp, +platform, organization, repository, branch, actor, action, metadata, +policy_evaluation (status, violations), and integrity (hash, previous_hash). + +Use Conventional Commits, implement proper error handling, structured +logging (pino), and OpenTelemetry instrumentation throughout. +``` diff --git a/docs/security/adversarial-scenarios.md b/docs/security/adversarial-scenarios.md new file mode 100644 index 0000000..3e75a17 --- /dev/null +++ b/docs/security/adversarial-scenarios.md @@ -0,0 +1,21 @@ +# Adversarial Scenarios + +## 1) Insider Attempt +- Scenario: privileged user tries policy bypass. +- Control: protected branches, audit logs, approval workflow evidence. + +## 2) DB Tampering +- Scenario: attempt UPDATE/DELETE in audit table. +- Control: append-only trigger + chain verification + RLS isolation. + +## 3) Replay Attack +- Scenario: repeated webhook delivery replay. +- Control: delivery-id tracking (planned), signature/timestamp checks, rate limiting. + +## 4) Signature Forgery +- Scenario: forged webhook signature. +- Control: HMAC verification with tenant-bound secret. + +## 5) Tenant Breakout Attempt +- Scenario: cross-tenant query access. +- Control: tenant context + RLS + API-level tenant checks. diff --git a/docs/security/cryptographic-model.md b/docs/security/cryptographic-model.md new file mode 100644 index 0000000..3fbb5fd --- /dev/null +++ b/docs/security/cryptographic-model.md @@ -0,0 +1,27 @@ +# Cryptographic Model + +## 1. Integrity +- Event chain hash algorithm: **SHA-256** +- Canonical serialization: deterministic JSON ordering +- Chain rule: `event_hash = SHA256(canonical_event || previous_hash)` + +## 2. Signatures +- Preferred event signature algorithm: **Ed25519** +- Signature scope: event hash + timestamp + tenant_id +- Verification required for external audit exports + +## 3. Key Management +- Keys stored in KMS/HSM (cloud KMS or dedicated HSM) +- No plaintext key material in application config +- Per-tenant key namespace +- Access through short-lived credentials only + +## 4. Rotation Policy +- Regular rotation every 90 days (or stricter by tenant policy) +- Immediate rotation on compromise indicators +- Versioned key IDs attached to signatures +- Backward verification support for retired keys + +## 5. Encryption +- At rest: AES-256 via managed storage encryption +- In transit: TLS 1.2+ (TLS 1.3 preferred) diff --git a/docs/security/data-flow-diagram.md b/docs/security/data-flow-diagram.md new file mode 100644 index 0000000..e96fbe0 --- /dev/null +++ b/docs/security/data-flow-diagram.md @@ -0,0 +1,31 @@ +# Data Flow Diagram (DFD) + +## Level 1 +```text +[GitHub/GitLab/Bitbucket] + | + | Webhooks/API + v ++--------------------+ +------------------+ +| Event Collector | ---> | Kafka Topics | ++--------------------+ +------------------+ + | | + v v ++--------------------+ +------------------+ +| Immutable Audit DB | <---- | Policy Engine | ++--------------------+ +------------------+ + | + v ++--------------------+ +------------------+ +| API Gateway | ---> | Executive UI | ++--------------------+ +------------------+ + | + v + [Audit Exports / SIEM / GRC] +``` + +## Trust Boundaries +- TB1: External provider network -> Meridian ingress +- TB2: Service runtime -> Data stores +- TB3: Tenant A -> Tenant B isolation boundary +- TB4: Admin plane -> Customer plane diff --git a/docs/security/dpia.md b/docs/security/dpia.md new file mode 100644 index 0000000..dc2fd1c --- /dev/null +++ b/docs/security/dpia.md @@ -0,0 +1,19 @@ +# Data Protection Impact Assessment (DPIA) + +## Processing Context +Meridian processes repository event metadata for governance and compliance purposes. + +## Data Categories +- User identifiers (username/email) +- Repository metadata +- Security/compliance events + +## Risks +- Unauthorized disclosure of personal data +- Excessive retention of identifiable event data + +## Mitigations +- Data minimization and pseudonymization where feasible +- Encryption at rest and in transit +- Tenant isolation and strict RBAC +- Retention and deletion policies by jurisdiction diff --git a/docs/security/external-validation-plan.md b/docs/security/external-validation-plan.md new file mode 100644 index 0000000..69cc17d --- /dev/null +++ b/docs/security/external-validation-plan.md @@ -0,0 +1,18 @@ +# External Validation Plan + +## Objective +Provide independent trust signals beyond internal documentation. + +## Tracks +1. **Third-party pentest** + - Scope: Collector, Policy Engine, tenant isolation, export verification flow + - Frequency: at least annually or after major architecture changes +2. **Independent crypto review** + - Validate hash-chain model and export signature model +3. **Security advisory board review** + - Quarterly architecture and threat review + +## Deliverables +- Executive pentest summary +- Findings and remediation log with SLA +- Public security advisory process and disclosure timeline diff --git a/docs/security/integrity-proof.md b/docs/security/integrity-proof.md new file mode 100644 index 0000000..c92caa4 --- /dev/null +++ b/docs/security/integrity-proof.md @@ -0,0 +1,21 @@ +# Hash Chain Integrity Proof (Lite) + +## Model +For ordered events `E_1 ... E_n`: +- `H_0 = GENESIS` +- `H_i = SHA256(Canonical(E_i) || H_{i-1})` + +Stored tuple per event: `(E_i, H_{i-1}, H_i)`. + +## Properties +1. **Mutation detection**: if any `E_i` changes, `Canonical(E_i)` changes, so `H_i` changes and all following links mismatch. +2. **Reordering detection**: swapping `E_i` and `E_j` changes predecessor relation, breaking `H_{k-1}` references. +3. **Removal detection**: removing `E_i` makes `H_{i+1}` reference a non-matching predecessor. + +## Adversarial Cases +- **Payload tamper**: detected by recomputing `H_i`. +- **Backfill insertion**: detected by predecessor mismatch in verification traversal. +- **Silent deletion**: detected by chain discontinuity. + +## Conclusion +Given preimage resistance of SHA-256 and deterministic canonicalization, tampering, reordering, or deletion is computationally infeasible to hide without chain break evidence. diff --git a/docs/security/key-management-runbook.md b/docs/security/key-management-runbook.md new file mode 100644 index 0000000..c88e62d --- /dev/null +++ b/docs/security/key-management-runbook.md @@ -0,0 +1,25 @@ +# Key Management Runbook + +## Provider Model +- Primary: cloud-managed KMS/HSM (`KEY_PROVIDER=aws-kms|azure-keyvault`) +- Fallback/dev: environment-provided keys (`KEY_PROVIDER=env`) +- Every signature includes `key_id` and `key_reference` + +## Rotation Procedure (Automated) +1. Provision new key version in KMS/Key Vault. +2. Update active alias to new key (`alias/meridian-export-signing`). +3. Deploy signing service with new `EXPORT_SIGNING_KEY_ID` and `KEY_REFERENCE`. +4. Keep verification support for N-1 key versions during grace window. +5. Record rotation event in governance log. + +## Revocation Procedure +1. Disable compromised key version at KMS layer. +2. Roll alias to emergency key version. +3. Trigger SEV-1 and notify affected customers. +4. Reissue signed exports for impacted periods if needed. + +## Compromise Response +1. Contain key usage via IAM deny policy. +2. Rotate and re-attest signing chain. +3. Run forensic review on signing events. +4. Publish incident summary and remediation evidence. diff --git a/docs/security/security-whitepaper.md b/docs/security/security-whitepaper.md new file mode 100644 index 0000000..5434abf --- /dev/null +++ b/docs/security/security-whitepaper.md @@ -0,0 +1,33 @@ +# Meridian Security Whitepaper (PDF-ready draft) + +## 1. Executive Summary +Meridian provides tamper-evident governance for software delivery by combining immutable event capture, tenant isolation controls, policy-as-code, and enterprise operational safeguards. + +## 2. Security Architecture +- Collector ingress with webhook authenticity checks +- Append-only event ledger with cryptographic hash chaining +- Tenant-isolated data model with RLS controls +- Enterprise identity integration model (OIDC/SAML) + +## 3. Data Security +- Encryption in transit (TLS 1.2+) +- Encryption at rest (AES-256) +- Key lifecycle and incident runbooks + +## 4. Supply Chain Security +- Signed releases (Cosign) +- SBOM generation in CI +- Provenance attestations +- Automated dependency update policy + +## 5. Detection and Response +- Correlation IDs and structured logs +- Metrics endpoints and integrity checks +- Security incident severity model and response process + +## 6. Compliance Alignment +- SOC 2, ISO 27001, DORA control mappings +- Evidence packaging and retention model + +## 7. Shared Responsibility +Defines Meridian-managed controls and customer-managed controls for SaaS, on-prem, and hybrid deployment models. diff --git a/docs/security/threat-model-stride.md b/docs/security/threat-model-stride.md new file mode 100644 index 0000000..69adaf0 --- /dev/null +++ b/docs/security/threat-model-stride.md @@ -0,0 +1,74 @@ +# Threat Model (STRIDE) + +## 1. System Components +- Event Collector +- Policy Engine +- Immutable Audit Store +- API Gateway +- Dashboard +- Integrations (Git providers, IdP, SIEM) + +## 2. Trust Boundaries +1. External Git provider to Collector +2. Internal service mesh boundary +3. Data plane (DB/Kafka/Redis) boundary +4. Tenant boundary in query and storage layers +5. Admin/operator boundary + +## 3. STRIDE Analysis + +## Event Collector +- **Spoofing:** forged webhook sender identity. + - Mitigations: provider signature verification, nonce/replay detection, ingress ACL. +- **Tampering:** modified payload in transit. + - Mitigations: TLS 1.2+, HMAC verification before processing. +- **Repudiation:** sender denies action. + - Mitigations: store delivery ID, source metadata, signed ingest record. +- **Information Disclosure:** sensitive payload leakage. + - Mitigations: header redaction, field-level filtering, encryption at rest. +- **Denial of Service:** webhook flooding. + - Mitigations: rate limiting, autoscaling, queue buffering. +- **Elevation of Privilege:** malformed event triggers privileged code path. + - Mitigations: strict schema validation, least-privilege service accounts. + +## Policy Engine +- **Spoofing:** unauthorized policy publisher. + - Mitigations: signed commits, CODEOWNERS, protected branches. +- **Tampering:** policy bundle modification. + - Mitigations: checksum verification, immutable artifact storage. +- **Repudiation:** policy decision denied later. + - Mitigations: decision logs with policy version hash. +- **Information Disclosure:** decision traces expose secrets. + - Mitigations: redact sensitive fields before logging. +- **Denial of Service:** expensive policy evaluation payloads. + - Mitigations: evaluation timeouts, input size bounds. +- **Elevation of Privilege:** policy bypass via fallback behavior. + - Mitigations: fail-closed strategy. + +## Immutable Audit Store +- **Spoofing:** unauthorized writer role. + - Mitigations: IAM-bound DB auth, short-lived credentials. +- **Tampering:** update/delete on historical events. + - Mitigations: append-only triggers, restricted roles, chain verification. +- **Repudiation:** actor disputes writes. + - Mitigations: signed write envelopes, append-only write logs. +- **Information Disclosure:** cross-tenant reads. + - Mitigations: schema isolation, RLS, encryption keys per tenant. +- **Denial of Service:** DB saturation. + - Mitigations: partitioning, replicas, write shedding. +- **Elevation of Privilege:** SQL injection path. + - Mitigations: parameterized queries, DB firewall policies. + +## API Gateway and Dashboard +- **Spoofing:** session/token impersonation. + - Mitigations: SSO (SAML/OIDC), MFA, token rotation. +- **Tampering:** API request manipulation. + - Mitigations: signed JWT validation, strict authorization checks. +- **Repudiation:** admin action denial. + - Mitigations: immutable admin audit log. +- **Information Disclosure:** insecure endpoints. + - Mitigations: RBAC/ABAC, tenant-scoped authorization. +- **Denial of Service:** API burst traffic. + - Mitigations: WAF, rate limits, autoscaling. +- **Elevation of Privilege:** broken access control. + - Mitigations: centralized authz layer, regression tests. diff --git a/docs/support/api-deprecation-policy.md b/docs/support/api-deprecation-policy.md new file mode 100644 index 0000000..97c7456 --- /dev/null +++ b/docs/support/api-deprecation-policy.md @@ -0,0 +1,6 @@ +# API Deprecation Policy + +- Deprecation notice period: minimum 180 days. +- Deprecation announced in changelog, docs, and release notes. +- Replacement endpoint/field required before removal. +- Runtime warning headers added for deprecated endpoints. diff --git a/docs/support/backward-compatibility-policy.md b/docs/support/backward-compatibility-policy.md new file mode 100644 index 0000000..544a122 --- /dev/null +++ b/docs/support/backward-compatibility-policy.md @@ -0,0 +1,6 @@ +# Backward Compatibility Policy + +- API changes follow semver and documented compatibility matrix. +- No breaking API change in minor/patch versions. +- Data export format is versioned and backward-parseable for at least two major versions. +- Migration guides required for any major release. diff --git a/docs/support/support-policy.md b/docs/support/support-policy.md new file mode 100644 index 0000000..85a9f82 --- /dev/null +++ b/docs/support/support-policy.md @@ -0,0 +1,15 @@ +# Support Policy + +## Version Support Window +- Major versions: supported for 24 months +- Minor versions: supported for 12 months +- Patch releases: security and critical fixes only + +## Support Tiers +- Standard: business-hours support +- Enterprise: 24x7 support with priority SLAs + +## Security Patch SLA +- Critical: patch or mitigation within 72h +- High: within 7 days +- Medium/Low: next scheduled patch cycle diff --git a/docs/support/upgrade-path.md b/docs/support/upgrade-path.md new file mode 100644 index 0000000..4359954 --- /dev/null +++ b/docs/support/upgrade-path.md @@ -0,0 +1,7 @@ +# Upgrade Path + +1. Review release notes and compatibility matrix. +2. Run pre-upgrade validation (config schema, DB backup, policy bundle validation). +3. Perform staged rollout (canary -> partial -> full). +4. Run post-upgrade checks (integrity verifier, policy evaluation health, metrics baseline). +5. Keep rollback package for one full release cycle. diff --git a/index.html b/index.html new file mode 100644 index 0000000..4e0e836 --- /dev/null +++ b/index.html @@ -0,0 +1,150 @@ + + + + + +Meridian — The Fixed Point of Truth for Enterprise Engineering + + + + + +
+ + +
+
+
+

Enterprise Git Governance · Fortune 500

+

The Fixed Point of Truth for Enterprise Engineering

+

Meridian installs on top of your existing Git infrastructure to deliver immutable auditability, policy enforcement, and executive-level compliance intelligence.

+ +
+
+
meridian · audit-event · live
+
{
+  "event_id": "e7f2a-9c1b",
+  "action": "pr_merge",
+  "branch": "main",
+  "approvals": 2,
+  "policy_result": "PASS",
+  "hash": "sha256:a3f9...",
+  "prev_hash": "sha256:b71c..."
+}
+✓ Audit trail recorded. Immutable.
+
+
+
+ +
+ +
+
+

The Problem

+

Enterprise Git is ungoverned at scale

+

Thousands of repositories and distributed teams create a governance blind spot. Meridian converts scattered controls into a provable, continuously monitored control plane.

+
+
01

No immutable audit trail

Teams struggle to prove what happened, who approved it, and whether policy was active at that point in time.

+
02

Policies drift silently

Critical controls often live in admin panels and can be changed without governed review or explicit version history.

+
03

No C-level visibility

Risk and compliance are opaque until incidents happen or audits force expensive evidence collection efforts.

+
+
+
+ +
+ +
+
+

The Platform

+

Four layers. One fixed point of truth.

+

Collector, Immutable Audit Store, Policy Engine, and Compliance Intelligence integrate with GitHub/GitLab/Bitbucket without migration.

+
+

Immutable Audit Trail

SHA-256 hash chaining provides tamper-evident evidence suitable for external audit workflows.

+

Policy-as-Code

Versioned Rego policies allow transparent control evolution with PR review and historical traceability.

+

Compliance Intelligence

Scores per repository, team, and organization mapped to SOC 2, ISO 27001, SOX, and DORA controls.

+

Anomaly Detection

Detect off-hours privileged pushes, suspicious review bypasses, and abrupt score degradation patterns.

+
+
+
+ +
+ +
+
+

Compliance Intelligence

+

A score your board can act on

+
+
Branch Protection
97%
+
Code Review (2+)
82%
+
Secret Scanning
100%
+
SBOM Coverage
54%
+
+
+
+ +
+
+

Get Started

+

Ready to answer your auditors?

+

Meridian deploys in under one day over your current Git platform and produces governance-grade evidence from day one.

+ Request Enterprise Demo +
+
+ +
© 2025 Meridian · SOC2 · ISO27001 · SOX · EU DORA
+ + diff --git a/package.json b/package.json new file mode 100644 index 0000000..8cc2093 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "meridian", + "private": true, + "version": "0.2.0", + "workspaces": [ + "apps/*", + "packages/*" + ], + "scripts": { + "build": "npm run -w apps/collector build && npm run -w apps/policy-engine build", + "dev": "npm run -w apps/collector dev", + "test": "npm run -w apps/collector test", + "lint": "npm run -w apps/collector lint && npm run -w apps/policy-engine lint" + } +} diff --git a/reports/failover-test.md b/reports/failover-test.md new file mode 100644 index 0000000..d346abe --- /dev/null +++ b/reports/failover-test.md @@ -0,0 +1,12 @@ +# Failover Test Report Template + +## Scenario +- Multi-AZ failover simulation for data plane and collector service + +## Observed Metrics +- Recovery Time (measured) +- Data Loss Window (measured) +- Queue backlog recovery time + +## Result +- PASS/FAIL with remediation actions diff --git a/reports/load-test-baseline.md b/reports/load-test-baseline.md new file mode 100644 index 0000000..34f7f4c --- /dev/null +++ b/reports/load-test-baseline.md @@ -0,0 +1,18 @@ +# Load Test Baseline Report + +- Tool: k6 +- Script: `tests/load/k6-script.js` +- Target: Collector webhook ingestion endpoint + +## Planned thresholds +- p95 < 2s +- p99 < 5s +- error rate < 1% + +## Run command +```bash +k6 run tests/load/k6-script.js +``` + +## Notes +Populate this report with measured values after executing in an environment with Docker + dependencies available. diff --git a/scripts/bootstrap.sh b/scripts/bootstrap.sh new file mode 100755 index 0000000..f7baa9d --- /dev/null +++ b/scripts/bootstrap.sh @@ -0,0 +1,4 @@ +#!/usr/bin/env bash +set -euo pipefail +npm install +docker compose up -d postgres redis kafka opa collector policy-engine diff --git a/scripts/validate-local.sh b/scripts/validate-local.sh new file mode 100755 index 0000000..92a3ecd --- /dev/null +++ b/scripts/validate-local.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT_DIR" + +REPORT_DIR="reports" +mkdir -p "$REPORT_DIR" +REPORT_FILE="$REPORT_DIR/local-validation-report.md" + +pass() { echo "✅ $1" | tee -a "$REPORT_FILE"; } +warn() { echo "⚠️ $1" | tee -a "$REPORT_FILE"; } +fail() { echo "❌ $1" | tee -a "$REPORT_FILE"; exit 1; } + +: > "$REPORT_FILE" +echo "# Local Validation Report" >> "$REPORT_FILE" +echo "" >> "$REPORT_FILE" +echo "Generated at: $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> "$REPORT_FILE" +echo "" >> "$REPORT_FILE" + +if command -v npm >/dev/null 2>&1; then + if npm install; then + pass "npm install" + else + warn "npm install failed (check registry/network/proxy)" + fi +else + warn "npm not found" +fi + +if command -v docker >/dev/null 2>&1; then + if docker compose up -d --build postgres collector; then + pass "docker compose up -d --build postgres collector" + else + fail "docker compose up failed" + fi +else + fail "docker CLI not found" +fi + +sleep 5 + +if curl -fsS "http://localhost:8080/metrics" >/tmp/meridian-metrics.out; then + pass "GET /metrics" +else + fail "Collector /metrics unavailable" +fi + +PAYLOAD='{"organization":"acme","action":"push","branch":"main"}' +if curl -fsS -X POST "http://localhost:8080/webhooks/gitlab" \ + -H "Content-Type: application/json" \ + -H "X-Tenant-Id: acme" \ + -H "X-Api-Key: acme-key" \ + -H "X-Gitlab-Event: Push Hook" \ + -H "X-Gitlab-Token: dev-gitlab-token" \ + -d "$PAYLOAD" >/tmp/meridian-webhook.out; then + pass "POST /webhooks/gitlab" +else + fail "Webhook ingest failed" +fi + +if curl -fsS "http://localhost:8080/audit/integrity?tenant_id=acme" >/tmp/meridian-integrity.out; then + pass "GET /audit/integrity?tenant_id=acme" +else + fail "Integrity endpoint failed" +fi + +if curl -fsS "http://localhost:8080/audit/export?tenant_id=acme" >/tmp/meridian-export.json; then + pass "GET /audit/export?tenant_id=acme" +else + fail "Audit export failed" +fi + +if node apps/cli/meridian.js verify --bundle /tmp/meridian-export.json --key dev-export-signing-key >/tmp/meridian-verify.out; then + pass "CLI verify audit export" +else + fail "CLI verification failed" +fi + +echo "" >> "$REPORT_FILE" +echo "## Artifacts" >> "$REPORT_FILE" +echo "- /tmp/meridian-metrics.out" >> "$REPORT_FILE" +echo "- /tmp/meridian-webhook.out" >> "$REPORT_FILE" +echo "- /tmp/meridian-integrity.out" >> "$REPORT_FILE" +echo "- /tmp/meridian-export.json" >> "$REPORT_FILE" +echo "- /tmp/meridian-verify.out" >> "$REPORT_FILE" + +pass "Local validation flow complete" diff --git a/tests/load/k6-script.js b/tests/load/k6-script.js new file mode 100644 index 0000000..6ba2c96 --- /dev/null +++ b/tests/load/k6-script.js @@ -0,0 +1,28 @@ +import http from 'k6/http'; +import { check, sleep } from 'k6'; + +export const options = { + vus: 20, + duration: '60s', + thresholds: { + http_req_duration: ['p(95)<2000', 'p(99)<5000'], + http_req_failed: ['rate<0.01'] + } +}; + +export default function () { + const payload = JSON.stringify({ organization: 'acme', action: 'push', branch: 'main' }); + const res = http.post('http://localhost:8080/webhooks/gitlab', payload, { + headers: { + 'Content-Type': 'application/json', + 'X-Tenant-Id': 'acme', + 'X-Gitlab-Event': 'Push Hook', + 'X-Gitlab-Token': 'dev-gitlab-token' + } + }); + + check(res, { + accepted: (r) => r.status === 202 + }); + sleep(0.5); +} diff --git a/tests/resilience/chaos-checklist.md b/tests/resilience/chaos-checklist.md new file mode 100644 index 0000000..e7c715e --- /dev/null +++ b/tests/resilience/chaos-checklist.md @@ -0,0 +1,7 @@ +# Resilience / Chaos Checklist + +- Simulate collector restart during ingress load +- Simulate postgres failover to standby +- Simulate kafka broker unavailability and recovery +- Verify policy engine recovery and backlog catch-up +- Verify no chain-integrity regression after failover From 170653afd3efc66d8b36d23eb964ba47df40948e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felipe=20Fernandes=20=E2=80=94=20Systems=20=26=20AI=20Engi?= =?UTF-8?q?neer?= Date: Sun, 22 Feb 2026 17:07:57 -0300 Subject: [PATCH 2/2] fix(collector): add pg type declarations for tsc lint --- apps/collector/package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/collector/package.json b/apps/collector/package.json index 84a9db3..cd324db 100644 --- a/apps/collector/package.json +++ b/apps/collector/package.json @@ -20,6 +20,7 @@ "@types/node": "^22.10.2", "tsx": "^4.19.2", "typescript": "^5.7.2", - "vitest": "^2.1.8" + "vitest": "^2.1.8", + "@types/pg": "^8.11.11" } }