Sample report
What a Code Audit report looks like, start to finish.
A complete sample report for a fictional application, so you can see the structure and depth before you order one. The application is invented. The vulnerability data comes from a real composer audit run.
Sample report, fictional application
"Acme Fleet Portal" and Acme Logistics do not exist. The code, metrics and configuration findings in this report are invented to show the structure and depth of a Refberg Code Audit. The vulnerability data is real: on 24 October 2026 we ran composer audit against a lock file with the package versions listed in section 4.1. The advisory counts, CVE identifiers and fixed versions come from that run. Prices are examples based on our published starting prices.
Code Audit report
| Client | Acme Logistics Ltd (fictional) |
|---|---|
| Application | Acme Fleet Portal: booking, invoicing and a JSON API for the driver mobile app |
| Audit size | M (50,000 to 200,000 lines of PHP code) |
| Access | read-only repository, staging and production .env (redacted), server configuration files, crontab |
| Date | 24 October 2026 |
| Prepared by | Refberg |
1. Summary for management
The portal works and earns money, but it runs on software that stopped receiving security fixes between 2022 and 2025. Three problems need action this week, before any upgrade work:
- The staging server can be taken over from the internet. It is publicly reachable, runs in debug mode and has a component with a published remote code execution flaw (CVE-2021-3129). Staging holds a weekly copy of the production database, including customer names, addresses and invoices.
- Production passwords and the application's encryption key are in the Git history. Anyone who has, or had, a copy of the repository can read them. They have not been changed since 2021.
- Backups are stored on the same server as the database and have never been restored. A disk failure or ransomware on that server would take the data and the backups together.
All three can be closed in one to two days without changing application code (section 6, stage 0).
The longer-term problem is the platform itself: PHP 7.4 (end of life 28 November 2022) and Laravel 6 (security fixes ended 6 September 2022). The dependency scan found 114 published security advisories in 20 packages. Some can be fixed with patch releases inside Laravel 6. Most need the upgrade.
There are almost no automated tests (6.1% line coverage, none on the six business-critical paths), so any change today is a guess. Our recommendation is option A: build a safety net first, then upgrade in two fixed-price stages to Laravel 13 on PHP 8.4, over about four months, with Care Standard from month 5. Option B (stay on Laravel 6 with patched PHP) costs less this year, and section 6 explains when it makes sense.
2. Scope and method
In scope: application code in the repository (main branch at commit 4f1c9e2), composer.json and composer.lock, Git history, configuration files, server configuration supplied by the client, deploy script.
Tools: composer audit (Composer 2, Packagist advisory database), PHPStan 2.x without the Laravel extension (Larastan for Laravel 6 exists only for the old PHPStan 0.12 line, so we re-run with Larastan after the framework upgrade), Rector 2.x in dry-run mode, PHP_CodeSniffer with PHPCompatibility, PHPUnit with PCOV for coverage, gitleaks for secrets in history, git log for change frequency. Then a manual review of routes, authentication, file uploads, PDF generation, queues and scheduled jobs.
Not in scope: penetration testing, load testing, review of the mobile app, the payment provider's systems, or the network setup of the hosting provider. We did not attempt to exploit anything. Where we say a vulnerability is exploitable, we mean its published preconditions are met in your configuration.
3. Risk map
Likelihood and impact are rated 1 (low) to 3 (high). Priority: P0 this week, P1 within the first stage, P2 during the upgrade.
| ID | Risk | Likelihood | Impact | Priority |
|---|---|---|---|---|
| R1 | Remote code execution on public staging via Ignition (CVE-2021-3129); staging holds production personal data | 3 | 3 | P0 |
| R2 | APP_KEY, database and mail passwords exposed in Git history since 2021 | 3 | 3 | P0 |
| R3 | Backups on the same server, never restored | 2 | 3 | P0 |
| R4 | PDF generation with remote resources enabled on dompdf 0.8.6 (CVE-2022-28368, remote code injection via fonts) | 2 | 3 | P0 |
| R5 | No tests on critical paths; 9 of 42 existing tests fail | 3 | 2 | P1 |
| R6 | PHP 7.4 from Ubuntu 20.04 without Ubuntu Pro: no runtime security updates since May 2025 | 2 | 3 | P1 |
| R7 | Laravel 6 past end of life; 5 framework advisories have no fix on the 6.x line | 2 | 2 | P2 |
| R8 | Abandoned or superseded packages (SwiftMailer, Faker, Ignition 1.x) | 2 | 2 | P2 |
| R9 | One former developer held all knowledge; no runbook, deploy is a shell script run by hand | 3 | 2 | P1 |
4.1 Dependencies
Result: 114 advisories in 20 packages: 13 critical, 44 high, 50 medium, 7 low. 110 of them are in production dependencies. Selected packages:
| Package | Installed | Advisories | Notable advisories | First fixed version | Fixable inside Laravel 6? |
|---|---|---|---|---|---|
laravel/framework | 6.20.0 | 11 | CVE-2021-21263 (query binding), CVE-2021-43808 (Blade XSS), CVE-2024-52301 (environment via query string) | 6.20.45 for 6 of 11 | partly: 6.20.45 |
facade/ignition (dev) | 1.16.3 | 3 | CVE-2021-3129 (remote code execution) | 1.16.14 (1.16.15 for all three) | yes; and must not be installed in production |
dompdf/dompdf | 0.8.6 | 14 | CVE-2022-28368 (remote code injection via fonts), CVE-2021-3838 (deserialization) | 1.2.1 / 2.0.x / 3.1.6 | partly: needs a major laravel-dompdf upgrade |
guzzlehttp/guzzle | 6.5.5 | 14 | CVE-2022-31090, CVE-2022-31091 (credentials kept after a redirect to another origin) | 6.5.8 for 5 of 14 | partly |
firebase/php-jwt | 5.2.0 | 2 | CVE-2021-46743 (key/algorithm confusion) | 6.0.0 | yes, with code changes (new API) |
league/flysystem | 1.0.70 | 2 | CVE-2021-32708 (race condition in file writes) | 1.1.4 | yes |
phpoffice/phpspreadsheet | 1.15.0 | 29 | XXE in XLSX reader (CVE-2024-45293, CVE-2024-48917) | 1.29.4 and later (for the two XXE issues) | probably, via a newer maatwebsite/excel 3.1.x; to confirm in stage 1 |
swiftmailer/swiftmailer | 6.2.3 | 1 | CVE-2024-28859 (deserialization gadget) | 6.2.5 | yes, but the package is abandoned |
Abandoned or superseded: swiftmailer/swiftmailer is marked abandoned on Packagist in favor of symfony/mailer (Laravel 9 switched to it). fzaninotto/faker is marked abandoned. facade/ignition was superseded by spatie/laravel-ignition; its last release was in January 2023. fideloper/proxy has had no release since February 2022; its functionality moved into Laravel 9.
Why this matters more than the count: the dangerous items are the ones whose preconditions you meet. In this application those are Ignition on staging (R1, debug mode is on), dompdf with enable_remote => true in config/dompdf.php (R4, the invoice template loads the logo by URL), and php-jwt in the mobile API (we could not confirm exploitability without the key setup; see 4.4).
4.2 Tests and CI
- 42 PHPUnit tests, 9 failing on
main. The failures date from 2023 and the tests were not run since. - Line coverage 6.1%. Covered: two helper classes and the login form.
- Critical paths with no test: booking creation, price calculation, invoice generation (PDF), payment callback, driver API authentication, monthly invoice export (XLSX).
- No CI pipeline.
deploy.shrunsgit pull,composer install(without--no-dev, which is why Ignition and Faker are on the servers), andphp artisan migrate --forceon production, in that order, with no backup step.
4.3 Static analysis and upgrade readiness
| Check | Result |
|---|---|
| PHPStan level 0 (no Laravel extension) | 286 errors. 41 are calls to undefined methods or functions, at least 7 of them on code paths reachable from routes |
| PHPStan level 5 (no Laravel extension) | 1,912 errors. We recommend a baseline at level 5 and no new errors from stage 1 onward |
| PHPCompatibility, target PHP 8.4 | 37 errors, 112 warnings |
| Rector dry-run, PHP 8.1 rule set | 214 files would change; about 80% are mechanical (constructor promotion, str_contains, null-safe calls) |
Blockers for PHP 8, in the order they would surface:
app/Libraries/LegacyCsv.phpuseseach(), removed in PHP 8.0. This class is used by the monthly import: a fatal error on the first run after the upgrade.- 23 classes use dynamic properties (deprecated in PHP 8.2), mostly in
app/Services/Pricing/. ${…}string interpolation (dollar sign outside the braces) in 31 places (deprecated in PHP 8.2).- Implicitly nullable parameters in 58 method signatures (deprecated in PHP 8.4).
- Laravel 6 is supported on PHP 8.0 at most, so the PHP and framework upgrades have to be interleaved (section 6).
Hotspots
Files changed most often in the last 24 months, by size.
| File | Lines | Commits (24 months) | Note |
|---|---|---|---|
app/Http/Controllers/BookingController.php | 2,140 | 61 | pricing, validation and email in one class; first candidate for characterization tests |
app/Services/InvoiceService.php | 1,310 | 38 | PDF generation, numbering, VAT; numbering uses MAX(id)+1 without a lock |
app/Http/Controllers/Api/DriverController.php | 870 | 29 | JWT handling written by hand around php-jwt |
4.4 Configuration and security
| ID | Finding | Evidence | Recommendation |
|---|---|---|---|
| C1 | Staging is public, APP_DEBUG=true, Ignition installed | staging.acme-fleet.example answers without authentication | restrict by IP or HTTP auth today; set APP_DEBUG=false; deploy with --no-dev |
| C2 | Staging database is a weekly copy of production with personal data | cron job refresh-staging.sh | replace with an anonymized copy; this is also a GDPR issue |
| C3 | Secrets in history | gitleaks: .env.production committed in March 2021 and removed in a later commit; contains APP_KEY, DB_PASSWORD, MAIL_PASSWORD | rotate all three; see the note on APP_KEY below |
| C4 | Trusted proxies set to * | app/Http/Middleware/TrustProxies.php | list the load balancer address; today any client can set its own IP via X-Forwarded-For, which defeats the login rate limit |
| C5 | Session cookie without Secure flag, no HSTS | SESSION_SECURE_COOKIE unset; no Strict-Transport-Security header | set both after confirming HTTPS everywhere |
| C6 | dompdf remote resources enabled | config/dompdf.php: enable_remote => true | embed the logo as a local file and disable remote |
| C7 | Queue worker started from cron every minute, as root | crontab -l for root | run under Supervisor or systemd as the application user |
| C8 | Backups: nightly mysqldump to /var/backups on the same server | crontab, disk listing | off-site copy, encryption, monthly restore test |
| C9 | JWT verification accepts the algorithm from the token header | DriverController::decodeToken() | covered by the php-jwt 6 upgrade; fix the handler at the same time |
About rotating APP_KEY
Laravel uses this key for encrypted cookies, signed URLs and any value your code encrypts with Crypt::encrypt() or encrypt(). Changing it logs out all users and makes those values unreadable unless they are re-encrypted. The portal stores one such field (drivers' national ID numbers, encrypted in a model mutator). Stage 0 includes a short script that decrypts with the old key and re-encrypts with the new one.
4.5 Runtime
The production server runs Ubuntu 20.04 with the distribution's PHP 7.4 package. Ubuntu's standard security maintenance for 20.04 ended in May 2025. The server is not attached to Ubuntu Pro, so the PHP package and the rest of the operating system get no security updates.
Attaching the server to Ubuntu Pro would resume them until May 2030 for packages in Ubuntu's main repository, which includes PHP 7.4. This fixes the runtime only; it does nothing for Laravel or the packages in section 4.1.
4.6 What is in good shape
- Database migrations rebuild the full schema from an empty database without errors, so a reproducible environment needs no manual SQL.
- The payment callback verifies the provider's signature before it changes an order.
- The application uses Laravel's own queues, scheduler and mail components without custom replacements, so the upgrade path follows the official upgrade guides.
5. Priorities
| Priority | What | Why first |
|---|---|---|
| P0, this week | C1, C2, C3, C6, C8 | exploitable now or would make an incident unrecoverable |
| P0, this week | laravel/framework 6.20.0 → 6.20.45, league/flysystem → 1.1.4, guzzlehttp/guzzle → 6.5.8, guzzlehttp/psr7 → 1.9.1 | patch releases, same major version, low risk even without tests |
| P1, stage 1 | tests on the six critical paths, CI, runbook, monitoring, C4, C5, C7, Ubuntu Pro or a new server | nothing else can be changed safely until this exists |
| P2, stages 2–3 | PHP and Laravel upgrade, replacement of abandoned packages, C9 | needs the test net from stage 1 |
6. Plan and fixed prices
Prices in this sample are examples based on our published starting prices. A real audit ends with a quote like this one, with prices fixed for the scope set out in it.
Option A (recommended): safety net, then Laravel 13 on PHP 8.4
| Stage | Scope | Duration | Fixed price | Payment |
|---|---|---|---|---|
| 0. Contain | items marked P0 in section 5; your team can do it in 1–2 days with our step-by-step instructions, or we do it as the first week of stage 1 | 1 week | included in stage 1 | with stage 1 |
| 1. Takeover | Docker environment that reproduces production; CI; characterization tests for the six critical paths; PHPStan baseline at level 5; off-site backups with a restore test; monitoring; runbook and architecture map; access inventory | 4 weeks (size M) | €3,000 | 100% at order |
| 2. Upgrade I | Laravel 6 → 9 and PHP 7.4 → 8.1 in steps (6 → 8 on PHP 7.4, then PHP 8.0, then Laravel 9 on PHP 8.1); SwiftMailer → Symfony Mailer; php-jwt 6 with the JWT handler fix; dompdf 2.x; Guzzle 7; Faker → fakerphp/faker; LegacyCsv rewrite | 6 weeks | €6,000 | 30% / 70% after green CI and your sign-off on staging |
| 3. Upgrade II | Laravel 9 → 13 and PHP 8.1 → 8.4; Ubuntu 24.04 server; phpspreadsheet and dompdf to current versions; removal of fideloper/proxy; dynamic properties and deprecations cleared | 6 weeks | €6,000 | 30% / 70% after green CI and your sign-off on staging |
| Care Standard | monthly updates of PHP and dependencies, monitoring, 8 hours of changes, critical response within 4 business hours, quarterly review | from month 5 | €800/month | monthly |
Option B: keep it safe on the current version for 12 months
Stage 0 and stage 1 as above (€3,000), Ubuntu Pro on the existing server for runtime patches (subscription paid by you to Canonical), the patch releases from section 5, and Care Basic at €400 a month.
This keeps the obvious holes closed while you decide about the application's future. It does not remove the 5 Laravel advisories that have no fix on the 6.x line, the dompdf and phpspreadsheet issues that need major upgrades, or the PHP 8 blockers. After 12 months, option A will cost about the same as today, plus whatever new advisories appear in the meantime.
Option B makes sense if the portal will be replaced or retired within 18 months, or if the budget for this year is fixed.
Option C: rewrite
We don't recommend it. The portal's business rules (pricing, invoice numbering, VAT handling) exist only in the code, and none of them are tested. A rewrite would have to rediscover them, and the old system would still need option B while the new one is built.
7. What we need from you to start
- A decision on option A or B.
- For stage 0: someone with admin access to staging and the database who can rotate passwords with us on a call or by email.
- For stage 1: named accounts for us in Git hosting and on staging, and a person who can say what the six critical paths should do when the code and the users disagree.
Appendix A: full dependency scan. The complete composer audit output (JSON, all 114 advisories) is delivered with the report as a separate file.
The report and all findings are confidential and covered by the NDA.
How a report this detailed fits the price
Tools do the counting and we read the code. AI coding tools, used with your written consent, speed up the reading and the checks; an engineer reviews every finding before it goes into the report. More in How we work and Security & trust.
Want a report like this for your application?
Code Audit from €800, report in 5–7 business days. Not sure yet? Start with a Debt Check for €300.