Skip to content

lord007tn/oss-protector

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

90 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 License: MIT PRs Welcome

Hosted instance · Install the App · Contributing


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

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

Open http://localhost:3000. 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:

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

Requires Node 20+, pnpm 10, 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 <onboarding@resend.dev>.
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 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.

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):

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:

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

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

The seed imports Bounty-Hunters/clankers.json. The initial dataset and the original concept are credited to the Clankers Leaderboard by @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:

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

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

Webhook URL:           https://<your-worker-host>/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:

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 for the full lifecycle, examples, and field reference.

{
  "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:

@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:

@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

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.

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 for setup and workflow, and CODE_OF_CONDUCT.md for community expectations.

Found a security issue? Please follow SECURITY.md and do not open a public issue.

License

MIT © OSS Protector contributors.

About

Shared OSS abuse intelligence for suspicious GitHub PR activity

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages