{"owner":"RetireJS","repo":"retire.js","hasSkills":true,"totalSkillsCount":3,"totalTokensCount":6586,"categories":["claude-rule","anthropic-skill"],"hasMcp":false,"mcpConfig":null,"found":["CLAUDE.md",".claude/skills/retire-add-ast-query/SKILL.md",".claude/skills/retire-add-library/SKILL.md"],"skills":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Repository structure\n\nThis is a monorepo with two main parts:\n\n- **`node/`** — The CLI tool and core library (TypeScript, published to npm as `retire`). Source in `node/src/`, compiled output in `node/lib/`.\n- **`repository/`** — The vulnerability database and tooling to manage it.\n\n## Node CLI (node/)\n\n```bash\ncd node\nnpm install\nnpm run build       # compile TypeScript → lib/\nnpm run test        # run the test suite (node/spec/tests/*.spec.ts)\nnpm run check       # lint + typecheck\nnpm run lint        # eslint --fix\nnpm run typecheck   # tsc --noEmit\n```\n\n### Testing\n\nTests use the built-in [`node:test`](https://nodejs.org/api/test.html) runner (`describe`/`it` with\n`node:assert`), not jest.\n\n**The specs import from `../../lib/`, not `../../src/`, so `npm run build` must be run before\n`npm run test`** — otherwise you are testing the previously compiled output and your changes will\nappear to have no effect:\n\n```bash\ncd node\nnpm run build && npm run test\n```\n\nRun a single test file:\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test spec/tests/contentscan.spec.ts\n```\n\nRun a single test by name (matched as a regex against the `it(...)` description):\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test \\\n  --test-name-pattern 'should validate report according to schema' spec/tests/cyclonedx.spec.ts\n```\n\nReporters that emit CycloneDX are validated against the JSON schemas bundled in\n`node/spec/schema/`. When adding support for a new spec version, add its `bom-<version>.schema.json`\nthere too.\n\n## Vulnerability repository (repository/)\n\nThe source of truth is `jsrepository-master.json`. The other `jsrepository*.json` files are derived outputs and must be kept in sync.\n\nAfter editing `jsrepository-master.json`, regenerate all derived files:\n```bash\ncd repository\nnode convertToVersioned\n```\n\nValidate that all derived files are in sync and the format is correct:\n```bash\ncd repository\nnode validate\n```\n\nTest that detection regexes actually match the real library files (downloads from CDN, cached in `repository/tmp/`):\n```bash\ncd repository\nnode test-detection.js\nnode test-detection.js <library-name>   # test a single library\n```\n\n**Before submitting a PR:** always run `validate` and `test-detection.js`.\n\n## Repository format\n\nEntries in `jsrepository-master.json` use `§§version§§` as a placeholder for a version-capturing regex group. Required fields per vulnerability:\n\n- `severity` — align with CVE/GHSA severity when available\n- `cwe` — array of CWE IDs\n- `identifiers` — at least one of: `CVE`, `githubID`, `pr`, `issue`\n\nVersion ranges: omit `atOrAbove` if no lower bound is known; use `\"999.0.0\"` for `below` when no upper bound is known.\n\nDo **not** edit `jsrepository.json` directly — it is generated from master. Do **not** submit to `npmrepository.json` (deprecated).\n\n## Core detection logic (node/src/)\n\n- `retire.ts` — core scanning functions: `scanUri`, `scanFileName`, `scanFileContent`, `check`\n- `scanner.ts` — filesystem walker, orchestrates scanning files and npm packages\n- `deepscan.ts` — AST-based detection for libraries that embed version strings in non-obvious ways\n- `repo.ts` — loads and validates the repository (uses zod schema)\n- `cli.ts` — commander-based CLI entry point\n- `reporters/` — output formatters (console, JSON, CycloneDX SBOM)\n\n## Bumping the version number\n\nWhen bumping the version number of node/package.json, also update the version number in:\n- node/lib/retire.js\n- chrome/extension/manifest.json\n- chrome/extension-no-func/manifest.json\n",".claude/skills/retire-add-ast-query/SKILL.md":"---\nname: retire-add-ast-query\ndescription: Add an AST query for a JavaScript library to the retire.js vulnerability database. Given a library name, downloads multiple versions, analyzes the code structure where the version string is assigned, writes an ASTronomical XPath-like query anchored with library-specific identifiers, tests the query locally, adds it to jsrepository-ast.js, and runs validate/test-detection. Use this skill whenever adding an AST-based version detection query to retire.js.\n---\n\n# retire-add-ast-query\n\nThis skill adds an ASTronomical query for a JavaScript library to the retire.js AST detection system. It downloads real files from CDNs, reads the code structure around version assignments, builds a query anchored to library-specific identifiers, tests it locally with `astronomical`, and validates with `test-detection.js`.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nAST queries detect the library version by traversing the JavaScript AST rather than matching raw text. Queries must be anchored to library-specific identifiers on sibling or parent nodes — otherwise they'll fire on unrelated files that happen to have a `version` assignment. The query returns the version string directly (no `§§version§§` placeholder needed — the Literal node's value is the result).\n\nAll queries live in `repository/jsrepository-ast.js` (a CommonJS module). After editing it, run `./convertToVersioned` to propagate to the versioned JSON files used at runtime.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description\n```\n\nor jsdelivr if not on cdnjs:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified name) and derive the non-minified name (strip `.min`). Record the full list of versions.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across history: 1 early (not the very first), 2–3 middle, and the latest.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-ast-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nVerify each downloaded successfully (non-zero, not an HTML 404 page):\n```bash\nls -la /tmp/retire-ast-{NAME}/\nhead -3 /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIf cdnjs returns 404, fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\n## Step 4 — Find Version Strings\n\nGrep each downloaded file for the exact version number:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nFor single-line minified files where grep context flags fail, use Python:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-ast-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,100}1\\.2\\.3.{0,100}', content)[:5]:\n    print(m)\n\"\n```\n\n## Step 5 — Understand the Code Pattern\n\nRead 10–20 lines around each version occurrence in non-minified files:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\n# Then:\nsed -n '${LINE_MINUS_10},${LINE_PLUS_10}p' /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIdentify two things:\n1. **What node holds the version**: e.g. `x.VERSION = \"1.2.3\"` (AssignmentExpression → right → Literal), `{version: \"1.2.3\"}` (ObjectExpression → Property → value → Literal), `var version = \"1.2.3\"` (VariableDeclarator → init → Literal)\n2. **Library-specific anchors nearby**: an identifier that uniquely identifies this library. The anchor must appear in the same block/expression as the version assignment — or inside a method body of the same object.\n\n**Anchor quality, best to worst:**\n- **Gold**: an identifier that contains the library name — e.g. `tinyMCEPreInit`, `isMoment`, `HandlebarsEnvironment`, `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These cannot match any other library by definition.\n- **Good**: a highly domain-specific sibling property or method that wouldn't appear together with a version string in any other library — e.g. `migrateVersion`, `focusedEditor` combined with `majorVersion`.\n- **Weak**: generic property names like `releaseDate`, `activeEditor`, `major`, `init`, `build`. These could plausibly appear in other libraries and should only be used in combination with other anchors, never alone.\n\n**Look inside method bodies too.** If the version is in an object literal, scan the body of methods on that same object for library-name identifiers — e.g. `setup() { ... window.tinyMCEPreInit ... }` inside the same ObjectExpression. A `//MemberExpression` or `//Identifier` traversal into a method body provides a much stronger anchor than any generic sibling property name.\n\nAsk yourself: *would this query fire on a file from an unrelated library that happens to assign a version string?* If you're not confident the answer is no, strengthen the anchor.\n\n## Step 6 — Write a Candidate Query\n\nUse ASTronomical syntax to navigate from the root to the version Literal, filtering with library-specific anchors.\n\n### ASTronomical syntax quick reference\n\n| Syntax | Meaning |\n|--------|---------|\n| `//NodeType` | Descendant at any depth |\n| `/NodeType` | Direct child |\n| `/:attr` | Named attribute of current node |\n| `/$:name` | Resolve identifier binding (follow variable reference) |\n| `/$$:name` | Binding or attribute fallback (when value may be variable or literal) |\n| `[cond]` | Filter; use `&&`, `\\|\\|`, `==` |\n| `../ ` | Go to parent (use sparingly — causes extra traversal) |\n| `/fn:concat(sel1, \".\", sel2)` | Concatenate results (for `major.minor.patch`) |\n\n### Common patterns\n\n**Named property assignment in a block with a sibling anchor:**\n```\n//BlockStatement[\n  /ExpressionStatement/AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]\n]/ExpressionStatement/AssignmentExpression[\n  /:left/:property/:name == \"version\"\n]/$$:right/:value\n```\n\n**Named object property with a co-occurring sibling property:**\n```\n//ObjectExpression[\n  /Property/:key/:name == \"ANCHOR_PROP\"\n]/Property[/:key/:name == \"version\"]/:value/:value\n```\n\n**Assignment where the object is named directly:**\n```\n//AssignmentExpression[\n  /:left/:object/:name == \"LibName\" &&\n  /:left/:property/:name == \"VERSION\"\n]/:right/:value\n```\n\n**Two properties sharing the same object binding (stronger anchor):**\n```\n//SequenceExpression[\n  /AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]/:left/$:object ==\n  /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value\n```\n\n**Object with library-name identifier inside a method body (gold anchor):**\n```\n//ObjectExpression[\n  /Property[/:key/:name == \"majorVersion\"] &&\n  /Property[/:key/:name == \"setup\"]//MemberExpression[/:property/:name == \"libraryNamePreInit\"]\n]/fn:concat(\n  /Property[/:key/:name == \"majorVersion\"]/:value/:value,\n  \".\",\n  /Property[/:key/:name == \"minorVersion\"]/:value/:value\n)\n```\n\nStart simple, add anchors until the query is specific enough. Multiple queries in the array are OR'd — add a second entry for a different version era if the code structure changed across major versions.\n\n**If two candidate queries end up nearly identical, stop and suspect an ASTronomical bug instead of writing both.** \"Nearly identical\" means the same filters/anchors, differing only in the terminal extraction step — e.g. one query ends in `/:value` and works against a plain string `Literal` (`x.version = \"1.2.3\"`), but a real-world build needs a second, otherwise-identical query ending in `/TemplateElement/:value/:cooked` because that build emits the version as a non-interpolated template literal instead (`` x.version = `1.2.3` ``). A `Literal` and a template literal with no `${}` are semantically the same string — the query *engine* should resolve either transparently, not every library's query author. Duplicating the query per literal-style is a local workaround that only fixes the one library, and this exact shape of gap tends to recur (react, react-dom, moment.js, and ua-parser-js all independently hit it in the same session because one bundler happened to prefer backtick strings).\n\nWhen you notice this, don't add the duplicate to `jsrepository-ast.js`. Instead:\n1. Report the pattern to the user and propose fixing it in ASTronomical itself, at `/Users/erlend/code/github/RetireJS/ASTronomical` (a sibling checkout — confirm it exists before assuming the path).\n2. The fix belongs in `src/index.ts`'s attribute-resolution helpers (`getPrimitiveChildren` / `getPrimitiveChildrenOrNodePaths`) — add a fallback so a requested attribute (e.g. `value`) transparently resolves through the alternate AST shape, instead of requiring the query to spell out both shapes.\n3. Validate with the engine's own suite (`npm run typecheck && npm test` in the ASTronomical repo) before touching retire.js again, and confirm the *original* (single, non-duplicated) query now matches both shapes.\n4. Bumping ASTronomical's version, updating its `CHANGELOG.md`, publishing to npm, and bumping the dependency in `node/package.json` are all separate, more consequential steps (a published package affects every consumer, not just this repo) — check with the user before doing any of them rather than assuming the fix should ship immediately.\n\n## Step 7 — Test the Query Locally\n\nCreate a test script:\n```javascript\n// /tmp/test-ast-query.js\nconst { multiQuery } = require('/Users/erlend/code/github/RetireJS/retire.js/node/node_modules/astronomical');\nconst fs = require('fs');\nconst code = fs.readFileSync(process.argv[2], 'utf8');\nconst query = `YOUR QUERY HERE`;\nconst results = multiQuery(code, { version: query });\nconsole.log(JSON.stringify(results.version, null, 2));\n```\n\nRun against each downloaded version:\n```bash\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.js\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nExpected output: a JSON array containing exactly the version string, e.g. `[\"1.2.3\"]`. If the output is `[]`, the query didn't match — revise it. If it returns multiple values, the anchor is too loose.\n\nIterate across all tested versions until each returns the correct version string. If an early version has a structurally different pattern, write a second query string.\n\n## Step 8 — Add to jsrepository-ast.js\n\nEdit `repository/jsrepository-ast.js`. Add the library key to `exports.queries` (in alphabetical order is nice but not required):\n\n```javascript\n\"library-name\": [\n  /*\n  Code pattern this query matches, e.g.:\n    LibName.isMoment = function; LibName.version = \"1.2.3\"\n  */\n  `//SequenceExpression[\n    /AssignmentExpression[/:left/:property/:name == \"isMoment\"]/:left/$:object ==\n    /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n  ]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value`,\n],\n```\n\nIf the library already has an entry (added by someone else), append to its array rather than replacing it.\n\n## Step 9 — Update testcases.json if needed\n\nIf the library is already in `repository/testcases.json`, check if any of the tested versions have a different code structure that the query can't reach. Add `allowAstMiss` for those versions:\n\n```json\n\"library-name\": {\n  \"https://cdnjs.cloudflare.com/.../§§version§§/...§§subversion§§.js\": {\n    \"versions\": [\"1.0.0\", \"2.0.0\", \"3.0.0\"],\n    \"subversions\": [\"\", \".min\"],\n    \"allowAstMiss\": [\"1.0.0\"]\n  }\n}\n```\n\nIf the library is **not yet** in `testcases.json`, add a minimal entry covering the 3–5 tested versions. Without a `testcases.json` entry, `test-detection.js` won't exercise the AST query.\n\n## Step 10 — Validate and Test\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\nnode test-detection.js {LIBRARY_NAME}\nnode test-detection.js          # full suite — catch any false positives introduced\n```\n\n**Fixing failures:**\n- `validate` error: check `jsrepository-ast.js` syntax — it's a JS module, not JSON; look for unclosed template literals or missing commas\n- `Did not detect ... using ast`: query doesn't match that version — refine the query (back to Step 7) or add `allowAstMiss` for that version. If the fix you're reaching for is \"add a near-duplicate query that only changes how the final value is extracted,\" stop — see the callout in Step 6 about suspecting an ASTronomical engine gap instead.\n- `Detect multiple components using ast`: query is too broad — strengthen the anchor (back to Step 6)\n\n## Step 11 — Report Results\n\nShow:\n- The final query string(s) added to `jsrepository-ast.js`\n- Which versions were successfully detected via AST\n- The timing output from `test-detection.js` (C: content ms, B: AST ms per version)\n",".claude/skills/retire-add-library/SKILL.md":"---\nname: retire-add-library\ndescription: Add a new JavaScript library to the retire.js vulnerability database. Given a library name, looks it up on CDNs, downloads multiple versions, finds version strings, generates detection regexes with (§§version§§) placeholder, adds entries to jsrepository-master.json and testcases.json, and runs validate/test-detection. Use this skill whenever adding a new library to retire.js, generating filecontent/filename/uri extractors, or finding version detection patterns for a JS library.\n---\n\n# retire-add-library\n\nThis skill adds a new JavaScript library to the retire.js vulnerability database. It downloads real files from CDNs, finds where version strings appear, builds and tests regex patterns against those files, and writes validated entries to the repository.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nThe key constraint: `(§§version§§)` is retire.js-specific syntax and won't match against real files. So you must **test regexes using the actual version number first**, then replace the version with `(§§version§§)` only after tests pass.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description,homepage\n```\nor \n```\nhttps://cdnjs.com/libraries/{NAME}\n```\n\nThis returns all available versions and the canonical filename (e.g. `lodash.min.js`). If the library isn't on cdnjs, try jsdelivr:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified) and derive the non-minified name by stripping `.min` (e.g. `lodash.min.js` → `lodash.js`). If cdnjs doesn't have a `.min` in the filename, treat that as the non-minified version and look for a `.min.js` variant.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across the history: 1 early (but not the first), 2–3 in the middle, and the latest. You'll download both minified and non-minified for each.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\n\nVerify each file downloaded successfully (non-zero size, not an HTML error page):\n```bash\nls -la /tmp/retire-{NAME}/\nhead -3 /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA suspicious file size (e.g. ~146 bytes) almost always means a 404 HTML response — check with `head`. If cdnjs returns 404 for a version (even one listed in its metadata), fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\nOnly include versions in testcases.json that successfully downloaded from the CDN URL used in the test template.\n\n## Step 4 — Find Version Strings\n\nSearch each file for the exact version string:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\nDo this for all downloaded versions to see how and where the version appears.\n\n## Step 5 — Identify Unique Context\n\n**Non-minified files**: Check the first 30 lines for block or line comments containing the library name and version:\n```bash\nhead -30 /tmp/retire-{NAME}/{VERSION}.js\n```\nA comment like `/*! Lodash v4.17.21` or `// MyLib JavaScript Library v1.2.3` is the ideal match — the library name provides uniqueness.\n\n**Minified files**: The version is often in a short string assignment. Use grep with context to see what's nearby:\n```bash\ngrep -o \".{0,60}1\\.2\\.3.{0,60}\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\n**Watch out**: some libraries (including newer major versions) ship both `lib.js` and `lib.min.js` as fully minified single-line files. `grep -o` with `.{0,N}` can fail with \"exceeds complexity limits\" on large one-liners. Use Python instead:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,80}1\\.2\\.3.{0,80}', content)[:5]:\n    print(m)\n\"\n```\n\nLook for the library's own name, a unique property (e.g. `lodash.VERSION`), or a characteristic string adjacent to the version. Prefer patterns where the library name appears.\n\n**If the version format changed between major versions** (e.g. 0.x/1.x embedded it in a header comment, 2.x moved it to a code property), write separate filecontent patterns for each era rather than forcing one pattern to cover all versions.\n\n## Step 6 — Write Candidate Regexes (with real version numbers)\n\nWrite candidate regexes using the **actual version number** — escape dots and regex metacharacters in the surrounding context. Do NOT use `§§version§§` yet.\n\nExamples:\n```\nNon-minified comment: /\\*!? Lodash v4\\.17\\.21\nMinified property:    [^a-z]lodash\\.VERSION=\"4\\.17\\.21\"\n```\n\n## Step 7 — Test Regexes Against All Downloaded Files\n\nFor each candidate regex, verify it matches across all versions (substituting each version's actual number):\n```bash\ngrep -oP '/\\*!? Lodash v4\\.17\\.21' /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA good regex matches consistently across all tested versions (with the version number swapped). Refine patterns that fail — look for stable surrounding text that doesn't change between versions.\n\n**Regex quality rules:**\n- Generic patterns like `version=\"1\\.2\\.3\"` or `static version=` are too broad — they will likely appear in unrelated libraries. Always anchor with library-specific identifiers visible in the surrounding context (library name, unique module names, characteristic property names).\n- Examine context on **both sides** of the version string. Prefer identifiers that are unique to that library (e.g. Quill's `delta` and `parchment` imports).\n- Any unbounded quantifier over a character class — `[^,]+`, `[^\"]*`, `.+` — **must** be size-limited: `[^,]{1,20}`, `[^\"]{1,30}`, `.{1,50}`. Unbounded quantifiers risk catastrophic backtracking and are a code-quality red flag.\n\n## Step 8 — Replace Version with `(§§version§§)`\n\nOnly after tests pass, replace the concrete version literal with `(§§version§§)`. This **must be the only capturing group** — convert any other `(...)` to `(?:...)`.\n\n```\n/\\*!? Lodash v4\\.17\\.21   →   /\\*!? Lodash v(§§version§§)\n[^a-z]lodash\\.VERSION=\"4\\.17\\.21\"  →  [^a-z]lodash\\.VERSION=\"(§§version§§)\"\n```\n\nAlso generate filename and uri patterns (adjust if the actual URL/filename structure differs):\n```\nfilename:  lodash-(§§version§§)(?:\\.min)?\\.js\nuri:       /(§§version§§)/lodash(?:\\.min)?\\.js\n```\n\n## Step 9 — Add Entry to jsrepository-master.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/jsrepository-master.json`.\n\nInsert the new entry **immediately before** the `\"dont check\"` key at the end of the file. The `vulnerabilities` field is an empty array — vulnerability details get added separately later.\n\n```json\n\"LIBRARY_NAME\": {\n  \"npmname\": \"npm-package-name\",\n  \"vulnerabilities\": [],\n  \"extractors\": {\n    \"filename\": [\"LIBRARY_NAME-(§§version§§)(?:\\\\.min)?\\\\.js\"],\n    \"filecontent\": [\"REGEX_1\", \"REGEX_2\"],\n    \"uri\": [\"/(§§version§§)/LIBRARY_NAME(?:\\\\.min)?\\\\.js\"]\n  }\n},\n```\nDon't add npmname if the library isn't on npm or if the npm package name is the same as the library name.\n\n## Step 10 — Add Entry to testcases.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/testcases.json`.\n\nAdd the library before the closing `}`. The last existing entry in the file needs a comma after its closing brace. Use cdnjs CDN URLs with `§§version§§` and `§§subversion§§` placeholders. Include the 3–5 tested versions:\n\n```json\n\"LIBRARY_NAME\": {\n  \"https://cdnjs.cloudflare.com/ajax/libs/LIBRARY_NAME/§§version§§/FILENAME§§subversion§§.js\": {\n    \"versions\": [\"VERSION_1\", \"VERSION_2\", \"VERSION_3\"],\n    \"subversions\": [\"\", \".min\"]\n  }\n}\n```\n\nAdjust the URL template if minified/non-minified files differ in path rather than just a `.min` suffix.\n\n## Step 11 — Validate and Test\n\nRun from the repository directory:\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\n./convertToVersioned && node test-detection.js LIBRARY_NAME\n./convertToVersioned && node test-detection.js\n```\n\n**Fixing failures:**\n- `validate` errors: likely means `§§version§§` is not the only capture group, or there's a JSON syntax error — check the regex for extra `(...)` groups\n- `node test-detection.js LIBRARY_NAME` failures: the regex doesn't match the downloaded URLs/files — refine the pattern\n- `node test-detection.js` (no arg) unexpected matches: the regex is too broad — add more library-specific context to narrow it\n\nIterate until all three commands pass cleanly.\n\n## Step 12 — Report Results\n\nShow the final extractors added to the repo and summarize the test results (which versions were detected, which commands passed).\n"},"files":{"CLAUDE.md":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Repository structure\n\nThis is a monorepo with two main parts:\n\n- **`node/`** — The CLI tool and core library (TypeScript, published to npm as `retire`). Source in `node/src/`, compiled output in `node/lib/`.\n- **`repository/`** — The vulnerability database and tooling to manage it.\n\n## Node CLI (node/)\n\n```bash\ncd node\nnpm install\nnpm run build       # compile TypeScript → lib/\nnpm run test        # run the test suite (node/spec/tests/*.spec.ts)\nnpm run check       # lint + typecheck\nnpm run lint        # eslint --fix\nnpm run typecheck   # tsc --noEmit\n```\n\n### Testing\n\nTests use the built-in [`node:test`](https://nodejs.org/api/test.html) runner (`describe`/`it` with\n`node:assert`), not jest.\n\n**The specs import from `../../lib/`, not `../../src/`, so `npm run build` must be run before\n`npm run test`** — otherwise you are testing the previously compiled output and your changes will\nappear to have no effect:\n\n```bash\ncd node\nnpm run build && npm run test\n```\n\nRun a single test file:\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test spec/tests/contentscan.spec.ts\n```\n\nRun a single test by name (matched as a regex against the `it(...)` description):\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test \\\n  --test-name-pattern 'should validate report according to schema' spec/tests/cyclonedx.spec.ts\n```\n\nReporters that emit CycloneDX are validated against the JSON schemas bundled in\n`node/spec/schema/`. When adding support for a new spec version, add its `bom-<version>.schema.json`\nthere too.\n\n## Vulnerability repository (repository/)\n\nThe source of truth is `jsrepository-master.json`. The other `jsrepository*.json` files are derived outputs and must be kept in sync.\n\nAfter editing `jsrepository-master.json`, regenerate all derived files:\n```bash\ncd repository\nnode convertToVersioned\n```\n\nValidate that all derived files are in sync and the format is correct:\n```bash\ncd repository\nnode validate\n```\n\nTest that detection regexes actually match the real library files (downloads from CDN, cached in `repository/tmp/`):\n```bash\ncd repository\nnode test-detection.js\nnode test-detection.js <library-name>   # test a single library\n```\n\n**Before submitting a PR:** always run `validate` and `test-detection.js`.\n\n## Repository format\n\nEntries in `jsrepository-master.json` use `§§version§§` as a placeholder for a version-capturing regex group. Required fields per vulnerability:\n\n- `severity` — align with CVE/GHSA severity when available\n- `cwe` — array of CWE IDs\n- `identifiers` — at least one of: `CVE`, `githubID`, `pr`, `issue`\n\nVersion ranges: omit `atOrAbove` if no lower bound is known; use `\"999.0.0\"` for `below` when no upper bound is known.\n\nDo **not** edit `jsrepository.json` directly — it is generated from master. Do **not** submit to `npmrepository.json` (deprecated).\n\n## Core detection logic (node/src/)\n\n- `retire.ts` — core scanning functions: `scanUri`, `scanFileName`, `scanFileContent`, `check`\n- `scanner.ts` — filesystem walker, orchestrates scanning files and npm packages\n- `deepscan.ts` — AST-based detection for libraries that embed version strings in non-obvious ways\n- `repo.ts` — loads and validates the repository (uses zod schema)\n- `cli.ts` — commander-based CLI entry point\n- `reporters/` — output formatters (console, JSON, CycloneDX SBOM)\n\n## Bumping the version number\n\nWhen bumping the version number of node/package.json, also update the version number in:\n- node/lib/retire.js\n- chrome/extension/manifest.json\n- chrome/extension-no-func/manifest.json\n",".claude/skills/retire-add-ast-query/SKILL.md":"---\nname: retire-add-ast-query\ndescription: Add an AST query for a JavaScript library to the retire.js vulnerability database. Given a library name, downloads multiple versions, analyzes the code structure where the version string is assigned, writes an ASTronomical XPath-like query anchored with library-specific identifiers, tests the query locally, adds it to jsrepository-ast.js, and runs validate/test-detection. Use this skill whenever adding an AST-based version detection query to retire.js.\n---\n\n# retire-add-ast-query\n\nThis skill adds an ASTronomical query for a JavaScript library to the retire.js AST detection system. It downloads real files from CDNs, reads the code structure around version assignments, builds a query anchored to library-specific identifiers, tests it locally with `astronomical`, and validates with `test-detection.js`.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nAST queries detect the library version by traversing the JavaScript AST rather than matching raw text. Queries must be anchored to library-specific identifiers on sibling or parent nodes — otherwise they'll fire on unrelated files that happen to have a `version` assignment. The query returns the version string directly (no `§§version§§` placeholder needed — the Literal node's value is the result).\n\nAll queries live in `repository/jsrepository-ast.js` (a CommonJS module). After editing it, run `./convertToVersioned` to propagate to the versioned JSON files used at runtime.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description\n```\n\nor jsdelivr if not on cdnjs:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified name) and derive the non-minified name (strip `.min`). Record the full list of versions.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across history: 1 early (not the very first), 2–3 middle, and the latest.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-ast-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nVerify each downloaded successfully (non-zero, not an HTML 404 page):\n```bash\nls -la /tmp/retire-ast-{NAME}/\nhead -3 /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIf cdnjs returns 404, fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\n## Step 4 — Find Version Strings\n\nGrep each downloaded file for the exact version number:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nFor single-line minified files where grep context flags fail, use Python:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-ast-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,100}1\\.2\\.3.{0,100}', content)[:5]:\n    print(m)\n\"\n```\n\n## Step 5 — Understand the Code Pattern\n\nRead 10–20 lines around each version occurrence in non-minified files:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\n# Then:\nsed -n '${LINE_MINUS_10},${LINE_PLUS_10}p' /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIdentify two things:\n1. **What node holds the version**: e.g. `x.VERSION = \"1.2.3\"` (AssignmentExpression → right → Literal), `{version: \"1.2.3\"}` (ObjectExpression → Property → value → Literal), `var version = \"1.2.3\"` (VariableDeclarator → init → Literal)\n2. **Library-specific anchors nearby**: an identifier that uniquely identifies this library. The anchor must appear in the same block/expression as the version assignment — or inside a method body of the same object.\n\n**Anchor quality, best to worst:**\n- **Gold**: an identifier that contains the library name — e.g. `tinyMCEPreInit`, `isMoment`, `HandlebarsEnvironment`, `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These cannot match any other library by definition.\n- **Good**: a highly domain-specific sibling property or method that wouldn't appear together with a version string in any other library — e.g. `migrateVersion`, `focusedEditor` combined with `majorVersion`.\n- **Weak**: generic property names like `releaseDate`, `activeEditor`, `major`, `init`, `build`. These could plausibly appear in other libraries and should only be used in combination with other anchors, never alone.\n\n**Look inside method bodies too.** If the version is in an object literal, scan the body of methods on that same object for library-name identifiers — e.g. `setup() { ... window.tinyMCEPreInit ... }` inside the same ObjectExpression. A `//MemberExpression` or `//Identifier` traversal into a method body provides a much stronger anchor than any generic sibling property name.\n\nAsk yourself: *would this query fire on a file from an unrelated library that happens to assign a version string?* If you're not confident the answer is no, strengthen the anchor.\n\n## Step 6 — Write a Candidate Query\n\nUse ASTronomical syntax to navigate from the root to the version Literal, filtering with library-specific anchors.\n\n### ASTronomical syntax quick reference\n\n| Syntax | Meaning |\n|--------|---------|\n| `//NodeType` | Descendant at any depth |\n| `/NodeType` | Direct child |\n| `/:attr` | Named attribute of current node |\n| `/$:name` | Resolve identifier binding (follow variable reference) |\n| `/$$:name` | Binding or attribute fallback (when value may be variable or literal) |\n| `[cond]` | Filter; use `&&`, `\\|\\|`, `==` |\n| `../ ` | Go to parent (use sparingly — causes extra traversal) |\n| `/fn:concat(sel1, \".\", sel2)` | Concatenate results (for `major.minor.patch`) |\n\n### Common patterns\n\n**Named property assignment in a block with a sibling anchor:**\n```\n//BlockStatement[\n  /ExpressionStatement/AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]\n]/ExpressionStatement/AssignmentExpression[\n  /:left/:property/:name == \"version\"\n]/$$:right/:value\n```\n\n**Named object property with a co-occurring sibling property:**\n```\n//ObjectExpression[\n  /Property/:key/:name == \"ANCHOR_PROP\"\n]/Property[/:key/:name == \"version\"]/:value/:value\n```\n\n**Assignment where the object is named directly:**\n```\n//AssignmentExpression[\n  /:left/:object/:name == \"LibName\" &&\n  /:left/:property/:name == \"VERSION\"\n]/:right/:value\n```\n\n**Two properties sharing the same object binding (stronger anchor):**\n```\n//SequenceExpression[\n  /AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]/:left/$:object ==\n  /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value\n```\n\n**Object with library-name identifier inside a method body (gold anchor):**\n```\n//ObjectExpression[\n  /Property[/:key/:name == \"majorVersion\"] &&\n  /Property[/:key/:name == \"setup\"]//MemberExpression[/:property/:name == \"libraryNamePreInit\"]\n]/fn:concat(\n  /Property[/:key/:name == \"majorVersion\"]/:value/:value,\n  \".\",\n  /Property[/:key/:name == \"minorVersion\"]/:value/:value\n)\n```\n\nStart simple, add anchors until the query is specific enough. Multiple queries in the array are OR'd — add a second entry for a different version era if the code structure changed across major versions.\n\n**If two candidate queries end up nearly identical, stop and suspect an ASTronomical bug instead of writing both.** \"Nearly identical\" means the same filters/anchors, differing only in the terminal extraction step — e.g. one query ends in `/:value` and works against a plain string `Literal` (`x.version = \"1.2.3\"`), but a real-world build needs a second, otherwise-identical query ending in `/TemplateElement/:value/:cooked` because that build emits the version as a non-interpolated template literal instead (`` x.version = `1.2.3` ``). A `Literal` and a template literal with no `${}` are semantically the same string — the query *engine* should resolve either transparently, not every library's query author. Duplicating the query per literal-style is a local workaround that only fixes the one library, and this exact shape of gap tends to recur (react, react-dom, moment.js, and ua-parser-js all independently hit it in the same session because one bundler happened to prefer backtick strings).\n\nWhen you notice this, don't add the duplicate to `jsrepository-ast.js`. Instead:\n1. Report the pattern to the user and propose fixing it in ASTronomical itself, at `/Users/erlend/code/github/RetireJS/ASTronomical` (a sibling checkout — confirm it exists before assuming the path).\n2. The fix belongs in `src/index.ts`'s attribute-resolution helpers (`getPrimitiveChildren` / `getPrimitiveChildrenOrNodePaths`) — add a fallback so a requested attribute (e.g. `value`) transparently resolves through the alternate AST shape, instead of requiring the query to spell out both shapes.\n3. Validate with the engine's own suite (`npm run typecheck && npm test` in the ASTronomical repo) before touching retire.js again, and confirm the *original* (single, non-duplicated) query now matches both shapes.\n4. Bumping ASTronomical's version, updating its `CHANGELOG.md`, publishing to npm, and bumping the dependency in `node/package.json` are all separate, more consequential steps (a published package affects every consumer, not just this repo) — check with the user before doing any of them rather than assuming the fix should ship immediately.\n\n## Step 7 — Test the Query Locally\n\nCreate a test script:\n```javascript\n// /tmp/test-ast-query.js\nconst { multiQuery } = require('/Users/erlend/code/github/RetireJS/retire.js/node/node_modules/astronomical');\nconst fs = require('fs');\nconst code = fs.readFileSync(process.argv[2], 'utf8');\nconst query = `YOUR QUERY HERE`;\nconst results = multiQuery(code, { version: query });\nconsole.log(JSON.stringify(results.version, null, 2));\n```\n\nRun against each downloaded version:\n```bash\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.js\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nExpected output: a JSON array containing exactly the version string, e.g. `[\"1.2.3\"]`. If the output is `[]`, the query didn't match — revise it. If it returns multiple values, the anchor is too loose.\n\nIterate across all tested versions until each returns the correct version string. If an early version has a structurally different pattern, write a second query string.\n\n## Step 8 — Add to jsrepository-ast.js\n\nEdit `repository/jsrepository-ast.js`. Add the library key to `exports.queries` (in alphabetical order is nice but not required):\n\n```javascript\n\"library-name\": [\n  /*\n  Code pattern this query matches, e.g.:\n    LibName.isMoment = function; LibName.version = \"1.2.3\"\n  */\n  `//SequenceExpression[\n    /AssignmentExpression[/:left/:property/:name == \"isMoment\"]/:left/$:object ==\n    /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n  ]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value`,\n],\n```\n\nIf the library already has an entry (added by someone else), append to its array rather than replacing it.\n\n## Step 9 — Update testcases.json if needed\n\nIf the library is already in `repository/testcases.json`, check if any of the tested versions have a different code structure that the query can't reach. Add `allowAstMiss` for those versions:\n\n```json\n\"library-name\": {\n  \"https://cdnjs.cloudflare.com/.../§§version§§/...§§subversion§§.js\": {\n    \"versions\": [\"1.0.0\", \"2.0.0\", \"3.0.0\"],\n    \"subversions\": [\"\", \".min\"],\n    \"allowAstMiss\": [\"1.0.0\"]\n  }\n}\n```\n\nIf the library is **not yet** in `testcases.json`, add a minimal entry covering the 3–5 tested versions. Without a `testcases.json` entry, `test-detection.js` won't exercise the AST query.\n\n## Step 10 — Validate and Test\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\nnode test-detection.js {LIBRARY_NAME}\nnode test-detection.js          # full suite — catch any false positives introduced\n```\n\n**Fixing failures:**\n- `validate` error: check `jsrepository-ast.js` syntax — it's a JS module, not JSON; look for unclosed template literals or missing commas\n- `Did not detect ... using ast`: query doesn't match that version — refine the query (back to Step 7) or add `allowAstMiss` for that version. If the fix you're reaching for is \"add a near-duplicate query that only changes how the final value is extracted,\" stop — see the callout in Step 6 about suspecting an ASTronomical engine gap instead.\n- `Detect multiple components using ast`: query is too broad — strengthen the anchor (back to Step 6)\n\n## Step 11 — Report Results\n\nShow:\n- The final query string(s) added to `jsrepository-ast.js`\n- Which versions were successfully detected via AST\n- The timing output from `test-detection.js` (C: content ms, B: AST ms per version)\n",".claude/skills/retire-add-library/SKILL.md":"---\nname: retire-add-library\ndescription: Add a new JavaScript library to the retire.js vulnerability database. Given a library name, looks it up on CDNs, downloads multiple versions, finds version strings, generates detection regexes with (§§version§§) placeholder, adds entries to jsrepository-master.json and testcases.json, and runs validate/test-detection. Use this skill whenever adding a new library to retire.js, generating filecontent/filename/uri extractors, or finding version detection patterns for a JS library.\n---\n\n# retire-add-library\n\nThis skill adds a new JavaScript library to the retire.js vulnerability database. It downloads real files from CDNs, finds where version strings appear, builds and tests regex patterns against those files, and writes validated entries to the repository.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nThe key constraint: `(§§version§§)` is retire.js-specific syntax and won't match against real files. So you must **test regexes using the actual version number first**, then replace the version with `(§§version§§)` only after tests pass.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description,homepage\n```\nor \n```\nhttps://cdnjs.com/libraries/{NAME}\n```\n\nThis returns all available versions and the canonical filename (e.g. `lodash.min.js`). If the library isn't on cdnjs, try jsdelivr:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified) and derive the non-minified name by stripping `.min` (e.g. `lodash.min.js` → `lodash.js`). If cdnjs doesn't have a `.min` in the filename, treat that as the non-minified version and look for a `.min.js` variant.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across the history: 1 early (but not the first), 2–3 in the middle, and the latest. You'll download both minified and non-minified for each.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\n\nVerify each file downloaded successfully (non-zero size, not an HTML error page):\n```bash\nls -la /tmp/retire-{NAME}/\nhead -3 /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA suspicious file size (e.g. ~146 bytes) almost always means a 404 HTML response — check with `head`. If cdnjs returns 404 for a version (even one listed in its metadata), fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\nOnly include versions in testcases.json that successfully downloaded from the CDN URL used in the test template.\n\n## Step 4 — Find Version Strings\n\nSearch each file for the exact version string:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\nDo this for all downloaded versions to see how and where the version appears.\n\n## Step 5 — Identify Unique Context\n\n**Non-minified files**: Check the first 30 lines for block or line comments containing the library name and version:\n```bash\nhead -30 /tmp/retire-{NAME}/{VERSION}.js\n```\nA comment like `/*! Lodash v4.17.21` or `// MyLib JavaScript Library v1.2.3` is the ideal match — the library name provides uniqueness.\n\n**Minified files**: The version is often in a short string assignment. Use grep with context to see what's nearby:\n```bash\ngrep -o \".{0,60}1\\.2\\.3.{0,60}\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\n**Watch out**: some libraries (including newer major versions) ship both `lib.js` and `lib.min.js` as fully minified single-line files. `grep -o` with `.{0,N}` can fail with \"exceeds complexity limits\" on large one-liners. Use Python instead:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,80}1\\.2\\.3.{0,80}', content)[:5]:\n    print(m)\n\"\n```\n\nLook for the library's own name, a unique property (e.g. `lodash.VERSION`), or a characteristic string adjacent to the version. Prefer patterns where the library name appears.\n\n**If the version format changed between major versions** (e.g. 0.x/1.x embedded it in a header comment, 2.x moved it to a code property), write separate filecontent patterns for each era rather than forcing one pattern to cover all versions.\n\n## Step 6 — Write Candidate Regexes (with real version numbers)\n\nWrite candidate regexes using the **actual version number** — escape dots and regex metacharacters in the surrounding context. Do NOT use `§§version§§` yet.\n\nExamples:\n```\nNon-minified comment: /\\*!? Lodash v4\\.17\\.21\nMinified property:    [^a-z]lodash\\.VERSION=\"4\\.17\\.21\"\n```\n\n## Step 7 — Test Regexes Against All Downloaded Files\n\nFor each candidate regex, verify it matches across all versions (substituting each version's actual number):\n```bash\ngrep -oP '/\\*!? Lodash v4\\.17\\.21' /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA good regex matches consistently across all tested versions (with the version number swapped). Refine patterns that fail — look for stable surrounding text that doesn't change between versions.\n\n**Regex quality rules:**\n- Generic patterns like `version=\"1\\.2\\.3\"` or `static version=` are too broad — they will likely appear in unrelated libraries. Always anchor with library-specific identifiers visible in the surrounding context (library name, unique module names, characteristic property names).\n- Examine context on **both sides** of the version string. Prefer identifiers that are unique to that library (e.g. Quill's `delta` and `parchment` imports).\n- Any unbounded quantifier over a character class — `[^,]+`, `[^\"]*`, `.+` — **must** be size-limited: `[^,]{1,20}`, `[^\"]{1,30}`, `.{1,50}`. Unbounded quantifiers risk catastrophic backtracking and are a code-quality red flag.\n\n## Step 8 — Replace Version with `(§§version§§)`\n\nOnly after tests pass, replace the concrete version literal with `(§§version§§)`. This **must be the only capturing group** — convert any other `(...)` to `(?:...)`.\n\n```\n/\\*!? Lodash v4\\.17\\.21   →   /\\*!? Lodash v(§§version§§)\n[^a-z]lodash\\.VERSION=\"4\\.17\\.21\"  →  [^a-z]lodash\\.VERSION=\"(§§version§§)\"\n```\n\nAlso generate filename and uri patterns (adjust if the actual URL/filename structure differs):\n```\nfilename:  lodash-(§§version§§)(?:\\.min)?\\.js\nuri:       /(§§version§§)/lodash(?:\\.min)?\\.js\n```\n\n## Step 9 — Add Entry to jsrepository-master.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/jsrepository-master.json`.\n\nInsert the new entry **immediately before** the `\"dont check\"` key at the end of the file. The `vulnerabilities` field is an empty array — vulnerability details get added separately later.\n\n```json\n\"LIBRARY_NAME\": {\n  \"npmname\": \"npm-package-name\",\n  \"vulnerabilities\": [],\n  \"extractors\": {\n    \"filename\": [\"LIBRARY_NAME-(§§version§§)(?:\\\\.min)?\\\\.js\"],\n    \"filecontent\": [\"REGEX_1\", \"REGEX_2\"],\n    \"uri\": [\"/(§§version§§)/LIBRARY_NAME(?:\\\\.min)?\\\\.js\"]\n  }\n},\n```\nDon't add npmname if the library isn't on npm or if the npm package name is the same as the library name.\n\n## Step 10 — Add Entry to testcases.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/testcases.json`.\n\nAdd the library before the closing `}`. The last existing entry in the file needs a comma after its closing brace. Use cdnjs CDN URLs with `§§version§§` and `§§subversion§§` placeholders. Include the 3–5 tested versions:\n\n```json\n\"LIBRARY_NAME\": {\n  \"https://cdnjs.cloudflare.com/ajax/libs/LIBRARY_NAME/§§version§§/FILENAME§§subversion§§.js\": {\n    \"versions\": [\"VERSION_1\", \"VERSION_2\", \"VERSION_3\"],\n    \"subversions\": [\"\", \".min\"]\n  }\n}\n```\n\nAdjust the URL template if minified/non-minified files differ in path rather than just a `.min` suffix.\n\n## Step 11 — Validate and Test\n\nRun from the repository directory:\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\n./convertToVersioned && node test-detection.js LIBRARY_NAME\n./convertToVersioned && node test-detection.js\n```\n\n**Fixing failures:**\n- `validate` errors: likely means `§§version§§` is not the only capture group, or there's a JSON syntax error — check the regex for extra `(...)` groups\n- `node test-detection.js LIBRARY_NAME` failures: the regex doesn't match the downloaded URLs/files — refine the pattern\n- `node test-detection.js` (no arg) unexpected matches: the regex is too broad — add more library-specific context to narrow it\n\nIterate until all three commands pass cleanly.\n\n## Step 12 — Report Results\n\nShow the final extractors added to the repo and summarize the test results (which versions were detected, which commands passed).\n"},"items":[{"name":"SKILL.md","path":".claude/skills/retire-add-ast-query/SKILL.md","rawUrl":"https://raw.githubusercontent.com/RetireJS/retire.js/HEAD/.claude/skills/retire-add-ast-query/SKILL.md","title":"retire-add-ast-query","category":"anthropic-skill","format":"markdown","content":"---\nname: retire-add-ast-query\ndescription: Add an AST query for a JavaScript library to the retire.js vulnerability database. Given a library name, downloads multiple versions, analyzes the code structure where the version string is assigned, writes an ASTronomical XPath-like query anchored with library-specific identifiers, tests the query locally, adds it to jsrepository-ast.js, and runs validate/test-detection. Use this skill whenever adding an AST-based version detection query to retire.js.\n---\n\n# retire-add-ast-query\n\nThis skill adds an ASTronomical query for a JavaScript library to the retire.js AST detection system. It downloads real files from CDNs, reads the code structure around version assignments, builds a query anchored to library-specific identifiers, tests it locally with `astronomical`, and validates with `test-detection.js`.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nAST queries detect the library version by traversing the JavaScript AST rather than matching raw text. Queries must be anchored to library-specific identifiers on sibling or parent nodes — otherwise they'll fire on unrelated files that happen to have a `version` assignment. The query returns the version string directly (no `§§version§§` placeholder needed — the Literal node's value is the result).\n\nAll queries live in `repository/jsrepository-ast.js` (a CommonJS module). After editing it, run `./convertToVersioned` to propagate to the versioned JSON files used at runtime.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description\n```\n\nor jsdelivr if not on cdnjs:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified name) and derive the non-minified name (strip `.min`). Record the full list of versions.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across history: 1 early (not the very first), 2–3 middle, and the latest.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-ast-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nVerify each downloaded successfully (non-zero, not an HTML 404 page):\n```bash\nls -la /tmp/retire-ast-{NAME}/\nhead -3 /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIf cdnjs returns 404, fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\n## Step 4 — Find Version Strings\n\nGrep each downloaded file for the exact version number:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nFor single-line minified files where grep context flags fail, use Python:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-ast-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,100}1\\.2\\.3.{0,100}', content)[:5]:\n    print(m)\n\"\n```\n\n## Step 5 — Understand the Code Pattern\n\nRead 10–20 lines around each version occurrence in non-minified files:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\n# Then:\nsed -n '${LINE_MINUS_10},${LINE_PLUS_10}p' /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIdentify two things:\n1. **What node holds the version**: e.g. `x.VERSION = \"1.2.3\"` (AssignmentExpression → right → Literal), `{version: \"1.2.3\"}` (ObjectExpression → Property → value → Literal), `var version = \"1.2.3\"` (VariableDeclarator → init → Literal)\n2. **Library-specific anchors nearby**: an identifier that uniquely identifies this library. The anchor must appear in the same block/expression as the version assignment — or inside a method body of the same object.\n\n**Anchor quality, best to worst:**\n- **Gold**: an identifier that contains the library name — e.g. `tinyMCEPreInit`, `isMoment`, `HandlebarsEnvironment`, `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These cannot match any other library by definition.\n- **Good**: a highly domain-specific sibling property or method that wouldn't appear together with a version string in any other library — e.g. `migrateVersion`, `focusedEditor` combined with `majorVersion`.\n- **Weak**: generic property names like `releaseDate`, `activeEditor`, `major`, `init`, `build`. These could plausibly appear in other libraries and should only be used in combination with other anchors, never alone.\n\n**Look inside method bodies too.** If the version is in an object literal, scan the body of methods on that same object for library-name identifiers — e.g. `setup() { ... window.tinyMCEPreInit ... }` inside the same ObjectExpression. A `//MemberExpression` or `//Identifier` traversal into a method body provides a much stronger anchor than any generic sibling property name.\n\nAsk yourself: *would this query fire on a file from an unrelated library that happens to assign a version string?* If you're not confident the answer is no, strengthen the anchor.\n\n## Step 6 — Write a Candidate Query\n\nUse ASTronomical syntax to navigate from the root to the version Literal, filtering with library-specific anchors.\n\n### ASTronomical syntax quick reference\n\n| Syntax | Meaning |\n|--------|---------|\n| `//NodeType` | Descendant at any depth |\n| `/NodeType` | Direct child |\n| `/:attr` | Named attribute of current node |\n| `/$:name` | Resolve identifier binding (follow variable reference) |\n| `/$$:name` | Binding or attribute fallback (when value may be variable or literal) |\n| `[cond]` | Filter; use `&&`, `\\|\\|`, `==` |\n| `../ ` | Go to parent (use sparingly — causes extra traversal) |\n| `/fn:concat(sel1, \".\", sel2)` | Concatenate results (for `major.minor.patch`) |\n\n### Common patterns\n\n**Named property assignment in a block with a sibling anchor:**\n```\n//BlockStatement[\n  /ExpressionStatement/AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]\n]/ExpressionStatement/AssignmentExpression[\n  /:left/:property/:name == \"version\"\n]/$$:right/:value\n```\n\n**Named object property with a co-occurring sibling property:**\n```\n//ObjectExpression[\n  /Property/:key/:name == \"ANCHOR_PROP\"\n]/Property[/:key/:name == \"version\"]/:value/:value\n```\n\n**Assignment where the object is named directly:**\n```\n//AssignmentExpression[\n  /:left/:object/:name == \"LibName\" &&\n  /:left/:property/:name == \"VERSION\"\n]/:right/:value\n```\n\n**Two properties sharing the same object binding (stronger anchor):**\n```\n//SequenceExpression[\n  /AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]/:left/$:object ==\n  /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value\n```\n\n**Object with library-name identifier inside a method body (gold anchor):**\n```\n//ObjectExpression[\n  /Property[/:key/:name == \"majorVersion\"] &&\n  /Property[/:key/:name == \"setup\"]//MemberExpression[/:property/:name == \"libraryNamePreInit\"]\n]/fn:concat(\n  /Property[/:key/:name == \"majorVersion\"]/:value/:value,\n  \".\",\n  /Property[/:key/:name == \"minorVersion\"]/:value/:value\n)\n```\n\nStart simple, add anchors until the query is specific enough. Multiple queries in the array are OR'd — add a second entry for a different version era if the code structure changed across major versions.\n\n**If two candidate queries end up nearly identical, stop and suspect an ASTronomical bug instead of writing both.** \"Nearly identical\" means the same filters/anchors, differing only in the terminal extraction step — e.g. one query ends in `/:value` and works against a plain string `Literal` (`x.version = \"1.2.3\"`), but a real-world build needs a second, otherwise-identical query ending in `/TemplateElement/:value/:cooked` because that build emits the version as a non-interpolated template literal instead (`` x.version = `1.2.3` ``). A `Literal` and a template literal with no `${}` are semantically the same string — the query *engine* should resolve either transparently, not every library's query author. Duplicating the query per literal-style is a local workaround that only fixes the one library, and this exact shape of gap tends to recur (react, react-dom, moment.js, and ua-parser-js all independently hit it in the same session because one bundler happened to prefer backtick strings).\n\nWhen you notice this, don't add the duplicate to `jsrepository-ast.js`. Instead:\n1. Report the pattern to the user and propose fixing it in ASTronomical itself, at `/Users/erlend/code/github/RetireJS/ASTronomical` (a sibling checkout — confirm it exists before assuming the path).\n2. The fix belongs in `src/index.ts`'s attribute-resolution helpers (`getPrimitiveChildren` / `getPrimitiveChildrenOrNodePaths`) — add a fallback so a requested attribute (e.g. `value`) transparently resolves through the alternate AST shape, instead of requiring the query to spell out both shapes.\n3. Validate with the engine's own suite (`npm run typecheck && npm test` in the ASTronomical repo) before touching retire.js again, and confirm the *original* (single, non-duplicated) query now matches both shapes.\n4. Bumping ASTronomical's version, updating its `CHANGELOG.md`, publishing to npm, and bumping the dependency in `node/package.json` are all separate, more consequential steps (a published package affects every consumer, not just this repo) — check with the user before doing any of them rather than assuming the fix should ship immediately.\n\n## Step 7 — Test the Query Locally\n\nCreate a test script:\n```javascript\n// /tmp/test-ast-query.js\nconst { multiQuery } = require('/Users/erlend/code/github/RetireJS/retire.js/node/node_modules/astronomical');\nconst fs = require('fs');\nconst code = fs.readFileSync(process.argv[2], 'utf8');\nconst query = `YOUR QUERY HERE`;\nconst results = multiQuery(code, { version: query });\nconsole.log(JSON.stringify(results.version, null, 2));\n```\n\nRun against each downloaded version:\n```bash\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.js\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nExpected output: a JSON array containing exactly the version string, e.g. `[\"1.2.3\"]`. If the output is `[]`, the query didn't match — revise it. If it returns multiple values, the anchor is too loose.\n\nIterate across all tested versions until each returns the correct version string. If an early version has a structurally different pattern, write a second query string.\n\n## Step 8 — Add to jsrepository-ast.js\n\nEdit `repository/jsrepository-ast.js`. Add the library key to `exports.queries` (in alphabetical order is nice but not required):\n\n```javascript\n\"library-name\": [\n  /*\n  Code pattern this query matches, e.g.:\n    LibName.isMoment = function; LibName.version = \"1.2.3\"\n  */\n  `//SequenceExpression[\n    /AssignmentExpression[/:left/:property/:name == \"isMoment\"]/:left/$:object ==\n    /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n  ]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value`,\n],\n```\n\nIf the library already has an entry (added by someone else), append to its array rather than replacing it.\n\n## Step 9 — Update testcases.json if needed\n\nIf the library is already in `repository/testcases.json`, check if any of the tested versions have a different code structure that the query can't reach. Add `allowAstMiss` for those versions:\n\n```json\n\"library-name\": {\n  \"https://cdnjs.cloudflare.com/.../§§version§§/...§§subversion§§.js\": {\n    \"versions\": [\"1.0.0\", \"2.0.0\", \"3.0.0\"],\n    \"subversions\": [\"\", \".min\"],\n    \"allowAstMiss\": [\"1.0.0\"]\n  }\n}\n```\n\nIf the library is **not yet** in `testcases.json`, add a minimal entry covering the 3–5 tested versions. Without a `testcases.json` entry, `test-detection.js` won't exercise the AST query.\n\n## Step 10 — Validate and Test\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\nnode test-detection.js {LIBRARY_NAME}\nnode test-detection.js          # full suite — catch any false positives introduced\n```\n\n**Fixing failures:**\n- `validate` error: check `jsrepository-ast.js` syntax — it's a JS module, not JSON; look for unclosed template literals or missing commas\n- `Did not detect ... using ast`: query doesn't match that version — refine the query (back to Step 7) or add `allowAstMiss` for that version. If the fix you're reaching for is \"add a near-duplicate query that only changes how the final value is extracted,\" stop — see the callout in Step 6 about suspecting an ASTronomical engine gap instead.\n- `Detect multiple components using ast`: query is too broad — strengthen the anchor (back to Step 6)\n\n## Step 11 — Report Results\n\nShow:\n- The final query string(s) added to `jsrepository-ast.js`\n- Which versions were successfully detected via AST\n- The timing output from `test-detection.js` (C: content ms, B: AST ms per version)\n","frontmatter":{"name":"retire-add-ast-query","description":"Add an AST query for a JavaScript library to the retire.js vulnerability database. Given a library name, downloads multiple versions, analyzes the code structure where the version string is assigned, writes an ASTronomical XPath-like query anchored with library-specific identifiers, tests the query locally, adds it to jsrepository-ast.js, and runs validate/test-detection. Use this skill whenever adding an AST-based version detection query to retire.js."},"isInternal":false,"tokens":3298,"sizeBytes":13188},{"name":"SKILL.md","path":".claude/skills/retire-add-library/SKILL.md","rawUrl":"https://raw.githubusercontent.com/RetireJS/retire.js/HEAD/.claude/skills/retire-add-library/SKILL.md","title":"retire-add-library","category":"anthropic-skill","format":"markdown","content":"---\nname: retire-add-library\ndescription: Add a new JavaScript library to the retire.js vulnerability database. Given a library name, looks it up on CDNs, downloads multiple versions, finds version strings, generates detection regexes with (§§version§§) placeholder, adds entries to jsrepository-master.json and testcases.json, and runs validate/test-detection. Use this skill whenever adding a new library to retire.js, generating filecontent/filename/uri extractors, or finding version detection patterns for a JS library.\n---\n\n# retire-add-library\n\nThis skill adds a new JavaScript library to the retire.js vulnerability database. It downloads real files from CDNs, finds where version strings appear, builds and tests regex patterns against those files, and writes validated entries to the repository.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nThe key constraint: `(§§version§§)` is retire.js-specific syntax and won't match against real files. So you must **test regexes using the actual version number first**, then replace the version with `(§§version§§)` only after tests pass.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description,homepage\n```\nor \n```\nhttps://cdnjs.com/libraries/{NAME}\n```\n\nThis returns all available versions and the canonical filename (e.g. `lodash.min.js`). If the library isn't on cdnjs, try jsdelivr:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified) and derive the non-minified name by stripping `.min` (e.g. `lodash.min.js` → `lodash.js`). If cdnjs doesn't have a `.min` in the filename, treat that as the non-minified version and look for a `.min.js` variant.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across the history: 1 early (but not the first), 2–3 in the middle, and the latest. You'll download both minified and non-minified for each.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\n\nVerify each file downloaded successfully (non-zero size, not an HTML error page):\n```bash\nls -la /tmp/retire-{NAME}/\nhead -3 /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA suspicious file size (e.g. ~146 bytes) almost always means a 404 HTML response — check with `head`. If cdnjs returns 404 for a version (even one listed in its metadata), fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\nOnly include versions in testcases.json that successfully downloaded from the CDN URL used in the test template.\n\n## Step 4 — Find Version Strings\n\nSearch each file for the exact version string:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\nDo this for all downloaded versions to see how and where the version appears.\n\n## Step 5 — Identify Unique Context\n\n**Non-minified files**: Check the first 30 lines for block or line comments containing the library name and version:\n```bash\nhead -30 /tmp/retire-{NAME}/{VERSION}.js\n```\nA comment like `/*! Lodash v4.17.21` or `// MyLib JavaScript Library v1.2.3` is the ideal match — the library name provides uniqueness.\n\n**Minified files**: The version is often in a short string assignment. Use grep with context to see what's nearby:\n```bash\ngrep -o \".{0,60}1\\.2\\.3.{0,60}\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\n**Watch out**: some libraries (including newer major versions) ship both `lib.js` and `lib.min.js` as fully minified single-line files. `grep -o` with `.{0,N}` can fail with \"exceeds complexity limits\" on large one-liners. Use Python instead:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,80}1\\.2\\.3.{0,80}', content)[:5]:\n    print(m)\n\"\n```\n\nLook for the library's own name, a unique property (e.g. `lodash.VERSION`), or a characteristic string adjacent to the version. Prefer patterns where the library name appears.\n\n**If the version format changed between major versions** (e.g. 0.x/1.x embedded it in a header comment, 2.x moved it to a code property), write separate filecontent patterns for each era rather than forcing one pattern to cover all versions.\n\n## Step 6 — Write Candidate Regexes (with real version numbers)\n\nWrite candidate regexes using the **actual version number** — escape dots and regex metacharacters in the surrounding context. Do NOT use `§§version§§` yet.\n\nExamples:\n```\nNon-minified comment: /\\*!? Lodash v4\\.17\\.21\nMinified property:    [^a-z]lodash\\.VERSION=\"4\\.17\\.21\"\n```\n\n## Step 7 — Test Regexes Against All Downloaded Files\n\nFor each candidate regex, verify it matches across all versions (substituting each version's actual number):\n```bash\ngrep -oP '/\\*!? Lodash v4\\.17\\.21' /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA good regex matches consistently across all tested versions (with the version number swapped). Refine patterns that fail — look for stable surrounding text that doesn't change between versions.\n\n**Regex quality rules:**\n- Generic patterns like `version=\"1\\.2\\.3\"` or `static version=` are too broad — they will likely appear in unrelated libraries. Always anchor with library-specific identifiers visible in the surrounding context (library name, unique module names, characteristic property names).\n- Examine context on **both sides** of the version string. Prefer identifiers that are unique to that library (e.g. Quill's `delta` and `parchment` imports).\n- Any unbounded quantifier over a character class — `[^,]+`, `[^\"]*`, `.+` — **must** be size-limited: `[^,]{1,20}`, `[^\"]{1,30}`, `.{1,50}`. Unbounded quantifiers risk catastrophic backtracking and are a code-quality red flag.\n\n## Step 8 — Replace Version with `(§§version§§)`\n\nOnly after tests pass, replace the concrete version literal with `(§§version§§)`. This **must be the only capturing group** — convert any other `(...)` to `(?:...)`.\n\n```\n/\\*!? Lodash v4\\.17\\.21   →   /\\*!? Lodash v(§§version§§)\n[^a-z]lodash\\.VERSION=\"4\\.17\\.21\"  →  [^a-z]lodash\\.VERSION=\"(§§version§§)\"\n```\n\nAlso generate filename and uri patterns (adjust if the actual URL/filename structure differs):\n```\nfilename:  lodash-(§§version§§)(?:\\.min)?\\.js\nuri:       /(§§version§§)/lodash(?:\\.min)?\\.js\n```\n\n## Step 9 — Add Entry to jsrepository-master.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/jsrepository-master.json`.\n\nInsert the new entry **immediately before** the `\"dont check\"` key at the end of the file. The `vulnerabilities` field is an empty array — vulnerability details get added separately later.\n\n```json\n\"LIBRARY_NAME\": {\n  \"npmname\": \"npm-package-name\",\n  \"vulnerabilities\": [],\n  \"extractors\": {\n    \"filename\": [\"LIBRARY_NAME-(§§version§§)(?:\\\\.min)?\\\\.js\"],\n    \"filecontent\": [\"REGEX_1\", \"REGEX_2\"],\n    \"uri\": [\"/(§§version§§)/LIBRARY_NAME(?:\\\\.min)?\\\\.js\"]\n  }\n},\n```\nDon't add npmname if the library isn't on npm or if the npm package name is the same as the library name.\n\n## Step 10 — Add Entry to testcases.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/testcases.json`.\n\nAdd the library before the closing `}`. The last existing entry in the file needs a comma after its closing brace. Use cdnjs CDN URLs with `§§version§§` and `§§subversion§§` placeholders. Include the 3–5 tested versions:\n\n```json\n\"LIBRARY_NAME\": {\n  \"https://cdnjs.cloudflare.com/ajax/libs/LIBRARY_NAME/§§version§§/FILENAME§§subversion§§.js\": {\n    \"versions\": [\"VERSION_1\", \"VERSION_2\", \"VERSION_3\"],\n    \"subversions\": [\"\", \".min\"]\n  }\n}\n```\n\nAdjust the URL template if minified/non-minified files differ in path rather than just a `.min` suffix.\n\n## Step 11 — Validate and Test\n\nRun from the repository directory:\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\n./convertToVersioned && node test-detection.js LIBRARY_NAME\n./convertToVersioned && node test-detection.js\n```\n\n**Fixing failures:**\n- `validate` errors: likely means `§§version§§` is not the only capture group, or there's a JSON syntax error — check the regex for extra `(...)` groups\n- `node test-detection.js LIBRARY_NAME` failures: the regex doesn't match the downloaded URLs/files — refine the pattern\n- `node test-detection.js` (no arg) unexpected matches: the regex is too broad — add more library-specific context to narrow it\n\nIterate until all three commands pass cleanly.\n\n## Step 12 — Report Results\n\nShow the final extractors added to the repo and summarize the test results (which versions were detected, which commands passed).\n","frontmatter":{"name":"retire-add-library","description":"Add a new JavaScript library to the retire.js vulnerability database. Given a library name, looks it up on CDNs, downloads multiple versions, finds version strings, generates detection regexes with (§§version§§) placeholder, adds entries to jsrepository-master.json and testcases.json, and runs validate/test-detection. Use this skill whenever adding a new library to retire.js, generating filecontent/filename/uri extractors, or finding version detection patterns for a JS library."},"isInternal":false,"tokens":2358,"sizeBytes":9103},{"name":"CLAUDE.md","path":"CLAUDE.md","rawUrl":"https://raw.githubusercontent.com/RetireJS/retire.js/HEAD/CLAUDE.md","title":"Claude Agent Guidelines & System Prompt","category":"claude-rule","format":"markdown","content":"# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Repository structure\n\nThis is a monorepo with two main parts:\n\n- **`node/`** — The CLI tool and core library (TypeScript, published to npm as `retire`). Source in `node/src/`, compiled output in `node/lib/`.\n- **`repository/`** — The vulnerability database and tooling to manage it.\n\n## Node CLI (node/)\n\n```bash\ncd node\nnpm install\nnpm run build       # compile TypeScript → lib/\nnpm run test        # run the test suite (node/spec/tests/*.spec.ts)\nnpm run check       # lint + typecheck\nnpm run lint        # eslint --fix\nnpm run typecheck   # tsc --noEmit\n```\n\n### Testing\n\nTests use the built-in [`node:test`](https://nodejs.org/api/test.html) runner (`describe`/`it` with\n`node:assert`), not jest.\n\n**The specs import from `../../lib/`, not `../../src/`, so `npm run build` must be run before\n`npm run test`** — otherwise you are testing the previously compiled output and your changes will\nappear to have no effect:\n\n```bash\ncd node\nnpm run build && npm run test\n```\n\nRun a single test file:\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test spec/tests/contentscan.spec.ts\n```\n\nRun a single test by name (matched as a regex against the `it(...)` description):\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test \\\n  --test-name-pattern 'should validate report according to schema' spec/tests/cyclonedx.spec.ts\n```\n\nReporters that emit CycloneDX are validated against the JSON schemas bundled in\n`node/spec/schema/`. When adding support for a new spec version, add its `bom-<version>.schema.json`\nthere too.\n\n## Vulnerability repository (repository/)\n\nThe source of truth is `jsrepository-master.json`. The other `jsrepository*.json` files are derived outputs and must be kept in sync.\n\nAfter editing `jsrepository-master.json`, regenerate all derived files:\n```bash\ncd repository\nnode convertToVersioned\n```\n\nValidate that all derived files are in sync and the format is correct:\n```bash\ncd repository\nnode validate\n```\n\nTest that detection regexes actually match the real library files (downloads from CDN, cached in `repository/tmp/`):\n```bash\ncd repository\nnode test-detection.js\nnode test-detection.js <library-name>   # test a single library\n```\n\n**Before submitting a PR:** always run `validate` and `test-detection.js`.\n\n## Repository format\n\nEntries in `jsrepository-master.json` use `§§version§§` as a placeholder for a version-capturing regex group. Required fields per vulnerability:\n\n- `severity` — align with CVE/GHSA severity when available\n- `cwe` — array of CWE IDs\n- `identifiers` — at least one of: `CVE`, `githubID`, `pr`, `issue`\n\nVersion ranges: omit `atOrAbove` if no lower bound is known; use `\"999.0.0\"` for `below` when no upper bound is known.\n\nDo **not** edit `jsrepository.json` directly — it is generated from master. Do **not** submit to `npmrepository.json` (deprecated).\n\n## Core detection logic (node/src/)\n\n- `retire.ts` — core scanning functions: `scanUri`, `scanFileName`, `scanFileContent`, `check`\n- `scanner.ts` — filesystem walker, orchestrates scanning files and npm packages\n- `deepscan.ts` — AST-based detection for libraries that embed version strings in non-obvious ways\n- `repo.ts` — loads and validates the repository (uses zod schema)\n- `cli.ts` — commander-based CLI entry point\n- `reporters/` — output formatters (console, JSON, CycloneDX SBOM)\n\n## Bumping the version number\n\nWhen bumping the version number of node/package.json, also update the version number in:\n- node/lib/retire.js\n- chrome/extension/manifest.json\n- chrome/extension-no-func/manifest.json\n","isInternal":false,"tokens":930,"sizeBytes":3747}],"systemPromptSnippet":"<yakaai_skills repo=\"RetireJS/retire.js\">\n<!-- File: .claude/skills/retire-add-ast-query/SKILL.md (Tokens: ~3298 | Category: anthropic-skill) -->\n---\nname: retire-add-ast-query\ndescription: Add an AST query for a JavaScript library to the retire.js vulnerability database. Given a library name, downloads multiple versions, analyzes the code structure where the version string is assigned, writes an ASTronomical XPath-like query anchored with library-specific identifiers, tests the query locally, adds it to jsrepository-ast.js, and runs validate/test-detection. Use this skill whenever adding an AST-based version detection query to retire.js.\n---\n\n# retire-add-ast-query\n\nThis skill adds an ASTronomical query for a JavaScript library to the retire.js AST detection system. It downloads real files from CDNs, reads the code structure around version assignments, builds a query anchored to library-specific identifiers, tests it locally with `astronomical`, and validates with `test-detection.js`.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nAST queries detect the library version by traversing the JavaScript AST rather than matching raw text. Queries must be anchored to library-specific identifiers on sibling or parent nodes — otherwise they'll fire on unrelated files that happen to have a `version` assignment. The query returns the version string directly (no `§§version§§` placeholder needed — the Literal node's value is the result).\n\nAll queries live in `repository/jsrepository-ast.js` (a CommonJS module). After editing it, run `./convertToVersioned` to propagate to the versioned JSON files used at runtime.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description\n```\n\nor jsdelivr if not on cdnjs:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified name) and derive the non-minified name (strip `.min`). Record the full list of versions.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across history: 1 early (not the very first), 2–3 middle, and the latest.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-ast-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nVerify each downloaded successfully (non-zero, not an HTML 404 page):\n```bash\nls -la /tmp/retire-ast-{NAME}/\nhead -3 /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIf cdnjs returns 404, fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\n## Step 4 — Find Version Strings\n\nGrep each downloaded file for the exact version number:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nFor single-line minified files where grep context flags fail, use Python:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-ast-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,100}1\\.2\\.3.{0,100}', content)[:5]:\n    print(m)\n\"\n```\n\n## Step 5 — Understand the Code Pattern\n\nRead 10–20 lines around each version occurrence in non-minified files:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-ast-{NAME}/{VERSION}.js\n# Then:\nsed -n '${LINE_MINUS_10},${LINE_PLUS_10}p' /tmp/retire-ast-{NAME}/{VERSION}.js\n```\n\nIdentify two things:\n1. **What node holds the version**: e.g. `x.VERSION = \"1.2.3\"` (AssignmentExpression → right → Literal), `{version: \"1.2.3\"}` (ObjectExpression → Property → value → Literal), `var version = \"1.2.3\"` (VariableDeclarator → init → Literal)\n2. **Library-specific anchors nearby**: an identifier that uniquely identifies this library. The anchor must appear in the same block/expression as the version assignment — or inside a method body of the same object.\n\n**Anchor quality, best to worst:**\n- **Gold**: an identifier that contains the library name — e.g. `tinyMCEPreInit`, `isMoment`, `HandlebarsEnvironment`, `__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These cannot match any other library by definition.\n- **Good**: a highly domain-specific sibling property or method that wouldn't appear together with a version string in any other library — e.g. `migrateVersion`, `focusedEditor` combined with `majorVersion`.\n- **Weak**: generic property names like `releaseDate`, `activeEditor`, `major`, `init`, `build`. These could plausibly appear in other libraries and should only be used in combination with other anchors, never alone.\n\n**Look inside method bodies too.** If the version is in an object literal, scan the body of methods on that same object for library-name identifiers — e.g. `setup() { ... window.tinyMCEPreInit ... }` inside the same ObjectExpression. A `//MemberExpression` or `//Identifier` traversal into a method body provides a much stronger anchor than any generic sibling property name.\n\nAsk yourself: *would this query fire on a file from an unrelated library that happens to assign a version string?* If you're not confident the answer is no, strengthen the anchor.\n\n## Step 6 — Write a Candidate Query\n\nUse ASTronomical syntax to navigate from the root to the version Literal, filtering with library-specific anchors.\n\n### ASTronomical syntax quick reference\n\n| Syntax | Meaning |\n|--------|---------|\n| `//NodeType` | Descendant at any depth |\n| `/NodeType` | Direct child |\n| `/:attr` | Named attribute of current node |\n| `/$:name` | Resolve identifier binding (follow variable reference) |\n| `/$$:name` | Binding or attribute fallback (when value may be variable or literal) |\n| `[cond]` | Filter; use `&&`, `\\|\\|`, `==` |\n| `../ ` | Go to parent (use sparingly — causes extra traversal) |\n| `/fn:concat(sel1, \".\", sel2)` | Concatenate results (for `major.minor.patch`) |\n\n### Common patterns\n\n**Named property assignment in a block with a sibling anchor:**\n```\n//BlockStatement[\n  /ExpressionStatement/AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]\n]/ExpressionStatement/AssignmentExpression[\n  /:left/:property/:name == \"version\"\n]/$$:right/:value\n```\n\n**Named object property with a co-occurring sibling property:**\n```\n//ObjectExpression[\n  /Property/:key/:name == \"ANCHOR_PROP\"\n]/Property[/:key/:name == \"version\"]/:value/:value\n```\n\n**Assignment where the object is named directly:**\n```\n//AssignmentExpression[\n  /:left/:object/:name == \"LibName\" &&\n  /:left/:property/:name == \"VERSION\"\n]/:right/:value\n```\n\n**Two properties sharing the same object binding (stronger anchor):**\n```\n//SequenceExpression[\n  /AssignmentExpression[/:left/:property/:name == \"ANCHOR_PROP\"]/:left/$:object ==\n  /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value\n```\n\n**Object with library-name identifier inside a method body (gold anchor):**\n```\n//ObjectExpression[\n  /Property[/:key/:name == \"majorVersion\"] &&\n  /Property[/:key/:name == \"setup\"]//MemberExpression[/:property/:name == \"libraryNamePreInit\"]\n]/fn:concat(\n  /Property[/:key/:name == \"majorVersion\"]/:value/:value,\n  \".\",\n  /Property[/:key/:name == \"minorVersion\"]/:value/:value\n)\n```\n\nStart simple, add anchors until the query is specific enough. Multiple queries in the array are OR'd — add a second entry for a different version era if the code structure changed across major versions.\n\n**If two candidate queries end up nearly identical, stop and suspect an ASTronomical bug instead of writing both.** \"Nearly identical\" means the same filters/anchors, differing only in the terminal extraction step — e.g. one query ends in `/:value` and works against a plain string `Literal` (`x.version = \"1.2.3\"`), but a real-world build needs a second, otherwise-identical query ending in `/TemplateElement/:value/:cooked` because that build emits the version as a non-interpolated template literal instead (`` x.version = `1.2.3` ``). A `Literal` and a template literal with no `${}` are semantically the same string — the query *engine* should resolve either transparently, not every library's query author. Duplicating the query per literal-style is a local workaround that only fixes the one library, and this exact shape of gap tends to recur (react, react-dom, moment.js, and ua-parser-js all independently hit it in the same session because one bundler happened to prefer backtick strings).\n\nWhen you notice this, don't add the duplicate to `jsrepository-ast.js`. Instead:\n1. Report the pattern to the user and propose fixing it in ASTronomical itself, at `/Users/erlend/code/github/RetireJS/ASTronomical` (a sibling checkout — confirm it exists before assuming the path).\n2. The fix belongs in `src/index.ts`'s attribute-resolution helpers (`getPrimitiveChildren` / `getPrimitiveChildrenOrNodePaths`) — add a fallback so a requested attribute (e.g. `value`) transparently resolves through the alternate AST shape, instead of requiring the query to spell out both shapes.\n3. Validate with the engine's own suite (`npm run typecheck && npm test` in the ASTronomical repo) before touching retire.js again, and confirm the *original* (single, non-duplicated) query now matches both shapes.\n4. Bumping ASTronomical's version, updating its `CHANGELOG.md`, publishing to npm, and bumping the dependency in `node/package.json` are all separate, more consequential steps (a published package affects every consumer, not just this repo) — check with the user before doing any of them rather than assuming the fix should ship immediately.\n\n## Step 7 — Test the Query Locally\n\nCreate a test script:\n```javascript\n// /tmp/test-ast-query.js\nconst { multiQuery } = require('/Users/erlend/code/github/RetireJS/retire.js/node/node_modules/astronomical');\nconst fs = require('fs');\nconst code = fs.readFileSync(process.argv[2], 'utf8');\nconst query = `YOUR QUERY HERE`;\nconst results = multiQuery(code, { version: query });\nconsole.log(JSON.stringify(results.version, null, 2));\n```\n\nRun against each downloaded version:\n```bash\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.js\nnode /tmp/test-ast-query.js /tmp/retire-ast-{NAME}/{VERSION}.min.js\n```\n\nExpected output: a JSON array containing exactly the version string, e.g. `[\"1.2.3\"]`. If the output is `[]`, the query didn't match — revise it. If it returns multiple values, the anchor is too loose.\n\nIterate across all tested versions until each returns the correct version string. If an early version has a structurally different pattern, write a second query string.\n\n## Step 8 — Add to jsrepository-ast.js\n\nEdit `repository/jsrepository-ast.js`. Add the library key to `exports.queries` (in alphabetical order is nice but not required):\n\n```javascript\n\"library-name\": [\n  /*\n  Code pattern this query matches, e.g.:\n    LibName.isMoment = function; LibName.version = \"1.2.3\"\n  */\n  `//SequenceExpression[\n    /AssignmentExpression[/:left/:property/:name == \"isMoment\"]/:left/$:object ==\n    /AssignmentExpression[/:left/:property/:name == \"version\"]/:left/$:object\n  ]/AssignmentExpression[/:left/:property/:name == \"version\"]/$$:right/:value`,\n],\n```\n\nIf the library already has an entry (added by someone else), append to its array rather than replacing it.\n\n## Step 9 — Update testcases.json if needed\n\nIf the library is already in `repository/testcases.json`, check if any of the tested versions have a different code structure that the query can't reach. Add `allowAstMiss` for those versions:\n\n```json\n\"library-name\": {\n  \"https://cdnjs.cloudflare.com/.../§§version§§/...§§subversion§§.js\": {\n    \"versions\": [\"1.0.0\", \"2.0.0\", \"3.0.0\"],\n    \"subversions\": [\"\", \".min\"],\n    \"allowAstMiss\": [\"1.0.0\"]\n  }\n}\n```\n\nIf the library is **not yet** in `testcases.json`, add a minimal entry covering the 3–5 tested versions. Without a `testcases.json` entry, `test-detection.js` won't exercise the AST query.\n\n## Step 10 — Validate and Test\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\nnode test-detection.js {LIBRARY_NAME}\nnode test-detection.js          # full suite — catch any false positives introduced\n```\n\n**Fixing failures:**\n- `validate` error: check `jsrepository-ast.js` syntax — it's a JS module, not JSON; look for unclosed template literals or missing commas\n- `Did not detect ... using ast`: query doesn't match that version — refine the query (back to Step 7) or add `allowAstMiss` for that version. If the fix you're reaching for is \"add a near-duplicate query that only changes how the final value is extracted,\" stop — see the callout in Step 6 about suspecting an ASTronomical engine gap instead.\n- `Detect multiple components using ast`: query is too broad — strengthen the anchor (back to Step 6)\n\n## Step 11 — Report Results\n\nShow:\n- The final query string(s) added to `jsrepository-ast.js`\n- Which versions were successfully detected via AST\n- The timing output from `test-detection.js` (C: content ms, B: AST ms per version)\n\n<!-- File: .claude/skills/retire-add-library/SKILL.md (Tokens: ~2358 | Category: anthropic-skill) -->\n---\nname: retire-add-library\ndescription: Add a new JavaScript library to the retire.js vulnerability database. Given a library name, looks it up on CDNs, downloads multiple versions, finds version strings, generates detection regexes with (§§version§§) placeholder, adds entries to jsrepository-master.json and testcases.json, and runs validate/test-detection. Use this skill whenever adding a new library to retire.js, generating filecontent/filename/uri extractors, or finding version detection patterns for a JS library.\n---\n\n# retire-add-library\n\nThis skill adds a new JavaScript library to the retire.js vulnerability database. It downloads real files from CDNs, finds where version strings appear, builds and tests regex patterns against those files, and writes validated entries to the repository.\n\nThe repo is at `/Users/erlend/code/github/RetireJS/retire.js`. All file edits and validation commands run there.\n\n## Overview\n\nThe key constraint: `(§§version§§)` is retire.js-specific syntax and won't match against real files. So you must **test regexes using the actual version number first**, then replace the version with `(§§version§§)` only after tests pass.\n\n## Step 1 — CDN Lookup\n\nFetch library metadata from cdnjs:\n\n```\nhttps://api.cdnjs.com/libraries/{NAME}?fields=name,versions,filename,description,homepage\n```\nor \n```\nhttps://cdnjs.com/libraries/{NAME}\n```\n\nThis returns all available versions and the canonical filename (e.g. `lodash.min.js`). If the library isn't on cdnjs, try jsdelivr:\n\n```\nhttps://data.jsdelivr.com/v1/packages/npm/{NAME}\n```\n\nNote the `filename` field (minified) and derive the non-minified name by stripping `.min` (e.g. `lodash.min.js` → `lodash.js`). If cdnjs doesn't have a `.min` in the filename, treat that as the non-minified version and look for a `.min.js` variant.\n\n## Step 2 — Select Versions\n\nPick 4–5 versions spread across the history: 1 early (but not the first), 2–3 in the middle, and the latest. You'll download both minified and non-minified for each.\n\n## Step 3 — Download Files\n\n```bash\nmkdir -p /tmp/retire-{NAME}\n# For each VERSION:\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{MIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.min.js\ncurl -sL \"https://cdnjs.cloudflare.com/ajax/libs/{NAME}/{VERSION}/{UNMIN_FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\n\nVerify each file downloaded successfully (non-zero size, not an HTML error page):\n```bash\nls -la /tmp/retire-{NAME}/\nhead -3 /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA suspicious file size (e.g. ~146 bytes) almost always means a 404 HTML response — check with `head`. If cdnjs returns 404 for a version (even one listed in its metadata), fall back to jsDelivr:\n```bash\ncurl -sL \"https://cdn.jsdelivr.net/npm/{NAME}@{VERSION}/dist/{FILENAME}\" \\\n  -o /tmp/retire-{NAME}/{VERSION}.js\n```\nOnly include versions in testcases.json that successfully downloaded from the CDN URL used in the test template.\n\n## Step 4 — Find Version Strings\n\nSearch each file for the exact version string:\n```bash\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.js\ngrep -n \"1\\.2\\.3\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\nDo this for all downloaded versions to see how and where the version appears.\n\n## Step 5 — Identify Unique Context\n\n**Non-minified files**: Check the first 30 lines for block or line comments containing the library name and version:\n```bash\nhead -30 /tmp/retire-{NAME}/{VERSION}.js\n```\nA comment like `/*! Lodash v4.17.21` or `// MyLib JavaScript Library v1.2.3` is the ideal match — the library name provides uniqueness.\n\n**Minified files**: The version is often in a short string assignment. Use grep with context to see what's nearby:\n```bash\ngrep -o \".{0,60}1\\.2\\.3.{0,60}\" /tmp/retire-{NAME}/{VERSION}.min.js\n```\n\n**Watch out**: some libraries (including newer major versions) ship both `lib.js` and `lib.min.js` as fully minified single-line files. `grep -o` with `.{0,N}` can fail with \"exceeds complexity limits\" on large one-liners. Use Python instead:\n```bash\npython3 -c \"\nimport re\nwith open('/tmp/retire-{NAME}/{VERSION}.min.js', 'r', errors='replace') as f:\n    content = f.read()\nfor m in re.findall(r'.{0,80}1\\.2\\.3.{0,80}', content)[:5]:\n    print(m)\n\"\n```\n\nLook for the library's own name, a unique property (e.g. `lodash.VERSION`), or a characteristic string adjacent to the version. Prefer patterns where the library name appears.\n\n**If the version format changed between major versions** (e.g. 0.x/1.x embedded it in a header comment, 2.x moved it to a code property), write separate filecontent patterns for each era rather than forcing one pattern to cover all versions.\n\n## Step 6 — Write Candidate Regexes (with real version numbers)\n\nWrite candidate regexes using the **actual version number** — escape dots and regex metacharacters in the surrounding context. Do NOT use `§§version§§` yet.\n\nExamples:\n```\nNon-minified comment: /\\*!? Lodash v4\\.17\\.21\nMinified property:    [^a-z]lodash\\.VERSION=\"4\\.17\\.21\"\n```\n\n## Step 7 — Test Regexes Against All Downloaded Files\n\nFor each candidate regex, verify it matches across all versions (substituting each version's actual number):\n```bash\ngrep -oP '/\\*!? Lodash v4\\.17\\.21' /tmp/retire-{NAME}/{VERSION}.js\n```\n\nA good regex matches consistently across all tested versions (with the version number swapped). Refine patterns that fail — look for stable surrounding text that doesn't change between versions.\n\n**Regex quality rules:**\n- Generic patterns like `version=\"1\\.2\\.3\"` or `static version=` are too broad — they will likely appear in unrelated libraries. Always anchor with library-specific identifiers visible in the surrounding context (library name, unique module names, characteristic property names).\n- Examine context on **both sides** of the version string. Prefer identifiers that are unique to that library (e.g. Quill's `delta` and `parchment` imports).\n- Any unbounded quantifier over a character class — `[^,]+`, `[^\"]*`, `.+` — **must** be size-limited: `[^,]{1,20}`, `[^\"]{1,30}`, `.{1,50}`. Unbounded quantifiers risk catastrophic backtracking and are a code-quality red flag.\n\n## Step 8 — Replace Version with `(§§version§§)`\n\nOnly after tests pass, replace the concrete version literal with `(§§version§§)`. This **must be the only capturing group** — convert any other `(...)` to `(?:...)`.\n\n```\n/\\*!? Lodash v4\\.17\\.21   →   /\\*!? Lodash v(§§version§§)\n[^a-z]lodash\\.VERSION=\"4\\.17\\.21\"  →  [^a-z]lodash\\.VERSION=\"(§§version§§)\"\n```\n\nAlso generate filename and uri patterns (adjust if the actual URL/filename structure differs):\n```\nfilename:  lodash-(§§version§§)(?:\\.min)?\\.js\nuri:       /(§§version§§)/lodash(?:\\.min)?\\.js\n```\n\n## Step 9 — Add Entry to jsrepository-master.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/jsrepository-master.json`.\n\nInsert the new entry **immediately before** the `\"dont check\"` key at the end of the file. The `vulnerabilities` field is an empty array — vulnerability details get added separately later.\n\n```json\n\"LIBRARY_NAME\": {\n  \"npmname\": \"npm-package-name\",\n  \"vulnerabilities\": [],\n  \"extractors\": {\n    \"filename\": [\"LIBRARY_NAME-(§§version§§)(?:\\\\.min)?\\\\.js\"],\n    \"filecontent\": [\"REGEX_1\", \"REGEX_2\"],\n    \"uri\": [\"/(§§version§§)/LIBRARY_NAME(?:\\\\.min)?\\\\.js\"]\n  }\n},\n```\nDon't add npmname if the library isn't on npm or if the npm package name is the same as the library name.\n\n## Step 10 — Add Entry to testcases.json\n\nEdit `/Users/erlend/code/github/RetireJS/retire.js/repository/testcases.json`.\n\nAdd the library before the closing `}`. The last existing entry in the file needs a comma after its closing brace. Use cdnjs CDN URLs with `§§version§§` and `§§subversion§§` placeholders. Include the 3–5 tested versions:\n\n```json\n\"LIBRARY_NAME\": {\n  \"https://cdnjs.cloudflare.com/ajax/libs/LIBRARY_NAME/§§version§§/FILENAME§§subversion§§.js\": {\n    \"versions\": [\"VERSION_1\", \"VERSION_2\", \"VERSION_3\"],\n    \"subversions\": [\"\", \".min\"]\n  }\n}\n```\n\nAdjust the URL template if minified/non-minified files differ in path rather than just a `.min` suffix.\n\n## Step 11 — Validate and Test\n\nRun from the repository directory:\n\n```bash\ncd /Users/erlend/code/github/RetireJS/retire.js/repository\n./convertToVersioned && ./validate\n./convertToVersioned && node test-detection.js LIBRARY_NAME\n./convertToVersioned && node test-detection.js\n```\n\n**Fixing failures:**\n- `validate` errors: likely means `§§version§§` is not the only capture group, or there's a JSON syntax error — check the regex for extra `(...)` groups\n- `node test-detection.js LIBRARY_NAME` failures: the regex doesn't match the downloaded URLs/files — refine the pattern\n- `node test-detection.js` (no arg) unexpected matches: the regex is too broad — add more library-specific context to narrow it\n\nIterate until all three commands pass cleanly.\n\n## Step 12 — Report Results\n\nShow the final extractors added to the repo and summarize the test results (which versions were detected, which commands passed).\n\n<!-- File: CLAUDE.md (Tokens: ~930 | Category: claude-rule) -->\n# CLAUDE.md\n\nThis file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.\n\n## Repository structure\n\nThis is a monorepo with two main parts:\n\n- **`node/`** — The CLI tool and core library (TypeScript, published to npm as `retire`). Source in `node/src/`, compiled output in `node/lib/`.\n- **`repository/`** — The vulnerability database and tooling to manage it.\n\n## Node CLI (node/)\n\n```bash\ncd node\nnpm install\nnpm run build       # compile TypeScript → lib/\nnpm run test        # run the test suite (node/spec/tests/*.spec.ts)\nnpm run check       # lint + typecheck\nnpm run lint        # eslint --fix\nnpm run typecheck   # tsc --noEmit\n```\n\n### Testing\n\nTests use the built-in [`node:test`](https://nodejs.org/api/test.html) runner (`describe`/`it` with\n`node:assert`), not jest.\n\n**The specs import from `../../lib/`, not `../../src/`, so `npm run build` must be run before\n`npm run test`** — otherwise you are testing the previously compiled output and your changes will\nappear to have no effect:\n\n```bash\ncd node\nnpm run build && npm run test\n```\n\nRun a single test file:\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test spec/tests/contentscan.spec.ts\n```\n\nRun a single test by name (matched as a regex against the `it(...)` description):\n```bash\ncd node\nTS_NODE_PROJECT=tsconfig.spec.json node --require ts-node/register --test \\\n  --test-name-pattern 'should validate report according to schema' spec/tests/cyclonedx.spec.ts\n```\n\nReporters that emit CycloneDX are validated against the JSON schemas bundled in\n`node/spec/schema/`. When adding support for a new spec version, add its `bom-<version>.schema.json`\nthere too.\n\n## Vulnerability repository (repository/)\n\nThe source of truth is `jsrepository-master.json`. The other `jsrepository*.json` files are derived outputs and must be kept in sync.\n\nAfter editing `jsrepository-master.json`, regenerate all derived files:\n```bash\ncd repository\nnode convertToVersioned\n```\n\nValidate that all derived files are in sync and the format is correct:\n```bash\ncd repository\nnode validate\n```\n\nTest that detection regexes actually match the real library files (downloads from CDN, cached in `repository/tmp/`):\n```bash\ncd repository\nnode test-detection.js\nnode test-detection.js <library-name>   # test a single library\n```\n\n**Before submitting a PR:** always run `validate` and `test-detection.js`.\n\n## Repository format\n\nEntries in `jsrepository-master.json` use `§§version§§` as a placeholder for a version-capturing regex group. Required fields per vulnerability:\n\n- `severity` — align with CVE/GHSA severity when available\n- `cwe` — array of CWE IDs\n- `identifiers` — at least one of: `CVE`, `githubID`, `pr`, `issue`\n\nVersion ranges: omit `atOrAbove` if no lower bound is known; use `\"999.0.0\"` for `below` when no upper bound is known.\n\nDo **not** edit `jsrepository.json` directly — it is generated from master. Do **not** submit to `npmrepository.json` (deprecated).\n\n## Core detection logic (node/src/)\n\n- `retire.ts` — core scanning functions: `scanUri`, `scanFileName`, `scanFileContent`, `check`\n- `scanner.ts` — filesystem walker, orchestrates scanning files and npm packages\n- `deepscan.ts` — AST-based detection for libraries that embed version strings in non-obvious ways\n- `repo.ts` — loads and validates the repository (uses zod schema)\n- `cli.ts` — commander-based CLI entry point\n- `reporters/` — output formatters (console, JSON, CycloneDX SBOM)\n\n## Bumping the version number\n\nWhen bumping the version number of node/package.json, also update the version number in:\n- node/lib/retire.js\n- chrome/extension/manifest.json\n- chrome/extension-no-func/manifest.json\n</yakaai_skills>"}