Skip to content

v0.1.0

Latest

Choose a tag to compare

@jshields-ca jshields-ca released this 21 Sep 03:53
· 3 commits to main since this release
fc6955d

Added

  • Real critical/moderate/minor severity breakdown in /api/analyze and /api/remediate responses, plus per-issue fixed/remaining detail, replacing a hardcoded proportional guess in the old UI.
  • Full frontend rebuild: React + Vite + Tailwind CSS + Framer Motion, replacing the vanilla HTML/CSS/JS app, with accessibility (Vitest + Testing Library + jest-axe) enforced in CI.
  • CodeQL scanning, npm audit / pip-audit CI gates, and Dependabot for npm, pip, GitHub Actions, and Docker.
  • CONTRIBUTING.md, CODE_OF_CONDUCT.md, SECURITY.md, GitHub issue/PR templates, and CODEOWNERS.
  • PLAN.md, a phased roadmap tracking project direction for contributors.
  • Multi-stage Docker build and a release pipeline publishing versioned, multi-arch images to GitHub Container Registry (see RELEASING.md).

Changed

  • BREAKING: Renamed the project from "PDF Accessibility Tool" (pdf-accessibility) to Docubil. Updated package name, Docker image/container names, and all documentation references.
  • BREAKING: Relicensed from MIT to AGPL-3.0-only.

Fixed

  • Resolved all npm audit findings in production dependencies (multer 1.x→2.x, uuid 9.x→11.1.1) and all pip-audit findings (pdfplumber upgrade, which pulled in a patched pdfminer.six).

Security

  • Removed 8 previously committed remediated PDF files from git tracking under output/.
  • Pruned requirements.txt from 12 packages to the 2 actually imported by the analysis code, reducing image size and attack surface.
📋 Full commit details

chore(deps): migrate root ESLint to flat config, bump to 10.x

ESLint 9+ removed the eslintrc-compat loader in v10, so the legacy
.eslintrc.js would simply stop being read -- there's no
ESLINT_USE_FLAT_CONFIG=false escape hatch left. Migrates the root config
to eslint.config.js (mirroring client/'s flat config), drops the now-
invalid --ext .js from the lint scripts, and bumps eslint to ^10.10.0 /
@eslint/js to ^10.0.1.

eslint:recommended's new preserve-caught-error rule caught 3 real spots
re-throwing an error without attaching { cause }, losing the original
stack trace on rethrow -- fixed by attaching it. Also dropped two unused
catch (_) bindings in favor of bindingless catch, which is what they
actually meant.

Verified against real Node 20 (matching CI): npm ci, lint, and the full
jest suite (37/37) all pass.

Supersedes #10 (a version-only Dependabot bump that couldn't work
without this migration).

Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

Co-authored-by: Claude noreply@anthropic.com

