Skip to content

feat: add --debug flag for verbose network and runtime logging#471

Merged
sonukapoor merged 9 commits into
OWASP:mainfrom
coder-Yash886:feature/issue-393-debug-flag
May 31, 2026
Merged

feat: add --debug flag for verbose network and runtime logging#471
sonukapoor merged 9 commits into
OWASP:mainfrom
coder-Yash886:feature/issue-393-debug-flag

Conversation

@coder-Yash886

Copy link
Copy Markdown
Contributor

Closes #393

Summary

Adds a --debug flag to cve-lite for maintainer-focused diagnostics
without affecting normal CLI output behavior.

When --debug is enabled, the CLI emits verbose runtime/network logs
to stderr, including:

  • Lockfile/source parsing details and package counts
  • Cache hit/miss decisions for package queries
  • OSV request/response lifecycle (URL, method, status, timing, payload metadata)
  • Caught fetch/runtime errors with full stack traces

Debug output is routed to stderr so JSON/SARIF/CDX output streams
remain clean and machine-readable on stdout.

Motivation

Intermittent or machine-specific OSV/network failures are currently
invisible from CLI output alone. This flag gives maintainers a
structured trace to identify exactly where in the request lifecycle
a failure occurred, without impacting normal users.

Changes

  • src/output/debug.ts — reusable debug logger (writes to stderr)
  • src/cli/args.ts--debug flag parsing
  • src/cli/help.ts — help text for --debug
  • src/types.ts — debug option type definition
  • src/scanner.ts — debug logging for lockfile parsing and cache decisions
  • src/advisory/osv-advisory-source.ts — debug logging for OSV requests/responses
  • src/index.ts — wire debug flag through runtime

Test Plan

  • npm test -- tests/helpers.test.ts tests/output.test.ts
  • npm test -- tests/cli-integration.test.ts
  • Full suite: 283/283 passed

No behavior changes for users who do not pass --debug.

@coder-Yash886

Copy link
Copy Markdown
Contributor Author

@sonukapoor please review the PR when you have free time

Copilot AI left a comment

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.

Pull request overview

Adds a maintainer-focused --debug flag to emit verbose runtime/network diagnostics to stderr while keeping normal CLI output (including JSON/SARIF/CDX) clean on stdout.

Changes:

  • Introduces a reusable debug logger (createDebugLogger) and wires --debug through CLI args/options and the main runtime.
  • Adds debug tracing for cache hit/miss decisions and OSV request/response/error lifecycle.
  • Adds/updates Jest coverage to verify debug logging behavior and arg parsing.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
tests/output.test.ts Adds a test ensuring debug logs write to stderr only when enabled.
tests/helpers.test.ts Extends parseArgs tests to assert --debug parsing.
src/types.ts Adds debug?: boolean to parsed CLI options typing.
src/scanner.ts Creates debug logger per scan and logs cache hit/miss; passes logger into advisory source.
src/output/debug.ts New debug logger utility that writes formatted debug lines to console.error.
src/index.ts Wires --debug into runtime logging (package parsing summary + stack trace on fatal errors).
src/cli/help.ts Documents --debug (and additionally updates help text for other commands/options).
src/cli/args.ts Adds --debug flag parsing.
src/advisory/osv-advisory-source.ts Adds debug logging around OSV requests/responses/errors (including timing).

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

Comment thread src/cli/help.ts
Comment thread src/cli/help.ts
Comment thread src/cli/help.ts
Comment thread src/advisory/osv-advisory-source.ts Outdated
@coder-Yash886
coder-Yash886 force-pushed the feature/issue-393-debug-flag branch from 5807734 to ef2b1e4 Compare May 27, 2026 19:31

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

See below comments.

Comment thread src/cli/help.ts Outdated
Comment thread src/index.ts Outdated
@coder-Yash886
coder-Yash886 force-pushed the feature/issue-393-debug-flag branch from 1e609d6 to ec46ef1 Compare May 28, 2026 01:16
@coder-Yash886

Copy link
Copy Markdown
Contributor Author

@sonukapoor please review the PR when you have free time

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both issues from the last round are fixed — duplicate --ca-cert is gone and pluralize is used correctly. The implementation is solid: typed DebugLogger, no-op for disabled mode, comprehensive OSV request/response/error coverage, and good test coverage. Two small things before this merges.

Comment thread src/scanner.ts Outdated
Comment thread src/scanner.ts
@sonukapoor

Copy link
Copy Markdown
Collaborator

Great start — the DebugLogger abstraction is clean and the OSV request/response entries are exactly what you want for network diagnosis. Before we merge, here are some extensions that would make the feature genuinely useful in production.

Output should go to a file, not stderr

