Acting on an external review
In September 2026 an external review of SimplifiedCS's code and live site raised issues in four areas. This page sets out what it found, what was changed, and how each change was checked. The changes went live on 24 September 2026. No usage figures or business outcomes are claimed.
Starting point
SimplifiedCS is a static single-page site (plain JavaScript, bundled with esbuild) hosted on Netlify. Two server functions call the Anthropic API for the optional AI insights, and scheduled Supabase functions fill the public News and Exploit feeds. The review covered the code and the live site.
Security
| Found | Changed | Checked by |
|---|---|---|
| News and exploit items are third-party text; one field was rendered through a hand-written HTML allowlist. | Text is decoded then escaped when it's stored and escaped again when it's shown; the one rich field goes through DOMPurify, with links limited to http(s). | Fixtures with encoded tags, event attributes and javascript: links, run from ingestion to stored row to page (test/feed-ingestion.test.js, test/html-safety.test.js). |
| The AI endpoints' rate limit counted by listing keys, which races, and let requests through when its store failed. | Admission uses Netlify Blobs conditional writes: per-visitor windows, a site-wide daily request and token budget, bounded concurrency and a duplicate-request lock. If the store fails, requests are refused. | Concurrency tests against an in-memory compare-and-set store; four of them fail against the old approach (test/ai-admission.test.js). |
| The scheduled feed jobs could be called with the public key, by any HTTP method, with nothing stopping overlapping runs. | POST only, a private scheduler secret compared in constant time, and a database lease so runs can't overlap or repeat too soon. | test/scheduled-jobs.test.js; in production, a request without the secret gets 401 and a GET gets 405. |
| The browser-facing database roles held default privileges beyond reading (including TRUNCATE), and the migrations weren't in the repository. | The five applied migrations committed as they were, plus a least-privilege grants migration. | Permission tests on an in-process Postgres (PGlite) that fail without the migration (test/supabase-policies.test.js); in production, a write with the public key is refused. |
| "Other" answers were sent to the AI automatically, there was no privacy page, and analytics loaded for everyone. | Nothing AI-related runs without an explicit, versioned agreement that shows exactly what's sent; a privacy page; Google Analytics loads only after "Allow". | The server refuses requests without the agreement (test/ai-endpoints.test.js); no Google script before consent (test/privacy.test.js); both checked again on the live site. |
Honest scoring
The review showed that a cloud-only organization with no multi-factor authentication, and every other answer at its best, scored 99% "Strong health", and that weak DevSecOps, secrets and container answers scored 100% with no gaps. Methodology 2.0 separates "Not sure" from "No", scores the setup answers that describe controls, lets critical gaps decide the reading before any percentage, ignores answers to questions that no longer apply, and ranks every finding with visible reasons. Each failure the review reproduced is now a regression test.
AI output tied to evidence
The AI response used to accept whatever links and claims the model returned. Now the server retrieves the public records itself (CISA's Known Exploited Vulnerabilities catalog and NIST's National Vulnerability Database), gives each one an ID, and the model may only cite those IDs; anything uncited, or cited against the wrong product, is dropped. Each product gets a status per source - "checked, no match", "source unavailable", "not checked, lookup limit reached" - so an outage can't look like a clean result. Tests cover prompt-like answer text, source outages, cross-product citations, cut-off and refused model replies, and valid empty results, all with fictional CVE IDs and a simulated model.
Product quality
- Quick mode needed about 52 answers; it's now a 14-answer screening, with a test that walks every path through it.
- Accessibility: answers are native radio buttons and checkboxes in labelled groups, and keyboard focus is kept when the page redraws. An automated audit (axe-core, WCAG 2.2 A and AA) runs on every page and assessment screen in both themes and now passes; it found text colours in the light theme that were too pale, since fixed.
- Framework references: section labels used NIST CSF 1.1 codes in a "CSF 2.0" interface. Every framework reference is now checked against the official identifier lists, and every MITRE ATT&CK technique against ATT&CK v19.2 - which caught one deprecated technique.
- PDF reports are built from the same report model as the page. Rendering them to images showed bold headings running past the right margin; a test now checks every line against both margins using the same font measurements.
- Speed and upkeep: the initial script went from 3.0 MB unminified to 543 KB minified, with the PDF generator and database client loaded only when needed. The 4,400-line main script now has one module per page. Every change runs the tests, checks the published build matches the source, and audits production dependencies.
Verified in production (24 September 2026)
| Area | Result |
|---|---|
| Database | Browser-facing roles can only read the three public feed tables; a write with the public key returns 401. The job-lease functions can only be run by the server role. |
| Feed jobs | Redeployed. Without the scheduler secret: 401. Manual runs refreshed 79 news items and 102 exploit records with no errors, and both leases were released. |
| Site | Security headers enforced (Content-Security-Policy, HSTS, frame blocking); built files cached for a year because their names change with their content. |
| AI endpoints | An empty request gets 400 (agreement required) and no model call. One authorized test with the fictional IT-services example answers returned in 22 seconds: a source status for each of the 6 named products, 4 possible vulnerabilities each citing a retrieved CISA KEV or NVD record, and nothing dropped by validation. |
| In the browser | No console errors; before consent, no request goes to Google Analytics; the News page loads 75 items; a Quick screening runs end to end and keeps the AI button disabled until the visitor agrees. Test data was cleared afterwards. |
Before release, the automated test suite had grown from 106 to 199 tests, all passing, and browser checks had run against a local server that applies the production security headers.
Not verified yet
- Screen-reader testing hasn't been done. Keyboard use was tested with real key presses, and an automated audit covers every page; the manual checklist is docs/screen-reader-check.md.
- The AI panel in the live page wasn't used for a real request - the one paid test called the endpoint directly. The panel's rendering is covered by local tests with the same response shape.
- Hosting settings: the Anthropic API key can't be limited to server functions on the site's current Netlify plan. It isn't used by any build step and never reaches the browser.
- Product versions aren't asked for, so vulnerability matches are always "potential" and ask the reader to confirm their version.
The full finding-by-finding record, with file references, is in docs/review-remediation-status.md.