Skip to content

Implement Bun lockfile parser, enhance scanner output, and update dependencies#24

Open
miccy wants to merge 47 commits intomainfrom
dev/preview-v2
Open

Implement Bun lockfile parser, enhance scanner output, and update dependencies#24
miccy wants to merge 47 commits intomainfrom
dev/preview-v2

Conversation

@miccy
Copy link
Copy Markdown
Owner

@miccy miccy commented May 4, 2026

This pull request introduces a major update across the codebase, focused on the 2.0.0 release of the project (now called wormsCTRL). The changes include a comprehensive rewrite of documentation, a new changelog, a new CLI packaging and build process, expanded threat catalog and scanning capabilities, and numerous bug fixes and security improvements. The update also introduces AI-assisted threat ingestion and validation, improved test coverage, and several developer experience enhancements.

Key highlights:

  • Major version bump to 2.0.0 with breaking changes to threat schema and parser outputs.
  • New AI ingestion pipeline for threat intelligence.
  • Improved CLI packaging with bundled threat catalog and build script.
  • Expanded and validated threat database.
  • Numerous bug fixes, security enhancements, and documentation updates.

Breaking Changes & Threat Schema Improvements

  • Enforced stricter threat schema validation: sha256 fields must be valid 64-character hex or null, and IOC/remediation arrays reject empty strings. Parser outputs for pnpm now separate resolved and integrity fields.
  • Version bump from 1.5.2 to 2.0.0, with full details and migration notes in the new CHANGELOG.md.

CLI Packaging & Build Enhancements

  • Added a new build script (apps/cli/build.mjs) to bundle the CLI and threat catalog for standalone distribution, and updated package.json to include the new build process and output files. [1] [2]
  • Updated TypeScript config to include Node types for better compatibility.

Documentation & Branding

  • Rebranded the project to wormsCTRL throughout all documentation and metadata, including repository links in AGENTS.md and SECURITY.md. [1] [2] [3]
  • Completely rewrote the root README.md with new architecture diagrams, quick start, threat DB overview, AI ingestion explanation, and grant context.

Threat Catalog & AI Ingestion

  • Expanded the threat catalog with new structured threat objects and fixture data for real-world incidents, and added validation and dynamic test discovery for threat files.
  • Introduced an AI ingestion skeleton (packages/engine/src/ingest.ts and related files) for extracting structured threat objects from advisories using OpenAI and Zod validation.

Bug Fixes, Security, and DX Improvements

  • Implemented SSRF protection, path traversal security, and centralized sensitive data redaction in reports.
  • Improved parser resilience, CLI error handling, E2E test robustness, and Playwright configuration for better CI debugging and test coverage. [1] [2] [3] [4] [5] [6] [7] [8]

These changes collectively deliver a more robust, secure, and user-friendly supply chain audit tool, with a focus on automation, AI integration, and real-world threat coverage.

Summary by CodeRabbit

  • New Features

    • AI-powered advisory ingestion and validation; expanded threat database with many incident records
    • CLI scan/threats commands with JSON/SARIF/text output and improved bundle of CLI tooling
  • Bug Fixes

    • Stronger path validation and SSRF protections; more resilient lockfile parsing across formats
    • More accurate detection of malicious/phantom dependencies and severity handling
  • Documentation

    • Rebranded to wormsCTRL; refreshed README, examples, and Czech docs; updated security reporting links
  • Tests

    • Expanded unit, integration, and E2E coverage for ingestion, validation, parsers, and CLI behavior

miccy and others added 30 commits April 21, 2026 17:15
…ng, and refactor scanner output to string rendering
…egexp'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Updated the search component layout to use CSS Grid, allowing the cancel
button to have a flexible width based on its content. This accommodates
different word lengths in localized translations.

- Replaced absolute positioning with CSS Grid on .dialog-frame.
- Used display: contents for intermediate search containers to allow
  nested elements to participate in the grid.
- Removed hardcoded --sl-search-cancel-space and width calculations.
- Added a Playwright test to verify layout flexibility.
Removed debug console.log statements from apps/docs/test-loader.ts.
Since the file only contained these logs and their supporting boilerplate, and it was not referenced anywhere in the codebase, the entire file was deleted to improve code health.
STIX 2.1 bundle IDs should be unique and ideally non-predictable.
Replacing Date.now() with crypto.randomUUID() ensures uniqueness and
adheres to security best practices for identifier generation.
Removed the unused underscore from the _dryRun parameter and
implemented conditional logging to respect the dryRun flag.
- Added validatePath utility to prevent null byte injection and empty paths
- Applied validation at all scanner entry points: scan(), findLockfiles(),
  parseNpmLockfile(), parseYarnLockfile(), detectInjection()
- Added security tests for path validation
BREAKING CHANGES:
- sha256 field in file_artifacts now requires 64-char hex or null
- pnpm parser separates resolved and integrity fields
- IOC/remediation arrays reject empty strings

Security:
- SSRF protection in ingest.ts (private URL blocking, timeouts, size limits)
- Path traversal validation in scanner (null byte, empty path)

Fixed:
- Bun/pnpm alias descriptor parsing (@npm: patterns)
- Bun lockfile deduplication (prefer bun.lock over bun.lockb)
- Per-entry threat catalog loading (one bad file no longer drops all)
- Threat shape validation before indexing
- readdirSync TOCTOU race in threat loader
- toFindingSeverity default case
- CLI writeFileSync error handling
- npm parser: skip root/link entries in v3 lockfiles
- Node ESM: replaced bare JSON imports with fs.readFileSync
- Empty sha256 indicators replaced with null
- Removed redundant parseNpmLock wrapper
- Dynamic test discovery for threat catalog validation
- Playwright config: pipe stdout/stderr for CI debugging
Dependencies updated:
- ora: 9.3.0 → 9.4.0
- @astrojs/check: 0.9.8 → 0.9.9
- @astrojs/starlight: 0.38.3 → 0.38.4
- astro: 6.1.8 → 6.2.1
- marked: 18.0.2 → 18.0.3
- vite: 7.0.0 → 8.0.10 (major)
- zod: 4.3.6 → 4.4.2
- @biomejs/biome: 2.4.12 → 2.4.14
- turbo: 2.9.6 → 2.9.8
- @types/bun: 1.3.12 → 1.3.13 (all workspaces)

