docs

How Shepherd works

How it works

You paste a public GitHub repo. Shepherd reads your code, looks for the usual ways apps get hacked or break, and hands you a score with a list of what to fix. It takes about ten seconds. Here is what happens in that time:

  1. 1
    grab · Reads your repo's file list from GitHub's public API. No login, no install.
  2. 2
    read · Opens up to 120 of your code files and reads them in memory.
  3. 3
    check · Runs 45 checks per file, picking the right ones for each language.
  4. 4
    look up · Asks the OSV vulnerability database if any of your packages have known holes.
  5. 5
    score · Adds up what it found and turns it into one number out of 100.

Languages it reads

Some checks, like finding leaked keys, run on every file no matter the language. Others are picked for the language of each file. For example, Shepherd looks for reentrancy in Solidity, unsafe blocks in Rust, and open CORS in your API code.

JavaScriptTypeScriptPythonRustSolidityGoRubyPHPJava

The Survival Score

The score is one number from 0 to 100. Everybody starts at 100. Each thing Shepherd finds takes points away, and worse problems take more:

how the math works
start:           100 points
each critical:   minus 16
each medium:     minus 6
each low:        minus 2

score = whatever is left (never below 0)

Quick example: one leaked key (critical) and two small things (low) gives 100 - 16 - 2 - 2 = 80. That lands in “Mostly Alive”.

We also show how many checks passed. A good repo lights up with passed checks, which is the honest way to show the score is earned.

Every check, listed

All 45 checks Shepherd runs. This list is built straight from the scanner, so it is always what actually runs. Search it or filter by group.

lowCode quality
.unwrap() that can crash

Calling .unwrap() or .expect() crashes the whole program if the value is missing or an error. Fine in scripts, risky in a running service.

runs on: Rusthow sure: worth a look
lowCode quality
panic!() in library code

panic!() stops the program. In a server or library, one bad request can take the whole thing down.

runs on: Rusthow sure: worth a look
mediumCode security
CORS open to everyone

CORS is set to '*', so any website can call your API from a user's browser.

runs on: JavaScript, TypeScripthow sure: pretty sure
mediumCode security
dangerouslySetInnerHTML in use

This injects raw HTML into the page. If the HTML comes from a user, they can run scripts in your app (XSS).

runs on: JavaScript, TypeScripthow sure: fairly sure
criticalCode security
eval() in the code

eval() runs whatever string you give it as code. If any of that string comes from a user, they can run their own code.

runs on: JavaScript, TypeScripthow sure: fairly sure
criticalCode security
eval() or exec() in Python

eval() and exec() run strings as code. With any user input, that is remote code execution.

runs on: Pythonhow sure: fairly sure
mediumCode security
Flask debug mode on

Running Flask with debug=True exposes an interactive debugger that can run code.

runs on: Pythonhow sure: fairly sure
mediumCode security
Go shell command from input

Running sh -c with a built string and user input can let people run their own commands.

runs on: Gohow sure: worth a look
criticalCode security
Go SQL built with Sprintf

Building a SQL string with Sprintf and user input lets attackers rewrite the query.

runs on: Gohow sure: fairly sure
mediumCode security
Java Runtime.exec with input

Runtime.exec with a string that includes user input can run extra commands.

runs on: Javahow sure: worth a look
criticalCode security
Java SQL built by string adding

Building SQL with + and user input lets attackers change the query.

runs on: Javahow sure: worth a look
criticalCode security
PHP eval()

eval() runs a string as PHP. With any user input it is full code execution.

runs on: PHPhow sure: fairly sure
criticalCode security
PHP SQL with variables inside

Putting request variables directly into a SQL string is SQL injection.

runs on: PHPhow sure: fairly sure
mediumCode security
pickle.loads on outside data

pickle can run code while loading. Never use it on data from users or the network.

runs on: Pythonhow sure: worth a look
criticalCode security
Ruby eval on a string

eval, instance_eval, and class_eval run strings as code. With user input that is remote code execution.

runs on: Rubyhow sure: fairly sure
mediumCode security
Ruby system command with input

Putting user input inside system() or backticks can run extra shell commands.

runs on: Rubyhow sure: worth a look
criticalCode security
Shell command built from input

exec() runs a shell command. If part of that command is user input, they can run their own commands on your server.

runs on: JavaScript, TypeScripthow sure: worth a look
mediumCode security
Shell command built with format!

Building a command string with format! and user input can let people run their own commands.

runs on: Rusthow sure: worth a look
criticalCode security
SQL built by gluing strings together

A SQL query is built with string joining and user input. Attackers can rewrite the query (SQL injection).

runs on: JavaScript, TypeScripthow sure: fairly sure
criticalCode security
subprocess with shell=True

Running a subprocess with shell=True and user input lets attackers run their own commands.

runs on: Pythonhow sure: fairly sure
mediumCode security
TLS verification turned off

Skipping TLS checks means a fake server can pretend to be the real one and read your traffic.

runs on: Gohow sure: pretty sure
mediumCode security
Unsafe yaml.load

yaml.load without SafeLoader can run code from a crafted file.

runs on: Pythonhow sure: fairly sure
criticalLogin & sessions
JWT "none" algorithm allowed

Your token setup allows the 'none' algorithm, which means a token with no signature is accepted. Anyone can forge a login.

runs on: JavaScript, TypeScripthow sure: fairly sure
mediumMemory safety (Rust)
mem::transmute used

transmute reinterprets bytes as another type with no checks. Easy to get wrong and cause memory bugs.

runs on: Rusthow sure: fairly sure
mediumMemory safety (Rust)
unsafe block in Rust

