Sentinel is a security auditor for small businesses that don't have a security team. Paste your website address, click Scan, and get a plain-language report of what's wrong and exactly how to fix it.
Because it scans your live website over HTTP/TLS (black-box), it works on any site no matter what framework or language it's built in — WordPress, Shopify, Django, Rails, a custom Node app, plain HTML, anything with a URL.
Website checks run in the web app; deep project scans run from your terminal. The website (and dashboard) perform safe, passive website scans for any URL. The terminal (after you log in on the site) deep-scans projects you have access to —
sentinel . --codestatically analyses a whole codebase, its dependencies and its git history. You can only deep-scan code on your own machine, never attack an arbitrary website.
⚠️ Only scan websites you own or have permission to test.
| Area | Examples |
|---|---|
| Encryption | HTTPS enforced, valid & current TLS certificate, no weak protocols, weak cipher suites, TLS 1.0/1.1 still enabled, HSTS preload eligibility |
| Security headers | CSP, HSTS, clickjacking, MIME-sniffing, referrer/permissions policy |
| DNS & email | SPF, DMARC, DKIM, MTA-STS, CAA, DNSSEC, open DNS zone transfer (AXFR) |
| Attack surface | Subdomain discovery from public Certificate Transparency logs; risky dev/staging/admin hosts |
| Exposed files | .env, .git, database dumps, backups, config files left public |
| Cookies | Missing Secure / HttpOnly / SameSite flags |
| Information leaks | Server/version disclosure, debug error pages, secret HTML comments |
| Injection | Reflected-input / possible XSS, open redirects (safe, non-destructive) |
| Malware | Crypto-miner & obfuscated-script heuristics, optional Google Safe Browsing |
| Other | Directory listing, mixed content, dangerous CORS |
Every finding comes with a severity, a plain-language explanation, a concrete fix, and compliance cross-references (OWASP Top 10, PCI-DSS, CIS Controls) so reports speak the language auditors and customers expect.
The DNS/email and attack-surface checks are fully passive — they query public DNS and Certificate Transparency infrastructure, never attacking your site — so they run in the standard (web app + API) profile alongside the other safe checks. The zone-transfer probe is SSRF-guarded: nameservers that resolve to internal addresses are skipped.
The terminal's deep scan targets a project you have access to, not a live
website: sentinel . --code statically analyses an entire codebase plus its
dependencies and git history, offline and read-only. You can only run it against
code on your own machine, so it can never be pointed at someone else's site.
Active website attacking is intentionally not exposed. Earlier versions offered a
--deepmode that actively attacked a live URL (SQLi/XSS/etc.). That was removed from the CLI so the tool can only ever deep-scan projects you control — not arbitrary websites. The web app and REST API perform safe, passive website checks only.
See Scan your local codebase (--code) below
for everything the project scan covers (secrets, injection, vulnerable
dependencies, secrets-in-git-history, and more).
Accounts, authentication and scan history all live in the Laravel + MySQL app. The Python service is a stateless scanning engine — it just runs a scan and returns the report; it stores nothing.
- Auth: Laravel session auth against the
userstable. Passwords are bcrypt hashed (Laravel'shashedcast). - Per-user history: every scan is saved to the MySQL
scanstable and is only visible to the user who ran it (admins can see all). - The Laravel app shows a login screen, then a dashboard with a standard scan bar and a history sidebar, plus a "Use in terminal" page that walks users through installing the CLI, logging in, and deep-scanning their projects (whose results then sync back into this same history).
php artisan migrate:fresh --seed creates two accounts:
| Role | Password | |
|---|---|---|
| Admin (super-admin) | admin@sentinel.test |
admin12345 |
| Demo (user) | demo@sentinel.test |
demo12345 |
(Seeders: database/seeders/AdminUserSeeder.php and DemoUserSeeder.php.)
Every account has a role (user or admin); admins also have an is_super
flag.
- User → the standard dashboard: run scans, view their own history.
- Admin → an extra dashboard with full controls:
- Overview — platform stats (users, scans, project scans, average score, grade distribution).
- Users — list every account, promote/demote roles, delete users.
- All scans — view and delete any user's scan reports.
- Scanner — the normal scan tool (admins can scan too).
The seeded admin is the super-admin: only a super-admin can create/promote
other admins, and the super-admin can't be demoted or deleted (prevents lockout).
The sentinel.admin middleware guards /admin/* routes.
backend/sentinel/ # Python: STATELESS scanning engine
core/ # framework-agnostic scanning library
checks/ # one module per check (+ checks/active/ for deep mode)
crawler.py # same-origin crawler (deep mode)
scanner.py # orchestrator
api/main.py # FastAPI: POST /api/scan -> ScanResult (no auth/DB)
cli.py # command-line tool
frontend-laravel/ # Laravel + Livewire: UI + accounts + history (MySQL)
app/Models/ # User, Scan (Eloquent)
app/Livewire/ # Login, Scanner, Admin\{Overview,Users,Scans}
app/Services/SentinelApi.php # calls the Python scanner
database/{migrations,seeders}/
The Python core has no web/auth/DB dependency — the CLI, the scan API and the
Laravel UI all sit on top of the same scan() function. Laravel owns all state
in MySQL and calls the scanner over HTTP (optionally with a shared
SENTINEL_INTERNAL_KEY header for server-to-server auth).
The whole point: a user logs in on the Sentinel website, and from then on can run scans from their own terminal, in any project, with results saved to their dashboard. There's nothing to configure and no API keys to paste.
# 1. install the CLI once (one command — no virtualenv to manage)
curl -fsSL https://your-sentinel-site/install.sh | sh
# 2. log in through your browser (opens a consent page on the website)
export SENTINEL_WEB_URL=https://your-sentinel-site # where your Sentinel lives
sentinel login # ✓ Logged in as you@example.com
# 3. use it anywhere — scans auto-sync to your dashboard history
cd ~/my-project
sentinel . # deep-scan this whole project
sentinel whoami # who am I logged in as?
sentinel logout # disconnect this terminalHow the login works (browser "loopback" flow, like gcloud/gh):
sentinel login starts a tiny local server on 127.0.0.1, opens your browser to
the website's /cli/authorize consent page, and — once you click Approve —
the site redirects back to http://127.0.0.1:<port>/callback with a fresh
personal access token. The token only ever travels to your own machine and is
stored in ~/.sentinel/config.json (owner-only, chmod 600). Override per
command with SENTINEL_TOKEN / SENTINEL_WEB_URL, or skip saving a scan with
--no-sync.
The installer is a thin wrapper (it uses pipx/uv/pip under the hood); to install straight from git instead:
pipx install 'git+https://github.com/<you>/sentinel.git#subdirectory=backend'The package is
sentinel-scanner; the command it installs issentinel.
cd backend
python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" # ".[dev]" adds pytest; plain "-e ." for runtime onlyCLI:
sentinel . # deep-scan the current project (code + deps + git history)
sentinel ./path/to/project # deep-scan a specific directory
sentinel . --fail-on high # exit non-zero in CI if High+ issues
sentinel . --json # machine-readableThe terminal has one scan: it deep-scans a project you have access to. It
can't be pointed at a live website — passive website scanning lives in the web
app and REST API. (sentinel . --code still works; --code is now the implied
default.)
Scan your local codebase (--code):
Point Sentinel at a local repo to statically analyse the source code for vulnerabilities and get fix suggestions — no website or deployment needed. This runs entirely offline and read-only (it never executes your code or makes network calls), so no ownership verification is required: it's your machine.
sentinel . --code # scan the current repo
sentinel ./path/to/project --code # scan a specific directory
sentinel . --code --json # machine-readable output
sentinel . --code --fail-on high # exit non-zero in CI on High+ findingsIt flags issues like hardcoded secrets/keys, SQL & command injection, eval/
unsafe deserialization, XSS sinks (innerHTML, dangerouslySetInnerHTML,
unescaped templates), debug mode left on, disabled TLS verification, wildcard
CORS, and weak hashing (MD5/SHA1) — across Python, PHP, JS/TS and config files.
Each finding includes the file:line, a plain-language explanation, and a fix.
Secrets are redacted in the output, and node_modules/vendor/.venv/build
artifacts and lock/binary files are skipped automatically.
The code scan also does two extra offline passes:
- Dependency / SBOM scan. It parses your lockfiles and manifests
(
requirements.txt,Pipfile.lock,package-lock.json,package.jsonexact pins,composer.lock) and flags pinned versions that match a bundled snapshot of well-known advisories (CVEs for popular packages likerequests,lodash,guzzlehttp/guzzle, …). Only exact-pinned versions are evaluated, so there are no false positives from open ranges. This is a curated subset, not a complete database — for exhaustive, always-current coverage run a dedicated SCA tool (pip-audit,npm audit,osv-scanner) too. - Git-history secret scan. When the target is a git repository, it walks
every blob in history (read-only
git rev-list/cat-file) and reports secrets that were committed at any point — even if they were later deleted from the current files. A deleted-but-still-in-history key must be treated as leaked; the finding tells you to rotate it and purge history withgit filter-repo/BFG.
Both passes are offline and read-only (no network, no code execution).
Scanner API (stateless engine — Laravel calls this):
uvicorn sentinel.api.main:app --reload --port 8000
# Standard: POST /api/scan {"url": "https://example.com"}
# Deep: POST /api/scan {"url": "https://example.com", "deep": true, "authorized": true, "verification_token": "<token>"}
# (the target must serve <token> at /.well-known/sentinel-verify-<token>.txt)
# Verify: POST /api/verify {"url": "https://example.com", "token": "<token>"}
# -> {"verified": bool, "method": "file"|"dns"|null, "path": "...", "dns_record": "sentinel-verify=<token>"}
# Optional: set SENTINEL_INTERNAL_KEY and send it as the X-Internal-Key header
# docs at http://localhost:8000/docsThe UI is a Laravel + Livewire app (server-rendered PHP, reactive without hand-written JS). It owns all data in MySQL and calls the Python scanner.
cd frontend-laravel
composer install
# configure .env: DB_* (MySQL) and SENTINEL_API_URL=http://localhost:8000
php artisan key:generate # if APP_KEY is missing
php artisan migrate --seed # create tables + seed admin/demo accounts
php artisan serve --port 8090 # open http://localhost:8090It uses Livewire components (no controllers): Auth\Login, Scanner, and
Admin\{Overview,Users,Scans}, with Eloquent User/Scan models. Auth is
Laravel's session guard; App\Services\SentinelApi calls the scanner over HTTP;
auth + sentinel.admin middleware guard the routes. Needs PHP 8.2+, Composer,
and a MySQL database.
Set GOOGLE_SAFE_BROWSING_KEY in the backend environment to enable the
reputable-blocklist check.
The Laravel app exposes a token-authenticated JSON API under /api/v1, so you
can drive Sentinel from a script or the terminal. It uses the same accounts and
scan history as the web app. (The Python engine stays internal — the API calls
it for you.)
The API performs standard (passive) website scans only. Active website
attacking isn't offered anywhere; sending deep=1 returns 422. Deep project
scans are run from the terminal (sentinel . --code) and synced to history via
POST /api/v1/scans/import.
Auth is a personal access token sent as Authorization: Bearer <token>.
BASE=http://localhost:8090
# 1. Get a token (or POST /api/v1/register to create an account + token)
TOKEN=$(curl -s -X POST $BASE/api/v1/tokens \
-d 'email=demo@sentinel.test&password=demo12345' | jq -r .token)
# 2. Run a standard scan
curl -s -X POST $BASE/api/v1/scans \
-H "Authorization: Bearer $TOKEN" -d 'url=example.com' | jq '.result.grade, .result.score'
# 3. List / fetch / delete your scans
curl -s $BASE/api/v1/scans -H "Authorization: Bearer $TOKEN"
curl -s $BASE/api/v1/scans/123 -H "Authorization: Bearer $TOKEN"
curl -s -X DELETE $BASE/api/v1/scans/123 -H "Authorization: Bearer $TOKEN"
# Deep project scans run in the terminal and sync to this same history:
# sentinel . --code| Method & path | Auth | What it does |
|---|---|---|
POST /api/v1/register |
— | Create an account, returns a token |
POST /api/v1/tokens |
— | Email+password → new token |
GET /api/v1/me |
token | Current account |
DELETE /api/v1/tokens/current |
token | Revoke the current token |
GET /api/v1/scans |
token | List your scans (paginated) |
POST /api/v1/scans |
token | Run a standard scan (deep=1 is rejected — use the CLI) |
POST /api/v1/scans/import |
token | Save a locally-run (deep/--code) scan to history — used by the CLI after sentinel login |
GET /api/v1/scans/{id} |
token | One scan report (yours only) |
DELETE /api/v1/scans/{id} |
token | Delete a scan |
Tokens are stored hashed; the plaintext (prefixed stnl_) is shown once, and the
public endpoints are rate-limited. Deep/active vulnerability scanning lives only
in the CLI, which verifies you own the domain before testing.
Run the whole app for free on an Oracle Cloud Always Free VM (Nginx + PHP-FPM
for Laravel, the FastAPI engine as a systemd service, SQLite for storage — no
cold starts, no external DB). A ready-to-run kit lives in
deploy/oracle/:
sudo git clone <your-repo-url> /opt/sentinel
sudo bash /opt/sentinel/deploy/oracle/setup.sh yourdomain.comIt installs everything, configures the services, sets production env (debug off,
fresh APP_KEY, a shared internal key), runs migrations, and prints the next
steps (HTTPS via certbot, creating your admin). Full walkthrough — including the
Oracle firewall rules you must add — is in
deploy/oracle/DEPLOY.md.
- All active checks are non-destructive: GET-only probes and a single benign, inert marker for the XSS/redirect tests. Nothing is submitted, written, or deleted.
- The API refuses to scan private/loopback/internal addresses (SSRF guard).
- Heuristic checks (malware, XSS) are flagged as possible and recommend manual confirmation.
Released under the MIT License.