Skip to content

fix(content-type): replace git-diff with jsdiff to clear Snyk issue - #324

Open
naman-contentstack wants to merge 1 commit into
developmentfrom
fix/snyk-replace-git-diff-with-jsdiff
Open

fix(content-type): replace git-diff with jsdiff to clear Snyk issue#324
naman-contentstack wants to merge 1 commit into
developmentfrom
fix/snyk-replace-git-diff-with-jsdiff

Conversation

@naman-contentstack

Copy link
Copy Markdown
Contributor

Why

git-diff@2.0.7 is unmaintained (last published 2018) and pulls in chalk@2, diff@3, loglevel, shelljs and shelljs.exec. Snyk flagged the only vulnerable path in the entire monorepo through that tree:

✗ Missing Release of Resource after Effective Lifetime [Medium]
  SNYK-JS-INFLIGHT-6095116
  git-diff@2.0.7 > shelljs@0.8.5 > glob@7.2.3 > inflight@1.0.6

Snyk lists it under "Issues with no direct upgrade or patch" — because git-diff is abandoned, there is nothing to bump to. The dependency has to go.

What

Replaced it with diff@^9 (jsdiff): zero runtime dependencies, bundled TypeScript types, dual CJS/ESM. As a side effect it also drops the dependency on a git binary and the temp-file + subprocess round trip git-diff used to shell out through shelljs.

import {createTwoFilesPatch} from 'diff'

function buildDiffString(previous: any, current: any) {
  return createTwoFilesPatch(
    previous.uid, current.uid,
    JSON.stringify(previous, null, 2), JSON.stringify(current, null, 2),
    current.updated_at, current.updated_at,
  )
}

createTwoFilesPatch emits the --- / +++ file headers itself, so the hand-rolled header concatenation is gone. @types/git-diff dropped too, since diff ships its own types.

Output equivalence

Not assumed — checked. Both the old and new patch strings were run through Diff2html.parse and compared on file names, added/deleted line counts, hunk headers, and every line's type and content. Identical. The rendered compare view is unchanged.

One behaviour actually improves: git-diff returned undefined when both sides matched, which interpolated the literal string "undefined" into the patch. createTwoFilesPatch returns a well-formed patch with no hunks.

Tests

The existing spec mocked diff2html, so the generated patch was never asserted — the diff-building logic had no real coverage. Added two cases against the string handed to Diff2html.parse:

  • headers, @@ hunk, and the actual changed -/+ lines
  • identical-input case: no hunks, and no literal "undefined"

Both were mutation-checked — corrupting buildDiffString makes them fail, so they catch regressions rather than just passing.

tsc -b --force   exit 0
Test Suites:     16 passed, 16 total
Tests:           78 passed, 78 total   (was 76)
snyk test --all-projects   no vulnerable paths, monorepo-wide

Reviewer notes

  • .talismanrc: the pinned pnpm-lock.yaml checksum is bumped. Talisman flags the lockfile's sha512-... npm integrity hashes as "base64 encoded text" — a false positive, not a secret. The file is already ignored by checksum; the lockfile change invalidated the pin. Same thing 38bebe6 and db3fcc5 did. The pre-commit hook was not bypassed — Talisman and Snyk both ran and passed.
  • Version bump: this branch also carries a pre-existing, uncommitted 1.5.31.5.4 bump that was already in the working tree, in the same package.json as the dep change and not cleanly separable. Happy to pull it out if you'd rather it landed on its own.
  • Pre-existing bug, deliberately not fixed heresrc/core/content-type/compare.ts:18:
    tmp.file({...}, async function (err: any, ...) {
      if (err) throw err
    The callback is async and nothing awaits it, so a real tmp failure becomes an unhandled rejection and kills the CLI instead of surfacing a readable error. This is the one uncovered branch in the coverage report. Out of scope for a dependency swap — worth a follow-up ticket.

🤖 Generated with Claude Code

git-diff@2.0.7 is unmaintained (last published 2018) and pulls in
chalk@2, diff@3, loglevel, shelljs and shelljs.exec. Snyk flagged the
only vulnerable path in the monorepo through that tree:

  Missing Release of Resource after Effective Lifetime [Medium]
  SNYK-JS-INFLIGHT-6095116
  git-diff@2.0.7 > shelljs@0.8.5 > glob@7.2.3 > inflight@1.0.6

Snyk reports no direct upgrade or patch, since git-diff is abandoned.

Replace it with diff@^9 (jsdiff): zero runtime dependencies, bundled
TypeScript types, dual CJS/ESM. It also removes the dependency on a
`git` binary and the temp-file/subprocess round trip git-diff used to
shell out through shelljs.

buildDiffString now calls createTwoFilesPatch, which emits the `---`
and `+++` file headers itself, so the hand-rolled header concatenation
is gone. Output was verified equivalent: both patch strings were run
through Diff2html.parse and compared on file names, added/deleted line
counts, hunk headers and every line type plus content - identical.

Identical inputs are also handled better. git-diff returned undefined
when both sides matched, which interpolated the literal string
"undefined" into the patch; createTwoFilesPatch returns a well-formed
patch with no hunks.

Drop @types/git-diff, since diff ships its own types.

Tests: the existing spec mocked diff2html, so the generated patch was
never asserted. Add two cases covering the string handed to
Diff2html.parse - one for headers, hunk and changed lines, one for the
identical-input case. Both were mutation-checked against a corrupted
buildDiffString.

Suite: 78 passed, 16 suites, tsc clean, Snyk reports no vulnerable
paths across the monorepo.

Bump the pinned pnpm-lock.yaml checksum in .talismanrc, which the
lockfile change invalidates. The finding is the usual sha512 integrity
hashes, not a secret.

Note: this commit also carries a pre-existing, uncommitted version bump
to 1.5.4 that was already present in package.json.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@naman-contentstack
naman-contentstack requested a review from a team as a code owner July 30, 2026 09:12
@github-actions

Copy link
Copy Markdown

🔒 Security Scan Results

ℹ️ Note: Only vulnerabilities with available fixes (upgrades or patches) are counted toward thresholds.

Check Type Count (with fixes) Without fixes Threshold Result
🔴 Critical Severity 0 0 10 ✅ Passed
🟠 High Severity 0 0 25 ✅ Passed
🟡 Medium Severity 0 0 500 ✅ Passed
🔵 Low Severity 0 0 1000 ✅ Passed

⏱️ SLA Breach Summary

✅ No SLA breaches detected. All vulnerabilities are within acceptable time thresholds.

Severity Breaches (with fixes) Breaches (no fixes) SLA Threshold (with/no fixes) Status
🔴 Critical 0 0 15 / 30 days ✅ Passed
🟠 High 0 0 30 / 120 days ✅ Passed
🟡 Medium 0 0 90 / 365 days ✅ Passed
🔵 Low 0 0 180 / 365 days ✅ Passed

✅ BUILD PASSED - All security checks passed

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