Docs
How yof works
Getting started
- Sign up. Start free. It costs nothing for your own apps. One account works in yof, sōtem and Mondaily.
- Add your site. Paste its address on the Overview. yof takes https addresses on a public name only (no IP addresses, no localhost, no private names) and keeps the origin, not a single path. Up to 10 apps per account.
- Prove it is yours. yof gives you a token. Put it in any one of three places: a
<meta name="yof-verify" content="TOKEN">tag on your home page, a file at/.well-known/yof-verify.txtthat holds the token, or a DNS TXT recordyof-verify=TOKEN. yof looks for the proof again at the start of every check, so removing it takes the app back to the short report. - Run a check. Press Check now. You see each check as it runs, then a report: findings by urgency (Fix now, Fix this week, Nice to have), a health score and, for each finding, a fix prompt to paste into the AI that built your app.
Without proof, yof reads only what any visitor's browser gets and shows a headline (counts and score), never details. Keys and secrets stay locked until you prove the site is yours, so nobody can use yof to find a weak spot on someone else's site.
The box on the yof home page runs a free check with no account: the start page, its scripts, the headers and public DNS, with the same headline-only rule.
A proven app can also get a daily check and one short email a day: all good, or the single most urgent open finding. Every email has a link to turn it off. Each person can start 6 checks an hour and 30 a day.
What each check reads
Every check reads through one polite fetcher: GET and HEAD only, no cookies, no body, a time limit and a size limit per answer, a request budget per run, at most two requests at a time per host, default ports only. It identifies itself as yof-check (+https://yof.io), so you can see who called.
| Check | What it reads | Runs for |
|---|---|---|
bundle | Keys and secrets in your pages and the JavaScript they load, as any visitor's browser gets them. Only scripts from your own site are read. | Proven apps |
database | Supabase: asks for a count of common sensitive tables the way any visitor could. No row is ever fetched. Firebase Realtime Database: whether the root answers without a login (keys only). | Proven apps |
files | Files left open: .env, the .git folder, backups, folder listings, debug pages and source maps. A file counts only when its content proves what it is. | Proven apps |
admin | A short list of common admin addresses: flagged only when the server sends admin content to a stranger without a sign-in form. | Proven apps |
api | Your own API addresses that your pages name: personal data served to anyone, keys in addresses, error details, CORS, open API docs. GET only. | Proven apps |
ai | Your own chatbot, about 12 mild test messages read with fixed rules: leaked instructions, hidden commands, keys, other people's data. | Proven apps, after you set it up and agree |
headers | Security headers on your start page and whether plain http redirects to https. | Every app |
email | SPF, DMARC and DKIM in public DNS, so you know if others could send mail as your domain. | Every app |
seo | What your start page tells search engines and link previews, robots.txt and the sitemap. | Every app |
frontend | Phone viewport, insecure files on an https page, very large images, fonts and low-contrast colour pairs in your CSS. | Every app |
content | Placeholder text, unedited AI phrases, the same paragraph on several pages, links to missing pages. | Every app |
website | A real browser opens your site on a laptop and a phone screen: console errors, failed requests, broken links, the 404 page, layout and speed. It never fills in or sends a form. | Proven apps (runs in our cloud browser in Frankfurt) |
The health score counts the safety checks only (bundle, database, files, admin, api, ai, headers, email). Search, front end, content and website findings show in the report but never pull the score down. Keys that are public by design (a Supabase anon key, a Firebase web key, a Stripe publishable key) are shown as info and never counted.
If a site's bot protection blocks yof, the report says so. The owner of a proven app can let yof through with a pass header that only they and yof know.
What yof never does
- It never logs in, never submits a form and never guesses passwords.
- It never attacks: no fuzzing, no guessed record ids, no port scans, no POST, PUT, PATCH or DELETE to your site. The one exception is the assistant test you turn on yourself, which sends short test messages to your own chatbot.
- It never reads rows from your database. The database check asks for a count and reads only the total.
- It never keeps page contents, scripts or answers. A finding keeps the rule, the place and a masked piece of evidence.
- It never sneaks past bot protection. Only the owner can let it in.
- A check describes what yof looked at on that date. It is not a promise that nothing can go wrong, so the badge says "Checked by yof" and nothing more.
Guard on GitHub
Guard is a GitHub app, yof by sotem. Install it on the repositories you choose. On every pull request it reads the changed files on our server and runs three sets of rules: code rules (who may read or change data, money and webhooks, swallowed errors and secrets named for the browser), key patterns, and the package check (made up, brand new, lookalike or with a known advisory).
- It leaves one comment, created once and updated on each push.
- It adds one check named
yof Guard. The check fails only when something must be fixed now. Findings for this week or nice to have give a neutral result. - It blocks a merge only if your repository makes that check required in branch protection. That is your choice; Guard never decides it.
- It never pushes, approves, merges or edits a file. All it writes is that comment and that check.
Settings: .yof.yml
Optional. Put it in the root of your repository. Guard and the CLI read the same file and the same two settings: profile and an ignore list (at most 50 entries). Anything else in the file is ignored.
profile: balanced # relaxed | balanced | strict
ignore:
- fixtures/
- docs/examples.md
- "**/*.snap"| Profile | The CLI shows | The CLI fails on |
|---|---|---|
relaxed | Fix now | Fix now |
balanced (default) | Fix now, fix this week | Fix now |
strict | Everything, including public keys that are fine | Fix now, fix this week |
In Guard, relaxed keeps only sure findings to fix now, balanced every sure finding, and strict adds the less certain ones. The yof Guard check fails only on something to fix now, in every profile.
Guard reads .yof.yml from the base branch of the pull request, not from the pull request itself. A pull request can't add an ignore rule to silence Guard on its own change. When a pull request edits .yof.yml, Guard says so, and the new rules apply after it is merged.
The CLI
The npm package @yof/cli is open source under the Apache 2.0 license, at github.com/yof-dev/yof. It runs on your machine, needs no account and Node 20 or newer. It finds secret keys in your code and dependencies that don't exist or imitate popular ones.
npx @yof/cli checkThat checks the current folder. To check every commit, add the pre-commit hook. It runs yof check --staged, which reads exactly what is about to be committed and stops the commit when something must be fixed now.
npx @yof/cli hook install| Command or flag | What it does |
|---|---|
yof check [path] | Check a folder (default: the current one) |
yof check --staged | Check only the files staged for the next commit |
yof hook install | Run yof check --staged before every commit |
yof hook uninstall | Remove that hook |
--offline | Skip the package-registry lookups (keys only, no network calls at all) |
--json | Machine-readable output, masked values only |
--profile <name> | relaxed, balanced or strict; overrides .yof.yml |
Exit codes: 0 nothing to fix, 1 something to fix (per the profile), 2 usage or setup error. The CLI never prints a key in full. Its only network calls are package lookups (names and pinned versions) to registry.npmjs.org, api.npmjs.org, pypi.org and api.osv.dev. There is no telemetry.
Run it on every push
The same CLI as a GitHub Actions step, on every push and pull request. The job fails when yof finds something to fix now.
# .github/workflows/yof.yml
name: yof
on: [push, pull_request]
permissions:
contents: read
jobs:
yof:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: yof-dev/yof@v0.1.0
with:
path: .
offline: falsepath and offline are optional. offline: true checks keys only, with no registry lookups.
Patch (early access)
Patch is yof's agent that watches your proven apps. It is free while in early access; nothing is charged. Toward your site it reads only, like the rest of yof. After each check it watches:
- Scripts: a new third-party script host, or a changed script at the same address, on your start page and up to two sensitive pages (login, checkout).
- Subdomains: new names under your domain in public certificate logs, and subdomains that point at a service someone else could claim.
- DNS and nameservers: the records that matter (A, AAAA, CNAME, MX, SPF, DMARC) and your domain's nameservers. A nameserver change moves control of the whole domain, so it counts as Fix now.
- Certificates that expire within 14 days.
Every Patch event shows what it noticed, the before and after, its verdict and what it did. Patch never changes your DNS; it only watches.
Checks after a deploy
Make a deploy hook for an app and call it from your deploy, or link a GitHub repository: a successful production deploy that GitHub reports (deployment_status) runs the same check. At most one deploy check per app every 5 minutes, within your usual check limits.
Fix pull requests
For a repository you linked (the yof App installed, and you have write access), Patch can open one pull request with a fix from a small set of safe templates: missing security headers (vercel.json or a Netlify _headers file), a .well-known/security.txt, and the missing .env lines in .gitignore. If it can't tell your host for certain, it writes nothing. The pull request goes on a new branch. Patch never merges, approves, deletes or force-pushes. You review it and merge it.
The terminal in the app
The Terminal in the yof app runs yof's actions on your own apps by typing. It runs on our side, for your account only, and can't run anything on your computer (that is what the CLI is for). A host that isn't one of your apps is refused before any check starts.
| Command | What it does |
|---|---|
help | Show the commands |
apps | Your apps: host, proven or not, score, last check |
check <host> | Check one of your apps now and watch it run |
findings [host] | Open findings, most urgent first |
fix <id> [--pr] | The fix for one finding, ready to paste into your AI. --pr opens a pull request when Patch has a safe fix |
ignore <id> --reason "<text>" | Accept the risk of one finding, with a reason |
accepted [host] | Your accepted risks: why, who and when |
reopen <id> | Count an accepted finding again |
diff <host> | What changed on one app since the check before |
export | Save yof-rules.md with your open findings and their fixes, for Cursor or Claude Code |
ask <question> | Ask yof about your apps |
guard | What Guard does and where to turn it on |
install | How to run the open-source CLI on your computer |
whoami | The account you're signed in with |
clear | Clear the screen |
Add --json to apps, check, findings, fix, ignore, accepted, reopen, diff or whoami to print the result as JSON. Tab completes commands, hosts and finding IDs.
Accepted risks
When a finding is a risk you have decided to live with, accept it with a short reason (up to 300 characters), in the app or with ignore in the Terminal. yof still checks it, but it stops counting in your score, your open findings and your daily email. A later check that finds it again in the same place keeps it accepted; the same problem in a new place is a new finding. Reopen it at any time to count it again. Only the owner of the app can accept or reopen.
What yof keeps
- Checks: your apps, the results of each check and the findings (the rule, where it was seen, a masked piece of evidence). Not page contents, scripts, API answers or database rows.
- Guard: where the App is installed and one row per reviewed commit: the repository, the pull request number, the commit, the status, the number of findings and the comment id. Never your code. Guard reads the changed files on our hosting in Frankfurt (EU) and keeps nothing of them.
- Patch: snapshots of hosts, addresses, script hashes, DNS values and certificate dates. Never page content.
- The cloud browser for the website check runs on AWS in Frankfurt (eu-central-1). Its error logs, which can include your site address, are kept for 14 days.
- The CLI keeps nothing and sends no code anywhere.
Found a problem in yof itself? See how to report it.