Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions docs/DEPS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

Living reference of every third-party dependency in PortOS, why it's kept, and what the current verdict is. Updated by `/do:depfree` runs.

**Last audited:** 2026-08-04 (scoped audit of the `keyv`/`cacheable` supply-chain compromise); prior follow-up 2026-07-14 (issue #2547), prior full audit 2026-04-28 (default mode), tables corrected 2026-07-01 during a docs audit.
**Verdict:** All dependencies justified. The 2026-08-04 audit replaced the entire `eslint` stack with `@biomejs/biome`, dropping 110 net client packages including the `file-entry-cache → flat-cache → keyv` chain named in the August 2026 Shai-Hulud npm compromise (PortOS held safe versions throughout — see the detailed finding below). The same pass closed a latent hole where `ignore-scripts=true` was only active for repo-root installs, not for any workspace install or CI. Since the last full audit: `sax` was removed (replaced with an owned parser, issue #1824), `portos-ai-toolkit` was vendored in-tree (`server/lib/aiToolkit/`), and monolithic `googleapis` was replaced with scoped `@googleapis/*` packages. The 2026-07-14 follow-up bumped `kokoro-js` to its latest patch `1.2.1` (still on maintenance watch — no publish since 2025-05) and aligned the dual `pm2` pins (root + server both `7.0.4`).
**Last audited:** 2026-09-02 (scoped: `pdf-lib` → `@cantoo/pdf-lib`, issue #5672); prior 2026-08-04 (scoped audit of the `keyv`/`cacheable` supply-chain compromise); prior follow-up 2026-07-14 (issue #2547), prior full audit 2026-04-28 (default mode), tables corrected 2026-07-01 during a docs audit.
**Verdict:** All dependencies justified. The 2026-08-04 audit replaced the entire `eslint` stack with `@biomejs/biome`, dropping 110 net client packages including the `file-entry-cache → flat-cache → keyv` chain named in the August 2026 Shai-Hulud npm compromise (PortOS held safe versions throughout — see the detailed finding below). The same pass closed a latent hole where `ignore-scripts=true` was only active for repo-root installs, not for any workspace install or CI. Since the last full audit: `sax` was removed (replaced with an owned parser, issue #1824), `portos-ai-toolkit` was vendored in-tree (`server/lib/aiToolkit/`), and monolithic `googleapis` was replaced with scoped `@googleapis/*` packages. The 2026-07-14 follow-up bumped `kokoro-js` to its latest patch `1.2.1` (still on maintenance watch — no publish since 2025-05) and aligned the dual `pm2` pins (root + server both `7.0.4`). The 2026-09-02 follow-up replaced abandoned `pdf-lib` (no publish since 2022-05) with the maintained MIT fork `@cantoo/pdf-lib@2.9.1` — a same-public-API swap across the four export paths, done while `npm audit` was still clean rather than under advisory pressure.

## Audit Methodology

Expand All @@ -22,6 +22,8 @@ Before removing a Tier 3 candidate, run a transitive-dep check (`npm ls <pkg>`).
| **Root deps** | | | | |
| `pm2` | 1 | KEEP | top-level scripts | Process manager, foundational. Declared in root `dependencies` (the root manifest has no devDependencies). Pinned `7.0.4` (aligned with server pin) |
| **Server deps** | | | | |
| `@cantoo/pdf-lib` | 2 | KEEP | PDF generation for the volume / comic / prose / legacy-archive exports | Maintained MIT fork of `pdf-lib`, which has had no publish since 2022-05. Same public API (`PDFDocument`/`rgb`/`StandardFonts`); the swap was 4 import lines. On maintenance watch — see the detailed finding |
| `pdf-lib` | — | REPLACED | PDF generation | 2026-09-02 → `@cantoo/pdf-lib` (issue #5672). Abandoned upstream: `1.17.1` is simultaneously the pinned and the latest published version, unchanged since 2022-05-12. Not deprecated and never flagged by `npm audit` — replaced ahead of an advisory, not in response to one |
| `@novnc/novnc` | 1 | KEEP | PortDeck remote desktop viewer | Mature RFB/VNC protocol implementation; replacing it would require owning multiple security types and framebuffer encodings |
| `@googleapis/calendar` | 1 | KEEP | Calendar integration | Scoped official Google SDK (replaced monolithic `googleapis`) |
| `@googleapis/gmail` | 1 | KEEP | Messages/Gmail integration | Scoped official Google SDK |
Expand All @@ -30,7 +32,6 @@ Before removing a Tier 3 candidate, run a transitive-dep check (`npm ls <pkg>`).
| `google-auth-library` | 1 | KEEP | Google OAuth | Pairs with `@googleapis/*` |
| `kokoro-js` | 2 | KEEP | `server/services/voice/tts-kokoro.js` | Only pure-JS in-process TTS; replacement = Python subprocess + pooling |
| `node-pty` | 1 | KEEP | shell/terminal services | Native PTY binding (N-API) |
| `pdf-lib` | 1 | KEEP | PDF generation/manipulation | |
| `pg` | 1 | KEEP | Postgres access | Official `pg` driver |
| `pm2` | 1 | KEEP | app lifecycle | Process manager. Pinned `7.0.4` (aligned with root pin) |
| `sharp` | 1 | KEEP | image processing | Native, widely-audited |
Expand Down Expand Up @@ -84,6 +85,17 @@ Before removing a Tier 3 candidate, run a transitive-dep check (`npm ls <pkg>`).

## Detailed Findings — Tier 2/3 Audits

### `@cantoo/pdf-lib` — KEEP (Tier 2), replaced `pdf-lib` 2026-09-02 (issue #5672)

- **Usage**: 4 import sites, all the same named import — `server/services/pipeline/volumePdf.js`, `comicPdf.js`, `proseExport.js`, and `server/services/legacyExport.js` (`import { PDFDocument, rgb, StandardFonts }`). ~40 call sites across them, all in the narrow high-level core: `PDFDocument.create`, `addPage`, `embedFont`, `drawText`, `drawImage`, `widthOfTextAtSize`, `save`, plus `rgb()` and 9 `StandardFonts` constants. No low-level `PDFDict`/`PDFRef` work, no form filling, no encryption, no incremental update.
- **Why the swap**: `pdf-lib@1.17.1` has had **no npm publish since 2022-05-12** — the pinned version and the latest published version are the same release. It is not deprecated and `npm audit` was clean, but it is a parser/serializer for a hostile binary format sitting on the export path for four user-facing pipelines. When a PDF advisory lands there is no upstream release to take. Migrating while audit is green is a planned change; migrating after an advisory is an emergency.
- **The fork**: `@cantoo/pdf-lib` (`github.com/cantoo-scribe/pdf-lib`), same MIT license, pinned `2.9.1`, published 2026-08. It preserves the `PDFDocument` / `rgb` / `StandardFonts` public API, so the migration was 4 import lines + 1 manifest line + the lockfile — no output, layout, font, or page-geometry change.
- **Transitive cost**: `server/package-lock.json` goes 380 → 385 packages (**+5 net**). In: the fork plus `color`, `color-string`, `is-arrayish`, `simple-swizzle`, `html-entities`, `node-html-better-parser`, and a top-level `pako`. Out: `pdf-lib` and its nested `pako` / `tslib` copies. `@pdf-lib/standard-fonts` and `@pdf-lib/upng` **stay** — the fork depends on them too — and so does the top-level `tslib`, which other packages pull. `npm audit` stays at 0 vulnerabilities.
- **Replacement complexity if owned in-tree instead**: Infeasible (300+ lines of font metrics, xref tables, and content-stream encoding) — which is why a maintained fork, not a rewrite, was the decision.
- **Regression cover**: two boundary tests assert real output bytes rather than helper behaviour, because a same-API fork can only regress below the helper layer — `proseExport.test.js` (`buildProsePdf` returns a `Uint8Array` starting `%PDF-` and ending `%%EOF`) and `comicPdf.test.js` (image XObject count scales with the number of embedded pages, catching a silently-dropped `drawImage` payload).
- **Grep caveat for the next audit**: `server/services/legacyExport.js` contains a byte sequence that makes `file(1)` classify it as `data`, so plain `grep -r` **silently skips it** — a repo-wide dependency sweep must use `grep -ra`. That is exactly how the fourth import site was missed when this migration was first scoped; it surfaced only as an `ERR_MODULE_NOT_FOUND` in the suite.
- **Re-audit trigger**: revisit if `@cantoo/pdf-lib` itself goes >12 months without a publish, or on any CVE against it. The fallback is the same shape as the swap in: another maintained fork, or upstream `pdf-lib` if it ever resumes releases.

### `kokoro-js` — KEEP (Tier 2)

- **Usage**: 1 dynamic import in `server/services/voice/tts-kokoro.js` (~80 LOC module). 3 call sites: `KokoroTTS.from_pretrained()`, `tts.generate(text, {voice, speed})`, `audio.toWav()`.
Expand Down
2 changes: 1 addition & 1 deletion server/lib/pdfImageEmbed.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* PDF image-embed helpers shared between the issue-level comic PDF and the
* volume-level trade-paperback PDF. Both assemblies read PNG/JPEG files
* from PATHS.images, embed them into a pdf-lib document, and fit each
* from PATHS.images, embed them into a @cantoo/pdf-lib document, and fit each
* image to a printable page with a small white margin.
*
* Caller passes its own error codes / subject strings so a failure inside
Expand Down
2 changes: 1 addition & 1 deletion server/lib/proseExportSettings.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export const TRIM_SIZES = Object.freeze({
});
export const DEFAULT_TRIM_SIZE = 'us-trade';

// Interior body fonts — the pdf-lib StandardFonts a print interior can use
// Interior body fonts — the @cantoo/pdf-lib StandardFonts a print interior can use
// without embedding a font file. Times is the traditional book-interior serif.
export const INTERIOR_FONTS = Object.freeze(['times', 'helvetica', 'courier']);
export const DEFAULT_INTERIOR_FONT = 'times';
Expand Down
123 changes: 98 additions & 25 deletions server/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"test:db:ci": "vitest run --config vitest.config.db.js --bail=1 --reporter=default --reporter=github-actions"
},
"dependencies": {
"@cantoo/pdf-lib": "2.9.1",
"@googleapis/calendar": "^16.0.0",
"@googleapis/gmail": "^18.0.0",
"@novnc/novnc": "1.7.0",
Expand All @@ -29,7 +30,6 @@
"google-auth-library": "^11.0.2",
"kokoro-js": "1.2.1",
"node-pty": "1.2.0-beta.15",
"pdf-lib": "^1.17.1",
"pg": "8.23.0",
"playwright-core": "1.62.1",
"pm2": "7.0.4",
Expand Down
12 changes: 6 additions & 6 deletions server/services/legacyExport.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import { createHash } from 'crypto';
import { hostname } from 'os';
import { PDFDocument, rgb, StandardFonts } from 'pdf-lib';
import { PDFDocument, rgb, StandardFonts } from '@cantoo/pdf-lib';
import { createZip } from '../lib/zipWriter.js';
import { getCurrentVersion } from './updateChecker.js';
import { exportDigitalTwin } from './digital-twin-export.js';
Expand Down Expand Up @@ -529,7 +529,7 @@ export function buildManifest(files, { sections, portosVersion, generatedAt, pdf

// === PDF rendering (Phase 2) ===
// A rendered, human-readable portrait of the same per-section Markdown the
// bundle already produces. Uses pdf-lib (already a dependency, shared with
// bundle already produces. Uses @cantoo/pdf-lib (already a dependency, shared with
// `pipeline/comicPdf.js`) — Helvetica family, basic word-wrap, heading sizing.
// No images, no external fonts: the Markdown bundle stays the primary artifact;
// the PDF is a convenience for reading/printing offline.
Expand All @@ -543,7 +543,7 @@ const PDF_TEXT_WIDTH = PDF_PAGE.width - PDF_MARGIN * 2;
const PDF_HEADING_SIZE = { 1: 22, 2: 16, 3: 13 };
const PDF_BODY_SIZE = 11;

// Strip the inline Markdown emphasis/link syntax pdf-lib can't render so it
// Strip the inline Markdown emphasis/link syntax @cantoo/pdf-lib can't render so it
// doesn't print literal `**`/`*`/`[text](url)` markers. Pure.
function stripInlineMarkdown(text) {
return String(text)
Expand All @@ -553,12 +553,12 @@ function stripInlineMarkdown(text) {
.replace(/`([^`]+)`/g, '$1'); // `code`
}

// WinAnsi (pdf-lib's StandardFont encoding) can't encode arbitrary Unicode —
// WinAnsi (@cantoo/pdf-lib's StandardFont encoding) can't encode arbitrary Unicode —
// an unencodable glyph throws at draw time. Map the few non-ASCII characters
// our Markdown builders actually emit (VO₂, ⚠️, smart quotes) to safe ASCII,
// then drop anything still outside the encodable range. Pure.
//
// pdf-lib's WinAnsi encoder also throws on control bytes that ARE ≤ 0xFF —
// @cantoo/pdf-lib's WinAnsi encoder also throws on control bytes that ARE ≤ 0xFF —
// the C0 controls (0x00–0x1F), DEL (0x7F), and the undefined C1 range
// (0x80–0x9F) — so a stray control char pasted into free-text identity content
// (brain notes, journals, autobiography) must be stripped BEFORE it reaches
Expand Down Expand Up @@ -617,7 +617,7 @@ function wrapLine(text, font, size, maxWidth) {

/**
* Render the per-section Markdown into a single PDF. Pure-ish (async only for
* pdf-lib's font embedding) — takes the same `{ name, data }` content files
* @cantoo/pdf-lib's font embedding) — takes the same `{ name, data }` content files
* `buildBundleFiles` produces and walks the `.md` ones in bundle order.
* Returns the PDF bytes (Uint8Array). `meta` stamps the title page.
*
Expand Down
Loading