Renamed:
- Replaced all 'dont-be-shy-hulud' references with 'wormsCTRL' across
  37 files (docs, scripts, configs, AGENTS.md, SECURITY.md)
- Fixed package.json repository URLs to match GitHub repo name
- Fixed curl/script URLs in installation.md and quickstart.md to use
  correct path packages/scripts/detect.sh (not scripts/detect.sh)
- Expanded isPrivateUrl loopback check from exact 127.0.0.1 to full
  127.0.0.0/8 range via regex
- Added TODO for IPv6 private range coverage (fc00::/7, fe80::/10)

Rejected CodeRabbit findings (5 of 8):
- dont-be-shy-hulud rename suggestions: that is the OLD name,
  wormsCTRL is the current GitHub repo
- worms-ctrl npm name: correct published name per package.json bin
- event-stream sha256 null: original values were empty strings,
  null correctly represents unknown hashes per updated schema
miccy and others added 3 commits May 4, 2026 06:58
…r robustness across parsers and threat validation
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 93 out of 101 changed files in this pull request and generated 11 comments.

Files not reviewed (3)
  • tests/fixtures/axios-compromise/package-lock.json: Language not supported
  • tests/fixtures/clean-baseline/package-lock.json: Language not supported
  • tests/fixtures/shai-hulud-worm/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +175