Right now --debug writes to stderr, which means the output gets mixed in with the normal scan output and is hard to redirect cleanly. Instead, debug output should be written to a timestamped log file in the current directory:

./cve-lite-debug-2026-05-27T14-30-00.log

When --debug is passed, print one line to stderr so the user knows where to look:

[debug] Writing debug log to ./cve-lite-debug-2026-05-27T14-30-00.log

Everything else goes into the file only. This keeps the terminal output clean and gives you a persistent artefact you can share when reporting an issue.

What's currently missing

The debug output has two other problems today:

  1. Per-package cache lines are extremely noisy (1,600+ lines for a mid-sized project) and crowd out the entries that actually matter
  2. Several important code paths are completely dark — network failures in fix command generation, subprocess execution in --fix mode, and the npm transitive graph build outcome are all invisible

Proposed log format

Every entry in the file should be a single JSON line with a timestamp prefix:

2026-05-27T14:30:00.012Z [debug] CLI started {"version":"1.18.1","args":["examples/nest","--debug","--no-cache"]}
2026-05-27T14:30:00.013Z [debug] Config loaded {"caCert":null}
2026-05-27T14:30:00.014Z [debug] Advisory source {"mode":"online","url":"https://api.osv.dev"}
2026-05-27T14:30:00.015Z [debug] Cache loaded {"path":"~/.cache/cve-lite/osv-vulns.json","queryEntries":11101,"detailEntries":260}
2026-05-27T14:30:00.021Z [debug] Lockfile selected {"source":"package-lock","path":"examples/nest/package-lock.json"}
2026-05-27T14:30:00.089Z [debug] Packages parsed {"count":1623,"source":"package-lock"}
2026-05-27T14:30:00.090Z [debug] Cache check {"hits":0,"misses":1623,"reason":"no-cache mode","uncachedBatches":17}
2026-05-27T14:30:00.091Z [debug] npm transitive graph {"status":"built","nodes":1623}
2026-05-27T14:30:00.092Z [debug] Workspace map {"type":"npm","workspaces":0}
2026-05-27T14:30:00.093Z [debug] OSV request  {"batchId":"b-01","queryCount":100,"sample":["@apollo/cache-control-types@1.0.3","@apollo/server@5.5.0",...]}
2026-05-27T14:30:00.094Z [debug] OSV request  {"batchId":"b-02","queryCount":100,"sample":["@fastify/forwarded@3.0.0","@fastify/middie@9.3.1",...]}
2026-05-27T14:30:00.095Z [debug] OSV request  {"batchId":"b-03","queryCount":100,"sample":["@nodelib/fs.scandir@2.1.5",...]}
2026-05-27T14:30:00.489Z [debug] OSV response {"batchId":"b-03","status":200,"durationMs":394}
2026-05-27T14:30:00.581Z [debug] OSV response {"batchId":"b-01","status":200,"durationMs":488}
2026-05-27T14:30:00.582Z [debug] OSV response {"batchId":"b-02","status":200,"durationMs":488}
2026-05-27T14:30:01.843Z [debug] OSV scan complete {"totalBatches":17,"totalDurationMs":1831,"packagesWithVulns":42}
2026-05-27T14:30:01.850Z [debug] Findings classified {"total":42,"direct":10,"transitive":32,"critical":3,"high":11,"medium":25,"low":3}
2026-05-27T14:30:01.851Z [debug] Registry fetch {"batchId":"r-01","package":"@fastify/middie"}
2026-05-27T14:30:02.103Z [debug] Registry response {"batchId":"r-01","status":200,"durationMs":252,"versionsCount":48}
2026-05-27T14:30:02.104Z [debug] Fix validation {"package":"@fastify/middie","installed":"9.3.1","resolvedFixVersion":"9.3.2","verified":true}
2026-05-27T14:30:02.890Z [debug] Parent upgrade resolution {"package":"form-data","version":"2.3.3","result":"upgrade-parent","parent":"coveralls","targetVersion":"3.1.0"}
2026-05-27T14:30:03.201Z [debug] Scan finished {"totalDurationMs":3189,"findings":42}

Why batchId?

CVE Lite sends up to 5 OSV requests concurrently via runWithConcurrency. This means request and response log entries for different batches will naturally interleave in the file — you might see request b-01, b-02, b-03 fire in quick succession, then responses arrive in a different order depending on which batch the OSV API answered first.

Without a correlation ID, a failed or hanging batch is impossible to diagnose — you can see a request was sent but you can't tell which packages were in it. With batchId, you grep for b-03 and immediately get both the request (which packages were included) and the response (status code, duration, or error message).

