DEV Community

Cover image for I built an offline CLI that audits a legacy PHP app in one command — and shows you the fix
getobserver
getobserver

Posted on • Originally published at sanks205.github.io

I built an offline CLI that audits a legacy PHP app in one command — and shows you the fix

Every time I inherit a legacy PHP app — a CodeIgniter admin panel, an old Laravel service, a WordPress site someone's cousin built — day one is the same ritual: grep for password, squint at config.php, wonder if display_errors is on in prod, and hope there isn't a hardcoded API key waiting to ruin my week.

So I built a tool to do that first pass for me, in one command, offline. It's called Observer, it's a single Go binary, and it's free and MIT.

observer analyze ./your-app --out report.html
Enter fullscreen mode Exit fullscreen mode

Point it at a folder, get one self-contained HTML report. No server, no account, no instrumentation, nothing sent anywhere.

What it actually found

I ran it against a real CodeIgniter 3 admin app I'd picked up. A few seconds later:

Project:   admin-panel
Language:  PHP
Framework: CodeIgniter 3 [High]
Database:  MySQL [High]

Security score: 81/100 (B)   Code health: 100/100 (A)
Static analysis: issues across secrets, config & dependencies

  [High]  application/config/firebase.php   Hardcoded Google API key
  [High]  index.php                         display_errors enabled
  [Med ]  composer.json                     End-of-life PHP version
Enter fullscreen mode Exit fullscreen mode

None of these are exotic. That's the point — the boring, high-impact stuff is exactly what gets missed on a handover, and it's what an attacker finds first. A hardcoded key in a config file that's sitting in the repo is a bad afternoon waiting to happen.

It doesn't just flag — it shows the fix

This is the part I care about most. Most free scanners hand you a wall of findings and leave you to Google each one. Every Observer finding carries a concrete before → after fix, right in the report — no AI key required, fully offline:

// Before
$db->query("SELECT * FROM users WHERE id = " . $id);

// After
$db->query("SELECT * FROM users WHERE id = ?", [$id]);
Enter fullscreen mode Exit fullscreen mode

Each finding also gets a severity, a file:line, and a CWE/OWASP tag, so you can triage instead of just staring.

What it checks

  • Security rules across PHP, JS/TS, Python, Java & Ruby — hardcoded secrets, SQL injection, XSS, command execution, insecure deserialization, weak crypto, dangerous config
  • Dependency CVEs via OSV.dev (--cve) — Composer, npm, PyPI, Go
  • A Security score and a Code-Health score (A–F) plus an estimated fix effort
  • SARIF / JSON / CSV output + a GitHub Action, so it drops into CI
  • It also auto-detects Semgrep / PHPStan / Bandit / gosec if you already have them, and folds their findings into the same report

Offline by default (and provably so)

No telemetry, no phone-home. The only things that touch the network are explicitly opt-in (--cve, or --ai with your own OpenAI key). For regulated or client-confidential work there's a flag that makes it enforceable:

observer analyze ./client-app --assert-offline
# Offline mode: no network I/O.
Enter fullscreen mode Exit fullscreen mode

It refuses any network-touching option and keeps the AI on a local heuristic. Handy when you're auditing someone else's code under NDA and the code genuinely cannot leave the machine.

What it's not

It's not trying to replace SonarQube, Snyk, or Sentry. Those go deep in one dimension and want a server, a cloud account, or instrumentation. Observer is the opposite shape: a zero-setup, offline snapshot for the moments those are too heavy — a legacy handover, a quick client audit, an air-gapped scan, or just "what's wrong with this thing I just cloned?" Run both; they answer different questions.

Try it / tell me what it misses

It's genuinely free and MIT — grab a binary and point it at any repo:

👉 https://github.com/sanks205/getobserver

There are a few optional one-time paid add-ons (branded PDF reports, scheduled scans, deeper framework rule packs), but the whole core scanner is free with no account.

This is early, and I'd really value feedback from people who work on older codebases: what did it flag that was noise? What rule would've actually saved you an afternoon? Drop a comment — that's what I'm building from next.

Top comments (0)