Supply-chain risk control plane for GitHub repositories.
SentinelFlow is a backend-focused monorepo for dependency and supply-chain risk analysis. It combines a Fastify API, a background worker, a React dashboard, PostgreSQL-backed jobs, policy evaluation, audit logging, GitHub checks, and signed webhook delivery so dependency changes can be reviewed like a real production security workflow.
In short: SentinelFlow answers the question, "Did this dependency change introduce install-time behavior that should block or require review?"
- Live demo: sentinelflow-api.onrender.com
- API docs: sentinelflow-api.onrender.com/docs
- Related scanner: anasm266/installsentry
- GitHub OAuth login, GitHub App installation flow, and webhook verification.
- PostgreSQL-backed job queue using
FOR UPDATE SKIP LOCKED. - Transactional outbox for reliable scan-completed webhook delivery.
- Policy engine for lifecycle scripts, secret reads, network egress, risky package approval, and blast radius.
- Dashboard views for grouped findings, audit logs, webhook endpoints, and delivery replay.
- Unit, integration, browser, and load-test coverage.
- A repository is connected through a GitHub App.
- A scan is queued for a branch, commit, or pull request.
- A worker downloads the repository archive, inspects the lockfile, and normalizes dependency-risk signals.
- A policy engine decides whether the dependency state passes or needs review.
- Results are persisted, displayed in the dashboard, written to audit logs, and optionally delivered to external webhook receivers.
The hosted demo is available at sentinelflow-api.onrender.com.
API documentation is available at sentinelflow-api.onrender.com/docs.
The hosted demo uses a repository allowlist, so scans are limited to configured repositories. This keeps public usage controlled while still exercising the GitHub, PostgreSQL, worker, policy, and webhook paths.
The dashboard includes a try demo button. Demo mode creates a limited session with seeded sample repositories, scan results, findings, audit logs, a webhook endpoint, and delivery history without requiring a GitHub App installation.
Modern npm projects routinely install hundreds or thousands of transitive packages. A package can run code during installation through lifecycle scripts such as install, preinstall, or postinstall. That behavior is legitimate for some packages, but it is also a common path for supply-chain attacks because install scripts execute before application code is ever imported.
SentinelFlow treats dependency changes as a backend security workflow rather than a static report. It combines policy evaluation, durable jobs, persistence, auditability, GitHub integration, and webhook delivery so teams can gate and trace dependency risk with a single system.
- GitHub OAuth login with server-side sessions.
- GitHub App repository sync and webhook ingestion.
- Repository allowlist for controlled public scanning.
- Manual scans from the dashboard.
- Pull request and push webhook handling.
- GitHub check-run updates for scan results.
- PostgreSQL-backed data model with handwritten SQL migrations.
- Postgres job queue using
FOR UPDATE SKIP LOCKED. - Transactional outbox for scan-completed events.
- Signed outbound webhook delivery with replay support.
- Policy controls for lifecycle scripts, secret reads, network egress, new risky packages, and blast radius.
- Grouped findings display that preserves raw evidence while keeping the UI readable.
- Audit logs for scan and policy actions.
- OpenAPI documentation.
- Prometheus-style metrics endpoint.
- Unit, integration, browser, and load-test scaffolding.
| Status | Meaning |
|---|---|
queued |
A scan job has been created but not processed yet. |
running |
A worker has claimed the scan job. |
succeeded |
The repository passed the active policy. |
failed |
The repository produced policy-blocking findings. |
unsupported |
The scanner cannot analyze the repository format yet. |
failed does not mean the repository build is broken. It means SentinelFlow found dependency behavior that violates the configured policy.
| Control | Purpose |
|---|---|
| Block lifecycle scripts | Flags dependencies that run install-time scripts. |
| Block secret reads | Flags scanner evidence that indicates secret/canary access. |
| New risky package approval | Adds review pressure when a risky package is newly introduced. |
| Max blast radius | Flags packages that are depended on by more packages than the configured threshold. |
Findings are grouped by package and lockfile path. Raw evidence remains stored in the database, while the dashboard shows a compact row with combined reasons.
Example:
medium | fsevents@2.3.3 | playwright/node_modules/fsevents | lifecycle script, optional platform package, new risky dependency
This means fsevents was found under playwright, it runs a lifecycle script, it is a newly introduced risky package under the current policy, and it is treated as medium severity because lockfile evidence marks it as an optional platform package.
Webhook endpoints are URLs that receive scan-completed events from SentinelFlow. They are useful for integrating with Slack bots, internal dashboards, logging systems, CI workflows, or security automation.
Each outbound webhook is delivered through a durable job. Delivery attempts are stored with status, response code, latency, and a response excerpt. Failed deliveries can be replayed from the dashboard.
Outbound webhook requests include an HMAC signature header:
x-sentinelflow-signature-256: sha256=<digest>
flowchart LR
User["User"] --> Web["React Dashboard"]
Web --> API["Fastify API"]
GitHub["GitHub App"] --> API
API --> DB[("PostgreSQL")]
API --> Jobs["jobs table"]
Jobs --> Worker["Worker"]
Worker --> GitHubApi["GitHub Archive API"]
Worker --> Scanner["Scanner Adapter"]
Scanner --> Policy["Policy Engine"]
Policy --> DB
DB --> Outbox["outbox_events"]
Outbox --> Worker
Worker --> Receiver["External Webhook Receiver"]
Worker --> Checks["GitHub Check Runs"]
apps/
api/ Fastify REST API, auth, GitHub callbacks, dashboard serving
web/ Vite + React dashboard
worker/ background job processor and scanner adapter
packages/
contracts/ shared schemas, policy logic, HMAC helpers, grouping helpers
db/ migrations, in-memory store, PostgreSQL store
tests/
e2e/ Playwright dashboard smoke tests
k6/
smoke.js load-test smoke script
SentinelFlow uses Fastify for conventional backend API structure: middleware, cookies, CORS, secure headers, structured routes, error handling, request IDs, OpenAPI, and testable server composition instead of relying on an edge-only request handler.
PostgreSQL stores users, sessions, GitHub installations, repositories, policies, scans, findings, jobs, outbox events, webhook endpoints, deliveries, idempotency keys, and audit logs. The schema is managed through handwritten SQL migrations so indexes, constraints, and relationships are explicit.
The browser dashboard uses secure HTTP-only cookies backed by server-side session records. This keeps GitHub OAuth tokens and session state out of browser storage.
Jobs are stored in PostgreSQL and claimed with FOR UPDATE SKIP LOCKED. This gives the worker durable retry behavior without introducing a separate queue dependency for the core demo.
Job types include:
manual_scanpr_scangithub_check_updatewebhook_delivery
When a scan completes, SentinelFlow writes the scan result and the scan.completed outbox event in the same transaction. A worker later creates and sends webhook deliveries. This avoids the common reliability bug where a database write succeeds but the external notification is lost.
Manual scan creation and webhook replay accept Idempotency-Key headers. Repeated requests with the same key return the original result instead of creating duplicate work.
Public scanning is restricted by SCAN_REPO_ALLOWLIST. Arbitrary repository scanning can be expensive and risky because the worker downloads archives and runs dependency analysis. The allowlist keeps the hosted demo safe while preserving the production pattern for authorization and policy checks.
The current scanner path focuses on npm package-lock.json repositories.
For each supported repository, the worker:
- Downloads the GitHub archive for the requested commit or branch.
- Requires
package-lock.json. - Parses dependency entries and lockfile metadata.
- Preserves clean package names, nested package paths, versions, optional/dev/platform evidence, and raw lockfile paths.
- Runs the InstallSentry CLI adapter when available.
- Normalizes scanner output into policy findings.
- Persists final findings with severity, rule ID, package, version, title, and evidence.
Unsupported package-manager formats currently produce an unsupported scan result instead of a misleading pass or failure.
OpenAPI documentation is served at:
/docs
/docs/json
Primary routes:
| Route | Purpose |
|---|---|
GET /health |
Liveness check. |
GET /ready |
Database readiness check. |
GET /metrics |
Prometheus-style metrics. Protected in production when METRICS_TOKEN is set. |
GET /auth/github/start |
Starts GitHub OAuth login. |
GET /auth/github/callback |
Completes GitHub OAuth login. |
GET /auth/github/install |
Redirects to GitHub App installation. |
POST /auth/demo/login |
Starts a limited public demo session. |
POST /github/webhook |
Receives GitHub App webhooks. |
GET /v1/me |
Current user. |
GET /v1/repos |
Connected repositories. |
GET /v1/repos/:repoId/policy |
Repository policy. |
PUT /v1/repos/:repoId/policy |
Update repository policy. |
POST /v1/repos/:repoId/scans |
Queue a manual scan. |
GET /v1/scans/:scanId |
Scan metadata. |
GET /v1/scans/:scanId/findings |
Raw persisted findings. |
GET /v1/audit-logs |
Audit events. |
GET /v1/webhook-endpoints |
Outbound webhook endpoints. |
POST /v1/webhook-endpoints |
Create an outbound webhook endpoint. |
GET /v1/webhook-deliveries |
Delivery history. |
POST /v1/webhook-deliveries/:deliveryId/replay |
Replay a delivery. |
- Node.js 22 or newer.
- npm 10 or newer.
- Docker Desktop for PostgreSQL-backed integration tests.
- k6 for the optional load smoke test.
npm installdocker compose up -dCreate a local environment file from the example:
cp .env.example .envOn Windows PowerShell:
Copy-Item .env.example .envThe default local database URL is:
postgres://sentinelflow:sentinelflow@localhost:5432/sentinelflow
npm run db:migrateIn separate terminals:
npm run dev:api
npm run dev:worker
npm run dev:webDefault local URLs:
| Service | URL |
|---|---|
| API | http://localhost:4000 |
| Dashboard | http://localhost:5173 |
| OpenAPI | http://localhost:4000/docs |
/auth/demo/login seeds a limited demo session and is enabled by default. /auth/dev/login is available only in non-production environments for local development and is disabled in production.
A self-hosted instance needs a GitHub App with these permissions:
| Permission | Access |
|---|---|
| Metadata | Read |
| Contents | Read |
| Pull requests | Read |
| Checks | Read and write |
Webhook events:
installationinstallation_repositoriespull_requestpush
Required production environment variables:
NODE_ENV=production
DATABASE_URL=<postgres connection string>
WEB_ORIGIN=<dashboard origin>
PUBLIC_APP_URL=<public app URL>
API_BASE_URL=<public API URL>
SESSION_SECRET=<random 32+ byte secret>
GITHUB_APP_ID=<GitHub App ID>
GITHUB_CLIENT_ID=<OAuth client ID>
GITHUB_CLIENT_SECRET=<OAuth client secret>
GITHUB_APP_SLUG=<GitHub App slug>
GITHUB_APP_PRIVATE_KEY=<PEM private key with escaped newlines if stored as one line>
GITHUB_WEBHOOK_SECRET=<GitHub webhook secret>
SCAN_REPO_ALLOWLIST=owner/repo,owner/another-repo
Useful optional variables:
RUN_WORKER_IN_API=true
MIGRATE_ON_STARTUP=true
METRICS_TOKEN=<token required for /metrics in production>
DEMO_LOGIN_ENABLED=true
SCANNER_TIMEOUT_MS=120000
SCANNER_MAX_OUTPUT_BYTES=200000
The repository includes a Render Blueprint:
render.yaml
The provided Render configuration runs the API and worker in the same web service by setting:
RUN_WORKER_IN_API=true
MIGRATE_ON_STARTUP=true
This is useful for a small hosted deployment. A production deployment should split the API and worker into separate services so web requests and background scans scale independently.
Recommended hosted components:
| Component | Example Provider |
|---|---|
| API + dashboard | Render web service |
| Worker | Render background worker or separate service |
| PostgreSQL | Neon, Supabase, Render Postgres, or RDS |
| Webhook receiver for testing | webhook.site or httpbin |
Run unit and integration tests:
npm testRun with PostgreSQL integration enabled:
DATABASE_URL=postgres://sentinelflow:sentinelflow@localhost:5432/sentinelflow npm testPowerShell:
$env:DATABASE_URL="postgres://sentinelflow:sentinelflow@localhost:5432/sentinelflow"
npm testRun typecheck:
npm run typecheckRun build:
npm run buildRun lint:
npm run lintRun browser smoke tests:
npm run test:e2eRun load smoke test when k6 is installed:
npm run test:load- Open the dashboard.
- Click
try demofor a seeded demo session, or sign in with GitHub for a real repository connection. - Select a repository in the sidebar.
- Adjust policy settings.
- Click
scan. - Review scan status and grouped findings.
- Add a webhook endpoint if external notification is needed.
- Review delivery history and replay failed deliveries when necessary.
- Use audit logs to trace scan and policy activity.
Use a disposable receiver to verify outbound delivery:
https://httpbin.org/post
Or use a request inspector such as:
https://webhook.site/
After adding the endpoint and running a scan, the deliveries table should show a scan.completed delivery with status delivered and HTTP status 200 when the receiver accepts the request.
- GitHub webhook requests are verified with HMAC signatures.
- Outbound webhooks are signed with endpoint-specific HMAC secrets.
- Browser sessions use HTTP-only cookies.
- Production dev login is disabled.
- API errors use
application/problem+json. - Stable GET responses use ETags.
- Mutating async endpoints support idempotency keys where duplicate work would be harmful.
- Scan completion and outbox event creation happen in one database transaction.
- Worker jobs retry with backoff and eventually dead-letter.
- Metrics are protected in production when
METRICS_TOKENis configured.
- The scanner currently supports npm
package-lock.jsonrepositories. Repositories using onlypnpm-lock.yaml,yarn.lock, Cargo, Poetry, Maven, or other lockfile formats are markedunsupported. - Hosted public scanning is intentionally restricted by
SCAN_REPO_ALLOWLIST. - The free-tier single-service deployment is suitable for small-scale use, not sustained production traffic.
- InstallSentry is consumed through a CLI adapter in this version rather than refactored into a library.
- The dashboard is intentionally simple and backend-focused.
- Add pnpm and yarn lockfile scanners.
- Add a scan detail drawer that exposes raw evidence behind each grouped finding.
- Add per-organization policy templates.
- Split API and worker services in production.
- Add Slack or Discord webhook templates.
- Add richer GitHub PR annotations.
- Add repository onboarding for unsupported lockfiles.
- Add a public sample-data mode for demos without GitHub installation.