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
- Host: GitHub
- URL: https://github.com/lord007tn/oss-protector
- Owner: lord007tn
- License: mit
- Created: 2026-05-14T12:06:02.000Z (about 2 months ago)
- Default Branch: master
- Last Pushed: 2026-06-07T14:14:07.000Z (about 1 month ago)
- Last Synced: 2026-06-07T16:10:25.343Z (about 1 month ago)
- Topics: abuse-detection, cloudflare-workers, github-app, moderation, open-source, tanstack-start
- Language: TypeScript
- Homepage: https://oss-protector.raedbahri90.workers.dev
- Size: 3.56 MB
- Stars: 3
- Watchers: 0
- Forks: 0
- Open Issues: 5
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
- Security: SECURITY.md
- Agents: AGENTS.md
Awesome Lists containing this project
README

# 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.
[](https://github.com/lord007tn/oss-protector/actions/workflows/ci.yml)
[](LICENSE)
[](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.