Skip to content

Back to the catalog

A Function-level Dataset of Vulnerable and Fixed Source Code in JavaScript and TypeScript
arxiv.orgpaper

A Function-level Dataset of Vulnerable and Fixed Source Code in JavaScript and TypeScript

JsVul is a function-level dataset of vulnerable and fixed JavaScript and TypeScript source code, built from seven sources using a language-specific pipeline that strips noise such as minified code and cosmetic edits, then applies multi-stage deduplication and heuristic labeling. Delivered as time-ordered JSONL pairs of pre-fix and post-fix code, it is designed to train and evaluate ML-based vulnerability-detection models, and the paper argues language-aware preprocessing improves data quality over generic multi-language datasets.

TL;DR A cleaned, deduplicated, time-ordered dataset of vulnerable and fixed JS and TS code pairs for training code vulnerability-detection ML models.
Also seen from another angle🛡️ Security →
Document

What it claims

JsVul is a function-level JS/TS vulnerability dataset. After cleaning it holds 1,960 fixing commits, 1,994 files and 4,391 affected functions. These split into 2,039 vulnerable and 19,862 non-vulnerable functions. A balanced pairs-only subset holds 1,342 vulnerable / 1,342 fixed functions. The data is ordered by publish_time and split chronologically 80/10/10 (train/val/test) to avoid temporal leakage.

The raw merge had 3,523 commits and about 1.52M changed lines. Most of that was discarded as noise, duplication or ambiguous labeling.

How it works

The pipeline has three stages: Merge, Process and Unify.

flowchart LR
  A[CrossVul] --> M
  B[CVEFixes] --> M
  C[JS Vuln Dataset] --> M
  D[OSSF CVE bench] --> M
  E[SecBench.js] --> M
  F[OSV] --> M
  G[NVD API] --> M
  M[GitHub API re-resolve<br/>commits + parent] --> P[Filter, normalize,<br/>extract functions]
  P --> L{onefunc or<br/>nvdcheck?}
  L -- no --> X[Commit discarded]
  L -- yes --> D2[Global dedup] --> O[JSONL, time-ordered]

Merge. Source datasets serve only as seed lists of fixing commits. Their labels and file contents are ignored. Every commit is re-queried through the GitHub API to:

  • get the full SHA,
  • resolve the current repository name,
  • keep only commits that touch JS/TS.

The pre-fix commit is recomputed as the direct parent, with special handling for merge commits (get_parent_sha in merge_datasets/merge.py). The two continuously updated sources were snapshotted: OSV on 2025-11-18 and NVD on 2025-10-31.

Process.

  1. Drop test, build/config and minified files. Minified files are detected heuristically from line length, whitespace density, punctuation and single-letter identifiers, using the custom js-minify-helper.
  2. Deduplicate commits by intersecting the hashes of the files each commit modified. All 97 flagged pairs were checked by hand and 87 were merged.
  3. Normalize both the pre-fix and post-fix files with ESLint, then Prettier, and regenerate the diffs.
  4. Extract functions with Babel and recast (js-function-extractor), stripping comments.
  5. Label a pre-fix function as vulnerable if it intersects modified lines. If its normalized body is identical to its post-fix version, it was only moved or cosmetically edited, so it is relabeled non-vulnerable.
  6. Keep a commit only if it passes onefunc (exactly one vulnerable function) or nvdcheck (the function or file is named in the NVD entry). Commits that fail both are discarded.
  7. Deduplicate globally. Among identical functions the earliest is kept. When labels conflict, the "vulnerable" label wins.

Evidence

Stage-by-stage counts:

StageCommitsChanged linesVuln / non-vuln functions
filtered3,310192,947n/a
normalized3,218162,2769,632 / 157,051
commit-wise function dedup + labeling filters1,96748,3022,079 / 26,190
final1,96048,0182,039 / 19,862

Intra-commit dedup removed 12 false-positive vulnerable functions and recovered 17 new ones. A random 25% of the normalization changes were inspected by hand for semantic preservation.

Baselines on the pairs-subset test set, where the classes are balanced so 0.5 is chance:

ModelSettingAccPRF0.5
CodeBERTfine-tuned0.5590.5031.0000.506
Qwen2.5-Coder-7B-Instructfine-tuned0.6030.6140.5630.604
Gemini 3 Prozero-shot0.5700.5440.8540.587

CodeBERT's recall of 1.0 at roughly 0.5 precision means it effectively labels everything vulnerable. The best model is about 10 points above chance.

How to use it

The data is on Zenodo as three archives:

  • js_vul: the full dataset.
  • js_vul_pairs_only: matched vulnerable/fixed pairs only.
  • merged_data: the raw pre-/post-fix files per repo and SHA, plus per-project metadata JSON with fields such as cwe, cve, commit_msg, vuln_sha and sources.

Record fields are:

  • id, formatted as repo::sha::path::line::col
  • paired_id
  • project, sha, file
  • loc
  • body
  • label (0/1)
  • name
  • cwe
  • cve, ghsa, snyk, other
  • publish_time

merged_data is the useful starting point if you want to apply your own labeling.

Limits

  • Function bodies are Prettier/ESLint-normalized and comment-stripped. A model trained on them may see a distribution shift on raw code.
  • The onefunc/nvdcheck filter keeps only fixes that localize to a single function or are confirmed by NVD. Multi-function fixes are largely excluded, and this filter is where most commits were lost.
  • Labels are heuristic. Cross-commit conflicts are resolved toward "vulnerable", which trades false negatives for false positives.
  • The baselines are near chance, which makes this a hard benchmark rather than proof of strong learnability.
  • The page documents concrete errors in CrossVul, CVEFixes, OSSF CVE Benchmark, SecBench.js and the JS Vulnerability Dataset. Examples include wrong parent commits, test-only "fix" commits, saved 404 pages and mislabeled languages. This is relevant if you use those sources directly.
  • The fetched text cuts off at Usage Notes. No licence is shown.

Organisations named here

  • JsVul

Technologies named here

  • TypeScript
  • JavaScript