Skip to content

chore(deps): clean npm audit and bump puppeteer to 25.10.0 - #19

Merged
kevinccbsg merged 1 commit into
mainfrom
chore/dependency-audit-2026-09
Sep 5, 2026
Merged

chore(deps): clean npm audit and bump puppeteer to 25.10.0#19
kevinccbsg merged 1 commit into
mainfrom
chore/dependency-audit-2026-09

Conversation

@kevinccbsg

Copy link
Copy Markdown
Member

Clears all 4 npm audit advisories (3 high, 1 moderate) and brings puppeteer to the current release.

The two are independent decisions, and it's worth separating them when reviewing: none of the advisories came from puppeteer.

Clean audit — lockfile only, zero risk

Package Bump Comes from
fast-uri 3.1.2 → 3.1.7 openapi-mock-validator → ajv
js-yaml 4.3.0 → 4.3.2 openapi-mock-validator → json-schema-ref-parser
nanoid 3.3.16 → 3.3.18 vitest → vite → postcss (dev only)
postcss 8.5.20 → 8.5.28 vitest → vite (dev only)

All four resolved in-range via npm audit fix — no --force, no direct dependency change. package.json was untouched by this half.

puppeteer 25.3.0 → 25.10.0 — audit-neutral, validated separately

This is where the actual risk sits, since the recording feature leans on undocumented screencast internals. Reviewed by diffing puppeteer's ScreenRecorder.ts and Page.ts between the two versions rather than trusting release notes:

  • The load-bearing parts are unchanged. bufferCount(2, 1) is byte-identical, so the holdFinalFrame workaround still holds. ffmpegPath and the setpts speed filter are also unchanged.
  • path/overwrite genuinely moved out of ScreenRecorder into Page.screencast(), which now writes through a stream instead of ffmpeg's -y flag. Verified by recording over a planted oversized 3 MB file: fully truncated, no leftover tail.
  • page.screencast() is now @deprecated in favour of Page.record(). JSDoc-only, no runtime warning, so CLI output stays quiet.

Recording verified for real, not just mocked

The unit suite mocks page.screencast(), so it proves nothing here. Ran actual recordings on both versions:

  • Identical codec / resolution / fps (vp9, 1280x720, 30fps).
  • 128 frames on both versions for the same 15s run.
  • Short-run frame counts vary run-to-run on both (25.3.0: 28-39, 25.10.0: 28-33), so there is no capture regression — the distributions overlap.
  • --record-speed 2 still halves duration; extracted frames are valid, sidebar hidden, html margin reset intact.

Also included

vitest and @vitest/coverage-v8 took their in-range 4.1.10 → 4.1.11.

Deliberately not included: vitest 5.0.0 — a dev-only major with no audit benefit. Better as its own branch where a break is obviously the upgrade's fault rather than mixed into a security fix.

Lockfile note

Regenerated with npm run lock:linux, and npm ci verified in a node:24 container (exit 0).