parsedPackages.push({
name,
version,
lockfileVersion: 0,
})
Comment on lines 201 to 205
if (!existsSync(WIKI_DIR)) {
console.error(`❌ Wiki directory not found: ${WIKI_DIR}`)
console.log(' Clone the wiki repo first:')
console.log(' git clone https://github.com/miccy/dont-be-shy-hulud.wiki.git')
console.log(' git clone https://github.com/miccy/wormsCTRL.wiki.git')
process.exit(1)
Comment on lines +19 to +25
export function parseLockfiles(targetDir: string): LockfilePackage[] {
return [
...parseNpmLockfile(targetDir),
...parseYarnLockfile(targetDir),
...parsePnpmLockfile(targetDir),
...parseBunLockfile(targetDir),
]
Comment on lines +89 to 92
export function formatSarif(result: ScanResult): SarifReport {
const rules = new Map<string, SarifRule>()

for (const finding of result.findings) {
Comment on lines +1 to +6
{
"type": "bundle",
"id": "bundle--3d0c41df-9d58-45da-9b59-4d8727284f18",
"spec_version": "2.1",
"objects": [
{
Comment thread examples/axios-compromise.sarif Outdated
Comment on lines +59 to +64
function isValidThreatShape(obj: unknown): obj is ThreatObject {
if (!obj || typeof obj !== 'object') return false
const candidate = obj as Record<string, unknown>
if (typeof candidate.id !== 'string') return false
if (!['CRITICAL', 'HIGH', 'MODERATE', 'LOW'].includes(candidate.severity as string)) return false
if (typeof candidate.attack_vector !== 'string') return false
Comment on lines 186 to 189
function generateFooter(): string {
return `---
📖 [Documentation](https://hulud.dev) | 🐙 [GitHub](https://github.com/miccy/dont-be-shy-hulud) | 🪱 v1.5.1
📖 [Documentation](https://hulud.dev) | 🐙 [GitHub](https://github.com/miccy/wormsCTRL) | 🪱 v1.5.1
`
Comment on lines 10 to 13
export function detect(targetDir: string): Finding[] {
const findings: Finding[] = []
const lockPkgs = parseNpmLockfile(targetDir)
const lockPkgs = parseLockfiles(targetDir)
findings.push(...detectInjection(lockPkgs, targetDir))
Comment on lines +16 to +22
if (dryRun) {
console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
} else {
console.log(`[safe-suspend] Freezing processes matching: ${sig}`)
// TODO/FIXME: implement the actual suspension using child_process (pgrep + kill -STOP)
throw new Error('suspension not implemented')
}
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

🧹 Nitpick comments (1)
packages/scanner/src/output/sarif.ts (1)

63-73: ⚡ Quick win

Prefer exhaustive Finding['type'] mapping over string normalization fallback.

Line 72–73 adds runtime normalization, but Finding.type is already a strict kebab-case union. Using Record<Finding['type'], string> gives compile-time coverage and avoids silently returning WCTRL/scan/unknown when a new finding type is added but not mapped.

Suggested diff
 function ruleId(finding: Finding): string {
-  const map: Record<string, string> = {
+  const map: Record<Finding['type'], string> = {
     injection: 'WCTRL/scan/injected-package',
     'hash-mismatch': 'WCTRL/scan/hash-mismatch',
     doppelganger: 'WCTRL/scan/doppelganger',
     'malicious-package': 'WCTRL/scan/malicious-package',
     'suspicious-script': 'WCTRL/scan/suspicious-script',
   }
-
-  // Normalize finding.type from camelCase to kebab-case if necessary
-  const normalizedType = finding.type.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
-  return map[normalizedType] ?? map[finding.type] ?? 'WCTRL/scan/unknown'
+  return map[finding.type]
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scanner/src/output/sarif.ts` around lines 63 - 73, Replace the
runtime normalization and fallback with an exhaustive compile-time mapping:
change the map variable to have type Record<Finding['type'], string> (using the
Finding type union) and remove normalizedType logic; perform a direct lookup
using map[finding.type] so TypeScript will error if a new Finding type is added
but not mapped (optionally, if you need a runtime guard, use an explicit
exhaustive switch or a type assertion with a clear error message rather than
silently returning 'WCTRL/scan/unknown').
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@cs/README.md`:
- Around line 34-40: Update the README examples to clarify command behavior:
state that the scan subcommand's --threats flag only logs the number of loaded
threat objects during scan (i.e., a quick count/diagnostic) and does not output
the full catalog, and explicitly document that the full threat catalog is
produced by the separate top-level command npx worms-ctrl threats; adjust the
example block under the optional formats/examples so one line shows npx
worms-ctrl scan . --threats (with a parenthetical like “prints loaded threat
count only”) and another line shows npx worms-ctrl threats (with a parenthetical
like “prints full threat catalog”) so users can see the difference between the
--threats flag and the threats command.

In `@package.json`:
- Around line 65-67: The package manifest currently pins Vite 8 via the
"overrides" entry ("vite": "8.0.10") but the root "engines" field still allows
Node >=18, which is below Vite 8's minimum; either update the root "engines"
Node constraint to ">=20.19" (or ">=22.12" if you prefer to require Node 22) to
match Vite 8, or remove/move the "overrides" entry into the specific workspace
package.json that actually depends on Vite 8 so the root manifest doesn't force
a higher Node requirement for all contributors.

In `@packages/engine/src/types.ts`:
- Around line 3-13: The type unions (ThreatEcosystem, ThreatSeverity,
ThreatStatus) currently include values (e.g., "maven", "nuget", "gem", "LOW",
"UNDER_REVIEW") that packages/engine/src/validate.ts rejects for ThreatObject;
fix by aligning types with runtime validation: either narrow
ThreatEcosystem/ThreatSeverity/ThreatStatus to only the values accepted by
validate.ts or change validate.ts to accept the broader set; implement the
preferred approach by splitting profile-level unions from object-level unions
(create separate types like ThreatProfileEcosystem vs ThreatObjectEcosystem or
adjust ThreatObject's field types) and update references to ThreatObject,
ThreatEcosystem, ThreatSeverity, and ThreatStatus accordingly so compile-time
types match runtime validation.

In `@packages/ioc/index.js`:
- Around line 55-57: Parsed JSON is currently pushed directly into threats which
allows arrays/primitives or objects missing required fields; replace the direct
push with parsing into a local variable (e.g., const parsed =
JSON.parse(content)), then validate that parsed is a non-null plain object
(typeof === 'object' && parsed !== null && !Array.isArray(parsed>) and contains
required properties 'id' and 'status' before calling threats.push(parsed); if
validation fails, skip pushing and emit a warning/error log referencing
THREATS_DIR and entry so invalid files are ignored.

In `@packages/remediation/src/scripts/safe-suspend.ts`:
- Around line 11-22: The non-dry-run path in safeSuspend is broken because it
throws inside the loop on first signature, so running safeSuspend() (dryRun
default false) never completes; update safeSuspend to either implement actual
suspension logic (use child_process to pgrep by signature and send SIGSTOP via
kill for each PID) or, until implementation exists, fail fast before the loop
when dryRun === false by throwing a clear "non-dry-run unsupported" error and
avoid emitting "Freezing…" logs; refer to the safeSuspend function and
MALICIOUS_SIGNATURES constant when making this change so logs and control flow
are corrected.

In `@packages/scanner/src/parsers/bun.ts`:
- Around line 9-15: The fallback resolution builds paths incorrectly when given
a file path (e.g., "/repo/bun.lock" or "/repo/bun.lockb") causing joins like
"/repo/bun.lock/bun.lockb" instead of checking the sibling file; update
resolveBunLockTextPath and the corresponding binary resolver (e.g.,
resolveBunLockBinaryPath) to: if basename(targetOrPath) matches the lock
filename check existsSync(targetOrPath) as now, else treat it as a directory and
check join(targetOrPath, '<name>'); additionally, if the input is a file path
for one lock variant, also check the sibling in the same directory by using
dirname(targetOrPath) + join(dirname, '<otherLockName>') so "/repo/bun.lock"
will correctly discover "/repo/bun.lockb"; use basename, dirname, join and
existsSync to implement these checks.

In `@packages/scanner/src/parsers/js-yaml.d.ts`:
- Around line 10-13: Update the ambient module declaration to match js-yaml v4:
remove the deprecated/incorrect safeLoad (and safeLoadAll if present) exports
and delete the deprecation comment on load so that export function load(...) is
the safe default; locate the declarations for load and safeLoad in the module
(symbols "load" and "safeLoad") and remove the safeLoad export and its mention,
leaving load(...) typed normally without the deprecation comment.

In `@packages/scanner/src/parsers/pnpm.ts`:
- Around line 123-136: The parser currently reads dependency edges from
entry.dependencies inside the packages loop (in the block using
parsePnpmPackageKey and pushing into parsedPackages), but for pnpm v9 dependency
relations live in lock.snapshots; update the logic that sets the requires field
so it prefers snapshot dependencies when available: if lock.snapshots exists,
look up the corresponding snapshot by the same package identifier (use the
parsed key or package id derived in parsePnpmPackageKey) and use
snapshot.dependencies, falling back to entry.dependencies when no snapshot is
found; ensure this change is applied where requires is assigned in the
parsedPackages.push so transitive dependency data is preserved for v9+
lockfiles.
- Around line 42-84: The npm alias handling for '@npm:' (the logic using
npmAliasIdx, aliasName and realPart) must be run before the scoped branch (if
(normalized.startsWith('@')) ) and the slash branch (if
(normalized.includes('/')) ) because entries like '@scope/alias@npm:pkg@1.2.3'
are currently parsed by those branches and produce incorrect name/version; move
the block that computes npmAliasIdx, aliasName, realPart and version to just
after you obtain normalized (i.e., before any checks for startsWith('@') or
includes('/')) so that when normalized.indexOf('@npm:') > 0 you return the alias
result immediately and skip the scoped/slash parsing. Ensure you keep the same
name/version extraction logic (realPart.lastIndexOf('@') and split('_')[0]) when
relocating the code.

In `@README.md`:
- Around line 34-40: The README mistakenly suggests `--threats` is a catalog
command; update documentation to show the separate catalog subcommand and
clarify the flag's behavior: add an explicit example line with `npx worms-ctrl
threats` for catalog output, and change the existing example or note to state
that `npx worms-ctrl scan ... --threats` only reports loaded threat count during
a scan (as implemented in packages/scanner/src/cli.ts, where the scan command
handles the --threats flag). Ensure the README mentions both usages (the
`threats` subcommand for catalog listing and the `--threats` scan flag for
count) so readers aren’t misled.

---

Nitpick comments:
In `@packages/scanner/src/output/sarif.ts`:
- Around line 63-73: Replace the runtime normalization and fallback with an
exhaustive compile-time mapping: change the map variable to have type
Record<Finding['type'], string> (using the Finding type union) and remove
normalizedType logic; perform a direct lookup using map[finding.type] so
TypeScript will error if a new Finding type is added but not mapped (optionally,
if you need a runtime guard, use an explicit exhaustive switch or a type
assertion with a clear error message rather than silently returning
'WCTRL/scan/unknown').
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3ca1bd55-0965-48ac-bf37-93ae16fa6d6b

📥 Commits

Reviewing files that changed from the base of the PR and between 2d41097 and d59ae23.

📒 Files selected for processing (58)
  • .gitignore
  • AGENTS.md
  • CHANGELOG.md
  • README.md
  • SECURITY.md
  • apps/docs/playwright.config.ts
  • apps/docs/tests/docs.e2e.ts
  • apps/docs/tests/verify-search.spec.ts
  • cs/README.md
  • examples/axios-compromise.sarif
  • package.json
  • packages/configs/bunfig-secure.toml
  • packages/configs/pnpm-workspace-secure.yaml
  • packages/configs/renovate-defense.json
  • packages/docs-content/cs/getting-started/installation.md
  • packages/docs-content/cs/index.mdx
  • packages/docs-content/cs/meta/AGENTS.md
  • packages/docs-content/cs/meta/README.md
  • packages/docs-content/cs/meta/SECURITY.md
  • packages/docs-content/cs/reference/cli.md
  • packages/docs-content/cs/reference/ioc-database.md
  • packages/docs-content/getting-started/installation.md
  • packages/docs-content/getting-started/quickstart.md
  • packages/docs-content/reference/cli.md
  • packages/docs-content/reference/ioc-database.md
  • packages/engine/src/__tests__/threats.validate.test.ts
  • packages/engine/src/extractor/ioc.test.ts
  • packages/engine/src/extractor/ioc.ts
  • packages/engine/src/ingest.ts
  • packages/engine/src/prompt.ts
  • packages/engine/src/types.ts
  • packages/engine/src/validate.ts
  • packages/engine/tests/osv.test.ts
  • packages/ioc/index.js
  • packages/ioc/threats/axios-2026.json
  • packages/ioc/threats/ctx-2022.json
  • packages/ioc/threats/event-stream-2018.json
  • packages/ioc/threats/shai-hulud-2025.json
  • packages/remediation/src/scripts/safe-suspend.ts
  • packages/scanner/src/detectors/injection.ts
  • packages/scanner/src/output/sarif.ts
  • packages/scanner/src/parsers/bun.ts
  • packages/scanner/src/parsers/index.ts
  • packages/scanner/src/parsers/js-yaml.d.ts
  • packages/scanner/src/parsers/pnpm.ts
  • packages/scanner/src/parsers/requirements.ts
  • packages/scanner/src/scan.ts
  • packages/scanner/src/threats.ts
  • packages/scanner/src/types.ts
  • packages/scanner/src/utils.ts
  • packages/scanner/tests/security.test.ts
  • packages/scripts/check-github-repos.sh
  • packages/scripts/detect.sh
  • packages/scripts/full-audit.sh
  • packages/scripts/harden-npm.sh
  • packages/scripts/quick-audit.sh
  • packages/scripts/release.sh
  • socket.yml
✅ Files skipped from review due to trivial changes (27)
  • packages/configs/pnpm-workspace-secure.yaml
  • packages/configs/bunfig-secure.toml
  • packages/engine/src/extractor/ioc.test.ts
  • packages/docs-content/reference/cli.md
  • packages/scripts/release.sh
  • packages/ioc/threats/ctx-2022.json
  • packages/ioc/threats/event-stream-2018.json
  • examples/axios-compromise.sarif
  • packages/docs-content/reference/ioc-database.md
  • packages/docs-content/cs/reference/ioc-database.md
  • packages/engine/src/prompt.ts
  • packages/engine/src/validate.ts
  • packages/scripts/full-audit.sh
  • packages/docs-content/cs/meta/AGENTS.md
  • packages/ioc/threats/shai-hulud-2025.json
  • packages/scripts/quick-audit.sh
  • packages/docs-content/cs/getting-started/installation.md
  • packages/scanner/src/types.ts
  • packages/scripts/check-github-repos.sh
  • AGENTS.md
  • packages/scripts/harden-npm.sh
  • packages/engine/tests/osv.test.ts
  • packages/docs-content/getting-started/quickstart.md
  • packages/engine/src/ingest.ts
  • packages/docs-content/cs/meta/README.md
  • SECURITY.md
  • packages/scripts/detect.sh
🚧 Files skipped from review as they are similar to previous changes (13)
  • packages/configs/renovate-defense.json
  • packages/engine/src/extractor/ioc.ts
  • apps/docs/tests/verify-search.spec.ts
  • packages/ioc/threats/axios-2026.json
  • packages/docs-content/getting-started/installation.md
  • packages/scanner/tests/security.test.ts
  • packages/scanner/src/parsers/index.ts
  • packages/docs-content/cs/reference/cli.md
  • packages/scanner/src/detectors/injection.ts
  • packages/scanner/src/scan.ts
  • CHANGELOG.md
  • packages/scanner/src/parsers/requirements.ts
  • packages/scanner/src/threats.ts

Comment thread cs/README.md
Comment on lines +34 to +40
Volitelné formáty:

```bash
npx worms-ctrl scan . --format json
npx worms-ctrl scan . --format sarif --output wormsctrl.sarif
npx worms-ctrl scan . --threats
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Clarify scan --threats vs threats command behavior.

Line 39 currently reads like a catalog command, but --threats in packages/scanner/src/cli.ts only logs the loaded threat-object count during scan; full catalog output is npx worms-ctrl threats. Please document both explicitly to avoid UX confusion.

Suggested doc tweak
 npx worms-ctrl scan . --format json
 npx worms-ctrl scan . --format sarif --output wormsctrl.sarif
-npx worms-ctrl scan . --threats
+npx worms-ctrl scan . --threats   # vypíše počet načtených threat objektů
+npx worms-ctrl threats            # vypíše celý katalog threatů
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cs/README.md` around lines 34 - 40, Update the README examples to clarify
command behavior: state that the scan subcommand's --threats flag only logs the
number of loaded threat objects during scan (i.e., a quick count/diagnostic) and
does not output the full catalog, and explicitly document that the full threat
catalog is produced by the separate top-level command npx worms-ctrl threats;
adjust the example block under the optional formats/examples so one line shows
npx worms-ctrl scan . --threats (with a parenthetical like “prints loaded threat
count only”) and another line shows npx worms-ctrl threats (with a parenthetical
like “prints full threat catalog”) so users can see the difference between the
--threats flag and the threats command.

Comment thread package.json
Comment thread packages/engine/src/types.ts Outdated
Comment thread packages/ioc/index.js
Comment on lines +11 to +22
export async function safeSuspend(dryRun = false): Promise<void> {
console.log('[safe-suspend] Scanning for malicious processes...')

for (const sig of MALICIOUS_SIGNATURES) {
// Placeholder: actual pgrep implementation
console.log(`[safe-suspend] Would freeze processes matching: ${sig}`)
if (dryRun) {
console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
} else {
console.log(`[safe-suspend] Freezing processes matching: ${sig}`)
// TODO/FIXME: implement the actual suspension using child_process (pgrep + kill -STOP)
throw new Error('suspension not implemented')
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Non-dry-run path is always broken right now

Line 11 defaults to dryRun = false, but Lines 19–21 always throw on the first loop iteration, so default execution can never complete remediation. This is a functional gap in the main path; either implement SIGSTOP here or fail fast once before the loop with an explicit “non-dry-run unsupported” error (and avoid “Freezing…” logs that imply action occurred).

Suggested minimal safeguard until SIGSTOP implementation lands
 export async function safeSuspend(dryRun = false): Promise<void> {
   console.log('[safe-suspend] Scanning for malicious processes...')
+  if (!dryRun) {
+    throw new Error('[safe-suspend] non-dry-run mode is not implemented yet')
+  }

   for (const sig of MALICIOUS_SIGNATURES) {
-    // Placeholder: actual pgrep implementation
-    if (dryRun) {
-      console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
-    } else {
-      console.log(`[safe-suspend] Freezing processes matching: ${sig}`)
-      // TODO/FIXME: implement the actual suspension using child_process (pgrep + kill -STOP)
-      throw new Error('suspension not implemented')
-    }
+    // Placeholder: actual pgrep + kill -STOP implementation
+    console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export async function safeSuspend(dryRun = false): Promise<void> {
console.log('[safe-suspend] Scanning for malicious processes...')
for (const sig of MALICIOUS_SIGNATURES) {
// Placeholder: actual pgrep implementation
console.log(`[safe-suspend] Would freeze processes matching: ${sig}`)
if (dryRun) {
console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
} else {
console.log(`[safe-suspend] Freezing processes matching: ${sig}`)
// TODO/FIXME: implement the actual suspension using child_process (pgrep + kill -STOP)
throw new Error('suspension not implemented')
}
export async function safeSuspend(dryRun = false): Promise<void> {
console.log('[safe-suspend] Scanning for malicious processes...')
if (!dryRun) {
throw new Error('[safe-suspend] non-dry-run mode is not implemented yet')
}
for (const sig of MALICIOUS_SIGNATURES) {
// Placeholder: actual pgrep + kill -STOP implementation
console.log(`[safe-suspend] [DRY-RUN] Would freeze processes matching: ${sig}`)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/remediation/src/scripts/safe-suspend.ts` around lines 11 - 22, The
non-dry-run path in safeSuspend is broken because it throws inside the loop on
first signature, so running safeSuspend() (dryRun default false) never
completes; update safeSuspend to either implement actual suspension logic (use
child_process to pgrep by signature and send SIGSTOP via kill for each PID) or,
until implementation exists, fail fast before the loop when dryRun === false by
throwing a clear "non-dry-run unsupported" error and avoid emitting "Freezing…"
logs; refer to the safeSuspend function and MALICIOUS_SIGNATURES constant when
making this change so logs and control flow are corrected.

Comment thread packages/scanner/src/parsers/bun.ts
Comment thread packages/scanner/src/parsers/js-yaml.d.ts Outdated
Comment on lines +42 to +84
if (normalized.startsWith('@')) {
const scopeSeparator = normalized.indexOf('/', 1)
if (scopeSeparator === -1) {
return null
}

const versionAtSeparator = normalized.indexOf('@', scopeSeparator + 1)
if (versionAtSeparator !== -1) {
return {
name: normalized.slice(0, versionAtSeparator),
version: normalized.slice(versionAtSeparator + 1).split('_')[0] ?? '',
}
}

const versionSlashSeparator = normalized.indexOf('/', scopeSeparator + 1)
if (versionSlashSeparator !== -1) {
return {
name: normalized.slice(0, versionSlashSeparator),
version: normalized.slice(versionSlashSeparator + 1).split('_')[0] ?? '',
}
}

return null
}

if (normalized.includes('/')) {
const versionSeparator = normalized.indexOf('/')
return {
name: normalized.slice(0, versionSeparator),
version: normalized.slice(versionSeparator + 1).split('_')[0] ?? '',
}
}

// Handle npm alias keys: alias@npm:real@version
const npmAliasIdx = normalized.indexOf('@npm:')
if (npmAliasIdx > 0) {
const aliasName = normalized.slice(0, npmAliasIdx)
const realPart = normalized.slice(npmAliasIdx + 5)
const realVersionSep = realPart.lastIndexOf('@')
const version =
realVersionSep > 0 ? (realPart.slice(realVersionSep + 1).split('_')[0] ?? '') : ''
return { name: aliasName, version }
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "pnpm.ts" | grep -E "scanner|parsers"

Repository: miccy/worms-ctrl

Length of output: 99


🏁 Script executed:

cat -n ./packages/scanner/src/parsers/pnpm.ts

Repository: miccy/worms-ctrl

Length of output: 5049


🏁 Script executed:

find . -type f -name "*.test.ts" -o -name "*.spec.ts" | xargs grep -l "pnpm\|parsePnpmPackageKey" 2>/dev/null

Repository: miccy/worms-ctrl

Length of output: 109


🏁 Script executed:

cat -n ./packages/scanner/src/__tests__/parsers.test.ts

Repository: miccy/worms-ctrl

Length of output: 3982


Move @npm: alias handling before scoped and slash branches to prevent corrupted name/version parsing.

Keys like @scope/alias@npm:pkg@1.2.3 or alias@npm:@scope/pkg@1.2.3 are currently misinterpreted by the scoped/slash parsers. For example, @scope/alias@npm:pkg@1.2.3 gets parsed as name: @scope/alias and version: npm:pkg@1.2.3, which breaks threat matching for aliased dependencies.

Suggested fix
function parsePnpmPackageKey(key: string): { name: string; version: string } | null {
   const normalized = key.replace(/^\/+/, '').replace(/\([^)]*\)/g, '')
   if (!normalized) {
     return null
   }
+
+  // Handle npm alias keys before generic scoped/unscoped parsing.
+  const npmAliasIdx = normalized.indexOf('@npm:')
+  if (npmAliasIdx > 0) {
+    const aliasName = normalized.slice(0, npmAliasIdx)
+    const realPart = normalized.slice(npmAliasIdx + 5)
+    const realVersionSep = realPart.lastIndexOf('@')
+    const version =
+      realVersionSep > 0 ? (realPart.slice(realVersionSep + 1).split('_')[0] ?? '') : ''
+    return { name: aliasName, version }
+  }
 
   if (normalized.startsWith('@')) {
     const scopeSeparator = normalized.indexOf('/', 1)
     if (scopeSeparator === -1) {
       return null
@@
-  // Handle npm alias keys: alias@npm:real@version
-  const npmAliasIdx = normalized.indexOf('@npm:')
-  if (npmAliasIdx > 0) {
-    const aliasName = normalized.slice(0, npmAliasIdx)
-    const realPart = normalized.slice(npmAliasIdx + 5)
-    const realVersionSep = realPart.lastIndexOf('@')
-    const version =
-      realVersionSep > 0 ? (realPart.slice(realVersionSep + 1).split('_')[0] ?? '') : ''
-    return { name: aliasName, version }
-  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scanner/src/parsers/pnpm.ts` around lines 42 - 84, The npm alias
handling for '@npm:' (the logic using npmAliasIdx, aliasName and realPart) must
be run before the scoped branch (if (normalized.startsWith('@')) ) and the slash
branch (if (normalized.includes('/')) ) because entries like
'@scope/alias@npm:pkg@1.2.3' are currently parsed by those branches and produce
incorrect name/version; move the block that computes npmAliasIdx, aliasName,
realPart and version to just after you obtain normalized (i.e., before any
checks for startsWith('@') or includes('/')) so that when
normalized.indexOf('@npm:') > 0 you return the alias result immediately and skip
the scoped/slash parsing. Ensure you keep the same name/version extraction logic
(realPart.lastIndexOf('@') and split('_')[0]) when relocating the code.

Comment on lines +123 to +136
for (const [key, entry] of Object.entries(packages)) {
const parsedKey = parsePnpmPackageKey(key)
if (!parsedKey) {
continue
}

parsedPackages.push({
...parsedKey,
resolved: entry.resolution?.tarball,
integrity: entry.resolution?.integrity,
engines: entry.engines,
requires: entry.dependencies,
lockfileVersion: lock.lockfileVersion ?? 'unknown',
})
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Pull dependency edges from snapshots for v9 lockfiles.

pnpm v9 moved dependency relations out of packages and into snapshots. Reading entry.dependencies here means requires will be empty for those entries, so transitive detections and parser parity regress on modern lockfiles. This needs a snapshot join before returning ParsedPackage[]. (github.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/scanner/src/parsers/pnpm.ts` around lines 123 - 136, The parser
currently reads dependency edges from entry.dependencies inside the packages
loop (in the block using parsePnpmPackageKey and pushing into parsedPackages),
but for pnpm v9 dependency relations live in lock.snapshots; update the logic
that sets the requires field so it prefers snapshot dependencies when available:
if lock.snapshots exists, look up the corresponding snapshot by the same package
identifier (use the parsed key or package id derived in parsePnpmPackageKey) and
use snapshot.dependencies, falling back to entry.dependencies when no snapshot
is found; ensure this change is applied where requires is assigned in the
parsedPackages.push so transitive dependency data is preserved for v9+
lockfiles.

Comment thread README.md
Comment on lines +34 to 40
Optional formats:

# View the Knowledge Base of tracked threats
npx worms-ctrl threats
```bash
npx worms-ctrl scan . --format json
npx worms-ctrl scan . --format sarif --output wormsctrl.sarif
npx worms-ctrl scan . --threats
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Document the catalog command separately from the scan flag.

Line 39 implies --threats is the catalog view, but in packages/scanner/src/cli.ts it only reports loaded threat count during scan. Add npx worms-ctrl threats explicitly for catalog output.

Suggested doc tweak
 npx worms-ctrl scan . --format json
 npx worms-ctrl scan . --format sarif --output wormsctrl.sarif
-npx worms-ctrl scan . --threats
+npx worms-ctrl scan . --threats  # prints loaded threat object count
+npx worms-ctrl threats           # prints full threat catalog
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
Optional formats:
# View the Knowledge Base of tracked threats
npx worms-ctrl threats
```bash
npx worms-ctrl scan . --format json
npx worms-ctrl scan . --format sarif --output wormsctrl.sarif
npx worms-ctrl scan . --threats
```
Optional formats:
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@README.md` around lines 34 - 40, The README mistakenly suggests `--threats`
is a catalog command; update documentation to show the separate catalog
subcommand and clarify the flag's behavior: add an explicit example line with
`npx worms-ctrl threats` for catalog output, and change the existing example or
note to state that `npx worms-ctrl scan ... --threats` only reports loaded
threat count during a scan (as implemented in packages/scanner/src/cli.ts, where
the scan command handles the --threats flag). Ensure the README mentions both
usages (the `threats` subcommand for catalog listing and the `--threats` scan
flag for count) so readers aren’t misled.

🧹 Resolve PR #24 code review comments & bump to v2.0.0
Copy link
Copy Markdown
Owner Author

@miccy miccy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

coderabbitai[bot]
coderabbitai Bot previously requested changes May 5, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/engine/tests/osv.test.ts (2)

20-27: ⚡ Quick win

Consider asserting modified/published passthrough fields too.

Those fields are part of the converter output, so one explicit assertion helps catch accidental omissions in future refactors.

Suggested assertion snippet
   test('converts basic OSV record correctly', () => {
     const result = osvToThreatProfile(baseOsv) as unknown as Record<string, unknown>
     expect(result.id).toBe(baseOsv.id)
     expect(result.name).toBe('test-package')
     expect(result.ecosystem).toBe('npm')
     expect(result.severity).toBe('LOW') // Default score 0 -> LOW
     expect(result.status).toBe('UNDER_REVIEW')
+    expect(result.modified).toBe(baseOsv.modified)
+    expect(result.published).toBe(baseOsv.published)
   })

Also applies to: 101-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/tests/osv.test.ts` around lines 20 - 27, The test "converts
basic OSV record correctly" currently asserts id, name, ecosystem, severity, and
status but omits passthrough date fields; update the test that calls
osvToThreatProfile(baseOsv) to also assert that the output includes the original
modified and published values from baseOsv (e.g.
expect(result.modified).toBe(baseOsv.modified) and
expect(result.published).toBe(baseOsv.published)), so the converter's
passthrough behavior for modified/published is explicitly verified.

29-58: ⚡ Quick win

Add one malformed CVSS_V3 score test case.

Since OSV data is external, a non-numeric score (for example 'N/A') is realistic; adding this test will lock in expected fallback behavior and prevent future parser regressions.

Suggested test addition
   test('maps CVSS_V3 scores to severity correctly', () => {
     const testCases = [
       { score: '9.5', expected: 'CRITICAL' },
       { score: '9.0', expected: 'CRITICAL' },
       { score: '8.9', expected: 'HIGH' },
       { score: '7.0', expected: 'HIGH' },
       { score: '6.9', expected: 'MEDIUM' },
       { score: '4.0', expected: 'MEDIUM' },
       { score: '3.9', expected: 'LOW' },
       { score: '0.0', expected: 'LOW' },
+      { score: 'N/A', expected: 'LOW' },
     ]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine/tests/osv.test.ts` around lines 29 - 58, Add a new test case
in packages/engine/tests/osv.test.ts that supplies a malformed CVSS_V3 score
(e.g., severity: [{ type: 'CVSS_V3', score: 'N/A' }]) and asserts
osvToThreatProfile(osv).severity falls back to 'LOW'; update the existing "maps
CVSS_V3 scores..." loop or add a separate small test to include this non-numeric
score to lock in the parser's fallback behavior and reference osvToThreatProfile
when locating the test.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/scanner/src/detectors/injection.ts`:
- Around line 21-42: loadPackageJsonDeps currently swallows parse/read errors
and returns null which disables the downstream gate (declared && pkg.version &&
!declared.has(pkg.name)); update the catch in loadPackageJsonDeps to log a
warning including the targetDir and the caught error (so operators can see
manifest read failures) and return an empty Set<string> (not null) so detection
still runs against the lockfile; ensure the log message includes context
(function name loadPackageJsonDeps and targetDir) and the error details.
- Around line 75-84: In the phantomMatches loop (where findings.push is called)
don't hardcode severity to 'critical'; use the same pattern as the
malicious-package block by deriving severity via
toFindingSeverity(match.threat.severity) (or, if you intentionally want to
escalate all phantom dependencies, add a one-line comment explaining that
decision next to the findings.push to make the intent explicit). Update the
severity assignment in the phantom dependency finding to reference match.threat
and toFindingSeverity so phantom IOC reports align with the threat catalog.

---

Nitpick comments:
In `@packages/engine/tests/osv.test.ts`:
- Around line 20-27: The test "converts basic OSV record correctly" currently
asserts id, name, ecosystem, severity, and status but omits passthrough date
fields; update the test that calls osvToThreatProfile(baseOsv) to also assert
that the output includes the original modified and published values from baseOsv
(e.g. expect(result.modified).toBe(baseOsv.modified) and
expect(result.published).toBe(baseOsv.published)), so the converter's
passthrough behavior for modified/published is explicitly verified.
- Around line 29-58: Add a new test case in packages/engine/tests/osv.test.ts
that supplies a malformed CVSS_V3 score (e.g., severity: [{ type: 'CVSS_V3',
score: 'N/A' }]) and asserts osvToThreatProfile(osv).severity falls back to
'LOW'; update the existing "maps CVSS_V3 scores..." loop or add a separate small
test to include this non-numeric score to lock in the parser's fallback behavior
and reference osvToThreatProfile when locating the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2852e907-94f0-4d88-8ee2-e5930a1f15bd

📥 Commits

Reviewing files that changed from the base of the PR and between d59ae23 and dc4a108.

📒 Files selected for processing (5)
  • apps/docs/playwright.config.ts
  • packages/engine/tests/osv.test.ts
  • packages/scanner/src/detectors/injection.ts
  • packages/scanner/src/parsers/js-yaml.d.ts
  • packages/wiki-sync/src/index.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/wiki-sync/src/index.ts
  • packages/scanner/src/parsers/js-yaml.d.ts

Comment thread packages/scanner/src/detectors/injection.ts
Comment thread packages/scanner/src/detectors/injection.ts
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 93 out of 101 changed files in this pull request and generated 10 comments.

Files not reviewed (3)
  • tests/fixtures/axios-compromise/package-lock.json: Language not supported
  • tests/fixtures/clean-baseline/package-lock.json: Language not supported
  • tests/fixtures/shai-hulud-worm/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +171 to +175
parsedPackages.push({
name,
version,
lockfileVersion: 0,
})
Comment on lines +42 to +54
if (normalized.startsWith('@')) {
const scopeSeparator = normalized.indexOf('/', 1)
if (scopeSeparator === -1) {
return null
}

const versionAtSeparator = normalized.indexOf('@', scopeSeparator + 1)
if (versionAtSeparator !== -1) {
return {
name: normalized.slice(0, versionAtSeparator),
version: normalized.slice(versionAtSeparator + 1).split('_')[0] ?? '',
}
}
Comment on lines +1 to +6
{
"type": "bundle",
"id": "bundle--3d0c41df-9d58-45da-9b59-4d8727284f18",
"spec_version": "2.1",
"objects": [
{
Comment on lines 186 to 205
@@ -201,7 +201,7 @@ async function syncToWiki() {
if (!existsSync(WIKI_DIR)) {
console.error(`❌ Wiki directory not found: ${WIKI_DIR}`)
console.log(' Clone the wiki repo first:')
console.log(' git clone https://github.com/miccy/dont-be-shy-hulud.wiki.git')
console.log(' git clone https://github.com/miccy/wormsCTRL.wiki.git')
process.exit(1)
Comment on lines +19 to +25
export function parseLockfiles(targetDir: string): LockfilePackage[] {
return [
...parseNpmLockfile(targetDir),
...parseYarnLockfile(targetDir),
...parsePnpmLockfile(targetDir),
...parseBunLockfile(targetDir),
]
Comment on lines +18 to +23
- indicators_of_compromise must be an object with exactly these keys:
- package_names: array of strings
- malicious_versions: array of objects with { package: string, version: string, reason?: string }
- network_iocs: array of objects with { type: "ip" | "domain" | "url", value: string }
- file_artifacts: array of objects with { filename: string, sha256: string, path?: string }
- remediation must contain immediate and long_term arrays
Comment on lines +59 to +74
function isValidThreatShape(obj: unknown): obj is ThreatObject {
if (!obj || typeof obj !== 'object') return false
const candidate = obj as Record<string, unknown>
if (typeof candidate.id !== 'string') return false
if (!['CRITICAL', 'HIGH', 'MODERATE', 'LOW'].includes(candidate.severity as string)) return false
if (typeof candidate.attack_vector !== 'string') return false
const ioc = candidate.indicators_of_compromise
if (!ioc || typeof ioc !== 'object') return false
const iocObj = ioc as Record<string, unknown>
if (!Array.isArray(iocObj.malicious_versions)) return false
return iocObj.malicious_versions.every((v: unknown) => {
if (!v || typeof v !== 'object') return false
const vObj = v as Record<string, unknown>
return typeof vObj.package === 'string' && typeof vObj.version === 'string'
})
}
Comment thread CHANGELOG.md
Comment on lines +11 to +13
### Added
- **Path validation** — Added `validatePath()` utility to scanner to prevent path traversal and reject null bytes/empty paths.
- **Threat database** — Added `xz-utils-2024.json` threat profile to `packages/ioc/threats`.
Comment on lines +31 to +34
expect(newBox!.width).toBeGreaterThan(originalBox?.width || 0);

await page.screenshot({ path: 'search-modal-mobile-long-cancel.png' });
});
Comment on lines +46 to +50
const cancelButton = page.locator('button[data-close-modal]');
await expect(cancelButton).toBeHidden(); // Hidden on desktop in original code (md:sl-hidden)

await page.screenshot({ path: 'search-modal-desktop.png' });
});
Copy link
Copy Markdown
Owner Author

@miccy miccy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed

miccy and others added 5 commits May 5, 2026 05:11
Replaces hardcoded fixed file paths for screenshots with
testInfo.outputPath() in verify-search.spec.ts to prevent
collisions during parallel test execution.
…4820

Signed-off-by: Miccy <support@miccy.dev>
🧹 Resolve PR #24 code review comments & bump to v2.0.0
…0430341770954550

🧪 fix: use testInfo.outputPath for playwright screenshots
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 93 out of 101 changed files in this pull request and generated 6 comments.

Files not reviewed (3)
  • tests/fixtures/axios-compromise/package-lock.json: Language not supported
  • tests/fixtures/clean-baseline/package-lock.json: Language not supported
  • tests/fixtures/shai-hulud-worm/package-lock.json: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +62 to +64
if (typeof candidate.id !== 'string') return false
if (!['CRITICAL', 'HIGH', 'MODERATE', 'LOW'].includes(candidate.severity as string)) return false
if (typeof candidate.attack_vector !== 'string') return false
Comment on lines +175 to +179
parsedPackages.push({
name,
version,
lockfileVersion: 0,
})
Comment on lines +1 to +5
{
"type": "bundle",
"id": "bundle--3d0c41df-9d58-45da-9b59-4d8727284f18",
"spec_version": "2.1",
"objects": [
Comment on lines 18 to 20
const CONTENT_DIR = join(import.meta.dirname, '../../docs-content/en')
const WIKI_DIR = join(import.meta.dirname, '../../../../dont-be-shy-hulud.wiki')
const WIKI_DIR = join(import.meta.dirname, '../../../../wormsCTRL.wiki')

Comment thread CHANGELOG.md Outdated
Comment on lines +18 to +23
- indicators_of_compromise must be an object with exactly these keys:
- package_names: array of strings
- malicious_versions: array of objects with { package: string, version: string, reason?: string }
- network_iocs: array of objects with { type: "ip" | "domain" | "url", value: string }
- file_artifacts: array of objects with { filename: string, sha256: string, path?: string }
- remediation must contain immediate and long_term arrays
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Signed-off-by: Miccy <code@miccy.dev>
@miccy
Copy link
Copy Markdown
Owner Author

miccy commented May 5, 2026

@copilot implement all suggestion in one PR include suggestions from CodeRabbit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation feat New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants