Security tests · release 2.0.0
What we tested, and what we found.
We tested release 2.0.0 ourselves, and this page holds the whole result: every tool with its version, date and scope, every finding and what happened to it, and what the tests do not cover. These are our own tests, not an independent audit.
Security tests
Release 2.0.0
Tested on8 October 2026
Tests and tools8
Findings fixed101
Critical or High, open0
Open, any severity0
The tests
What was tested.
8 tests and tools, each with its version, the date it ran, what it looked at and how much it checked.
- 577addresses scanned with OWASP ZAP
- 151PHP packages checked for published advisories
- 16,650files scanned for secrets
- 2,330automated tests passed, of 2,333
| Test or tool | Version | Date | What was scanned | Checked | In detail |
|---|---|---|---|---|---|
| composer audit --locked | Composer version 2.10.3 2026-08-27 13:34:23 | 8 October 2026 | Every package in composer.lock (runtime and dev) | 151 packages | runtime packages: 119; dev packages: 32; advisories: 0; abandoned: 0 |
| Vendored JavaScript vs OSV.dev | OSV.dev API v1/query | 8 October 2026 | alpinejs 3.17.4, @alpinejs/csp 3.17.4, leaflet 1.9.4 | 3 libraries | advisories: 0 |
| Semgrep CE | semgrep 1.180.0 | 8 October 2026 | First-party code: app, routes, config, bootstrap, database, lang, resources/views, public/index.php, public/js/site.js, public/ | 1,974 files | files scanned: 1,974; rules loaded: 1,080; rules for scanned languages: 342; findings: 269; errors: 0 |
| scan-secrets.sh (--path, --history) | scan-secrets.sh sha256 36da7b311362dc27 | 8 October 2026 | The buyer's view of the tree (git archive + vendor/) and every file name ever committed | 16,650 files | files scanned: 16,650; history commits: 363; history file names: 2,659; history hits: 0 |
| Automated test suite (gate, one process per file) | gate-suite.sh (one process per test file) | 8 October 2026 | Every test file of the commit | 2,333 tests | tests: 2,333; passed: 2,330; failed: 0; skipped: 3; files: 439; files failing: 0 |
| OWASP ZAP (spider, passive and active scan) | ZAP 2.17.0 | 8 October 2026 | A local copy of this release (never a live site), scanned per role: guest 78 URLs + active scan; api 166 URLs + active scan; member 212 URLs + active scan; admin 121 URLs + active scan | 577 URLs | alert types: 16; URLs guest: 78; URLs API: 166; URLs member: 212; URLs admin: 121 |
| Response headers of the local copy (curl) | curl | 8 October 2026 | 6 security headers on /, /connect/login, /admin/login, /give, /events, /api/v1/openapi.json, /robots.txt | 7 pages | headers checked: 42 |
| Code reviews by AI code reviewers (Claude), directed by Cerevonix | 8 reviews, 25 Sep to 4 Oct 2026 | 4 October 2026 | Whole codebase (25 and 26 Sep); the changes up to 27 Sep; the changes up to 3 Oct (two reviews); and three reviews on 4 Oct: the changes up to 4 Oct together with our licence service, the built-in updater, and the email templates. Sign-in, sessions, authorisation, payments, uploads, SSRF, XSS, SQL, exports, the licence check, update packages and email templates. | 8 reviews | findings: 110; fixed: 100; accepted: 10; false positive: 0; open: 0 |
The run is dated 8 October 2026 and is recorded against one version of the code, commit a3ff91d807fab5d4. A test that ran on another day shows its own date in the table. The automated suite: 2,333 tests in 439 files, 2,330 passed, 0 failed, 3 skipped.
The results
What the tests found.
Nothing was rated Critical. Rated High: 6 (4 fixed, 2 false positive). No finding is open.
A row in the full list can stand for several places in the code; the totals here count every place. Each finding ends in one of these states:
- Fixed: corrected in the release named, with the test that keeps it fixed.
- Accepted: real, and left as it is for the reason given.
- False positive: the tool flagged something that is not a problem, for the reason given.
- Open: real and not fixed yet, with the plan for it.
| Severity | Found | Fixed | Accepted | False positive | Open |
|---|---|---|---|---|---|
| Critical | 0 | 0 | 0 | 0 | 0 |
| High | 6 | 4 | 0 | 2 | 0 |
| Medium | 43 | 23 | 18 | 2 | 0 |
| Low | 75 | 64 | 11 | 0 | 0 |
| Info | 708 | 10 | 4 | 694 | 0 |
| All | 832 | 101 | 33 | 698 | 0 |
| Test or tool | Found | By severity | Fixed | Accepted | False positive | Open |
|---|---|---|---|---|---|---|
| composer audit --locked | none | Critical 0, High 0, Medium 0, Low 0, Info 0 | 0 | 0 | 0 | 0 |
| Vendored JavaScript vs OSV.dev | none | Critical 0, High 0, Medium 0, Low 0, Info 0 | 0 | 0 | 0 | 0 |
| Semgrep CE | 269 | Critical 0, High 0, Medium 0, Low 4, Info 265 | 0 | 6 | 263 | 0 |
| scan-secrets.sh (--path, --history) | 4 | Critical 0, High 0, Medium 0, Low 0, Info 4 | 0 | 0 | 4 | 0 |
| Automated test suite (gate, one process per file) | none | Critical 0, High 0, Medium 0, Low 0, Info 0 | 0 | 0 | 0 | 0 |
| OWASP ZAP (spider, passive and active scan) | 443 | Critical 0, High 2, Medium 19, Low 1, Info 421 | 1 | 17 | 425 | 0 |
| Response headers of the local copy (curl) | 6 | Critical 0, High 0, Medium 0, Low 0, Info 6 | 0 | 0 | 6 | 0 |
| Code reviews by AI code reviewers (Claude), directed by Cerevonix | 110 | Critical 0, High 4, Medium 24, Low 70, Info 12 | 100 | 10 | 0 | 0 |
The full list
Every finding, and what happened to it.
89 rows, grouped by the test that reported them. Each row carries its status and the reason for it.
Semgrep CE: 12 rows, 269 places
| ID | Severity | What was found | Rule or source | Places | Status | Why, or where it was fixed |
|---|---|---|---|---|---|---|
| SG-001 | Info | Unescaped Blade output {!! !!} | alumdeck. | 139 | False positive | Each value is escaped first (e(), nl2br(e()), escaped str_replace parts), cleaned (clean_html(), SafeMarkdown), JSON with the HEX flags, a fixed entity or generated QR SVG, a hex-checked theme colour, or text in a plain-text email. |
| SG-002 | Info | Tracking code printed as written | alumdeck. | 2 | Accepted | By design: full administrators paste their own tracking code (Settings); it waits for the visitor's consent, and its script and style tags get the page's nonce. |
| SG-003 | Info | Raw SQL built from a non-literal string | alumdeck. | 37 | False positive | Column and table names come from the code, never the request; every value is a bound parameter, and LIKE patterns escape their wildcards. |
| SG-004 | Info | md5() or sha1() used | php. | 33 | False positive | Used for cache keys, ETags, idempotency keys and short identifiers, never for passwords, tokens or signatures. |
| SG-005 | Low | Vonage inbound signature may use md5hash | php. | 1 | Accepted | md5hash is one of Vonage's own signature methods; the site uses the method chosen for the Vonage account, and the HMAC-SHA methods otherwise. The comparison is constant-time. |
| SG-006 | Info | unlink() on a variable path | php. | 36 | False positive | Every path is built by the code inside its own folders; uploaded file names are accepted only in the exact shape the upload wrote. |
| SG-007 | Info | Redirect to a non-literal address | php. | 14 | False positive | Filament resource addresses, the admin path, which is validated to letters, digits, hyphens and underscores, and the signed link for downloading a backup, which the code builds itself after checking the backup; always this site. |
| SG-008 | Low | Second fetch of the site's own address without certificate checks | alumdeck. | 2 | Accepted | Check my headers and the installer's media check fetch only APP_URL; when the verified fetch fails they try once more without checks to tell a broken certificate from no answer, and say the result is unverified. |
| SG-009 | Info | openssl_decrypt() result | php. | 2 | False positive | SAML decryption checks the result: OpenSSL checks the GCM tag, and the CBC padding is checked in constant time before the plaintext is used. |
| SG-010 | Info | exec() in the backup | php. | 1 | False positive | One helper runs the backup's tar, gzip, mysqldump and mysql commands. Each is built from fixed words and escapeshellarg() values only; the database password is passed in an options file, never on the command line. |
| SG-011 | Info | File path from a server variable | php. | 1 | False positive | DOCUMENT_ROOT is set by the web server, not by a visitor. |
| SG-012 | Low | unserialize() of queued notifications | php. | 1 | Accepted | The mail outbox reads back notifications it wrote itself under storage/ |
scan-secrets.sh (--path, --history): 1 row, 4 places
| ID | Severity | What was found | Rule or source | Places | Status | Why, or where it was fixed |
|---|---|---|---|---|---|---|
| SS-001 | Info | Credential-like words in third-party code | credential-content | 4 | False positive | An option description (Laravel's down command), a PDF form constant (dompdf, twice) and a null constant (psysh): no credential. |
OWASP ZAP (spider, passive and active scan): 17 rows, 443 places
| ID | Severity | What was found | Rule or source | Places | Status | Why, or where it was fixed |
|---|---|---|---|---|---|---|
| ZP-002 | Medium | Admin panel: the Content- | 10055 CSP: script-src unsafe-eval | 4 | Accepted | Admin panel only: Filament 5.8.4's own templates hold 67 Alpine expressions the CSP build cannot run, and Livewire runs @script blocks with new Function(). The public site and member area run Alpine's CSP build and allow no eval; inline scripts need the page's nonce everywhere. |
| ZP-003 | Medium | Admin panel: the Content- | 10055 CSP: style-src unsafe-inline | 4 | Accepted | Admin panel only: Filament writes style attributes on its components, its scripts set more, and Livewire updates bring style blocks after the page's nonce is fixed. A style cannot run code. The public site and member area allow no inline style (two pages that show an email as sent excepted). |
| ZP-004 | Medium | Admin panel: the Content- | 10055 CSP: Wildcard Directive | 4 | Accepted | Admin panel only: rich-text fields show the images their stored text holds, from any https host, and upload previews are blob: addresses. Images cannot run code. The public site allows images only from itself and the hosts each page needs. |
| ZP-005 | Medium | The REST API answers any origin (Access- | 10098 Cross-Domain Misconfiguration | 5 | Accepted | The API reads only the token in the Authorization header (AuthenticateApi |
| ZP-006 | Info | A redirect carries a body | 10044 Big Redirect Detected (Potential Sensitive Information Leak) | 40 | False positive | The body is Laravel's standard Redirecting to page, which repeats the target address; nothing else. |
| ZP-007 | Info | The XSRF-TOKEN cookie is readable by scripts | 10010 Cookie No HttpOnly Flag | 20 | False positive | By design: it is the CSRF token for the page's own scripts and is useless without the session cookie, which is HttpOnly. |
| ZP-008 | Info | A Unix timestamp in pages | 10096 Timestamp Disclosure - Unix | 20 | False positive | The ?v= version of the site's own CSS and script files (their modification time). |
| ZP-009 | Info | No X-Content-Type-Options on static files | 10021 X-Content-Type-Options Header Missing | 20 | False positive | Only on files PHP's built-in server sent directly in the local copy (images, scripts, robots.txt). On Apache public/.htaccess adds nosniff to every file (docs/server has the Nginx and LiteSpeed lines); the application's own pages always carry it. |
| ZP-010 | Info | Words such as admin, select or from in scripts | 10027 Information Disclosure - Suspicious Comments | 28 | False positive | Ordinary words in minified JavaScript and the page's structured data, not comments that reveal anything. |
| ZP-011 | Info | A query parameter appears in an attribute | 10031 User Controllable HTML Element Attribute (Potential XSS) | 47 | False positive | The page address is printed HTML-escaped (canonical and og:url), and the contact form accepts only its listed purposes. |
| ZP-012 | Info | ZAP noted a sign-in form | 10111 Authentication Request Identified | 7 | False positive | Informational: not a finding. |
| ZP-013 | Info | ZAP noted the session cookie | 10112 Session Management Response Identified | 234 | False positive | Informational: not a finding. |
| ZP-014 | High | Path traversal suspected (ZAP: low confidence) | 6 Path Traversal | 1 | False positive | ZAP sent the page's own name back as the value (purpose=contact on the contact page; in an earlier scan of this release also location=alerts on the job-alert page) and the page looked the same. Both are text values: the contact purpose must be one of the listed purposes, and the job-alert location prefills a search box. Neither touches a file. |
| ZP-015 | Medium | A form without a CSRF token | 10202 Absence of Anti-CSRF Tokens | 2 | False positive | The form is method="dialog": it closes a pop-up in the browser and sends nothing to the server. Every form that posts carries the CSRF token. |
| ZP-016 | Info | ZAP noted a script-driven page | 10109 Modern Web Application | 5 | False positive | Informational: not a finding. |
| ZP-017 | High | SQL injection suspected on a script file's version number (ZAP: timing only) | 40024 SQL Injection - SQLite (Time Based) | 1 | False positive | ZAP put a database command into the version number at the end of a JavaScript file's address and timed the answer: 0.5 to 0.9 seconds, against 0.5 seconds for the unchanged address. That address is a file on disk. The web server returns the file itself and runs neither the application nor the database for it, so nothing reads the value. Asked again 160 times on a quiet machine with four different values, ZAP's two among them, the file came back in 0.3 thousandths of a second every time, the same 6,330 bytes. The slow answers during the scan came from other work running on the test machine. This row covers script and style files only: the same alert on any other address would stay untriaged. |
| ZX-001 | Low | A form posted with a list where a word belongs made its page answer 500 (seen in the application log, not as a ZAP alert) | application log during the member scan | 1 | Fixed | Fixed in 2.0.0; test: trust_ |
Response headers of the local copy (curl): 1 row, 6 places
| ID | Severity | What was found | Rule or source | Places | Status | Why, or where it was fixed |
|---|---|---|---|---|---|---|
| HD-001 | Info | robots.txt arrived without the security headers in the local copy | missing security headers on a static file | 6 | False positive | robots.txt is a static file, which PHP's built-in server sent with no headers at all. On Apache public/.htaccess adds nosniff, Referrer-Policy, X-Frame-Options and the base Content- |
Code reviews by AI code reviewers (Claude), directed by Cerevonix: 58 rows, 110 places
| ID | Severity | What was found | Rule or source | Places | Status | Why, or where it was fixed |
|---|---|---|---|---|---|---|
| CR-001 | High | Review of 26 Sep: a Users-area staffer could take over accounts above them; a raised event gift kept the old payment | docs/09 B-1, C-1 | 2 | Fixed | Fixed in 2.0.0; test: i18n_ |
| CR-002 | Medium | Review of 26 Sep: authenticator seed in admin pages, staff remember-me cookie, level checks, javascript: links, push SSRF | docs/09 A-1, A-2, B-2, B-3, C-2, C-3 | 6 | Fixed | Fixed in 2.0.0; test: i18n_ |
| CR-003 | Low | Review of 26 Sep: sign-in answer for staff-only accounts, campaign rescheduling at view level | docs/09 A-4, B-8 | 2 | Fixed | Fixed in 2.0.0; test: i18n_ |
| CR-004 | Medium | Review of 27 Sep: eight Medium findings in payments, admin levels, feeds and messaging | R1-01 to R1-08 | 8 | Fixed | Fixed in 2.0.0; test: improve_FIX_R1*, improve_FIX_S2A_* (each names its R1 id) |
| CR-005 | Low | Review of 27 Sep: 22 Low findings, and the superseded API note B-9 | R1-09 to R1-30, docs/09 B-9 | 23 | Fixed | Fixed in 2.0.0; test: improve_FIX_R1*, improve_FIX_S2A_*, improve_ |
| CR-006 | Medium | Review of 3 Oct: any member could give themselves door check-in rights | S3-SEC-01 | 1 | Fixed | Fixed in 2.0.0; test: s3fix1_ |
| CR-007 | Low | Reviews of 3 Oct: eight Low findings (money labels, donor search, group rules, attachments audit, OAuth redirect and cap, API idempotency) | S3-SEC-02 to S3-SEC-05, S3-SEC2-01 to S3-SEC2-04 | 8 | Fixed | Fixed in 2.0.0; test: s3fix1_*, s3fix3_*, idapi_McpOAuthTest |
| CR-008 | Low | Other sessions kept access to staff-only files and door check-in after a password change | docs/09 A-3 | 1 | Fixed | Fixed in 2.0.0; test: trust_ |
| CR-009 | Low | Anyone could force a licence check on every request | docs/09 A-8 | 1 | Fixed | Fixed in 2.0.0; test: guard_LicenceLockTest, secfix_ |
| CR-010 | Low | Answering going to an event ignored its membership rule and showed the online joining link | docs/09 B-6 | 1 | Fixed | Fixed in 2.0.0; test: trust_ |
| CR-011 | Low | The emailed-code fallback of two-factor sign-in did not notify the account | docs/09 A-5 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ |
| CR-012 | Low | The password-reset form may have answered more slowly for an address that has an account (suspected) | docs/09 A-6 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ |
| CR-013 | Low | Emailed links followed the request host, and any subdomain of the site's own host was accepted | docs/09 A-7 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ |
| CR-014 | Low | A saved audience with giving conditions listed its members to staff without the see money right | docs/09 B-4 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ |
| CR-015 | Low | A block was not enforced on contact requests, mentoring requests or group invitations | docs/09 B-5 | 1 | Fixed | Fixed in 2.0.0; test: sechard_BlocksTest |
| CR-016 | Low | The membership-tier gate did not cover every member page: the business directory's rule gated nothing | docs/09 B-7 | 1 | Fixed | Fixed in 2.0.0; test: sechard_TierGateTest (every page a rule names exists and runs the gate) |
| CR-017 | Low | Forum images had a 40-megapixel limit but no memory check (denial of service on small hosts) | docs/09 C-6 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ImagesTest (a picture that will not fit PHP's memory limit is refused before it is decoded) |
| CR-018 | Low | Profile photos and archive uploads kept their EXIF data (for example a phone's location) | docs/09 C-7 | 1 | Fixed | Fixed in 2.0.0; test: sechard_ImagesTest (JPEG, PNG and WebP uploads are written again without their metadata before they are stored) |
| CR-019 | Low | The geocoder and AI-assistant addresses are fetched without a private-network check | docs/09 C-4, C-5 | 2 | Accepted | Only full administrators set these addresses (Settings); by design. |
| CR-020 | Low | The Content- | SEC-12 | 1 | Fixed | Fixed in 2.0.0; test: csp_PolicyTest, csp_RouteRenderTest (nonces everywhere; no eval and no inline style outside the admin panel, whose remaining exceptions are ZP-002 to ZP-004) |
| CR-021 | Low | Left from CR-011: the two-factor method can still be changed with an emailed code alone, and the account is not emailed that it changed | docs/09 A-5, what the fix left | 1 | Accepted | By design the emailed code is the way back in for staff who have lost their phone. Every change first sends a code to the account's own address, a sign-in by emailed code in place of the app now sends the security notice (CR-011), and the change is written to the audit log. |
| CR-022 | Low | Left from CR-012: under Apache mod_php or CGI the reset form's connection may still close later for an address that has an account | docs/09 A-6, what the fix left | 1 | Accepted | The answer is written out in full, in the same words, before the lookup starts; PHP-FPM and LiteSpeed also release the connection first. The member form is limited to 5 tries a minute and the admin form to 2. The difference was not measured on a real host. |
| CR-023 | Info | Left from CR-013: links in notices a queue worker sends later, in the in-app bell, in push messages and inside calendar and PDF attachments still follow the request host | docs/09 A-7, what the fix left | 1 | Accepted | The site now answers only to its configured host, its www twin and the names the administrator lists in TRUSTED_HOSTS, so the request host is always one of the site's own names. Email sent during the request is rewritten to the configured address; a queue worker is used only if QUEUE_CONNECTION is changed from the shipped sync. |
| CR-024 | Low | Left from CR-014: a campaign sent to an audience with a giving condition showed how many people it reaches, and after sending who received it, to Communications staff without the see money right | docs/09 B-4, what the fix left | 1 | Fixed | Fixed in 2.0.0; test: sechard_ |
| CR-025 | Low | Left from CR-018: photos stored before 2.0.0, and photos staff upload in the admin panel, keep their EXIF data | docs/09 C-7, what the fix left | 1 | Accepted | The fix covers what members upload from 2.0.0 on. An update does not rewrite files that are already stored, and pictures added in the admin panel are chosen and published by staff. |
| CR-026 | High | Licence check: on a site whose sign-in was paused, an address written with percent-encoding (for example /%61dmin) got past the pause | Review of 4 Oct, licence: F1 | 1 | Fixed | Fixed in 2.0.0; test: secfix_LicencePathTest (encoded addresses, the admin panel's own update requests and staff pages are paused like the pages they reach) |
| CR-027 | Medium | Licence check: a plain PHP file could stand in for built-in functions the licence code calls, so a forged licence answer would have verified | Review of 4 Oct, licence: F2 | 1 | Fixed | Fixed in 2.0.0; test: secfix_ |
| CR-028 | Medium | Licence check: with the install record deleted, the grace period was counted from a date in the site's own database, which its owner can change | Review of 4 Oct, licence: F3 | 1 | Fixed | Fixed in 2.0.0; test: secfix_ |
| CR-029 | Medium | Licence check and licence service: anyone could make a site ask for licence checks until the service throttled its key, so the site got no fresh answer | Review of 4 Oct, licence: F4 | 1 | Fixed | Fixed in 2.0.0; test: secfix_ |
| CR-030 | Medium | Licence service: a site that had been quiet for 30 days lost its place to another site with no confirmation, which let two sites share one licence | Review of 4 Oct, licence: F5 | 1 | Fixed | Fixed in 2.0.0; test: licence service test-status.php (a quiet site keeps its licence; a place is freed only by the emailed release or by us) |
| CR-031 | Medium | Licence service: the standby signing key was kept on the server beside the active one, in a hosting account that also runs other sites of ours | Review of 4 Oct, licence: F6 | 1 | Accepted | The standby key's secret was taken off the server on 8 Oct 2026 and is now kept off it. The active key has to stay online to sign licence answers, and still shares that account. Whoever stole it could forge licence answers but, since 2.0.0, not an update: an update needs a second signature, made with a key that is never on the server (CR-039). |
| CR-032 | Low | Licence check: a site whose clock was set back ignored every newer signed licence answer | Review of 4 Oct, licence: F7 | 1 | Fixed | Fixed in 2.0.0; test: secfix_ |
| CR-033 | Low | Left from CR-032: a site that cannot reach the licence service has only its own clock to measure the licence deadline by | Review of 4 Oct, licence: F7, what the fix left | 1 | Accepted | Offline there is no other clock to trust. It takes control of the server itself, not of the application, and nothing a visitor or member can reach is affected. |
| CR-034 | Low | Licence service: a site states its own address, so the service cannot prove which server is asking | Review of 4 Oct, licence: F8 | 1 | Accepted | A self-declared address cannot be proved. A licence that checks in from two server addresses within a day is reported to us (licence service test-status.php). Nothing a visitor or member can reach is affected. |
| CR-035 | Low | Licence check: staff pages outside the admin panel (door check-in, staff file downloads) stayed open while sign-in was paused | Review of 4 Oct, licence: F9 | 1 | Fixed | Fixed in 2.0.0; test: secfix_LicencePathTest |
| CR-036 | Low | Licence service: a buyer's country was taken from the billing address alone; the card's country was not compared | Review of 4 Oct, licence: F10 | 1 | Fixed | Fixed in 2.0.0; test: licence service test-geo.php, test-webhook.php (a card from another country than the billing address holds the sale) |
| CR-037 | Low | Licence service: two of its self-tests deleted real audit entries written while they ran | Review of 4 Oct, licence: F11 | 1 | Fixed | Fixed in 2.0.0; test: licence service test-webhook.php, test-download.php (entries a buyer wrote meanwhile are kept) |
| CR-038 | Low | Licence service: our admin screen said that withdrawing a key does not affect a running site, which was no longer true | Review of 4 Oct, licence: F12 | 1 | Fixed | Fixed in 2.0.0; test: licence service test-status.php |
| CR-039 | High | Updater: an update was trusted on the licence service's online signing key alone, so whoever took over our server could have sent code to every site | Review of 4 Oct, updater: H1 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest (an offer without a valid offline release signature downloads nothing); licence service test-latest.php. What is left: CR-031 |
| CR-040 | Medium | Updater: a visitor's request arriving as an install finished could undo a good update | Review of 4 Oct, updater: M1 | 1 | Fixed | Fixed in 2.0.0; test: updfix_GuardTest (the guard takes the lock before it reads the plan) |
| CR-041 | Medium | Updater: an update replaced the whole .htaccess, dropping rules the site's owner or cPanel had added (password protection, IP blocks) | Review of 4 Oct, updater: M2 | 1 | Fixed | Fixed in 2.0.0; test: updfix_HtaccessTest (only the product's own marked block is replaced) |
| CR-042 | Low | Updater: the install step trusted its plan file and the staged files as found on disk | Review of 4 Oct, updater: L1 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest (the plan is signed, every path is checked again and staged files are hashed again at install) |
| CR-043 | Low | Updater: "already the same" was decided by size and CRC-32, so a file altered to match both would have been kept | Review of 4 Oct, updater: L2 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest (files are compared by SHA-256) |
| CR-044 | Low | Updater: scheduled tasks kept running on half-updated files during an update | Review of 4 Oct, updater: L3 | 1 | Fixed | Fixed in 2.0.0; test: updfix_GuardTest (scheduled commands are skipped during an update, and undo one that was cut off) |
| CR-045 | Low | Updater: the download was not capped at the signed size, and a redirect could fall back to http | Review of 4 Oct, updater: L4 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest |
| CR-046 | Low | Updater: where the host forbids clearing PHP's opcode cache, the wait for it was capped at 10 seconds | Review of 4 Oct, updater: L5 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest (the update is refused when the cache can be neither cleared nor waited for) |
| CR-047 | Low | Updater: the list of files an update must never replace was case-sensitive and did not allow for Windows file names | Review of 4 Oct, updater: L6 | 1 | Fixed | Fixed in 2.0.0; test: updfix_SecurityTest (names are judged in lower case and against a list of allowed folders) |
| CR-048 | Info | Updater: seven smaller points (a warning on a malformed token, the holding page's policy, the token cookie, cached copies of removed files, a missing guard file, a redirect loop, error text) | Review of 4 Oct, updater: I1 to I7 | 7 | Fixed | Fixed in 2.0.0; test: updfix_GuardTest (the first five), updfix_SecurityTest (the last two) |
| CR-049 | Medium | Email templates: staff with only the Communications area could rewrite the sign-in code, password-reset and other security emails | Review of 4 Oct, email templates: F1 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_AccessTest (security emails are changed by full administrators only) |
| CR-050 | Medium | Email templates: an edited email could put a reset link or sign-in code inside a link to another site, or hide it from the reader | Review of 4 Oct, email templates: F2 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_SaveRulesTest (such a text is refused when saved, and a stored one sends the default) |
| CR-051 | Low | Email templates: the check on link addresses could be passed by starting the address with a merge tag | Review of 4 Oct, email templates: F3 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_SaveRulesTest, mailsec_ |
| CR-052 | Low | Email templates: Send a test to me had no limit, and the audit log did not name the address | Review of 4 Oct, email templates: F4 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_AccessTest (10 an hour for each person; the audit log names the address) |
| CR-053 | Low | Email templates: the button tag, or a block of details, placed inside an attribute broke the email's markup | Review of 4 Oct, email templates: F5 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_SaveRulesTest, mailsec_ |
| CR-054 | Low | Email templates: a translation that wrote a tag in capitals sent the tag's name in place of the value (a sign-in code, for example) | Review of 4 Oct, email templates: F6 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_ |
| CR-055 | Low | Email templates: an edited email could carry an image from another site, which tells that site who opened it | Review of 4 Oct, email templates: F7 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_SaveRulesTest, mailsec_ |
| CR-056 | Low | Email templates: the update that moves email wording out of Settings deleted wording it had not copied | Review of 4 Oct, email templates: F8 | 1 | Fixed | Fixed in 2.0.0; test: mailsec_MigrationTest |
| CR-057 | Info | Email templates: three smaller points (members' sign-in links and codes written to the log while email is not set up, edit-page helpers callable from the browser, a text too long for its column) | Review of 4 Oct, email templates: I1 to I3 | 3 | Fixed | Fixed in 2.0.0; test: mailsec_ |
| CR-058 | Info | Left from CR-057: while email is not set up, staff sign-in codes and admin password-reset links are still written to the application log | Review of 4 Oct, email templates: I1, what the fix left | 1 | Accepted | By design: with email off, the log is the only way a full administrator can get back into the admin panel. The log is under storage/, which the install guide's layout keeps outside the web root. |
No findings from: composer audit --locked; Vendored JavaScript vs OSV.dev; Automated test suite (gate, one process per file).
Open findings are described without the steps to exploit them until they are fixed. The raw tool reports, with their requests and responses, are not published; these tables are made from them.
The limits
What these tests do not cover.
Read this list as carefully as the results above it.
- No external penetration test: every test here is in-house, run by us and by AI code reviewers (Claude) we direct. None is an independent audit.
- ZAP's spider runs no JavaScript, so the admin panel's actions (Livewire) are covered by the automated tests and the code reviews, not by ZAP.
- Payments were never tested with real money. Stripe was tested in its test mode on 9 Oct 2026, with Stripe's test cards: gifts once and monthly, an event ticket, a membership invoice, refunds, a declined card, an abandoned checkout, a renewal, a declined renewal and a cancellation in the product, and a purchase, a held purchase, a renewal and a cancellation in our own licence checkout. PayPal, yearly gifts and instalment charges on a saved card were tested only by the automated tests against stand-ins for the payment providers.
- Nginx and LiteSpeed configurations (docs/server) were not tested. ZAP and the header test ran against a local copy served by PHP's own web server, so the Apache .htaccess headers were checked by reading them.
- The encoded release build is not scanned; the same code before encoding is.
- The built-in updater was tested by the automated tests on code that is not encoded; it has not been run end to end on an encoded build or on a live site.
- Our licence service was reviewed in code and has its own tests; it was not scanned with ZAP.
- Code written after the last code review (4 Oct 2026) has automated tests but has not had a code review of its own. That includes the fixes for CR-011 to CR-018 and the Back up now and Download buttons on the backup page.
- Denial of service, social engineering, and your own host, PHP, TLS certificate and server are not tested.
On your own site
Check your own installation.
Three checks you can run yourself once AlumDeck is installed.
Check the headers your host really sends
In the admin, open Settings, Security, "Check my headers". It shows the security headers and the policy that really reach a browser on your host.
Audit the packages
On a computer with Composer, run
composer audit --lockedin the AlumDeck folder.composer.lockships in the zip, so the command lists any advisory published since this release.Scan only what is yours
If your IT team runs a web vulnerability scanner, point it only at your own installation, never at alumdeck.com or at anyone else's site.
Advisories
Advisories for released versions.
A security problem found in a release that people already run is announced here, with its fix.
No advisory has been issued for a shipped release.
AlumDeck is built on other people's software too. Before the release, the published advisories for it were checked against the exact versions that ship:
| Check | Date | Checked | Advisories that apply |
|---|---|---|---|
| composer audit --locked | 8 October 2026 | 151 packages | 0 |
| Vendored JavaScript vs OSV.dev | 8 October 2026 | 3 libraries | 0 |
No advisory was reviewed and kept: the list for that, which ships with the release, is empty.
Earlier releases
The releases before this one.
This is the first release whose tests are published.
For your IT reviewer
Where to go from here.
Security and privacy, for your IT review
A security summary, answers in the style of a HECVAT Lite, and a data processing agreement template.
Security review
One of the code reviews in full: how it was done, each finding, and what was fixed. It is not a penetration test.
Report a security problem
One route, the contact form, and what we promise to do with a report. Good-faith research is welcome.
Questions
About these tests.
Has AlumDeck had an independent penetration test?
No. No independent penetration test has been done. Every test on this page is in-house: Cerevonix ran the tools itself, and the code reviews were done by AI reviewers (Claude) that Cerevonix directs.
That is why the results are published in full, beside what the tests do not cover, instead of a one-line claim.
Who did the code reviews?
AI reviewers (Claude), directed by Cerevonix. Cerevonix decides what is reviewed, and each finding is checked against the code before it is fixed or accepted.
For this release: 8 reviews, 25 Sep to 4 Oct 2026. Whole codebase (25 and 26 Sep); the changes up to 27 Sep; the changes up to 3 Oct (two reviews); and three reviews on 4 Oct: the changes up to 4 Oct together with our licence service, the built-in updater, and the email templates. Sign-in, sessions, authorisation, payments, uploads, SSRF, XSS, SQL, exports, the licence check, update packages and email templates.
Is it safe to publish findings that are still open?
Open findings are described without the steps to exploit them until they are fixed, and the raw tool reports, with their requests and responses, are not published.
A release cannot be packaged while a finding rated Critical or High is open: the packaging step reads this data file and refuses. No finding is open in this release.
Can our IT team see the raw tool reports?
The raw reports are not published. What you get is the data file this page is built from, in the zip, with a readable guide made from it.
For a questionnaire, the guide for IT reviews has a security summary and answers in the style of a HECVAT Lite, and the security review walks through one code review, finding by finding.
Is every release tested like this?
The step that builds a release zip refuses to run unless this data file is for that exact release, every finding has been triaged and explained, and no finding rated Critical or High is open. It runs the dependency audit again as it packs.
So each release carries its own results, and the data file keeps a summary of the releases before it, shown under earlier releases.
We found a problem these tests missed. How do we report it?
Through the contact form, with the topic set to "Security report". The disclosure policy says what we do next. Good-faith research is welcome; please test only an installation of your own.
The data file
Match this page with your zip.
This page is built from one data file, and so is the Security tests guide inside the zip. The SHA-256 checksum of the file this page was built from:
4428e7f9e948c9bdfa2f83f9c26e6edaef762caaafb25a73bd10fab035fb741e docs/security-tests.jsonThe file ships in the AlumDeck 2.0.0 zip as docs/security-tests.json. Work out the checksum of your copy; if it matches, this page and your zip hold the same results.
Windows (PowerShell): Get-FileHash .\docs\security-tests.json -Algorithm SHA256
macOS: shasum -a 256 docs/security-tests.json
Linux: sha256sum docs/security-tests.jsonOpen the live demo. It puts itself back every night.
Sign in as an admin or a member and click anything. Nothing you do can break it.
Sign-in details on the demo pageResets every night