Policy · v1.17

Data Retention Policy

Last updated 2026-08-26 · Effective 2026-08-09 · Applies to tool version 1.166.0 and newer · This document is part of the open-source project source code and is version-controlled at apps/web/app/pages/data-retention.vue.

This policy describes how the ICJIA File Accessibility Audit tool ingests, processes, retains, and deletes uploaded files — PDF, Word (.docx), PowerPoint (.pptx), and Excel (.xlsx) documents — and related metadata. It is intended for managers, records-retention officers, accessibility auditors, legal counsel, and other stakeholders who need a complete and accurate account of the tool's data handling. Technical details are included verbatim — vague language has been avoided in favor of precision.

No AI is used.

Your file is never sent to ChatGPT, Claude, Gemini, Copilot, or any other artificial-intelligence service — from any provider, in any version. No machine-learning model is loaded on this server. The tool uses rule-based, deterministic, open-source software exclusively. See § 4 below for the complete exclusion list.

Key facts at a glance

0

Files retained after processing

≤30 min

Maximum remediation-output retention

0

AI services contacted

0

Third-party data transmissions

100%

Deletion verified via fs.stat

7 yr

Remediation audit-trail retention (configurable)

100%

Open-source toolchain

6

Open-source tools (qpdf · pdfjs · JSZip · fast-xml-parser · ODL · veraPDF)

Contents

  1. 1. Scope & applicable systems
  2. 2. Audit pipeline — how files are handled when you click "Audit"
  3. 3. Remediation pipeline — how PDFs are handled when you click "Remediate"
  4. 4. AI usage statement (none)
  5. 5. The open-source toolchain (qpdf · pdfjs · JSZip · fast-xml-parser · OpenDataLoader · veraPDF)
  6. 6. Lifecycle audit trail (the auditor's evidence)
  7. 7. Retention periods by data category
  8. 7a. Why anything is backed up when documents aren't stored
  9. 8. What is and isn't stored
  10. 8a. Storage verification — the evidence for § 8 (re-verified 2026-08-09)
  11. 9. Security & technical safeguards
  12. 10. Security audit history (red/blue team reviews)
  13. 11. Right to inspect & verify
  14. 12. Standards & compliance alignment
  15. 13. Glossary of technical terms
  16. 14. Change log for this policy
  17. 15. Contact & questions

1. Scope & applicable systems

This policy applies to all files — PDF, Word (.docx), PowerPoint (.pptx), and Excel (.xlsx) — processed by the ICJIA File Accessibility Audit tool — both the public production deployment at https://audit.icjia.app and any derivative deployment running the same source code. The infrastructure is hosted on DigitalOcean (a U.S.-based cloud provider), managed via Laravel Forge, and runs on a single virtual private server (VPS) located in a DigitalOcean data center. No content is replicated to external storage, content delivery networks, or backup services.

Two distinct processing pipelines exist within the tool:

  • The audit pipeline (always available) — analyzes an uploaded document (PDF, Word, PowerPoint, or Excel) for WCAG 2.1 AA / ADA Title II / Illinois IITAA 2.1 accessibility conformance signals and returns a score and findings.
  • The remediation pipeline (optional, gated by the server-side REMEDIATION_ENABLED environment flag) — produces a tagged, more-accessible version of the uploaded PDF.

Both pipelines are described separately below because their data lifecycle differs. The audit pipeline operates entirely in memory; the remediation pipeline requires brief on-disk storage during processing, which is described in detail with corresponding deletion verification.

2. Audit pipeline (always available)

When a user uploads a file — PDF, Word, PowerPoint, or Excel — for auditing, it is processed entirely in volatile server memory. No copy is written to disk at any point during the audit pipeline, regardless of format.

Client → HTTPS upload (multipart/form-data)
  │
  ▼
[multer.memoryStorage()] — buffer in API process memory
  │
  ▼
[validate file]
  - Content-based type check: PDF ('%PDF-' signature) or a ZIP package
    confirmed as Word / PowerPoint / Excel (OOXML) — never the filename
    or declared MIME type
  - File size limit: 25 MB (configurable; rejected if exceeded)
  │
  ▼
[analyzeDocument(buffer, filename)] — detects format, dispatches:
  ├── PDF → analyzePDF(), on the main API process
  │     • qpdf subprocess: structure tree, language, outlines, tables
  │     • pdfjs (Node.js library): text, metadata, page order
  │
  └── Word / PowerPoint / Excel → a dedicated, short-lived child
        Node.js process (buffer handed over a local, in-memory channel;
        killed if analysis runs past its timeout)
        • JSZip: unzips the OOXML container
        • fast-xml-parser: parses the XML parts
  │
  ▼
[scorer] — WCAG-aligned categories, weighted overall score
  │
  ▼
HTTP response → client (typically < 10 seconds total)
  │
  ▼
Node.js garbage collector reclaims the buffer
(file no longer exists in any form, anywhere)
Audit pipeline — visual flow
Audit pipeline — visual flow

Flowchart of the audit pipeline. The uploaded file — PDF, Word, PowerPoint, or Excel — is held in memory and validated by its content, not its filename. A PDF is analyzed by qpdf (via a short-lived temp copy, deleted in the same request) and by pdfjs reading the buffer directly; a Word, PowerPoint, or Excel file is unzipped and parsed by JSZip and fast-xml-parser inside a dedicated, short-lived child process with no disk access. Results are scored across WCAG-aligned categories, and the memory buffer is discarded after the response is sent.

Once the HTTP response has been sent, the in-memory buffer is unreferenced and garbage-collected by the Node.js runtime in the next collection cycle. For a PDF, the qpdf analyzer (a command-line tool that needs a file path) works from a short-lived, randomly named temp copy that is deleted within the same request, even when analysis fails. When veraPDF (the PDF/UA validator) is configured, a PDF audit also writes its own short-lived, randomly named temp copy — separate from qpdf's, but following the same pattern and lifecycle — so veraPDF can produce the PDF/UA machine-check verdict (PDF/UA-1, or PDF/UA-2 when the document declares it), and that copy is likewise deleted within the same request, even when the check fails. For a Word, PowerPoint, or Excel file, analysis runs inside a dedicated child Node.js process — spawned fresh for that request and terminated immediately afterward — which unzips and parses the in-memory buffer directly with JSZip and fast-xml-parser (see § 5); no temporary file is ever created for these formats. Since v1.100.0 the web page usually drives an audit through a progress variant of this same pipeline (it shows per-step status while you wait); the one retention difference is that the finished report — the same result the synchronous path returns, never the file — waits in server process memory until the page collects it, for at most 10 minutes, and delivery removes it immediately. The uploaded file's own lifetime is unchanged. In every case, the uploaded content does not persist on disk, in a cache, in a log file, or in any other location. The only thing a browser-upload audit produces is metadata in the audit_log table — described in § 8 — data about the file, never the file, and nothing about the caller (event type, filename, score, grade, timestamp, and SHA-256 hash of the file's bytes; the schema has no email, IP-address, or browser column — tool v1.68.0). Since tool v1.46.0 a refused upload also writes one row: the file name it was offered under (sanitized) and a timestamp, with no content hash, score, or grade — the file is never accepted, so there is nothing to hash or score.

Encrypted PDFs are rejected. A password-protected PDF cannot be analyzed without the password; the tool returns a clear error before any analysis is attempted, and the file is discarded immediately. The same applies to formats the tool recognizes but cannot audit: legacy binary Office files (.doc, .xls, .ppt, .rtf) and CSV/TSV exports are refused with a specific explanation — detected from the file's content, so a renamed file gets the right message — and the file is never accepted.

3. Remediation pipeline (optional, gated)

The remediation pipeline is disabled by default. It can be enabled by setting REMEDIATION_ENABLED=true in the server's environment. When enabled, a new Attempt remediation action appears on the audit results page. Clicking it triggers the following lifecycle:

Client → HTTPS multipart upload (re-upload required by design)
  │
  ▼
[validate] magic bytes, size cap, page count cap (500 pages)
  │
  ▼
[create remediation_jobs row]
  • status: 'pending'
  • content_hash: SHA-256 of input bytes
  • download_token: 32-byte random, sha256-hashed at rest
  │
  ▼
[write input → data/remediation/<jobId>/work/input.pdf] (mode 0600)
  │
  ▼
[spawn detached worker: tsx src/jobs/remediate.ts <jobId>]
  │
  ▼
(API responds 202 to client; worker runs independently)
  │
[Stage 1: preparing]
  • qpdf --object-streams=disable input.pdf → normalized.pdf
  • DELETE input.pdf + fs.stat verify ENOENT
  • Emit lifecycle events: 'normalize_complete', 'input_deleted',
    'verified_absent'
  │
  ▼
[Stage 2: tagging]
  • OpenDataLoader convert(normalized.pdf) → tagged.pdf
  • DELETE normalized.pdf + fs.stat verify ENOENT
  • Emit events: 'tagging_complete', 'intermediate_deleted',
    'verified_absent'
  │
  ▼
[Stage 3: validating]
  • qpdf --check tagged.pdf → must not report warnings
  • veraPDF --flavour ua1 tagged.pdf → conformance verdict (informational)
  • Emit: 'validation_passed' OR 'validation_failed'
    + 'verapdf_passed' / 'verapdf_failed' / 'verapdf_error' / 'verapdf_unavailable'
  │
  ▼
[Stage 4: comparing]
  • Re-audit tagged.pdf → output score
  • If Overall OR Strict score regresses: REJECT
  │
  ▼
(success branch)
[Move tagged.pdf → data/remediation/<jobId>.pdf (final, mode 0600)]
[update job: status='complete', expires_at = NOW + 30 min]
[Emit: 'output_ready']
  │
  ▼
Client polls /api/remediate/<jobId>/status?token=…; sees 'complete'
  │
  ▼
Client downloads via single-use token:
  [stream output via createReadStream + pipe(res)]
  → on response 'close': DELETE output.pdf + fs.stat verify ENOENT
  → Emit: 'downloaded', 'output_deleted', 'verified_absent'
  → job status → 'expired' (token invalidated; concurrent requests get 410)
  │
  ▼
(or, if no download in 30 minutes)
[Cleanup sweep deletes output.pdf + fs.stat verify ENOENT]
[Emit: 'expired', 'output_deleted', 'verified_absent']

ALL OUTCOMES → final state: zero PDF artifacts on disk.
Remediation pipeline — visual flow
Remediation pipeline — visual flow

Flowchart of the remediation pipeline. Eleven steps from upload through final delete + verify. Every intermediate file is deleted before the next stage starts, and every delete is fs.stat-verified.

Key invariants of the remediation pipeline:

  • At any instant during the pipeline, at most one copy of the PDF exists on disk. The input is deleted before the normalized intermediate is written for downstream stages; the normalized intermediate is deleted before the tagged output is finalized; the tagged output is deleted on first download or after 30 minutes.
  • The entire scratch directory (data/remediation/<jobId>/work/) is removed in a finally block regardless of pipeline outcome — including crashes, errors, and rejected outputs. A worker crash mid-pipeline triggers cleanup on API restart (see § 9).
  • The remediated output is served via a one-time download token. The token is generated as 32 cryptographically random bytes and stored on the job row only as its SHA-256 hash (the raw token is never stored). A successful download invalidates the token immediately, before the file contents are streamed, so any concurrent or repeat request receives 410 Gone.
  • The remediation pipeline does not cache the PDF between audit and remediation. Clicking "Remediate" triggers a fresh multipart upload — the just-audited buffer is not preserved. This is a deliberate UX-vs-privacy trade-off that costs the user one extra upload click in exchange for a stricter retention posture.
  • The pipeline runs entirely on the ICJIA-controlled server. No PDF content is transmitted to external services, cloud APIs, or AI models — see § 4.

4. AI usage statement

No artificial intelligence is used in this tool.

Specifically: no file content, no extracted text, no metadata, no filenames, no derivative artifacts, no diagnostic data, and no telemetry of any kind are transmitted to any artificial-intelligence service, large language model, vision model, or hosted machine learning API, including but not limited to:

  • OpenAI (ChatGPT and the GPT model family, embedding APIs)
  • Anthropic (the Claude model family)
  • Google (the Gemini model family, Vertex AI)
  • Microsoft (Copilot, Azure OpenAI)
  • Meta (the Llama model family, hosted endpoints)
  • Amazon (Bedrock, SageMaker hosted endpoints)
  • Any open-source model hosted by a third-party inference provider (Replicate, Modal, Hugging Face Inference API, etc.)
  • Self-hosted machine-learning models on this server (none are loaded)

Providers and model families are named deliberately, without version numbers — specific model versions change too quickly for a policy document to chase, and the exclusion covers every past, current, and future version from every provider, listed here or not.

The auto-remediation pipeline uses three open-source software tools (qpdf, OpenDataLoader PDF, veraPDF — see § 5), all of which operate on rule-based, deterministic algorithms. None of these tools load or run a machine-learning model at runtime. Their source code is publicly available and auditable.

The audit pipeline — which covers all four supported formats — is built the same way: qpdf and pdfjs-dist read a PDF; JSZip and fast-xml-parser read a Word, PowerPoint, or Excel file (see § 5). Every one of these libraries is rule-based and deterministic; none of them loads or runs a machine-learning model, and none of them make an outbound network call.

A future feature on the project roadmap (Phase 1, documented at docs/archive/pdf-remediation-alt-text-walkthrough-spec.md) adds an interactive walkthrough that lets users manually author alt-text for figures in their remediated PDFs. This feature is specifically designed to be AI-free — the user types descriptions themselves, and the descriptions are written back into the PDF by the deterministic pdf-lib library. No AI suggestion, no autocomplete from a model, no inference of any kind.

Any future addition of AI features will be announced in this policy and in the public changelog before the feature is enabled in production, with a corresponding update to the policy version above.

No AI services contacted
No AI services contacted

Flowchart showing the ICJIA server talks only to local tools (qpdf, OpenDataLoader, veraPDF, SQLite). It NEVER sends data to ChatGPT, Claude, Gemini, or Copilot.

5. The open-source toolchain

Every tool involved in processing an uploaded document — PDF, Word, PowerPoint, or Excel — is open source, license-clear, and runs locally on the ICJIA-controlled server. No commercial PDF or Office-document SDK is licensed, and no per-document fees are paid. The tools are:

The open-source toolchain: each tool, what it's used for, its license, and source
ToolUsed forLicenseSource
qpdf PDF structure parsing (audit) and PDF normalization + validity checking (remediation) Apache 2.0qpdf.sourceforge.io
pdfjs-dist PDF text and metadata extraction (audit pipeline only) Apache 2.0github.com/mozilla/pdf.js
JSZip Unzips the Word / PowerPoint / Excel (OOXML) container (audit pipeline only) MIT OR GPL-3.0github.com/Stuk/jszip
fast-xml-parser Parses the XML parts inside a Word / PowerPoint / Excel file (audit pipeline only) MITgithub.com/NaturalIntelligence/fast-xml-parser
OpenDataLoader PDF Rule-based PDF auto-tagging (remediation pipeline only) Apache 2.0opendataloader-project (ICJIA fork: ICJIA/opendataloader-pdf)
veraPDF PDF/UA conformance validation — PDF/UA-1 (ISO 14289-1), or PDF/UA-2 (ISO 14289-2) when a document declares it (every PDF audit since v1.37.0, plus the remediation pipeline; optional) — and, since v1.97.0, a second pass per PDF audit against its machine-testable WCAG 2.2 validation profile (a repository-vendored rule file; same engine, same temp-copy lifecycle) MPL 2.0verapdf.org

Each tool is invoked as a separate operating-system process (qpdf, OpenDataLoader, veraPDF), as a Node.js library running on the main API process (pdfjs-dist), or as a Node.js library running inside a dedicated, short-lived child process spawned for that one request (JSZip and fast-xml-parser, for Word, PowerPoint, and Excel). That child process receives the file buffer over a local, in-memory channel — never a temporary file — and is terminated if it exceeds its analysis timeout. None of these tools opens an outbound network connection during processing. During a remediation job the API process makes no outbound network connections at all — the only data store is the local SQLite database (no network), and since tool v1.68.0 the service sends no email either: the sign-in system, the one thing that ever emailed anyone, was removed along with the mail-sending code.

6. Lifecycle audit trail

Every remediation job produces an append-only series of timestamped events in the server's SQLite database file (apps/api/data/audit.db, table remediation_events). The same database also holds the lighter-weight audit log (audit_log table) for plain audit requests — its schema is shown in § 8a. The two remediation tables:

CREATE TABLE remediation_events (
  id          INTEGER PRIMARY KEY AUTOINCREMENT,
  job_id      TEXT NOT NULL,
  event       TEXT NOT NULL,
  occurred_at INTEGER NOT NULL,   -- milliseconds since Unix epoch
  details     TEXT,               -- JSON, content-free metadata only
  FOREIGN KEY (job_id) REFERENCES remediation_jobs(id)
);
CREATE INDEX idx_remediation_events_job   ON remediation_events(job_id, occurred_at);
CREATE INDEX idx_remediation_events_event ON remediation_events(event);

CREATE TABLE remediation_jobs (
  id                   TEXT PRIMARY KEY,   -- UUIDv4
  input_filename       TEXT NOT NULL,      -- sanitized
  original_filename    TEXT,               -- as offered, length-clamped only
  content_hash         TEXT,               -- SHA-256 of input bytes
  page_count           INTEGER,
  status               TEXT NOT NULL
    CHECK (status IN ('pending','running','complete','failed','expired')),
  step                 TEXT,
  progress_pct         INTEGER DEFAULT 0,
  input_score          REAL,               -- pre-flight audit score
  output_score         REAL,               -- post-remediation audit score
  output_valid         INTEGER,            -- 1 = qpdf --check passed
  output_path          TEXT,               -- absolute path, only while complete
  download_token_hash  TEXT,               -- SHA-256 of raw token
  failure_reason       TEXT,
  input_audit_json     TEXT,               -- full pre-flight report
  output_audit_json    TEXT,               -- full post-remediation report
  verapdf_available    INTEGER,
  verapdf_passed       INTEGER,
  verapdf_summary_json TEXT,
  created_at           INTEGER NOT NULL,
  completed_at         INTEGER,
  expires_at           INTEGER NOT NULL
);

The closed set of event types emitted per job is:

receivedprocessing_startednormalize_completeinput_deletedtagging_completeintermediate_deletedvalidation_passedvalidation_failedverapdf_passedverapdf_failedverapdf_errorverapdf_unavailableoutput_readydownloadedoutput_deletedverified_absentverify_failedexpirederror

The verified_absent event is the critical compliance signal. It is emitted only after the worker (or the cleanup sweep, or the download handler) calls fs.unlink() followed by fs.stat() on the deleted path, and receives an ENOENT (no-such-entity) response — definitively confirming the file no longer exists on the filesystem. If fs.stat() returns any other result (file still present, permission error, etc.), a verify_failed event is recorded instead, indicating a compliance anomaly that must be investigated.

File paths in event payloads are stored as SHA-256 hashes, not raw strings. This keeps the payload uniform-length, resistant to log-scraping, and ensures the audit trail cannot accidentally reveal directory structure or user identifiers via path strings.

A sample event payload (the details JSON for a verified_absent event):

{
  "path_hash": "a3f5e7d2c4b6a8e9f1c3d5b7a9e1c3d5b7a9e1c3d5b7a9e1c3d5b7a9e1c3d5b7"
}

The audit trail is intentionally append-only: no application code path overwrites or deletes individual event rows. Rows are purged only by the periodic cleanup sweep after they exceed the retention period (see § 7), which executes a single DELETE statement bounded by an age cutoff. Anomalies — for example, a job that completed without a corresponding verified_absent event — are visible to any auditor running a sentinel query.

7. Retention periods by data category

Three groups, by what the data is: the document you upload (held seconds, never kept), the service's own records about audits (metadata only, kept on the schedules below), and systems adjacent to this application (the host's web server and ICJIA's self-hosted page-view counter).

The document itself — held seconds to minutes, never kept

The uploaded document and its remediation intermediates: where each briefly exists, maximum retention, and whether it's configurable
Data categoryWhere storedMaximum retentionConfigurable
Uploaded document (audit) — PDF, Word, PowerPoint, or Excel Server memory only (a PDF additionally uses a short-lived qpdf temp copy, deleted same request; Word/PowerPoint/Excel analysis never touches disk) SecondsDiscarded after the HTTP responseNo
Uploaded PDF (remediation input)data/remediation/<jobId>/work/input.pdfSecondsDeleted after qpdf normalize stageNo
Normalized intermediate PDFdata/remediation/<jobId>/work/normalized.pdfSecondsDeleted after OpenDataLoader tag stageNo
Remediated tagged PDF (output)data/remediation/<jobId>.pdf≤ 30 minFirst download OR 30 minutes (whichever first) Yes — REMEDIATION.OUTPUT_TTL_MS

Application records — metadata only, never file content

The service's own records about audits and remediations: where each is stored, maximum retention, and whether it's configurable
Data categoryWhere storedMaximum retentionConfigurable
Remediation job row (metadata only) SQLite, remediation_jobs table 30 daysAfter completion Yes — REMEDIATION.JOB_ROW_RETENTION_DAYS
Lifecycle events (audit trail) SQLite, remediation_events table 7 years(default) Yes — REMEDIATION.EVENT_LOG_RETENTION_DAYS
Usage log — audits, failed audits, and refused-upload attempts (no file content) SQLite, audit_log table365 days(default) Yes — SHARED_REPORTS.AUDIT_LOG_RETENTION_DAYS
Daily activity files — one CSV per calendar day (Central time), derived from the usage log and holding the same fields (no file content) On the same server, in logs/ at the application's root — beside the code, outside the web root, unreachable from the web; not part of the nightly backup 365 days— the usage log's window Yes — SHARED_REPORTS.AUDIT_LOG_RETENTION_DAYS (shared with the usage log; there is no separate setting)
Application error log — what the service writes to its own error output: a timestamp, the operation that failed, the error message and stack trace (no file content) On the same server, in logs/ at the application's root, one file per day; not part of the nightly backup; never served 30 days Yes — ACTIVITY_EXPORT.ERROR_LOG_RETENTION_DAYS
Nightly database backup snapshots (same metadata as the tables above — never any file content, because none is stored to begin with) On the same server, in a dedicated backups directory beside — but outside — the application, unreachable from the web 5 newestThe 5 newest snapshots; older ones deleted by rotation Yes — BACKUP_KEEP_COUNT (backup script environment variable, default 5 — not in audit.config.ts)
Shared reports (audit results only) SQLite, shared_reports table ≈395 daysLink stops working 365 days from share creation; the stored row is deleted by the cleanup sweep roughly 30 days after that (≈395 days total) Yes — SHARED_REPORTS.EXPIRY_DAYS (the link's 365 days) + SHARED_REPORTS.PURGE_GRACE_DAYS (the ≈30-day grace before the row is deleted)

Adjacent systems — outside this application

Systems adjacent to the application (the host web server and the self-hosted analytics counter): where each stores data, maximum retention, and whether it's configurable
Data categoryWhere storedMaximum retentionConfigurable
Host web-server access log (nginx — outside this application; IP address, timestamp, URL path, status, browser user-agent; see § 8a) /var/log/nginx/ on the server, managed by the host's logrotate — never readable from the web ≈52 daysRotated daily, 52 rotations kept (≈52 days)Yes — host logrotate config (not this application)
Page-view analytics (self-hosted Plausible — outside this application; per view: page URL, referrer, browser and operating-system family, device type, country/region. Never a cookie, never a stored IP address or user-agent, never anything about an uploaded document; see § 8, § 9) ICJIA's own Plausible server (plausible.icjia.cloud, a DigitalOcean droplet ICJIA runs itself). The visitor's browser reports directly to it; this application's server never receives or forwards the data, and no commercial analytics provider is involved No auto-purgeKept as anonymous visit statistics with no automatic purge; the salted hash Plausible uses to link one day's page views rotates every 24 hours, so activity can never be connected across days or across sites Yes — ANALYTICS in audit.config.ts (an empty PLAUSIBLE_HOST removes the counting script entirely)

Retention periods marked "configurable" can be adjusted in the source configuration file (audit.config.ts) before deployment. The defaults shown represent the standing posture for the production deployment; any deployment running modified values publishes those values in its own deployment notes.

A periodic cleanup sweep runs every 5 minutes within the API process and on every API startup — regardless of whether the optional remediation feature is enabled (tool v1.51.0+). It performs eight tasks idempotently: expire outputs past expires_at; mark stuck jobs as failed; remove orphan directories; purge old remediation_jobs rows; purge old remediation_events rows; purge audit_log rows past their 365-day retention; delete shared_reports rows roughly 30 days after their link expires; and write the previous day's activity file, deleting activity files past the same 365-day window, and delete application error-log files past their 30-day window (tool v1.88.0+). Source: apps/api/src/services/remediationCleanup.ts.

Backups interact with retention honestly: only the 5 newest nightly snapshots are kept (tool v1.49.0 and newer), so with one snapshot per night a row deleted by any purge above persists inside a snapshot for roughly 5 further days before it is gone everywhere. Snapshots contain exactly the database tables listed in this section — no file content, because none is ever stored in the database.

7a. Why anything is backed up when documents aren't stored

Because two different things are involved, and only one of them is kept. Your document is never saved. What is kept is metadata about the audit — data about the file, never the file: a note that a document with this name was checked on this date and received this grade. That metadata is what an agency shows when it has to prove what it reviewed and when, and it says nothing about who did the checking — the service has no accounts, no sign-in, and no column anywhere for an email address, an IP address, or a browser identifier. The nightly backup protects that metadata.

Your document

the PDF, Word, PowerPoint or Excel file

  1. You upload it. It is held in the server's memory only.
  2. It is read and scored against the accessibility rules.
  3. It is discarded when the response is sent — seconds later.

Never written to disk, so it cannot be in a backup — there is nothing to copy.

The audit metadata

one line in the service's own logbook — data about the file, never the file

  1. One row of metadata is written: date, file name, score, grade — a record about the document, never a copy of any part of it.
  2. It stays so an agency can show what it checked and when.
  3. It is deleted on the schedule in the table above (365 days).

This — and only this — is what the nightly backup copies.

Bottom line: a backup could not reproduce one page of anyone's document, and could not say who audited anything. It is a copy of the logbook — audit metadata — not of the files or the people that passed through it. If every snapshot were handed to a stranger, they would learn which file names were checked, when, and what they scored — not what any document said, and not who brought it. For auditors, the precise claim matters: this policy still never claims the metadata is free of personal detail. The one personal thing it can carry is the file name as uploaded — a file named after a person stores that person's name — and a saved or shared report quotes short labels from inside the document (§ 8a). What no row can carry any more is an email address, an IP address, or a browser identifier: since tool v1.68.0 the database schema has no such columns, there are no accounts and no sign-in, and the caller's address is used only in server memory to rate-limit requests, written nowhere.

What personal details the metadata can still contain: the file name as uploaded — so a file named after a person stores that person's name — and, for a report someone chose to save or share, short quoted labels from inside the document: image alt text, link wording, heading text, bookmark titles, the document's own title and author fields, and a short excerpt (up to 80 characters) from any box of text the document tags as an image — because the findings have to point at what to fix (§ 8a). Never the pages themselves, and never who uploaded anything: there is no account to record, and the schema has no email, IP-address, or browser column to fill. The complete accounting is in § 8, What is and isn't stored.

8. What is and isn't stored

Stored (metadata only)

  • Filename of the uploaded file (sanitized before storage)
  • For a refused upload (a file type the tool cannot check): the file name it was offered under (sanitized) and a timestamp — the file itself is never accepted, so no content and no content hash exist
  • For a failed audit (tool v1.88.0+): the file name or page address it was attempted on (sanitized), a timestamp, the request tier, and a one-word reason code — unreadable, timeout, fetch-failed, navigation-failed or internal — never the error text; no score, no grade, no content hash
  • Daily activity files (tool v1.88.0+): the usage log's rows for one calendar day, written to a CSV file in logs/ on the server so a day can be reviewed without querying the database — the same fields as the rows above and nothing more. The file name is the one field that can carry personal information, if a person put it there. Deleted after 365 days; not part of the nightly backup; not downloadable from this site
  • Application error log (tool v1.88.0+): one text file per day in logs/ on the server holding what the service writes to its own error output — a timestamp, which operation failed, and the error message and stack trace. A message or stack can name a file, a page address, a library path, or the address of a server the tool tried to reach; the service never writes the address of the person making the request, their browser identifier, or a token to it. Kept 30 days for diagnosing faults; not part of the nightly backup; not downloadable
  • SHA-256 hash of file bytes (a 64-character hex digest)
  • Page count (integer)
  • Pre-flight audit score and grade (numbers + letter)
  • Post-remediation audit score and grade
  • Per-category findings and explanations (generated by the scorer; in a saved or shared report these can quote short strings from the document — see § 8a)
  • For a shared report or remediation job only: document metadata (title, author, subject, keywords), image alt-text values, link text and destinations (with page numbers), heading text, bookmark titles, form-field names, and — for a box of text that the document tags as an image — an excerpt of up to 80 characters of that text, quoted inside the findings (§ 8a)
  • Server-side timestamps for every lifecycle event
  • Job status, step name, progress percentage
  • Failure reasons (string descriptions, no content)
  • veraPDF verdict summary (passed/failed + rule IDs of failing rules, no content)
  • SHA-256 hash of download token (token itself never stored)
  • SHA-256 hash of deleted file paths (paths never stored)

Never stored

  • File content of any audited format — PDF, Word, PowerPoint, or Excel (audit pipeline)
  • PDF file content after a remediation completes (output is deleted on download or after 30-minute TTL)
  • Page or paragraph text from inside uploaded documents (a saved report quotes only the specific short strings listed opposite — § 8a)
  • Images extracted from uploaded documents (none are stored)
  • Form-field values from PDFs
  • Any data transmitted to AI services (there are none — see § 4)
  • Any data shared with commercial analytics, ad networks, or tracking services — page views are counted by ICJIA's own self-hosted, cookie-free Plausible instance, which runs on ICJIA's own server, stores no IP address, and never sees anything about an uploaded document (§ 7, § 9)
  • Browser fingerprints or cross-site tracking identifiers — the analytics counter links one day's page views with a salted hash that rotates every 24 hours and can never connect two days or two sites (§ 7)
  • Raw file paths in lifecycle events (paths are hashed before storage)
  • Raw download tokens (tokens are hashed; the original 32-byte random token is held in the URL only)
  • Backups on any external or cloud service — nightly snapshots (tool v1.49.0+) exist only on the same server, in a directory outside the application, and only the 5 newest are kept (see § 7); nothing is copied off the machine by the application
  • Any document content inside those snapshots — a snapshot copies this database, and this database holds only the "stored" column opposite, so the backups contain records of audits, never the audited files (§ 7a)

What your own browser keeps (new in v1.147.0)

So that visiting the status page — or any other link — no longer throws away an audit in progress, this site now remembers the current audit in your browser’s session storage. That is a per-tab store the browser empties when the tab closes. It is written by the page you are on, it is never sent anywhere, and no other website can read it.

  • While a document is being checked: the job’s identifier and its one-time key, the file’s name, and its type. Not the file
  • Once the check finishes: the report itself — the same JSON the page is displaying, so that leaving and coming back re-renders it instead of asking you to upload the file again. Typically 75–100 KB
  • Cleared when: the tab closes, you start another audit, you clear the results, or the app is updated to a new version
  • The uploaded file itself is never put there — it is not storable, and the server has already deleted it (see the columns above)

8a. Storage verification — the evidence for § 8

Audited 2026-08-05, against the source code at tool v1.49.0; re-verified 2026-08-09 against tool v1.68.0 after the identifier-removal release (accounts, sign-in, and every email / IP-address / user-agent column removed; migration 11 dropped the columns and their existing data from the live database). Section 8 above states what is and isn't stored. This section is the proof: a complete, dated inventory of every place the application can write data — every database table, every statement that inserts into one, every file the server creates, everything its process logs can contain — each verified by direct inspection of the code, with the code quoted. It exists so that a manager, records officer, or external auditor does not have to take § 8 on trust.

Method. The entire database schema was read (apps/api/src/db/migrations.ts — the only file that creates tables); every INSERT/UPDATE in the server code was enumerated and traced to what flows into it; every filesystem write (writeFile, createWriteStream, temp files) was enumerated; the dependency list was checked for request loggers; and every console.* call that lands in the process log was reviewed. Anyone can repeat this: the repository is public, and the verification is a handful of grep commands over apps/api/src.

The entire database, table by table

The application has exactly four tables, all created in one file. None has a BLOB (binary) column — a search of the whole server codebase finds no binary column type anywhere, so the database is structurally incapable of holding file bytes. There is also no identity anywhere: since tool v1.68.0 there are no accounts and no sign-in, and no table has a column for an email address, an IP address, or a browser user-agent — the columns were removed from the schema itself, not merely left unwritten.

Every database table: name, purpose, and which columns hold user-supplied data
TablePurposeUser-supplied data it can hold
audit_log Usage metadata (audits + refused uploads); gates remediation filename (sanitized, 512-char clamp), content hash, request tier (trusted-tool vs public — a property of the shared service token, not an identity), and on a failed audit a one-word reason code (never error text)
shared_reportsReports someone chose to share, or fleet/URL audit reports filename (or audited URL), the report itself — see the nuance below
remediation_jobsRemediation job lifecycle (metadata only, 30-day rows) filenames, content hash, before/after report JSON (same nuance below)
remediation_eventsAppend-only auditor receipt per job (§ 6)sanitized filename + byte size on the "received" event

The usage-metadata table's complete effective definition (baseline from apps/api/src/db/migrations.ts, plus migration 2's content_hash, minus the identity columns migration 11 dropped, plus migration 12's privileged tier flag, plus migration 13's reason code) — note there is nowhere for document content, or for an identity, to go:

CREATE TABLE audit_log (          -- shape after migration 13
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  event_type TEXT NOT NULL,
  filename TEXT,
  score INTEGER,
  grade TEXT,
  created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
  content_hash TEXT,
  privileged INTEGER,  -- request tier: 1 = trusted-tool (fleet), 0 = public,
                       -- NULL = row predates the column. A property of the
                       -- shared service token, never an identity.
  reason TEXT          -- failed audits only (v1.88.0): one of five fixed codes —
                       -- unreadable, timeout, fetch-failed, navigation-failed,
                       -- internal. NULL otherwise. Never error text.
);

Uploaded files live and die in memory

The upload handler is configured with memory storage — there is no upload directory to write to. Verbatim, from apps/api/src/middleware/uploadMiddleware.ts:

const storage = multer.memoryStorage();
...
export const uploadMiddleware = multer({
  storage,
  limits: { fileSize: ANALYSIS.MAX_FILE_SIZE_MB * 1024 * 1024, files: 1 },

An exhaustive search for filesystem writes across the whole API produces eight sites, all accounted for: the database directory itself, the remediation pipeline's working files (§ 3 — deleted mid-job with deletion verified), the two short-lived analysis temp copies below, and the cleanup sweep's own deletions. There is no createWriteStream anywhere in the server code, no cache, and no other write. The two analysis tools that need a file path get a temp copy under a random name (the user's filename never appears on disk), deleted in a finally block so failure cannot leak it — from packages/analyzer/src/qpdfService.ts (veraPDF uses the identical pattern in apps/api/src/services/veraPdfBuffer.ts):

const tmpPath = path.join(tmpDir, `${randomUUID()}.pdf`);
try {
  fs.writeFileSync(tmpPath, buffer);
  const stdout = await execQpdfAsync(tmpPath);
  ...
} finally {
  try { fs.unlinkSync(tmpPath); } catch {}
}

Word, PowerPoint, and Excel files never touch disk at all: the in-memory buffer crosses to a short-lived analysis child process over an inter-process channel (packages/analyzer/src/ooxmlRunner.ts) — no temp file exists on that path to delete.

The one nuance: a shared report quotes small parts of your document

A plain audit — upload, read the results, close the tab — stores only the metadata row shown above: filename, score, grade, hash. But when a report is saved (you click share, or a report is created by the URL/fleet audit paths, or a remediation job stores its before/after reports), the stored report includes the findings text — and findings can quote short strings copied from inside the document, because naming the problem requires showing it. Specifically: the document's own metadata (title, author, subject, keywords, creator/producer), image alt-text values, link text and link destinations with their page numbers, heading text, bookmark titles, form-field names, font names, and — since v1.83.0 — an excerpt of up to 80 characters from each box of text the document tags as an image, so the report can point at the right box instead of telling an author to describe it. For example, the alt-text check quotes each image's alt text so a reader can judge it (packages/analyzer/src/scoring/pdf.ts):

findings.push(`  ${label}: "${fig.altText || "(empty alt)"}"`);
...
findings.push(`  "${link.text.trim()}" → ${link.url}`);

What is never in a stored report, verified against every finding the scorers can produce: page or paragraph text, images, form-field values, or any raw file bytes. Headings are recorded as levels (H1, H2 …), not their text — except where a heading also appears as a bookmark title. Web-page audit reports store CSS selectors for failing elements, never page HTML (the code deliberately drops the HTML snippet field the underlying engine offers).

A shared report is retrievable by its unguessable 128-bit random ID, its link stops working 365 days after creation, the stored row itself is deleted by the cleanup sweep roughly 30 days after that (§ 7, tool v1.51.0+), and remediation job rows carrying the same report JSON are purged after 30 days. If a document is sensitive enough that its alt text, link URLs, or bookmark titles are themselves confidential, audit it without sharing the report — the plain audit path stores none of this.

What the server's own logs can contain

  • No request logging exists in the application. The dependency list contains no request logger (no morgan, pino, winston, or bunyan), and no middleware writes per-request lines. The process log accumulates: startup banners, cleanup-sweep counts, and error stack traces. Every console.* call in the server was reviewed: in production, none interpolates a filename, an address, or any document content.
  • No email can leave the server at all — tool v1.68.0 removed the sign-in system, which was the only thing that ever sent mail, along with the mail-sending code and its credentials. There is no mailer left to misuse.
  • The hosting layer keeps standard web-server access logs. The site runs behind nginx, which — as on effectively every website — records each request's IP address, timestamp, URL path, status, and browser user-agent in its own log files on the server, outside this application. It is stated here, with its retention listed in § 7, so the phrase "standard web-server logs" is concrete rather than a hedge.

Verdict on each § 8 claim

Verification verdict for each claim in section 8, with the evidence that supports it
Claim (§ 8)VerdictDecisive evidence
File content of audited documents is never storedVerified memory-only uploads; no BLOB column; all 8 filesystem writes accounted for
Remediation files deleted (download or 30-min limit)Verified input deleted mid-job, before completion; every deletion re-checked and the check recorded (§ 6)
Extracted text from documents is never storedQualified 2026-08-05 true for page/paragraph text; a shared report quotes alt text, link text/URLs, bookmark titles, metadata — § 8 now says so
Images and form-field values are never storedVerified no image bytes anywhere; form-field names can appear in findings, values never
No AI services, analytics, or trackers receive dataQualified 2026-08-14 true for AI services and for everything read from an uploaded document — the server makes no such outbound call (§ 4). Since 2026-08-14 the web pages count visits with ICJIA's self-hosted, cookie-free Plausible (§ 7): the visitor's browser reports the page address (generalized since 2026-08-15 — per-file repair, shared-report, and web-page-audit report pages count only as their base routes, and query strings are never sent) directly to ICJIA's own analytics server — never audit data, never document content — and no commercial provider receives anything
No email, IP address, or user-agent is stored anywhere (§ 7a's claim) Verified 2026-08-09 true since tool v1.68.0 — migration 11 dropped the columns and their data; before that the usage log did store IP and user-agent (disclosed since policy v1.2, and still visible in snapshots until the keep-5 rotation ages them out, ≈5 days)
Filenames are sanitized before storageVerified sanitizer lives inside the writer (cannot be bypassed by a new caller) + 512-char clamp; remediation rows additionally keep the original name, length-limited
Raw tokens are never storedVerified download tokens SHA-256-hashed; raw values exist only in transit (login codes and API-token rows no longer exist at all — v1.68.0)
No backups leave the serverVerified nightly snapshots (v1.49.0) stay on-server, outside the application directory, only the 5 newest kept (§ 7)

Limitations. This verification is a statement about the code as of tool v1.68.0, re-verified 2026-08-09 (first audited 2026-08-05 at v1.49.0), not a permanent guarantee; it is re-verified when data handling changes, and any change would appear in § 14's change log. The full technical evidence pack — every write site with file and line numbers — is preserved in the project repository's history for this release.

9. Security & technical safeguards

  • HTTPS / TLS 1.2+ on all transport between client and server. The production deployment uses certificates issued by Let's Encrypt and renewed automatically.
  • No cookies, no sessions — the tool has no accounts or sign-in (v1.68.0), and the API sets no cookie of any kind. The only credentials in the system are the per-job download token (returned once at job creation, stored only as a SHA-256 hash) and the optional fleet service token.
  • Cookie-free, self-hosted page-view analytics — the site counts visits with Plausible, a privacy-first, open-source analytics tool, running on ICJIA's own server (plausible.icjia.cloud, a DigitalOcean droplet ICJIA manages itself) rather than at any commercial analytics provider. It sets no cookie and stores no IP address or browser user-agent; the visitor's browser reports the page address directly to that server — generalized since 2026-08-15 so that per-file repair, shared-report, and web-page-audit report pages count only as their base routes (/remediate, /report, /page-report) and query strings are never sent — and nothing about an uploaded document is ever included. The browser's Content-Security-Policy allows that single external origin and no other (§ 7, § 8).
  • Restrictive filesystem permissions on remediation data: 0700 on directories, 0600 on output files. Only the process owner can read these files.
  • Unguessable identifiers: job IDs are UUIDv4 (122 bits of cryptographic entropy); download tokens are 32-byte random base64url-encoded strings.
  • Constant-time-ish token comparison: download tokens are compared via byte-wise XOR over fixed-length SHA-256 hashes, mitigating timing side channels.
  • Content-based file-type validation on uploads: the file's actual bytes — never its filename or declared MIME type — must match a supported format. A PDF must begin with the five bytes %PDF-; a Word, PowerPoint, or Excel upload must be a well-formed ZIP (OOXML) package whose internal parts confirm which of the three it is. Anything else is rejected immediately.
  • File size cap: 25 MB for the audit pipeline, 50 MB for the remediation pipeline (configurable).
  • Page count cap: 500 pages for remediation (configurable). Pathological PDFs with thousands of pages are rejected before any processing.
  • JVM memory cap on the OpenDataLoader child process: 768 MB heap via JAVA_TOOL_OPTIONS=-Xmx768m to bound resource consumption.
  • Wall-clock timeout on the remediation worker: 5 minutes (configurable). The JVM child is killed on overrun.
  • Daily remediation cap: 100 jobs per caller per rolling 24 hours (configurable), counted in server memory keyed by the caller's IP address — used transiently, written nowhere, reset on restart. Analysis concurrency is bounded globally at 2 simultaneous documents.
  • Rate limiting on upload endpoints to prevent abuse.
  • Encrypted PDFs are rejected with a clear error before any analysis is attempted.
  • Cleanup on startup: when the API restarts, a sweep reconciles disk vs database — jobs stuck in "running" for over 10 minutes are marked failed; orphan files with no matching database row are removed.
  • Regression guard on remediation: the output PDF is rejected if its overall or Strict (WCAG + IITAA §E205.4) score regresses relative to the input. The user never sees an output that would make any visible metric worse.

10. Security audit history (red/blue team reviews)

What is a red/blue team audit, in plain language?

Imagine the tool is a bank vault. The red team plays the role of someone trying to break in — looking for unlocked doors, weak walls, or ways to trick the guards. They aren't actually attackers; they're security-minded reviewers who deliberately think like attackers. The blue team plays the defenders — documenting every lock, alarm, and procedure that's supposed to keep the vault safe.

A red/blue team audit is when both teams sit down together — often the same person playing both roles — and systematically work through everything that could go wrong: "What if someone uploads a poisoned file?" "What if two people try to download the same thing at once?" "What if the server runs out of memory mid-job?" For each scenario, they identify whether existing protections are adequate, what could fail, and how to fix it.

The output is a list of findings, each rated by severity:

  • P0 — critical: the system is broken right now and users are exposed. Must be fixed immediately, before any release.
  • P1 — serious: a real vulnerability that could be exploited. Must be fixed before the upcoming release.
  • P2 — moderate: a real concern, but its impact is bounded by other protections. Documented; sometimes accepted as a known limitation if mitigation is in place.
  • P3 — minor: a small concern or theoretical risk. Tracked; addressed when convenient.

Why this matters for compliance: ADA Title II, Illinois IITAA 2.1, and most state-agency procurement standards require a "reasonable" level of security. A documented red/blue team audit before each release is concrete evidence of due diligence — it demonstrates that the development team didn't just hope nothing would go wrong, they systematically checked. For an external auditor, this section IS the documentation of that diligence.

Audit entries below are in reverse-chronological order (most recent first). Each entry lists the findings discovered during that release's review and what was done about them. Every release since v1.18.0 has an entry.

On dates. Some entries are marked "entry recorded 2026-08-08". Those releases were reviewed at the time, but this plain-language write-up of them was reconstructed later from the project's own change log — 27 releases, mostly small follow-up corrections, had been left out of this section. The distinction is marked rather than smoothed over: a record that quietly backdates itself is worth less than one that says which of its entries were written after the fact. An automated check now prevents a release from shipping without an entry here, so the gap cannot reopen.

v1.166.0

Reviewed 2026-10-06 · scope: how PowerPoint text colours and sizes are read, and pictures “described” only by a page label.

No new attack surface. To find the colour and size of PowerPoint text that sets none of its own, the checker now follows the text styles in the slide’s layout and master — parts of the same file it already reads, under the same size limits. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixPowerPoint text is checked in the colour PowerPoint shows it in. Most text in a deck takes its colour from the deck’s design rather than setting its own, and that text was never checked for contrast; now it is. Links are judged in the colour PowerPoint draws links in, and highlighted text against its highlight. A colour shaded lighter or darker, or made see-through, is left unchecked rather than guessed at — guessing had wrongly failed six titles on a real deck.
  • FixA picture “described” only by a label such as “Table (page 30)” now counts as undescribed. Automatic tagging tools write these when they cannot describe a picture. Some real documents’ grades went down as a result, correctly.
  • OPSChecked, not assumed. Six new test documents; the checker as it stood failed all six. Where a reading was in doubt, the slide was drawn by a second program to see what it really shows. The scan of third-party code finds the same six warnings as before, none in the live service.
Earlier reviews — 243 of them (v1.165.0 → v1.17.0 and earlier) — click to expand

v1.165.0

Reviewed 2026-10-06 · scope: how pictures’ descriptions and PowerPoint slide backgrounds and bullets are read, and new test documents built the way real programs write them.

No new attack surface. To find a PowerPoint slide’s background and bullets, the checker now reads the slide’s layout and master — parts of the same file, read under the same size limits, a bounded number of them per file. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixA picture “described” only by its file name or a placeholder word now counts as undescribed. Some programs fill in a picture’s description when the author writes none — Google Slides uses the file name, others write “Picture” or “image 1” — and the checker had counted these as real descriptions. Some real documents’ grades went down as a result, correctly.
  • FixPowerPoint slides are read the way PowerPoint shows them. A slide with no background of its own now uses its layout’s or master’s, so text on it is checked for contrast; and lines whose layout turns bullets off are no longer counted as real list items, so hand-typed bullets are caught.
  • OPSChecked, not assumed. Eleven new test documents copy what five real programs write; the checker as it stood failed eight of them. Every claim about what a program writes was checked by making a file with it. The scan of third-party code finds the same six warnings as before, none in the live service.

v1.164.1

Reviewed 2026-10-06 · scope: one paragraph’s width on the “Can I trust this?” page, and a third-party warning that appeared after the last release.

No new attack surface. One paragraph on the trust page now uses the card’s full width, like the list above it. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • OPSA new warning about outside code, closed the same day. After the last release, the scan of third-party code began reporting a seventh warning, in a small text-quoting library. That library is used only by a developer tool that opens files in a code editor; it is not part of the live service, and the built site does not contain it. It was updated to the fixed version anyway, and the scan is back to the same six warnings as before, none in the live service.

v1.164.0

Reviewed 2026-10-06 · scope: how the text inside Word, PowerPoint and Excel files is read, and a new check that reads one document written every legal way.

No new attack surface. This release changes how the text inside Office files is read, so the review looked closely at that path. The reader now turns the short character codes a file may contain (such as &#xA; for a line break) back into the characters they stand for, as the format requires. Each code becomes at most one character, so nothing can grow, and files that try to define their own codes are still refused before they are read. Files stored in the UTF-16 text encoding are now read too, under the same size limits as every other file. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixSeven ways of writing an Office file that the checker misread, all fixed. A new check builds one Word document, one presentation and one workbook, writes each one 81 different but equally valid ways, and requires the same grade every time. On its first run it found seven misreadings: line breaks in a picture’s description kept as codes, files stored as UTF-16 refused, “bold: off” read as bold in Word and in Excel, “true” and “false” not understood, the wrong slide named, and spreadsheet cells without written addresses not found. No real document’s grade changed.
  • OPSChecked, not assumed. Seven new test documents fail on the checker as it stood and pass now. Each of the thirteen individual corrections was undone on its own to confirm a test notices. Claims about what other software writes were checked by making files with that software. The scan of third-party code finds the same six warnings as before, none in the live service.

v1.163.0

Reviewed 2026-10-06 · scope: how PDF tables are told apart from grids used only for layout, and three new notes about table headers that label nothing.

No new attack surface: the change is to how documents are graded and to the notes the report gives. To tell a PDF table apart from a layout grid, the checker now looks at whether the page draws lines or shading where the table sits — information it already read for other checks. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixPDF tables are judged by what they show on the page, as Word tables already were. A table with no header cells and nothing drawn — no lines, no shading — is a grid used only to line things up, and is no longer accused of a missing header row. A table drawn with lines is still a data table and still needs its headers. No real document’s grade changed.
  • FixTable headers that say nothing are pointed out. A header row that is marked but empty, or an Excel table still headed “Column1, Column2”, is now noted in the report. These do not change the grade.
  • OPSChecked, not assumed. Six new test documents were written before the changes and run against the checker as it stood. Three older test documents were corrected to draw the lines a real data table has, so they keep testing what they were built to test. The scan of third-party code finds the same six warnings as before, none in the live service.

v1.162.0

Reviewed 2026-10-06 · scope: how Word tables are told apart from grids used only for layout, and seven new test documents for tables.

No new attack surface: the change is to how documents are graded and to one line of the report’s wording. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixWord tables are judged by what they show on the page. A grid with its borders switched off — how Google Docs and other programs save an invisible table — or one carrying a table style that draws nothing, was treated as a data table and accused of a missing header row. A data table drawn with lines around each cell, rather than around the whole table, was never checked at all. All three are now judged by what is actually drawn.
  • OPSChecked, not assumed. Seven new test documents for tables were written before any fix and run against the checker as it stood: three showed the problems above, and four confirm things it already gets right, each shown to fail if the part of the checker it protects is switched off. No real document’s grade changed. The scan of third-party code finds the same six warnings as the previous release, none in the live service.

v1.161.1

Reviewed 2026-10-06 · scope: updating third-party code named in the previous release’s warnings.

No new attack surface and no change to the service’s own code: only third-party code it depends on was updated. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixSix of the twelve warnings from the previous release are cleared. None affected the live service, as that release explained; the code is updated anyway so the scan reads clean. That includes the three warnings about code that does run in the live service: the part that works out a visitor’s network address, the part that builds each page, and a code-mapping library that ships with it.
  • OPSSix warnings remain, none in the live service. Four concern a tool used only while developing the service on a programmer’s own computer. Its fix is a new major version that the development toolkit cannot load yet, so it waits for that toolkit’s next release. The other two still have no fix published. Every test, the full build and the pages of the built site were checked after the update, and no document’s grade changed.

v1.161.0

Reviewed 2026-10-06 · scope: section titles in PDFs that are tagged as plain text, a document’s language when it is marked on the text itself, one new best-practice row, and ten newly published warnings about third-party code.

No new attack surface: the change is to how documents are graded and to the advice the report gives. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixPDF section titles tagged as plain text are now counted, as they always were in Word. Two annual reports tag only three of their headings — about seventy section titles are tagged as ordinary paragraphs — and their heading score read “No issues found”. Such titles now cost what they cost in a Word file. The check was measured against the test documents first, so that cover pages, letterheads, pull quotes and captions, which all look like headings, are never counted. Four real reports lost a score they had never earned; all four were already graded D for other problems.
  • FixA language marked on the text itself now counts. A Word file with no overall language setting, whose every word was marked English exactly as this report advises, was still told it declared no language. Word and PowerPoint now both take the language marked on most of the text, and a single marked word no longer stands in for a whole presentation.
  • FixPowerPoint gets the best-practice check Word has for tables used only to line things up.
  • OPSTen new warnings about third-party code, none affecting the live service. The routine scan of the code this service depends on found ten warnings published since the previous release. Seven concern tools used only while building or developing the service. The other three concern code that does run in the live service, and each affects only a way of using it that this service does not use: one is about trusting address ranges where this service trusts a fixed number of hops, one about attribute names taken from outside data, which this service never does, and one about reading a kind of file (an indexed source map) that the service never reads. The update that clears the fixable ones follows as the next release. Two older warnings still have no fix published.
  • OPSChecked, not assumed. Seven new test documents pin the new rules, and each one that checks a fix was shown to fail when the old rules were put back. Every safeguard against a false accusation was shown to matter by switching it off and watching a test fail.

v1.160.0

Reviewed 2026-10-05 · scope: the last small grading differences between file formats, two new checks for Word, PowerPoint and Excel files, and clearer advice about logos in page headers.

No new attack surface: the change is to how documents are graded and to the advice the report gives. To check a document’s declared language, Word and PowerPoint files now have a short sample of their text read while they are being checked — the same size PDF files have had read since August. That sample exists only inside the separate process that checks the file and is discarded with it: it is never stored with a report, written to a log, or kept in the database. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixThe same share of described images now earns the same score in every format. A report with 16 of its 23 images described could grade a letter lower as a PDF than as a Word file, because the two used slightly different arithmetic. All four formats now use one rule.
  • FixA damaged file is no longer reported as having no title. When the part of a PowerPoint or Excel file that holds its title could not be read, the report said the title was missing — a claim about something it never saw. Word files already said “could not be read” instead; all three now do.
  • FixA table used only to line things up on a slide is no longer graded as a data table. Word files have always been treated this way; PowerPoint files now are too.
  • FixTwo problems that were missed in Office files are now caught. Light-colored text typed onto a spreadsheet’s plain white grid is now checked for contrast, as the same text in a Word document always was. And a Word or PowerPoint file whose declared language does not match its text — a Spanish notice labeled English, for example, which a screen reader would read aloud with English pronunciation — is now flagged, as PDF files already were. A passage the file itself marks as Spanish is never flagged.
  • FixLogos in page headers are explained. A logo without a description in a Word document’s header still counts, but the report now says where it is and how to mark a purely decorative logo so it no longer counts.
  • FixThe fix-it step for a language problem no longer asks for a title that is already there. A Word or PowerPoint file with a good title and a missing or wrong language was told to “give the document a title and set its language”. It is now told to fix the language only.
  • OPSChecked, not assumed. Nine new test documents pin the new rules; each one that checks a fix was shown to fail when the old rules were put back. Every pinned document’s grade was re-checked: no real document’s grade changed. The scan for known problems still finds the same two, which have no fix published yet.

v1.159.0

Reviewed 2026-10-05 · scope: three more grading rules made the same across file formats, and the fix-it step for document titles.

No new attack surface: the change is to how documents are graded and to the advice the report gives. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixA list typed by hand no longer costs a D. In Word and PowerPoint, a list typed by hand instead of made with the Bullets or Numbering buttons graded the whole file down to a D, even though every word is still there and reads in order. It now costs what an unmarked table does: a C at most.
  • FixUnmarked section headings in a slide deck now cost what they cost elsewhere. A deck whose section headings were all typed into text boxes, with no slide titles anywhere, was graded more gently than the same headings in a Word file or a PDF. All three are now graded the same way.
  • FixWord, PowerPoint and Excel titles are now checked the way PDF titles always were. A title that is just a file name, or the default a program fills in, does not tell anyone what the document is — a standards failure only PDF reports caught. PowerPoint’s own default, “PowerPoint Presentation”, was caught in no format at all, though an agency template carries it. The fix-it advice for titles also stopped asking for a language setting that was already in place, or that a spreadsheet cannot hold.
  • OPSChecked, not assumed. Four new test documents pin the new rules and two existing ones were updated; each was shown to fail when the old rules were put back. Every pinned document’s grade was re-checked: no real document’s grade changed. The scan for known problems still finds the same two, which have no fix published yet.

v1.158.0

Reviewed 2026-10-05 · scope: how a document with no heading styles is graded when one line looks like a heading, and three wording changes on the “Can I trust this?” page.

No new attack surface: the change is to how documents are graded and to wording. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixA memo’s title is no longer graded as missing sections. When a document uses no heading styles, one line that looks like a heading is its title, and two or more are sections that need real heading markup. PDF reports have followed that rule since September. Word did not: a one-page memo with a single bold title line graded D, while the same memo saved as a PDF graded A, and PowerPoint took a smaller deduction for a lone title. All three now follow the same rule, and a document with two or more unmarked section headings is still flagged.
  • FixThe trust page no longer says “alone” after its 30-day figures. The figures are counted live, and the word read as a boast when a figure was small. The downloadable copy of the page also now names every checker bug the web page lists.
  • OPSChecked, not assumed. Three new test documents pin the rule from both sides, and each was shown to fail when the old rule was put back. Every pinned document’s grade was re-checked: one real document moved — a quick-reference guide whose only heading-like line is its own title — and it still names that line in its report. The scan for known problems still finds the same two, which have no fix published yet.

v1.157.1

Reviewed 2026-10-05 · scope: updating software written by others that this tool includes, to close the known problems reported in the entry below.

No new page, no new question asked of anyone, nothing new kept or sent, and no change to how any document is checked. This release updates five pieces of software written by others, then re-runs every accuracy check to prove no document would now be graded differently.

  • FixedTwenty-four of the twenty-six known problems are closed. None could be reached through this site as it runs, as the entry below explains; they are updated anyway, so the scan comes back nearly clean instead of needing an explanation for each. The part of the web server that packages each page’s data is now on its fixed version, and so is the component that receives uploaded files.
  • NoteTwo remain, because no fixed version exists yet. Both are in tools used only while the site is being built or worked on, never by the running site, and both will be updated as soon as their makers publish a fix.
  • OPSChecked, not assumed. After the update, every accuracy check was run again: every trap document was judged the same as before, and every pinned document’s grade came out exactly as it was. The software that reads Word, PowerPoint and Excel files did not change at all.

v1.157.0

Reviewed 2026-10-05 · scope: how tables are graded in Word, PowerPoint and Excel files, how a Word table’s header row is recognized, the advice given for PDFs made from Word, and a re-run of the scan for known problems in the software this tool depends on.

No new attack surface: the change is to how documents are graded and to the advice the report gives. Nothing about how files are received, checked, stored or deleted changed, and nothing new is kept or sent.

  • FixA Word table marked the way Microsoft documents is no longer called unmarked. Microsoft tells authors to mark a table’s header row with the Header Row box on the Table Design tab, and its own accessibility checker accepts that box. This tool accepted only a different, older setting, so a correctly built meeting agenda was graded D for a table it had marked properly. The box now counts, as the matching box in PowerPoint and Excel always has.
  • FixThe same problem in a table now costs the same whichever program made the file. A table whose header row really is unmarked held a Word or PowerPoint file to a D, a PDF to a C, and an Excel workbook to a B. It now holds all four to a C. Excel also stops piling the cost up table by table, and a table used only for layout no longer waters the cost down.
  • FixPasted layout grids are no longer mistaken for data tables. Text pasted from a web page or an email carries a marker that means “no shading”, and the checker read that marker as shading — a sign of a styled data table. A borderless grid used only for layout could then be graded as a data table missing its header row.
  • FixThe report no longer promises something Word does not do. For PDFs made from Word, the report said that re-saving from Word would add the label that tells a screen reader which way each table header points. Checked against real files, Word’s built-in Save as PDF does not add it, so an author following the advice would have been flagged again. The advice now says so, and names the ways that do add it.
  • OPSChecked, not assumed. Six new test documents pin the new rules, including one built like the agenda that started this, and each was shown to fail when the rule it protects was put back the old way. Every pinned document’s grade was re-checked: only the two test documents for an unmarked table changed (both now a C), plus the agenda itself, which is now graded A.
  • NoteThe scan for known problems found 26, and none of them can be reached through this site as it runs. All 26 were made public between September 3 and October 1, and none was reported by the scan recorded for v1.156.4 on September 11, below. Nineteen are in tools used only while the site is being built or worked on, never by the running site; two of those have no fixed version published yet. Six are in the part of the web server that packages each page’s data, and each needs a feature of it this site does not use or data this site cannot produce. The last affects a way of receiving uploads straight onto disk that this site does not use: the component here holds each upload in memory as it arrives.

    Updating them is left to a release of its own, because moving a dependency means re-running every accuracy check before it ships.

v1.156.5

Reviewed 2026-09-16 · scope: the box on the home page where a file is chosen for checking, which could only be used with a mouse.

No new attack surface: presentation only. Nothing about how files are received, checked, stored or deleted changed — the same file types, the same limits and the same upload as before.

  • FixThe box where you choose a file now works without a mouse. The accessibility check of September 16, 2026 found that someone using a keyboard or a switch device could not choose a file to check at all: pressing Tab jumped past the box, no key opened the file picker, and a screen reader heard three lines of text with nothing to say they could be pressed. The box is now a real button. Tab reaches it, Enter or the space bar opens the file picker, and a screen reader announces it as a button for choosing files to audit, including the limits the box shows.
  • FixYou can see where the keyboard is. When the box is reached with Tab, an outline appears around it in the site’s link colour, measured at more than twice the contrast the standard requires in both the dark and the light appearance. Someone using a mouse sees exactly what they saw before.
  • OPSChecked in a real browser, not assumed. Before the change, the keyboard could not reach the box at all. After it, the box is reached once, in the expected place, and Enter, the space bar and a mouse click each opened the file picker. Pictures of the box taken before and after were identical in both appearances, on a desktop-sized and a phone-sized screen, and a file dropped onto it was checked as before. Ten new automated tests now guard this, and each one was shown to fail when what it checks was deliberately broken.

v1.156.4

Reviewed 2026-09-11 · scope: updating the component that receives uploaded files and one tool used while the site is built, to close the six known problems reported in the entry below.

No new page, no new question asked of anyone, nothing new kept or sent. This release updates two pieces of software written by others that this tool includes, switches on one safety limit that the updated component leaves off, and re-runs every accuracy check afterwards to prove no document would now be graded differently.

  • FixedA single crafted upload can no longer stop the service. The component that receives uploaded files could be made to fail in a way that shut the whole service down, using one request and no account. Before updating, the problem was reproduced against this site’s own upload handling, on every route that accepts a file. After the update, the same request is simply refused.
  • FixedA second crafted upload can no longer tie the service up. The update includes a limit that closes this problem, but ships with that limit switched off — so updating alone would have left it open. It is now switched on everywhere this site accepts a file, set as tightly as it can go, because no form here sends anything alongside the file itself.
  • HardenedA refused upload is now recorded as the sender’s mistake, not a server failure. Uploads the component turned away were answered as internal server errors and logged in full, including text the sender had put in the request. They are now answered as ordinary refusals and logged as a single line that records the kind of refusal — and nothing the sender wrote.
  • FixedThe build tool is updated too. It only tidies the site’s own style sheets before the site goes live and never handled anything a visitor sends, so it was never reachable. It is updated so that the scan comes back clean instead of needing an explanation, and the style sheets it produces were checked and came out the same as before.
  • OPSEvery accuracy check was re-run, not assumed. All 156 trap documents were re-judged correctly, all 286 pinned scores came back identical, and the check that re-saves documents in different byte layouts still produced the same grades. The component that reads inside Word, PowerPoint and Excel files — the one whose behaviour could shift scores silently — did not change version. The scan for known problems now reports none.

v1.156.3

Reviewed 2026-09-11 · scope: how one figure on the public status page is worked out, and a re-run of the scan for known problems in the software this tool depends on.

Nothing about what this tool collects, keeps, or sends changed. The status page — the page anyone can read without signing in — now shows a resting server’s processor figure as zero instead of saying it could not be read.

  • FixThe status page said it could not measure a server that was simply resting. About an hour after the last document was checked, the server’s processor figure settles at exactly zero, and the page treated that zero as a broken measurement — which is what zero means on Windows, a system that does not keep this figure at all. The page now treats zero as the ordinary reading of an idle server everywhere except Windows. Nothing new is published: zero was always one of the values this figure could show.
  • P1Four known problems were found in the component that receives uploaded files, and two of them could be used against this site. One could stop the service with a single specially crafted upload; the other could tie it up for a long time. Neither needs an account, and the published advice offers no way to avoid them short of updating. The remaining two affect ways of storing and checking uploads that this site does not use.

    Fixed in v1.156.4, released at the same time as this version rather than scheduled for later, so the deployment that ships this change ships the fix with it.

  • NoteTwo more were found in a tool used only while the site is being built. It tidies the site’s own style sheets once, before the site goes live, and never handles anything a visitor sends — so nothing on the site could reach them. They are updated in v1.156.4 as well.
  • NoteThe previous clean scan was accurate. All six problems were made public on the evening of September 8, about four hours after the v1.156.2 review below recorded a scan that found none. Nothing in that entry needs correcting.

v1.156.2

Reviewed 2026-09-08 · scope: updating outside software this tool relies on, to close the three known problems reported in the entry below.

No part of the tool itself changed — no new page, no new question asked of anyone, nothing new kept or sent. This release only updates versions of software written by others that this tool includes, and re-runs every accuracy check afterwards to prove no document would now be graded differently.

  • FixedThe two problems in the part that reads web-address options are closed. Every request to this service carries options at the end of its web address, and the component that reads them had two known faults — one that let a crafted address slip past a size limit, and one that could be used to make the service work far harder than it should. Both are fixed by the newer version now in use.
  • FixedThe problem in the unused text-editing component is closed. It arrived as part of the interface library, and no page here has ever used it, so nothing on this site could reach the fault. It is updated regardless, because unused today is not a guarantee about tomorrow.
  • NoteThe first attempt at that second fix was wrong, and is recorded rather than quietly discarded. Updating the single faulty package left 37 related packages from the same family behind at their old version, each expecting the newer one — a mismatch the packaging tool warned about and no automatic check would have caught. The fix was made one level up instead, by updating the interface library that brings the whole family in together.
  • OPSEvery accuracy check was re-run, not assumed. All 156 trap documents were re-judged correctly, all 286 pinned scores came back identical, and the check that re-saves documents in different byte layouts still produced the same grades. The component that reads inside Word, PowerPoint and Excel files — the one whose behaviour could shift scores silently — did not change version at all. The scan for known problems now reports none.

v1.156.1

Reviewed 2026-09-08 · scope: one paragraph of wording on the public status page, and a re-run of the scan for known problems in the software this tool depends on.

Nothing about what this tool collects, keeps, or sends changed. The only change is the wording of one explanatory note on the status page — the page anyone can read without signing in.

  • FixThe status page was describing one of its own numbers incorrectly. The note under the server-load figures said they were all read at the instant the page was built. That is true of the memory and disk figures and not of the processor one, which is an average over the previous minute. A visitor who saw the processor figure at zero therefore could not tell a quiet server from a broken gauge. The note now says which figure is a one-minute average and that a quiet reading is normal. No number, limit or behaviour changed — only the sentence explaining them.
  • P3Three known problems were found in outside software this tool relies on, and none of them come from this change. All three are in code the project does not write but does include: two in the part of the web framework that reads the options at the end of a web address, and one in a text-editing component that ships with the interface library but which no page here actually uses. All three are rated moderate, and all three already have fixed versions available.

    Handled as its own release rather than folded into a wording fix, because changing the versions of underlying software means re-running the full set of control documents to prove no accessibility verdict moved. That work is scheduled next.

  • NoteAn earlier record in this log was too confident, and is corrected here rather than edited. The v1.156.0 entry, written the same day, reported that the scan for known problems found none. The three problems above had in fact been published six days earlier, so that scan result did not reflect them. These entries are dated compliance records that an auditor may already have read, so nothing below is rewritten; the correction is made in the open, here, and the practice is tightened: a scan result is recorded only when the scan was genuinely re-run for that release.

v1.156.0

Reviewed 2026-09-08 · scope: the new server-load figures on the public status page, a published way to report a security problem, and the build system's own permissions.

Nothing here changes what the tool collects. No new place to send anything, no new information kept, and nothing new about any person. The status page — which anyone can read, without signing in — now also says how busy the server is, so the addition was reviewed as a question of what the public should be told rather than as a new feature.

  • NewThe status page says how hard the server is working. It already warned when the disk was filling up. It now also shows how busy the processors are and how much memory is in use, so a visitor whose document is taking a while can see whether the machine is simply busy. Busy is not broken: these figures can never mark the service as having a problem, and the page says so in those words. What is published is numbers and nothing else.
  • NoteThree details were deliberately left out. The make and model of the processor was excluded, because it also names the kind of machine the service runs on. How long the machine has been running was excluded, because that quietly says how long it has gone without a security update — useful to nobody except someone looking for an unpatched server. And no folder or file location is published, which has been the rule for this page since a 2026-06 review found one being disclosed. An automatic check now lists exactly which figures the page is allowed to publish and refuses anything else. That check was tested by deliberately breaking it — adding the processor model on purpose to confirm the check catches it — rather than assumed to work.
  • NoteWhat the figures could tell an outsider, and why it is accepted. Knowing how busy a server is could in principle help someone trying to overload it. Two things weigh against that: the page has published whether each checking tool is working for some time, which says more; and anyone can already estimate the server's load simply by timing how long a page takes to answer. The figures are also recomputed at most a few times a minute and sit behind the same request limit as the rest of the page. Recorded as an accepted, low-severity residual rather than an unnoticed one.
  • FixedThere is now a published way to report a security problem. A project with no stated channel invites people to raise problems in public before they are fixed. Reports now go through a private channel on the code-hosting service, with no personal mailbox on either side, and the policy says plainly what is welcome and what is not — for example, deliberately overloading the live site is not a finding, and a wrong accessibility verdict is an ordinary bug rather than a security problem. Reporters are not named in this log. An automatic check warns a month before the policy's own renewal date, so it cannot quietly go out of date.
  • FixedThe build system runs with fewer permissions, on fixed versions of its tools. The automated checks that run on every code change only need to read the code — they now hold read-only credentials, so nothing borrowed from outside could use them to alter the project. The three outside tools those checks rely on are now locked to exact versions by fingerprint rather than by a label, because a label can be quietly repointed at different code by whoever publishes it. Each fingerprint was verified against its published release before it was accepted, and a monthly job proposes updates so the locks stay current.

The scan for known problems in the software this tool depends on reported none.

v1.155.1

Reviewed 2026-09-02 · scope: three report-copy follow-ups from reading a real report after v1.155.0.

No new attack surface: report copy and one additional not-assessed disclosure. The best-practices section now counts the practices it hides (waiting on the required fixes, or not judgeable yet) instead of reading as a clean bill; the link rows credit the links the tool could read and say how many it could not; and links whose text could not be attributed are listed for manual review under the name-role-value criterion. Every new sentence renders through the same escaped paths as the rest of the report; nothing new is accepted, stored, or sent.

v1.155.0

Reviewed 2026-09-02 · scope: a second fresh-eyes audit of the product (accuracy, documentation, best practices, veraPDF) with a full security re-test.

No new attack surface. The accuracy work changed what the reports assert and how the documentation describes it; the security review re-tested every control the two previous reviews had declared sound — parameterised SQL, 128/256-bit identifiers, non-reflecting CORS, escape-first HTML sinks, the nonce-based content security policy, the proxy trust setting, forwarded-address spoofing, and the timing-safe token comparison — and could break none of them. The production dependency audit reported zero advisories. Every regular expression applied to document text was timed on multi-megabyte adversarial inputs and measured linear.

  • FixedP2 — the page-audit fetch could be steered after its check. The address check for a web page audit read only the first address a name resolved to, and the browser that then fetched the page resolved the name again on its own, with a cache that kept a name marked safe for the whole audit. A name answering with a public address and a private one, or one changed mid-audit, could reach an internal service. Every address is now judged, each audit launches its own browser with the checked address pinned into it, and the cache is gone. What such a request could ever return was limited to the page title and rule selectors.
  • HardenedThe one parser that runs inside the service. PDF text extraction runs in the API process (every other engine runs as a separate, secret-stripped process). It now refuses to compile code from a document's embedded programs and bounds the size of any image it decodes. Moving it into its own process remains open.
  • HardenedBulk inventory scoring limited as the fan-out it is. One unauthenticated request can fetch and score up to a hundred allowlisted documents; it shared the share-link limit and its code comment still claimed a login that no longer exists. It has its own budget (three per hour per address) and an honest comment.
  • FixedHygiene. Temporary copies of uploaded PDFs are written readable by the service account only; fetched addresses and browser console messages are stripped of control characters before they are logged; the framework name is no longer announced in a response header; stored page reports have their help-link addresses neutralised at rest, as document reports already did.

v1.154.0

Reviewed 2026-09-01 · scope: fix-time estimates in the report's action plan.

No new attack surface: presentation only. The action plan now shows how long each fix should take, computed inside your browser from numbers the report already contains — the app accepts nothing new, stores nothing new, and sends nothing anywhere. The estimate text renders through the same escaping as every other line of the report, on the page and in the printable plan alike.

v1.153.0

Reviewed 2026-09-01 · scope: a fresh-eyes accuracy audit of the product, plus a security read of the two preceding days' changes.

No new attack surface: this release is scoring logic, report copy, and documentation — no new endpoint, input, output, storage, or dependency. It also records the security review that ran alongside the accuracy audit: the 108 commits from the two preceding days (v1.148.x through v1.152.0) were read for injection surfaces, new data paths, and privilege changes.

  • NoteNo finding at reportable confidence. The one new browser-side persistence in the reviewed window — the store that lets an audit survive leaving the page — is written only by the audit page itself, holds a server-produced payload, renders through the same escaped components as a live result, and is cleared when the tab closes. Nothing user-supplied gains a new path into markup.
  • FixedAn overclaim in the tool's favour, closed. The Matterhorn checklist labelled three checkpoints “checked by veraPDF” that its PDF/UA-1 profile has no rules for, so the promise could never fire and reports rendered them as machine-clean. All three now read “human review”, and tests pin the correction.
  • HardenedThe legal-basis CI gate now checks both directions. A deduction must name a failing WCAG criterion, and a criterion the verdict names must correspond to a deduction — the converse check found eleven real documents where a link census and the criterion disagreed, all corrected in this release.

v1.152.0

Reviewed 2026-08-31 · scope: the plain-text summary offered for pasting into an AI assistant.

No new attack surface: presentation only. The summary is assembled from information the report already contains — no new data is collected, stored or sent anywhere.

A Word document submitted for checking had no title but did declare its language correctly. The checker judged this exactly right and recorded a single failure. The summary offered for pasting into an assistant, however, listed two rules as failing — adding one about language — and printed the line “Document language: en-US” underneath a heading saying those were the failures. The language was fine. Anyone following that summary would have been told to change something that was already correct.

The cause was the summary listing every rule a category can fail rather than the ones actually found failing, which the report already knew. It now takes them from the verdict itself. Reports saved before that detail was recorded fall back to the older list, clearly labelled as rules the category covers rather than as failures.

The summary also gained two clearly separated sections, at a reader’s request: work that is worth doing but is not required by law, and the independent PDF/UA check for PDF files. Both state plainly that nothing in them is scored or legally required, and the summary now instructs the assistant to keep them out of its priority list. A document that passes outright no longer says there is nothing to do when there is optional work worth listing.

v1.151.1

Reviewed 2026-08-31 · scope: the pop-up panels, which the previous review could not see.

No new attack surface: presentation only. Nothing about how files are handled, stored or deleted changed.

The review published hours earlier checked all six pages with three separate tools and found nothing left to fix. A reader’s screenshot showed otherwise: the pop-up panels on the trust page opened as a dark sheet filled with white cards. The reason none of the tools caught it is worth recording — a panel that has not been opened is not part of the page yet, so no whole-page scan can measure anything that sits behind a click.

Opening them and measuring inside found three faults. The panels themselves kept two fixed dark colours while everything inside followed the theme, which is what produced the split. The panel explaining how grades are awarded wrote its colours directly onto the text, which overrides any theme, so in light mode it drew its A and its C at roughly half the required contrast — while explaining what a good grade means. And two of the severity labels were a fraction under the requirement in dark mode as well, which had been true for some time.

All are fixed and re-measured in both appearances, this time by compositing each colour over the surface it actually sits on rather than the page behind it.

v1.151.0

Reviewed 2026-08-31 · scope: the site’s own light mode, which had never been measured.

No new attack surface: presentation only. Nothing about how files are handled, stored or deleted changed — the edits are colour definitions, four underlines and one heading level.

This site ships in dark mode, and the theme switch is easy to miss. Every automated check it runs on itself had therefore only ever looked at one of the two appearances it offers. Setting the default to light and re-running the contrast checks found 136 failures — 135 of them on this page, and one on the home page where text sat at a ratio of 1.18 against a required 4.5. For a tool that grades other people’s documents on exactly this, the honest thing is to publish the number rather than quietly correct it.

It was one decision made twice, not 136 mistakes. The colours used for code and structure were chosen for a dark background and are close to invisible on a light one, and the panels behind them are see-through tints that turn muddy when the page beneath is white. Both are now corrected in a single place for the whole site, so the colour scheme stays consistent and no individual part of the page has to know which appearance is in use.

Two older faults surfaced in the same pass and are fixed: four links on the home page were told apart from the text around them by colour alone, and are now underlined like every other link on the site; and this project’s trust page skipped a heading level, the same fault it reports in documents it checks. Afterwards, all six pages were re-checked in both appearances with three independent tools: a perfect accessibility score on each, with no violations and no contrast failures.

v1.150.0

Reviewed 2026-08-31 · scope: a real accessibility failure PowerPoint files could contain that the checker never reported.

No new attack surface: no data path, input, or output changes. Every other correction made today stopped the checker claiming more than it could support. This one is the reverse, and the more serious of the two: a slide whose heading was typed into a floating text box rather than the slide’s title placeholder is a genuine Level A failure, Word has always caught it, and PowerPoint reported nothing at all. A tool that under-reports tells an agency a document is fine when it is not.

The new check is deliberately narrow, because a false accusation costs more than a miss: the text must sit in a shape with no placeholder role at all, be short enough to be a heading, and have its size or weight set on the text itself rather than inherited from the slide layout — inherited sizes prove nothing and are ignored.

The boundary matters as much as the rule, because the neighbouring question has the opposite answer: requiring a slide to have a heading is a Level AAA requirement that the law does not adopt, and it stays out of the grade. Three test documents pin both sides — one with typed headings, one done properly, and one with no heading at all that must keep a perfect score. Removing the rule fails the first; letting it grow to cover every untitled slide fails the other two. No existing document’s score changed.

v1.149.1

Reviewed 2026-08-31 · scope: completing the label fix released hours earlier, after a reader found two places it had missed.

No new attack surface. The release above stopped a category claiming nothing was found when it had reported something, and did it incompletely: on a 23-page report checked the same day, two categories scored 100, each reported a finding that is never counted, and both still carried the old label.

Two separate causes. The check ran one step too early — before the pass that adds several of those findings — so six test documents kept the wrong label; and it recognised advisories written only in one particular shape, missing two wordings the checker genuinely uses, one of them among the most common findings it produces. Both corrected. Nine further labels changed across the 188 test documents, and again no score changed at all.

The guard added for it is a check across every test document rather than one more example file: no category may score a perfect 100, claim no issues, and still carry a finding marked as never counted. Reinstating either fault makes it name the affected documents outright. The original defect was never about a particular file — it was a label derived from a number — so the guard is written the same way, and applies to every document added in future.

v1.149.0

Reviewed 2026-08-31 · scope: a category label that said nothing was found when something had been reported.

No new attack surface: no data path, input, or output changes. A reader checking a 41-page annual report found the Bookmarks category reading “100 · A · No issues found” directly above its own finding that the document has 41 pages and no bookmarks. The score was right — no WCAG 2.1 rule requires bookmarks inside a single document, so nothing was counted against the file — and the label was what overstated it.

Categories in that position now read “No scored issues”: nothing counted, something still reported. “No issues found” keeps its meaning where a category genuinely had nothing to say. Across the 188 control documents, 119 labels changed and no score changed at all — the arithmetic of every grade is untouched, which is the property that had to hold.

One risk was specifically guarded: the table that caps a document’s grade by its worst finding lists only the three problem severities, and a test now asserts the new label can never appear there. Had it leaked in, every document carrying a harmless advisory would quietly have lost a grade.

v1.148.3

Reviewed 2026-08-31 · scope: presentation of the figures on the trust page.

Presentation only — no data path, input, or output changes. The large figures on the trust page were sized against the width of the window rather than the width of the card holding them, so the size had been chosen twice to fit whichever number was largest at the time. Both choices went stale as the collection of test documents grew, and one figure had reached the edge of its card. Each figure is now measured against its own card and centred, so it stays inside the box at any screen width and as the numbers continue to rise.

v1.148.2

Reviewed 2026-08-31 · scope: narrowing the best-practices section to what a reader can actually act on.

No new attack surface: this narrows what a report displays. The best-practices section now lists a practice only when there is something to act on or take credit for, and nothing that repeats or contradicts the graded half of the report.

Worth recording plainly, because it is the third entry about the same few rows in one day: three labels were shipped and withdrawn in a single afternoon, each wrong in a different direction, and every one was caught by a reader looking at a real report rather than by a test. The lesson is not about wording. A section defined as “above and beyond the standard” cannot describe things the standard already handled, under any label — so those rows are no longer described at all. The tests that guard this were rewritten to check that such a row is ABSENT rather than to check what it says, which is the only form of the check that cannot be satisfied by better phrasing.

v1.148.1

Reviewed 2026-08-31 · scope: correcting the label shipped an hour earlier, after a reader caught it claiming the opposite.

No new attack surface. The release above replaced a misleading “not applicable” with “counted in your score”, and a reader immediately pointed out that this was wrong in the other direction: the label sits next to the practice’s name, so on Heading level order it claimed heading level order is scored. It is not, and never has been — this tool’s own analyzer says as much in the report itself, that skipping heading levels is a best-practice concern and not a WCAG 2.1 failure, so the grade is not affected. What was scored on that document was the absence of headings, a different fact that happened to sit in the same row.

The fix is the section’s own definition rather than a third label: best practices are things above and beyond the legal standard, so anything already counted does not belong in the section. Those rows are now left out entirely and live only in the action plan. A practice that is genuinely above and beyond but could not be judged — heading level order on a document with no headings — is reported as not checked, with the reason, and is the one case that does not end by saying nothing is wrong with the document, because something is.

Recorded here rather than quietly amended: two labels were wrong in one afternoon, in opposite directions, and both were caught by a reader looking at a real report rather than by a test. The sentences that drive this behaviour are now single shared constants with a test forbidding either being written out again, which is precisely how the label and the text beneath it came apart the first time.

v1.148.0

Reviewed 2026-08-31 · scope: a label that told readers a scored failure did not apply to their document.

No new attack surface: nothing here adds a data path, an input, or an output. It corrects what a report says, which for a compliance tool is the part that carries the risk.

A reader reported an annual report showing nine “not applicable” labels in the best-practices list — five of them about headings — while the same report scored that document’s heading structure zero and listed a WCAG 1.3.1 Level A failure against it. Headings were the single largest reason the file graded F, and the section read as though they were irrelevant to it. The text inside those rows had said the right thing since the standards audit two releases earlier; only the label above it was wrong, because the label could tell that a row did not apply but not why.

Rows now carry the reason. One means there is genuinely nothing of that kind in the document and nothing was lost; the other means the practice applies, the document falls short, and the points are already gone. Only the second gets the new counted in your score label, shown in the same colour as the action plan it points to. The summary counts the two separately, because “9 not applicable” was the same misstatement in miniature.

Three properties are enforced rather than intended, since this was a repair of an earlier partial repair. The reason is worked out in one place from a single shared sentence, so a new case cannot forget to set a flag it never sets by hand. A test forbids that sentence being written out anywhere else, which is exactly how the label and the text came apart the first time. And the rows about table headers — which needed to know whether the missing headers had actually cost points — are checked against the real score, with a test that they never claim a deduction that did not happen: the mirror of this fault, and the more damaging direction to get wrong.

v1.147.0

Reviewed 2026-08-31 · scope: the first thing this site has ever kept in a visitor’s own browser, and the navigation warning it made unnecessary.

This release stores something in your browser for the first time, so this entry is mostly about that. To stop a click on the Status link from throwing away an audit, the page now remembers the current audit in session storage — a per-tab store the browser empties when the tab is closed, readable only by this site, and never transmitted anywhere.

What is kept, in full: while a document is being checked, the job’s identifier, its one-time key, and the file’s name; once the check finishes, the report itself, so that leaving and returning shows it again instead of asking for the file a second time. The uploaded file is never kept — it cannot be, and the server has already deleted it. The server’s own behaviour is unchanged: a finished report waits in memory only until a page collects it, or ten minutes, whichever comes first. Section 8 of the data-retention page now names every one of these fields, because a page that only lists what it does not store invites a reader to assume the list is empty.

Three properties are enforced rather than merely intended. Every access to that storage is guarded, because some privacy settings make it throw and a full quota rejects the write — a failure to remember simply returns the old behaviour and never breaks the audit. Every read is checked before anything is drawn: a report written by a different version of the app is discarded rather than rendered, as is a job older than the server keeps them, or any payload that is malformed. And returning to a job the server no longer has — already collected, expired, or lost to a restart — puts the upload form back quietly, rather than showing an error for something the visitor did not do.

In the other direction, the warning shown before leaving a running audit was narrowed. It said the audit would be cancelled and its report discarded, which for a single document is no longer true. It now appears only when leaving would genuinely destroy work: a batch, an older deployment without the newer endpoints, or a browser that refused the storage. A warning that describes a loss which does not happen is how people learn to dismiss warnings without reading them.

v1.146.0

Reviewed 2026-08-31 · scope: an audit of the test suite’s own validity, and three scoring rules that took points without naming a rule.

No new attack surface: nothing in this release adds a data path, an input, or an output. It changes what can move a grade, and what the build is able to prove about itself.

The review asked a single question of every automated check in this repository — can it still fail for the reason it was written? — and nine could not. The most serious guarded the promise that only WCAG 2.1 moves a grade, and did it by searching the scoring source for a piece of text rather than by running the code. The text it searched for never appears there, so the check passed no matter what the code did, and would have survived the exact change it existed to prevent. It now evaluates a real verdict under both settings of the standard, and was confirmed by making that change and watching it fail. A second had been pinned to the wrong version of WCAG since the switch to 2.1, hiding any drift across a quarter of the report tests. A third promised to check a column width and checked only that three rows existed.

The repository’s own build scripts were never type-checked, and carried three real type errors. Two of them reached the published trust brief: it displayed 130 test documents beneath a summary that counted 128, and advertised one caught defect as having “passed clean”. The scripts are type-checked in the build now, and the brief refuses to build unless its three counts add up to the number of documents it shows.

Three scoring rules were taking points that the report could not tie to any WCAG criterion — the accuracy gate that exists to catch exactly this could not see them, because no test document in the corpus exercised them. The largest: a Word document with two “click here” links lost its entire Link Quality score at the highest severity, capping the file at a D, while the verdict named no rule at all. Weak link text is now reported in full and never counted, matching the reasoning already applied to PDFs — WCAG 2.4.4 (Level A) lets the sentence around a link supply its purpose, which no automated check can weigh. A link with no text at all is still counted, and now cites WCAG 4.1.2 Name, Role, Value (Level A), which no context can satisfy. Fourteen new test documents pin these rules, each verified by breaking the rule it guards, confirming the failure, and restoring the file byte for byte.

v1.145.1

Reviewed 2026-08-31 · scope: the filename shown while a document is analysed, and the last metadata still naming the older standard.

The waiting screen now shows the name of the document being analysed. That name is the one string on that screen a visitor controls — it arrives from their own file picker — so it is rendered as text through the framework’s escaping, never as markup, and a test feeds it a deliberately hostile filename and asserts that no element is created from it. Long names wrap rather than overflow. No new data is stored, sent, or logged: the name was already in the browser, and is simply displayed.

Also corrected: the page description search engines and AI assistants quote, and the machine-readable descriptions of this tool, still advertised WCAG 2.2 as the operative standard after the reports themselves had moved to WCAG 2.1. No new attack surface; the change is what those files claim.

v1.145.0

Reviewed 2026-08-31 · scope: the copy and citation sweep that made WCAG 2.1 the standard every report names, plus two criterion listings that were wrong.

No new attack surface: nothing in this release adds a data path, an input, or an output. It changes which standard the reports name and how they cite it. Every label, criterion link and export now says WCAG 2.1 — the version ADA Title II and the Illinois IITAA both require. Until now a verdict could state one version while the links beside it opened pages for another.

Two criterion listings were wrong in a way worth recording: the text-extractability card cited 1.4.5 Images of Text, a criterion the same report lists among those it explicitly does not assess, while omitting 1.1.1 Non-text Content, which it does check; and form accessibility omitted 3.3.2 Labels or Instructions. Citing a rule the tool admits it never checks is worse for a reader than failing to link one, so both were corrected against the shared definition, which had them right.

One consequence is stated plainly in the release notes rather than left to be discovered: on PDFs with interactive form fields, three criteria that exist only in WCAG 2.2 no longer appear as manual-review notes, because they are not part of 2.1. They are named on the What’s new in WCAG 2.2 page instead. (Corrected 2026-09-02: those notes were restored the same day as “beyond the standard your grade measures” — 2.5.8 and 3.3.7 are listed on any PDF with form fields; 3.3.8 is not, because a form is not an authentication process.) A related check found four sentences that the version change had made untrue — each promised a per-document disclosure the new default no longer produces — and all four were rewritten to describe what actually happens.

v1.144.0

Reviewed 2026-08-31 · scope: an adversarial audit of every claim the reports make about the accessibility standards, carried out as a hostile reviewer looking for one error that would discredit the tool.

No new attack surface: nothing in this release adds a data path, an input, or an output. The changes are to what the reports assert, and to two automated gates that check those assertions. The audit was deliberately hostile — four independent reviews, each verifying against the primary sources rather than against this project’s own notes — because a compliance tool that gets a citation wrong has no way to argue it got the document right.

One accusation was false. A Word document whose headings were built from a picture (an agency letterhead) or a symbol could be told it was failing WCAG 1.3.1 about headings that were not blank at all — while the same report showed grade A and said nothing needed fixing. It was found by the audit, reproduced, and fixed at the point where the count is made; a trap document now holds it in place. The compliance dates for the ADA Title II rule were also out of date on ten surfaces: the Department of Justice extended them in April 2026, to April 26, 2027 for public bodies serving 50,000 people or more and April 26, 2028 for smaller ones and special districts.

Five standards citations were corrected, including a technique described as satisfying a requirement it is only advisory for, and a help link naming a success criterion that does not exist — which had been shipping inside every downloadable report. Three statements about other software were wrong or contradicted this project’s own code, and have been corrected or withdrawn. Checked and found accurate, and therefore left alone: every success-criterion number, name and conformance level in the reports, all of the links to the W3C explanations, the ISO clause map, and the published figures for the industry checklist.

A reader-facing addition in the same release: every practice the checker could not examine now carries an information icon explaining why, and each explanation opens by saying that nothing is wrong with the reader’s document — “not checked” is the one status that can be mistaken for an accusation. Two gates were widened as a result of the audit. A new check, run on every change, fails the build if a practice reported as optional sits beside a legal failure in the same category that nobody has reviewed — the reverse of the existing check, and the more damaging error of the two, because under-reporting tells an agency it is compliant when it is not. The existing check now also fails when a category reported as “not examined” carries a confirmed failure; it found a second, older instance of exactly that the day it was widened.

v1.143.0

Reviewed 2026-08-30 · scope: the new Best practices section — 38 non-scored checks across PDF, Word, PowerPoint, and Excel, each showing a document’s own evidence.

The new section opens no new trust boundary. Every interpolated value in the printable plan passes through the same escapeHtml helper already used for scored findings; every link — the catalog’s own and any built from document content — passes through safeHttpUrl before it can render, and an unparseable one is dropped rather than shown broken. The catalog’s own copy (labels, descriptions, fix routes) is authored in this repository, never user-supplied. A second review pass (the same day) closed the one way a stored report could still overstate itself: the section reads findings a past version of the analyzer wrote, and a report created before one of its advisories existed carries no complaint from it. Each witness-based check now records the date its advisory began, and older shared reports show that row as not checked rather than met.

evaluateBestPractices, which runs during /report/[id]’s server-side render of stored JSON, narrows every field it reads — file type, category id, page count — before use, and returns nothing rather than throwing on a shape it does not recognize.

Also fixed: six advisory findings for Word and Excel documents were rendering under the heading that says the score measures them. The check that separates them now recognizes all three of the analyzer’s not-scored prefixes, not two.

v1.142.0

Reviewed 2026-08-29 · scope: a requested red/blue security audit of every change shipped today (thirty releases). Verdict: no findings, no new attack surface.

Today’s thirty releases — the legal-only scoring change and everything that followed — were audited as one body of work: 75 files and roughly 4,400 added lines, swept mechanically for dangerous patterns and then verified by hand at every seam where new code touches data an attacker could influence. Verdict: no critical, high, or medium findings, and no new attack surface — no new web addresses, no storage changes, no new parsing of uploaded content, no new outbound requests, and no new raw-HTML rendering anywhere.

The specifics worth stating: the one new shell invocation lives in a developer-only test gate, takes no user input, and passes arguments as a list (no shell interpretation); the validator error message newly shown on reports can only ever be one of three fixed sentences written by this project (never text from a document); and every new on-page field renders through the framework’s escaping. One pre-existing behavior is documented as accepted by design: the “For Use with AI Assistants” text quotes passages from the analyzed document, which is its purpose — and its new standing instruction narrows what a malicious document could talk an assistant into claiming. The full audit — method, seams, verifications, and the accepted item — is published in the repository at docs/security-audit-2026-08-29-legal-only-sweep.md.

In the same release, the trust page’s “good twins” jargon was rewritten in plain language: for each flawed test file there is a matching corrected copy the checker must not flag, and the broken version may never outscore the corrected one.

v1.141.3

Reviewed 2026-08-29 · scope: the last staled bug-count on the page, made historical and countless; the automated guard now catches its whole class.

One more dated announcement still said the battery had “caught one real bug” — true when written, staled by the five bugs found since. It now reads as history (“the first of its own bugs”), and the automated check that already forbids stale trap totals in announcements now also rejects word-number status claims and any “one real bug” phrasing. Writing that check exposed a small flaw in its own pattern, which is fixed and pinned. No score changed; nothing else changed.

v1.141.2

Reviewed 2026-08-29 · scope: two staled counts in the trust timeline rewritten so they can never stale; the page generator now rejects that class of sentence outright.

Two milestone lines on the trust page still carried counts from the moment they were written: “caught one of its own bugs” (the record is six, all detailed on the same page) and “lost two public arguments” (the record is three). Both are rewritten without numbers — “caught the first of its own bugs; every one since is documented above” — so growth can never silently falsify them.

And the fix is enforced, not remembered: the page generator now refuses to build if any once-staled phrasing returns to the source — hard-coded bug counts, argument counts, or trap totals — with instructions to compute the number from live data or write the sentence without one. The banned list grows every time a new pattern slips.

How this was reviewed: wording and one build-time check. No score changed, no new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.141.1

Reviewed 2026-08-29 · scope: one grammar fix in the verdict summary (“in 1 category”).

The verdict summary’s new criteria-to-categories bridge did not singularize its noun — a live report read “2 criteria failing in 1 categories.” On a product whose credibility is its precision, grammar is copy. Fixed and pinned by a test in both directions. Nothing else changed.

v1.141.0

Reviewed 2026-08-29 · scope: remediation reports open with the two-standards summary, and the copy-for-AI text can no longer blur required law with best practice.

The auto-remediation results page was audited against the week’s scoring changes. Its numbers were already correct — every remediation run grades with the current engine — but its presentation predated the split, so the before-and-after reports now open with the same summary every audit gets: what WCAG 2.1 requires (the whole score) and what PDF/UA adds (never counted).

The “For Use with AI Assistants” text got the same discipline, because an AI reading it will repeat whatever framing it is handed. The secondary “practical” number is now labelled remediation tracking — informational, never the compliance verdict; findings are split into what fails WCAG 2.1 versus best practice, not scored; and a standing instruction tells the assistant outright: never present PDF/UA or “not scored” items as legally required.

How this was reviewed: presentation and prompt-text changes, pinned by tests; no score changed, no new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.140.1

Reviewed 2026-08-29 · scope: the optional-work section is now visually unmissable. Presentation only.

The “Above and beyond” section of the action plan — the optional PDF/UA and best-practice work that never affects a grade — was easy to read as a footnote. It is now a clearly separated section: a full-width divider states that everything the law requires is above it, the heading is headline-sized, and a row of at-a-glance figures leads it — “0 of these count toward your score”, how many optional items this report found, and the independent validator’s own totals.

How this was reviewed: presentation markup only, pinned by a new test; no wording that other tests rely on changed, no score changed, no new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.140.0

Reviewed 2026-08-29 · scope: all six bugs ever found in this checker are now detailed on the trust page, and the count is computed, never hand-written.

A reader noticed the trust page saying the test battery had caught one real bug while the trap inventory showed several “found a real bug” chips. The chips were right. In the spirit of the rest of that page, the checker’s full defect history is now spelled out: six real bugs, each found, fixed, published, and turned into a trap document that re-proves the fix on every run. One was caught by the battery itself, three by real agency files (each of which was right and this checker was wrong), one by the encoding gate before any file arrived, and one by a trap written first and watched fail.

The number itself is no longer prose: everywhere the bug count appears it is computed from the same data that renders the chips, the page generator refuses to build if the detailed list drifts from that count, and an automated test enforces the same rule. A tool whose credibility rests on admitting its mistakes should never be able to under-report them by forgetting to update a sentence.

How this was reviewed: transparency copy and count plumbing, pinned by tests; the score ledger confirms no document’s score changed. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.139.2

Reviewed 2026-08-29 · scope: the trap-document list now runs in numeric order and ends on the highest-numbered trap.

A reader reported the trap list “showing 115” — and on the second look they were right in a way the first check missed. All 124 documents were listed, but the two test batteries were joined in the wrong order: the PDF documents (…100, then 116–124) came first and the Office documents (101–115) last, so the list ended on “synthetic-115” and scrolling to the bottom read like a total of 115.

The list is now sorted numerically across both batteries — it runs 01 through 124 and always ends on the highest-numbered document — and an automated check enforces exactly that, so a future battery can never make the bottom of the list understate the count again.

How this was reviewed: a sort in the page generator and one new test. No score changed, no new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.139.1

Reviewed 2026-08-29 · scope: the failing-criteria count and the severity tiles now reconcile on sight. No scores changed.

A reader added up the severity tiles (4 critical + 1 moderate = 5) and saw the verdict say 6 criteria failing. Both numbers were right: the tiles count categories, and one category can break two laws at once — a document with no title and no language declaration fails both rules inside the single Title & Language category. What was missing was the bridge.

The verdict now reads “6 criteria failing in 5 categories” whenever the two counts differ, and the action plan finishes the arithmetic: “Together they clear all 6 failing WCAG 2.1 criteria — fix № 1 clears more than one.” Reports saved before category data existed never show the bridge rather than showing a wrong number.

How this was reviewed: two sentences of presentation logic, pinned by new tests; the score ledger confirms no document’s score changed. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.139.0

Reviewed 2026-08-29 · scope: four more legal PDF encodings verified identical (13 total), and announcement links can never carry a stale trap count again.

A reader noticed old announcements still saying “see all 115 trap documents” while the trap list itself — which updates automatically — correctly shows all 124. The dated entries now read as history (“all 115 traps then in the battery”), their links no longer carry a number at all, and a new automated check forbids any future announcement link from hardcoding a trap count.

The bigger addition answers a standing question: can unusual table and text constructions be caught before a real document exposes them? The same-document/many-encodings gate — which has already caught one such gap before any file arrived — now also rebuilds its test document with table rows wrapped in header/body groups, attribute lists interleaved with revision numbers, the entire table vocabulary renamed behind a role map, and the table’s text painted inside a reusable form object referenced across streams. All thirteen constructions grade identically. Each is now locked in: a future change that goes blind to any of them fails the build, not an agency’s file.

How this was reviewed: test-document builders inside an existing local gate, two wording fixes to dated announcements, and new tests. No score changed, no new input is parsed from visitors, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.138.1

Reviewed 2026-08-29 · scope: two copy follow-ups caught on the live site — the trust-page heading, and fix steps that now say only what actually failed.

Two small corrections, both caught by re-checking the live site after the previous release. The trust page’s new heading (“Built to be checked. See for yourself.”) had been edited in one template while the page renders from another; it is now fixed at the real source and both outputs are regenerated together.

And a fix step could tell a reader to do something already done: a document with a missing title but a correctly declared language was shown “Give the document a title and set its language.” The step now adapts to the findings — title-only, language-only, or both — and each form lists only the repair steps that apply.

How this was reviewed: template and wording changes, pinned by three new tests. No score changed, no new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed.

v1.138.0

Reviewed 2026-08-29 · scope: every description of how scoring works was audited against the new legal-only model, and one false legal claim was removed. No scores changed.

After the scoring change, every page that describes the scoring was audited against it — on the principle that one error in copy undermines the whole tool. The audit found and removed one flatly false legal claim, stated in two places: that bookmarks are “required by ADA Title II for documents over a certain length.” No law requires bookmarks inside a single document, and both places now say so. The technical explainer’s per-category “How it’s scored” descriptions, the footer’s Scoring Rubric, the front page, and the copy-this-for-AI block were all updated to state the same, consistent rule: the score counts only WCAG 2.1 Level A/AA — the standard ADA Title II and the Illinois IITAA name — and WCAG 2.2’s added criteria are disclosed for manual review, never counted.

The independent validator was also verified end-to-end. Test documents with designed defects were run through veraPDF, and it flagged exactly the right rules — an unembedded font, a table header without a direction — and, correctly, said nothing about a document whose declared language contradicts its text, because the PDF/UA standard checks that a language is declared, not that it is true. That gap is precisely what this tool’s own language check covers. The installed veraPDF (1.30.1, server and development machines alike) is the current stable release; the 1.31 series is the project’s development stream, so no update is needed.

How this was reviewed: wording changes across explanatory pages, pinned by the existing tests; the locked score ledger and the legal-basis gate confirm no document’s score changed. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.137.0

Reviewed 2026-08-29 · scope: a failing verdict now names WCAG 2.1 — the standard the law cites — and the report shows one verdict banner instead of two. No scores changed.

A reader of a live report caught the last inconsistency of the day: the red verdict banner said the document “does not meet WCAG 2.2 Level AA” directly above the summary explaining that nothing beyond WCAG 2.1 is counted. Both statements were technically defensible; together they were confusing. Since every failure this tool can assert is a WCAG 2.1 criterion (a rule the test suite enforces mechanically), a failing verdict now says what matters plainly: “Does not meet WCAG 2.1 Level AA” — in the banner, in the verdict’s explanation, and in the report summary.

The report also showed two stacked verdict banners saying the same thing; the older one is retired, and its one unique line — how many criteria still need a quick manual review — moved into the summary that remains. One verdict, stated once, naming the standard the law names.

How this was reviewed: wording changes and one component removal, pinned by tests; the locked score ledger confirms no document’s score changed. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.136.0

Reviewed 2026-08-29 · scope: scores now measure WCAG 2.1 and nothing else. Most documents score the same or higher on re-analysis; none score lower.

The standard this tool is accountable to is the one the law names: WCAG 2.1 A/AA, via ADA Title II and the Illinois IITAA. This release audited every check in the scoring engine — for PDF, Word, PowerPoint, and Excel — against a single rule: a deduction must correspond to a failing WCAG 2.1 criterion the report can name, or it may not touch your grade. Fifteen checks measured best practices rather than the law — bookmarks in long documents, reading-order estimates, heading-level conventions, link wording such as “click here”, slide titles, sheet names, and similar. All of them still appear in your report, clearly labelled not scored, and none of them affect your grade any more.

The same audit worked in the other direction too: several genuine legal failures were being deducted without the report naming the criterion they break. They now carry explicit citations — a long document with no headings (WCAG 1.3.1), text painted outside the reading structure (1.3.1), a wrong or unusable language declaration (3.1.1), a filename standing in for the title (2.4.2), links assistive technology cannot reach (1.3.1). And a new automated gate re-verifies the whole rule on every code change: every point lost must name the law it broke, across all 167 test documents, or the build fails. The gate was deliberately sabotaged once to prove it fires.

How this was reviewed: scoring rules, criterion attributions, one new verification script, and updated tests — 183 pinned document scores moved, every one upward, none down, and each movement was reviewed and re-approved through the locked score ledger in the same commit. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.135.0

Reviewed 2026-08-29 · scope: the plan’s REQUIRED label is now earned per step, and the fix list explains its own arithmetic. Nothing new is received, sent, or stored.

A reader caught a contradiction worth fixing the same day: a report could say “3 criteria failing” while listing five fix steps, each stamped “Required by WCAG 2.1”. For some steps that label was simply wrong — bookmarks in a long document and the reading-order check raise your score and genuinely help readers, but they are not WCAG 2.1 criterion failures, and the bookmarks step’s own explanation said so directly beneath its label.

Now the label is earned: REQUIRED appears only on a step whose category actually produced one of the failing criteria in the report’s conformance verdict; every other step says RECOMMENDED; and a stored report from before conformance verdicts existed shows no label at all rather than an unverifiable claim. The plan’s first line now does the math for the reader: “3 of the 5 clear WCAG 2.1 criterion failures; the other 2 are recommended.” The change was verified against the exact report that surfaced the problem.

In the same pass, the summary’s phrase “everything 2.1 requires, and more” was removed — “and more” could be read as the grade including WCAG 2.2 extras, which it never does. The line now says plainly: “Nothing beyond WCAG 2.1 A/AA is counted — not the criteria WCAG 2.2 added, not PDF/UA.” And that guarantee is now enforced by tests that fail the build if any scored check ever cites a criterion that exists only in WCAG 2.2.

How this was reviewed: label logic and copy, both driven by data the report already carried, pinned by tests. No new input is parsed, no new request is made, no score changed, and no code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.134.0

Reviewed 2026-08-29 · scope: each optional PDF/UA finding now opens into two fix routes — source file or exported PDF. Nothing new is received, sent, or stored.

The optional-work section of the action plan lists what veraPDF — the PDF industry’s own validator — found on your document. Each of those findings now opens, only when you ask, into a short “How to fix” written both ways: in the source file (Word or InDesign, preventing the issue before export) and in the finished PDF (Adobe Acrobat, repairing it after). Whichever side of the export you work on, each item gives you a place to start.

The advice is deliberately cautious. It appears only for rules this tool can speak to confidently; a rule it does not recognize shows veraPDF’s own words and nothing more, because wrong advice underneath the referee’s verdict would be worse than none. The guidance for the PDF/UA identification flag says explicitly that it is a claim of conformance to be added last, not a repair. The feature was verified against the real agency report that prompted it: every one of its ten failing rules received both routes.

How this was reviewed: one pure text-mapping function and one template change, both exercised by new tests, including pins on the keyword ordering (several of veraPDF’s rule texts contain each other’s keywords). No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.133.0

Reviewed 2026-08-29 · scope: the required standard is named precisely (WCAG 2.1), veraPDF’s full verdict appears in the plan, and a same-day self-review fixed a scoring blind spot before any file hit it.

Precision over vagueness: what reports called “required by law” now says “Required by WCAG 2.1” — the exact standard the federal ADA Title II rule and the Illinois IITAA name — and everything optional is labelled a PDF/UA best practice. The action plan’s “Above and beyond” section now shows veraPDF’s complete verdict for the document: every failing PDF/UA rule with its ISO clause and how many times it occurs, a clean pass stated plainly, and an incomplete check’s error shown rather than hidden. None of it is counted in the grade, and the section says so.

The same day’s scoring changes were then re-reviewed line by line, and the review found and fixed four of its own mistakes. The most important: a one-row table with a leading header cell was being misread as a two-axis grid and docked for a missing header direction — exactly the kind of false positive this week’s work exists to prevent. A trap document was written first, confirmed to fail, and the fix confirmed against all 124 traps; a deliberate sabotage run proved the battery goes red if the new scoring rule is ever disabled. No stored document’s score changed. The other fixes: the two-standards summary was missing from the Detailed view it claimed to be in, optional fix advice was dropped from the plan’s optional-work list, and printing lost the collapsed document-properties table.

How this was reviewed: presentation and scoring-rule changes, three new test documents, and two extra encodings in an existing local test gate (the same file re-saved by qpdf two more ways, which must not change any verdict). No new input is parsed from visitors, no new request is made, and no code path that receives, sends, or stores anything changed. The server’s veraPDF was verified current (1.30.1, the latest stable release). No web address, stored record, or retention period changed.

v1.132.0

Reviewed 2026-08-29 · scope: each fix step now states whether the law requires it, and optional work is listed apart from the numbered steps. Nothing new is received, sent, or stored.

Two refinements to the report’s fix list. First, the link on a failing legal verdict pointed at the technical evidence rather than the fixes; it now goes straight to the action plan, where a reader who has just been told their document does not meet the law actually needs to be. Second, every numbered step now says REQUIRED BY LAW beside it. That label is true by construction rather than by promise: an item that only the PDF industry standard asks for carries no severity, so it can never become a numbered step in the first place.

Anything optional is now gathered at the end of the plan under “Above and beyond — not required by law”, with a plain statement that none of it affected the grade and that it is worth doing only if the author is also aiming at PDF/UA conformance. Keeping it out of the numbered list is the whole point: a number in this plan means a legal obligation, and folding optional work back in would undo the separation the last two releases established.

How this was reviewed: the change adds one anchor, one label and one grouped list, all rendering data the report already carried. No new input is parsed, no new request is made, no scoring rule changed and no document’s score moved. No web address, stored record, or retention period changed.

v1.131.0

Reviewed 2026-08-29 · scope: the legal verdict now leads every report, and two deductions no law requires were removed from the score. Seven documents score slightly higher; none score lower.

The first question a reviewer asks is “does this file meet the law?” — so every report now answers it first. Directly under the grade, in full width and large type: Required by WCAG 2.1 — the standard ADA Title II and the Illinois IITAA name, with the verdict for this document and the plain statement that this, and only this, is what the grade measures. Beneath it, in a deliberately quiet footnote, sits PDF/UA readiness — the PDF industry’s own standard, checked by an independent validator — marked not counted in your score. The previous release made that separation real; this one makes it impossible to miss.

A claim like that is only worth making if it is true everywhere, so every deduction the score is built from was checked against the law, and two were removed. Font embedding: no legal requirement asks for it — a substituted font still displays and still reads aloud — though the PDF industry standard does ask for it, so it is now reported without affecting the grade. Nested tables: harder to navigate, genuinely, but a properly marked-up nested table still conveys its relationships, so it too is reported rather than scored. Seven documents in the project’s own test set scored slightly higher; none scored lower and none changed letter grade. Three further judgment calls about heading rules were deliberately left as they are and written down publicly, so the decision is visible rather than quietly made.

How this was reviewed: the change adjusts scoring rules and adds one presentational panel that reads data the report already carried. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. Some documents score higher on re-analysis; none score lower. No web address, stored record, or retention period changed.

v1.130.0

Reviewed 2026-08-29 · scope: the score now measures only what the law requires; PDF/UA work is shown beside it and not counted. Some documents score slightly higher.

Two different rulebooks apply to a PDF, and only one of them is the law. ADA Title II and the Illinois IITAA both name WCAG 2.1 Level AA; PDF/UA is the PDF industry’s own standard — excellent practice, not a legal obligation. This report was already built on the legal standard, but one deduction had drifted across the line, and the wording admitted it: a finding was described as “a readiness gap rather than a confirmed failure” while still costing points. That is now resolved. Your grade measures the law. PDF/UA work is listed beside it, clearly marked as not counted.

The clearest case is the direction of table headers. The law asks that software be able to work out which header belongs to which cell. In a plain table whose headers run along a single edge, that is already clear from the table’s shape — so a missing direction is now reported as optional PDF/UA work and does not affect the grade. In a table with headers along the top and down the side, it genuinely cannot be worked out, and that remains a real legal failure and is still scored. Both report views now show the two groups under plain headings, and two test documents were added to hold the line from both sides. Two real documents scored one point higher as a result; neither changed letter grade, and the change was approved through the locked score ledger in the same commit.

How this was reviewed: the change adjusts scoring rules and how findings are grouped on the page. No new input is parsed, no new request is made, and no code path that receives, sends, or stores anything changed. Some documents score higher on re-analysis; none score lower. No web address, stored record, or retention period changed.

v1.129.0

Reviewed 2026-08-29 · scope: a new self-check that writes the same document every legal way and requires the same answer for all of them. Nothing new is received, sent, or stored.

Five times in two days, a document from an outside agency revealed the same kind of mistake: the file said something perfectly ordinary, but wrote it a legal way this checker had not seen before — and the checker read the packaging instead of the content. Each time, the wrong grade had already been given to a real person. The PDF standard allows many different ways to record the same fact, and every program that makes PDFs chooses differently, so waiting to be surprised is not a plan.

There is now a self-check that stops waiting. It builds one document, then rewrites the same content in every legal form of it, and requires the checker to return the identical grade and identical per-area verdicts for all of them. A difference means the checker is reading the packaging, and the check names exactly which form it cannot read. It found a real gap on its very first run, before any outside document arrived: one of the three legal ways to attach table-header directions had never been supported, and a table using it was marked down. That is now fixed, and this is the first problem of this kind the project caught by itself rather than being handed by an agency it had graded wrongly.

How this was reviewed: the check is a build-machine script that generates its own files; the fix widens where the checker looks for information already inside the document. No code path that receives, sends, or stores anything changed, and no existing document’s score moved. No web address, stored record, or retention period changed.

v1.128.0

Reviewed 2026-08-29 · scope: two table findings corrected, and a new check for a document that declares the wrong language. One document’s table score improves; a wrong language declaration now costs points.

A table’s header directions and cell spans can be stored as references to shared values rather than written out in place — permitted by the PDF standard, and how Word often writes them. This checker was reading the reference instead of the value it points at, which produced two wrong findings on the same well-built document: every header direction reported missing, and a perfectly regular table reported as having uneven columns. Both are fixed, and that document’s table score rises from 85 to 100 — matching the independent validator, which passed the file all along.

New check, from the same document: a file can declare the wrong language. This one is written in English but declares itself French, because a single French sentence in the Word original became the language of the entire PDF when it was exported. Every conformance checker passes it, because they confirm a language tag is present and correctly formed — not that it is true. A screen reader obeys the declaration and reads the whole English document with French pronunciation. Reports now say so, and the check is deliberately hard to trigger: it needs a substantial amount of text, a language it can actually recognise, and overwhelming evidence before it will say anything — and it stays silent for a document that correctly marks a foreign passage, which is a locked test.

Worth recording plainly: the document that exposed both problems is this project’s own reference example of an accessible PDF, and a test had asserted its language was fine for as long as the fixture has existed. It now asserts the truth. How this was reviewed: the changes read values already present in the file and one text heuristic; no new input is parsed and no new request is made. No code path that receives, sends, or stores anything changed. One stored table score improves on re-analysis; a document that declares a contradicted language now scores lower on that one category. No web address, stored record, or retention period changed.

v1.127.0

Reviewed 2026-08-29 · scope: reports now show which findings come from the independent validator rather than from this tool. Nothing new is received, sent, or stored.

Every PDF report has always been checked twice: once by this tool, and once by veraPDF — the validator published by the PDF Association, which nobody here wrote. Until now that second opinion sat in its own panel further down the page. It now appears beside the finding it supports, in both the plain-language plan and the detailed report, quoting veraPDF’s own sentence, its standard clause number, and how many checks failed. Anyone who wonders whether a finding is just this tool’s opinion can now see, in place, that an independent validator reached the same conclusion about their document.

Two safeguards keep that claim honest. First, where veraPDF is stricter than this tool — it fails something this tool does not even score — the report says exactly that, and marks it not counted in your score, because this tool never scores from another tool’s verdict. Second, silence is never presented as agreement: if veraPDF did not run, or ran and did not flag that point, nothing at all is shown. Both behaviours are locked by tests.

How this was reviewed: the change reads data the report already contained and renders it; no new input is parsed, no new request is made, and no score changed. No code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.126.0

Reviewed 2026-08-29 · scope: a real scoring bug, found in the state IT agency’s own reference documents — their file was right. One document’s grade improved; no other grade moved.

When a table has labels across the top and down the side, the corner cell labels both directions at once — and the PDF standard provides a third setting for exactly that case, “Both,” beside “Row” and “Column.” This checker recognised only two of the three. Because a table passes this check only when every header is marked, one correctly marked corner cell was enough to report the whole table as missing header directions and mark the document down. The bug was found in reference documents published by the Illinois Department of Innovation & Technology, and their file was right: every one of its seven headers was properly marked. That document now scores 100 instead of 89.

Two things were checked before changing anything. First, the file itself was read directly — not the checker’s own report — to confirm the markings really were there. Second, the other four documents from the same agency were re-examined the same way: their grades did not move, and two of them genuinely have no header directions anywhere in the file, which is a real gap under the PDF-specific standard even though the report already explains it may still satisfy WCAG. The fix-it advice was corrected as well: it had been steering authors away from the correct corner-cell marking. A new trap document locks the behavior, and was verified to fail against the old code with exactly the symptom the real document showed.

How this was reviewed: the change widens one comparison from two accepted values to three and corrects two lines of advice; no new input is parsed and no new code path exists. No code path that receives, sends, or stores anything changed. One stored grade improves on re-analysis; no grade worsens. No web address, stored record, or retention period changed.

v1.125.0

Reviewed 2026-08-29 · scope: twenty-five new tests that prove the accuracy alarms can actually ring. Nothing new is received, sent, or stored.

A checkpoint that has only ever been passed proves less than it seems — nothing shows the alarm works. Twenty-five new tests close that gap. The decision rules behind the score ledger, the twin rule, and the page generator were gathered into one shared piece of code, and sabotage tests now feed those rules exactly the failures they exist to catch: a score that drifted, a per-area verdict that moved, a “flawed twin” that somehow outscored its corrected copy, a page template with a typo in a number slot. Each must set off its alarm — and stay quiet when nothing is wrong.

Also pinned at the finest level: the exact line between a decorative speck and a real image (49 pixels is ignored, 50 is counted — the boundary behind last release’s test-document fix), and the rule that made a wrongly-graded real report teach the checker humility (“we could not read this page” and “these headings are empty” are different statements). And every public page of this site now has its own render test with a one-main-heading rule — the landmark screen-reader users navigate by, which an accessibility checker of all sites should keep on its own pages.

How this was reviewed: everything added is a test or a shared pure-logic module used by developer scripts; nothing runs on the server. No code path that receives, sends, or stores anything changed; no document’s score moved. No web address, stored record, or retention period changed.

v1.124.0

Reviewed 2026-08-29 · scope: trap documents for Word, PowerPoint, and Excel, and a new fairness rule across matched pairs. Nothing new is received, sent, or stored.

Until now the trap battery covered only PDFs. Fifteen new trap documents were hand-built in Word, PowerPoint, and Excel’s own file formats, each around one designed answer known in advance: bold large text posing as headings (must be called fake), pictures whose description panel was never opened (the count must read 0 of 2), a table whose header row was never marked, a document with no title (readers hear the filename instead), slides with no titles at all, and a workbook still named “Sheet1.” Each sits beside a done-right twin that must pass clean — and every one of the 115 traps returned its designed answer.

A new rule is also enforced across every matched pair, in both batteries: the flawed version may never outscore the correct one — overall, or in the very area the flaw lives in. That sounds obvious; it is also the kind of promise that only holds if something checks it, and now something does, on every code change. The honest note, as always: the one first-run failure was the checker being right — the “done-right” twins genuinely lacked a language declaration, so the title-and-language check docked them correctly; the test documents were fixed to declare one, the way real exports do. The locked-in score ledger grew to 151 documents, and the trust page’s inventory now lists all 115 traps.

How this was reviewed: the new traps come from a developer-only script that never runs on the server; the page changes are generated content; the technical guide (README) was corrected to current numbers. No code path that receives, sends, or stores anything changed; no real document’s score moved. No web address, stored record, or retention period changed.

v1.123.0

Reviewed 2026-08-29 · scope: three new accuracy gates on the checker itself, and a page explaining every gate. Nothing new is received, sent, or stored.

Three new automatic gates now stand between every code change and this site, each built to guarantee the accuracy of your report, not just that the software runs. First, a score ledger: the exact score and per-area verdict of all 136 test documents is written down and locked in; if any change to the software would move any of those grades, the change is blocked until a person looks at the movement and approves it. No grade can drift silently. Second, a re-save test: every trap document is re-saved by a different tool — same content, different file bytes — and must receive the identical grade, digit for digit; how a file happens to be packaged can never change its grade. Third, live-site sentinels: after each deployment, trap documents with known answers are uploaded to this very site — the disguised scan must still score 0, the perfect document must still score 100 — proving the checker actually running here answers the same as the one that passed testing.

Honest part, as always: the re-save test caught something on its very first run. The trap documents’ test images were unrealistically tiny — smaller than the checker’s own “ignore tiny decorations” rule — which made one measurement depend on file packaging. The trap documents were rebuilt with realistic images (no real design program ships art that small), three of their locked-in scores moved for that stated reason, and the movement was approved through the new ledger — the exact workflow it exists for. The trust page now also opens a plain-language list of every way this app is tested before anything ships, twelve gates with their schedules, one click from the front page.

How this was reviewed: the gates are developer and build-machine scripts plus one committed list of scores; the sentinel check uploads only the app’s own generated trap files, which are deleted after analysis like every upload. No code path that receives, sends, or stores anything changed; no real document’s score moved. No web address, stored record, or retention period changed.

v1.122.0

Reviewed 2026-08-28 · scope: the trap-document battery doubled to 100 and published as a full inventory; the battery now runs on every code change. Nothing new is received, sent, or stored.

The battery of trap documents grew from 50 to 100, with every designed answer returned correctly. The new fifty are modeled on the three programs behind most problem documents: Canva (beautiful posters exported with no reading structure at all, swarms of decorative shapes tagged as content, headlines that are pictures of words), InDesign (custom style names with and without their translation table, images whose description panel was never opened, header rows that are only styled bold), and Word (styled titles turned into one picture per line, “tables” drawn with spaces, files made with Print to PDF — which loses the reading structure, the title, and the language in one habit). Each family also includes documents done right, which must pass clean — and did.

Two additions make the battery harder to doubt. First, the trust page now opens a full inventory of all 100 documents — each with a plain-language label and its verdict — drawn from a manifest file that only a fully verified battery run can produce, with a self-check that counts the listed cards against the same number the page’s statistics use. Second, the battery now runs automatically on every code change: all 100 documents are rebuilt from scratch and re-judged, and a single wrong answer blocks the change from shipping. That is the battery’s real job — every failure is a defect to find and fix, in a test document or in the checker itself, exactly how it caught a real bug once before.

How this was reviewed: the trap documents are files produced by a developer-only script that never runs on the server; the inventory is generated page content; the “Can I trust this?” link simply moved to the front of the menu. No code path that receives, sends, or stores anything changed; no score moved on any real document. No web address, stored record, or retention period changed.

v1.121.0

Reviewed 2026-08-28 · scope: routine dependency safety update — the third-party building blocks the software stands on, brought current. Nothing new is received, sent, or stored.

GitHub runs an automatic advisory watcher over every project it hosts; it had flagged three third-party components this software builds on, and all three are now updated. The most serious flag was on a small file-unpacking helper that has no fixed version anywhere — so the cure was removal: the browser engine that renders web pages for page check-ups was updated to a version that no longer uses that helper at all, and it is gone from the software entirely. The toolkit the site’s buttons and menus are built from was brought to its patched line (the affected pieces were never used here to begin with), and a diagram-drawing tool used only while writing documentation — it never runs on the server — was updated past five advisories of its own.

How this was verified before release: the full battery of 2,922 self-checks passed; a real web page was rendered end-to-end on the updated browser engine with the accessibility scan completing cleanly; the site’s own pages were checked visually on the updated toolkit; and the component that reads Word, PowerPoint, and Excel files was deliberately left untouched — so no document’s score can shift from this update.

No code path that receives, sends, or stores anything changed. No web address, stored record, or retention period changed.

v1.120.0

Reviewed 2026-08-28 · scope: the trap-document battery grown to 50, and the trust page teaching the industry checklist by name. Nothing new is received, sent, or stored.

The battery of trap documents grew from 39 to 50, with every designed answer still returned correctly. The new traps round out coverage: a twelve-page document with no bookmarks beside an identical one with them (only the first is flagged); a one-page cover sheet that must not be punished for being small; an image marked decorative yet also tagged as content with no description (the tag is the author’s claim, so the missing description is flagged); pages rotated sideways; a mathematical formula with no spoken form beside one with it; a file carrying three unrelated defects at once, all three caught in one report; and a “grand good twin” that does everything right — and scores a perfect 100, proving the checker recognizes complete work, not just mistakes.

Worth recording, again, because it is the honest part: two of the three traps that initially failed were OUR traps built wrong, and the checker’s own rules won both arguments — a 48-character “cover sheet” really is too little text to call a readable document, and a “good” sample genuinely lacked the title-display setting another trap exists to catch. The third was a wording mismatch in the test itself. The trust page also now teaches the industry checklist by name: all 31 points of the Matterhorn Protocol, with the chain spelled out — the laws name WCAG, and Matterhorn (published by the same industry body that builds the veraPDF referee) translates WCAG into the 31 PDF-specific checks professionals test. The closing section now also says plainly what the app is built from: qpdf, veraPDF, and Matterhorn — tools used daily by remediators and certified specialists worldwide.

The trust page also gained three sections managers asked for. “Does it actually work?” shows the fail → fix → re-check → pass loop with live numbers — how many documents came back for a second check-up after repairs in the last 30 days, and how many climbed to an A: the same file failed and then passed is evidence no sales pitch can fake. An honest SiteImprove comparison: what SiteImprove does well (watching whole websites over time), what this does well (one file, one minute, free), and what each is not built for — both viable, different jobs. And the self-checks are now shown with examples in plain words (a scanned page with no readable text must score zero; the on-screen grade must match the downloaded report digit for digit), with the release rule stated: all must pass, one failure stops a release cold. The project-history section now links to the public change log and every numbered version on GitHub.

How this was reviewed: the new traps are files produced by a developer-only script that never runs on the server; the page changes are generated content. No code path that receives, sends, or stores anything changed; no score moved on any real document. No web address, stored record, or retention period changed.

v1.119.0

Reviewed 2026-08-28 · scope: the trust page joined the app properly, menu rows stopped breaking words in half, and the site gained a sitemap. Nothing new is received, sent, or stored.

The “Can I trust this?” page described below now lives fully inside the site — same menu, same look, same footer as every other page — rather than as a separate standalone file. Its content is still produced by the same generator that keeps its numbers current, and a test guarantees the page and the emailable document version stay word-for-word identical. Its project timeline now reads newest-first. The menus at the top and bottom of every page were also repaired: on mid-sized screens they had been squeezing entries onto two lines mid-word (“What’s / New”); rows now wrap as whole entries, never inside a word.

Housekeeping for search engines, prompted by an external report card on our page metadata (94 out of 100): the site now publishes a sitemap — a machine-readable list of its public pages, refreshed with a real date whenever the trust page is regenerated — and the robots file points to it. The sitemap lists only pages meant for the public; addresses the robots file asks crawlers to skip are deliberately excluded, and a test keeps the two files from ever contradicting each other. The site’s one-line description was shortened so search results and shared links stop cutting it off mid-sentence.

How this was reviewed: appearance, navigation, and two static text files for search engines. The page renders the same generator-produced content as before; no code runs on it, nothing is collected, and no score moved. No web address handling, stored record, or retention period changed.

v1.118.0

Reviewed 2026-08-28 · scope: a new “Can I trust this?” page on the site, and the trap-document battery doubled. Nothing new is received, sent, or stored.

The site now answers its own hardest question. A new page — “Can I trust this?” in the menu at the top and bottom of every screen — lays out, in plain English, how this checker is verified: the laws it serves (Title II of the ADA, the Illinois IITAA, and the WCAG rulebook both point to, each linked to its official source), the independent referee program that co-signs every report, the battery of trap documents built to fool it, the working sessions with internal and external accessibility specialists, and the grade disputes it lost in public and fixed the same day. A large dated stamp shows exactly when its numbers were pulled from the live system, so a reader can always see how fresh the page is; it is regenerated with current numbers at every release.

The trap battery described in the previous entry more than doubled, from 18 documents to 39 — adding, among others: headings that skip levels, headings that exist but say nothing, a title that is really a filename, header cells that point in no direction, bullets typed as plain text, tables nested inside tables, rows that do not line up, links that read “click here”, links no structure claims, footnotes that cannot be linked to, internal maps that loop in a circle, and hidden scripted actions — plus two “good twin” documents built CORRECTLY in unusual ways, which the checker must praise, not flag. All 39 held on the first run.

How this was reviewed: the new page is a plain, static page — it runs no code, collects nothing, and has no form; the program that refreshes it runs only on the developer’s machine, never on the server. A test now guarantees the page served by the site is byte-for-byte identical to the document version shared by email, so the two can never quietly disagree. No score changed, and no web address, stored record, or retention period changed beyond the one new page.

v1.117.0

Reviewed 2026-08-28 · scope: a new battery of trap documents built to catch this checker being wrong, one real bug it caught, and the fix. Nothing new is received, sent, or stored.

A fair question was put to us: how do we know this checker actually works? Running it on real documents only proves it agrees with itself. So we built eighteen small PDF documents designed to trick it, each one constructed so the correct answer is known in advance: a document with a perfect title and language that is secretly just one big photograph; a description field filled with nothing but spaces; internal structure that loops back on itself; headings that are really whole paragraphs in disguise; a form with no labels next to an identical form with proper ones; the leftovers design tools leave behind; and text carrying hostile computer code, to prove it stays harmless. The checker was tested against all eighteen, and the battery can be re-run by anyone at any time — the documents are rebuilt from scratch on every run, so there is nothing to go stale.

The battery caught one real bug, which is exactly what it is for. A picture description consisting only of blank spaces was being counted as a real description, because the check asked “is there anything in the field?” rather than “is there anything a person would hear?”. A screen reader reading three spaces says nothing. Fixed the same day, for both of the two fields a PDF can carry a description in — and we checked every real document in our test set: none of them has this defect, so no existing score changes. Three other traps initially failed and turned out to be OUR traps built wrong, not the checker — in each case the checker’s existing judgment held up under scrutiny, including its deliberate choice not to call a fully-tagged image document “a scan”, since such a document can legitimately carry its text in its descriptions.

How this was reviewed: the fix is one word’s worth of logic (trim before testing emptiness) in the reading of documents, plus two developer-only scripts that never run on the server. After the change, all 54 test documents — 36 real and 18 synthetic — pass every bookkeeping check, and not one real document’s score moved. No web address, stored record, or retention period changed.

v1.116.0

Reviewed 2026-08-28 · scope: a full security review of the day’s thirteen releases, the two small things it turned up, and a complete re-verification of every test document. Nothing new is received, sent, or stored.

At the end of a day with many releases, we reviewed all of them together as one body of change — every new way document content flows into a report was traced from the file to the screen, to the saved copy, and to the downloadable export. No security problem was found: text quoted from a document is always displayed through escaping that prevents it acting as code, the new internal walks over document structure are strictly bounded, and the additions to the status page are the same numbers it already published, written out readably. The two current advisories in our third-party libraries were checked and neither applies: one concerns a login form component this site does not use (and there are no logins), the other only runs while installing developer tooling, never while serving visitors.

The review did surface two small things, both fixed the same day. First, an earlier safeguard — ignoring pages whose text our reader provably cannot match to the document’s structure — protected headings but not two other checks, which could in principle describe a photo as text or misquote a link on such a page. Those checks now respect the same page-by-page verdict. Second, a purely cosmetic quirk: a document containing a particular word in a heading could make one line of its own report display a neutral marker instead of a failure mark, with the score and grade unaffected. The report now judges only its own wording, never the document’s, when choosing icons.

Alongside the review, every one of the 36 documents we keep for testing was run through the full pipeline with the bookkeeping checked document by document: the letter grade always matches the score, every category’s weighting matches the published table for its format, no score ever exceeds the ceiling its worst finding imposes, and the one deliberately-unsupported legacy file is refused exactly as designed. The same set was run before and after today’s fixes: not one document’s score or grade moved. One visible change rides along: on the checklist panel, “Issues found” is now marked in red rather than amber, so good and bad read at a glance. No web address, stored record, or retention period changed.

v1.115.0

Reviewed 2026-08-28 · scope: laying one panel out in a single column with larger step numbers. No wording, scoring, or data changed.

The last of three passes over the checklist panel described below. The first two settled which findings belong to which checkpoint and shaded alternate rows, but left the page reading unevenly — one column in places, two in others, and in one spot a shaded row that stopped halfway across. It now runs as a single column from the first checkpoint to the thirty-first, so every checkpoint is plainly its own row and the shading always crosses the full width.

Each checkpoint’s number is now printed large enough to see at a glance. That is worth the space here because the checklist is a numbered standard: the number is how someone matches a finding in this report against the same checkpoint in the industry document, or in the professional tools that evaluators use. The panel is longer than it was, which is the trade — thirty-one rows read top to bottom instead of tiled two across, and on a document with many findings that is what makes each step visible.

How this was reviewed: appearance only. The same checkpoints, the same wording, the same evidence, and the same rule that this section never shows a total or a pass mark. No new information is shown, collected, sent, or stored; no score moved; and no web address, stored record, or retention period changed.

v1.114.0

Reviewed 2026-08-28 · scope: shading alternate rows of one panel so each row reads as one thing. No wording, scoring, or data changed.

A follow-on to the change described below. That one settled which findings belong to which checkpoint; this one makes the line between checkpoints visible, by shading every other row very lightly. The care needed here is that a row on this panel is not simply a list item: a checkpoint with findings fills a row on its own, while two short ones sit side by side. Shading every other item would have tinted one half of a pair and left the other plain, which looks like a fault rather than a stripe, so the shading follows the actual row instead — both halves of a pair always match.

Worth recording because it is the kind of detail that gets waved through: the first shade we tried was three shades of grey away from the background, which is correct in principle and invisible in practice. It was rejected by looking at the rendered page rather than by reading the code, and replaced with the slightly stronger tone the rest of the report already uses for raised panels. Nothing else changed: the same checkpoints, the same wording, the same evidence, and the same rule that this section never shows a total or a pass mark.

How this was reviewed: appearance only — no new information is shown, collected, sent, or stored, and no score moved. No web address, stored record, or retention period changed.

v1.113.0

Reviewed 2026-08-28 · scope: the layout of one panel on the report, so a document with many findings can be read. No wording, scoring, or data changed.

Reports carry a section that lays this tool’s findings out against the PDF industry’s 31-point checklist. It was arranged in two columns, and a document with a lot of problems became hard to follow: a checkpoint carrying five long technical findings sat beside one carrying none, so half the page was empty and it took effort to see which findings belonged to which heading. A reader told us exactly that. A checkpoint that has findings now takes the full width of the page, with its findings directly underneath it and a line down the left tying them together; the short, clean checkpoints still sit two to a row, which is what they suit.

The findings themselves now line up in columns, so the long technical quotations start at the same place and a sentence that runs onto a second line stays indented instead of returning to the far left. Nothing about the content changed — the same checkpoints, the same wording, the same evidence, and the same rule that this section never shows a total or a pass mark, because a second number beside a grade is read as a second grade.

How this was reviewed: this release changes appearance only — no new information is shown, collected, sent, or stored, and no score moved. It was checked by eye as well as by test, rendered against a 246-page report carrying the same findings as the one that prompted the report. No web address, stored record, or retention period changed.

v1.112.0

Reviewed 2026-08-28 · scope: making the machine-readable version of this page easier for a person to read. Nothing new is received, sent, or stored.

This page has a plain-data version that monitoring tools read, and anyone can open it. It reported sizes only as exact byte counts — 58131922944 for the free space on the server’s disk, for instance — while the page you are reading has always shown the same figure as “54.1 GB”. The data version now carries both: the exact number, and the same number written the way people read it. Nothing was removed, and the exact counts remain the authoritative ones.

The two versions now share a single piece of code for turning a byte count into words, rather than each having its own. That is the whole point of the change: two separate versions of the same calculation eventually disagree, and a page saying one thing beside data saying another is worse than either alone. One consequence worth naming: a size that cannot be read now says “unknown” instead of showing as zero, because an unreadable figure should never look like an empty disk.

How this was reviewed: the added values are the same numbers already published, simply written out — they reveal nothing further about the server. This page’s privacy test pins the exact list of fields it is allowed to publish, so the two additions had to be reviewed and approved deliberately rather than slipping in unnoticed. No web address, stored record, or retention period changed, and no score changed.

v1.111.0

Reviewed 2026-08-28 · scope: images that are not in a document were being counted against it. One report goes from a C to an A. Nothing new is received, sent, or stored.

The remediator of a document told us that two images we had marked as needing a description “aren’t in the text”. That was correct. The document contains four pictures, and every one of them already carries a description. The two we complained about are not pictures in that report at all: they are leftovers from a page that was rebuilt at some point — a scrap of an older version left inside the file, sitting on no page, connected to nothing, and impossible for a screen reader to reach. We were counting them as if they were real content. The report scores 100 out of 100; we had given it 79.

The mistake was in how we decided whether a piece of a document is really part of it. We had been asking each piece on its own — does this one say who its parent is? — which is true even when the parent is itself an abandoned scrap. Whether something is really in a document depends on the path back to the top, so we now follow that path instead of taking a single step. This kind of leftover is completely ordinary: it is what design and layout programs leave behind when a page is remade after being prepared for accessibility, and this file was made in Canva. Nothing about the submitted document needed fixing.

How this was reviewed: we read the file’s own internal structure before changing any code, and confirmed the picture exactly — ten leftover markers of this kind in total, four belonging to real images, four already correctly ignored, and exactly two being wrongly counted. Because this changes scores, all 29 documents we keep for testing were re-measured: not one of them moved, because none of them carries leftovers like these. This is the second time in two days that someone questioned a grade and turned out to be right; both times we read the document rather than defending the score. No web address, stored record, or retention period changed.

v1.110.0

Reviewed 2026-08-28 · scope: how headings are judged, and one piece of advice that pointed the wrong way. Some documents will score lower. Nothing new is received, sent, or stored.

Someone asked us to check whether a report’s findings were actually correct, so we read the document itself rather than re-running the tool and trusting it. The findings held up — but one of them was far too kind. The report noted that the document’s headings “skip levels” and marked it down modestly for that. In truth, of its 96 headings, 19 contained no words at all, 14 were entire paragraphs marked as though they were headings, and 29 stopped in the middle of a word — things like “Population d” and “property crime a”. Only about a third were headings in any useful sense. Headings are how most screen-reader users move around a long document, in the way a sighted reader skims for bold titles; an outline like that leaves them landing on silence and half-sentences. The report was already listing those broken headings on screen and giving them no weight in the score. Now it weighs them.

The second fix is a piece of advice that sent people the wrong way. The same document was told that 13 of its lists were “missing” the part that holds each item’s text, and to go and add them. Nothing was missing: all 43 of them are there, spelled with one letter in the wrong case, and the document never tells software what that spelling means. Following the old advice would have meant hours of work rebuilding something that already existed. The report now says the parts are almost certainly present under the wrong name, and gives the actual repair — a single line in the document’s tag dictionary.

How this was reviewed, and one thing worth admitting. This release changes scores, so it was checked as one: all 27 documents we keep for exactly this purpose were re-measured, and 26 came out with precisely the same score and grade — the only one that moved is the report that prompted the question. Along the way our first attempt got it wrong: it dropped one of our known-good documents from a perfect score to a middling one. Rather than accept that, we looked, and found the cause was on our side — on certain pages the software we use to read PDFs cannot tell us which words belong to which heading, and five perfectly ordinary headings had looked empty as a result. Pages we cannot read properly are now left out of the count instead of being held against the document, because “we could not check this” and “this is broken” are different statements. No web address, stored record, or retention period changed.

v1.109.1

Reviewed 2026-08-28 · scope: a one-minute limit on the server’s front door that was cutting off long audits. One configuration line. Nothing new is received, sent, or stored.

The release described below gave long documents up to two minutes to be checked. When we tested that on the live site rather than assuming it, we found a second limit we had not changed: the program that answers web requests before passing them to the audit tool was still hanging up after one minute. A 246-page annual report that the tool now finishes in 59.8 seconds was being cut off at 61 — failing by a margin of about one second, which is why it looked intermittent. Nothing in our own records showed this, because the request was ended by the front door rather than by the tool, so there was nothing for the tool to write down.

The waiting screen people see in a browser was never affected: it asks for the work to start and then checks back every second, so no single request is ever long. What was affected is the way our own scanner — the one that checks published documents across agency websites — asks for an audit, because it waits for the whole answer in one go. The limit is now three minutes, and the same report comes back complete, with both of the independent standards checks included.

This is the second time a limit sitting in front of the tool has quietly broken something that worked, so we have recorded it the same way we recorded the first: the required setting is written down in the code alongside a test that fails the build if the audit is ever allowed to take longer than the front door will wait. The setting itself lives on the server rather than in the code, so the test cannot check the server — what it can do is make sure the two are never designed to disagree. No web address, stored record, or retention period changed, and no score changed.

v1.109.0

Reviewed 2026-08-28 · scope: why some long documents were refused instead of graded, and why the site sometimes reported itself as unwell when it was not. No scoring rule changed. Nothing new is received, sent, or stored.

Someone asked us to check a 246-page annual report and the tool refused it, saying the file was “too complex to analyze within the time limit”. That was wrong, and it was our fault rather than the document’s. We timed the document on the live server: the part that reads a PDF’s structure finished it in under two seconds. Nothing about the file was difficult. What actually happened is that the tool runs several checking programs on one small server, and it had been starting them all at the same moment. They crowded each other out, and one of them was stopped by its own stopwatch while it was sitting waiting for a turn — not while it was working. The message the author then received blamed their document, and an author who believes that goes off and breaks up a perfectly good report for no reason.

The same crowding explains something visitors saw at the top of the page: a red “audit server offline” mark, or an amber “degraded” one, during and after a large document was checked. The server was not offline. It was busy enough that its own health check could not get a word in, and — this is the part that made it look worse than it was — a failed health check was remembered for ten minutes, so the warning stayed up long after everything was working again. We confirmed that directly: one of the programs was answering normally in about two seconds while the page was still describing it as down.

Four things changed. The checks now run in order rather than all at once, so none of them can starve another. The heaviest program — the independent validator that checks a file against the PDF accessibility standard — now runs its two checks one after the other instead of together, which halves the memory a single audit needs. Long documents are given up to two minutes rather than one, and the waiting screen says so. And a failed health check is now re-tested after a minute instead of being remembered for ten, so the warning clears itself once the server is free. The refusal message was rewritten as well: it no longer tells you your document is too complex, it explains that this is usually about timing, and it asks you to try again before it suggests anything else.

How this was reviewed: this release adds nothing that receives, sends, or stores information. There is no new page, no new form field, no new address the tool talks to, no new program installed, and no change to what is recorded or how long it is kept. No scoring rule changed — a document that scored 78 yesterday scores 78 today. What changed is that documents which were previously refused now get a grade at all. The report in question now completes in about two seconds and scores 56 out of 100; it does have real accessibility problems, it simply could never be told so. We also checked our own records: only 17 audits have ever failed this way, three of them this document while we were investigating, so there is no backlog of wrongly-refused files to re-check.

v1.108.0

Reviewed 2026-08-27 · scope: better wording on one table finding, and an explanation of why two experts can disagree about it. No score changed. Nothing new is received, sent, or stored.

A table header can be marked correctly and still not say which way it points — whether it labels the column beneath it or the row beside it. The report was asking authors to set that direction without saying which value to use where, and in the step-by-step view it was telling people to “mark a header row” on documents whose header row was already marked. It now gives the actual rule: the cells along the top label what is beneath them, the cells down the left label what is across from them, and the empty corner cell needs nothing. It also points out that in Word you rarely need to do this by hand — tick the Header Row box and Word writes it for you.

The more useful addition is an explanation of something that confuses people regularly. An author may be told by one expert that their file is fully compliant, and by this report that something is missing, and both can be right — they are measuring against different rulebooks. The PDF-specific standard treats a header with no direction as a defect outright. The web content guidelines only ask that the connection between headers and data be workable by software, and do not insist on this particular way of doing it. The report now says so plainly, and ends with the practical point: setting the direction satisfies both, so the disagreement does not need settling.

This came from someone sending us a document and questioning the result — the second in two days. On the first, the tool was wrong and we changed it. On this one the tool was right, and we checked that properly before saying so: none of the 16 cells in that table carried the information in question, in any of the forms the standard allows. So this release changes wording only. The document scores exactly what it scored before, all 27 of our test documents are unchanged, and this finding is still reported as a “not yet ready” note rather than a declared failure — which is precisely what the new wording explains. No web address, stored record, or retention period changed.

v1.107.0

Reviewed 2026-08-27 · scope: one measurement we were getting wrong, and two places the report was overstating a problem. Some forms will score higher than before. Nothing new is received, sent, or stored.

Someone told us a score was wrong, and it was. A form built by the State’s own accessibility team was marked down for “reading order”, and its author said the document was fine. They were right. The check compares the order the document is tagged in against the order it is painted in — useful for ordinary documents, but meaningless for a form, because a form paints its field labels last no matter how carefully they are tagged. The better the form was built, the worse it scored. In this case the whole mark-down came from four labels reading “Order Date”, “City”, “State” and “ZIP”. The report now says plainly that it cannot judge reading order in a form, and tells you how to check it yourself: tab through the form and see whether the cursor moves the way a person would fill it in, then listen to it with a screen reader.

Two related over-statements are fixed as well. The report was drawing a red cross beside every line on a low-scoring card — including plain facts like how many pages the document has, and including its own note saying the measurement was not necessarily a problem. Those now show as neutral points. And the note about words being part of a picture was giving a full correction to authors who had already done the right thing by describing the image; it now says so, and marks itself as advice rather than a fault.

How this was reviewed: this release changes a score, so it was checked as one. Every one of the 27 documents we keep for exactly this purpose was re-measured, and each of them — apart from the form — came out with precisely the same score and grade as before. Being straight about the trade: a form that genuinely has a bad reading order will no longer lose points for it here, because the measurement could not tell a good form from a bad one. We would rather say “we cannot measure this, here is how to check it” than keep publishing a verdict we know is unreliable. No web address, stored record, or retention period changed.

v1.106.0

Reviewed 2026-08-27 · scope: where the note described just below appears, and a correction to what it said. Nothing new is received, sent, stored, or scored.

The note about lettering that may not be real text now also appears in the plain-language step list — the view most people actually read — instead of only in the detailed report. It leads by answering the question the report otherwise raises: what images? I never added one.

We also got the explanation wrong the first time, and would rather say so than quietly change it. The previous release stated that Word had flattened text into a picture, and told authors to remove the visual effect on that text and save the PDF again. Looking more closely at the document that prompted the work, its letterhead words were not a picture at all — they were letter shapes, traced outlines inside a piece of pasted artwork. No export setting brings those back. Someone following the old advice would have gone looking in Word for an effect that was never there. The note now explains that there are two different causes, and that only one of them can be repaired: lettering baked into a logo or letterhead cannot be recovered, so the same wording needs to appear as ordinary text elsewhere on the page, while text that merely carries an effect does come through as text once the effect is removed.

Two smaller corrections in the same spirit. The previous entry said every flagged item we inspected by hand was a genuine problem; checking the remaining ones found three genuine and one not (a solid coloured callout box behind a chart, whose text is perfectly readable). And the announcement about this feature was rewritten in place rather than shown again, because that release was never published to the live site — nobody had read the original wording. How it was reviewed: this release changes wording and where the wording appears. No web address, stored record, retention period, or document score changed, and all 27 test documents come out with exactly the same scores and grades as before.

v1.105.0

Reviewed 2026-08-27 · scope: one new thing the report can tell you about your document. Nothing new is received, sent, stored, or scored.

Some documents contain words that are not really words. When a PDF is made from Word, text that has a visual effect on it — a drop shadow, an outline, a glow, a reflection, or a colour that fades or is see-through — can be flattened into a picture, one picture per line. It still looks perfect on screen. But a screen reader has nothing to read out, find-on-this-page cannot search it, it will not rearrange itself when someone zooms in, and it goes blurry when magnified. A real board agenda is what brought this to light: the letterhead carrying the agency’s own name had become pictures, and nothing in the report said so.

The report now points this out when it sees it, and says what to do — which is the opposite of the old advice. Adding a description to the picture does not bring the words back; the repair belongs in the Word file, not in Acrobat. The note also gives you a check you can run yourself in about ten seconds: open the PDF and try to select those words with your mouse. If they highlight, they are real text. If nothing highlights, they are a picture. This is a problem no check inside Word can find, because in the Word file it is still ordinary text — it only becomes a picture at the moment the PDF is made.

How it was reviewed: this only looks at information the tool had already read out of the document — the recorded width and height of each picture — so nothing new is opened, run, sent, or kept, and no web address or stored record changed. It adds a note to the report and cannot change anyone’s score: all 27 test documents come out with exactly the same scores and grades as before. And because recognising a shape is a clue rather than a certainty, the note is deliberately worded as something to check rather than a failure it declares — a rule the automated tests enforce.

v1.104.0

Reviewed 2026-08-27 · scope: removing the notice described in the two entries below. Nothing replaced it, and nothing about what is received, sent, stored, or scored changed.

The notice that asked you to confirm you had read it — and held the page still until you did — has been withdrawn while we reconsider how it should look and work. Everything that existed for it is gone: the notice, the hold on the page, the block on checking or fixing a file, and the single date-and-time value it saved in your browser. Nothing was added in its place, so this release removes the only thing this service had ever asked your browser to remember on its own behalf. Uploading, checking, and fixing files all behave exactly as they did before that notice was written.

Worth stating plainly for this record: the two releases that introduced the notice were never put onto the live site. So nobody ever saw it, no browser ever saved anything for it, and there is nothing to undo or clean up on anyone’s machine — the entries below describe work that only ever existed in our source code.

What has not changed is the honesty this was meant to serve. Every report still carries the same message it did: this tool checks roughly 30–40% of accessibility problems, the same is true of every automated checker, the rest has to be checked by a person, and a good score does not mean the document is accessible — with links to the independent studies those figures come from. Only the notice that interrupted you was removed, not the disclosure itself.

v1.103.0

Reviewed 2026-08-26 · scope: how the notice described just below looks and behaves. Nothing about what is received, sent, stored, or scored changed — the acknowledgment still never leaves your browser.

The notice now holds the page still until you answer it: the page will not scroll, and the area behind it dims so it is obvious why. We also rewrote it in plain language — no jargon, no acronyms — because the point is that anyone can understand it. It says the tool finds some accessibility problems and not all of them, that checkers like this one (including the ones built into Adobe Acrobat and Microsoft Word) catch only about 30–40% of the problems in a document, and that the rest can only be found by a person opening the file and looking. Then the sentence the whole notice exists for: a good score means the document passed the checks a computer can run — it does not mean the document is accessible.

One earlier decision is reversed here, and we would rather say so than quietly change it. The previous release deliberately did not hold the page still, on the reasoning that trapping someone in a pop-up is itself an accessibility fault. Holding the page still changes that calculation: if the page cannot scroll, then a keyboard user could still tab onto things they can no longer reach, and someone using a screen reader would get no signal that the rest of the page is inactive. So the notice is now announced properly as a dialog, keeps the keyboard inside it, and is released by its own button — which is the standard, accessible way to do this, and is not the kind of trap the guidelines prohibit. There is no “close” or Escape shortcut on purpose, because the acknowledgment is required; the button you need is where the keyboard lands first.

Two things are guarded by automated test because failing at them would be serious and easy to miss: the page is released both when you acknowledge and if the notice ever goes away for any other reason, so a stuck, unscrollable page can never outlive it; and someone who has already acknowledged never has their page held still at all. No web address, stored record, retention period, or document score changed in this release.

v1.102.0

Reviewed 2026-08-26 · scope: a notice you have to acknowledge before checking a file, and a wider version of the same notice on every report. Nothing new is received, sent, or stored — the acknowledgment never leaves your browser.

Automated checkers can only test part of accessibility — roughly 30–40% of issues in independent testing, and that is true of every checker, including Adobe Acrobat’s, PAC, and Word’s. The rest is judgment no software can make: whether the text describing an image actually describes it, whether a screen reader reads the page in an order that makes sense, whether a complicated table can be navigated. So the tool now says so plainly and asks you to confirm you have read it before it will check a file. Every report carries the same split at every grade — not only the good ones, as before — with the studies behind the figures linked so you can read them yourself.

What that acknowledgment is, precisely: a single date-and-time value saved by your own browser, so the notice does not reappear for a week. It is not a cookie, it is never sent to us, and nothing about you is recorded — this service has no accounts and stores no identifiers. Being straight about what that means: it shows the notice was shown and required on a device, not who agreed to it. Recording that would mean identifying visitors, which we deliberately do not do. If the saved value is missing, unreadable, or has been tampered with, the notice simply comes back and the tool stays closed until it is acknowledged again.

The notice deliberately does not trap you in a pop-up. There is no dark overlay, nothing steals your keyboard, and the rest of the page — the FAQs, the technical details, the Matterhorn checklist — stays open to read without acknowledging anything. Only starting work on a file is held back. A pop-up that seized keyboard focus would be an accessibility fault in an accessibility tool, which is the one place it cannot be excused. No part of this release changed a web address, a stored record, how long anything is kept, or how any document is scored.

v1.101.0

Reviewed 2026-08-26 · scope: one explanatory card on the landing page's Matterhorn checklist. Nothing received, sent, stored, or scored differently.

The checklist now answers a fair question up front: why is it only about PDFs? The Matterhorn Protocol is the test model for PDF/UA, a standard written for the PDF format alone, so its checkpoints have no meaning for Word, PowerPoint, or Excel files — which this tool still checks fully, under their own per-format audits. The card says both halves out loud (each pinned by an automated test, so neither wrong reading can creep back), points at the technical-details page for the per-format checks, and links the protocol’s specification at the PDF Association — static, repository-authored copy whose only new outbound link opens the PDF Association’s own page.

v1.100.0

Reviewed 2026-08-26 · scope: two new endpoints that let the page show real progress while an audit runs. The checks themselves are the same code; policy v1.17 describes the one small retention nuance honestly.

Instead of one silent request, the page can now start an audit and ask the server how it’s going: which steps are running, which are done. The audit itself is the exact same pipeline — the code was moved, not copied, and the existing automated tests pass unchanged against it, which is the proof the two paths cannot drift. What the progress job holds is step states and, once finished, the same report the old endpoint would have returned — in server memory only, never on disk or in the database, deleted the moment the page collects it or after ten minutes.

  • HardenedNo identity, no guessing, no oracle. Progress jobs carry no account or address — like remediation, an unguessable token returned once is the only key, stored only as a cryptographic hash and compared in constant time. Asking about a wrong job and asking with a wrong key are deliberately indistinguishable. The store is capacity-capped, every job has a hard timeout, and results are handed over exactly once.
  • NoteProgress is observed, never invented. Each step’s state flips only when the pipeline actually reports it, and there is no percentage anywhere — the slow steps (the two veraPDF engine passes) expose none, and showing one would be fiction. An automated test pins that rule.

v1.99.1

Reviewed 2026-08-26 · scope: two lines of wording on the waiting screen. Nothing received, sent, stored, or scored differently.

The waiting screen’s two veraPDF lines now say “pass 1 of 2” and “pass 2 of 2, both run together.” The second half of that phrase is the honest part: the two passes run at the same time and the page hears nothing until both finish, so numbering alone would imply step-by-step progress the page cannot actually see.

v1.99.0

Reviewed 2026-08-26 · scope: what the page shows while an audit is running. Nothing new is received, sent, or stored; no scoring change.

A real report of an audit that “just spins” turned out to be a healthy 26-second run on a design-heavy PDF with no feedback on screen. The waiting screen now rotates through the actual checks being run, counts the seconds, and — after 15 and 60 seconds — explains truthfully why some documents take longer and that every step has a hard server-side timeout. The upload area also says up front that analysis can take up to a minute.

  • NoteHonesty was the review. The server reports nothing until an audit finishes, so the rotating list deliberately cycles instead of claiming step-by-step progress it cannot know, and it never marks a step complete. Screen-reader users get the same facts on a calmer 15-second cadence rather than an announcement every two and a half seconds.

v1.98.0

Reviewed 2026-08-26 · scope: the technical-details page and the audit page's technical section now share one body of text. Nothing about how the service runs, what it collects, or how long anything is kept changed.

The standalone technical-details page used to be written separately from the technical section on the audit page, and separately-written twins drift — an earlier release (v1.95.2) had to correct that page after it was found still describing a scoring method retired weeks before. Both surfaces now render the same underlying content, so they can never disagree again, and an automated test enforces it. The pieces the standalone page alone used to carry — the worked scoring example, the WCAG 2.2 alignment explanation, and the tool license table — moved into the shared content, so readers of either surface get all of it.

  • NoteNo new surface. This is a re-composition of pages already served, written by the maintainers in the repository — no new route, no new request, no new input handling, nothing new collected or stored.

v1.97.0

Reviewed 2026-08-26 · scope: a second automated check on every PDF audit — the veraPDF engine already in use, run a second time against the industry’s machine-testable WCAG rules. No new retention: the same single temporary copy, deleted in the same request.

PDF reports gain an independent second opinion: the veraPDF checker that already validates the PDF/UA standard now also runs the machine-testable subset of the WCAG accessibility rules — the same subset professional checkers verify by machine, including text contrast, which this tool’s own score deliberately does not compute. The rule file it checks against ships inside this application’s own source code (fetched once from the veraPDF project, version-matched to the installed engine, and verified against the production server before being wired in). The result appears as its own panel; it never changes a score.

  • HardenedThe new pass was reviewed as attack surface before shipping. It runs through the same guarded path as the existing check — application secrets stripped from the subprocess, a hard timeout, bounded output — and a document waiting in line is still never written to disk (an existing automated test caught the first draft weakening exactly that guarantee, and the design was fixed rather than the test). A tampered shared report stuffed with thousands of fake results displays at most twenty, saying how many were left out; the attack is replayed by an automated test.
  • NoteHonesty rules carried over. A clean result reads “No machine-detected failures” — never “Pass” and never a conformance claim, because most accessibility criteria still need human judgment. If the check cannot run, the report says “Did not run”; reports from before this release simply don’t show the section. An automated test pins that this second opinion can never move a score.

v1.96.0

Reviewed 2026-08-26 · scope: this policy page itself — a full accuracy read plus presentation changes (policy v1.15). Nothing about how the service runs, what it collects, or how long anything is kept changed.

This data-retention page was read end to end against the current system and reshaped for readability: the header now shows when the policy was last updated (checked automatically against § 14’s newest entry so the two can never disagree), the AI exclusion list in § 4 names companies and model families rather than version numbers that go stale, the retention table in § 7 is grouped into three color-coded tables with an at-a-glance duration chip per row, and this section now shows the most recent review expanded with the earlier history in one expandable list — every entry still on the page. Every retention figure, storage location, and wording pin (including the rules against overclaiming “no personal data”) carried over verbatim and is still enforced by the same automated tests.

  • NoteReviewed as attack surface anyway. The reshaped section renders the same repository-authored text through the same guarded path (a new child component receives entries only from the compiled data file — nothing user-supplied can reach it, and the automated check on the data file’s contents is unchanged). No new route, no new request, nothing new collected or stored.

v1.95.2

Reviewed 2026-08-26 · scope: corrections to one explanation page (the standalone technical-details tour). Nothing about how the service runs, what it collects, or how long anything is kept changed.

The diagram-led technical-details page is written separately from the audit page’s own technical section, and a full read-through found it had drifted further: its worked example of how the score is computed still described a calculation method retired in mid-July (v1.58.3) — under the current method, categories with nothing to check count in the document’s favor rather than being dropped — and it did not mention that the score is capped by the worst finding. The example now demonstrates both, and several smaller descriptions (which checks appear on Word/Excel reports, how Office files are parsed, which PDF/UA standards the checker validates) were brought up to date.

  • NoteWords only, again. Scores were always computed by the current method — only this page’s description of the method was out of date. No code that processes documents changed, no score can move, nothing new is collected, and no retention period changed.

v1.95.1

Reviewed 2026-08-26 · scope: a wording-accuracy pass across the explanation pages — nothing about how the service runs, what it collects, or how long anything is kept changed.

After the five releases above, every page that explains the tool was re-read against what the tool now actually does: the front page, the full technical-details page, this data-retention policy, and the project README. Descriptions that had fallen behind were corrected — for example, the checker validates the newer PDF/UA-2 standard when a document declares it, and Word/Excel theme colors are now checked for contrast, but several pages still described the older behavior. Two long-standing description errors were also found and fixed: two scoring categories displayed each other’s weights (the scoring itself was always right — the label on the card was wrong), and one section described a safety comparison in the remediation pipeline as checking three numbers when the code checks two.

  • NoteWords only. No code that processes documents changed, no score can move, nothing new is collected, and no retention period changed. The stored-report format is untouched — one deliberately unchanged label in the machine-readable export is kept because outside consumers may match its exact text.

v1.95.0

Reviewed 2026-08-26 · scope: deeper automated checks inside the Word and Excel analyzers — theme-based colors now checked for contrast, plus new counts of languages, floating objects, merged cells, and form fields. Nothing new is collected, no new way to reach the service, no retention period changed.

This release extends to Word and Excel the same discipline the last four releases applied to PDF. The headline change: colors that come from a document’s theme — which is how most Office text is actually colored — are now resolved and checked for contrast, where before the checker could only read colors written out explicitly and stayed silent on the rest. Several new counts (languages used, floating objects, merged table cells, form fields, hidden sheets) are disclosed on the report so a human reviewer knows where to look. Every new reader of document internals was reviewed as a new attack surface before shipping.

  • HardenedForged document internals hit validation walls. A document declaring kilobytes of junk as a “language” cannot push that junk into the report — only real language codes are collected; a spreadsheet declaring a million-entry color palette stops at a fixed cap; an out-of-range theme or palette reference is reported as unresolvable rather than guessed. Each attack is replayed by an automated test.
  • FixedAn independent adversarial review then found ten more weaknesses, all fixed before release. The most important protected documents from false accusations: an automatic Table of Contents could have been reported as a form control needing accessibility work, a corrupt color attribute could have fabricated a “white text on white background” failure, and a form field written the way Word actually writes them could have been missed entirely — producing a false “no form controls” claim. Each fix ships with an automated test that replays the mistake.
  • NoteHonesty over silence, again. Where a color still cannot be resolved (inherited from a style, or set to automatic), the report says contrast could not be evaluated there — it never pretends the check ran. Two real-world test spreadsheets moved from “could not check contrast” to a checked, passing result; no document’s score changed.

v1.94.0

Reviewed 2026-08-26 · scope: three deeper automated checks inside the PDF analyzer, plus a deliberate adversarial (red-team) review of every change since the “Did not run” release — thirteen weaknesses found that way were fixed before this release shipped. Nothing new is collected, no new way to reach the service, no retention period changed.

The analyzer now catches three problems that can hide behind a good-looking page: text that renders fine but extracts as unpronounceable symbols (a font problem screen readers cannot work around), visible text painted outside the document’s tag structure (a screen reader following the structure never meets it), and form fields the structure never points at. Before release, these and every other recent change were attacked on purpose — hostile documents, forged shared-report data, misleading edge cases — first by the developer’s own adversarial pass and then by an independent automated review.

  • FixedThe most important find protected YOU from a false accusation. A correctly tagged form written in a less common (but perfectly legal) internal layout could have been reported as “every form field unreachable” — a confirmed failure the document didn’t deserve. The reader now understands every legal layout, and an automated test replays the tricky ones.
  • HardenedHostile inputs hit hard limits. A document built to make the checker do enormous unnecessary work (an endless chain of tag-name mappings, footnote IDs megabytes long) and a forged shared report stuffed with thousands of fake checker results each now stop at a bounded cost, with tests that replay the attacks.
  • FixedHonesty details. Advisory notes the report itself waves off can no longer flip a checklist row to “issues found” or change a fix-plan headline; older shared reports no longer show a green “no issues detected” for checks that did not exist when they were made; and when a list is shortened for safety, the report now says how many entries were left out instead of staying silent.

v1.93.0

Reviewed 2026-08-26 · scope: a new collapsed section on report pages that regroups a report’s existing findings under the PDF industry’s 31-point checklist. Nothing new is collected, no new way to reach the service, no retention period changed.

Every PDF report now offers “Your document against the Matterhorn checklist”: the same findings the report already shows, regrouped under the 31 checkpoints of the PDF industry’s standard test model (the checklist the front page explains). It is computed in the reader’s browser from the report data the page already had — no new information is generated, requested, or stored, and reports shared before this release gain the section automatically because nothing about the stored data changed.

  • NoteWorded so it cannot be mistaken for a second grade. The section never shows a tally (“24 of 31”), a percentage, or the word “Pass” — automated tests fail the build if any of those ever appear. Checkpoints no software can judge always say “Needs human review”, and when the veraPDF check did not run, the checkpoints it covers say “Not machine-checked” rather than looking clean.
  • NoteNothing is silently dropped. A veraPDF finding that does not fit under one checkpoint is listed in its own “Other PDF/UA rules” block, and every line shows the rule’s own reference number and wording.

v1.92.0

Reviewed 2026-08-26 · scope: eight new automated checks inside the PDF analyzer, and a plain-language rewrite of the front page’s checklist section. Nothing new is collected, no new way to reach the service, no retention period changed.

The analyzer now checks more of the PDF accessibility standard by itself: formulas without a spoken alternative, footnotes without linkable IDs, heading tags that mix two labeling conventions, language declarations that are not usable codes (“english” instead of “en-US”), tag-name mappings that go in circles or redefine standard names, embedded audio/video and scripts (disclosed for human review, never auto-failed), and document “layers” that could switch content without the reader acting. The front page’s checklist section was also rewritten so a non-technical reader can follow it, and its coverage labels were updated to match the new checks.

  • NoteSame inputs, more scrutiny. Every new check reads the same document structure the analyzer already parsed; nothing new is fetched, executed, or stored, and every new finding renders through the same escaping pipelines as existing ones. Lists of unrecognized tag names are capped in size so a hostile file cannot bloat a report.
  • NoteExisting scores are untouched. Before release, every document in the reference collection was re-scored with the new checks in place: all 27 kept identical scores, grades, and verdicts. The new checks change results only for documents that actually carry the newly-detected problems.

v1.91.0

Reviewed 2026-08-25 · scope: two candor additions — PDF reports now say when the PDF/UA machine check did not run, and the front page lists the Matterhorn Protocol checkpoints with which layer checks each. Nothing new is collected, no new way to reach the service, no retention period changed.

Alongside its own analysis, this tool runs the open-source veraPDF validator on every PDF to check the machine-checkable rules of the PDF/UA standard. Until now, if that extra check could not run — the validator missing from the server, or briefly at capacity — its panel was simply left out of the report, and an absent panel can read as “nothing to report”. The report now says plainly: “PDF/UA-1 machine checks (veraPDF): Did not run”, that not run means not checked — never passed — and that the score is computed independently. The front page also gained the Matterhorn checklist: all 31 checkpoints of the PDF Association’s test model for PDF accessibility, each labeled with what checks it here — this tool’s own engine, the veraPDF pass, or human review for the checks no software anywhere can make.

  • NoteOnly fixed text was added. The new disclosure and the checklist render wording written in this repository — no uploaded document content and no visitor input appears in either. The one behavioral change is that a “did not run” marker is now included with PDF results instead of being left out; it carries no file paths or server details.
  • NoteOverclaiming is guarded by automated tests. The checklist must never label a human-judgment checkpoint (flicker, color and contrast, article threads) as machine-checked, and checkpoints this tool does not check in-house stay attributed to veraPDF. A deploy-time probe now also confirms the validator is working on the server, using the already-public status page.

v1.90.0

Reviewed 2026-08-25 · scope: the status page’s re-audit summary now counts public audits only. The change narrows what is published; nothing new is collected, no new way to reach the service, no retention period changed.

On its first live day the new re-audit summary was dominated by the automated fleet inventory, which re-scans the same unchanged documents on a schedule through the internal trusted-tool tier — so the “typical score change” read as zero and said nothing about the documents people actually fix. The summary now counts audits from the public tier only. Records whose tier is unknown (written before the tier was recorded) are excluded as well, so the figures build from when tier recording began rather than guessing — the same approach the trusted-tool volume figures have used since v1.86.0. The count of distinct documents checked is unchanged and still includes every tier.

  • NoteNarrower, not wider. The change removes rows from an aggregate; no new value reaches the published document or the page, and the selection rule is a fixed constant, not anything request-derived. Automated tests watch both directions: a trusted-tool run leaking into the summary would flip a published figure the tests check directly, and the distinct-document counts are pinned to keep including every tier.
  • NoteThe page says what it counts. The card’s own caption states that the figures come from public uploads only and that counting began when the request tier was first recorded; the data-retention policy records the clarification in § 14 (policy v1.14).

v1.89.1

Reviewed 2026-08-25 · scope: one field on one What’s New entry — the link to the status page navigated the wrong way and showed a “page not found” screen. No new way to reach the service, nothing new collected, no retention period changed.

The v1.89.0 update notice linked to the status page, but the link was missing the marker that tells the site to load that page as a full page rather than in-app — the status page is served directly by the server, not by the in-browser application, so the in-app route finds nothing and shows “page not found”. Typing the address directly always worked. Fixed within minutes of a visitor’s report by adding the marker, and an automated test now checks every update notice that links to the status page carries it.

  • FixedThe What’s New link to the status page opens the status page again. A navigation defect only — no data was wrong, exposed, or at risk; the status page itself was serving correctly throughout.

v1.89.0

Reviewed 2026-08-25 · scope: two new families of aggregate figures on the public status page — distinct documents checked, and whether re-checked documents improve. Nothing new is collected or stored; no new way to reach the service; no retention period changed.

The status page now answers a question its raw counts could not: do documents actually get fixed? It publishes how many distinct documents were checked per period, and — for the last 30 days — how many documents were checked more than once, how many of those improved, and the median score change. Every figure is computed inside the database from the usage records this policy already describes (§ 6, § 8), by grouping on the stored file name; only counts and one median come out. The addition is recorded in § 14 (policy v1.13).

  • NoteNo new exposure by construction, and proven by test. The grouping query can only return numbers — the file name is the grouping key and is never in its output; the content fingerprint is consumed by a distinct-count. An automated test plants a distinctive file name and two fingerprints, confirms the new figures counted them, and confirms neither value appears anywhere in the published document. The queries are parameterized throughout, and the web rendering receives nothing but numbers.
  • NoteSmall-sample withholding. When fewer than five documents were re-checked in the period, the rates and the median are withheld — over so few documents they would describe one visitor’s documents rather than a usage pattern. The counts themselves remain published, like every other aggregate on the page.
  • NoteResidual, accepted. The service has no accounts, so documents with the same file name — whoever uploaded them — are grouped together. Someone who already knows a document’s exact file name could, during quiet traffic, infer that its score moved by watching the counts change. What that reveals is an accessibility score and nothing else; anyone who holds the file itself can already obtain the exact score by auditing it, and the page has always published score information at this granularity in its per-period grade distributions.

v1.88.5

Reviewed 2026-08-25 · scope: two read-only views added to the staff log-reading script, and a calmer start-of-day wait in its error-log follower. No new way to reach the service, no new information collected, no retention period changed.

The staff script that reads the daily activity files (v1.88.1) can now show the same records grouped by document: one line per file audited two or more times, with how many runs, how many distinct versions, and the first and last score — the fix-and-recheck cycle the activity record already contains, made visible. A second view lists every audit of one document, oldest first, found by part of its file name or by its content fingerprint. Both are re-arrangements of records staff could already read in full, over the same staff-only SSH access.

  • NoteNo new exposure. The new views draw only on the existing daily files; nothing about what the site collects, keeps, or serves has changed. Automated tests pin what each view shows — including that failed audits are not counted in the grouped view.
  • UXWatching the error log before the day’s first error now reads calmly. The follower says the day’s file has not been created yet and waits for it, instead of surfacing a raw cannot open warning that looked like a failure.

v1.88.4

Reviewed 2026-08-23 · scope: one number in the staff log-reading script — its default view grew from the 50 most recent audits to the 500 most recent. No new way to reach the service, no new information collected, no retention period changed.

Run with no instructions, the staff script that reads the daily activity files (v1.88.1) now shows the 500 most recent audits rather than 50 — more history per look. It draws on the same files, over the same staff-only SSH access, as before; nothing about what the site collects, keeps, or serves has changed.

  • NoteNo new exposure. The larger view reads only the files staff could already read in full, and the site itself serves nothing new. An automated test now holds the default to exactly 500, so the number cannot drift unnoticed.

v1.88.3

Reviewed 2026-08-23 · scope: the staff log-reading script only — made easier to use for someone new to it. No new way to reach the service, no new information collected, no retention period changed.

The script staff use to read the daily activity files and the error log on the server (added in v1.88.1) now shows the most recent audits when run with no instructions, carries a built-in help page — including examples of how a date must be written — and accepts the words today and yesterday. It reads the same files, over the same staff-only SSH access, as before; nothing about what the site collects, keeps, or serves has changed.

  • NoteNo new exposure. The files are still readable only by a staff member logged in to the server, and the site itself serves nothing new. The review checked that the script’s new default view draws only on those same files.
  • FixedA mistyped date now stops the script with one clear message. Previously it could print a second, misleading error and in one case report success. An automated test now holds it to exactly one message.

v1.88.2

Reviewed 2026-08-23 · scope: a retention defect found by the previous release’s own self-report — finished remediation job rows were being kept longer than the policy says. Fixed; no new way to reach the service, no new data collected, no retention period changed.

The clean-up sweep’s new summary line (v1.88.1) reported on its first run that one of its steps had been failing quietly: the step that deletes finished remediation job records after 30 days. A database rule tying each job’s event history to the job record had blocked the deletion, so those records were never removed — the policy’s 30-day window (§ 7) was being exceeded rather than cut short. This release removes that rule; the event history is kept for its own seven-year period, as the policy describes, and the job records now leave on schedule.

  • FixedFinished remediation job records now leave after 30 days, as § 7 states. Previously none had ever been deleted; the oldest on the production server was 97 days old. Nothing about what a record holds changed, and the event history — the auditor’s trail — is untouched.
  • NoteWhy it was invisible until now. The failure was recorded internally but never written anywhere a person would see. The previous release’s one-line sweep report and error log surfaced it within a minute of deploying, which is what they were added for.

v1.88.1

Reviewed 2026-08-23 · scope: operator conveniences that followed the v1.88.0 review — a script for reading the log files over SSH, a one-line self-report from the retention sweep, and a database index. No new way to reach the service, no new data collected, no retention period changed.

Housekeeping on the record-keeping itself. The administrators can now read the daily activity files and the error log with one command, the nightly and five-minute clean-up sweep says what it did in the server’s own log, and the usage-metadata table gained an index so date-range questions no longer read the whole table.

  • OPSA log-reading script, for administrators only. It reads the files already described in this policy (§ 7, § 8) over the same SSH access the server has always required, and can lay them out as a table or copy them to the administrator’s own clipboard. Nothing new is published by the site and no new credential exists.
  • NoteNo new attack surface; posture re-verified. The sweep’s new summary lines carry counts and step names only; the index changes how quickly the table is read, not what it holds.

v1.88.0

Reviewed 2026-08-22 · scope: a new record of failed audits in the usage-metadata table, and a daily file of that table written on the server for auditors. No new way to reach the service; one new column; no retention period changed.

Two additions for the people who review this service rather than use it. An audit the tool attempted and could not complete is now recorded like any other audit — same fields, no score or grade, and a one-word reason that is never the error text. And each night the server writes the previous day’s usage-metadata rows to a file on its own disk, so a day can be reviewed without querying the database. The data-retention policy is at v1.12 to describe both.

  • NewFailed audits are recorded with a fixed reason. One column was added (audit_log.reason, migration 13) holding one of five codes: unreadable, timeout, fetch-failed, navigation-failed, internal. Error messages, which can embed a file name, an address or a library path, are never stored. Failure rows carry no content hash, so they can never satisfy the check that gates remediation behind a prior audit.
  • NewA daily activity file, on the server only. One CSV per calendar day, derived from the usage-metadata table, kept for the same 365 days and deleted on the same schedule — there is no separate setting to drift. Nothing on the site serves these files; they are readable only by the service’s own account on the server, and they are not part of the nightly backup. The writer quotes every field and neutralises spreadsheet formula injection, because a file name is user-chosen and the files are meant to be opened in Excel.
  • NewAn application error log, on the server only. The service now keeps a copy of its own error output — the error message and stack trace for each fault — as one file per day beside the activity files, for 30 days, so an unexpected error can be diagnosed quickly. It holds exactly what the process already wrote to its error stream: never the address of the person making a request, their browser identifier or a token; a file name, a page address, a library path or the address of a server the tool tried to reach can appear, and the policy says so. A day’s file stops growing at a fixed size, so a malfunction cannot fill the disk.
  • HardenedQuieter, safer logs. Two expected conditions that used to log a full stack trace each time — a page audit whose address turns out to be a download, and an over-sized upload — now log one line. Neither line carries an address, a token, a browser identifier or a request body; a test fails the build if one ever does.
  • NoteData-retention policy updated to v1.12. Section 7 lists the activity files and their window; section 8 says what a failed-audit row holds; section 8a shows the new column; the file name remains the one field that can carry personal information, and the policy continues to say so rather than claim otherwise.

v1.87.1

Reviewed 2026-08-21 · scope: wording and links on the landing-page “What’s New” notices. Nothing about what is collected, stored, or handled changed.

The update notice on the home page mentioned this security log but did not link to it; it now does. A couple of older notices that mentioned the service’s status page gained links to it as well. This is a navigation improvement only — no page, setting, stored record, or retention period changed, and the security log itself is unchanged content that the notice simply points to now.

  • UXUpdate notices now link to the pages they mention. An update that refers to this security log, the data-retention policy, the changelog, or the status page now carries a working link to it rather than leaving the reader to find it. Nothing about the notices’ content changed beyond adding the links.

v1.87.0

Reviewed 2026-08-21 · scope: which network interface the service listens on. No change to what is collected, stored, or how requests are handled.

The parts of the service that talk to the web server now accept connections only from the same machine, rather than from any network interface. The web server (nginx) already reaches them over the local loopback address, and no outside caller ever connects to them directly, so this removes an exposure without changing anything a visitor sees or does.

  • HardenedThe service listens on loopback only, in production. Both of the service’s own processes previously accepted connections on every network interface, with only the host firewall in front of them. They now bind to the local loopback address in production, so they are reachable only from the same machine — the web server proxies to them locally, and the automated fleet reaches the service through its public address, not the raw ports. Development is unchanged.
  • NoteConfined to this service. The change touches only this application’s own two processes; other applications sharing the same server are configured separately and were not altered. There was no change to the web-server configuration, to any shared credential, or to any other machine-wide setting.

v1.86.0

Reviewed 2026-08-21 · scope: a new status-page measure of trusted-tool request volume, plus the routine server maintenance carried out the same day. Nothing new is collected about people; no data-handling or retention period changed.

The service now reports how much of its audit volume came through its own internal trusted-tool tier — the automated fleet inventory — versus the public. This lets the internal access credential be watched: after it is renewed, its usage can be confirmed to match the fleet and nothing else. The measure records a property of the shared credential, not who made a request.

  • NewTrusted-tool request volume on the status page. One column was added to the usage-metadata table recording which tier each audit came through (trusted-tool or public), and the public status page now shows the trusted-tool totals. It is a tier flag on the shared service credential — never an identity, and there is still nowhere in that table for document content or for who made a request. Rows recorded before the change carry no tier and are simply not counted, rather than being guessed at.
  • NoteData-retention policy updated to v1.11. Section 8a documents the new column and names the request tier among the things the table can hold; section 14 records the change. No new table, no document content, and no change to any retention period.

Server maintenance carried out the same day

Alongside the change above, the server the service runs on received routine security maintenance. None of it changed how documents are checked, what is stored, or how long it is kept. It is recorded here to keep this log complete.

  • OPSInternal access credential renewed. The service uses one internal token so its own trusted tools can request audits at a higher rate. That token was replaced with a fresh one. It is tied to no person or account, and grants only a higher request rate — never access to a stored document, nor any bypass of the service’s other protections.
  • OPSDatabase and backup files restricted further. The database and its nightly backups were already limited to the account that runs the service; their permissions were tightened so no other account on the server can read them.
  • OPSUnused software removed, system patched. A print service that was installed but never used was removed, and the host operating system was brought to its current security-patch level.

v1.85.0

Reviewed 2026-08-20 · scope: the web framework this site is built on, and other third-party code it depends on. Nothing about how documents are checked, stored, or deleted changed.

Software this site is built from — not written here, but relied on here — had published security flaws. They were fixed by updating to the current versions. No page, setting, stored record, or retention period changed, and no document was affected. This entry exists because the update closed one flaw that was genuinely reachable on the live site rather than only possible in theory.

  • FixedA framework feature this site never uses was switched on anyway. The web framework ships an internal address for loading page fragments. This site does not use that feature, but the address was still answering on the live server — and in the version being run, it was the route through which a set of published flaws could be reached, the most serious allowing an attacker to run code on the server. The framework has been updated to the version that fixes it.
  • FixedOne flaw could have shown one visitor another visitor’s page data. The same framework version had a caching fault that could serve data prepared for one person to someone else. It is fixed by the same update. This was a fault in the framework’s own caching, not in how this service stores reports; a shared report link has always been readable by anyone holding the link, and that is unchanged.
  • FixedA developer tool with a serious flaw was removed from the build. A tool used only while writing the software carried a flaw allowing commands to be run on a developer’s own computer. It was confirmed never to have been included in the live site, so no visitor was ever exposed; it has been updated regardless.
  • NoteTwo known issues remain, deliberately and on the record. One is in a component with no fixed version published yet, and is only used when the software downloads a browser for its own internal testing. The other affects a sign-in form component this service does not contain — sign-in was removed entirely in v1.68.0. Neither is reachable here. They are listed rather than hidden so the count in this log matches what an outside scanner would report.

v1.84.0

Reviewed 2026-08-20 · scope: how much of a site update the home page shows. Nothing is collected, stored, sent, or deleted differently.

The notice at the top of the home page used to print a whole update — the most recent one ran to about ten lines — directly above the upload area, pushing the tool itself down the page. It now shows the opening few sentences and offers a link to read the rest. The What’s New page is unchanged: it still carries every update word for word, and it is linked from the header and the footer of every page.

  • UXShorter notice, same words. The shortening happens when the page is drawn; the update text itself is written once, in full, by the maintainers and is not edited or rewritten anywhere. The cut is always at the end of a sentence, and the link to the full text appears only when there is more to read.
  • NoteNothing about this touches your documents or your data. The text being shortened is site copy written in the project’s own source code before release. It is never anything you upload, anything read out of a document, or anything from your request — and this release adds no page, no setting, no stored information, and no outside service. The data-retention policy is unaffected and stays at v1.10.
  • HardenedChecked for the two ways this kind of change goes wrong. Shortening text can accidentally create markup out of a half-finished tag, or leave the notice empty; neither is possible here — the shortener only cuts a sentence off the end of the maintainers’ own text and always leaves at least one sentence, both pinned by tests. A display fault was also caught before release, in which two links could render touching each other with no space between.

v1.83.0

Reviewed 2026-08-20 · scope: accuracy fixes to what a PDF report says about links, reading order, and images. Nothing new is collected or sent; one more kind of document text appears in stored reports.

A shared report was re-verified against its document by an independent method, and every count in it held up. What did not hold up was some of the report's wording: the text it showed for each link had been read from the line around the link rather than from the link itself, links that carry no tag at all (a real barrier — screen readers never reach them) went unmentioned outside the technical PDF/UA panel, the reading-order card counted problem pages without naming them, and the image advice would have told an author to describe boxes of text as pictures, which hides that text from screen readers. All four are corrected.

  • FixedLink text comes from the link's own tag. Vague links (“here”) are caught even when the surrounding sentence is descriptive, a link split across two lines is no longer judged by its first word, and each flagged link names its page. Links with no tag are now a finding in their own right, in the score, in the conformance verdict, and in the fix steps.
  • NoteOne more kind of document text in stored reports. To point an author at the right text box, a report now keeps a short excerpt (up to 80 characters) from each image-tagged box of text, alongside the link text, image descriptions, and heading text it already kept. Like those, it is text from the document itself and can therefore contain whatever the document contains; it is shown only through the same escaping every other document-derived string passes through. Data-retention policy v1.9 → v1.10 (§ 8a).
  • HardenedProved on a real file, pinned by tests. The case that exposed the problem — a link whose rectangle also covers the start of the next sentence — is now a permanent test, along with the untagged-link census and the retag-not-describe guidance. Reports saved before this release are unchanged and behave exactly as they did; checking the document again produces the corrected report.

v1.82.1

Reviewed 2026-08-18 · scope: an analytics privacy correction. Less now leaves the browser; nothing new is collected.

Since 2026-08-15 this site's analytics deliberately count pages, not files: visiting an individual repair job or shared report is reported to ICJIA's self-hosted analytics server as the base page address only. The report page for web-page audits, added in v1.82.0, was left out of that rule — for part of one day, each visit reported the report's individual address, and the analytics dashboard showed one-visit rows per report.

  • FixedWeb-page audit reports now count only as /page-report. The address generalization covers the new page type, so a report's individual address no longer leaves the visitor's browser. What briefly leaked was the report's random identifier — the same one that appears in the shareable link; it names a stored report, not the person viewing it — and it was recorded only on ICJIA's own analytics server.
  • HardenedThe gap cannot quietly reopen. An automated test now discovers every per-file page type in the site's own source and fails the build unless the analytics generalization covers it, so a future page type added without the rule is caught before release rather than noticed on the dashboard.
  • NoteRows already recorded. Analytics history is never rewritten, so the few individual addresses recorded during the gap remain in past date ranges of ICJIA's own dashboard; every visit after this release counts as the base route. Data-retention policy v1.8 → v1.9 (§ 8a and § 9 name the third generalized route; § 14 discloses the gap).

v1.82.0

Reviewed 2026-08-18 · scope: a new report page for web-page audits. Nothing new is collected or sent.

Reports about web pages (produced for the fleet accessibility service, which checks both documents and the web pages that link to them) are stored with a shareable link. Those links pointed at a page of this site that had never been built, so every one of them showed “Page not found” while the report itself sat safely in storage. The page now exists, and because the link format did not change, every previously shared link began working the moment this release deployed.

  • FixOld links work retroactively. Nothing needed regenerating: the stored reports and their addresses were always valid — only the page that displays them was missing. Links keep their original 365-day expiry, and an expired link says so rather than pretending the report never existed.
  • HardenedDisplayed content is treated as untrusted. A page audit records the audited page's address, title, and the locations of problem elements — text that originates in someone else's website. The new report page renders all of it as inert text, and only ever turns an address into a clickable link when it is a plain http(s) URL. An automated test also fails the build if the link format and the page ever go out of step again.
  • NoteRead-only addition. The page displays reports through the same public report-lookup endpoint that document reports already use. Nothing new is collected, stored, or transmitted, and no server route, parameter, or dependency was added.

v1.81.0

Reviewed 2026-08-17 · scope: a scoring-accuracy fix inside the document analyzer. Nothing new is collected or sent.

A document could lose reading-order points for something no reader ever experiences: where in the drawing sequence its images were painted. Programs like Excel paint images last no matter where they belong on the page, so a correctly organized document with a logo at the top was marked down. The reading-order comparison now ignores images' paint position and judges only the order of the text — which is what a screen reader actually follows.

  • FixSame detection for real problems. Text that is genuinely tagged out of order is flagged exactly as before; only the meaningless image-paint signal was removed. The same reported document's other finding — table header cells missing their scope — was verified genuine and still stands.
  • NoteAnalysis-time change only. The fix lives in the read-only parser that examines an uploaded document. Nothing new is collected, stored, or transmitted; no route, parameter, or dependency was added. Previously saved reports keep their stored evaluation — a fresh audit of the same file picks up the corrected scoring.

v1.80.0

Reviewed 2026-08-17 · scope: making multi-file results visible, in the browser only. Nothing new is collected or sent.

When several files were checked at once, each file's report was there — but the row for switching between them was so faint that a person who uploaded two files reported seeing only the first. The switcher is now a row of clearly visible report cards, one per file, each showing the file's grade and score. The upload area also now accepts the five files its label has always promised (the enforced limit was three).

  • UXSame results, actually findable. Nothing about the analysis changed — each file is still checked by the same server endpoint under the same limits; only the way finished reports are presented in the browser changed.
  • NoteNo new information is collected, stored, or transmitted. The change is entirely in the page's own display code. No route, parameter, or dependency was added, and rate limits and file-size caps are unchanged.

v1.79.0

Reviewed 2026-08-17 · scope: a scoring-accuracy fix inside the document analyzers. Nothing new is collected or sent.

Two documents that Adobe's own preflight tools pass were being marked down here for “fonts not embedded.” Both flags were false alarms: in one file the un-embedded font is only ever used to draw single space characters (a space paints nothing and cannot be garbled); in the other, the flagged fonts are leftover bookkeeping from Acrobat's own repair process — no page actually uses them. The check now looks at what the document really displays: a font is only flagged when it is both un-embedded and used to show visible text, which is how Adobe's tools evaluate it.

  • FixFewer false alarms, same protection. A font that genuinely displays text without being embedded is still flagged exactly as before. When the tool cannot tell how a font is used — including on reports saved before this release — it keeps the cautious old behavior and flags it.
  • NoteAnalysis-time change only. The fix lives in the two read-only parsers that examine an uploaded document. Nothing new is collected, stored, or transmitted; no route, parameter, or dependency was added. Previously saved reports keep their stored contents unchanged — a fresh audit of the same file is what picks up the corrected evaluation.

v1.78.1

Reviewed 2026-08-16 · scope: making saved audit answers agree with their report pages. Nothing new is collected or sent.

When the same document is checked again — typically by an agency's automated document inventory — the tool answers from its saved copy instead of re-auditing. Those saved answers carried the score computed on the day the document was first audited, even though the scoring rules have been refined since, while the report link in the very same answer already showed the up-to-date number. An inventory could therefore print one score beside a report page saying another.

  • FixSaved answers are re-scored under the current rules before they are returned — the same re-scoring report pages have applied on read since v1.58.4 — so an inventory cell and the report it links to can no longer disagree.
  • NoteStored records are not rewritten. The saved report stays exactly as computed on its audit date; the corrected score is derived at answer time. Nothing new is collected, stored, or transmitted, and no route or parameter was added.

v1.78.0

Reviewed 2026-08-16 · scope: showing already-recorded document information beside the fix steps. Nothing new is collected or sent.

Readers of an audit report may not know what program made the document or when it was created — information the audit has always recorded and shown in its technical section. The report's plain-language view now shows it up front: an “About this document” card lists everything the file records about itself (the program that made it, author, created and last-modified dates, page count, and so on), says Not set where the file is silent, and closes with a sentence naming which program the fix steps are written for and why. The detailed view's existing metadata panel carries the same closing sentence.

  • UXSame information, more visible. Every field shown was already extracted, stored, and displayed by the audit; the new card re-presents it where the fix steps are read, so the steps' choice of program is explained rather than implied.
  • NoteNo new information is collected, stored, or transmitted. The card renders in the reader's own browser from the stored report. Records keep the same fields as before; nothing about retention changes.

v1.77.0

Reviewed 2026-08-16 · scope: fix-step instructions that adapt to the program a PDF was made with. Nothing new is collected or sent.

The audit report's step-by-step fix instructions were written for Microsoft Word, but many agency documents — annual reports especially — are laid out in Adobe InDesign, whose menus are entirely different. The report now checks a piece of information the audit has always recorded and shown (the “Source Application” stored inside the PDF by the program that made it) and, when it names InDesign, shows InDesign instructions instead.

  • NewInstructions for InDesign-made PDFs. Each fix now carries the InDesign menu path, verified against Adobe's current documentation on 2026-08-16. If the stored information is missing or names any other program, the report shows exactly what it showed before.
  • NoteNo new information is collected, stored, or transmitted. The choice of instructions happens in the reader's own browser, using a field the audit already stored and already displayed. Records keep the same fields as before; nothing about retention changes.

v1.76.0

Reviewed 2026-08-15 · scope: what the page-view counter is told. Less is now sent; nothing new is collected anywhere.

The self-hosted page-view counter added in v1.72.0 was being told more than it needs. Two reductions, both applied before anything leaves the visitor's browser:

  • HardenedWeb addresses are generalized before they are counted. Every repair job and every shared report has its own web address containing a long per-file code; the counter now records only the base page — /remediate, /report — so per-file addresses never reach the analytics server, and the dashboard counts how much each feature is used rather than accumulating one-visit rows per file.
  • FixedQuery strings are no longer sent at all. The stock counting script includes the page's full address in its report — on a repair-result page that full address contains the one-time download code for the repaired file. Both servers involved belong to ICJIA, but the analytics server has no business holding download codes; the reported address is now built from the page path alone, so no query string of any page can leave with the count.

The data-retention policy moved to v1.8 to record this: § 14 has the dated entry, and §§ 8a and 9 now describe the generalized address. What is recorded per page view is otherwise unchanged, and audits themselves still do not pass through analytics at all.

v1.75.4

Reviewed 2026-08-15 · scope: the wording of the source-document advice. Nothing about what the tool does, collects, or stores changed.

The card that recommends fixing the original document made a few promises stronger than the truth, and they were softened to match it. “The PDF inherits that structure automatically and no remediation is needed” now says a PDF exported with tagging turned on carries the structure with it, and that in most cases no further remediation is needed — with a reminder to re-check the exported PDF here to confirm. The tips about Microsoft Office's built-in accessibility checker no longer say it “finds and fixes most issues” (it finds many common ones and offers fixes — no automated checker covers most of the job, which is this tool's own standing caveat about itself). And a line claiming the “#1 cause” of PDFs needing repair, a statistic this tool does not measure, now calls it the classic cause instead.

v1.75.3

Reviewed 2026-08-15 · scope: the advice shown when an automatic repair fails. Nothing about what the tool does, collects, or stores changed.

When the automatic repair can't improve a PDF, the page now says what accessibility practitioners consider the honest first answer: go back to the source document if it still exists — fix accessibility in Word (or PowerPoint, InDesign, Google Docs), re-export to PDF with tagging turned on, and re-check here. Repairing a finished PDF, with this tool or any other, is the last resort, and the page now says so in those words, with the step-by-step source-document guidance shown on this failure page too (it previously appeared only after a successful repair). One inaccuracy was corrected in the same pass: scanned documents were listed as a common reason repairs fail, but in fact they normally complete with a very low score rather than fail — the list now names the real reasons, including the safeguard that discards a repair attempt entirely rather than hand back a file that scored worse.

v1.75.2

Reviewed 2026-08-15 · scope: one word in the score notice. Nothing about what the tool does, collects, or stores changed.

The amber notice under good-looking scores said “Even a perfect score is not a guarantee.” It appears for A and B grades alike, so “perfect” overstated its own trigger; it now says “Even a high score is not a guarantee” everywhere it appears — the report, the downloaded copy, the printout, and the landing-page announcement, which was corrected in place rather than re-shown to people who had already dismissed it. This entry is the on-the-record note of that in-place correction; dated entries below keep their original wording.

v1.75.1

Reviewed 2026-08-15 · scope: a display fix on two pages. Nothing about what the tool does, collects, or stores changed.

On this page and the technical-details page, the “← Back” button was drawn on top of the small heading line beneath it — a layout slip introduced by a styling-framework upgrade earlier this year, reported by a reader with a screenshot. The spacing is now stated explicitly, the same gap is verified by an automated test on both pages so the upgrade path can't re-break it silently, and a sweep confirmed no other page uses the pattern that broke.

v1.75.0

Reviewed 2026-08-15 · scope: the fix steps and the repair results were made to tell the same story as the audit. Wording and reporting only — scoring, storage, and retention are unchanged.

A user's fact sheet surfaced two honesty gaps. Its text was perfectly readable by screen readers — the only text-layer flag was three fonts not packed into the file, a minor finish item — yet the fix list showed the step written for scanned documents (“…a picture of text”), advice that was both alarming and wrong to follow. One check covers several different text problems, and the fix step now describes the one the audit actually found: embed the fonts, add the missing hidden tags, change the security setting that locks screen readers out, or run text recognition on a scan — whichever it saw.

  • FixedRepair results now account for every finding. After an automatic repair, each issue the audit flagged is listed with exactly one outcome — fixed, improved but not fully fixed, no change, got worse, or newly visible — with its before-and-after score. A finding the repair could not touch now says so in plain words instead of sitting silently in a list; previously the same item could even appear as both “fully fixed” and “still outstanding” at once.
  • NoteNothing about scoring changed. A document whose text genuinely cannot be read still receives the lowest possible score — the change is that a small finish item is no longer described as that catastrophe, and the audit report and the repair results now use the same plain-language names for each finding.

v1.74.1

Reviewed 2026-08-14 · scope: the not-a-guarantee notice was made more prominent. Presentation only — its words, and everything else, are unchanged.

Feedback from use: readers were stopping at the green “ready to publish” line and missing the notice below it — and that notice carries the single most important caveat on the page: a high automated score is not the end of the accessibility work. So the notice now leads with a solid amber banner (dark text on amber, readable in both themes) and sits directly under the verdict line, above the progress meter, on both report views, the before/after cards, and the downloadable report — the reassuring parts of the page can no longer be reached without passing it. Not a word of it changed, and nothing about what the tool does, collects, or stores changed either.

v1.74.0

Reviewed 2026-08-14 · scope: links added to the printer-friendly action plan. No storage, retention, or behavior change beyond the printed page itself.

The printer-friendly action plan now links every accessibility rule it cites to the exact page of the W3C standard that explains it — and because a link on paper can't be clicked, printing writes each web address out in full next to the rule, so a reader can type it in. The list of rules the tool never machine-checks links out the same way, and the page's footer names the address of the full standard.

  • HardenedAddresses from stored reports are checked before they print — on a shared report page, the rule addresses arrive from stored data, so each one is verified to be an ordinary web address before it is rendered; anything else is dropped rather than linked or printed. An automated test pins this.
  • NoteThe printed page stays completely self-contained: it runs no scripts and loads nothing from the network — a link on it is text until a reader chooses to follow it from their browser.

v1.73.2

Reviewed 2026-08-14 · scope: a truth pass over the pages that explain scoring. Wording only — the scoring itself did not change today.

A reader asked whether the front page's claims were still accurate, and the check widened into every page that explains how scores work. The scoring engine was improved in early August so that a category that doesn't apply to a document (no tables, no images) counts in the document's favor instead of being dropped — but four explanatory pages still described the old dropped-and-rebalanced arithmetic. Those pages (the technical explainer, the scoring-rubric panel, the methodology note on every report, and the machine-readable summaries) now describe the arithmetic the engine actually performs, and say plainly that the old method was removed because it made simple documents look worse than they were.

  • FixedThe explanation now matches the engine — every page that describes scoring states the current rule: a check that doesn't apply counts as passing; only checks the tool genuinely could not run sit outside the score; and the score is capped by the worst open finding, with the letter grade following the score.
  • NoteThe front page's operational claims were verified against the code, and held — the repair pipeline and its no-lower-score guarantee, uploads processed in memory, repaired files deleted on first download or after 30 minutes with the deletion then verified, and a usage history that names no person. One tile was extended rather than corrected: the no-AI tile now also mentions the self-hosted page-view counter added in v1.72.0, so it cannot be read as denying it.

v1.73.1

Reviewed 2026-08-14 · scope: a follow-up to the notice below, plus a small wording pass. No storage or security control changed.

A follow-up to the v1.73.0 notice described just below. That notice appears with high grades (A or B) — the reports most likely to be read as finished. This release completes the rule for every other grade: reports graded C, D, or F now carry a compact one-line reminder in the same place — whatever the grade, automated checks are only part of the job; a person still has to review the document. So no report, at any grade, is silent about the human half of accessibility. The same reminder rides the downloadable report file, and the share-by-email text now includes its one-line caveat at every grade rather than only high ones.

Also on the record for completeness: a few phrases across the site and reports that described a good result as “strong” now say “high” (a high score, a high grade, a high mark). That rewording also reached the v1.73.0 entry just below — it was corrected before that version ever went live, so no visitor or auditor saw the earlier phrasing; this entry is the disclosure of that correction. Earlier dated entries in this history keep their original wording, as always. Nothing about what the tool does, collects, or stores changed in any way.

v1.73.0

Reviewed 2026-08-14 · scope: a new warning shown beside high scores. Nothing about audits, uploads, storage, or the database changed.

Reports with a high grade (A or B) now show a prominent amber notice directly under the score: even a perfect score is not a guarantee. Automated checking can verify that accessibility structure is present — a title, a document language, tags, text descriptions on images, table headers — but it cannot judge whether any of it is right, and it cannot tell you the document actually works with a screen reader. Only a person can. The notice splits the work into the half software finished (which is what the score measures) and the half a person still has to do, says how many accessibility criteria were never machine-checked at all for that document, and points to the report's “Still worth checking by hand” checklist.

  • NewThe warning follows the score wherever the score goes — both report views, the before/after cards on the automatic-fix page, the printer-friendly action plan, the downloadable report file, and the share-by-email text all carry it, and one shared rule decides when it appears, so the surfaces cannot fall out of step.
  • NoteDeliberately no percentage — the notice never says “automated tools catch a third of issues”. A figure beside a letter grade tends to be read as the grade (a lesson this tool has already paid for), and a borrowed statistic would be someone else's number about someone else's tool. The only number shown is this audit's own count of criteria it never machine-checked, and an automated test keeps any percentage from appearing there.
  • NoteNo new information is collected and nothing new is stored — the notice is assembled entirely from the audit result the report already contains, and its text is written in this repository, never taken from the document or from any visitor.

v1.72.0

Reviewed 2026-08-14 · scope: the addition of page-view analytics — what the counter can see, where it reports, and what the browser is now allowed to talk to. Nothing about audits, uploads, or the database changed.

The site now counts page views with Plausible, an open-source, cookie-free analytics tool — self-hosted by ICJIA on its own server (plausible.icjia.cloud), so no commercial analytics provider, ad network, or tracker receives anything. This review covers the two things that change: what is recorded about a visit, and the one new network destination the browser is permitted.

  • NewWhat the counter records — and what it cannot do — per page view: the page address, where the visitor came from, browser and operating-system family, device type, and country/region. No cookie is set and no IP address or browser signature is stored; views within one day are linked by a code that is scrambled afresh every 24 hours, so the counter cannot recognize a returning visitor tomorrow and cannot follow anyone to another website. Nothing about an uploaded document is ever included — audits do not pass through analytics at all.
  • HardenedThe browser's allow-list grew by exactly one name, and a test holds it there — the Content-Security-Policy, which previously let the site talk only to itself, now additionally allows the analytics server, in the two narrow permissions the counter needs (loading the script, and reporting a view). An automated test asserts this is the only outside address in the entire policy, so a second one cannot appear without a test failing. The protection against injected scripts is unchanged.
  • NoteThe audit application never touches the data — the visitor's browser reports straight to the analytics server; nothing is routed through, stored on, or forwarded by this tool's own server. The data-retention policy moves to v1.7 and documents the new store in §§ 7, 8, 8a and 9 — including re-marking § 8a's "no analytics" verification row as qualified rather than leaving it to overclaim.

v1.71.0

Reviewed 2026-08-13 · scope: record-keeping about the service's own behaviour, after a false alarm. No new findings; nothing about what is stored, how it is used, or how long it is kept changed.

The service was reported as being offline on August 12. It was not, and the reason it took so long to establish that has now been fixed. The tool had been running continuously for days, nothing had crashed, and it answered a real document check three minutes before anyone looked into it. What had actually happened is that a large automated run — the periodic check of documents across agency websites — was making requests far faster than the tool allows unidentified callers to, so the tool slowed it down, exactly as designed. From the other end that looks the same as a server that has stopped answering. The awkward part was that the tool kept no record of having slowed anything down, so the one fact that would have answered the question in seconds had to be pieced together from indirect evidence.

The tool now writes a note to its own log whenever it slows a caller down, and says plainly when a caller's access key was not recognised. That second point is the one that mattered: a run using a wrong key and a run using no key at all behaved identically — both were quietly treated as anonymous and slowed — and now they are told apart and named. These notes deliberately record no information about who was calling. No network address, no access key, no browser identifier. This service keeps no record of the people who use it, and its speed limits are tracked only in memory and forgotten when it restarts; writing that information into a log file would put it on disk and contradict the retention rules described on this page. What gets written is the kind of request, the limit that applied, and whether a key was recognised — enough to explain a slowdown, and nothing about the person who experienced it.

The service-status page now also reports whether the access key for trusted automated systems is in place. If that key is ever lost — a server restart in the wrong conditions is enough to lose it — every caller, including the agency's own document sweep, is silently limited to the slow lane, while every other indicator on the status page continues to look perfectly healthy. That state now shows on the status page and triggers the same automatic alert already used for other problems, so it is noticed rather than discovered weeks later. The page reports only whether the key is present or absent — never the key itself, nor any part of it. Nothing about what is stored, how it is used, or how long it is kept changed in this release, and no part of the service gained new exposure: no new address to visit, no new information to submit, and no change to the speed limits themselves.

v1.70.0

Reviewed 2026-08-13 · scope: one numeric raise to the file-size limit. No new findings; nothing about what is stored, how it is used, or how long it is kept changed.

The largest file the tool will accept goes from 15 MB to 25 MB. A large audit of agency documents ran on August 12–13 and checked 1,966 PDFs. Six were turned away for being too big — and all six were ordinary published documents, including a budget-committee packet and an HR newsletter. The limit exists to protect the server's memory, not to judge documents, so it has been raised to let those through. Two very large reports (roughly 50 MB and 60 MB) are still refused on purpose: accepting files that size would risk the server running out of memory when two people upload at once, and a 60 MB document has an accessibility problem of its own worth fixing at the source.

Nothing else about the service changed, and no part of it gained new exposure. The number of documents checked at the same time is unchanged (two), so the most memory the tool can use for uploads is still bounded — now 50 MB instead of 30 MB. Every other protection is exactly as it was: the checks on where a file may be fetched from, the limits on how many requests one caller may make, the guards against oversized or malformed documents, and the scheduled deletion of stored data. The same audit run also produced two findings that were referred elsewhere rather than changed here: eight files on an agency website are web pages saved with a .pdf name, which this tool correctly refuses to grade, and a slow run was traced to a missing access token rather than to any fault in the service.

v1.69.0

Reviewed 2026-08-11 · scope: the accuracy of the fix instructions shown on reports — a guidance-copy release, not a security release.

A user followed a report's fix steps and couldn't find the menu items in Adobe Acrobat — because Adobe redesigned Acrobat's entire menu system in 2023, and parts of this tool still described the old one. Every Word and Acrobat instruction in every report was re-checked, word for word, against the vendors' current official documentation on 2026-08-11. Acrobat steps now show the current menu path first with the older “classic” path in parentheses wherever the two differ, so the instructions match the screen whichever version a reader has — important here, since machines across the agency run a mix of both.

Every card of fix steps now also says what it was written for and who to call. Each card names the exact versions the steps were verified against (Microsoft 365 Word and Adobe Acrobat Pro version 26, August 2026), explains how to recognize which Acrobat interface you are looking at, and — when the menus still don't match — says to contact IDS at ICJIA to have the software brought current. Nothing about what is stored, how it is used, or how long it is kept changed in this release, and no part of the service gained new exposure: instruction text and one small note on the report pages are the whole of it.

v1.68.3

Reviewed 2026-08-10 · scope: the wording of the home-page announcement about sign-in — a copy change, not a security release.

The home-page announcement said the sign-in system had been removed. It now also says the thing that matters more: it was never switched on. Sign-in code existed from the tool's first release, but the setting that would have required anyone to log in was off in every version ever published — the server treated every visitor as anonymous before it even looked for a login, and the space where an account holder's email address would have gone held a placeholder for everyone. Nobody ever needed an account here, and no audit was ever tied to one. That was checked release by release, against every published version, before the sentence was written.

The wording is deliberately careful. The sign-in feature was built and shipped, so this record does not claim it was never written — only that it was never turned on, which is what the code history shows for every released version. It was deleted outright in v1.68.0 rather than left sitting unused in a tool this widely used, along with the columns that could have held an email address, IP address, or browser identifier. Nothing about what is stored, how it is used, or how long it is kept changed in this release, and no part of the service gained new exposure: one sentence of text on the home page is the whole of it.

v1.68.2

Reviewed 2026-08-09 · scope: every claim on this page, the README, and the technical pages re-checked against the code after the identifier removal — not a security release.

After removing sign-in and identifier storage, we re-verified this page line by line against the code — and fixed what the check found. The most visible correction: the header above said Policy v1.4 while this very change log said v1.6; the header had simply not been updated, twice in a row. It now reads from the same version as § 14's newest entry, and an automated test keeps the two in agreement from now on.

Other corrections, all wording: § 9 no longer lists sign-in cookies (there is no sign-in) and now describes the real remediation brake — a daily per-caller cap counted in server memory; § 11's example link now shows the access token a receipt requires; § 7 gains a row for the web server's own access log, the one identifier-bearing store whose retention (about 52 days, managed by the host) was not listed anywhere; and several technical descriptions were tightened to match the code exactly, down to which checks run in a separate helper process. One small code change accompanied the words: remediated files are now explicitly restricted to owner-only permissions, so a promise § 9 was already making is enforced rather than assumed. Nothing about what is stored, how it is used, or how long it is kept changed.

v1.68.1

Reviewed 2026-08-09 · scope: an emergency fix, found by our own verification pass within the hour.

Shared-report links stopped working for about an hour after the v1.68.0 release, and this fix restored them. One database query still asked for the deleted email column; the database rightly refused, and every shared-report link answered with an error instead of the report. No data was lost and nothing was exposed — the links simply failed until this release.

How it was caught matters: not by a visitor report, but by the verification pass this project runs on its own documentation — checking § 8a's claim that every database statement had been enumerated turned up the one that hadn't. A new automated test now loads a shared report against the real, migrated database shape on every future change, so this exact failure cannot ship silently again.

v1.68.0

Reviewed 2026-08-09 · scope: the sign-in system removed, and identifier storage removed at the database level — the largest data-minimization change in the tool's history.

The tool no longer has accounts, sign-in, or any way to know who you are. The optional email sign-in — login codes, sessions, the My History page — is gone entirely, and with it the only email the service could ever send. The tool is free and open: upload a document, read the report, leave. Nothing to register for, nothing to remember, nothing to be locked out of.

And the database physically cannot store identifiers any more. This release did not just stop writing the email, IP-address, and browser columns — it deleted the columns themselves, along with every value they already held, and removed the sign-in tables outright. What each audit leaves behind is metadata — the file's name, its score and grade, the date, and a fingerprint of its bytes. Data about the file, never the file — and now, about nobody. An automated test asserts the columns are absent from the schema, so they cannot quietly return.

What this deliberately does not claim: that the records hold nothing personal. A file named after a person stores that person's name, and a report someone chooses to share quotes short labels from inside the document. The caller's network address is still used for a moment, in server memory only, to limit request rates — written nowhere. The hosting layer's ordinary web-server logs exist outside the application, as on effectively every website (§ 8a). Backups made before this release keep the old shape for about five days until rotation replaces them. Recorded as v1.6 in the policy's own change log.

v1.67.1

Reviewed 2026-08-09 · scope: precise wording about what the retained records are, for federal and state auditors — not a security release, and nothing stored changed.

The records this service keeps are now described as exactly what they are: metadata about the audit. A retained row says that a file with this name was checked on this date and received this grade. It is a record about the document, never a copy of any part of it — it says the file was checked, not what the file said. The status page's backup explanation and § 7a of this policy now both use those words.

And the policy is deliberately precise about personal detail, because auditors test claims. It would be shorter to say the records contain nothing personal — and it would be wrong: a sign-in email is personal, the routine connection log (IP address, browser) is personal, and a file name as uploaded can itself name a person. So the policy names those fields and their deletion schedule instead of waving them away, and this project's automated tests fail any page that claims the records are free of personal detail. What the records never hold is the document, or anything read from inside it. Nothing about what is stored, how it is used, or how long it is kept changed in this release — wording only, recorded as v1.5 in the policy's own change log.

v1.67.0

Reviewed 2026-08-09 · scope: reports now name every heading, so people can see exactly what to fix — not a security release.

Reports now show your document's heading outline with its actual text. Where a report used to say a Word document had “3 real heading(s)”, it now lists them — Heading 1 “Annual Report”, Heading 2 “Introduction”, and so on — and PDF reports, which showed only the pattern of heading levels, now show each heading's text beside its level. This exists for the person who has to do the fixing: “a heading level is skipped” is only actionable when you can see which heading it is.

Word reports also list paragraphs that only look like headings. Text made bold and large reads as a heading to the eye but is invisible as one to a screen reader. Each such paragraph is now quoted on the report, so an author can go straight to it and apply a real Heading style rather than hunting through the document.

The technical detail on each report card now starts open instead of hidden behind a toggle. Someone who chose the detailed view has already asked for depth, and the specifics — which image, which heading, which table — are the point of it. Each card's toggle still collapses the detail for anyone who prefers the summary. The new outline text is read from the document you upload and shown back to you on your own report, exactly like every other finding; no change to what data is collected, how it is used, or how long it is kept.

v1.66.0

Reviewed 2026-08-08 · scope: an adversarial audit of the scoring itself, and the corrections it produced — not a security release.

The scoring was audited against a set of real documents whose faults we know, and the verdict was that it tells the truth. Every failure it reported and every clean result it gave was checked by hand against what is actually inside the files, and all of them held up. Two kinds of correction came out of the review, and both are about honesty rather than about catching more or fewer problems.

First: style preferences no longer lower a document's grade. A few rules the tool scored — such as “a document should have exactly one top-level heading” — are style-guide advice, not requirements of the accessibility standard. A document meeting the standard could still lose its A to one of them. Those rules are now stated as advice on the report without affecting the score. Because of this, a document audited again today may score somewhat higher than an older shared report of the same file — the old link keeps the number it always had, and the difference is the removed style rules, not a change in what passes or fails the standard.

Second: the report is more explicit about what automation cannot see. The list of things this tool never checks now includes five more parts of the standard (among them whether colours alone carry meaning, and whether passages in another language are marked for screen readers). And when every image in a document has been marked “decorative” — which hides them from screen readers entirely — the report now says so prominently and asks a person to confirm none of them actually carries information, instead of staying silent. No change to what data is collected, how it is used, or how long it is kept.

v1.65.1

Reviewed 2026-08-08 · scope: colour added to the header status panel's marks — not a security release.

The header status panel's marks are now coloured — a green check for a part of the service that is up, a red cross for one that is down, grey for one that has not been checked yet. The colour is an addition, not a replacement: every state is still written out as a word, so the panel reads the same for anyone who cannot distinguish the colours. The colours used are the same ones already verified against the contrast standard in both the dark and light themes, and an unverified state deliberately stays grey rather than green — the panel never presents something unchecked as something known to be fine. No change to what data is collected, how it is used, or how long it is kept.

v1.65.0

Reviewed 2026-08-08 · scope: the status light in the page header becomes a link with an accessible detail panel — not a security release.

The status light in the page header is now a button to the status page, and it can tell you what it knows. Hovering over it — or reaching it with the keyboard — shows a small panel naming each part of the service behind that light: the database, the three checking engines, the nightly backup, and disk space, each marked up or down in words, not colour alone. Clicking it goes to the full status page. The panel is honest about uncertainty: a part of the service that has not been checked yet says not yet checked rather than being presented as fine, because the one indicator visible on every page should never claim more than the service has actually verified.

Checking the panel in both colour themes caught a real accessibility fault, which was fixed. The status wording in the header — the green “audit server online” itself — measured well below the required contrast against a light background. In a tool whose job is catching exactly this in other people's documents, that is not acceptable in its own header. The colours now meet the standard in both themes, verified by an automated check that measures them against every background they actually appear on, including while hovered. No change to what data is collected, how it is used, or how long it is kept.

v1.64.0

Reviewed 2026-08-08 · scope: completing this section's own record — no change to the tool itself, and not a security release.

This history is now complete. Twenty-seven releases had no entry here — almost all of them small follow-up corrections rather than significant changes — while the project's own change log carried every one. Each has now been written up in the same plain language as the rest of this section, from that release's change log. Nothing that was already published has been altered.

Those entries say so. Every one of them is marked "entry recorded 2026-08-08", and the note above this list explains why. The releases were reviewed at the time; this write-up of them was not, and a record that presents a reconstruction as though it had been written on the day is worth less to the auditor reading it than one that is honest about which of its entries came later.

One of the new entries records something that did not work — a first attempt, on 7 August, at resolving a report that showed a letter grade and a number contradicting each other. It was replaced the same day. It is included because how a mistake in the scoring was found and corrected is part of the record, and leaving out the unsuccessful step would make that history read more smoothly than it deserves. An automated check now confirms that every release since v1.18.0 has an entry both here and in the project's technical documentation, so this section cannot fall behind again. No change to what data is collected, how it is used, or how long it is kept.

v1.63.2

Reviewed 2026-08-08 · scope: how this page's own history is stored and rendered — no change to any record, and not a security release.

This history is now stored as records rather than as hand-written page markup. Every entry below used to be its own block of layout code — the same card, the same heading, the same coloured label, copied and re-edited for each release, in a single file of more than three thousand lines. The wording of each entry is unchanged; only the way it is stored and displayed has changed. It was verified by rendering the page both the old way and the new way and comparing the text of all sixty-five entries character for character: identical.

Why an auditor should care at all: a record that is expensive to add is a record that eventually gets skipped, and this section is the evidence of due diligence for every release. Adding an entry now takes a few lines instead of fifty. An automated check also now refuses to let a release ship unless this section has an entry for it, so the gap cannot be left open by oversight — and separate checks confirm these records contain only text and formatting, never anything that could run in your browser. No change to what data is collected, how it is used, or how long it is kept.

v1.63.1

Reviewed 2026-08-08 · scope: the status light in the page header, and one dead link on a printout — not a security release.

The status light in the header now tells you the same thing the status page does. It was only checking whether the service was running at all, so it could show a confident green "online" while the status page was reporting a real problem — an overdue backup, a checking engine that had stopped answering. It now turns amber and says degraded — see status, and naming what is wrong is a click away. It deliberately does not ask the status page itself: that page is deliberately cheap to check and rate-limited so the external uptime monitor is never crowded out, and a light on every page in every open tab would have crowded it out.

The printer-friendly steps for an auto-remediated file no longer print a link that goes nowhere. The printout carried the address of the remediation job it came from — but that page expires, and by then the file has already been fixed, so the link led nowhere useful. It has been removed from that printout. Reports, which stay available for a year, still print their address. No change to what data is collected, how it is used, or how long it is kept.

v1.63.0

Reviewed 2026-08-08 · scope: a printable fix plan, and one contradiction about whether a file could be published — not a security release.

Every report now has a printer-friendly version of its fix steps. The button opens a clean page in a new tab, listing each fix in full — both how to correct it in the original Word or PowerPoint file and how to correct it in Adobe Acrobat — so it can be printed, saved as a PDF, or handed to whoever actually edits the document. That page is built entirely inside your own browser from the report already on screen: nothing is sent anywhere to produce it, and the page itself loads nothing from the internet.

The audit report and the auto-remediation result no longer disagree about whether a file is ready to publish. They were using two different rules, so one file could be called ready on one page and not ready on the other. Both now use the same rule, and when a file's results cannot be re-read the tool says so rather than assuming the file is fine. Reports also always open in the visual, step-by-step view, and the control for switching to the detailed view is far easier to find. No change to what data is collected, how it is used, or how long it is kept.

v1.62.0

Reviewed 2026-08-08 · entry recorded 2026-08-08 · scope: two controls that were being missed on screen — not a security release.

The control for choosing how to read a report is now unmistakably a control. It had been two small labels in a strip above the report, and it was missed — including by the person who asked for it. Someone looking for the plain-language, step-by-step plan could not find the button that shows it, and reported the plan as having been removed. It had not been. The chooser now runs the full width of the page, asks its question out loud, and says in a sentence what each option actually gives you. The option you are currently reading is marked with the word Showing rather than by colour alone, because colour is not available to every reader — a requirement this tool checks other people's documents against, and should not fail itself.

After an automatic repair, whatever is still outstanding is now shown by default. It used to sit behind a closed panel, so the only thing visible after a successful repair was a green result and a one-line count. That is exactly the moment someone is most likely to decide a file is finished, and a count reads as a footnote beside a success message. The detail is still collapsible; it simply starts open whenever anything remains. No change to what data is collected, how it is used, or how long it is kept.

v1.61.1

Reviewed 2026-08-08 · scope: one fewer thing stored on your device — not a security release.

Every report now opens in the visual, step-by-step view, for everyone, every time. The choice between the visual and detailed views used to be remembered on your device, which meant anyone who looked at the detailed view once saw it for every report afterwards — and because the step-by-step fix list only appears in the visual view, people reported that the plan had vanished. That preference is no longer stored anywhere, and the old value is deleted from your browser the next time you open a report. The toggle still works; it simply applies to the report in front of you rather than to every report you will ever open. No change to what data is collected, how it is used, or how long it is kept — this removes one of the few things the tool kept on your device.

v1.61.0

Reviewed 2026-08-08 · entry recorded 2026-08-08 · scope: the home page settling as it loads, and the length and colour of the announcement — not a security release.

The home page no longer jumps as it finishes loading. The announcement at the top used to appear only after the page had already drawn, pushing the heading and the upload area down. It is now sent complete by the server. One consequence is worth stating plainly here, of all places — the alternative fix would have required storing a small marker in your browser so the server could know you had already dismissed an announcement. That was rejected. This tool lists every piece of information it keeps on your device, and adding another one to remove a flicker is not a trade worth making.

Announcements are now capped at four or five sentences. Two had grown to the length of a memo and dominated the page they sit above. Shorter is also less disruptive for anyone who has already dismissed them. The announcement was also given its own background shade so it reads as distinct from the page without competing with the report below it; the text contrast was measured at 11.9:1 in the dark theme and 9.2:1 in the light one, well above the 4.5:1 standard. No change to what data is collected, how it is used, or how long it is kept.

v1.60.1

Reviewed 2026-08-08 · entry recorded 2026-08-08 · scope: one number on the public status page shown in the wrong unit — not a security release.

The status page reported free disk space in five-digit megabytes — "61112.6 MB free of 78284.0 MB" for what is really a 76 GB volume. Accurate, unreadable, and on the one page written specifically for people who do not think in megabytes. It now reads "78% (59.7 GB free of 76.4 GB)". As before, the page reports a percentage and a size and never a location on disk. Found by looking at the live page rather than by an automated check, because nothing had ever fed that formatter a number that large; three checks now cover it. No change to what data is collected, how it is used, or how long it is kept.

v1.60.0

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: three items carried over from the 2026-08-05 operational review, including one accessibility failure in this tool itself.

The service now watches its own free disk space. A full disk would break uploads and the nightly backup at the same time, silently, while every other check on the status page stayed green — the first symptom would otherwise be a failed restore months later. Free space is now reported on the status page and the service flags itself as needing attention below 10%, where the external monitoring already watches. It is deliberately a warning and never an outage: the tool can still check documents on a nearly-full disk, and raising an alarm about an outage that has not happened is how alarms come to be ignored. No location on disk is ever published — an automated check asserts the published figures contain no file path at all.

The server now gives up restarting a process that cannot stay running. Previously a failed deployment would have been restarted for ever: a silent loop burning processor time and filling the log disk, while the monitoring showed the service as "online" in the gaps between crashes. A process that cannot stay up for twenty seconds, ten times in a row, is now marked as failed and left down — visible failure being far better than invisible failure. A written procedure for rotating the server's log files was added at the same time.

An accessibility failure in this tool was found and fixed. The colours used for grades and severity ratings are tuned for the dark theme, where they are comfortably readable. On the light theme the same colours measured between 1.9:1 and 3.8:1 against their backgrounds — every one of them below the 4.5:1 minimum this tool checks other people's documents against. There are now two sets of colours, one per theme, each verified against all three background shades it is actually painted on. A separate re-check also found a link whose spoken name did not match its visible words, which would have prevented someone using voice control from activating it. It was the site's only remaining accessibility failure; an independent audit of the site now scores 100 for accessibility. No change to what data is collected, how it is used, or how long it is kept.

v1.59.2

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: making the human-review statement unconditional, and rewriting the status page for the people who actually open it.

Every report now carries the human-review statement, whatever the score. It had appeared only when there was something to list — which meant a badly failing document, the case that most needs a person to look at it, could receive no such statement at all. Every report now opens that section with a standing line: no automated audit, this one included, can tell you a document is accessible; it can only tell you where it definitely is not. A document that still has problems is additionally told that working through the list is not the finish line, because a completed list is a stronger pull toward "done" than a perfect score ever is.

The status page now describes each of its checking programs in plain language. The people who open a status page are rarely engineers; they are usually managers arriving sceptical — what is this thing, and is it really doing what you say it is? Each entry now says what the program is, who maintains it, what it does here specifically, and what its running does and does not prove. In particular veraPDF is maintained by an independent organisation, which is what stops this tool marking its own homework. The page also states the moment it was generated, and the one genuinely cached figure on it now says when its reading was taken rather than implying it is live. No change to what data is collected, how it is used, or how long it is kept.

v1.59.1

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: a checklist added the day before reached only one of the two ways of reading a report.

The "still worth checking by hand" list now appears however you read a report. It had been added to the step-by-step view only, while the wording elsewhere on the page had already been changed to point at it — so in the detailed view it pointed at something that was not there. On a document with no problems that view showed nothing at all beneath the score, and a reader's honest reaction was to ask where the findings had gone. The list now appears on all three surfaces that display a report, and an automated check pins each one, because this is the second time in two releases that something shipped into one view and not the other. No change to what data is collected, how it is used, or how long it is kept.

v1.59.0

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: what a person still has to check when the automated result is perfect.

Every report now lists what still needs a human eye — including reports that scored 100. A perfect document previously produced an empty list of fixes and a short green panel, which left its author with the obvious question: so what should I still look at? The honest answer is that these checks confirm accessibility structure exists; almost none of them can judge whether it is correct. A picture described as "image" passes. A heading that describes the wrong section passes. That gap is invisible on a clean report unless the report names it.

Every check that passed now contributes an entry saying what was actually established and what judgment only a person can make — phrased as something to go and do, not as a restatement of the check. Checks that failed are deliberately absent, since those are already the list of fixes. Below that, the accessibility criteria this tool does not evaluate at all are listed by name with links to the published standard, described plainly as unexamined rather than as passed. No change to what data is collected, how it is used, or how long it is kept.

v1.58.4

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: an older shared link disagreeing with a fresh check of the same file — the last correction in that day's scoring work.

A report shared earlier now shows the same score as checking the file again today. Found by testing the previous release on the live service: a shared link to a Word file showed 71 while re-uploading the identical document gave 79 — precisely the disagreement that recalculating on open exists to prevent. The recalculation had only been able to move a score down, so it could pick up the corrections that lowered scores but was structurally unable to pick up the one that raised simple documents. Saved reports keep everything the calculation needs, so the score is now fully re-derived when a stored report is opened.

The stored records themselves are still never rewritten — they are an agency's evidence of what was computed on the day. The recalculation happens when a report is displayed. No change to what data is collected, how it is used, or how long it is kept.

v1.58.3

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: documents being marked down for being simple.

A document is no longer penalised for being short. Checks that do not apply now count as passing and stay in the calculation, instead of being removed from it — a document with no tables does not have a table problem, it has no tables. Reported from real use: a one-page public notice and a longer meeting agenda, both missing a document title and nothing else in common, scored 71 and 79 — the notice worse, despite having fewer problems, because only three of its ten checks applied at all and its single fault therefore counted for more than half its score. Both now score 79.

Two limits were kept deliberately, and both were confirmed by test rather than by argument. A check the tool could not evaluate is still left out rather than assumed to have passed — "no tables were found" and "contrast could not be determined" mean different things, and scoring the second as a pass would be an unverified claim. And a scanned document still scores zero, because its checks come back empty for the opposite reason: a screen reader gets nothing from it at all. No change to what data is collected, how it is used, or how long it is kept.

v1.58.2

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: the score and the letter grade contradicting each other on the page.

The score and the letter grade agree again. The previous day's change had capped the letter at a document's worst problem while leaving the number alone, so a report could show a D directly above 80 out of 100 — wrong on its face against the scale this tool publishes, where 90 and above is an A, 80 a B, 70 a C, 60 a D and anything lower an F. It was reported twice, in the plainest possible terms: "a 'D' is not 80", then "80 and above is a B. Not a C, and certainly not a D."

The intermediate attempt (v1.58.1) tried to solve this by relabelling the number rather than changing it; a reader read the relabelled figure as a percentage grade within minutes. Any figure out of 100 beside a letter grade is read as the grade, whatever it is called. So the number itself now carries the limit — a minor problem holds a document at 89, a moderate one at 79, a critical one at 69 — and the letter is worked out from that number exactly as it always was. Those ceilings are derived from the published bands rather than written down separately, so they cannot drift apart from them. Every contradiction the first attempt fixed stays fixed: across the 31-document reference set the distribution of letters is identical. No change to what data is collected, how it is used, or how long it is kept.

v1.58.1

Reviewed 2026-08-07 · entry recorded 2026-08-08 · scope: a first, unsuccessful attempt at the contradiction corrected later the same day by v1.58.2.

This release tried to resolve a report showing a D above 80 out of 100 by relabelling the number rather than changing it. The letter was the verdict; the number was moved into a panel called "Fix progress" with a sentence reconciling the two. It did not work — a reader read "81 of 100" as a percentage grade within minutes of it going out — and it was replaced the same day by v1.58.2, which changed the number itself.

It is recorded here rather than omitted because an unsuccessful attempt is part of the honest history of how the scoring was corrected, and because the lesson it produced is the one that finally settled the design: a figure out of 100 sitting beside a letter grade will be read as the grade no matter what it is labelled. No change to what data is collected, how it is used, or how long it is kept.

v1.58.0

Reviewed 2026-08-07 · scope: two places where the tool contradicted itself in front of the people it is written for — not a security release.

Documents are no longer penalized for being simple. A check that does not apply to a document — table markup in a document with no tables, alt text in one with no images — now counts as passing, rather than being removed from the calculation. Removing them meant a short, simple document had almost nothing to average against, so a single problem counted for far more: a one-page public notice and a longer meeting agenda with the identical missing-title problem scored 71 and 79, the notice worse even though it had fewer problems. Both now score 79. Two things deliberately did not change: a check the tool could not evaluate is still left out rather than assumed to have passed, and a scanned document still scores zero, because a screen reader can read nothing from it at all.

Scores are now capped by the worst problem found in a document. A reader noticed that two reports with the same missing-title problem carried different grades, and they were right. The score had been an average across the checks that applied, so the same single fault counted for more in a short document with little to check and less in a longer one with more — and a few perfect checks could outweigh one serious failure. Two files missing both a title and a language declaration were scored better than a file missing only the title. A document's score can no longer rise above what its worst unresolved problem allows: a minor item holds it at 89, a moderate problem at 79, a critical problem at 69. The letter still comes from the same scale it always has — 90 and above is an A, 80 a B, 70 a C, 60 a D, below that an F — so the number and the letter always agree. Scores can only move down under this rule, never up, and a document with a critical problem can no longer read as close to publishable. Where a score is being held at its ceiling, the report names the problem holding it there, and reports shared before this change show the corrected score when reopened, so an older link and a fresh audit of the same file no longer disagree.

The status page now explains why anything is backed up when the tool promises your file is never stored — a fair question someone asked out loud. Your document is never saved; the service's own record that a document was checked is, and that record is what the nightly backup copies. Section 7a of this policy draws the same distinction, and both places state plainly what personal detail those records do contain — a sign-in email address, the routine connection log every web server keeps, and the file name as uploaded — rather than claiming there is none. Three pieces of stale or wrong wording were also corrected, including a severity level advertised on the home page that this tool has never actually used. No change to what data is collected, how it is used, or how long it is kept.

v1.57.0

Reviewed 2026-08-07 · scope: follow-through on the same day's whole-application review — not a security release.

Three gaps in the project's own safety net were closed: automated checks now verify that the report pages are wired to their data correctly, that the display code still matches what the audit engine actually produces, and that the "Evidence & technical detail" link moves keyboard focus to the section it scrolls to — a real accessibility fix in the report itself. The public status page also now states plainly when the tool cannot audit at all, rather than showing the same wording it uses when one optional feature is unavailable. No change to what data is collected, how it is used, or how long it is kept.

v1.56.0

Reviewed 2026-08-07 · scope: whole-application adversarial review — UX, security, documentation, operations, code health, and test coverage.

Six independent reviews examined the whole tool with deliberately hostile eyes. Two robustness defects were found and fixed: a deliberately malformed shared report could make its own report page fail to load (only that one link was ever affected — no other report, and no stored data, could be reached or altered this way), and a section of the remediation results page filtered for an issue level that does not exist, so lower-priority findings were never listed. The report's headline was also corrected: because the letter grade is a weighted average while the publish-or-not verdict counts blocking issues, a document could be described as "Excellent" and "not ready to publish" in the same sentence; the blocking issue now leads. Wording throughout the fix steps was made plainer, and the guidance no longer implies the free Adobe Reader can perform fixes that require Acrobat Pro. No change to what data is collected, how it is used, or how long it is kept.

v1.55.0

Reviewed 2026-08-07 · scope: the public status page's presentation — not a security release.

The status page's browser view now presents its sections as collapsible cards with an always-visible health summary on top, so a first-time visitor sees the essentials at a glance rather than a wall of tables. Presentation only: the page publishes exactly the same aggregate operational numbers as before (service health, engine availability, anonymous document counts and grades, backup recency), still contains no names, no filenames, and no content from anyone's documents, and still runs no scripts in your browser. What monitoring services read is byte-for-byte unchanged. No change to what data is collected, how it is used, or how long it is kept.

v1.54.1

Reviewed 2026-08-07 · scope: the remediation results page — not a security release.

The download for an auto-remediated file now appears inside the "After Remediation" card, beneath the grade and its explanation, and carries a plain-language readiness banner: unless the remediated file grades an A, the banner states that issues remain and should be fixed before publishing. Presentation only — the remediation pipeline, the one-time download token, and the file-retention windows are all unchanged. No change to what data is collected, how it is used, or how long it is kept.

v1.54.0

Reviewed 2026-08-07 · scope: the redesigned report presentation — not a security release.

Audit reports now open in a visual, plain-language layout, with the complete technical report available through a Visual/Detailed toggle on every report. This is a presentation change only: the audit itself, what it examines, and what the service stores are all unchanged, and previously shared reports simply display in the new layout. The view preference was kept on your own device (in your browser's local storage), never sent to the server — as of v1.61.1 it is not stored at all, and every report opens in the visual view (see that entry above). Every fact shown in the classic report remains visible in the new one, and the redesigned interface itself was contrast-checked against WCAG AA during development. No change to what data is collected, how it is used, or how long it is kept.

v1.53.0

Reviewed 2026-08-05 · scope: readability of the technical material on this page and the technical-details page — not a security release.

Several technical blocks on this page — the database schemas in § 6, the pipeline flows in §§ 2–3, the auditor queries in § 11, and their counterparts on the technical pages — had been reduced to single horizontally-scrolling lines by an interaction between the project's code formatter and the markup that displayed them. All now render as proper multi-line, color-coded code blocks, keyboard-scrollable for assistive-technology users. While restoring the § 6 schema it was also re-checked against the actual database definition and brought back in sync (the displayed copy had drifted: it omitted the original_filename column added in v1.48.0). The technical-details page additionally gained four new detailed blocks, including a worked example of how category weights are redistributed when a category doesn't apply. Display only — no query, retention period, or behavior changed. No change to what data is collected, how it is used, or how long it is kept.

v1.52.0

Reviewed 2026-08-05 · scope: the service now flags an overdue nightly backup — a monitoring improvement.

The status page has shown the last successful backup since v1.50.0; as of this release the service also raises its own attention flag when that backup becomes overdue for a nightly schedule (about thirty hours old) — the same "degraded" marker used when an optional checking engine is down, which the external uptime monitoring already watches. In plain terms: if the nightly backup silently stops, a person now gets told, instead of discovering it on the day a restore is needed. Two boundaries were kept deliberately: a server where backups have never run still does not raise the flag (a brand-new deployment must not alarm before its first scheduled run), and an overdue backup can never make the service report itself as fully down — audits continue working regardless. No change to what data is collected, how it is used, or how long it is kept.

v1.51.1

Reviewed 2026-08-05 · scope: documentation wording about backups — not a security release.

The standing documentation — the project README and this page — now states the backup arrangement plainly and in general terms: the database is backed up nightly, only the five newest snapshots are kept, and they live on this server in a dedicated directory beside — but outside — the application, unreachable from the web. The one place that previously printed the exact directory location on this page now describes it generally instead; the precise paths remain in the operator runbook in the repository, where the person restoring a backup needs them. No behavior changed: the backups themselves, their schedule, their rotation, and everything § 7 and § 8a state about them are exactly as in v1.49.0–v1.51.0. No change to what data is collected, how it is used, or how long it is kept.

v1.51.0

Reviewed 2026-08-05 · scope: shared reports are now deleted after their link expires — a data-retention improvement.

A shared report's link has always stopped working 365 days after it was created — but the stored report itself was kept indefinitely, which this policy disclosed (§ 7). The cleanup sweep now permanently deletes the stored report roughly 30 days after its link expires, so nothing shared outlives its usefulness by more than a month. The 30-day gap is deliberate: while the record still exists, a visitor clicking an expired link is told plainly that it expired; after deletion, the address behaves as if it never existed. Since a saved report is the one place that can quote short strings from inside a document (§ 8a), this is a privacy improvement, not only housekeeping.

The same release fixes a latent coupling: the sweep that enforces every retention period on this page used to pause entirely if the optional remediation feature was switched off — a configuration that never occurred in production, but under which deletion schedules would have silently stopped. The sweep now runs unconditionally, and an automated test holds it to that with the feature disabled. This release only deletes data sooner — nothing new is collected, and every other retention period is unchanged.

v1.50.0

Reviewed 2026-08-05 · scope: the public status page now shows the last successful backup — reviewed for what it discloses.

The status page gains one row: when the nightly database backup last completed, how old that is, its size, and how many usage-log records it contains. The row appears only for a backup that passed its integrity check, and it discloses nothing else — in particular, never a server file location, which the same automated privacy checks that guard the rest of the page now also enforce for this row. A server where backups have not yet run says so in plain words rather than raising the service's alarm state, so turning the feature on cannot cause a false outage alert. The backups themselves now live beside the application's code folder on the server — easier to find, still outside the code checkout and unreachable from the web. No change to what data is collected, how it is used, or how long it is kept.

v1.49.0

Audited 2026-08-05 · scope: nightly database backups, plus an independent line-by-line verification of § 8's storage claims, published as § 8a.

The database is now backed up nightly. The backup uses SQLite's own online-backup mechanism — safe to run while the service is writing, where a naive file copy could produce a silently incomplete snapshot. Every snapshot is integrity-checked before it is kept, refused outright if the source is missing or is not this application's database, and stored in a directory outside the application on the same server. Only the five newest snapshots are retained; older ones are deleted automatically. The restore procedure is scripted, verifies the snapshot before touching anything, sets the current database aside rather than deleting it — and was rehearsed end-to-end the day it was written. The hosting provider's own whole-server backups run separately, covering loss of the server itself.

Section 8's claims were verified line by line against the source code: every database table, every statement that writes to one, every file the server can create, and everything its logs and email can contain. The evidence — with the code quoted — is published as § 8a, so the claims no longer rest on trust. The verification found one statement that claimed too much, and it has been corrected rather than quietly reworded: a report you choose to share quotes short strings from inside your document (image alt text, link labels and destinations, bookmark titles, the document's own metadata fields) in its findings, because naming a problem requires showing it. A plain audit — upload, read, close — stores none of those strings, and page text, images, form values, and file bytes are never stored anywhere.

The backup adds no new kind of data — it is a copy of exactly the database documented in §§ 7–8a, kept on the same server, with a shorter life than anything inside it.

v1.48.1

Reviewed 2026-08-05 · scope: corrections to this policy and the explanatory pages — not a security release.

Every explanatory page — this policy, the technical and scoring explanations, and the project documentation — was checked line by line against the source code, and everything found inaccurate was corrected. The corrections that matter for a reader of this policy: it now states plainly that the usage log records the caller's IP address and browser identification with each entry (it always has; § 8 previously implied otherwise), that usage-log entries are deleted after 365 days rather than kept indefinitely (§ 7 — the automatic deletion has existed since tool v1.20.1), and that refused uploads are recorded (§§ 2, 7, 8). The audit entries above for v1.43.0 through v1.48.0 were added in the same pass; this register had fallen six releases behind. The full list of corrections is in this policy's own change log (§ 14, policy v1.2).

One correction was visible on reports: the legend explaining severity colors described a score of 90–99 as meeting accessibility standards, while the scoring itself reserves that description for a perfect 100. The legend now matches the scorer. No change to what data is collected, how it is used, or how long it is kept — only to how accurately these pages describe it.

v1.48.0

Audited 2026-08-05 · scope: a full red/blue team audit of the entire application, plus a live check of the production web server — a standalone comprehensive review, not a per-release one.

The whole tool was re-examined end to end: how files are received and refused, sign-in, the checking engines, the database, rate limits, what the service publishes about itself, and the web-server configuration in front of the application. The attack half of the review — poisoned archives, oversized files, forged web addresses, a dozen ways of trying to make the server fetch its own internal resources — was run against an isolated copy of the service, so no production data or statistics were touched; the live site received only read-only checks. Every attack was turned away. Nothing critical or serious was found. Two low-severity issues were identified and fixed the same day.

What changed for an auditor reading this page

  • Fixed Refused uploads no longer store the file name exactly as sent — When the tool refuses a file it keeps a note of the attempt, including the file name it was offered under. That name was being stored exactly as the sender supplied it — any length, any characters. Only an administrator can see those notes, and the screens that display them already refuse to treat stored text as anything but text, so this was not exploitable — but it left a single safeguard doing all the work. File names are now cleaned and length-limited before they are stored, on every path that writes them, so no future change can quietly reopen the gap.
  • Fixed Line breaks can no longer hide inside stored file names — The cleaning rule that strips unusual characters from file names treated line breaks as ordinary spacing, so a name containing them passed through — and this affected normal, accepted uploads too, not just refused ones. A file name is a single line by definition, and anything that later prints these records one per line would have inherited the break. All spacing characters are now collapsed to a plain space before the rule runs.
  • Hardened The production web server's protective headers were tightened — Three gaps were found in the web server that sits in front of the application (not in the application itself), and all three were fixed and verified on the live site the same day: browsers are now told to always use the encrypted connection on every page, not only on API responses; a duplicated, conflicting anti-embedding header was reduced to one clean value; and the protective headers now appear on error pages as well as successful ones.

No change to what data is collected, how it is used, or how long it is kept — file names are simply cleaned more strictly before storage. Everything else examined — sign-in and its fail-safe behavior, the randomness behind codes and links, database query construction, how helper programs are launched, the guard against fetching internal addresses, and the encryption settings — was confirmed sound with no change needed. The full technical write-up, including the complete list of what was checked and found sound, is in the project's README security section.

v1.47.0

Reviewed 2026-08-05 · scope: clearer labels on the public status page — not a security release.

Two different columns on the public status page were both labeled "other" and meant opposite things — one counted documents that were audited normally but whose file type could not be told from their web address, the other counted uploads that were refused outright. A reader pointed out that the page appeared to contradict itself. The first is now labeled "Unrecognized extension", the second "Other file types", and a short section explains the difference. The counts themselves are unchanged, and the page still publishes only totals — no file names, no email addresses, nothing identifying anyone. No change to what data is collected, how it is used, or how long it is kept.

v1.46.0

Reviewed 2026-08-04 · scope: the public status page now counts refused uploads — this adds one new kind of record to the usage log.

When someone offers the tool a file it cannot check — an old-format Word document, an image, a spreadsheet export — the tool previously kept no record of the attempt at all. It now records that a refusal happened, the file name the upload was offered under, and when. The file itself is never accepted, so no file content is ever stored — and, deliberately, no content fingerprint is recorded either, so a refused file can never be mistaken for an audited one by the safeguard that gates the remediation feature. The status page publishes only the resulting totals, grouped by file type.

This adds one item to what is collected: the name and time of a refused upload. These records are kept for the same 365-day period as the rest of the usage log and are visible only to an administrator; how data is used, and every other retention period, is unchanged.

v1.45.0

Reviewed 2026-08-04 · scope: clearer refusal messages for old Office formats and CSV files — not a security release.

Old-format Office files (.doc, .xls, .ppt, .rtf) are now refused with an explanation of why they cannot be checked and how to convert them, instead of a generic list of accepted formats; CSV files are told there is nothing to check and that this is not a fault. To give the right message even when a file has been renamed, the tool now recognizes these formats by their content — reading only a small, fixed-size portion of the file's beginning, deliberately without parsing the file itself, so a hostile file gains nothing. No new format is accepted, refused files are still never stored, and no response the server gives changed shape — only the wording. No change to what data is collected, how it is used, or how long it is kept.

v1.44.0

Reviewed 2026-08-04 · scope: letter-grade totals on the public status page — not a security release.

The public status page now shows how audited documents have scored, as counts of letter grades over three time windows. These are the same kind of anonymous totals the page already published: no file names, email addresses, or anything identifying a person or document participates — only counts. Because a figure like "62% scored F" invites being quoted as a statistic about all government documents, which it is not — people tend to check documents they already suspect have problems — the page prints that caveat above the numbers, and an automated test keeps it there. No change to what data is collected, how it is used, or how long it is kept.

v1.43.0

Reviewed 2026-08-04 · scope: two browser conveniences — not a security release.

The status page gained a link back to the audit tool, and the tool now asks for confirmation before you leave the page while an audit is still running, so work in progress is not lost to a stray click. The confirmation uses the browser's own built-in dialog — no custom pop-up to get wrong for screen-reader users. Both changes live entirely in the visitor's browser; nothing new is sent to or stored on the server. No change to what data is collected, how it is used, or how long it is kept.

v1.42.1

Reviewed 2026-08-03 · entry recorded 2026-08-08 · scope: the wording of in-site links to the status page — not a security release.

Links to the status page now state which version of it they want — the readable page for people, the raw data for monitoring systems. Nothing a visitor sees has changed; browsers were already being given the readable page automatically. The point is that the intent is now written down in the link itself rather than inferred, so a future change to how that choice is made cannot silently hand an automated monitor a human-readable page and blind its alerting. No change to what data is collected, how it is used, or how long it is kept.

v1.42.0

Reviewed 2026-08-03 · scope: presentation of the public status page — not a security release.

The public status page is now legible in an ordinary browser: the same information, laid out as a colour-coded outline rather than one unbroken block of text. What it publishes has not changed — still only totals and yes/no answers, with no file names, no email addresses, and nothing identifying anyone who has used the service. The checks that enforce that were left in place and still pass. Two details of note: the page runs no scripts at all, so there is nothing on it that could execute in a visitor's browser; and everything it displays is escaped before rendering, so no value drawn from the system can be interpreted as page markup. No change to what data is collected, how it is used, or how long it is kept.

v1.41.2

Reviewed 2026-08-03 · scope: an internal deployment script — not a security release.

The deployment script updates itself as part of each deployment, but continued running the previous version of its own later steps — so a correction made to it did not take effect until the following deployment. It now restarts itself after updating, ensuring each deployment runs the code it just retrieved. This concerns only the deployment process; the application and its data are unaffected. No change to what data is collected, how it is used, or how long it is kept.

v1.41.1

Reviewed 2026-08-03 · scope: an internal deployment script — not a security release.

The automatic checks introduced in the previous release ran a moment too early and reported the service as unavailable when it was in fact starting normally. They now wait for the service to respond before checking. This affected only the report printed during deployment; the service itself was healthy throughout, which was confirmed independently at the time. No change to what data is collected, how it is used, or how long it is kept.

v1.41.0

Reviewed 2026-08-03 · scope: navigation and an accessibility fix — not a security release.

The header now carries a "Status" link and no longer carries an "Analyze" link — clicking the site title clears your results and starts a new file, which it already did. Because that title is now the only way to start over, it was also fixed to work with a keyboard: previously it responded only to a mouse click, which made it unusable for anyone navigating by keyboard or screen reader. On a tool that audits other people's documents for exactly this kind of problem, that was worth correcting promptly.

This release also adds automatic checks that run after each deployment, confirming the service answers correctly and that its search-engine instructions file is being served. That file is currently not being served in production due to a web-server configuration issue unrelated to this application's code; the pages that most need to stay out of search results carry a separate instruction in their response headers, which is working. The configuration fix is queued. No change to what data is collected, how it is used, or how long it is kept.

v1.40.3

Reviewed 2026-08-03 · scope: uptime-monitoring reliability — not a security release.

The two addresses an external monitoring service uses to confirm this site is running did not respond to one of the two standard ways of asking. A monitor set up that way would have reported the service as down while it was working normally — a false alarm that, if repeated, teaches people to ignore real ones. Both addresses now answer either form of request, and they report the true result rather than a canned "everything is fine". These addresses return only whether the service is up; they are the same ones described in the entries below. No change to what data is collected, how it is used, or how long it is kept.

v1.40.2

Reviewed 2026-08-03 · scope: where one link appears — not a security release.

The "Scoring" link, which opens an explanation of how documents are graded, moved from the top of the page to the footer alongside the other reference links. The explanation itself is unchanged. No change to what data is collected, how it is used, or how long it is kept.

v1.40.1

Reviewed 2026-08-03 · scope: one remaining third-party update — a security release.

Applies the last outstanding fix left over from the previous release, in a tool used only while developing the application — never on the live server. One advisory remains open and cannot be acted on yet: it concerns a form component in the interface library this site uses, and its authors have not published a fix. It does not affect this application, which does not use the component in question. It will be applied as soon as a fix exists. No change to what data is collected, how it is used, or how long it is kept.

v1.40.0

Reviewed 2026-08-03 · scope: third-party software updates — a security release.

Like all modern software, this application is built on top of open-source components maintained by others. When a flaw is found in one of those components, a security advisory is published and the component's authors release a fix. This release applies every outstanding fix — around 25 advisories in total, several rated high severity.

Every one of these was in a supporting component used to build and package the application rather than in the code that reads your documents, and we have no indication any was ever exploited here. They are applied because keeping dependencies current is basic maintenance, not because a problem was observed.

One component that is used to read your documents was updated: the XML reader behind Word, PowerPoint and Excel checks. Because a subtle change there could shift scores without producing any visible error, four sample documents were audited on both the old and new versions and the results compared — they were identical. The status page also gained a Chicago-local timestamp alongside the existing UTC one. No change to what data is collected, how it is used, or how long it is kept.

v1.39.3

Reviewed 2026-08-03 · scope: how often one number on the status page refreshes — not a security release.

The counts on the public status page are now recalculated every few seconds rather than once a minute, so a document that has just been audited appears in the totals almost immediately. This changes only how current those totals are. It does not change what is counted, and the page still publishes nothing beyond totals — no file names, no email addresses, and nothing identifying anyone who has used the service. No change to what data is collected, how it is used, or how long it is kept.

v1.39.2

Reviewed 2026-08-03 · scope: navigation links only — not a security release.

Adds a "What's New" link to the site header and footer, pointing at the archive of past announcements introduced in the previous release. That archive was previously reachable only from the notice banner on the home page, which can be dismissed permanently — so the list of past updates vanished exactly when someone would want it. These are ordinary navigation links to a page showing notices already published on the home page. No change to what data is collected, how it is used, or how long it is kept.

v1.39.1

Reviewed 2026-08-03 · scope: a broken link and a new archive page — not a security release.

The link to the new status page, added in the previous release, produced a "page not found" error when clicked from the home-page banner, though the page itself worked normally when its address was typed directly. That is fixed. This release also adds a page listing every past announcement, since the banner shows only the most recent one and can be dismissed. That archive displays the same notices already shown on the home page and reads nothing from your documents. No change to what data is collected, how it is used, or how long it is kept.

v1.39.0

Reviewed 2026-08-03 · scope: a new public status page — reviewed specifically for what it discloses.

This release adds a status page at /status that anyone can visit without logging in. Because it is public, the central question for this review was what it reveals. As first shipped it published only totals and yes/no answers: whether the service is running, whether each checking engine is working, how long the server has been up, and how many documents have been audited in the last day, the last month, and in total — split by file type. Later releases added further anonymous totals: letter-grade counts (v1.44.0) and refused-upload counts (v1.46.0) — see those entries. Then as now, the page publishes no file name, email address, IP address, browser identifier, or document fingerprint, and nothing that identifies who used the service or what they checked.

That guarantee is enforced by an automated test rather than by review alone. The test loads the database with a deliberately distinctive file name, email address and IP address, builds the real status page, and fails the build if any of them — or any server file path, or any email address at all — appears anywhere in the output. The file-type breakdown is counted inside the database itself, so file names are never handed to the code that builds the page. A related check pins the list of published fields, so a future change cannot add a new one without a deliberate decision.

Two figures were considered and deliberately left out. Report-sharing counts were rejected because sharing cannot actually be observed: the system records that a report was created, never whether its link was sent to anyone, so any number published under the word "shared" would claim more than the software can know. Web-page audit counts were left out because the distinction between a document and a web page raises more questions than it answers for a general reader. Two further points of note: the page is excluded from search engines by two independent mechanisms, and when a checking engine fails, the page reports a fixed short label rather than the underlying error message, because those messages routinely contain server file paths. No change to what data is collected, how it is used, or how long it is kept.

v1.38.2

Reviewed 2026-07-26 · scope: completes the v1.38.1 report-ordering fix for a second technical panel — not a security release.

The previous release moved one technical panel below the list of issues that must be fixed, but there are two, and the other one was missed: a "PDF/UA-1 signals" card that appeared at the very top of the report, immediately under the score and above the critical issues. Both now sit below the issues. The distinction matters because these signals are easy to mistake for a passing grade: they report structural facts about the file — whether it carries accessibility tags, whether its fonts are embedded — and cannot judge whether an image description is meaningful or whether the document reads in a sensible order, which is what the accessibility grade measures. A document can therefore satisfy every one of these structural markers and still be unusable for someone with a disability. When critical issues remain, the card now says that plainly instead of leaving the reader to infer it. This is a presentation change only — no scores, grades, or verdicts changed, and no new information is read from your documents. No change to what data is collected or how long it is kept.

v1.38.1

Reviewed 2026-07-26 · scope: the order in which report sections are presented — not a security release.

A report can carry two different verdicts that mean different things, and they were shown in a misleading order. The technical PDF/UA-1 check (which examines whether a PDF's tagging is formally well-formed) was displayed above the list of accessibility issues that actually have to be fixed before publishing — and that technical check can report "Pass" on a document that still has critical problems, because it answers a narrower question than the accessibility grade does. Someone reading their report could see a green result first and reasonably conclude they were finished. The critical issues and their fix steps now appear directly beneath the score, above the technical panel; and when the technical check passes while critical issues remain, it now says so in plain language rather than showing a bare green tick. This is a presentation change only — no scores, grades, or verdicts changed, and no new information is read from your documents. No change to what data is collected or how long it is kept.

v1.38.0

Audited 2026-07-26 · scope: a fresh-eyes review of the audit algorithms themselves, plus the security posture of the PDF/UA checker added in v1.37.0 — this one is a security release.

This review looked at the audit engine with fresh eyes and found three ways a document could be judged wrongly — all of them in the direction of being too generous, which is the more damaging direction for a compliance tool. The most serious: a PDF could carry an empty set of accessibility tags — the shelf was there, but nothing was on it — and the tool would report it as a properly tagged document with no detected WCAG failures. The identical file with the empty shelf removed was correctly failed. In other words, a document could be made to "pass" by adding tagging that did nothing. Empty tagging is now treated exactly like no tagging, because that is what it means for someone using a screen reader.

The second: images that were never tagged at all used to be skipped rather than counted against a document, even though they are worse for a screen-reader user than a tagged image with a missing description — an untagged image is invisible to the reader entirely. A document with ten such images could score 100 while a document with a single missing description scored 98 and was marked as failing. Untagged images now count. The third was narrower: a particular way of storing the tag structure made a real, complete set of tags look empty to part of the tool, which then reported a false "flat structure" problem.

On the security side, three findings were fixed. A specially crafted PDF — only a few hundred bytes — could send the analyzer into a loop that consumed the server's attention entirely, making the site unresponsive for everyone until it was restarted; the file-size limit was no protection, because the file did not need to be large. The PDF/UA checker introduced in v1.37.0 was also being given a copy of the server's own passwords and keys, which it has no need for and which every other external tool the site uses had already been shielded from; and it could be started an unlimited number of times at once, so a burst of uploads could exhaust the server's memory. It is now limited to two at a time. Finally, when that checker failed it was reporting the server's own internal file paths back to the browser; it now reports a plain message and keeps the detail in the server log.

What this means for you: re-auditing a document may now give a different score than it did before this release — in every case that changed, because the tool now catches a real barrier it used to miss. Reports you saved earlier keep the score they were given at the time, so an old saved report and a fresh audit of the same file can disagree; the fresh one is correct. Of the 23 reference documents used to check the engine, 19 were completely unaffected. No change to what data is collected or how long it is kept.

v1.37.5

Reviewed 2026-07-23 · entry recorded 2026-08-08 · scope: attribution and reachability of one label on the PDF conformance panel — no change to scoring.

A borrowed phrase on the conformance panel now credits its author, and can be read by everyone. The reference had been placed in a hover tooltip, which requires holding a mouse still for about a second, never appears on a touchscreen, and is not announced to screen readers — so for most people it simply did not exist, and a borrowed phrase read as an uncredited one. It is now a proper button that reveals a footnote on the page crediting Douglas Adams and The Hitchhiker's Guide to the Galaxy (1979). No change to what data is collected, how it is used, or how long it is kept.

v1.37.4

Reviewed 2026-07-23 · entry recorded 2026-08-08 · scope: the wording of the PDF conformance verdict — no change to scoring.

The PDF conformance panel no longer leads with the word "Fail." A document that does not meet the formal PDF/UA-1 file standard now reads "Additional checks could be addressed". This is not softening a real result: the accessibility grade is the measure that reflects what a person using the document actually experiences, and a formal file-format gap is a punch list rather than an alarm. The specific rules that did not pass remain one click away, unchanged. No change to what data is collected, how it is used, or how long it is kept.

v1.37.3

Reviewed 2026-07-23 · entry recorded 2026-08-08 · scope: explaining how a strong accessibility grade can sit beside a formal conformance failure — no change to scoring.

The conformance panel now explains why a good grade and a formal "fail" can appear on the same report. They answer different questions: the grade measures human impact and is graded, while PDF/UA-1 is a binary check of formal file conformance — which is why Adobe Acrobat, PAC and veraPDF can each disagree with the grade and with one another. The panel now says so, in place, rather than leaving a reader to reconcile two apparently contradictory verdicts on their own.

One piece of repair advice was also withdrawn as actively harmful. A hint about embedded fonts had suggested re-creating the PDF through a print-to-PDF route, which flattens a tagged document and destroys the accessibility structure inside it — trading a cosmetic conformance point for a real accessibility loss. It now points to repairs that preserve the structure, and warns against the other route explicitly. No change to what data is collected, how it is used, or how long it is kept.

v1.37.2

Reviewed 2026-07-23 · entry recorded 2026-08-08 · scope: a count on the PDF conformance panel that overstated the work — no change to scoring.

The conformance panel no longer headlines a number that reads as thousands of separate problems. The underlying validator reports one failure for every occurrence, so a document could be described as having "6,941 rule failures" when it really had a handful of distinct issues repeated across many objects. The panel now leads with the number of distinct issues to fix, keeps the occurrence total as quiet context, and explains the difference in one line. Where a small number of issues genuinely accounts for most of the occurrences, it says so — and where the spread is flat, it does not pretend otherwise. A related fault was fixed at the same time: the stored list of failures was being shortened before it was sorted, so the most frequent issues could be the ones omitted. No change to what data is collected, how it is used, or how long it is kept.

v1.37.1

Reviewed 2026-07-22 · entry recorded 2026-08-08 · scope: making the PDF conformance verdict actionable — no change to scoring.

Each failed conformance checkpoint now carries a short repair instruction rather than only a diagnosis, so the panel tells a document author what to do about it. A summary line was also added covering the six structural essentials of PDF/UA-1 — tagging, marked content, embedded fonts, language, title and the format identifier — which are determined from the document itself and are therefore shown on every PDF report regardless of which optional checking programs are available. It is presented as separate from the accessibility grade, because it is a structural checklist rather than a measure of human impact. No change to what data is collected, how it is used, or how long it is kept.

v1.37.0

Reviewed 2026-07-22 · scope: a new PDF/UA-1 conformance verdict shown on audit results — not a security release.

Audit results now show a PDF/UA-1 (ISO 14289-1) machine-check verdict from veraPDF — the open-source validator — alongside the accessibility grade. It was reviewed before shipping: veraPDF reads a short-lived temporary copy of your PDF (its own copy, created and deleted within the same request, exactly like the existing qpdf copy — nothing new is retained), it cannot stall the page (a 30-second cap; if it can't finish it simply reports "could not validate"), and the verdict is shown for information only — it does not change your accessibility grade. No user data is stored beyond what a saved report already keeps. No change to what data is collected or how long it is kept.

v1.36.3

Reviewed 2026-07-22 · scope: extends the v1.36.2 PDF image fix to lists and tables, from the same reported document — not a security release.

The same "phantom tag" problem fixed for images in v1.36.2 also affected lists and tables: a design tool had left behind dozens of empty list and table tags that are not part of the document a screen reader reads, and the audit was reporting them as broken ("incomplete") structure and lowering the score. The tool now ignores these disconnected tags for lists and tables as well, so the reported document is scored on its real content only. This changes only how existing information in the file is interpreted — no new information is read from your documents. No change to what data is collected or how long it is kept.

v1.36.2

Reviewed 2026-07-22 · scope: a single accuracy fix to PDF image detection, prompted by a document a user reported — not a security release.

Some PDFs — often those exported from design tools like Adobe InDesign — carry leftover "phantom" image tags that are not part of the document a screen reader actually reads. The audit was counting those phantom tags as real images and reporting them as missing a description, which unfairly lowered the score of otherwise well-built documents. The tool now ignores image tags that are not connected to the live document structure, and correctly recognizes when every image on a page is deliberately marked as decorative and therefore needs no description. This changes only how existing information in the file is interpreted — no new information is read from your documents. No change to what data is collected or how long it is kept.

v1.36.1

Reviewed 2026-07-19 · entry recorded 2026-08-08 · scope: four accuracy corrections found by checking a real accessible form.

Four scoring inaccuracies were corrected, all found by auditing a genuinely accessible form that this tool was marking down unfairly. A category of form built with Adobe's designer tools was being refused a verdict altogether, even though it ships a complete conventional version that is exactly what a reader sees; those are now checked normally. A related fault meant the document's declared language and title settings were being read as internal reference codes rather than as their values, producing a false penalty. A structural measure about reading order was scoring routine, correct forms as though they had a critical fault, and has been reduced to a moderate one — the measure genuinely cannot tell which of two orderings is the correct one. And tables that associate their headings using one of the two methods the standard permits were losing points for not using the other; both are now accepted, as the formal conformance check and independent tools already did.

The reference set of documents was re-run in full: all 22 previously checked documents produced identical results, and the form that prompted the work went from a withheld verdict to a clean one. No change to what data is collected, how it is used, or how long it is kept.

v1.36.0

Audited 2026-07-19 · scope: a dedicated accuracy review of the audit algorithms themselves — how the tool judges PDF, Word, PowerPoint, and Excel files — followed by fixes for every issue it confirmed.

v1.36.0 makes the audit's judgments more trustworthy in both directions. The review found places where the tool accused documents of accessibility failures they did not have — for example, white text on dark table headers (a correct, accessible design) was being reported as an extreme color-contrast violation because the tool didn't look up the header's background color, and slide decks that record their language on every line of text were told they had "no language declared". Those false alarms are fixed: the tool now only asserts a confirmed WCAG failure when it actually resolved the evidence from the file, and says "not assessed — review manually" when it could not.

The review also closed the reverse problem — a serious barrier that used to pass silently: a PDF whose (older-style) security settings forbid screen readers from reading it at all could previously receive a perfect score. Such files are now failed with a clear explanation and fix. The tool additionally reads more of each document than before (Word headers, footers and footnotes, legacy link and image formats, Excel chart sheets), so fewer barriers can hide in unread corners. No change to what data is collected or how long it is kept.

v1.35.0

Audited 2026-07-19 · scope: a new automated status-check address used for uptime monitoring — not a security release.

v1.35.0 adds a single public status-check address that reports whether both halves of the service — the website itself and the analysis engine behind it — are running, so an external monitoring service can alert the team the moment either goes down. The check was reviewed before shipping: it reveals only "running or not" for each half and how long the analysis engine has been up — no user data, no file names, and nothing about any audit anyone has run. It was also designed so that deliberately overloading the status check cannot trick the monitoring service into reporting a false outage. No change to what data is collected or how long it is kept.

v1.34.0

Audited 2026-07-12 · scope: five preventive hardening measures covering file uploads, sign-out, and auto-remediation status pages, alongside an internal code reorganization and a new automated test/quality pipeline.

This release adds five defensive improvements identified during a routine internal review of the whole application. None of them close a hole that was ever actually used against the tool — think of it as adding a second lock to a door that already had one, not replacing a broken lock. The same review also reorganized how the audit engine's code is packaged internally (no change to what it checks, how it scores, or what data it collects) and added an automated pipeline that runs the full test suite, a code-style check, and a type-correctness check on every change pushed to the repository — so future changes are checked automatically going forward, not only when someone remembers to run the tests by hand.

What changed for an auditor reading this page

  • Hardened Stricter limits on compressed Word, PowerPoint, and Excel files — These files are compressed bundles of smaller pieces. The tool now checks, before it opens any of those pieces, how many there are and how large they would add up to be once uncompressed, and refuses a bundle that crosses a safe ceiling. This closes a gap the per-piece checks already in place (added when Word, PowerPoint, and Excel auditing first shipped) didn't cover on their own: a bundle made of an extreme number of small pieces, or one whose pieces add up to an extreme total.
  • Hardened Refusal of a risky, never-legitimately-used document feature — Word, PowerPoint, and Excel files are built internally from a markup language that has a handful of advanced features no ordinary document ever needs, but that a booby-trapped file could misuse to make the reading process balloon in memory or reach outside the file. The tool now recognizes this specific feature on sight and treats that piece of the file as empty rather than processing it. Genuine Word, PowerPoint, and Excel exports never use it, so no ordinary file is affected.
  • Hardened Signing out now fully ends your session on the server, not just in your browser — Previously, clicking sign-out cleared your browser's copy of your sign-in credential, but if a copy of that credential had ever been captured some other way, it would technically have remained usable until it expired on its own. The server now keeps a short record of every sign-out and immediately rejects that exact credential if it is ever presented again, so sign-out is final the moment you click it. (Sessions that began before this change shipped aren't covered by this new check, but they still expire on their own normal schedule, same as always.)
  • Hardened Auto-remediation status pages now require your job's private link — When you start an auto-remediation job without signing in, checking that job's progress or its completion receipt now requires the same private, single-use address you were given when the job started. Anyone without it is told the job doesn't exist, rather than being able to check on it by guessing or reusing an identifier.
  • Hardened Safer, more reliable database upgrades — Every update to the tool that changes the internal database's structure is now numbered and recorded, so the server always knows exactly which structural updates a given installation has already received and applies only the ones it's missing, in order, automatically — including on the existing production database. This replaces a less formal check-before-change approach and removes a way a future update could have been skipped or mistakenly reapplied.

No change to what data is collected or how long it is kept. Files are still processed in memory and discarded in seconds, exactly as before. The full technical write-up is in the project's README security section.

v1.33.0

Audited 2026-07-03 · scope: the new PowerPoint (.pptx) and Excel (.xlsx) audit features — a fresh, independent three-team red/blue review of everything a malicious Office file could try to do to the server.

v1.33.0 extends the tool to audit PowerPoint and Excel files, not just PDF and Word. Because these are also user-supplied files the server has to open and parse, this release got the same treatment as the earlier Word rollout: three independent reviews — covering server overload, hidden malicious content, and ways the scoring or access rules could be tricked — deliberately tried to break it with poisoned, oversized, and malformed files. Everything the reviews found was fixed and covered by a new automated test before this release shipped.

What changed for an auditor reading this page

  • Fixed Tighter limits on how much work a booby-trapped slide deck or spreadsheet can force — A PowerPoint or Excel file can bury thousands of objects several layers deep, or pair a small file with a few oversized embedded pictures, to make the server do far more work than the file's size suggests. The tool now counts that work — shapes, text, and cells at every nesting depth, and the running byte size of embedded pictures — and stops as soon as a safe limit is crossed, instead of after the damage is already done.
  • Hardened PowerPoint and Excel files are now analyzed in a separate, cancellable process — Previously, a pathological file could tie up the same in-process worker used for everything else; if analysis ran past its time limit, the work kept running in the background instead of truly stopping. Word, PowerPoint, and Excel files are now analyzed in their own short-lived process that the server can immediately and completely cancel the moment the time limit is reached.
  • Fixed Uploaded-file processing can no longer see the server's own passwords and keys — Every helper program the server hands an uploaded file to (for PowerPoint/Excel/Word analysis, for PDF repair, and for auto-remediation) now runs with the server's login secrets, API keys, and mail credentials stripped from its environment — so even a fully compromised helper process has nothing worth stealing.

No change to what data is collected or how long it is kept. PowerPoint and Excel files are processed in memory and discarded in seconds, exactly like PDF and Word; nothing new is stored or transmitted. The full technical write-up is in the project's README security section.

v1.32.1

Reviewed 2026-07-02 · entry recorded 2026-08-08 · scope: the automatic repair page interfering with its own progress reporting.

The automatic repair page no longer blocks itself on longer jobs. While a repair was running, the page asked the service for progress four times a second. On any job longer than about twenty-five seconds that was enough to exhaust the ordinary limit on how often one visitor may contact the service, so the page reported "too many requests" even though the repair itself was completing normally on the server. Progress checks are now counted against their own separate, much higher allowance — that particular request is a single quick lookup — and the page asks once a second, backs off if anything goes wrong, and clears its own error message as soon as it recovers. The general limit protecting the rest of the service is unchanged, and was confirmed by live test to still apply exactly as before. No change to what data is collected, how it is used, or how long it is kept.

v1.32.0

Audited 2026-07-02 · scope: a follow-up red/blue team review of a round of internal code-quality changes, plus a hardening of the website's defenses against malicious scripts.

This release reorganized how the tool is built internally (no change to what it checks or how it scores). Because that touched the pages that display a saved, shareable report, an independent review went back over them. It found — and this release fixes — a way that someone could craft a booby-trapped shareable report link so that a "helpful link" on it ran a hidden script in the viewer's browser. That has been closed at three levels: the link address is now checked when the report is saved and again when it is shown, and — the bigger, permanent safety net — the website now tells the browser to refuse any script that wasn't part of the original page, so this whole category of attack is blocked even if a new bug were introduced later.

What changed for an auditor reading this page

  • Fixed Malicious "helpful links" on shared reports — A shareable report link could be hand-crafted so that a link on it, once clicked, ran a hidden script instead of opening a web page. Link addresses on saved reports are now verified to be ordinary web (http/https) addresses both when the report is saved and when it is displayed, so a disguised script address is dropped.
  • Fixed Deliberately broken report links no longer knock out the page — A hand-crafted, malformed report link could make the shared-report page fail to load. The page now handles missing or malformed pieces gracefully instead of erroring.
  • Hardened The browser now blocks any un-approved script — The website's Content-Security-Policy was tightened so the browser will only run the scripts that are genuinely part of each page (each one carries a fresh, one-time stamp). Any injected or inline script — the main tool of this kind of attack — is refused outright, regardless of any future bug.

No change to what data is collected or how long it is kept. These are display-and-safety changes only; uploaded files are still processed in memory and discarded in seconds. The full technical write-up is in the project's README security section.

v1.31.1

Reviewed 2026-07-01 · entry recorded 2026-08-08 · scope: controls left behind in a downloaded report that could not do anything.

A downloaded report no longer shows buttons that do nothing. The download is a fixed snapshot with every section already opened, so the controls it had captured — show/hide switches, an expand chevron, a "click a row" hint — were visible but inert. They are now removed from the download while everything they would have revealed stays fully visible. The live page keeps them. No change to what data is collected, how it is used, or how long it is kept.

v1.31.0

Reviewed 2026-07-01 · entry recorded 2026-08-08 · scope: making the downloadable report identical to the one on screen.

The downloadable report is now exactly what you saw on screen. It had been separately assembled, and had drifted: different wording, and missing the method notes, the disclaimer, and the per-category detail. It is now taken directly from the report in front of you, with every collapsed section opened first and the page's styling included so the file stands alone. This matters because these downloads are what gets sent back to a document's author when a file fails — a download that disagrees with the page it came from is worse than no download. No change to what data is collected, how it is used, or how long it is kept.

v1.30.3

Reviewed 2026-07-01 · entry recorded 2026-08-08 · scope: the same correction applied to the two remaining download formats.

The plain-text and Markdown downloads now separate scored checks from those that did not apply, exactly as the web page, the shared report and the HTML download already did — with the reason given, and the distinction between "not applicable" and "not assessed" preserved. All four ways of reading a result now present it identically. No change to what data is collected, how it is used, or how long it is kept.

v1.30.2

Reviewed 2026-07-01 · entry recorded 2026-08-08 · scope: a downloadable report that disagreed with the page it came from.

The downloadable report listed checks that did not apply as though they were results. The web page and the shared report separated the checks that were scored from those that did not apply — showing the reason for each — while the download put everything in one table and showed detailed findings for all of it. On a Word document, which commonly has several inapplicable checks, a result showing five scored checks on screen could show ten rows in the download. The download now mirrors the page. No change to what data is collected, how it is used, or how long it is kept.

v1.30.1

Reviewed 2026-07-01 · entry recorded 2026-08-08 · scope: documentation catching up with Word support — no code paths changed.

The explanatory pages and diagrams now describe Word as well as PDF. Word checking had shipped in the previous release while the technical explanation, the description on the home page and the scoring guide still described a PDF-only tool — including how the two formats are examined by different means, and which checks apply to Word and which do not. The processing diagram was redrawn to show both paths. No checking behaviour changed. No change to what data is collected, how it is used, or how long it is kept.

v1.30.0

Audited 2026-07-01 · scope: the new Microsoft Word (.docx) audit feature — a fresh, independent red/blue team review of everything a malicious Word file could try to do to the server.

This release adds the ability to audit Word (.docx) files, not just PDFs. Because a Word file is really a compressed bundle the server has to open and read, three independent reviews deliberately tried to break it — by feeding it poisoned, oversized, or malformed files. The good news up front: the most serious risk (tricking the tool into showing malicious content to another person) was already fully blocked, because the tool escapes every piece of text taken from an uploaded document before it is ever displayed. Everything the review found was a way to overload the server, and all of it was fixed before this release.

What changed for an auditor reading this page

  • Fixed Protection against "zip-bomb" Word files — A tiny Word file can be crafted to expand into gigabytes when opened, to exhaust the server's memory. The tool now measures each part as it opens it and stops immediately if it grows past a safe limit, so a booby-trapped file is rejected instead of crashing the service.
  • Fixed Word files now share the same workload limits as PDFs — Word audits run through the same "two at a time" queue and the same hard time limit that PDF audits already used, so no single upload (or flood of uploads) can starve the server of resources.
  • Fixed Stricter handling of downloaded reports and error messages — The downloadable HTML report now escapes every value it shows (including scores and grades), and the audit-by-web-address feature no longer includes raw internal error text in its response.

No change to what data is collected or how long it is kept. Word files are processed in memory and discarded in seconds, exactly like PDFs; nothing new is stored or transmitted. The full technical write-up is in the project's README security section.

v1.29.0

2026-06-27 · scope: how often the tool will accept automated audit requests, and an optional access key for a trusted partner system. Reviewed for security; no change to what data is collected or how long it is kept.

v1.29.0 tightens the limit on how many audits an anonymous visitor can request per hour — back down from a temporary increase used during a large internal audit campaign — so the public tool can't be hammered with thousands of automated requests an hour. A trusted ICJIA system can present a secret access key to get a higher limit and to check ICJIA pages that live on non-Illinois web addresses.

No data is collected, stored, transmitted, or retained any differently; no retention window changed. The access key only raises rate limits and widens which web addresses can be checked — it never lets anyone reach internal or private systems (those stay blocked for everyone), and it is held only as a server environment secret, never in the database or in any report.

v1.28.1

2026-06-10 · scope: a small fix to make a loading spinner icon appear. No security review was required — nothing about data handling changed.

v1.28.1 restores a loading-spinner icon that was failing to load on the auto-remediation screen. No data is collected, stored, transmitted, or retained any differently; no retention window, endpoint, or permission changed.

v1.28.0

2026-06-10 · scope: front-end performance and a change to one export format. No security review was required — nothing about data handling changed.

v1.28.0 replaces the Microsoft Word download with a plain-text download and makes the explanatory diagrams load faster, by removing two large code libraries from the website. No data is collected, stored, transmitted, or retained any differently; no retention window, endpoint, or permission changed. Your audited files are still held in memory only and discarded in seconds.

v1.27.0

Audited 2026-06-10 · scope: a full, independent red-team security review of the entire application — the website, the server, the audit pipeline, and the optional auto-remediation pipeline.

A comprehensive adversarial security audit was performed across the whole application. It found no critical issue and no way for one user to reach another user's data: the high-impact vulnerability classes (database injection, command injection, file-path escape, cross-site scripting, and login bypass) were each tested and verified clean. The items found were hardening against denial-of-service and against future misconfiguration, and all of them were fixed in this release.

What changed for an auditor reading this page

  • Fixed Stronger protection against server-side request abuse — The feature that checks a web page's accessibility now strictly confirms, on every request the page makes, that it is only reaching approved public addresses — never an internal or cloud-metadata address. Verified to still load legitimate state-government pages normally.
  • Fixed Hard time limits on document processing — The audit and remediation steps now have enforced time limits and will cleanly stop a document that is deliberately crafted to run forever, so one upload can't degrade the service for everyone.
  • API Additional safe-by-default protections — Stricter browser security headers on the website, a fail-safe refusal to start if the login secret is ever misconfigured, removal of the sharer's email from public share links, and several smaller defensive fixes. No code path that stores, transmits, or retains your data changed; no retention window, endpoint, or permission changed.

v1.26.1

Audited 2026-06-10 · scope: follow-up fixes to v1.26.0 — the auto-remediation intake, one title-quality check, and a missing interface icon. No security review was required — nothing about data handling changed.

v1.26.1 lets auto-remediation accept documents with minor, repairable file defects (previously these failed immediately, even though they are exactly the files remediation is for), flags machine-generated download filenames used as document titles so they are not mistaken for real titles, and restores the loading spinner icon. No security-relevant behavior changed.

What changed for an auditor reading this page

  • Note Remediation accepts repairable files — A document with a small file defect is repaired during intake instead of being rejected, matching how the audit itself reads such files since v1.26.0.
  • Note Filename titles are called out — A title like "Report-210525T15080148" (a download filename) now earns partial credit with a note to write a real title; short legitimate titles like "COVID-19" are unaffected. Some documents' Title & Language score may move slightly.
  • API No new data and no new attack surface — The remediation change reads a status code and a file the tool already wrote inside the job's own working folder; the icon fix bundles an image set at build time. No endpoint, retention window, or permission changed.

v1.26.0

Audited 2026-06-10 · scope: accuracy fixes across the PDF analysis engine — how the document file is read, how tables, forms, lists, and titles are judged, and when the report may claim a confirmed WCAG failure. No security review was required — nothing about data handling changed.

v1.26.0 corrects cases where the audit reported things that were not true about a document. The most important: a PDF with a minor, repairable file defect (common in older or re-saved documents) was scored as if it had no accessibility tagging at all — the identical document could score 100 or 42 depending on that one defect. The release also stops several false alarms, closes a detection gap, and re-verifies every "How to fix" instruction against Adobe's current documentation. An independent code review was completed before release.

What changed for an auditor reading this page

  • Note Slightly damaged files are now read correctly — A document with a small, automatically repairable file defect is no longer falsely reported as untagged. Some previously low scores on tagged documents will rise to reflect their real structure.
  • Note False alarms removed — A multiple-choice (radio button) question no longer counts as several unlabeled form fields; tables with merged cells are no longer flagged as irregular; one-word document titles ("Budget2024") are no longer treated as missing; lists without a separate bullet label are no longer failed; and a table nested inside another is no longer counted twice.
  • Note "Confirmed failure" now means measured, not guessed — The conformance verdict only claims a reading-order failure when the tool actually measured the tag order against the visual order, and only claims a missing title when the document truly has none.
  • API No new data and no new attack surface — Every fix reads output the analysis tools already produced for the same document. No code path that stores, transmits, or retains data changed; no endpoint, retention window, or permission changed.

v1.25.0

Audited 2026-06-05 · scope: PDF/UA + artifact + font detection fixes, link and reading-order scoring calibration, and a new PDF/UA-1 conformance-signals panel. No security review was required — nothing about data handling changed.

v1.25.0 corrects how the audit reads three signals it had been reporting incorrectly (the PDF/UA identifier, artifact tagging, and embedded Type3 fonts), softens two score rules to match WCAG and PAC (a visible web address used as link text is no longer treated as a failure; an essentially-correct reading order is no longer docked for a tiny measurement difference), and adds a panel summarizing the document's PDF/UA-1 signals. No security-relevant behavior changed.

What changed for an auditor reading this page

  • Note More accurate findings — The report no longer claims a PDF/UA-tagged file "has no PDF/UA identifier" or "no artifact tags," and it no longer flags embedded Type3 fonts as missing. These were wording/display errors; document scores were not affected by them.
  • Note Two score rules relaxed — A link whose visible text is a full web address now counts as acceptable (it tells the reader where it goes), and a document whose reading order is essentially correct is no longer docked for a 1–2% measurement difference. Some documents score slightly higher.
  • API No new data and no new attack surface — The fixes read data the analyzers already produced; the new PDF/UA-1 panel displays values already computed during the audit. No code path, endpoint, retention window, or data-handling behavior changed.

v1.24.2

Reviewed 2026-06-05 · entry recorded 2026-08-08 · scope: a table scoring rule that penalised documents for something the standard does not require.

A table without a caption is no longer marked down. A caption is good practice, but no accessibility success criterion requires one — and a fully conformant simple table without one was being capped at 95. Those points are now awarded unconditionally and a missing caption is raised as an optional suggestion. Combined with the header correction in the previous release, a simple table built correctly now scores 100. Scores can move slightly upward on documents containing uncaptioned tables. No change to what data is collected, how it is used, or how long it is kept.

v1.24.1

Reviewed 2026-06-05 · entry recorded 2026-08-08 · scope: three reporting and scoring inaccuracies reported by a user.

Three inaccuracies in what reports said about tables and headings were corrected, all reported by someone using the tool. A table nested inside another table's cell was being counted as a separate table, so reports showed more tables and more rows than the document actually had. Headings were being listed in the order they happened to be stored rather than in reading order, so a heading added later could appear at the end of the outline — which also carried a hidden scoring fault, since an out-of-order list could trigger a false "heading levels skipped" penalty.

And a table could score below 100 while passing every check. The standard permits two methods of associating a table's headings with its data, and only one of them was being credited; a table built correctly using the other was losing points it had earned. Both are now accepted, which is what the formal conformance check and independent tools already did. No change to what data is collected, how it is used, or how long it is kept.

v1.24.0

Audited 2026-06-03 · scope: WCAG 2.2 re-anchor, IITAA 2.1 citations, announcement banner, and a new /wcag-2-2 page. No security review was required — nothing about data handling changed.

v1.24.0 re-anchors the displayed standard to WCAG 2.2 Level AA, a superset of the WCAG 2.1 AA that IITAA 2.1 (§E205.4) and ADA Title II require. No automated check changed and no score weight changed; the new 2.2 criteria are interactive/manual and are shown as "not assessed — manual review" (only for documents with interactive form fields). The audit can be reverted to WCAG 2.1 by an administrator via the WCAG_VERSION environment setting. No security-relevant behavior changed.

What changed for an auditor reading this page

  • Note WCAG 2.2 Level AA is now the displayed standard — The audit labels, conformance verdict, exports, and UI copy all reference WCAG 2.2 AA (a strict superset of WCAG 2.1 AA). New 2.2 criteria are shown as "not assessed — manual review" rather than pass or fail. WCAG 2.1 AA remains the legal minimum under IITAA 2.1 §E205.4 and ADA Title II; WCAG 2.2 is the newer, stricter version.
  • Note IITAA 2.1 cited throughout — Illinois IITAA 2.1 is now cited alongside WCAG and ADA Title II across the homepage, footer, conformance box, exports, and meta. This page's §1 description and the compliance-explainer in §10 have been updated to include "IITAA 2.1".
  • API No new data and no new attack surface — All changes are presentational. No code path, endpoint, or data-handling behavior changed; every defensive control from prior releases remains in force. The WCAG_VERSION env flag controls text and criteria display only.

v1.23.0

Reviewed 2026-06-03 · entry recorded 2026-08-08 · scope: making every report state which document it describes — not a security release.

Every report now names the file it describes, in full, across the top. A report that is saved, printed or forwarded on could previously be mistaken for one about a different document — the filename appeared only in smaller text beside the score. It now leads the page as a banner, wrapping rather than being cut short, and carries the same prominence into every downloadable form: the web page, the printable version, the Word version and the plain-text version all lead with the file's name. This is a provenance improvement rather than a security one: an accessibility report is a record about a specific document, and a record that cannot be tied confidently to its subject is of limited use to whoever receives it. No change to what data is collected, how it is used, or how long it is kept.

v1.22.3

Audited 2026-05-22 · scope: a scoring-engine cleanup — a more honest summary, a rounding fix, and removal of dead code. No security review was required; nothing about data handling changed.

v1.22.3 refines how the audit is scored and explained. It does not change what the audit collects, where it is stored, or how long it is kept. No new endpoints, no authentication change, no retention change, no new attack surface.

What changed for an auditor reading this page

  • Note A more honest plain-language summary — The summary shown with the score now takes the WCAG conformance verdict into account. Previously a document could be summarised as "strong" while the verdict box separately reported a failure; a confirmed failure is now reflected in the summary as well.
  • Note A category one item short can no longer look perfect — Category scores for alt text, links, and form fields are now rounded down. A document missing one item out of many — for example one image without alternative text — can no longer round up to a flawless score; it now scores just below 100, so the report never implies a category is issue-free when it is not.
  • API Dead code removed; no new attack surface — About 170 lines of unreachable scoring code were deleted. This release is internal computation only — no code path, endpoint, or data-handling behaviour changed, and every defensive control from prior releases remains in force.

v1.22.2

Audited 2026-05-22 · scope: one verdict-box heading string and a documentation correction. No security review was required — nothing about data handling changed.

v1.22.2 reworks the wording shown in the conformance verdict box when a document does not pass, and corrects stale test counts in the project README. It does not change what the audit checks, what data is collected, where it is stored, or how long it is kept. No new endpoints, no authentication change, no retention change, no new attack surface.

What changed for an auditor reading this page

  • Note Clearer next-step wording on a failing document — When a document does not pass, the verdict box heading now says it needs "additional manual remediation" — a plain signal that automated tooling has done what it can and the remaining fixes are hands-on (Adobe Acrobat's Accessibility Checker, or correcting the source document and re-exporting).
  • API No new data and no new attack surface — This release is a copy and documentation change only. No code path, endpoint, or data-handling behavior changed; every defensive control from prior releases remains in force.

v1.22.1

Audited 2026-05-22 · scope: a wording and presentation change to the conformance verdict box. No security review was required — nothing about data handling changed.

v1.22.1 changes how the v1.22.0 WCAG conformance verdict is displayed. It does not change what the audit checks, what data is collected, where it is stored, or how long it is kept. No new endpoints, no authentication change, no retention change.

What changed for an auditor reading this page

  • Note Clearer, less alarming verdict wording — When a document scores well (an A or B grade) but still has a flagged accessibility issue, the verdict box now explains plainly that WCAG is judged one criterion at a time — a single gap is still worth fixing, but a strong grade still means the document is in good shape. The box is shown in green for strong documents and red for weak ones; every flagged issue is still listed in full, whatever the color.
  • New Links to the official standards — The verdict box now links directly to the published WCAG 2.1, Illinois IITAA, and ADA Title II standards, so a reader can check the rules the audit measures against at their source.
  • API No new data and no new attack surface — This release is presentation only. The verdict is still computed from information the audit already produced, the downloadable reports are unchanged, and no new information is sent or stored anywhere. Every defensive control from prior releases remains in force.

v1.22.0

Audited 2026-05-21 · scope: a scoring-methodology release — a new WCAG conformance verdict, recalibrated category weights, and clearer labels. Reviewed with an adversarial scoring audit, not a red/blue-team security review.

v1.22.0 changes how the audit is scored and explained — it does not change what data is collected, where it is stored, or how long it is kept. No new endpoints, no authentication change, no retention change. The headline addition is a plain pass/fail WCAG 2.1 conformance verdict shown alongside the 0–100 score, because a high score is not the same thing as passing WCAG. One correctness bug found during the review was fixed before this release was tagged.

What changed for an auditor reading this page

  • New WCAG conformance verdict — Every audit now states plainly whether the document has confirmed failures against WCAG 2.1 Level AA — the standard the Illinois IITAA and the federal ADA Title II rule require. The verdict is separate from the 0–100 score and never claims a document is "conformant"; when the automated checks find nothing it says so, and still asks for manual review. Each cited rule links to the official W3C explanation.
  • Fix No false verdicts on unreadable files — The review found that a damaged or password-protected PDF could be handed a fabricated "fails WCAG" verdict because the analyzer had not actually been able to read it. That is now fixed: an unreadable file honestly reports that no verdict could be determined.

    This was a correctness defect in brand-new code, caught and fixed before tagging — no released version ever shipped it.

  • Note Scores shifted — by design — Category weights and some labels were recalibrated to match WCAG conformance levels more honestly. As a result, a score produced by v1.22.0 is not directly comparable to a score from an earlier version. An audit campaign that spans this upgrade will see numbers move; that movement reflects the improved methodology, not a change in the documents.
  • API No security regressions — Every defensive control from prior releases remains in force. No schema migration. The conformance verdict is computed from data the audit already produced; the report exports gained a verdict section but send no new data anywhere.

v1.21.1

Audited 2026-05-19 · scope: shared-report UI parity with the real-time audit page, plus an elevated analyze rate limit for the duration of the in-flight ICJIA fleet audit campaign.

This is a small follow-up release to v1.21.0, not a security change. v1.21.0 simplified the live audit page by removing the Adobe Acrobat parity panel, but the same panel was left in place on the shared-report page (/report/:id) — so two auditors looking at the same content via different URLs ended up seeing two different summaries. This release fixes that inconsistency. It also bumps the per-caller hourly analyze rate limit to support an in-flight fleet audit pass.

What changed for an auditor reading this page

  • UX Consistency — Shared and saved report pages now show exactly what the live audit page shows. No more Acrobat parity panel on /report/:id.

    What was wrong: v1.21.0 removed the 32-rule Adobe Acrobat parity card from the live audit page in favor of a single WCAG-anchored Strict score, but the same card kept rendering on the shared-report page. Auditors comparing notes off a shared link saw a presentation that didn't match the live audit, which could read as a deliberate difference in scoring.
    What this release does: the parity-card block was removed from the shared-report template. The underlying data is still saved in the database (so historic API consumers that already parse it keep working), but it's no longer rendered on the page. No schema change. The per-finding "How to Fix in Adobe Acrobat" remediation guidance inside each category card is kept — that's per-finding remediation advice, not a separate scoring profile, and it appears on the live audit page too.

  • OPS Elevated analyze rate limit for the audit campaign — The per-caller hourly analyze rate limit was raised from 35/hour to 5000/hour for the duration of the in-flight ICJIA fleet audit campaign. The ~5000-PDF inventory is being re-audited across multiple passes over several days as content is remediated and re-checked, not a single one-shot pass. The elevated limit will stay in place for the duration of the campaign and revert to a tighter number once it concludes.

    Why this is OK: the per-caller analyze limit is a fair-use throttle. The actual abuse mitigations live on the remediation side — the 100/day remediation cap per caller, the 60-minute audit-gate sha256(bytes) hash check, the SSRF allowlist, the upload size cap, and the auth gate are all unchanged. The audit pipeline does not write user-supplied content to durable storage beyond the lightweight audit_log row (no PDF bytes; just metadata).

  • API No security regressions — Every other defensive control from v1.20.1 and v1.21.0 remains in force. No schema migration. No change to the authentication layer, the SSRF allowlist, the audit-gate hash check, the daily remediation cap, the retention windows, or the URL-fetch posture.

    The two changes in this release are a 5-line UI deletion on the shared-report template and a single numeric raise on one rate-limit constant. No other code paths were touched.

v1.21.0

Audited 2026-05-19 · scope: simplification release. Retired the dual Strict/Practical scoring toggle in favor of a single canonical Strict score; promoted veraPDF PDF/UA-1 verdict on the remediation result page.

This release is a UI simplification, not a security change. Auditors and agency staff consistently reported that the audit page was hard to read because it showed two scoring profiles at once — "Strict" and "Practical" — and asked users to choose between them. That cognitive load got in the way of the actual accessibility findings. After review, the team retired Practical and kept Strict, which is the WCAG 2.1 AA + IITAA §E205.4-anchored score that maps directly to Illinois accessibility law. The PDF/UA technical conformance signal that Practical tried to summarize is now surfaced more authoritatively on the remediation page via a dedicated veraPDF Pass/Fail check.

What changed for an auditor reading this page

  • UX Simplified — The audit results page shows one score, anchored to WCAG and IITAA. No more "view by Strict / view by Practical" toggle. The grade you see is the legally-relevant grade.

    What was wrong: showing two profiles created an implicit "which one is correct?" question for the reader. The Strict view is what Illinois IITAA and the ADA point to; the Practical view layered a separate PDF/UA-flavored weighting on top, which was useful for tool reconciliation but not for publication decisions.
    What this release does: the audit page now shows only the Strict / WCAG-anchored score. The underlying scoring engine is unchanged — same nine categories, same weights, same WCAG-anchored thresholds. Just less noise on the page.

  • UX Promoted — The remediation result page now surfaces a clear PDF/UA-1: Pass / Fail / Not run badge right next to the post-remediation score.

    What was wrong: the veraPDF conformance verdict (an open-source check against the published PDF/UA-1 / ISO 14289-1 standard) was already running as part of every remediation, but it was buried in a section labeled "Compliance disclaimer" further down the result page. Auditors needing the PDF/UA verdict had to scroll.
    What this release does: a compact Pass/Fail badge appears immediately below the score; the detailed section below was renamed to "PDF/UA-1 conformance check" so its purpose is obvious; the badge jumps to that section for the full rule failure list when failures exist. When veraPDF isn't installed on the server, the badge clearly reads "check not run" rather than pretending the check ran successfully.

  • API Compatibility — Historical reports and external automation keep working without changes.

    What was wrong: a hard removal of the Practical profile would have broken the fleet-CSV integration shipped in v1.20.0, which lists both Strict and Practical columns per audited PDF.
    What this release does: the scoreProfiles.remediation field and the practical key in the /api/audit-url response are kept as aliases of Strict — same number, same grade. External CSV consumers see both columns populated with the Strict score and don't need updates. The alias will be removed in a future release once we've confirmed no consumer depends on the values differing.

  • API No security regressions — All SSRF, rate-limit, audit-gate, daily-cap, and retention controls from v1.20.1 remain in force. The cleanup pass still purges remediation files, jobs, and audit-log rows on schedule.

    The simplification is a UI and scoring-presentation change. It does not modify the upload pipeline, the authentication layer, the rate limiters, the audit-gate hash check, the daily cap, the SSRF protections, or the retention windows.

v1.20.1

Audited 2026-05-18 · scope: post-feature red/blue team review of the v1.20.0 fleet-integration surface

This is a dedicated security release that follows the team's standing practice: every feature ships through a fresh red/blue team review before tagging. The v1.20.0 release introduced the fleet-audit-by-URL endpoint; this review examined that new surface plus the related existing endpoints, found seven issues worth flagging, and fixed all of them before this release was tagged. The purpose of this entry is to document those findings so an auditor can see (a) what was looked at, (b) what was discovered, (c) what was done about it, and (d) how the team's iterative-review pattern works.

Findings & what was done

  • P1 Fixed — A DNS-based trick could have let an attacker reach the server's own internal network through our URL audit endpoint.

    What was wrong: when someone submitted a URL for audit, the tool checked whether the hostname matched the allowlist of approved ICJIA domains before fetching it. If an attacker could control DNS for any subdomain of an approved domain — for example, by compromising a partner agency that operates a subdomain — they could point that hostname at the server's loopback address (127.0.0.1) and trick us into fetching our own internal services on their behalf.
    How it was fixed: the tool now resolves the hostname's IP address itself, before fetching, and refuses to connect to any IP in private, loopback, link-local, or multicast ranges. The check repeats on every redirect hop so a redirector planted on an approved host can't chain us into a private address either. The fix covers both IPv4 and IPv6.

  • P1 Fixed — Redirects from approved hosts to private addresses were silently followed.

    What was wrong: when the URL audit endpoint encountered an HTTP redirect, it followed the chain up to 20 hops without re-checking each hop against the allowlist. An attacker who could place content on an approved host could redirect us through to an internal address.
    How it was fixed: redirects are now handled manually with the full allowlist and DNS-IP check on every hop, capped at three redirects total.

  • P1 Fixed — The bulk-inventory endpoint had no allowlist check at all.

    What was wrong: caught during the security review while migrating the other URL-fetch endpoints. The bulk-inventory endpoint accepts a list of PDF URLs and fetches each one. It had its own private fetcher with no allowlist — an authorized user could submit a list containing internal addresses and the tool would fetch them. Latent since the endpoint shipped, not previously discovered.
    How it was fixed: the bulk endpoint now uses the same allowlist-plus-private-IP-block plumbing as the other URL endpoints.

  • P2 Fixed — In no-login deployments, one user could unlock remediation for content audited by a different user.

    What was wrong: when the tool is run without requiring login, every user is treated as the same "anonymous" identity. The new audit-before-remediation check (added in this release — see "Added" below) would have matched any anonymous user's audit against any other anonymous user's remediation attempt.
    How it was fixed: in no-login mode, the identity now includes the user's IP address. The production deployment requires login, so this issue never affected real users.

  • P2 Fixed — The audit-history table grew without limit.

    What was wrong: the canonical audit-history table had no retention policy. An attacker repeatedly auditing unique files could slowly fill the database.
    How it was fixed: records older than 365 days are now purged by the periodic cleanup sweep, matching the share-link retention window.

  • P2 Fixed — A narrow race window let two simultaneous remediation requests both pass the daily limit.

    What was wrong: the daily-limit check and the actual job-creation were two separate steps. Two perfectly-simultaneous requests at the cap boundary could both see "you're under the limit" and both proceed.
    How it was fixed: the limit check is now repeated as part of the same atomic database transaction that creates the job, so the cap can no longer be exceeded by even one.

  • P3 Verified clean — Browser cookie security flags.

    What was checked: the login session cookie is set with the protective flags (HttpOnly, Secure, SameSite-Strict) that prevent it from being read by client-side scripts, transmitted over plain HTTP, or sent with cross-site requests.
    Result: all three flags are correctly set in production. No change needed; recorded in this audit trail for completeness.

Also added in this release — driven by the same security thinking

  • Audit required before remediation. Every request to remediate a PDF must be preceded by an audit of the same content within the previous 60 minutes. Any audit path counts — direct upload, URL audit, or fleet bulk. This prevents automated abuse where someone bypasses the audit pipeline and floods the remediation worker directly.
  • Daily remediation cap. Up to 100 remediations per caller per 24 hours. Sized so a normal agency workflow (~50 PDFs in a busy day) is unaffected, but a flood of thousands is blocked.
  • Unified audit record. Every audit endpoint now writes a row to the canonical audit-history table with the content fingerprint (SHA-256 hash of the file's bytes). Required so the audit-before-remediation gate works uniformly across all audit paths. The hash is just a fingerprint — it doesn't expose the PDF's contents and can't be reversed back into the document.

Methodology — for the auditor record

The team follows a deliberate practice: every feature ships through a fresh red/blue team review before tagging. The review examines the newly-introduced surface from a sophisticated-adversary perspective, looks for attack patterns like DNS rebinding, race conditions, identity collapse, and slow-burn denial-of-service, and either fixes findings in the same release window or documents them for future work. This release (v1.20.1) is the security-followup to v1.20.0, which added the fleet-audit-by-URL feature. The pattern repeats with every feature release — earlier entries in this audit history list the findings from prior reviews.

For a manager reading this page: the intent here is transparency. The tool is built and reviewed iteratively, and this page is the auditor-readable trail of what was reviewed, what was found, what was fixed, and what was deliberately accepted with mitigation. The technical equivalent (with full code references) lives in README.md § Security for engineers and security reviewers who need that level of detail.

v1.20.0

Audited 2026-05-18 · scope: download filename dialog, PDF export, accessibility polish

A feature release with two material auditor-facing changes: remediated PDFs can now be downloaded under the exact original filename (critical for CMS file replacement, where existing links resolve by name), and the audit report can be saved as a PDF using the browser's own print dialog. No new data is collected, retained, or transmitted. The retention policy described elsewhere on this page is unchanged.

Findings & changes

  • P3 Changed — Remediated PDF download now defaults to the user's exact original filename.

    What changed: when a user remediates a PDF and clicks Download, the file is now saved under the same filename they uploaded — including any spaces, unicode, or punctuation. The download dialog presents three options with "Keep original filename" pre-selected and badged Recommended. The other two ("Add a _remediated suffix" or "Use a different filename") are opt-in.
    Why: the most common workflow for remediating an agency PDF is to replace the file in the CMS in place — every existing link on the website, in old emails, in shared documents, keeps working as long as the filename matches. The previous behavior automatically appended _remediated to the filename, which broke this workflow.
    Safeguards: the "use a different filename" path explicitly warns the user that the change will break existing links and requires a second click of the Download button to confirm. There is no path traversal risk — the custom filename is treated only as a display name for the browser's save dialog and is capped, encoded, and forced to .pdf before being sent in the response header. The actual file on disk is always located by job ID, never by user-supplied filename.

  • P3 Added — Audit reports can now be saved as PDF via the browser's print dialog.

    What changed: the audit report page and the shared-report page each gained a "PDF (browser print)" button. Clicking it opens the browser's own print dialog, where the user picks "Save as PDF" as the destination. The page applies a print stylesheet that hides interactive controls, switches to black-on-white text, expands collapsed technical sections, and arranges page breaks cleanly.
    What this does not change: no new server-side rendering happens — the PDF is created entirely by the user's own browser, on the user's own machine. No PDF content is transmitted to or stored on our server as part of this feature. The chosen filename is whatever the user types in the browser's save dialog and is not visible to us.

  • P3 Fixed — Accessibility polish on the remediation result page.

    What changed: the result page was showing layout shift after content loaded (a known accessibility annoyance for users on slow connections or with reduced-motion preferences), and result sections were appearing partway through the progress animation rather than after it. Both fixed.
    Visible improvement: Lighthouse performance score on the result page rose from 84 to 96 on desktop. No retention or privacy implications.

Operational improvements

  • New AGENTS.md at the repository root documents the load-bearing conventions for AI coding agents (Claude Code, Codex, Cursor, etc.) so engineers using those tools to extend the code base get oriented in one read. Not user-facing; reduces the chance of a misconfigured agent committing the wrong thing.
  • The "Technical Details" expandable on the main results page now includes the same four pipeline diagrams already on the standalone Technical Details page.

v1.19.0

Audited 2026-05-18 · scope: fleet integration + accessibility polish + retention-policy change

This release adds the fleet inventory integration (one HTTP call per PDF returns strict + practical grades plus a year-long shareable report link), expands the URL allowlist to cover all *.illinois.gov state-agency subdomains, bumps the shared-report retention window from 15 days to 365 days, and fixes seven accessibility rule violations across the public policy + technical-details pages. The most material policy change for an auditor reading this page is the retention bump — see the first finding below.

Findings & changes

  • P2 Accepted — Shared-report retention window extended from 15 days to 365 days.

    What changed: when someone creates a shareable audit-report link (either from the web UI's "Create Shareable Link" button or via the new fleet audit-by-URL automation), the resulting link now stays valid for one year instead of 15 days. This applies to the metadata record only — no PDF content is stored alongside it. After 365 days the row becomes eligible for the periodic cleanup sweep and the URL stops working.
    Why: auditors and managers reviewing fleet-inventory reports (which list every PDF across ICJIA's sites) need report links that survive between quarterly review cycles. A 15-day TTL caused most links to break before the next review even happened.
    Storage cost: the row holds scores, category findings, and timestamps — no PDF bytes. A 100-PDF fleet at roughly 50 KB per record grows the database by about 5 MB per year. The tradeoff was evaluated and accepted in favor of usability.

  • P2 Accepted — URL allowlist expanded so the fleet automation can audit PDFs across the full Illinois state-agency footprint.

    What changed: the audit-by-URL endpoint previously accepted only a handful of explicit ICJIA subdomains. It now also accepts: illinois.gov (every state-agency subdomain), icjia.cloud, icjia.app, and ilheals.com (each including all subdomains).
    Why: the ICJIA fleet audit lists PDFs across every site the agency operates and every partner agency. The previous narrow allowlist couldn't cover that fleet.
    What it doesn't change: all of the existing protections still apply — the server still blocks private / local / loopback addresses (no SSRF into internal networks), still rejects oversized files (100 MB cap), still requires the fetched bytes to begin with the %PDF- header, and still rejects look-alike domains (a URL like illinois.gov.evil.com does not match the allowlist). The threat profile is the same as a person pasting any one of these URLs into the web interface.

  • P3 Fixed — Seven accessibility rule violations on the public policy and technical-details pages.

    What was wrong: a full axe + Lighthouse audit found that the diagram boxes on these pages couldn't be reached via keyboard, that an inline link in this audit history section was distinguishable only by color (a barrier for colorblind readers), and that several scrollable code blocks couldn't be scrolled without a mouse.
    How it was fixed: each scrollable region is now keyboard-focusable, the inline link is now underlined, and the diagram boxes' redundant ARIA labels were replaced with proper structural markup. Both pages now score a perfect 100 / 100 on both axe (no violations) and Lighthouse's accessibility audit.

  • P3 Fixed — The new fleet endpoint reported the strict score in both the strict and practical slots of its response.

    What was wrong: the new /api/audit-url endpoint had a key-name mismatch with the underlying scoring engine — what the engine internally calls "remediation" the user interface labels "practical." The endpoint looked for the wrong name, found nothing, and fell back to the strict score, so the practical column in the fleet output would have shown the strict number instead of the practical one.
    How it was fixed: caught in the local smoke-test step before any caller integrated against the endpoint, so no production fleet report ever published the wrong number. The name mapping is now correct (verified against three test PDFs whose strict and practical scores genuinely differ).

v1.18.1

Audited 2026-05-18 · scope: veraPDF integration correctness + remediation result-page UX

A patch release with four operational fixes against the v1.18.0 remediation feature. None of these findings expose private data or change the file-retention guarantees described elsewhere on this page. One finding is security-adjacent: an auditor who consulted the PDF/UA-1 compliance card on the remediation result page would have seen a silently wrong verdict in any deployment running a recent veraPDF version. Note: at the time of the fix, this feature flag was still off in production, so no real audit was shown the wrong verdict.

Findings

  • P1 Fixed — PDF/UA-1 compliance verdict was always shown as "not compliant," regardless of the actual PDF.

    What was wrong: the tool calls a third-party validator (veraPDF) to report whether the remediated PDF technically conforms to the PDF/UA-1 accessibility standard. The newest version of that validator changed the shape of its result data slightly (it now returns a list of profile results rather than a single one). The tool was reading the result in the old shape, so the verdict was always missing, and the missing verdict was treated as "not compliant." Any auditor looking at the compliance card on the result page would have been shown an incorrect technical verdict.
    How it was fixed: the tool now handles both the new and old result shapes correctly. Verified against a live install of the latest veraPDF version. No production deployment had this feature enabled yet at the time of the fix, so no real audit was actually shown the wrong verdict.

  • P2 Fixed — A second veraPDF shape change could have caused a crash inside the validation routine.

    What was wrong: in the same shape change that broke the verdict, veraPDF also moved its rule-by-rule detail list. A defensive fallback in the tool would have tried to read the new "count of failed rules" as if it were a list, which would have crashed the validation routine on certain inputs.
    How it was fixed: the unsafe fallback was removed and the read order was updated to prefer the new location first. No crashes were observed in production — this was caught during the same review as the P1 above.

  • P3 Fixed — Failure count under-reported on heavily-non-compliant PDFs.

    What was wrong: the tool reported a compliance-failure total based on the top 20 issues it displayed, rather than veraPDF's own total. On a deeply non-compliant PDF the displayed total would have been lower than reality.
    How it was fixed: the tool now uses veraPDF's own total when available. Older veraPDF versions still use the "sum the displayed list" fallback.

  • P3 Fixed — The "Fix steps" links on the remediation result page were dead.

    What was wrong: clicking "Fix steps" next to an outstanding issue on the result page did nothing. The link tried to jump to a card that exists on the audit page but not the result page.
    How it was fixed: each issue row now opens an inline accordion showing the detailed findings and numbered Adobe Acrobat fix steps right there on the result page — no navigation needed. Same content as the audit-page cards. Not a privacy or security issue, but a real usability problem for an auditor following up on outstanding items.

Operational improvements

  • The Ubuntu deploy script (rebuild.sh) now auto-detects an installed veraPDF and, when it isn't installed, prints copy-paste install instructions including the persistence command so the path survives a server reboot. Reduces drift between development and production installs.

v1.18.0

Audited 2026-05-18 · scope: PDF auto-remediation feature (entire new surface)

The remediation pipeline was the first major surface added to this tool. The pre-release red/blue team review covered the public API endpoints, the worker, the frontend, the cleanup sweep, the database schema, and the file lifecycle. The 15-row threat-model checklist documented in docs/archive/pdf-remediation-integration-plan.md (§ Security) was the basis of the review.

Findings

  • P1 Fixed — Memory exhaustion via large output downloads.

    What was wrong: the download endpoint loaded the entire remediated PDF (up to 50 MB) into the API process's memory before sending it to the user's browser. Under several simultaneous downloads, this could exceed the API process's 512 MB memory cap and crash it.
    How it was fixed: switched to streaming the file in small chunks (createReadStream + stream.pipe(res)). Memory usage is now constant regardless of output size.

  • P1 Fixed — Race condition allowed concurrent double-download.

    What was wrong: the download token was supposed to be single-use, but two near-simultaneous requests with the same token could both pass the validation check and both retrieve the file before either completed. This violated the "single-use" privacy guarantee.
    How it was fixed: the job is marked status='expired' before the file is sent, so any concurrent second request immediately sees the expired status and receives a "410 Gone" response.

  • P2 Mitigated — Auth-bypass when login is not required (dev/internal mode).

    What was found: when the tool runs with the "require login" flag turned off (typical for internal development), the per-job email check on the status, download, and receipt endpoints is bypassed. Anyone who knows a job's UUID could read its data.
    How it was handled: job UUIDs use 122 bits of cryptographic randomness — guessing one is computationally impractical. Production deployments run with login required, which closes the gap entirely. This limitation is documented in the integration plan as the known posture; it does not affect the production deployment.

  • P2 Accepted — Legacy scoring data computed but unused.

    What was found: the Adobe Acrobat parity score (a 32-rule check) is still calculated on the server even though the user interface no longer displays it. Costs about 50 milliseconds per audit.
    How it was handled: intentionally kept for data-shape stability so existing tests and audit-log entries continue to work. May be removed in a future release if the cost ever matters. Not a privacy or security issue — just dead code.

  • P3 Accepted — Conservative PDF validation rejects borderline files.

    What was found: the qpdf --check validator flags some technically-valid PDF outputs as "warnings," which the tool treats as failures.
    How it was handled: accepted by design. Better to reject a borderline file (the user is told the remediation didn't work, can try a different path) than to serve a file that might be damaged and contaminate the user's records. Privacy and integrity over feature completion.

Pre-launch items still open

  • External penetration test on the remediation surface (planned before public-announce; budget tracked in Phase 4 roadmap).
  • Full automated test coverage for the remediation pipeline (remediation.test.ts, remediation-privacy.test.ts, remediation-receipt.test.ts). Tracked in Phase 4.
  • File the upstream OpenDataLoader object-streams bug with reproducer PDFs (the qpdf preprocessing workaround is in place in the meantime).

v1.17.0 and earlier

Pre-formatted-audit era

Security reviews for releases prior to v1.18.0 were not yet captured in this format. Earlier releases focused on the synchronous audit pipeline (added in v1.0) and authentication flow (Personal Access Tokens added in v1.16, analyze-by-URL added in v1.17). Review history for those releases is available via the commit history on GitHub. Going forward — beginning with v1.18.0 — every release will have a corresponding entry in this section before tagging.

11. Right to inspect & verify

Authorized agency staff — including managers, records-retention officers, and accessibility auditors — can inspect the lifecycle of any specific remediation job by querying the SQLite database directly. Sample queries for common compliance questions:

-- All remediations in a date range (jobs carry no user identity)
SELECT id, input_filename, status, input_score, output_score,
       datetime(created_at/1000,   'unixepoch', 'localtime') AS started,
       datetime(completed_at/1000, 'unixepoch', 'localtime') AS finished
FROM remediation_jobs
WHERE created_at BETWEEN ? AND ?
ORDER BY created_at DESC;

-- Full lifecycle of a specific job
SELECT event, datetime(occurred_at/1000, 'unixepoch', 'localtime') AS at, details
FROM remediation_events
WHERE job_id = ?
ORDER BY occurred_at;

-- Sentinel: any job whose output was retained past the 30-minute TTL
SELECT j.id, j.input_filename,
       (e.max_at - j.completed_at) / 60000 AS extra_minutes_on_disk
FROM remediation_jobs j
JOIN (
  SELECT job_id, MAX(occurred_at) AS max_at
  FROM remediation_events
  WHERE event IN ('output_deleted', 'verified_absent')
  GROUP BY job_id
) e ON e.job_id = j.id
WHERE j.status IN ('expired', 'complete')
  AND (e.max_at - j.completed_at) > 30 * 60 * 1000;
-- This query should return ZERO ROWS for a properly-functioning system.

-- Sentinel: any deletion that wasn't verified absent
SELECT job_id, occurred_at
FROM remediation_events
WHERE event = 'output_deleted'
  AND NOT EXISTS (
    SELECT 1 FROM remediation_events e2
    WHERE e2.job_id = remediation_events.job_id
      AND e2.event = 'verified_absent'
      AND e2.occurred_at >= remediation_events.occurred_at
  );
-- This query should ALSO return ZERO ROWS.

A Phase 3 roadmap item adds a manager-facing verification endpoint that accepts a filename or a file's SHA-256 hash and reports whether the file was ever audited or remediated, with full timestamps. The underlying content_hash column has been populated on every audit and remediation since v1.18.0 in preparation for that feature. Until that endpoint ships, equivalent information is available via direct database query as shown above.

Whoever started a remediation can also see its complete receipt by returning to the result page with the job's download token (URL pattern: https://audit.icjia.app/remediate/<jobId>?t=<token> — the token is issued once, when the job is created). Without the token the server answers 404, deliberately: jobs carry no owner identity, so the token is the only key, and a wrong key must not even confirm the job exists. The receipt shows every lifecycle event with human-readable labels, including the verified-deletion event.

12. Standards & compliance alignment

The tool's design and this policy aim to align with the following standards and regulations. Alignment with a standard does not constitute certification — official conformance audits remain the responsibility of the user agency.

  • WCAG 2.1 Level AA (Web Content Accessibility Guidelines, W3C) — the version IITAA 2.1 and ADA Title II both require; the audit scores each uploaded document — PDF, Word, PowerPoint, or Excel — against a set of WCAG-aligned categories that map to these success criteria for non-web documents. The exact category set is tailored per format (for example, reading order and form accessibility are structurally not applicable to Word, and heading structure does not apply to a spreadsheet).
  • ADA Title II (U.S. federal law; compliance due April 26, 2027 for public entities serving 50,000 or more, and April 26, 2028 for smaller entities and special districts) — informs the tool's diagnostic and remediation framing.
  • Illinois IITAA (Information Technology Accessibility Act) — the tool's compliance disclaimers on the remediation result page link to the Illinois DOIT accessibility standards.
  • PDF/UA (ISO 14289) — every PDF audit runs veraPDF against PDF/UA-1 (ISO 14289-1), or PDF/UA-2 (ISO 14289-2) when the document declares it — plus, since v1.97.0, a second pass against veraPDF's machine-testable WCAG 2.2 profile —, and the remediation pipeline validates its output against PDF/UA-1. veraPDF's verdict is surfaced honestly on the result page; manual review is acknowledged as still required for full accessibility.
  • State of Illinois records-retention policy — the default 7-year retention period for the remediation_events audit trail matches typical state-agency records-retention schedules. Adjust via configuration if your agency's schedule differs.

13. Glossary of technical terms

Append-only audit log
A database table whose rows are added but never modified or deleted by application code. Rows are removed only by an explicit retention-policy purge after a configured age. Append-only design ensures the audit trail is tamper-evident from inside the running system.
ENOENT (Error: No such ENTity)
The error code returned by the operating system when a program asks for the status of a file that doesn't exist. The remediation worker uses an fs.stat() call expecting ENOENT after a delete — receiving any other response indicates the file is still present, which is treated as a compliance anomaly.
fs.stat()
A Node.js function that asks the operating system whether a file exists and, if so, returns its size, permissions, and timestamps. We use it specifically to confirm that a file has been deleted (we expect a "no such file" response).
OOXML
Office Open XML — the ZIP-of-XML file format used by Word (.docx), PowerPoint (.pptx), and Excel (.xlsx) files. The audit pipeline reads it by unzipping the container with JSZip and parsing its XML parts with fast-xml-parser, working only from the in-memory upload buffer — see § 5. No temporary file is written to disk.
PDF/UA-1
ISO 14289-1: the technical specification for "accessible PDF." Defines the structural requirements (tags, language declaration, metadata) a PDF must meet to be considered conformant. Validated by veraPDF. A successor, PDF/UA-2 (ISO 14289-2), exists for newer PDFs — the audit validates against it instead when a document declares it.
Remediation
The process of taking an existing PDF and adding accessibility structure to it after the fact. Distinguished from accessible authoring, which produces a tagged PDF directly from a source document.
SHA-256 hash
A cryptographic function that turns any input into a fixed-length (64-character) hexadecimal string. The hash is one-way: you can compute the hash from the input, but not the input from the hash. We use it for two purposes here: (1) as a content fingerprint to identify whether two files are the same without storing the files themselves; (2) as a token comparison mechanism that resists timing attacks.
Structure tree / tagged PDF
An optional second layer inside a PDF that describes the semantic role of each piece of content (heading, paragraph, figure, table cell). A PDF with this layer populated is called "tagged" and is readable by screen readers; one without it is "untagged" and is inaccessible. See the Technical Details dropdown on the audit page for a full primer.
UUIDv4
A version 4 universally unique identifier — a 36-character random string with 122 bits of entropy. We use UUIDv4s as job IDs so that no two remediation jobs ever share an identifier, and so that an attacker cannot guess a valid job ID by enumeration.

14. Change log for this policy

  • v1.17 · 2026-08-26 — Nothing new is collected, no new kind of data is stored, and no retention period changes. Description update for a processing variant: the web page now usually runs an audit through progress endpoints that report per-step status while you wait. The one difference § 2 now describes: the finished report (the same result JSON as always — never the file) waits in server process memory until the page collects it or for at most 10 minutes, then is discarded; delivery removes it immediately. The uploaded file's own lifetime is unchanged — discarded the moment analysis completes. Progress jobs carry no owner identity; like remediation, an unguessable token returned once is the only key, stored only as a hash.
  • v1.16 · 2026-08-26 — Nothing new is collected or stored, no retention period changes, and the file lifecycle is unchanged. Description update for a new processing step: each PDF audit's veraPDF check now runs twice — once against the PDF/UA standard (as before) and once against veraPDF's machine-testable WCAG 2.2 validation profile (a rule file that ships inside this application's own source code). Both passes read the same single short-lived temp copy already described in § 2, deleted in the same request; the second result appears on PDF reports as its own informational panel and never affects the score. § 5 and § 12 describe the added pass.
  • v1.15 · 2026-08-26 — Presentation and wording only; nothing new is collected or stored, no retention period changes, and no data practice changes. The header now carries a "Last updated" date. § 4's AI exclusion list names providers and model families without version numbers (versions change too quickly for a policy to chase; the exclusion covers all of them, and the section now says so). § 7's retention table is regrouped into three visually distinct groups — the document itself, the application's own records, and adjacent systems — with the same rows, figures, and wording. § 10 now shows the most recent security review expanded and collapses the earlier history into a single expandable list (every entry remains on the page). § 13's PDF/UA entry notes the PDF/UA-2 successor standard the audit validates when a document declares it.
  • v1.14 · 2026-08-25 — One clarification of the v1.13 figures; nothing new is collected or stored and no retention period changes. The re-audit summary on the status page now counts public audits only: audits made through the internal trusted-tool tier (the automated fleet inventory, § 8a) are excluded, because the fleet re-scans unchanged documents on a schedule and its runs were drowning out the picture of documents people actually fix. Records whose tier is unknown — written before the tier was recorded — are excluded as well, so the summary climbs from when tier recording began rather than guessing. The distinct-document counts are unchanged and still include every tier.
  • v1.13 · 2026-08-25 — Nothing new is collected or stored; no retention period changes. The public status page publishes two new families of aggregate figures computed from the usage-metadata records the policy already describes (§ 6, § 8): the number of distinct documents audited (a count of distinct content fingerprints — the fingerprints themselves are never shown), and a re-audit summary for the last 30 days — how many documents were checked more than once, how many of those improved, and the median score change. The summary is computed by grouping records by file name inside the database; no file name, fingerprint, or individual score is published. When fewer than five documents qualify, the rates and the median are withheld — over so few documents they would describe a single visitor's documents rather than a usage pattern. The counts themselves remain published, like every other aggregate count on the status page.
  • v1.12 · 2026-08-22 — Two additions, no change to any retention period. Failed audits are now recorded in the usage-metadata table: an audit the tool attempted and could not complete leaves a row with the same fields as a successful one, no score or grade, no content hash, and a one-word reason code (unreadable, timeout, fetch-failed, navigation-failed, internal) — never the error text. § 8a shows the new audit_log.reason column. Daily activity files: each night the server writes the previous day's usage-log rows to a CSV file so auditors and managers can review a day without querying the database. Derived from the table, same fields, deleted on the same 365-day schedule, kept on the server's disk only, not part of the nightly backup, never served by the site (§ 7, § 8). Application error log: the service's own error output — message and stack trace, for diagnosing faults — is also kept as one file per day in the same place, for 30 days, not backed up, never served (§ 7, § 8).
  • v1.11 · 2026-08-21 — Adds one column to the usage-metadata table (audit_log.privileged) recording which request tier an audit came through: the internal trusted-tool tier used by the automated fleet, or the public tier. It lets the status page report privileged-tier volume so the shared service token's use can be watched. It is a property of that token, not an identity — nothing about who made the request — and no new table, no document content, and no change to any retention period (§ 8a shows the updated schema).
  • v1.10 · 2026-08-20 — Discloses one more kind of document text that a saved or shared PDF report can quote. When a document tags a box of text as an image (Word does this with text boxes, sidebars, and chart titles), the report now keeps an excerpt of up to 80 characters of that text so it can point an author at the right box — and tell them to change its tag rather than describe it, because a description would hide the text from screen readers. The same section now also names heading text and the page number beside each quoted link, both of which reports already carried. Nothing about who uploaded a file, nothing about visitors, and no new table, column, or retention period: the excerpts live inside the same stored report text, under the same 365-day expiry (§ 7, § 8, § 8a).
  • v1.9 · 2026-08-18 — Extends the v1.8 address generalization to a third per-file page type and discloses a short gap. The report page for web-page audits (/page-report/…) shipped on 2026-08-18 without being covered by the generalization, so for part of that day each visit to such a report reached the analytics server with the report's individual address — contrary to what v1.8 states. Fixed the same day: those pages now count only as their base route (/page-report), an automated check fails the build for any future per-file page type left out of the generalization, and the few individual addresses already recorded remain only in the analytics tool's historical data on ICJIA's own server. What is recorded per page view is otherwise unchanged (§ 7). Nothing about what the audit tool itself stores changed.
  • v1.8 · 2026-08-15 — Narrows what the analytics beacon reports. Page addresses are now generalized before they leave the visitor's browser: a repair-job page (/remediate/…) or a shared-report page (/report/…) counts only as its base route, so per-file addresses never reach the analytics server, and query strings — including the one-time repair download token, which the previous stock analytics script included in its payload URL — are never sent at all. What is recorded per page view is otherwise unchanged (§ 7); § 8a and § 9 state the generalization. Nothing about what the audit tool itself stores changed.
  • v1.7 · 2026-08-14 — Documents the addition of privacy-friendly page-view analytics. The site's web pages now count visits with Plausible, an open-source, cookie-free analytics tool, self-hosted by ICJIA on its own server (plausible.icjia.cloud, a DigitalOcean droplet ICJIA manages itself) — no commercial analytics provider, ad network, or tracker receives anything. Recorded per page view: the page URL, referrer, browser and operating-system family, device type, and country/region — never a cookie, never a stored IP address or user-agent, and never anything about an uploaded document. The visitor's browser reports directly to the analytics server; the audit application never receives or forwards that data, and audits themselves do not pass through analytics at all. Page views within a single day are linked by a salted hash that rotates every 24 hours, so activity can never be connected across days or across sites. Adds the analytics store to § 7, reworks the analytics bullets in § 8, qualifies the analytics row in § 8a, and describes the safeguard in § 9. Nothing about what the audit tool itself stores changed.
  • v1.6 · 2026-08-09 — Documents the identifier-removal release (tool v1.68.0): the sign-in system is removed entirely — no accounts, no login codes, no sessions, no API tokens tied to a person — and the service stops storing identifiers altogether. The email, IP-address, and browser user-agent columns were dropped from the database schema itself (migration 11), destroying the previously stored values, not merely ending new writes; the login-code and token tables were deleted; and the one email the service could ever send (the login code) is gone with the mail-sending code. What remains per audit is metadata about the event — file name, score, grade, timestamp, content hash — and it says nothing about who did the checking. The caller's IP address is still used transiently in server memory to rate-limit requests and cap remediation jobs, and is written nowhere. Old nightly snapshots retain the old shape until the keep-5 rotation ages them out (≈5 days). The hosting layer's standard nginx access logs are unchanged and remain outside these application records (§ 8a; their retention is now listed in § 7). Updates §§ 2, 3, 4, 5, 6, 7, 7a, 8, 8a, 9, 11.
  • v1.5 · 2026-08-09 — Wording only; nothing stored, used, or retained changed. States explicitly — for federal and state auditors — that every retained row is metadata about an audit event (date, file name, score, grade): a record about the document, never a copy of any part of it (§ 7a; the same wording now appears on the status page's backup card). Adds the reconciliation auditors need in one place: this policy never claims the records are free of personal detail — the personal fields are named (sign-in email for signed-in users; the connection log's IP address and browser user-agent, purged after 365 days; the file name as uploaded, which can itself name a person), and what the records never hold is the document or anything read from inside it (§ 7a, § 8, § 8a).
  • v1.4 · 2026-08-05 — Documents that shared-report rows are now physically deleted by the cleanup sweep roughly 30 days after their link expires (tool v1.51.0) — a total stored lifetime of about 395 days (§ 7, § 8a). Previously the link stopped working at 365 days but the stored row was retained indefinitely, which § 7 disclosed. Also documents that the retention sweep now runs regardless of the optional remediation feature's on/off switch (§ 7); previously, disabling that feature would have silently paused the periodic purges.
  • v1.3 · 2026-08-05 — Adds § 8a, a dated storage-verification annex proving § 8 against the source code, and qualifies § 8 where verification showed the wording overclaimed: a saved or shared report quotes short strings from the document (metadata fields, image alt-text values, link text and destinations, bookmark titles, form-field names) inside its findings; page and paragraph text, images, form-field values, and file bytes remain never-stored, and a plain unshared audit stores none of the quoted strings either. Documents the nightly database backups added in tool v1.49.0 (§ 7, § 8): on-server snapshots kept outside the application directory, integrity-checked, with only the 5 newest retained — so a purged row persists in snapshots for roughly 5 further days.
  • v1.2 · 2026-08-05 — Documents that refused uploads are recorded in the usage log (tool v1.46.0 and newer): a refusal stores the file name the upload was offered under (sanitized) and a timestamp — never any file content, and deliberately no content fingerprint (§ 7, § 8, § 10). Corrects the usage-log retention entry in § 7: rows are purged after 365 days by the periodic cleanup sweep (configurable), not retained indefinitely — the 365-day purge has been in place since tool v1.20.1. Corrects § 8: the usage log has always recorded the caller's IP address and browser user-agent alongside each row; the previous wording implied otherwise. Documents that veraPDF also runs during every PDF audit (tool v1.37.0 and newer), not only in remediation (§ 2, § 5). Corrects the OTP code lifetime in § 7 (15 minutes, not 10). Records the 2026-08-05 full red/blue security audit and the stricter file-name cleaning it introduced (§ 10). Corrects the on-page numbering of §§ 12–15, which had drifted one behind the table of contents.
  • v1.1 · 2026-07-03 — Documents that the audit pipeline (§ 2) covers Microsoft Word (.docx), PowerPoint (.pptx), and Excel (.xlsx) files in addition to PDF. All four formats are handled identically by the audit pipeline: processed in memory only, never written to disk, and discarded after the response. Adds the OOXML container-parsing tools (JSZip, fast-xml-parser) to the toolchain table (§ 5) and an OOXML glossary entry (§ 13). The remediation pipeline (§ 3) is unchanged and remains PDF-only.
  • v1.0 · 2026-05-18 — Initial publication. Covers tool versions v1.18.0 and newer. Documents the audit pipeline and the optional auto-remediation pipeline introduced in v1.18.0.

This policy is version-controlled with the source code. Any change to the data-handling behavior of the tool is reflected here, with a corresponding version bump and a dated entry above. The complete change history is available via git log apps/web/app/pages/data-retention.vue on the project's GitHub repository.

Related documents & source code

Every claim in this policy is verifiable against publicly-available source code and documentation. Audit it yourself:

15. Contact & questions

For questions about this policy, requests for technical details beyond what's documented here, requests to inspect a specific job's audit trail, or any concern about how this tool handles data:

Innovation and Digital Services (IDS)

Illinois Criminal Justice Information Authority

cja.info@illinois.gov

Source code and issue tracker: github.com/ICJIA/file-accessibility-audit.