The same pattern applies to registry calls in fix command generation — each fetchPackument call gets its own batchId (prefixed r-) so a 429 or timeout can be traced back to the specific package that triggered it.

A simple counter is enough — no need for UUIDs. Just increment an integer per session and format it as b-01, b-02, etc.

Performance — debug mode must be a true no-op when disabled

This is important. Every new log call added to hot paths like fetchPackument, the cache check loop, and runWithConcurrency must be completely skipped when --debug is not passed. The current createDebugLogger(false) already returns () => {}, so the call overhead is minimal — but be careful with anything computed before the call:

// Wrong — JSON.stringify runs on every scan regardless of debug mode
debugLog('OSV request', JSON.stringify(payload));

// Right — the object literal is only evaluated if debugLog is not a no-op
debugLog('OSV request', { batchId, queryCount, sample });

Similarly, opening the log file, incrementing the batchId counter, and any Date.now() calls for timing should all be gated behind if (enabled) inside debug.ts. A normal scan with no --debug flag should have zero file I/O and zero string formatting overhead from this feature.

Files that need changes

src/output/debug.ts - Switch from console.error to writing to a timestamped file. Print the file path to stderr once on startup.

src/scanner.ts - Replace per-package cache hit/miss lines with a single Cache check summary. Add: npm transitive graph build outcome (currently catch { return null } with no log), workspace map outcomes, findings classification summary, and scan finished summary.

src/advisory/osv-advisory-source.ts - Add batchId to existing request/response entries.

src/remediation/npm-registry.ts - This file currently has no debug logging at all. fetchPackument has a catch {} that silently returns null — if the npm registry is unreachable or returns 5xx, fix commands silently fail to generate with no indication in the log. Thread debugLog through fetchPackument, resolvePublishedFixVersion, and resolveLowestKnownNonVulnerableVersion and log each registry call and its outcome.

src/utils/fix-runner.ts - runInstallCommand discards all stdout/stderr. In debug mode, log the exact command being run and its exit code. The debugLog isn't currently passed to applyFixesIfRequested at all — thread it through from index.ts.

src/osv/cache.ts - Log cache file path and entry counts on load. Log when the cache falls back to empty due to a parse error (currently catch { return createEmptyCache() } is completely silent).

src/index.ts - Log when NODE_EXTRA_CA_CERTS is set (the CA cert path). This is the first thing to check when diagnosing SSL proxy failures and it is currently invisible.


Happy to discuss any of this — the goal is a debug log that fits on one screen for a typical scan and gives you a clear timeline of exactly where time was spent and where something went wrong.

@coder-Yash886

Copy link
Copy Markdown
Contributor Author

@sonukapoor please review the PR when you have free time

@sonukapoor

Copy link
Copy Markdown
Collaborator

@sonukapoor please review the PR when you have free time

I will review this later today.

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Good progress — the file-only output is exactly right, the batchId correlation on OSV requests/responses is clean, and the cache check summary (instead of per-package lines) is the correct call. The foundation is solid.

Before we merge, the log format is still missing a significant portion of what I outlined in my earlier comment. Comparing the proposed format against what the file produces today:

src/index.ts

The [debug] Writing debug log to ... line currently appears before the banner because createDebugLogger is called outside main(). Move it inside main(), after printBanner(), so it appears after "Fast. Local. Developer-first." in the output.

Also missing: the CLI started, Config loaded, and Advisory source entries from the proposed format. And when NODE_EXTRA_CA_CERTS is set (line 123), that should be logged — it's the first thing to check when diagnosing SSL proxy failures and right now it's completely invisible in the debug output.

The stack trace on error (line 341) goes to console.error which prints to the terminal. That should go to the log file instead — same as everything else.

src/scanner.ts

Missing: npm transitive graph, Workspace map, Findings classified, and Scan finished entries. These are all in the proposed format and give the timing and classification picture that makes the log useful for performance diagnosis.

src/remediation/npm-registry.ts

This file has no debug logging at all. fetchPackument has a catch {} that silently returns null — if the npm registry is unreachable or rate-limits, fix commands fail to generate with no trace in the log. Thread debugLog through fetchPackument, resolvePublishedFixVersion, and resolveLowestKnownNonVulnerableVersion and log each registry call, its outcome, and any errors.

src/utils/fix-runner.ts

runInstallCommand discards all stdout/stderr from the install process and debugLog isn't threaded through from index.ts at all. In debug mode, log the exact command being run and its exit code.

src/osv/cache.ts

Missing the Cache loaded entry with path and entry counts. Also the catch { return createEmptyCache() } fallback is completely silent — a corrupted or unreadable cache file produces no trace.

