No AI is used.
Your file is never sent to ChatGPT, GPT-4, GPT-4o, Claude, Gemini, Copilot, or any other artificial-intelligence service. 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.
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. Schemas:
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 email TEXT, -- null when anonymous input_filename TEXT NOT NULL, content_hash TEXT, -- SHA-256 of input bytes page_count INTEGER, status TEXT NOT NULL, -- 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 on disk, only when complete download_token_hash TEXT, -- SHA-256 of raw token failure_reason TEXT, verapdf_available INTEGER, verapdf_passed INTEGER, verapdf_summary_json TEXT, input_audit_json TEXT, -- full pre-flight ScoringResult output_audit_json TEXT, -- full post-remediation ScoringResult 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_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.
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.
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.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.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.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.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.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.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.