Contributing to PerspectiveGraph
Thanks for your interest! PerspectiveGraph is Apache-2.0 and built to be extended.
Looking for somewhere to start? The
good first issue
label marks work that is scoped, self-contained and does not need any prior knowledge of
the engine — each one says what “done” looks like. For anything on the
roadmap, open an issue first so we can agree on the shape before you write
code.
Layout
Section titled “Layout”backend/ Go backend (CGO_ENABLED=0 → static binary, pure-Go resolver) cmd/perspectivegraph/ entry point; wires every layer. Also the `healthz` and `verify-audit <file>` subcommands. internal/ ingestion/ webhook server + collectors: trivy, semgrep, custodian, falco, build, supplychain, k8s, cloudnet, iam, sso, dataclass connector/ agentless pull connectors (AWS live, Azure fixtures) redteam/ AWS policy-simulator oracle (`redteam`, `-compare`) benchmark/ CloudGoat-shaped precision/recall battery mcp/ read-only MCP server over the GraphQL API ai/ optional Claude / OpenAI-compatible assistant broker/ NATS JetStream wrapper (stream/consumer, dead-letter, backoff) normalization/ identity resolution (join confidence) → graph upsert graph/ Store interface + memory & Apache AGE backends + per-tenant Manager analyzer/ path traversal, scoring, Monte Carlo risk, k-shortest, what-if, history/MTTR, TTL pruning, leader-gated side effects policy/ architectural invariants (forbidden graph shapes) attck/ MITRE ATT&CK technique mapping per edge type remediation/ K8s NetworkPolicy / Terraform generation (rule + hint registries) detection/ Falco + Sigma detection-as-code generation compliance/ NIST OSCAL assessment-results export action/ GitHub/GitLab PR/MR commenters (shared base) notify/ drift-alert webhook (Slack/generic) threatintel/ CISA KEV + FIRST EPSS enrichment search/ optional OpenSearch full-text index api/ GraphQL BFF + REST (suppress/ticket/validation/export), CORS auth/ ingest HMAC, bearer tokens (hash/expiry/app-scope), OIDC/JWT, RBAC audit/ tamper-evident hash-chained audit log cryptostore/ AES-256-GCM at-rest encryption for the stores + audit log exportsign/ Ed25519 detached signatures for OSCAL/SIEM exports secwatch/ sliding-window detector (auth lockout + exfiltration alerts) suppress/ ticket/ file-backed governance stores (triage, ticketing, validation/ history/ red-team verdicts, posture/MTTR trend) ratelimit/ metrics/ per-IP token bucket; Prometheus metrics httpx/ leader/ shared JSON-HTTP client; Postgres advisory-lock leader election config/ env-based config (12-factor) pkg/ontology/ shared node/edge vocabulary + Event envelope testdata/ sample scanner output for `make seed` (+ Go fuzz corpus)frontend/ React + Vite + Tailwind + Cytoscape dashboard (light/dark theming via CSS vars; inline SVG icon set, no emoji)deploy/postgres/ Postgres+AGE init SQLdeploy/helm/perspectivegraph/ Helm chart (auth / persistence / export-signing knobs)Dev loop
Section titled “Dev loop”make up # Postgres+AGE + NATS via docker composemake run-backend # Go backend (falls back to in-memory graph if no Postgres)make run-frontend # Vite dev server on :5173make seed # feed sample data → ranked attack paths appearmake seed-discovery # K8s + cloud-network + IAM + SSO topology (auto-discovered)make test # Go tests (CGO disabled for static, portable binaries)The backend builds with
CGO_ENABLED=0(see the Makefile) for static binaries and Go’s pure-Go resolver. Keep new dependencies pure-Go so this holds.
Checks CI runs - run them locally before a PR
Section titled “Checks CI runs - run them locally before a PR”# Backend (go1.26): build, vet, tests, dependency vulns, and SASTcd backendGOTOOLCHAIN=go1.26.8 CGO_ENABLED=0 go build ./... && go vet ./... && go test ./...go run golang.org/x/vuln/cmd/govulncheck@latest ./...go run github.com/securego/gosec/v2/cmd/gosec@latest -quiet -exclude=G104 ./...
# Frontend: types, build, unit testscd ../frontend && npx tsc --noEmit && npm run build && npm testCI also runs gitleaks (secret scan), npm audit, a Trivy image scan, and an
AGE-store + leader-election integration job against a real Postgres.
Adding a new collector
Section titled “Adding a new collector”This is the most common contribution. The trivy and semgrep packages are
worked examples. To add, say, a Checkov (IaC) collector:
- Create
backend/internal/ingestion/checkov/checkov.go. - Implement the
ingestion.Collectorinterface:Source() stringandParse(io.Reader, ingestion.Options) ([]ontology.Event, error). UseOptions.Repository/RepoSlugwhen the tool’s output doesn’t self-identify the asset. - Map the tool’s findings onto the ontology (
pkg/ontology) - reuse existing node labels and edge types; propose new ones in a PR if needed (e.g. Semgrep addedWeakness). Keep edges oriented in the direction of attack progression. If the hop is an adversary action, add its MITRE ATT&CK mapping ininternal/attck. - Register it in
cmd/perspectivegraph/main.goalongside the others:ingestion.NewServer(bus, trivy.New(), semgrep.New(), …, checkov.New()). - Add a sample report under
testdata/, a table test, and aFuzzParse: a parser eats untrusted webhook bytes, so it must never panic and never emit a malformed node/edge (seeinternal/ingestion/trivy/trivy_test.go).
Collectors must produce stable node IDs (ontology.NewID) so the graph
deduplicates instead of creating parallel nodes. That is what lets findings from
different tools correlate onto the same asset.
How this project is written
Section titled “How this project is written”PerspectiveGraph is developed by a human working with Claude (Anthropic). Design decisions, the threat model and what ships are the maintainer’s; a large share of the implementation and its tests were written in that collaboration. It is said here so it does not have to be taken on trust: the gates above are what the project asks to be judged on, and they run on your machine. Contributions written the same way are welcome; hold them to the same gates.
Signing your work
Section titled “Signing your work”Every commit must carry a Signed-off-by: line. It is the
Developer Certificate of Origin - the full text is in this repository, so what you
are certifying is not behind a link - and it says, in short, that you wrote the change or
otherwise have the right to submit it under this project’s licence.
git commit -s -m "fix: ..." # appends the line using your git user.name / user.emailA DCO rather than a CLA on purpose: a CLA is a document somebody’s legal team has to approve before a first contribution, which is where most drive-by fixes die. A DCO is a statement you make in the commit, with nothing to sign and nobody to email.
CI checks it on every pull request. If you forgot, repair the branch rather than adding a new commit on top:
git rebase --signoff origin/maingit push --force-with-leaseBot commits are exempt: Dependabot signs its own, and release-please’s release commits carry no human authorship to certify - failing every release on a certification nobody is making would be theatre rather than provenance.
Dependency licences
Section titled “Dependency licences”CI fails on a dependency whose licence is outside a permissive allowlist (Apache-2.0, MIT, BSD-2-Clause, BSD-3-Clause, ISC) - for Go and for the frontend’s production dependencies alike. This project ships binaries and images under Apache-2.0, so what it redistributes has to be checkable rather than assumed, and a transitive copyleft dependency normally arrives in a routine bump rather than in a reviewed decision.
Anything outside that list stops the build so a human decides. If your change needs such a dependency, say why in the pull request rather than widening the list quietly.
Conventions
Section titled “Conventions”-
Go:
gofmt,go vet,gosecclean. Justify an unavoidable gosec finding inline with// #nosec Gxxx -- why it's safe, never a blanket exclude. Tests (and a fuzz test for parsers) for new logic. New deps must be pure-Go. -
Frontend: must pass
tsc,build, andvitest. Use the inline SVG icon set (components/icons.tsx) - no emoji in the UI; colors come from the CSS-variable design tokens (so light/dark both work), not hardcoded hex. -
Running the stack while you work:
make demoruns the published images, so it will not show your changes. Usemake demo-build(ormake up-full), which builds the backend and dashboard from your working tree. -
Frontend dependencies: install with
npm ci(make install-frontend), and to add or update one, editpackage.jsonand runmake lockfile- never a barenpm install. CI and the release image both usenpm ci, so the build is reproducible and the SBOM and SLSA provenance describe what is actually shipped.make lockfileregenerates the lockfile inside the same Linux image the release build uses, because npm records the transitive dependencies of optional platform-specific packages only for the platform it runs on: regenerating on macOS silently drops entries the Linux build needs, andnpm cithen fails in CI. -
Node is pinned once, and npm comes with it. The only place Node is named is the build stage of
frontend/Dockerfile, by exact version and digest - Node 24.21.0, which ships npm 11.19.0. CI’ssetup-nodeandmake lockfileread it throughscripts/node-image.sh, so a Dependabot bump of that line is a complete one; to change Node by hand, change that line and nothing else. Nothing installs npm over it:npm install -g npm@xpins a version but fetches it unauthenticated, which is a supply-chain regression (OpenSSF Scorecard reports it as an unpinned dependency) where the image digest is a cryptographic pin. A Go test fails if Node is written out anywhere else, if the tag floats, or if a global npm install comes back.engines.npmrecords what that Node ships: CI fails if the pinned Node brings a different npm, and if your own npm differs you get anEBADENGINEwarning - the intended signal. -
Docs + Postman: every user-facing feature updates the docs and
.env.exampleand the Postman collection (docs/perspectivegraph.postman_collection.json).README.mdis the landing page - keep it short; the depth (architecture, scoring, deploy, onboarding runbook) lives in the pages underdocs/manual/, indexed bydocs/MANUAL.md. Those files are also the source of docs.a3thinker.it: edit them as usual, and the Docs workflow checks every link and publishes the site on merge (seesite/README.md). -
Security: this tool is a map of how to attack the org, so don’t weaken its own controls (ingest HMAC, API auth/RBAC, audit log, at-rest encryption, export signing). Never commit secrets - the gitleaks gate enforces it. Found a vulnerability? Report it privately - see SECURITY.md, not a public issue or PR.
-
Commits & releases: Conventional Commits (
feat:,fix:,docs:,chore:, …) - they drive the automated CHANGELOG and versioning (release-please). One fact per message line; explain the why. Enable the bundled git hooks once so a bad commit message or a stray secret is caught before it leaves your machine:Terminal window git config core.hooksPath .githooks # commit-msg (conventional) + pre-push (gitleaks)