The goal is a log that covers the full lifecycle from startup to scan finished, with enough signal to diagnose network failures, cache issues, and fix command generation problems without having to add temporary console.log calls. The proposed format in the original comment covers exactly that — worth getting all of it in before merge.

@coder-Yash886
coder-Yash886 force-pushed the feature/issue-393-debug-flag branch from 16a7649 to defb907 Compare May 29, 2026 13:21
@coder-Yash886

Copy link
Copy Markdown
Contributor Author

@sonukapoor please review the PR When you have free time

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This is coming together really well. The lifecycle coverage is solid — Cache loaded, OSV request/response with batch IDs, Registry fetch, Fix command with exit codes, Findings classified, Scan finished — that is exactly the kind of structured trace that makes network and performance issues diagnosable without adding temporary console.log calls. The file-only output design is the right call too. Good work getting this far.

Two things to fix before this merges, then a few gaps in diagnostic coverage worth filling.

Commit message on defb907 — the message starts with [200~feat(debug)..., which is a bracketed paste escape code that leaked into git. Please squash or amend this before merge so the history is clean.

Redundant configureNpmRegistryDebug call in index.ts — lines 233-235:

if (options.debug) {
  configureNpmRegistryDebug(debugLog);
}

This is redundant. scanPackages already calls configureNpmRegistryDebug(log) unconditionally when !offline (scanner.ts line 103), and log is debugLog ?? (() => {}), so the same logger lands either way. Remove the index.ts call — every line needs to earn its place.

saveCache is silentloadCache logs path and entry counts on every branch, but saveCache writes nothing. A user trying to diagnose "why isn't my cache persisting" would see Cache loaded on the way in but no confirmation on the way out. Add a Cache saved entry to saveCache with the path and entry counts.

--usage and --only-used have no debug coverage — in scanProject, when options.usage is set there's a filesystem scan with no timing logged. When options.onlyUsed is set, findings are silently filtered with no trace. Log a Usage scan entry with timing and matched package count, and a Findings filtered entry showing before/after counts when onlyUsed drops findings.

Scan finished is missing packageCount — currently just totalDurationMs and findings. Adding packages: params.scanInput.packages.length lets you correlate scan time with problem size, which is the first thing to check when someone reports a slow scan.

Zero-packages early exit leaves no trace — the packages.length === 0 path in index.ts calls process.exit(0) with no debug entry. A user running --debug on a folder with no recognizable lockfile would get a log that just stops. Add a Scan skipped entry before the early exit so the log tells a complete story.

@coder-Yash886
coder-Yash886 force-pushed the feature/issue-393-debug-flag branch from e51dea9 to 4828655 Compare May 30, 2026 15:03
@coder-Yash886

Copy link
Copy Markdown
Contributor Author

@sonukapoor please review the PR when you have free time

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both previous issues are fixed: configureNpmRegistryDebug is no longer called from index.ts (scanner handles it), and saveCache now logs. The logging coverage across loadCache, OsvAdvisorySource, and fix-runner is solid.

Two things before this is merge-ready:

1. The test writes real files to disk. In output.test.ts, createDebugLogger(true) followed by debug.log(...) actually calls fs.appendFileSync and creates a cve-lite-debug-*.log file in the project root during every test run. captureErrors only mocks console.error — the file write is not mocked. After each npm test, untracked log files will appear in the working tree.

Fix by unlinking the file after the test:

it("announces debug log file path to stderr when enabled", () => {
  let logFile: string | undefined;
  const enabledLines = captureErrors(() => {
    const debug = createDebugLogger(true);
    debug.log("cache hit");
    debug.announcePath();
    // capture filename from the announce line so we can clean up
    logFile = enabledLines?.[0]?.match(/cve-lite-debug-[^\s]+/)?.[0];
  });
  // ... assertions ...
  if (logFile && fs.existsSync(logFile)) fs.unlinkSync(logFile);
});

Or mock fs.appendFileSync for the block so no file is created at all.

2. Commit history needs squashing. The branch has 8 commits, and commit 2df7ffc still has the ^[[200~feat(debug): bracketed-paste artifact in its message. Please squash all 8 commits into one clean commit and push with --force-with-lease.

@coder-Yash886

coder-Yash886 commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

@sonukapoor Please review the PR When you have free time

@sonukapoor sonukapoor left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Test fix is exactly right — fs.appendFileSync is mocked before the enabled logger runs, the spy assertion proves the write path executes, and the finally cleanup is a good safety net. Everything looks good. Going ahead and squash-merging on our end to clean up the commit history.

@sonukapoor
sonukapoor merged commit 1b7791d into OWASP:main May 31, 2026
6 checks passed
@sonukapoor sonukapoor mentioned this pull request Jun 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: add --debug flag for verbose network and runtime logging

3 participants