{"id":49505681,"url":"https://github.com/ugurcan-aytar/rampart","last_synced_at":"2026-05-01T15:40:23.015Z","repository":{"id":352662403,"uuid":"1215998113","full_name":"ugurcan-aytar/rampart","owner":"ugurcan-aytar","description":"Cloud-agnostic supply chain incident response engine. One OpenAPI contract, four consumers: Backstage plugin, GitHub Action, CLI, Slack. Go + Rust, stdlib-first.","archived":false,"fork":false,"pushed_at":"2026-04-27T19:14:26.000Z","size":5485,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-27T20:28:55.140Z","etag":null,"topics":["backstage","clean-architecture","cli","devsecops","github-actions","golang","incident-response","monorepo","openapi","platform-engineering","rust","sbom","supply-chain-security","typescript"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ugurcan-aytar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":"SECURITY.md","support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-20T13:13:48.000Z","updated_at":"2026-04-27T18:57:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ugurcan-aytar/rampart","commit_stats":null,"previous_names":["ugurcan-aytar/rampart"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ugurcan-aytar/rampart","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ugurcan-aytar%2Frampart","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ugurcan-aytar%2Frampart/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ugurcan-aytar%2Frampart/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ugurcan-aytar%2Frampart/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ugurcan-aytar","download_url":"https://codeload.github.com/ugurcan-aytar/rampart/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ugurcan-aytar%2Frampart/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32503203,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-30T13:12:12.517Z","status":"online","status_checked_at":"2026-05-01T02:00:05.856Z","response_time":64,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["backstage","clean-architecture","cli","devsecops","github-actions","golang","incident-response","monorepo","openapi","platform-engineering","rust","sbom","supply-chain-security","typescript"],"created_at":"2026-05-01T15:40:22.331Z","updated_at":"2026-05-01T15:40:22.999Z","avatar_url":"https://github.com/ugurcan-aytar.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# rampart\n\n[![engine](https://github.com/ugurcan-aytar/rampart/actions/workflows/engine.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/engine.yml)\n[![native](https://github.com/ugurcan-aytar/rampart/actions/workflows/native.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/native.yml)\n[![parity](https://github.com/ugurcan-aytar/rampart/actions/workflows/parity.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/parity.yml)\n[![e2e](https://github.com/ugurcan-aytar/rampart/actions/workflows/e2e.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/e2e.yml)\n[![codeql](https://github.com/ugurcan-aytar/rampart/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/codeql.yml)\n[![release](https://github.com/ugurcan-aytar/rampart/actions/workflows/release.yml/badge.svg?branch=main)](https://github.com/ugurcan-aytar/rampart/actions/workflows/release.yml)\n[![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)\n\n\u003e A rampart for your supply chain.\n\n`rampart` is an open-source supply chain incident response engine for\nfive ecosystems — **npm, Go modules, Cargo, PyPI, and Maven/Gradle**.\nIt ingests Software Bill of Materials snapshots, matches them against\nIndicators of Compromise, opens incidents on match, and tells you the\nthree things that matter when a `shai-hulud`-class event is breaking\non Hacker News at 3 a.m.:\n\n1. **Which services are affected.** Every SBOM is pinned to a Backstage\n   component reference; the blast radius is a query, not a hunt.\n2. **Who owns them.** Component ownership flows from the catalog; the\n   incident routes to the team before a human has to triage.\n3. **What's the playbook.** The incident carries an append-only\n   remediation log — `pin_version`, `rotate_secret`, `open_pr`,\n   `notify`, `dismiss`. Audit-grade, not vibes-grade.\n\nThe engine is Go with a Postgres-backed storage layer; an optional\nRust sidecar parses lockfiles over a Unix Domain Socket for\ndeployments that want an extra layer of parser isolation (see\n[ADR-0005](./docs/decisions/0005-no-cgo-rust-via-uds.md)).\nThree consumers — a Backstage plugin, a GitHub Action, and a CLI —\nspeak the same OpenAPI contract; no one imports the engine's Go types\ndirectly.\n\n## Why rampart exists\n\nThere's an axe-shaped hole in supply chain tooling. Dependabot raises\nPRs, Socket scores packages, CodeQL flags source-code issues. None of\nthem answer the \"is my production affected right now, and by whom\" of\na live incident. rampart is the glue that turns a feed entry into a\nrouted, triageable incident with the blast radius attached.\n\n| Concern | Snyk / Socket | rampart |\n|---|---|---|\n| Self-hosted | Limited / paid tier | First-class, default |\n| Telemetry | Cloud callbacks | Zero (ADR-0011 non-goal) |\n| Backstage native | No | Yes — first-class plugin |\n| Storage | Cloud DB | Local Postgres (or in-memory for dev) |\n| Coverage | Source + supply chain | Supply-chain incident lifecycle |\n| Cost | Per-dev SaaS | OSS + self-hosted infra |\n\nThree user segments, three different shapes of the pain:\n\n### Solo developer\nRunning `npm audit` weekly isn't enough when Shai-Hulud worms itself\nacross 187 packages in hours. You want to know at dinner that one of\nyour open-source projects pulled a compromised transitive, without\npaying for an enterprise seat.\n\n### Mid-size team (10–50 engineers, no platform org)\nDependabot covers the weekly grind; Socket or Semgrep cover the\nper-package score. Nothing correlates \"SBOM × IoC feed → who owns\nwhat, who's on call, how's the incident tracked\". rampart runs as a\nself-hosted daemon; Slack gets the event, the team gets the incident.\n\n### Platform team (Backstage-equipped or ready to adopt)\nYour blast-radius lives in three places: SBOMs in one pipeline, owner\nmappings in the catalog, incident history in PagerDuty. The rampart\nBackstage plugin pulls all three into one `IncidentDashboard` inside\nthe developer portal your engineers already open fifty times a day.\n\n## Quickstart — pick your path\n\nEach path runs in under 5 minutes on a fresh checkout with Go 1.25\nand Docker installed.\n\n### Path 1 — Solo developer (CLI)\n\nScan a lockfile from the terminal. No daemon, no services, just a\nbinary. Auto-detects the ecosystem from the filename:\n\n```bash\ngit clone https://github.com/ugurcan-aytar/rampart\ncd rampart\ngo run ./cli/cmd/rampart scan path/to/your/package-lock.json     # or go.sum, Cargo.lock, requirements.txt, poetry.lock, uv.lock, pom.xml, gradle.lockfile\n```\n\nPre-built binaries (linux / darwin / windows × amd64 / arm64) are\nattached to every [GitHub Release](https://github.com/ugurcan-aytar/rampart/releases),\ncosign-signed against the GitHub OIDC issuer. See [SECURITY.md](./SECURITY.md)\nfor the verification recipe.\n\nThe default `scan` output is the pure ParsedSBOM (no ID, no\ntimestamp). Add `--component-ref kind:Component/default/your-app\n--commit-sha $(git rev-parse HEAD)` to get a full SBOM with a ULID +\n`GeneratedAt` — feed that into your own storage, or pipe it to a\nrampart daemon. Use `--format sarif` for the\n[SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html)\nshape consumed by the GitHub code-scanning API\n(`integrations/github-action/` wraps this flow).\n\n### Path 2 — Mid-size team (self-hosted)\n\nBring up the engine, Postgres, the mock npm registry, and the Slack\nnotifier. No Backstage, no demo scenarios — just the incident engine\nand a subscriber:\n\n```bash\ndocker compose up -d postgres engine mock-npm-registry slack-notifier\ncurl -sSf http://localhost:8080/healthz                    # {\"status\":\"ok\"}\n\n# Register components + ingest SBOMs + publish an IoC\n./scripts/seed-catalog.sh\n./scripts/demo-scenarios/axios-compromise.sh\n\n# Slack-notifier logs the payloads it would POST:\ndocker compose logs slack-notifier | grep 'would POST to Slack'\n```\n\nSet `SLACK_WEBHOOK_URL` in your shell (or a `.env`) to flip\n`slack-notifier` out of dry-run and hit a real webhook. The engine\nexposes `/v1/stream` (Server-Sent Events); anything that speaks SSE\ncan subscribe — PagerDuty routing, your own bridge, etc. State lives\nin the Postgres volume (`rampart-pg-data`) and survives container\nrestarts.\n\n### Path 3 — Platform team (Backstage)\n\nFull stack: engine + Postgres + mock npm + Slack + a production\nBackstage container that renders the rampart `IncidentDashboard` plus\nthe v0.2.0 incident detail drawer and blast-radius graph:\n\n```bash\nmake demo-axios\nopen http://localhost:3000                                 # IncidentDashboard\n```\n\nReplay the 2026-03-31 `axios@1.11.0` compromise, the 2026-04-18\n`rampage-*` worm, or the 2026-04-19 Vercel OAuth leak — each is a\nseparate make target:\n\n```bash\nmake demo-axios          # 2 affected components\nmake demo-shai-hulud     # 1 component × 5 IoCs = 5 incidents\nmake demo-vercel         # 1 narrow-blast-radius incident\nmake demo-native         # axios scenario routed through the Rust sidecar\nmake demo-down           # clean teardown\n```\n\n## Features\n\n**Five ecosystems, eight lockfile dialects.**\n\n| Ecosystem | Lockfile(s) | Auto-detect filename(s) |\n|---|---|---|\n| npm | `package-lock.json` v3 (Go + Rust parity-tested) | `package-lock.json` |\n| Go modules | `go.sum` + `go.mod` (replace directives, pseudo-versions) | `go.sum` |\n| Cargo | `Cargo.lock` (workspaces, registry vs git source) | `Cargo.lock` |\n| PyPI | `requirements.txt`, `poetry.lock`, `uv.lock` (PEP 503 name normalisation) | `requirements*.txt`, `poetry.lock`, `uv.lock` |\n| Maven | `pom.xml` (property substitution), `gradle.lockfile` | `pom.xml`, `gradle.lockfile` |\n\nPyPI and Maven (Theme C3+C4) ship CLI-only in v0.2.1 — Wasm-bridged\nparity with the Rust sidecar lands in v0.5.0+.\n\n**Matching and incident lifecycle.**\n- Three IoC kinds: `packageVersion` (exact), `packageRange` (semver\n  via `Masterminds/semver`), `publisherAnomaly` (Theme F3 + ADR-0014\n  bridges to the anomaly detector).\n- Forward matching when an IoC is published, retroactive when an\n  SBOM is ingested; idempotent by `(IoCID, ComponentRef)`.\n- Incident state machine: `pending → triaged → acknowledged →\n  remediating → closed`, with `dismissed` reachable from any\n  non-terminal state.\n- Remediation audit log: append-only, typed kind enum.\n- Blast radius (`POST /v1/blast-radius`) — hybrid lookup: cached\n  `incidents` JOIN for ingested IoCs, live matcher for what-if\n  queries (`shai-hulud@1.12 — who pages out?`).\n- Server-Sent Events stream (`GET /v1/stream`) broadcasts five\n  event types, with heartbeat framing for proxy compatibility.\n\n**Publisher-anomaly detector (Theme F).** Three detectors against\npublisher-graph snapshots ingested from npm + GitHub:\nmaintainer email drift, OIDC publishing regression, version jump.\nAnomaly hits flow into the standard incident workflow via the\n`IoCBodyAnomaly` bridge ([ADR-0014](./docs/decisions/0014-anomaly-ioc-bridge.md)).\nDefault off (`RAMPART_PUBLISHER_ENABLED`, `RAMPART_ANOMALY_ENABLED`).\n\n**Production security posture (Theme A).**\n- JWT middleware on `/v1/*` with HS256/RS256, scope-based\n  authorisation (`read` / `write` / `admin`), token issuance via\n  `POST /v1/auth/token`. Default off for backward compat —\n  see [docs/operations/auth-providers.md](./docs/operations/auth-providers.md)\n  for GitHub OAuth / Azure AD / Okta / generic-OIDC templates.\n- Env-allow-list CORS (`RAMPART_CORS_ORIGINS`); v0.1.x permissive\n  wildcard removed.\n- Service-to-service auth between Backstage backend and engine via\n  static JWT — see [ADR-0012](./docs/decisions/0012-auth-boundary-at-engine.md)\n  for the auth-boundary-at-engine architecture.\n\n**Postgres storage.** `pgx` + goose migrations. `docker-compose.yml`\nincludes `postgres:16-alpine` with healthcheck. Default backend;\nin-memory mode preserved via `RAMPART_STORAGE=memory` for dev. The\nshared `storagetest` contract suite enforces parity between\nbackends.\n\n**Frontend depth (Theme E).** Backstage plugin ships an incident\ndetail drawer (timeline, matched IoC, affected components,\nremediation log), a blast-radius graph (`reactflow` + `dagre`\nauto-layout, double-click re-root), and a multi-state /\nmulti-ecosystem / time-range / owner search toolbar with URL state\nfor bookmarkable views.\n\n**Consumers.**\n- CLI (`cli/cmd/rampart`) — scan a lockfile in any of 8 dialects.\n- GitHub Action (`integrations/github-action`) — lockfile → SARIF →\n  code-scanning upload.\n- Slack notifier (`integrations/slack-notifier`) — SSE subscriber,\n  dry-run by default, real webhook behind `SLACK_WEBHOOK_URL`.\n- Pre-commit hook (`integrations/precommit-hook`) — validate a\n  lockfile before it lands.\n- Backstage frontend plugin — `IncidentDashboard` + `IncidentDetail`\n  routable extensions, blast-radius graph, anomaly panel.\n- Backstage backend plugin — `/api/rampart/v1/*` proxy +\n  `CatalogSync` tick.\n\n**Infrastructure.**\n- Docker Compose demo stack (`make demo-axios` — 5 s cold boot).\n- GitHub Actions: per-PR engine / native / parity / gen-check /\n  backstage / integrations / codeql / e2e + dependency-review +\n  release.\n- OpenAPI contract (`schemas/openapi.yaml`) drives Go + TS codegen;\n  drift is a CI failure (`make gen-check`).\n- `goreleaser` + `cargo` cross-compile matrix + multi-arch container\n  images on `ghcr.io`, all cosign-signed with SBOM attestation.\n\n## Architecture\n\n```\n  ┌───────────────┐      ┌───────────────────┐\n  │  Backstage    │      │  GitHub Action    │\n  │  plugin +     │      │  SARIF uploader   │\n  │  backend      │      └────────┬──────────┘\n  └───────┬───────┘               │\n          │  HTTP + SSE           │\n          ▼                       ▼\n  ┌─────────────────────────────────────────┐\n  │  engine (Go)                            │\n  │    api/      OpenAPI server + JWT auth  │\n  │    sbom/     parsers (5 ecosystems)     │\n  │    matcher   IoC vs SBOM (hybrid cache) │\n  │    publisher anomaly detector (3 kinds) │\n  │    events    in-process SSE bus         │\n  │    storage   postgres (default)         │\n  └──────┬─────────────────────────┬────────┘\n         │ UDS (opt-in, ADR-0005)  │ pgx\n         ▼                         ▼\n  ┌─────────────────┐     ┌───────────────────┐\n  │  rampart-native │     │  postgres:16      │\n  │  Rust parser    │     │  goose migrations │\n  └─────────────────┘     └───────────────────┘\n                          ┌───────────────────┐\n                          │  slack-notifier   │\n                          │  SSE subscriber   │\n                          └───────────────────┘\n```\n\n**Domain entities.** Component, SBOM, IoC, Incident, Remediation,\nPublisher, PublisherSnapshot, Anomaly. The publisher domain split\nbetween snapshots (history) and profile (aggregate identity) is\ncovered by [ADR-0013](./docs/decisions/0013-publisher-domain-split.md).\n\n**Contract.** [`schemas/openapi.yaml`](./schemas/openapi.yaml).\nEverything downstream — the Backstage TS client, the engine's Go\nserver, the CLI, the SARIF shape in the GitHub Action — speaks this\nsingle document. Drift is enforced at CI time by `make gen-check`.\n\n## Performance\n\nv0.2.1 baseline measured on Apple M3 Pro / `postgres:16-alpine` /\nGo 1.25 against a 10 000-component × 10 000-SBOM × 500-IoC fixture\n(see [`docs/performance/v020-load-test.md`](./docs/performance/v020-load-test.md)\nfor methodology, the harness in `test/load/`, and the v0.2.0 →\nv0.2.1 delta table):\n\n| Metric | Result |\n|---|---|\n| Total ingest | 86 s |\n| Blast-radius p95 | 2.49 ms |\n| Incident-detail p95 | 2.61 ms |\n| Incidents opened (correctness sanity) | 5 047 |\n\nHonest baseline; the v0.2.0 measurement, the v0.2.1 fix delta, and\nthe methodology notes (3-run median, single-run caveats) are all in\nthe doc. Reproducible end-to-end with `bash test/load/load_test.sh`\non the same host class.\n\n## Configuration\n\nEngine env vars (default in parens; see\n[docs/operations/deployment-patterns.md](./docs/operations/deployment-patterns.md)\nfor the three supported deployment shapes):\n\n| Variable | Default | Purpose |\n|---|---|---|\n| `RAMPART_STORAGE` | `postgres` | `postgres` or `memory` |\n| `RAMPART_DB_DSN` | `postgres://rampart:rampart@postgres:5432/rampart?sslmode=disable` | pgx connection string |\n| `RAMPART_DB_MAX_CONNS` | `10` | pgx pool size |\n| `RAMPART_AUTH_ENABLED` | `false` | JWT middleware on `/v1/*` |\n| `RAMPART_AUTH_ALGORITHM` | `HS256` | `HS256` (shared secret) or `RS256` (IdP public key) |\n| `RAMPART_AUTH_SIGNING_KEY` | _(unset)_ | Shared secret or PEM-encoded public key |\n| `RAMPART_AUTH_AUDIENCE` | _(unset)_ | Required when auth enabled — must match IdP's `aud` claim |\n| `RAMPART_CORS_ORIGINS` | _(empty — deny all)_ | Comma-separated allow-list |\n| `RAMPART_CORS_ALLOW_ALL` | `false` | Dev convenience; logs a warning when on |\n| `RAMPART_PUBLISHER_ENABLED` | `false` | Theme F1 publisher-graph ingestion |\n| `RAMPART_PUBLISHER_REFRESH_INTERVAL` | `1h` | Cron interval when publisher ingestion is on |\n| `RAMPART_ANOMALY_ENABLED` | `false` | Theme F2 anomaly detectors |\n| `RAMPART_ENGINE_AUTH_TOKEN` | _(unset)_ | Static JWT the Backstage backend forwards on every proxied call |\n\n## Documentation\n\n- [ARCHITECTURE.md](./ARCHITECTURE.md) — domain model, event flow, storage shape\n- [CONTRIBUTING.md](./CONTRIBUTING.md) — dev setup, supply-chain rules, PR conventions\n- [SECURITY.md](./SECURITY.md) — threat model, disclosure policy, cosign verification recipe\n- [CHANGELOG.md](./CHANGELOG.md) — release notes\n- [ROADMAP.md](./ROADMAP.md) — what's next; pre-1.0 cadence policy\n- [DEPS.md](./DEPS.md) — every runtime dependency, justified\n- [docs/decisions/](./docs/decisions/) — 11 ADRs (parser placement, auth boundary, publisher split, anomaly bridge, …)\n- [docs/migration/v0.1.x-to-v0.2.0.md](./docs/migration/v0.1.x-to-v0.2.0.md) — upgrade guide if you're coming from v0.1.x\n- [docs/operations/auth-providers.md](./docs/operations/auth-providers.md) — GitHub OAuth / Azure AD / Okta / generic-OIDC templates\n- [docs/operations/deployment-patterns.md](./docs/operations/deployment-patterns.md) — Backstage-fronted, standalone, reverse-proxied\n- [docs/performance/v020-load-test.md](./docs/performance/v020-load-test.md) — release-gate baseline + reproduction harness\n- [docs/benchmarks/sbom-parser.md](./docs/benchmarks/sbom-parser.md) — Go vs Rust parser throughput (honest numbers)\n- [schemas/openapi.yaml](./schemas/openapi.yaml) — API contract (single source of truth)\n- [schemas/native-ipc.md](./schemas/native-ipc.md) — wire protocol for the opt-in Rust sidecar\n\n## Status\n\nrampart is **pre-1.0 (currently v0.2.1)**. Per\n[ROADMAP.md](./ROADMAP.md), 1.0 stays deferred — release cadence\ncontinues v0.3, v0.4, … v0.10 as feature themes ship. CLI flags,\npublic Go API surface, and SQL schema may shift between minor\nversions; semver patch releases stay backwards-compatible.\n\n## License\n\nMIT. See [LICENSE](./LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fugurcan-aytar%2Frampart","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fugurcan-aytar%2Frampart","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fugurcan-aytar%2Frampart/lists"}