An unsafe block skips Rust's memory checks. Mistakes here can cause crashes or memory bugs that Rust normally prevents.

runs on: Rusthow sure: fairly sure
criticalSecrets
Anthropic key sitting in the code

An Anthropic key (sk-ant-...) is in the code. Anyone can run up your bill with it.

runs on: any languagehow sure: pretty sure
criticalSecrets
AWS access key in the code

An AWS access key ID is hard-coded. Paired with the secret, it opens your whole AWS account.

runs on: any languagehow sure: pretty sure
criticalSecrets
GitHub token in the code

A GitHub personal access token is hard-coded. Depending on scope it can read and write your repos.

runs on: any languagehow sure: pretty sure
mediumSecrets
Google or Firebase key in the code

A Google or Firebase API key is hard-coded. Without restrictions it can be used by anyone.

runs on: any languagehow sure: fairly sure
mediumSecrets
Hard-coded JWT signing secret

A JWT signing secret looks hard-coded. If it leaks, anyone can forge logged-in sessions.

runs on: any languagehow sure: worth a look
criticalSecrets
MongoDB connection string exposed

A MongoDB connection string with a username and password is in the code.

runs on: any languagehow sure: pretty sure
criticalSecrets
OpenAI key sitting in the code

An OpenAI key (sk-...) is hard-coded. Anyone who reads this file can spend your money.

runs on: any languagehow sure: pretty sure
criticalSecrets
Postgres connection string exposed

A Postgres connection string with credentials is hard-coded.

runs on: any languagehow sure: pretty sure
criticalSecrets
Private key pasted into the repo

A private key block is committed. This can be an SSH, TLS, or signing key.

runs on: any languagehow sure: pretty sure
mediumSecrets
Slack or Discord webhook exposed

A chat webhook URL is hard-coded. People can send messages to your channel with it.

runs on: any languagehow sure: fairly sure
criticalSecrets
Stripe live key out in the open

A live Stripe secret key is in the code. That is full access to charges, refunds, and customer data.

runs on: any languagehow sure: pretty sure
criticalSecrets
Supabase service_role key exposed

The Supabase service_role key skips all your security rules. Anyone with it can read or delete everything.

runs on: any languagehow sure: pretty sure
mediumSmart contracts (Solidity)
block values used for randomness

block.timestamp and blockhash can be influenced by whoever produces the block, so they are unsafe for lotteries or anything random.

runs on: Solidityhow sure: worth a look
criticalSmart contracts (Solidity)
delegatecall to a variable address

delegatecall runs another contract's code with your contract's storage. If the target can be set by someone else, they own your contract.

runs on: Solidityhow sure: fairly sure
mediumSmart contracts (Solidity)
Floating compiler version

A pragma like ^0.8.0 lets the contract compile with many versions. A different version can change behavior in subtle ways.

runs on: Solidityhow sure: pretty sure
criticalSmart contracts (Solidity)
Low-level call result ignored

A low-level .call() returns whether it worked, but the result is being ignored. Failures pass silently and can break your logic.

runs on: Solidityhow sure: fairly sure
criticalSmart contracts (Solidity)
Money sent before state is updated

Sending ETH with .call before updating balances lets an attacker call back in and drain funds (reentrancy).

runs on: Solidityhow sure: worth a look
criticalSmart contracts (Solidity)
selfdestruct in the contract

selfdestruct can delete the contract and send its balance away. If anyone can trigger it, your contract can be wiped.

runs on: Solidityhow sure: pretty sure
mediumSmart contracts (Solidity)
Sensitive function may be unprotected

A function named like mint, withdraw, or setOwner looks public with no obvious access check. Anyone might be able to call it.

runs on: Solidityhow sure: worth a look
criticalSmart contracts (Solidity)
tx.origin used for auth

Using tx.origin to check who is calling can be tricked. A malicious contract in the middle passes the check and steals funds.

runs on: Solidityhow sure: pretty sure

What it can't do

Being straight with you matters more than looking clever, so here is where Shepherd stops:

  • It reads, it does not run.

    Shepherd looks at your code as text. It does not run your app, so it can miss bugs that only show up while running.

  • Public repos only, for now.

    Private repos need a GitHub login, which is on the roadmap.

  • It uses patterns, so it is not perfect.

    A clean score does not mean you cannot be hacked. It means the common traps are not obvious in your code.

  • For real money, get a human too.

    If your app holds real money or sensitive data, also pay for a human security review. Shepherd is a great first pass, not the last word.

Privacy

Shepherd reads your code through GitHub's public API and checks it in memory. When the scan is done, that code is gone. We do not save your files.

If you tick the box to join the Wall of Fame, we keep only a random ID, your score, the tier, the top issue type, and how many issues there were. Your repo name is not shown.

The scan history on the scan page lives in your browser, not on our servers.

FAQ

Is this safe to run on my repo?

Yes. Shepherd only reads. It never writes to your repo, never opens a pull request, and never runs your code.

Why is it free?

Because we are building in public and watching apps blow up in production makes us sad. No catch.

My score is 23. Am I doomed?

No. Vibe has survived worse. Fix the critical items first, scan again, and watch the number climb.

Does a high score mean I am unhackable?

No. It means the common mistakes are not sitting in plain sight. Keep good habits and, for serious apps, get a human review too.

Which languages does it support?

Right now: JavaScript, TypeScript, Python, Rust, Solidity, Go, Ruby, PHP, Java. Secret detection works on any file.

Ready to see your score?

free, no account, paste a repo URL

Scan my app →