chore(deps-dev): bump lint-staged from 15.5.2 to 17.5.1 (#16)

Bumps lint-staged from 15.5.2 to 17.5.1.


updated-dependencies:

  • dependency-name: lint-staged
    dependency-version: 17.5.1
    dependency-type: direct:development
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump express-rate-limit from 7.5.1 to 8.7.0 (#15)

Bumps express-rate-limit from 7.5.1 to 8.7.0.


updated-dependencies:

  • dependency-name: express-rate-limit
    dependency-version: 8.7.0
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps-dev): bump husky from 8.0.3 to 9.1.7 (#11)

Bumps husky from 8.0.3 to 9.1.7.


updated-dependencies:

  • dependency-name: husky
    dependency-version: 9.1.7
    dependency-type: direct:development
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump pino-http from 10.5.0 to 11.0.0 (#12)

Bumps pino-http from 10.5.0 to 11.0.0.


updated-dependencies:

  • dependency-name: pino-http
    dependency-version: 11.0.0
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps-dev): bump jest from 29.7.0 to 30.5.1 (#18)

Bumps jest from 29.7.0 to 30.5.1.


updated-dependencies:

  • dependency-name: jest
    dependency-version: 30.5.1
    dependency-type: direct:development
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump helmet from 7.2.0 to 8.3.0 (#14)

Bumps helmet from 7.2.0 to 8.3.0.


updated-dependencies:

  • dependency-name: helmet
    dependency-version: 8.3.0
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump dotenv from 16.6.1 to 18.0.0 (#17)

Bumps dotenv from 16.6.1 to 18.0.0.


updated-dependencies:

  • dependency-name: dotenv
    dependency-version: 17.4.2
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps-dev): bump vitest from 4.1.11 to 5.0.1 in /client (#21)

Bumps vitest from 4.1.11 to 5.0.1.


updated-dependencies:

  • dependency-name: vitest
    dependency-version: 5.0.1
    dependency-type: direct:development
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps-dev): bump @testing-library/jest-dom in /client (#24)

Bumps @testing-library/jest-dom from 6.9.1 to 7.0.1.


updated-dependencies:

  • dependency-name: "@testing-library/jest-dom"
    dependency-version: 7.0.1
    dependency-type: direct:development
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump pikepdf from 8.7.1 to 10.13.0.post1 (#7)

Bumps pikepdf from 8.7.1 to 10.13.0.post1.


updated-dependencies:

  • dependency-name: pikepdf
    dependency-version: 10.13.0.post1
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump the npm-minor-patch group across 1 directory with 5 updates (#8)

Bumps the npm-minor-patch group with 5 updates in the / directory:

Package From To
compression 1.8.1 1.8.2
cors 2.8.5 2.8.6
swagger-jsdoc 6.2.8 6.3.0
cz-git 1.12.0 1.14.0
prettier 3.6.2 3.9.8

Updates compression from 1.8.1 to 1.8.2

Updates cors from 2.8.5 to 2.8.6

Updates swagger-jsdoc from 6.2.8 to 6.3.0

Updates cz-git from 1.12.0 to 1.14.0

Updates prettier from 3.6.2 to 3.9.8


updated-dependencies:

  • dependency-name: compression
    dependency-version: 1.8.2
    dependency-type: direct:production
    update-type: version-update:semver-patch
    dependency-group: npm-minor-patch
  • dependency-name: cors
    dependency-version: 2.8.6
    dependency-type: direct:production
    update-type: version-update:semver-patch
    dependency-group: npm-minor-patch
  • dependency-name: cz-git
    dependency-version: 1.14.0
    dependency-type: direct:development
    update-type: version-update:semver-minor
    dependency-group: npm-minor-patch
  • dependency-name: prettier
    dependency-version: 3.9.7
    dependency-type: direct:development
    update-type: version-update:semver-minor
    dependency-group: npm-minor-patch
  • dependency-name: swagger-jsdoc
    dependency-version: 6.3.0
    dependency-type: direct:production
    update-type: version-update:semver-minor
    dependency-group: npm-minor-patch
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump actions/setup-python from 5 to 7 (#4)

Bumps actions/setup-python from 5 to 7.


updated-dependencies:

  • dependency-name: actions/setup-python
    dependency-version: '7'
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump actions/setup-node from 4 to 7 (#6)

Bumps actions/setup-node from 4 to 7.


updated-dependencies:

  • dependency-name: actions/setup-node
    dependency-version: '7'
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

chore(deps): bump actions/checkout from 4 to 7 (#13)

Bumps actions/checkout from 4 to 7.


updated-dependencies:

  • dependency-name: actions/checkout
    dependency-version: '7'
    dependency-type: direct:production
    update-type: version-update:semver-major
    ...

Signed-off-by: dependabot[bot] support@github.com
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

fix: skip lifecycle scripts in production npm install

  • chore(release): 0.1.0

Cuts the first versioned release under the new Docubil name. Per
RELEASING.md's documented first-release procedure (npm run release
can't be used as-is here -- see 5efcb2b/0390381 for why): renamed the
Unreleased CHANGELOG section to 0.1.0, bumped package.json/
package-lock.json via npm version 0.1.0 --no-git-tag-version.

Also fixed two hardcoded '0.0.1' version literals (the /health endpoint
and the OpenAPI spec) that npm version's bump doesn't touch -- both now
read from package.json at runtime so they can't go stale on future
releases.

Once this merges, tagging the resulting commit v0.1.0 and pushing the
tag triggers .github/workflows/release.yml: multi-arch Docker image to
GHCR + a GitHub Release with categorized notes.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix: skip lifecycle scripts in production npm install

The release build (v0.1.0) failed at npm ci --omit=dev: npm runs the
prepare script (husky install) on every install regardless of
--omit=dev, but husky is a devDependency, so it's missing and the
runtime stage exits 127 before the image can build.

--ignore-scripts is also just correct for a production image -- there's
no reason to run devDependency-only git-hook setup there. Verified
against Node 20 (matching CI) that npm ci --omit=dev --ignore-scripts
exits 0, installs prod deps only, and runs no scripts.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt


Co-authored-by: Claude noreply@anthropic.com

chore(release): 0.1.0 (#26)

Cuts the first versioned release under the new Docubil name. Per
RELEASING.md's documented first-release procedure (npm run release
can't be used as-is here -- see 5efcb2b/0390381 for why): renamed the
Unreleased CHANGELOG section to 0.1.0, bumped package.json/
package-lock.json via npm version 0.1.0 --no-git-tag-version.

Also fixed two hardcoded '0.0.1' version literals (the /health endpoint
and the OpenAPI spec) that npm version's bump doesn't touch -- both now
read from package.json at runtime so they can't go stale on future
releases.

Once this merges, tagging the resulting commit v0.1.0 and pushing the
tag triggers .github/workflows/release.yml: multi-arch Docker image to
GHCR + a GitHub Release with categorized notes.

Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

Co-authored-by: Claude noreply@anthropic.com

feat(release): publish versioned multi-arch images to GHCR on tag push (#25)

  • feat(release): publish versioned multi-arch images to GHCR on tag push

Adds .github/workflows/release.yml: on a vX.Y.Z tag push, builds a
multi-arch (amd64/arm64) Docker image via buildx/QEMU and pushes it to
ghcr.io/jshields-ca/docubil tagged both with the version and latest,
then creates a GitHub Release.

Release notes (scripts/build-release-notes.mjs) put the categorized
CHANGELOG.md section for that version up top -- short and readable --
with the full commit history for the range in a collapsed


accordion for anyone who wants the detail, per the release-notes format
in PLAN.md Phase 4.

Adds docker-compose.release.yml, a standalone compose file referencing
the published GHCR image, so Docubil can be pulled and run with
docker compose pull && docker compose up without cloning the repo --
the Phase 4 approval metric.

.versionrc.json customizes standard-version's changelog section headers
to match this project's existing Keep a Changelog style (Added/Fixed/
Security/etc. instead of the Angular preset's Features/Bug Fixes).

Found while testing this end-to-end: because PRs #3 and #19 were
squash-merged with plain (non-conventional-commit) titles, their
commits are invisible to standard-version's changelog generator, and
running npm run release as-is would have silently deleted the
hand-written Unreleased section in favor of an auto-generated one
missing nearly all of Phases 1-3. Fixed by: rewriting CHANGELOG.md's
Unreleased section to comprehensively and accurately cover that work by
hand, documenting a one-time manual first-release procedure in
RELEASING.md, and adding a PR template checklist item + RELEASING.md
callout that PR titles must be conventional-commits-formatted going
forward (a squash merge's commit subject is the PR title, not the
original per-commit messages).

Verified: release-notes generation tested against disposable local
tags (not pushed) with both matching and non-matching CHANGELOG
versions; a real (non-dry-run) standard-version invocation tested in
a throwaway copy of the repo to confirm the above finding and the fix.
No tags were created or pushed to the real repository as part of this
work -- cutting the actual first release is left to the maintainer per
RELEASING.md.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix(security): fully escape regex metacharacters in release-notes script

CodeQL correctly flagged scripts/build-release-notes.mjs:26 (regular
expression injection + incomplete string escaping): the CHANGELOG
heading regex was built from the tag CLI argument but only escaped
literal dots, leaving other regex metacharacters (*, +, (, ), [, ], ,
etc.) able to inject regex syntax or produce unintended matches.

Replaced with a proper escapeRegExp() helper covering the full
metacharacter set. The script is only ever invoked by our own release
workflow with a tag GitHub Actions has already validated against
v*.., so exploitability was low, but the fix is trivial and correct
regex-escaping shouldn't depend on that.

Verified: script still correctly extracts CHANGELOG sections, and no
longer throws on a version string containing regex metacharacters.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt


Co-authored-by: Claude noreply@anthropic.com

Phase 3: Rebuild frontend in React + Vite + Tailwind (#19)

  • feat(api): return real severity breakdown and remediation detail

/api/analyze and /api/remediate previously returned only a flat issue
count. The old frontend faked a critical/moderate/minor split with a
hardcoded 30/50/20 proportional guess -- now the API computes and
returns the actual counts (issuesBySeverity, remainingBySeverity), plus
per-issue fixed/remaining detail (fixedIssuesDetail,
remainingIssuesDetail) so a UI can show what was actually done without
an extra round trip to the HTML report.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • feat(frontend): rebuild UI in React + Vite + Tailwind CSS

Replaces the vanilla HTML/CSS/JS frontend (public/index.html, app.js,
styles.css) with a component-based React app in client/, built with
Vite and styled with Tailwind CSS v4. Framer Motion provides purposeful
transitions between steps (upload -> progress -> results ->
remediation) and micro-interactions, with prefers-reduced-motion
respected.

Accessibility, since that's the whole point of this product:

  • Semantic landmarks, skip link, and a live-region progress announcer
  • Native for the upload dropzone instead of a
    hand-rolled role="button" wrapper -- the earlier approach put a
    focusable inside a focusable button-role div, which axe
    correctly flags as nested-interactive
  • Native for About/Help/Privacy (focus trap, Escape-to-close,
    backdrop dismissal for free) with aria-labelledby wired to its
    heading
  • Real severity counts (via the new /api/analyze and /api/remediate
    response fields) instead of the old UI's fabricated proportional
    split

Testing: Vitest + Testing Library, with jest-axe asserting zero
accessibility violations on every rendered component (App, UploadCard,
ResultsPanel, RemediationPanel) -- this is the CI-enforced approval
metric from PLAN.md Phase 3, not just a nice-to-have.

Verified manually end-to-end with Playwright against the built app
(upload -> analyze -> remediate -> download, plus mobile viewport and
dark-mode rendering) -- see PR description for screenshots.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • chore: wire the frontend build into package.json, Docker, and CI
  • Root npm run build installs client deps and runs the Vite build.
  • public/ is now gitignored (generated output, not source) -- ESLint
    ignores client/ too, since it lints itself via client/eslint.config.js.
  • Dockerfile is now multi-stage: a frontend-builder stage runs the Vite
    build, and the runtime stage copies its output into ./public instead
    of relying on a checked-in public/ directory.
  • CI gets a new frontend job: lint, test (component + jest-axe
    accessibility checks), build, and npm audit for client/.
  • Dependabot now also watches client/'s npm dependencies.
  • README documents the new two-terminal dev workflow (Express + Vite
    dev server with API proxying) and updated architecture tree.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix(ci): pin vitest to a Node-20-compatible version

CI's frontend job pins Node 20 (matching the backend job and the
node:20-slim Docker image), but vitest's latest major (5.x) transitively
requires Node >=22.19 via undici/whatwg-url, so npm test crashed in CI
with "webidl.util.markAsUncloneable is not a function" even though it
passed locally on a newer local Node.

vitest 3.x supports Node 20 but only vite <=7 (we're on vite 8), which
broke JSX transform in tests ("React is not defined") since vitest
couldn't correctly hook into vite 8's plugin pipeline. vitest ^4.1.0
supports both Node 20 and vite 8 -- pinned to that instead of bumping
Node everywhere just to chase the newest vitest.

Verified: npm ci (as CI runs it), lint, test, build, and npm audit all
pass locally with this pin.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix(ci): pin jsdom and jest-dom to Node-20-compatible versions

Same root cause as the vitest pin (76c4585), a second time: jsdom@30
added an undici@^8 dependency that requires Node >=22.19, and
@testing-library/jest-dom@7 requires Node >=22 outright. Both installed
unpinned, so npm grabbed the latest majors, which happened to work on
this sandbox's Node 22 but broke on CI's pinned Node 20.

Pinned jsdom to ^27.0.0 (no undici dependency, engines >=20) and
jest-dom to ^6.6.3 (engines >=14).

This time verified against the actual Node 20.20.2 binary (/opt/node20)
rather than the sandbox's Node 22 -- lint, test, build, npm audit, and
npm ci (exactly as CI invokes it) all pass. Local-Node-22-vs-CI-Node-20
was the gap that let two rounds of this slip through; re-checked every
remaining client/ dependency's engines field against Node 20 to close
that gap for good.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt


Co-authored-by: Claude noreply@anthropic.com

Docubil re-kickoff: audit, rebrand, AGPL-3.0 relicense, CI/security hardening (#3)

  • docs: add PLAN.md with phased roadmap from Phase 0 audit

Covers Phase 0 audit findings (license inconsistency, incomplete rebrand,
leaked PDF files in git history, dependency bloat/vulnerabilities, missing
governance docs, CI gaps) and defines Phases 1-5 with goals and approval
metrics for each.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • chore: rebrand to Docubil and relicense to AGPL-3.0

Reconciles the repo rename (pdf-accessibility -> Docubil, matching the
GitHub repo which was already renamed) across package.json, Docker
image/container names, README badges/links, generated PDF metadata,
OpenAPI spec, and UI copy.

Also reconciles the license: LICENSE already contained full AGPL-3.0
text but package.json and README still declared MIT. Both now correctly
declare AGPL-3.0-only.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • docs: add CONTRIBUTING, CODE_OF_CONDUCT, SECURITY, and GitHub templates

Adds baseline open-source governance docs: CONTRIBUTING.md (including
what the AGPL-3.0 license means for contributors), a Contributor
Covenant CODE_OF_CONDUCT.md, a SECURITY.md with a private disclosure
process and data-handling policy for uploaded PDFs, plus GitHub issue
templates, a PR template, and CODEOWNERS.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix(deps): resolve npm audit findings and prune unused Python packages

Upgrades multer 1.x -> 2.x (1.x has known unpatched CVEs) and uuid 9.x
-> 11.1.1 (patches a buffer-bounds-check advisory; verified require()
compatibility, since uuid 12+ dropped CommonJS support entirely).
npm audit now reports 0 vulnerabilities in production dependencies.

Prunes requirements.txt from 12 packages to the 2 actually imported by
src/python/*.py (pikepdf, pdfplumber) -- the other 10 (flask, reportlab,
matplotlib, etc.) were unused dead weight inflating the Docker image and
attack surface.

Verified: npm run lint and npm test (37/37) pass against both changes.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • ci: add CodeQL scanning, dependency-audit gate, and Dependabot

Adds a security job to ci.yml running npm audit (--audit-level=high)
and pip-audit on every PR, a separate CodeQL workflow scanning both
JS/TS and Python on push/PR/weekly schedule, and dependabot.yml for
npm, pip, GitHub Actions, and Docker base image updates.

Also documents the GitHub repo settings (branch protection, secret
scanning, required status checks) that need to be applied manually in
.github/REPOSITORY_SETTINGS.md, since those aren't configurable from
files in the repo.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt

  • fix(security): upgrade pdfplumber to patch pdfminer.six vulnerabilities

CI's new pip-audit gate (from the prior commit) correctly caught 4 known
vulnerabilities in pdfminer-six 20221105, a transitive dependency pulled
in by pdfplumber==0.10.3. pdfplumber 0.11.10 pins pdfminer.six==20260107,
which resolves all of them (pip-audit: 0 vulnerabilities).

Verified in an isolated venv: analyze_pdf.py still correctly detects
headings, font sizes, and color-contrast issues against real PDF text
content on the upgraded version -- pdfplumber's core page.chars API
used here is unchanged across 0.10.x -> 0.11.x.

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_013x5pTFRUCur8PvBXHhKoXt


Co-authored-by: Claude noreply@anthropic.com

Add LICENSE file

Signed-off-by: Jeremy 'scootR' Shields jeremy@scootr.ca

Merge pull request #2 from jshields-ca/claude/frosty-matsumoto

feat: implement real PDF analysis, security hardening, tests, and pro…

fix: resolve ESLint errors blocking CI

  • Add curly braces to 8 braceless single-statement if conditions in
    ReportGenerator.js (curly rule)
  • Remove unused moderateIssues variable in generateRecommendations
    (no-unused-vars rule)
  • Remove unused path import in tests/server.test.js (no-unused-vars rule)

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

feat: implement real PDF analysis, security hardening, tests, and production infrastructure

Core analysis & remediation:

  • Replace all placeholder AccessibilityAnalyzer checks with real WCAG 2.1 AA/AAA analysis
    using pikepdf (tagging, language, bookmarks, form tooltips) and pdfplumber (headings,
    contrast ratio) via a gracefully-degrading Python bridge
  • Replace placeholder RemediationService with pdf-lib + pikepdf two-stage pipeline;
    honestly marks reading-order/heading issues as requiring manual review
  • Rewrite PDFProcessor.extractInfo() to prefer pdf-lib getters over pdf-parse for
    metadata stability; add magic-bytes (%PDF-) validation in validatePDF()
  • Add PythonBridge.js: spawns Python scripts, returns null on failure (never throws)
  • Add src/python/analyze_pdf.py and src/python/remediate_pdf.py

Security hardening (server.js):

  • Enforce ALLOWED_ORIGINS env var for CORS instead of allow-all
  • Remove unsafe-inline from CSP scriptSrc
  • Wire rate limit, file size, and cleanup intervals to env vars
  • Validate :jobId params as UUID before filesystem access (path-traversal prevention)
  • Hide error details in production (NODE_ENV=production)
  • Post-upload magic-bytes check via validatePDF()

Infrastructure:

  • Replace better-sqlite3 (fails on Node 24 Windows) with zero-dep file-backed JSON
    job store (src/db.js) using in-memory Map + sync fs writes
  • Add pino structured logging (src/logger.js) with pino-http request middleware
  • Add full OpenAPI 3.0 spec (src/openapi.js) served via Swagger UI at /api/docs
  • Add JSON + CSV report export endpoints (/api/report/:jobId/json, /api/report/:jobId/csv)
  • Add Dockerfile (node:20-slim + Python 3, non-root user) and docker-compose.yml
  • Fix CI workflow to run real npm run lint and npm test with Python setup step
  • Add .eslintrc.js, jest.config.js, .claude/launch.json

Tests (37 passing):

  • tests/server.test.js: 29 integration tests (all API routes, magic-bytes rejection,
    UUID validation, JSON/CSV export, Swagger docs, health endpoint)
  • tests/services/PDFProcessor.test.js: 8 unit tests
  • tests/services/AccessibilityAnalyzer.test.js: 8 unit tests
  • tests/helpers/createTestPDF.js: fixture builder using useObjectStreams:false for
    pdf-parse compatibility

Miscellaneous:

  • Add output/ and data/ to .gitignore
  • Update .env.example with DB_PATH, PYTHON_PATH, PYTHON_TIMEOUT_MS

Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com

chore: ignore uploads directory and remove tracked uploads from git

chore: ignore reports directory and remove tracked reports from git

chore: add MIT license, CI workflow, update .gitignore, and fix README badges

chore: add MIT license, CI workflow, and fix README badges for jshields-ca/pdf-accessibility

docs(readme): fix badge URLs to use jshields-ca/pdf-accessibility

chore(release): initial 0.0.1 public release, setup best practices, cleaned repo, and removed node_modules from tracking

chore(release): 1.0.1

Initialize PDF accessibility tool with README, config, and installation scripts (#1)

Co-authored-by: Cursor Agent cursoragent@cursor.com

Initial commit