An open API service indexing awesome lists of open source software.

https://github.com/lord007tn/oss-protector

Shared OSS abuse intelligence for suspicious GitHub PR activity
https://github.com/lord007tn/oss-protector

abuse-detection cloudflare-workers github-app moderation open-source tanstack-start

Last synced: 30 days ago
JSON representation

Shared OSS abuse intelligence for suspicious GitHub PR activity

Awesome Lists containing this project

README

          

OSS Protector

# OSS Protector

**Shared abuse intelligence for suspicious GitHub pull request activity.**

A single GitHub App + public directory that helps maintainers spot bounty-farming, AI-spam, low-effort duplicate PRs, and outright malicious contributions before they waste anyone's time.

[![CI](https://github.com/lord007tn/oss-protector/actions/workflows/ci.yml/badge.svg)](https://github.com/lord007tn/oss-protector/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)

[Hosted instance](https://oss-protector.raedbahri90.workers.dev) · [Install the App](https://github.com/apps/oss-protector/installations/new) · [Contributing](CONTRIBUTING.md)

---

## What it does

When you install the OSS Protector GitHub App on a repository, it:

1. **Watches PRs.** Every `pull_request`, `issue_comment`, and `pull_request_review_comment` event is sent to a shared webhook.
2. **Analyzes the PR.** Changed files, patch snippets, and metadata are inspected for known abuse patterns — fake bounty farming, duplicate low-effort PRs, AI-filler, credential phishing, malicious code, dependency-script abuse, obfuscation, and backdoor indicators.
3. **Scores the contributor.** A scoring engine combines signals from this PR, prior reports across all installed repos, reporter trust, and age decay. AI validation (via OpenRouter) sanity-checks the result; deterministic fallback runs when no API key is configured.
4. **Comments.** Strong evidence gets posted as a PR assessment. Weaker signals stay in a review queue. Maintainers can confirm, dismiss, allow, or reset with `@oss-protector` commands.
5. **Publishes.** Confirmed risky accounts show up on the public directory so other maintainers can review them before merging.

It's one GitHub App, one database, one feed — maintainers don't each have to run their own.

## Key features

- **Maintainer-first review lifecycle** — PR webhooks are tracked, external contributors are reviewed, maintainers can correct outcomes from PR comments, and public scores update from validated evidence.
- **Grounded abuse signals** — lifecycle-script execution, token exfiltration, obfuscation, privileged `pull_request_target` workflow patterns, duplicate cross-repo campaigns, and maintainer reports are handled separately.
- **False-positive guardrails** — repo insiders are skipped, non-maintainer reports stay in review until a maintainer confirms them, command-only reports are capped, and harmless docs about webhooks/secrets are not treated as credential phishing.
- **Repo-local policy** — projects can add `.github/oss-protector.json` to disable analysis, trust local automation accounts, ignore path-only changes, or raise the confidence threshold for likely-abuse results.
- **Auditable profiles** — public profile pages show recent public PRs, reports, and a decision timeline of the signals that affected the score while hiding private-repo source links.

## Stack

- **Frontend** — TanStack Start (file-based routing, SSR), React 19, shadcn/ui on Base UI primitives, Tailwind 4.
- **API + Worker** — TanStack Start server functions on Cloudflare Workers.
- **Data** — Drizzle ORM `1.0.0-beta.24` on Cloudflare D1.
- **Auth** — Better Auth (GitHub user sign-in) + `@octokit/auth-app` (App installation tokens).
- **AI** — OpenRouter chat completions, free-tier model chain with a paid fallback.
- **Lint/format** — Ultracite (oxlint + oxfmt), Biome.
- **Tests** — Vitest.

## Quick start

```bash
git clone https://github.com/lord007tn/oss-protector.git
cd oss-protector
pnpm install
cp .env.example .env
pnpm dev
```

Open . Most UI and scoring work can be done without D1 or a GitHub App. Database-backed directory data stays empty until D1 is configured or seeded.

For full Worker + D1 testing locally:

```bash
pnpm build
pnpm exec wrangler dev --local --port 8787
pnpm run db:migrate:local
pnpm run db:seed
```

> Requires Node 20+, [pnpm 10](https://pnpm.io/installation), and a Cloudflare account for the Worker preview.

## Configuration

Copy `.env.example` to `.env` and fill what you need. None of the GitHub or OpenRouter values are required to run `pnpm dev`.

| Variable | Required for | Description |
| --- | --- | --- |
| `VITE_APP_URL` | always | Public origin. Defaults to `http://localhost:3000`. |
| `VITE_ENABLE_GITHUB_AUTH` | UI sign-in | Set to `true` to enable the GitHub login button. |
| `VITE_ENABLE_DEVTOOLS` | local debugging | Set to `true` to enable TanStack, React Query, and React Scan devtools in development. |
| `ALLOW_UNSIGNED_GITHUB_WEBHOOKS` | local webhook testing | Keep `false` outside local development. Localhost can still run unsigned when no webhook secret is configured. |
| `CLOUDFLARE_ACCOUNT_ID` | deploy / D1 | Required by Wrangler when your Cloudflare login has access to more than one account. |
| `CLOUDFLARE_D1_DATABASE_NAME` | D1 | Defaults to `oss-protector`. |
| `CLOUDFLARE_D1_DATABASE_ID` | self-hosted D1 | Optional override for the committed hosted D1 UUID. |
| `CLOUDFLARE_D1_TOKEN` | Drizzle Kit | API token for `drizzle-kit push` against remote D1, if you use that workflow. |
| `VITE_GITHUB_STARS` | build | Optional override for the generated GitHub star count. |
| `VITE_GITHUB_REPO_SLUG` | build | Optional `owner/repo` override for the generated GitHub star count. |
| `BETTER_AUTH_SECRET` | sign-in | Required to enable Better Auth sessions. Must be ≥ 32 bytes — generate with `openssl rand -base64 32`. Also used to derive the BYOK encryption key. |
| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | sign-in | GitHub OAuth credentials for Better Auth. |
| `VITE_ENABLE_EMAIL_OTP` | UI sign-in | Set to `true` to show the email OTP option alongside the GitHub button. |
| `RESEND_API_KEY` | email OTP delivery | Resend API key. If unset, OTP codes are logged to the server console in local dev; in non-localhost the send is rejected with a clear error. |
| `EMAIL_FROM` | email OTP delivery | From address for OTP emails. Production must use a Resend-verified domain. Defaults to `OSS Protector `. |
| `GITHUB_APP_ID` / `GITHUB_APP_PRIVATE_KEY` | webhooks | The GitHub App's identity, used for installation tokens. |
| `GITHUB_APP_SLUG` / `VITE_GITHUB_APP_SLUG` | server / UI | GitHub App slug. Set both for self-hosted installs so webhook code and browser install links point at the same app. |
| `GITHUB_MANIFEST_TOKEN` | GitHub App setup | Optional token used to exchange a GitHub App manifest code from `/install`. |
| `GITHUB_APP_CREATE_OWNER` | GitHub App setup | Optional owner slug for GitHub App manifest creation. |
| `GITHUB_WEBHOOK_SECRET` | webhooks | Verifies inbound webhook signatures. |
| `OPENROUTER_API_KEY` | AI scoring | If unset, the deterministic fallback runs. |
| `SMOKE_HEALTH_TOKEN` | deploy smoke | Bearer token required by the private post-deploy webhook health endpoint. |

## Public read endpoints

Other projects can query the directory through filterable JSON endpoints (see [`/api-docs`](https://oss-protector.raedbahri90.workers.dev/api-docs) for the full reference):

- `/api/accounts` — risky accounts with status / score / reason / search filters.
- `/api/protectors` — maintainers who submitted review signals.
- `/api/openrouter/free-models` — the model IDs the platform key cycles through (for transparency).

Both filterable endpoints are rate-limited per client IP (60 req/min) via the Cloudflare Rate Limiting binding configured in `wrangler.json`. Webhooks are not throttled.

Authenticated maintainer endpoints (require a Better Auth session cookie, not rate-limited):

- `GET / POST /api/user/preferences` — read and update your notification kinds + BYOK OpenRouter key.
- `POST /api/openrouter/test` — validate a BYOK key against OpenRouter without consuming model credit.
- `POST / DELETE /api/maintainer/repo-decision`, `GET /api/maintainer/repo-decisions` — per-repo allow/block decisions for a specific account.
- `GET / POST / DELETE /api/maintainer/repo-policy?repositoryId=…` — dashboard-saved repository policy that fills in for fields not set by the committed `.github/oss-protector.json`.

## Database

Schema lives in `src/db/schema.ts` and is the single source of truth. Migrations
are generated from it with Drizzle Kit and applied with `drizzle-kit migrate`.

```bash
pnpm run db:generate # generate a migration after editing schema.ts
pnpm run db:migrate:local # apply pending migrations to the local D1
pnpm run db:migrate # apply pending migrations to the remote (prod) D1
pnpm run db:seed # seed locally (db:seed:remote for prod)
```

Local maintenance (these never touch prod unless you add `--remote --yes`):

```bash
pnpm run db:reset # wipe all rows, keep the schema
pnpm run db:clean # drop every table (then db:migrate:local to rebuild)
```

**Deploys apply migrations first.** On a push to `master`, migrations run before
the Worker is built and deployed, so the schema is always ahead of the code:

- GitHub Actions: `.github/workflows/deploy.yml` runs `db:migrate` → build → deploy.
- Cloudflare Workers Builds (Git integration): set the build command to
`pnpm run cf:build`, which runs `db:migrate` before `build:prod`.

**One-time production baseline.** The prod database was created before this
Drizzle-tracked flow, so its tables already exist. Run the baseline **once** to
record the current migration as applied without re-running it — otherwise the
first deploy would try to re-create existing tables:

```bash
pnpm run db:baseline:remote # mark current migrations applied on prod, runs no SQL
```

After baselining, every future change is just: edit `schema.ts` →
`pnpm db:generate` → push. The deploy applies it automatically.

The seed imports [`Bounty-Hunters/clankers.json`](https://raw.githubusercontent.com/UnsafeLabs/Bounty-Hunters/main/clankers.json). The initial dataset and the original concept are credited to the [Clankers Leaderboard](https://clankers-leaderboard.pages.dev/) by [@heyandras](https://x.com/heyandras).

## GitHub App

OSS Protector is **one shared GitHub App**. Maintainers don't create their own — they install the shared app on the repos they own:

```text
https://github.com/apps/oss-protector/installations/new
```

If you're self-hosting your own instance, create a GitHub App with:

```text
Webhook URL: https:///api/github/webhook
Repository permissions: Contents read, Issues write, Pull requests write
Subscribed events: Issue comment, Pull request, Pull request review comment
Visibility: Public
```

Then store the secrets in Cloudflare:

```bash
wrangler secret put GITHUB_APP_ID
wrangler secret put GITHUB_APP_PRIVATE_KEY
wrangler secret put GITHUB_WEBHOOK_SECRET
wrangler secret put BETTER_AUTH_SECRET
wrangler secret put GITHUB_CLIENT_ID
wrangler secret put GITHUB_CLIENT_SECRET
wrangler secret put OPENROUTER_API_KEY
wrangler secret put SMOKE_HEALTH_TOKEN
```

Better Auth handles GitHub user sign-in at `/api/auth/*` once `BETTER_AUTH_SECRET`, `GITHUB_CLIENT_ID`, and `GITHUB_CLIENT_SECRET` are set. The webhook + installation-token flow still uses `@octokit/auth-app` — Better Auth does not replace GitHub App installation authentication.

The internal OpenRouter model chain only uses model IDs that end in `:free`, with a paid fallback when free-tier models hallucinate.

## Maintainer dashboard

Once signed in at `/login`, every maintainer gets `/dashboard` with these tabs:

- **Review queue** — pending and needs-review reports from your repos.
- **Appeals** — flagged accounts who submitted an appeal via `/appeal`. Uphold / reject from here.
- **Audit log** — unified timeline of reports + maintainer corrections + repo overrides, filter by `all` / `decisions` / `overrides` / `reports`.
- **Coverage** — every repo where OSS Protector is installed and active.
- **Allowlist** — accounts you explicitly trust; their PRs bypass the analyzer.
- **Repo overrides** — per-repo allow/block for a specific account, independent of the shared score. Allow short-circuits AI review; block synthesizes a high-confidence flag without consuming AI credit.
- **Repo policy** — form-based editor for the same fields as `.github/oss-protector.json`. The committed file always wins per-field; the DB-saved policy fills in where the file is silent.

The `/settings` page lets you toggle which **notification kinds** you receive (`report`, `dispute`, `flag`, `correction`, `ok`, `info` — muted kinds aren't even created) and **bring your own OpenRouter key** for AI scoring. The key is encrypted at rest with AES-256-GCM (HKDF off `BETTER_AUTH_SECRET`); the earliest-linked maintainer's key wins on multi-maintainer installs. Without a BYOK key, the platform-provided key runs the free-model chain only.

## Repository policy

Each repository can tune OSS Protector with an optional `.github/oss-protector.json` file, or by editing the **Repo policy** tab in the dashboard. The committed file always takes precedence per-field; the dashboard value fills in for any field the file doesn't set. See [Repository policy](./docs/repository-policy.md) for the full lifecycle, examples, and field reference.

```json
{
"enabled": true,
"analyzePrivateRepositories": false,
"minimumLikelyAbuseConfidence": 80,
"trustedAuthors": ["dependabot[bot]", "renovate[bot]"],
"ignoredPaths": ["docs/", "examples/"]
}
```

- `enabled: false` tracks PR metadata but skips automatic abuse review for that repo.
- `analyzePrivateRepositories: true` explicitly opts private repositories into third-party AI analysis. Private repos default to metadata tracking without OpenRouter review.
- `minimumLikelyAbuseConfidence` is clamped between `65` and `95`; lower-confidence likely-abuse findings become review-needed.
- `trustedAuthors` skips automatic review for known local automation accounts.
- `ignoredPaths` skips automatic review when every changed file starts with one of the configured prefixes.

## Maintainer commands

Anyone can mention the bot to file a report:

```text
@oss-protector review this user
@oss-protector flag this user reason: fake bounty
@oss-protector recommend block reason: malicious code
```

Repo owners, organization members, and collaborators (GitHub `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR`) can correct the system from any PR comment:

```text
@oss-protector dismiss # mark all open reports on this PR's author as dismissed
@oss-protector confirm # validate the most recent open report
@oss-protector allow # allowlist the PR author (status = allow, score = 0)
@oss-protector reset # clear a prior allowlist; score recomputes on the next webhook
```

The bot posts a confirmation comment for each correction. Non-maintainer comments using those verbs are ignored. Cross-target syntax (`@oss-protector allow @other-user`) is not supported — corrections always act on the PR author. The bot flags any cross-target attempt in its ack comment.

## Verification

```bash
pnpm check # ultracite (oxlint + oxfmt)
pnpm run typecheck # tsc --noEmit
pnpm test # vitest
pnpm build # vite build
```

CI runs the same chain on pushes to `master` and PRs targeting `master`.

## Deploy

The hosted Worker is bound to the `oss-protector` D1 database in `wrangler.json`. Self-hosted deploys must set `CLOUDFLARE_D1_DATABASE_ID`, update the Worker name and public URL in `wrangler.json`, and store their own GitHub App/OpenRouter secrets before deploying. The deploy script refuses to publish the hosted configuration unless `OSS_PROTECTOR_DEPLOY_TARGET=hosted` is set.

```bash
pnpm run deploy
```

Deploys are wired through Cloudflare's Git integration on the hosted instance — every push to `master` triggers a Cloudflare-managed build and deploy. Maintainers of the hosted instance can run `pnpm run deploy:hosted` locally only for out-of-cycle hotfixes.

## Contributing

PRs and bug reports are very welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and workflow, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community expectations.

Found a security issue? Please follow [SECURITY.md](SECURITY.md) and do **not** open a public issue.

## License

[MIT](LICENSE) © OSS Protector contributors.