{"id":50824527,"url":"https://github.com/lord007tn/oss-protector","last_synced_at":"2026-06-13T17:03:58.912Z","repository":{"id":361434643,"uuid":"1238739577","full_name":"lord007tn/oss-protector","owner":"lord007tn","description":"Shared OSS abuse intelligence for suspicious GitHub PR activity","archived":false,"fork":false,"pushed_at":"2026-06-07T14:14:07.000Z","size":3732,"stargazers_count":3,"open_issues_count":5,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-06-07T16:10:25.343Z","etag":null,"topics":["abuse-detection","cloudflare-workers","github-app","moderation","open-source","tanstack-start"],"latest_commit_sha":null,"homepage":"https://oss-protector.raedbahri90.workers.dev","language":"TypeScript","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/lord007tn.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-14T12:06:02.000Z","updated_at":"2026-06-01T19:23:14.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/lord007tn/oss-protector","commit_stats":null,"previous_names":["lord007tn/oss-protector"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/lord007tn/oss-protector","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lord007tn%2Foss-protector","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lord007tn%2Foss-protector/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lord007tn%2Foss-protector/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lord007tn%2Foss-protector/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/lord007tn","download_url":"https://codeload.github.com/lord007tn/oss-protector/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/lord007tn%2Foss-protector/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34292326,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-13T02:00:06.617Z","response_time":62,"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":["abuse-detection","cloudflare-workers","github-app","moderation","open-source","tanstack-start"],"created_at":"2026-06-13T17:03:57.642Z","updated_at":"2026-06-13T17:03:58.904Z","avatar_url":"https://github.com/lord007tn.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n\u003cimg src=\"public/oss-protector-mark.svg\" alt=\"OSS Protector\" width=\"96\" height=\"96\" /\u003e\n\n# OSS Protector\n\n**Shared abuse intelligence for suspicious GitHub pull request activity.**\n\nA 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.\n\n[![CI](https://github.com/lord007tn/oss-protector/actions/workflows/ci.yml/badge.svg)](https://github.com/lord007tn/oss-protector/actions/workflows/ci.yml)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\n[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](CONTRIBUTING.md)\n\n[Hosted instance](https://oss-protector.raedbahri90.workers.dev) · [Install the App](https://github.com/apps/oss-protector/installations/new) · [Contributing](CONTRIBUTING.md)\n\n\u003c/div\u003e\n\n---\n\n## What it does\n\nWhen you install the OSS Protector GitHub App on a repository, it:\n\n1. **Watches PRs.** Every `pull_request`, `issue_comment`, and `pull_request_review_comment` event is sent to a shared webhook.\n2. **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.\n3. **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.\n4. **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.\n5. **Publishes.** Confirmed risky accounts show up on the public directory so other maintainers can review them before merging.\n\nIt's one GitHub App, one database, one feed — maintainers don't each have to run their own.\n\n## Key features\n\n- **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.\n- **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.\n- **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.\n- **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.\n- **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.\n\n## Stack\n\n- **Frontend** — TanStack Start (file-based routing, SSR), React 19, shadcn/ui on Base UI primitives, Tailwind 4.\n- **API + Worker** — TanStack Start server functions on Cloudflare Workers.\n- **Data** — Drizzle ORM `1.0.0-beta.24` on Cloudflare D1.\n- **Auth** — Better Auth (GitHub user sign-in) + `@octokit/auth-app` (App installation tokens).\n- **AI** — OpenRouter chat completions, free-tier model chain with a paid fallback.\n- **Lint/format** — Ultracite (oxlint + oxfmt), Biome.\n- **Tests** — Vitest.\n\n## Quick start\n\n```bash\ngit clone https://github.com/lord007tn/oss-protector.git\ncd oss-protector\npnpm install\ncp .env.example .env\npnpm dev\n```\n\nOpen \u003chttp://localhost:3000\u003e. 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.\n\nFor full Worker + D1 testing locally:\n\n```bash\npnpm build\npnpm exec wrangler dev --local --port 8787\npnpm run db:migrate:local\npnpm run db:seed\n```\n\n\u003e Requires Node 20+, [pnpm 10](https://pnpm.io/installation), and a Cloudflare account for the Worker preview.\n\n## Configuration\n\nCopy `.env.example` to `.env` and fill what you need. None of the GitHub or OpenRouter values are required to run `pnpm dev`.\n\n| Variable | Required for | Description |\n| --- | --- | --- |\n| `VITE_APP_URL` | always | Public origin. Defaults to `http://localhost:3000`. |\n| `VITE_ENABLE_GITHUB_AUTH` | UI sign-in | Set to `true` to enable the GitHub login button. |\n| `VITE_ENABLE_DEVTOOLS` | local debugging | Set to `true` to enable TanStack, React Query, and React Scan devtools in development. |\n| `ALLOW_UNSIGNED_GITHUB_WEBHOOKS` | local webhook testing | Keep `false` outside local development. Localhost can still run unsigned when no webhook secret is configured. |\n| `CLOUDFLARE_ACCOUNT_ID` | deploy / D1 | Required by Wrangler when your Cloudflare login has access to more than one account. |\n| `CLOUDFLARE_D1_DATABASE_NAME` | D1 | Defaults to `oss-protector`. |\n| `CLOUDFLARE_D1_DATABASE_ID` | self-hosted D1 | Optional override for the committed hosted D1 UUID. |\n| `CLOUDFLARE_D1_TOKEN` | Drizzle Kit | API token for `drizzle-kit push` against remote D1, if you use that workflow. |\n| `VITE_GITHUB_STARS` | build | Optional override for the generated GitHub star count. |\n| `VITE_GITHUB_REPO_SLUG` | build | Optional `owner/repo` override for the generated GitHub star count. |\n| `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. |\n| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` | sign-in | GitHub OAuth credentials for Better Auth. |\n| `VITE_ENABLE_EMAIL_OTP` | UI sign-in | Set to `true` to show the email OTP option alongside the GitHub button. |\n| `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. |\n| `EMAIL_FROM` | email OTP delivery | From address for OTP emails. Production must use a Resend-verified domain. Defaults to `OSS Protector \u003conboarding@resend.dev\u003e`. |\n| `GITHUB_APP_ID` / `GITHUB_APP_PRIVATE_KEY` | webhooks | The GitHub App's identity, used for installation tokens. |\n| `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. |\n| `GITHUB_MANIFEST_TOKEN` | GitHub App setup | Optional token used to exchange a GitHub App manifest code from `/install`. |\n| `GITHUB_APP_CREATE_OWNER` | GitHub App setup | Optional owner slug for GitHub App manifest creation. |\n| `GITHUB_WEBHOOK_SECRET` | webhooks | Verifies inbound webhook signatures. |\n| `OPENROUTER_API_KEY` | AI scoring | If unset, the deterministic fallback runs. |\n| `SMOKE_HEALTH_TOKEN` | deploy smoke | Bearer token required by the private post-deploy webhook health endpoint. |\n\n## Public read endpoints\n\nOther projects can query the directory through filterable JSON endpoints (see [`/api-docs`](https://oss-protector.raedbahri90.workers.dev/api-docs) for the full reference):\n\n- `/api/accounts` — risky accounts with status / score / reason / search filters.\n- `/api/protectors` — maintainers who submitted review signals.\n- `/api/openrouter/free-models` — the model IDs the platform key cycles through (for transparency).\n\nBoth 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.\n\nAuthenticated maintainer endpoints (require a Better Auth session cookie, not rate-limited):\n\n- `GET / POST /api/user/preferences` — read and update your notification kinds + BYOK OpenRouter key.\n- `POST /api/openrouter/test` — validate a BYOK key against OpenRouter without consuming model credit.\n- `POST / DELETE /api/maintainer/repo-decision`, `GET /api/maintainer/repo-decisions` — per-repo allow/block decisions for a specific account.\n- `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`.\n\n## Database\n\nSchema lives in `src/db/schema.ts` and is the single source of truth. Migrations\nare generated from it with Drizzle Kit and applied with `drizzle-kit migrate`.\n\n```bash\npnpm run db:generate          # generate a migration after editing schema.ts\npnpm run db:migrate:local     # apply pending migrations to the local D1\npnpm run db:migrate           # apply pending migrations to the remote (prod) D1\npnpm run db:seed              # seed locally   (db:seed:remote for prod)\n```\n\nLocal maintenance (these never touch prod unless you add `--remote --yes`):\n\n```bash\npnpm run db:reset             # wipe all rows, keep the schema\npnpm run db:clean             # drop every table (then db:migrate:local to rebuild)\n```\n\n**Deploys apply migrations first.** On a push to `master`, migrations run before\nthe Worker is built and deployed, so the schema is always ahead of the code:\n\n- GitHub Actions: `.github/workflows/deploy.yml` runs `db:migrate` → build → deploy.\n- Cloudflare Workers Builds (Git integration): set the build command to\n  `pnpm run cf:build`, which runs `db:migrate` before `build:prod`.\n\n**One-time production baseline.** The prod database was created before this\nDrizzle-tracked flow, so its tables already exist. Run the baseline **once** to\nrecord the current migration as applied without re-running it — otherwise the\nfirst deploy would try to re-create existing tables:\n\n```bash\npnpm run db:baseline:remote   # mark current migrations applied on prod, runs no SQL\n```\n\nAfter baselining, every future change is just: edit `schema.ts` →\n`pnpm db:generate` → push. The deploy applies it automatically.\n\nThe 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).\n\n## GitHub App\n\nOSS Protector is **one shared GitHub App**. Maintainers don't create their own — they install the shared app on the repos they own:\n\n```text\nhttps://github.com/apps/oss-protector/installations/new\n```\n\nIf you're self-hosting your own instance, create a GitHub App with:\n\n```text\nWebhook URL:           https://\u003cyour-worker-host\u003e/api/github/webhook\nRepository permissions: Contents read, Issues write, Pull requests write\nSubscribed events:     Issue comment, Pull request, Pull request review comment\nVisibility:            Public\n```\n\nThen store the secrets in Cloudflare:\n\n```bash\nwrangler secret put GITHUB_APP_ID\nwrangler secret put GITHUB_APP_PRIVATE_KEY\nwrangler secret put GITHUB_WEBHOOK_SECRET\nwrangler secret put BETTER_AUTH_SECRET\nwrangler secret put GITHUB_CLIENT_ID\nwrangler secret put GITHUB_CLIENT_SECRET\nwrangler secret put OPENROUTER_API_KEY\nwrangler secret put SMOKE_HEALTH_TOKEN\n```\n\nBetter 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.\n\nThe internal OpenRouter model chain only uses model IDs that end in `:free`, with a paid fallback when free-tier models hallucinate.\n\n## Maintainer dashboard\n\nOnce signed in at `/login`, every maintainer gets `/dashboard` with these tabs:\n\n- **Review queue** — pending and needs-review reports from your repos.\n- **Appeals** — flagged accounts who submitted an appeal via `/appeal`. Uphold / reject from here.\n- **Audit log** — unified timeline of reports + maintainer corrections + repo overrides, filter by `all` / `decisions` / `overrides` / `reports`.\n- **Coverage** — every repo where OSS Protector is installed and active.\n- **Allowlist** — accounts you explicitly trust; their PRs bypass the analyzer.\n- **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.\n- **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.\n\nThe `/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.\n\n## Repository policy\n\nEach 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.\n\n```json\n{\n  \"enabled\": true,\n  \"analyzePrivateRepositories\": false,\n  \"minimumLikelyAbuseConfidence\": 80,\n  \"trustedAuthors\": [\"dependabot[bot]\", \"renovate[bot]\"],\n  \"ignoredPaths\": [\"docs/\", \"examples/\"]\n}\n```\n\n- `enabled: false` tracks PR metadata but skips automatic abuse review for that repo.\n- `analyzePrivateRepositories: true` explicitly opts private repositories into third-party AI analysis. Private repos default to metadata tracking without OpenRouter review.\n- `minimumLikelyAbuseConfidence` is clamped between `65` and `95`; lower-confidence likely-abuse findings become review-needed.\n- `trustedAuthors` skips automatic review for known local automation accounts.\n- `ignoredPaths` skips automatic review when every changed file starts with one of the configured prefixes.\n\n## Maintainer commands\n\nAnyone can mention the bot to file a report:\n\n```text\n@oss-protector review this user\n@oss-protector flag this user reason: fake bounty\n@oss-protector recommend block reason: malicious code\n```\n\nRepo owners, organization members, and collaborators (GitHub `author_association` of `OWNER`, `MEMBER`, or `COLLABORATOR`) can correct the system from any PR comment:\n\n```text\n@oss-protector dismiss     # mark all open reports on this PR's author as dismissed\n@oss-protector confirm     # validate the most recent open report\n@oss-protector allow       # allowlist the PR author (status = allow, score = 0)\n@oss-protector reset       # clear a prior allowlist; score recomputes on the next webhook\n```\n\nThe 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.\n\n## Verification\n\n```bash\npnpm check          # ultracite (oxlint + oxfmt)\npnpm run typecheck  # tsc --noEmit\npnpm test           # vitest\npnpm build          # vite build\n```\n\nCI runs the same chain on pushes to `master` and PRs targeting `master`.\n\n## Deploy\n\nThe 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.\n\n```bash\npnpm run deploy\n```\n\nDeploys 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.\n\n## Contributing\n\nPRs 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.\n\nFound a security issue? Please follow [SECURITY.md](SECURITY.md) and do **not** open a public issue.\n\n## License\n\n[MIT](LICENSE) © OSS Protector contributors.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flord007tn%2Foss-protector","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flord007tn%2Foss-protector","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flord007tn%2Foss-protector/lists"}