The @emnapi/* entries disappeared, but not as the usual macOS staleness — vite 8.2.2 pulls a rolldown that no longer ships the wasm32-wasi binding those were dependencies of. They left the tree legitimately.

Verification

  • 439 tests pass (21 files), coverage unchanged at 96.13% statements
  • npm audit → 0 vulnerabilities
  • npm ci on linux/node:24 → exit 0

Follow-up (not in this PR)

Page.record() would remove the ffmpeg dependency entirely — the pre-flight probe, assertFfmpegAvailable(), the install-help message, record.ffmpegPath. But it has no speed option, so --record-speed would be lost; pace arguably covers that need better anyway. Worth its own spike.

🤖 Generated with Claude Code

Resolves all 4 advisories (3 high, 1 moderate) and brings puppeteer to
the current release.

The audit findings were entirely transitive and none came from puppeteer:

  fast-uri 3.1.2 -> 3.1.7   (openapi-mock-validator > ajv)
  js-yaml  4.3.0 -> 4.3.2   (openapi-mock-validator > json-schema-ref-parser)
  nanoid   3.3.16 -> 3.3.18 (vitest > vite > postcss, dev only)
  postcss  8.5.20 -> 8.5.28 (vitest > vite, dev only)

All four resolved in-range via `npm audit fix`, no --force and no direct
dependency change.

puppeteer 25.3.0 -> 25.10.0 is audit-neutral and was validated separately
against the recording feature, since that is the part that leans on
undocumented screencast internals:

- ScreenRecorder's frame pipeline is unchanged, `bufferCount(2, 1)`
  included, so the holdFinalFrame workaround still holds.
- `ffmpegPath` and the `setpts` speed filter are unchanged.
- `path`/`overwrite` moved out of ScreenRecorder and into Page.screencast,
  which now writes through a stream instead of ffmpeg's -y flag. Verified
  by recording over a planted oversized file: fully truncated, no tail.
- `page.screencast()` is now @deprecated in favour of `Page.record()`, but
  JSDoc-only. No runtime warning, so CLI output stays quiet.

Verified end to end with real recordings, not just the mocked suite:
identical codec/resolution/fps, and 128 frames on both versions for the
same 15s run. Frame counts on short runs vary run to run on both
(25.3.0: 28-39, 25.10.0: 28-33), so there is no capture regression.

vitest and @vitest/coverage-v8 took their in-range 4.1.10 -> 4.1.11.
vitest 5.0.0 is left alone: a dev-only major with no audit benefit.

Lockfile regenerated with `npm run lock:linux` and `npm ci` verified in a
node:24 container. The @emnapi/* entries are gone rather than stale this
time: vite 8.2.2 pulls a rolldown that no longer ships the wasm32-wasi
binding those were dependencies of.

439 tests pass, 0 vulnerabilities.
@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown

TWD Contract Validation

Spec Passed Failed Warnings Mode
./contracts/users-3.0.json 2 3 1 warn
./contracts/posts-3.1.json 2 2 0 warn
./contracts/products-3.0.json 13 23 2 warn
./contracts/events-3.1.json 6 13 0 warn

23 passed · 41 failed · 3 warnings · 1 skipped

Failed validations

./contracts/users-3.0.json

  • GET /users/{userId} (200) — mock getUserNoAddress — in "Contract Validation - Mismatches > should fail: missing nested address field"
    • response.address: missing required property "address"
  • GET /users/{userId} (200) — mock getUserBadAddress — in "Contract Validation - Mismatches > should fail: nested address missing required city"
    • response.address.city: missing required property "city"
    • response.address.country: missing required property "country"
  • GET /users/{userId} (200) — mock getUserBadRole — in "Contract Validation - Mismatches > should fail: oneOf role with invalid variant"
    • response.role: oneOf best match (branch 2 of 2) failed: must be one of: "viewer"

./contracts/posts-3.1.json

  • GET /posts/{postId} (200) — mock getPostNoAuthor — in "Contract Validation - Mismatches > should fail: post missing nested author object"
    • response.author: missing required property "author"
  • GET /posts/{postId} (200) — mock getPostBadMeta — in "Contract Validation - Mismatches > should fail: post oneOf metadata matches neither variant"
    • response.metadata: oneOf best match (branch 1 of 2) failed: missing required property "category", unexpected property "duration", must be one of: "article"

./contracts/products-3.0.json

  • GET /products (200) — mock getProductEmptyName — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: empty name violates minLength"
    • response[0].name: must NOT have fewer than 1 characters
  • GET /products (200) — mock getProductBadSku — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid SKU pattern"
    • response[0].sku: must match pattern "^[A-Z]{2,4}-\d{4,8}$"
  • GET /products (200) — mock getProductBadUuid — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid uuid format for id"
    • response[0].id: must match format "uuid"
  • GET /products (200) — mock getProductBadDateTime — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid date-time format"
    • response[0].createdAt: must match format "date-time"
  • GET /products (200) — mock getProductBadDate — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid date format"
    • response[0].releaseDate: must match format "date"
  • GET /products (200) — mock getProductBadEmail — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid email format"
    • response[0].contactEmail: must match format "email"
  • GET /products (200) — mock getProductBadUri — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid uri format"
    • response[0].website: must match format "uri"
  • GET /products (200) — mock getProductBadIp — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid ipv4 format"
    • response[0].serverIp: must match format "ipv4"
  • GET /products (200) — mock getProductBadIpV6 — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid ipv6 format"
    • response[0].serverIpV6: must match format "ipv6"
  • GET /products (200) — mock getProductZeroPrice — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: price of 0 violates exclusiveMinimum"
    • response[0].price: must be > 0
  • GET /products (200) — mock getProductNegQty — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: negative quantity violates minimum"
    • response[0].quantity: must be >= 0
  • GET /products (200) — mock getProductOverQty — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: quantity exceeds maximum"
    • response[0].quantity: must be <= 999999
  • GET /products (200) — mock getProductBadWeight — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: weight not multipleOf 0.01"
    • response[0].weight: must be multiple of 0.01
  • GET /products (200) — mock getProductBadRating — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: rating above maximum (5)"
    • response[0].rating: must be <= 5
  • GET /products (200) — mock getProductBadCurrency — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid enum value for currency"
    • response[0].currency: must be one of: "USD", "EUR", "GBP", "JPY"
  • GET /products (200) — mock getProductBadCategory — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid enum value for category"
    • response[0].category: must be one of: "electronics", "clothing", "food", "books", "toys"
  • GET /products (200) — mock getProductBadBool — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: string value for boolean inStock"
    • response[0].inStock: expected boolean, got string
  • GET /products (200) — mock getProductDupTags — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: duplicate tags violates uniqueItems"
    • response[0].tags: must NOT have duplicate items (items ## 1 and 0 are identical)
  • GET /products (200) — mock getProductTooManyTags — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: tags exceeds maxItems (10)"
    • response[0].tags: must NOT have more than 10 items
  • GET /products (200) — mock getProductBadMeta — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: non-string value in metadata additionalProperties"
    • response[0].metadata.count: expected string, got number
  • GET /settings (200) — mock getSettingsBadExtra — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: extra property on Settings (additionalProperties: false)"
    • response.extraField: unexpected property "extraField"
  • GET /settings (200) — mock getSettingsBadLang — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: invalid language pattern in Settings"
    • response.language: must match pattern "^[a-z]{2}(-[A-Z]{2})?$"
  • GET /products (200) — mock getProductBadNullable — in "Contract Validation - Products Mismatches (OpenAPI 3.0 — error mode) > should fail: wrong type for nullable description (number instead of string|null)"
    • response[0].description: expected string,null, got number

./contracts/events-3.1.json

  • GET /events (200) — mock getEventsEmpty — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: empty events array violates minItems (1)"
    • response: must NOT have fewer than 1 items
  • GET /events (200) — mock getEventShortName — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: event name too short (minLength: 3)"
    • response[0].name: must NOT have fewer than 3 characters
  • GET /events (200) — mock getEventBadDate — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: invalid date-time format for startDate"
    • response[0].startDate: must match format "date-time"
  • GET /events (200) — mock getEventFloatId — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: float value for integer id"
    • response[0].id: expected integer, got number
    • response[0].id: must match format "int64"
  • GET /events (200) — mock getEventBadBool — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: number value for boolean active"
    • response[0].active: expected boolean, got number
  • GET /events (200) — mock getEventBadStatus — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: invalid enum value for status"
    • response[0].status: must be one of: "draft", "published", "archived"
  • GET /events (200) — mock getEventScoreMax — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: score at exclusiveMaximum boundary (100)"
    • response[0].score: must be < 100
  • GET /events (200) — mock getEventLowPriority — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: priority below minimum (1)"
    • response[0].priority: must be >= 1
  • GET /events (200) — mock getEventHighPriority — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: priority above maximum (5)"
    • response[0].priority: must be <= 5
  • GET /events (200) — mock getEventDupAttendees — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: duplicate attendees violates uniqueItems"
    • response[0].attendees: must NOT have duplicate items (items ## 1 and 0 are identical)
  • GET /events (200) — mock getEventNoAttendees — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: empty attendees array violates minItems (1)"
    • response[0].attendees: must NOT have fewer than 1 items
  • GET /events (200) — mock getEventBadAttendee — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: invalid email format in attendees"
    • response[0].attendees[0]: must match format "email"
  • GET /events/{eventId} (200) — mock getEventBadNullable — in "Contract Validation - Events Mismatches (OpenAPI 3.1 — error mode) > should fail: wrong type for nullable description (number instead of string|null)"
    • response.description: expected string,null, got number

View full report →

@kevinccbsg
kevinccbsg merged commit 8c58929 into main Sep 5, 2026
6 checks passed
This was referenced Sep 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant