Security, GOV.UK and usability review
10 October 2026: dependencies, forms, headers, sign-in limits, GDS-Check and the GOV.UK checklist.
This review covers the service after it was made general: nothing in it is specific to one app (HMRC's Budget Payment Plan service is an ordinary organisation), payment plan amounts and the tax so far this year are capabilities any organisation can switch on for each way of use, and pages in the middle of a flow only open in order.
It is a review by the development team with automated tools. It does not replace the specialist accessibility audit, the penetration test or the data protection sign-off listed in the README under "Still to do before going live".
Summary
| Area | Result |
|---|---|
| Security | No known vulnerabilities in the dependencies. 6 issues found and fixed (below). Security tests run on every page and form |
| Pages only in order | No page in the middle of the calculator, payment plan, tax so far, account or admin flows opens without going through the pages before it. Tested locally and on the live site |
| GOV.UK and accessibility (GDS-Check, WCAG 2.2 AA) | No proven or likely findings on any question page. Every journey ran to the end |
| Usability (GOV.UK Service Manual, Design System and HMRC patterns) | All 21 checks pass, after 4 small fixes: a back link, two shorter sentences, and the email address filled in on the sign-in page after it is confirmed |
| Live site | 61 of 61 checks pass, including the new options. One warning: the site has no database |
Security
What was checked
| Check | How | Result |
|---|---|---|
| Known vulnerabilities in dependencies | pip-audit -r requirements.txt |
cryptography was affected by PYSEC-2026-3552 and is now 50.0.0. No known vulnerabilities remain |
| Secrets in the repository | Searched what git tracks | None. Local settings are in .env.local and the throwaway GDS-Check account journey is not committed (both gitignored) |
| CSRF | tests/test_security.py posts to every form route without a token |
Every form refuses it |
| Security headers and cookies | tests/test_security.py on every page |
Content Security Policy with a nonce for each request, X-Frame-Options: DENY; the session cookie is HttpOnly, SameSite and Secure (except when run locally over http) |
| Open redirects | Apps' return addresses and the language switch tried with other sites | All refused. The language switch also accepted /\host, which some browsers treat as another site: fixed |
| Escaping | Every \| safe in the templates reviewed; request text sent back to pages |
The dummy Notify inbox showed request text unescaped: fixed. Banners now take plain text and escape it |
| Authentication | Admin API, organisations' API, admin sign-in, customer accounts | Keys and tokens required. Admin sign-in had no limit on wrong passwords; it now locks after 5. Customer accounts already locked |
| Rate limits | Public API, organisations' API | Organisations had limits and quotas; the public API had none. It now has a limit for each address |
| Organisations kept apart | Journeys J25, J39 and J58 | One organisation cannot see another's settings, results or administration. A journey link opened in a second browser is refused, and a result can only be collected by the organisation that started it |
| Capabilities | tests/test_security.py |
A capability cannot be switched on for a way of use the organisation has closed |
| Notifications to apps | Journey J62 and the live check | Signed, and the dummy payroll app checks the signature |
| Pages only in order | tests/test_flows.py, the live check |
No page in the middle of a flow opens directly; posting to a later page does not skip ahead |
| Personal data in logs | Every log call in app/ |
The service logs page paths, task names, error types and notification attempts, not answers or personal details. Unexpected errors are logged with their trace, which could include a value from a request: worth checking when log storage is chosen |
Recommendations
- Switch off the API documentation page on a real service.
/docs(Swagger UI) and/openapi.jsonare on by default so developers can try the API on the test site. SetENABLE_API_DOCS=falseand publish the documentation on HMRC's Developer Hub instead. - Link the database on the live site. The live site has no
DATABASE_URL, so accounts and settings are lost each time Render restarts it. Linktaxcalc-dbto the web service in Render. - The penetration test and data protection sign-off are still needed before real customers use it.
GOV.UK and accessibility: GDS-Check
Every screen was scanned with GDS-Check (WCAG 2.2 AA and the GOV.UK Design System), each scan starting its own local server: the public pages (30 pages), the service overview (25), the admin pages (20) and 11 journeys through the calculator, the payment plan pages, the tax so far at Example Accountants, customer accounts, document reading, organisation settings and the dummy payroll app.
- No proven or likely findings on any question page. The only proven findings are on
/docs(61), which is Swagger UI, not a GOV.UK page. - Four journeys stopped part way on the first run because their files had fallen behind the pages (the offer to fill in answers from HMRC records, the residence question, GOV.UK One Login on the account page, and confirming the email address before signing in). The journey generator now walks the pages with the same settings as the local server, and the four were run again to the end.
- The account journey found that after confirming their email address, people had to type it again to sign in (WCAG 3.3.7, redundant entry). The sign-in page reached from the confirmation link now has it filled in.
Left for review, with reasons:
| Finding | Why it is left |
|---|---|
| Only one way to find each page (WCAG 2.4.5) | Pages in a transaction are steps in a process, which 2.4.5 excludes. The service is reached through GOV.UK navigation and search |
| Reading age of the service overview, admin pages and accessibility statement | Written for people assessing or running the service. The accessibility statement follows the GOV.UK model wording |
| Help links in a different order on the account pages (WCAG 3.2.6) | The footer is the same on every page. The scanner counts links to the privacy notice and terms in the account pages' text |
| "Key" used as an adjective | It means an API key |
| No back link on sign-in, result and account home pages | These are not question pages: there is no earlier answer to go back to |
| The email address asked for again when signing in later | Signing in after signing out is a new process, and the browser can fill it in (autocomplete="email") |
| No check answers step in the document reading journey | That scan stops at the sample documents page on purpose; the calculator's check answers page is covered by the other journeys |
| Pages of the dummy services | They stand in for HMRC, GOV.UK One Login, Notify and an app, and are not part of the service |
Usability: GOV.UK and HMRC checklist
| Check | Result |
|---|---|
| Start page with a "Start now" button and what you need before you start | Pass |
| One thing per page | Pass |
Page title is the heading, the service name and GOV.UK; Error: in front when there is an error |
Pass, on every question page of the calculator, payment plan and tax so far flows (new tests/test_govuk_patterns.py) |
| One h1 on each page | Pass (same test) |
| Back link on every question page | Pass. The take-home pay page had none: fixed |
| Error summary at the top linking to each field, and a message by the field | Pass (same test) |
| Error messages say what to do ("Enter…", "Select…") | Pass |
| Optional questions say "(optional)" | Pass |
| Nothing already given is asked for again in the same process (WCAG 3.3.7) | Pass. The email address was asked for again after confirming it: fixed |
| Check your answers with Change links, and hidden text for screen readers | Pass |
| A final button cannot be pressed twice | Pass |
| Session timeout warning, and a page saying the answers were deleted | Pass (HMRC timeout dialog) |
| Pages only open in order | Pass |
| Works without JavaScript | Pass |
| No "please" | Pass (tested on every question page) |
| Sentences of 25 words or fewer on customer pages | Two were longer, on the tax so far start page and the privacy notice: split |
Skip link, lang="en" |
Pass |
| Footer: cookies, accessibility statement, privacy, terms, help, contact, Welsh services, licence and copyright | Pass |
| Cookies | Pass. Only essential cookies, so no cookie banner is needed; the cookies page lists them |
| Phase banner with a feedback link | Pass (Beta). The test site shows a "Test site" banner instead |
| "Is this page not working properly?" link | Pass |
| Page not found and service error pages in GOV.UK style | Pass |
Still outstanding (in the README): Welsh translations from HMRC's Welsh Language Unit, the specialist accessibility audit and research with users, including assistive technology users.
Live site
scripts/live_check.py now also checks pages only open in order, a payment plan amount and the tax so far (on Example
Accountants' pages, inside Example Payroll App, and through Example Tax API's API for a fixed date, where it must be
£1,760.22 exactly). On 10 October 2026 all 61 checks passed on https://uk-tax-calculator-5844.onrender.com. The one
warning was that the site has no database.