Skip to content

Refactor audit-utils and add bulk advisory fallback (async)#340

Merged
leynos merged 15 commits intomainfrom
fix-ci-audit-failure-frontend-pwa-k31pyk
Apr 17, 2026
Merged

Refactor audit-utils and add bulk advisory fallback (async)#340
leynos merged 15 commits intomainfrom
fix-ci-audit-failure-frontend-pwa-k31pyk

Conversation

@leynos
Copy link
Copy Markdown
Owner

@leynos leynos commented Apr 15, 2026

Summary

  • Refactors the walkDependencyTree logic by introducing modular dependency traversal helpers and adds shared audit-utils helpers to support both the native pnpm audit path and the bulk advisory path. Includes registry resolution and parsing/normalisation of bulk advisories.
  • Keeps backward compatibility; the bulk advisory path is activated when the legacy endpoint is retired.
  • Updates frontend audit orchestration to handle async audit flows and the new advisory source, plus related tests.
  • Adds tests covering the new bulk advisory path and the updated helpers; updates related documentation and CI wiring.

Changes

Core audit/fallback logic

  • Introduced new helpers in security/audit-utils.js: parseJsonOutput, isRetiredAuditEndpoint, normaliseBulkAdvisories, readRegistryUrl, collectInstalledPackageVersions, runBulkAdvisoryAudit, and async runAuditJson with fallback.
  • runAuditJson now attempts pnpm audit --json, detects retired endpoint responses, and falls back to runBulkAdvisoryAudit when needed.
  • Bulk advisory fetch uses the registry's bulk endpoint and normalises advisories into a consistent json/advisories shape.
  • Refactored walkDependencyTree to a more modular approach using walkDependencySection and helpers to accumulate installed versions via collectInstalledPackageVersions.
  • Updated security/validate-audit.js and frontend-pwa/scripts/run-audit.mjs to reflect async audit flows and the new advisory source, plus adjusted documentation strings.

Frontend tests

  • Added frontend-pwa/scripts/audit-utils.test.mjs to thoroughly test audit-utils behavior, including the bulk advisory fallback scenario.

CI and tooling

  • Makefile audit target now delegates to audit:validate, aligning with the new validation flow.
  • bun.lock updated to reflect newer vite/esbuild ranges across frontend and related packages.

Documentation

  • docs/repository-structure.md updated to document the fallback path and the rationale for pnpm bulk advisory usage when legacy endpoints are retired.

Test plan

  • Run unit tests for the new audit utils path (frontend-pwa/audit-utils.test.mjs) covering native audit success and bulk advisory fallback paths.
  • Execute end-to-end workflow in CI to ensure the audit flow handles retired endpoints and falls back correctly.
  • Validate registry URL resolution and repository-wide version collection for the bulk advisory payload.
  • Confirm Makefile audit target uses the new audit:validate flow.

How to test locally

  • Install and run tests across workspaces (e.g., via pnpm):
    • pnpm i
    • pnpm -w test
  • Trigger audit flow changes locally by running the frontend audit wrapper and ensuring that a retired audit scenario triggers the bulk advisory path and returns a consistent json with advisories.
  • Verify that the registry URL is respected (env npm_config_registry or NPM_CONFIG_REGISTRY) and that the default registry is used otherwise.

Notes

  • This change is backward-compatible in environments where the legacy audit endpoint remains available; the new path only activates when the retired endpoint is detected.
  • The lockfile changes are intentional to bring in updated transitive dependencies (e.g., vite and esbuild) that support the new audit fallback flow.
  • If any CI environments rely on the old audit behavior, they should still pass as the fallback is opt-in at runtime based on the endpoint response.

◳ Generated by DevBoxer


ℹ️ Tag @devboxerhub to ask questions and address PR feedback

📎 Task: https://www.devboxer.com/task/1c7c3f6e-9984-4c5a-86f2-940376afcd9c

📎 Task: https://www.devboxer.com/task/eff8ed2d-7627-4c32-951b-f2f88f065a7e

Summary by Sourcery

Introduce a shared audit helper that supports pnpm’s native audit JSON output and a bulk advisory fallback, and update consumers to use the new async flow.

New Features:

  • Add bulk advisory audit support using the npm registry’s security advisories bulk endpoint, driven by the installed dependency tree.

Enhancements:

  • Refactor dependency tree traversal into reusable helpers for collecting installed package versions from pnpm ls output.
  • Normalize bulk advisory responses into a consistent advisories map keyed by GitHub advisory IDs or stable fallbacks.
  • Update frontend audit orchestration to call the shared async audit helper instead of invoking pnpm audit directly.
  • Improve audit-related documentation to describe the new fallback behavior and registry usage.

Build:

  • Change the Makefile audit target to route through the audit:validate script in line with the new validation flow.

Documentation:

  • Document the audit fallback path and pnpm bulk advisory usage in the repository structure guide.

Tests:

  • Add frontend audit utility tests covering native audit output, bulk advisory fallback behavior, and error handling for failed bulk endpoints.

📎 Task: https://www.devboxer.com/task/a1973a97-54b0-4120-bcbb-9fb0a437bef0

… pnpm audit

- Added fallback to npm bulk advisory endpoint when pnpm audit endpoint is retired.
- Improved resilience by parsing legacy and new audit outputs uniformly.
- Updated audit utilities to fetch installed package versions and query bulk advisories.
- Enhanced validate-audit.js to await asynchronous runAuditJson for advisory validation.
- Added comprehensive tests for the shared audit helper including bulk advisory fallback.
- Updated Makefile audit target to run audit:validate for consistent validation.

This enables seamless audit compliance checks despite registry audit endpoint changes.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 15, 2026

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Attempt pnpm audit --json first; if retired, fall back to npm’s bulk advisory endpoint. Change make audit to run pnpm run audit:validate. Make audit utilities and CLI async, add a bulk-advisory flow and end-to-end tests for both primary and fallback paths.

Changes

Cohort / File(s) Summary
Configuration & Documentation
Makefile, docs/repository-structure.md
Change audit target to run pnpm run audit:validate. Update docs to describe preferring pnpm audit --json and falling back to the npm bulk advisory endpoint; reflow Corepack enablement guidance.
Audit Utilities
security/audit-utils.js
Convert runAuditJson() to async; add parseJsonOutput() with blank-output handling; detect retired pnpm endpoint and implement bulk-advisory fallback: resolve registry, run pnpm ls --json --depth Infinity, collect package versions, POST to bulk endpoint, parse/validate response, extract GHSA IDs, normalise and deduplicate advisories; increase subprocess buffer and add execFileSync usage.
Validation & CLI Wrapper
security/validate-audit.js, frontend-pwa/scripts/run-audit.mjs
Await async runAuditJson() in validator. Make CLI main() async and await it before process.exit; update JSDoc/examples.
Test Coverage
frontend-pwa/scripts/audit-utils.test.mjs
Add Vitest suite mocking child_process and fetch; test pnpm success, retired-endpoint fallback path, non-OK bulk responses, advisory ID casing preservation, and empty-response parse errors.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI / Makefile
    participant AuditUtil as audit-utils
    participant PnpmAudit as pnpm audit (--json)
    participant PnpmLS as pnpm ls (--json --depth Infinity)
    participant Registry as Registry / pnpm config
    participant BulkAPI as npm bulk advisory endpoint
    participant Validator as validate-audit

    CLI->>AuditUtil: Invoke runAuditJson()
    AuditUtil->>PnpmAudit: Execute pnpm audit --json
    alt pnpm audit succeeds
        PnpmAudit-->>AuditUtil: JSON advisories + status
        AuditUtil->>Validator: Return advisories and status
    else pnpm audit retired (ERR_PNPM_AUDIT_BAD_RESPONSE)
        PnpmAudit-->>AuditUtil: Retired endpoint error
        AuditUtil->>Registry: Resolve registry URL (env or pnpm config)
        AuditUtil->>PnpmLS: Execute pnpm ls --json --depth Infinity
        PnpmLS-->>AuditUtil: Dependency tree JSON
        AuditUtil->>BulkAPI: POST package/version list to bulk endpoint
        BulkAPI-->>AuditUtil: Advisory array (or error)
        AuditUtil->>AuditUtil: Normalise advisories (extract GHSA IDs, de-duplicate)
        AuditUtil->>Validator: Return normalised advisories and status
    end
    Validator->>Validator: Evaluate and report results
Loading

Poem

🔎 Call pnpm first, let fallback take the stage,
Resolve the registry, walk dependency page.
Parse the JSON neat, normalise each line,
Async flows aligned, advisories combine.
Security checks run — report the final sign.

🚥 Pre-merge checks | ✅ 4 | ❌ 3

❌ Failed checks (3 warnings)

Check name Status Explanation Resolution
Testing ⚠️ Warning Critical helper functions added in this PR lack direct unit test coverage despite handling complex logic for dependency analysis and advisory fetching. Add unit tests for parseJsonOutput(), predicate helpers (isValidRegistryValue, isPlainAdvisoryObject, isExpectedAdvisory), extractGithubAdvisoryId(), and integration tests for collectInstalledPackageVersions() and runBulkAdvisoryAudit() error paths.
Developer Documentation ⚠️ Warning PR introduces significant architectural changes (async fallback to bulk advisory endpoint, 20+ new internal helpers, changed public API) but documents them only briefly in repository-structure.md, not in the canonical developers-guide.md. Add a new section to docs/developers-guide.md explaining the audit architecture, including the retired-endpoint detection mechanism, bulk advisory fallback flow, internal helpers' purpose and usage, async/Promise-based public API, and build requirements.
Module-Level Documentation ⚠️ Warning Modified modules lack module-level docstrings explaining their purpose, utility, and functions. Add JSDoc blocks to the top of each modified file documenting their purpose and responsibilities before all imports.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarises the main changes: refactoring audit-utils and introducing async bulk advisory fallback.
Description check ✅ Passed The description comprehensively documents all changes, rationale, test coverage, and backward-compatibility considerations across audit logic, frontend orchestration, tests, CI, and documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
User-Facing Documentation ✅ Passed Documentation for the user-facing make audit command was updated in docs/repository-structure.md to describe the fallback behaviour from pnpm audit to npm bulk advisory.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ci-audit-failure-frontend-pwa-k31pyk

Comment @coderabbitai help to get the list of available commands and usage tips.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Apr 15, 2026

Reviewer's Guide

Refactors the shared audit utilities to support a bulk advisory fallback when pnpm’s legacy audit endpoint is retired, updates the frontend and security validation flows to use the new async helper, wires the Makefile to the validation entry point, and adds tests and docs around the new behavior.

Sequence diagram for audit flow with bulk advisory fallback

sequenceDiagram
  actor Developer
  participant Makefile
  participant FrontendAudit as frontend_run_audit_mjs
  participant SecurityValidator as security_validate_audit_js
  participant AuditUtils as security_audit_utils_js
  participant Pnpm as pnpm_CLI
  participant Registry as npm_registry

  Developer->>Makefile: make audit
  Makefile->>Pnpm: pnpm -r install / run audit
  Makefile->>Pnpm: pnpm run audit:validate
  Pnpm->>SecurityValidator: execute validate-audit.js

  rect rgb(235, 245, 255)
    SecurityValidator->>AuditUtils: await runAuditJson()
    AuditUtils->>Pnpm: spawnSync pnpm audit --json
    Pnpm-->>AuditUtils: stdout, status
    AuditUtils->>AuditUtils: parseJsonOutput(stdout, pnpm_audit)
    alt legacy_audit_endpoint_retired
      AuditUtils->>AuditUtils: collectInstalledPackageVersions()
      AuditUtils->>Pnpm: spawnSync pnpm ls --json --depth Infinity
      Pnpm-->>AuditUtils: dependency_tree_json
      AuditUtils->>AuditUtils: walkDependencyTree / walkDependencySection
      AuditUtils->>AuditUtils: readRegistryUrl()
      alt registry_env_set
        AuditUtils->>AuditUtils: normaliseRegistryUrl(env_registry)
      else registry_env_missing
        AuditUtils->>Pnpm: execFileSync pnpm config get registry
        Pnpm-->>AuditUtils: registry_url
        AuditUtils->>AuditUtils: normaliseRegistryUrl(registry_url)
      end
      AuditUtils->>Registry: POST bulk_advisories (installed_versions)
      Registry-->>AuditUtils: bulk_advisories_json
      AuditUtils->>AuditUtils: normaliseBulkAdvisories()
      AuditUtils-->>SecurityValidator: { json:{advisories}, status }
    else legacy_audit_endpoint_available
      AuditUtils-->>SecurityValidator: { json, status }
    end
  end

  SecurityValidator->>SecurityValidator: collectAdvisories(json)
  SecurityValidator->>SecurityValidator: validateAgainstExceptionLedger()
  SecurityValidator-->>Pnpm: exit_code
  Pnpm-->>Makefile: exit_code
  Makefile-->>Developer: audit result

  Note over Developer,Registry: Frontend script frontend_run_audit_mjs uses
  Note over Developer,Registry: the same runAuditJson helper and fallback path.
Loading

Class diagram for updated audit-utils and consumers

classDiagram
  class AuditUtils {
    <<module>>
    +parseJsonOutput(stdout, commandLabel)
    +isRetiredAuditEndpoint(payload)
    +isLocalWorkspaceVersion(version)
    +shouldSkipPackageVersion(packageName, version)
    +addPackageVersion(versionsByPackage, packageName, version)
    +processDependencySection(section, versionsByPackage)
    +walkDependencySection(section, versionsByPackage)
    +walkDependencyTree(node, versionsByPackage)
    +collectInstalledPackageVersions()
    +normaliseRegistryUrl(rawRegistry)
    +readRegistryUrl()
    +extractGithubAdvisoryId(advisoryUrl)
    +deriveAdvisoryKey(packageName, advisory)
    +addPackageAdvisories(packageName, packageAdvisories, advisories)
    +normaliseBulkAdvisories(bulkPayload)
    +runBulkAdvisoryAudit()
    +runAuditJson()
  }

  class FrontendAuditRunner {
    <<script frontend_pwa_scripts_run_audit_mjs>>
    +collectAdvisories(json)
    +evaluateAudit(payload, options)
    +main()
  }

  class SecurityAuditValidator {
    <<script security_validate_audit_js>>
    +assertValidSchema(entries)
    +assertNoExpired(entries)
    +validateAudit(entries, advisories)
  }

  class PnpmCLI {
    <<external>>
    +audit_json()
    +ls_json_depth_Infinity()
    +config_get_registry()
  }

  class NpmRegistry {
    <<external>>
    +bulkAdvisoriesEndpoint
  }

  AuditUtils ..> PnpmCLI : uses_spawnSync_execFileSync
  AuditUtils ..> NpmRegistry : uses_bulk_advisory_endpoint

  FrontendAuditRunner ..> AuditUtils : uses_runAuditJson
  SecurityAuditValidator ..> AuditUtils : uses_runAuditJson
  SecurityAuditValidator ..> FrontendAuditRunner : uses_collectAdvisories

  note for AuditUtils "Exports asynchronous runAuditJson that encapsulates native pnpm audit and the bulk advisory fallback path."
Loading

File-Level Changes

Change Details Files
Add modular helpers for parsing audit output, walking dependency trees, and calling the registry bulk advisory endpoint, then integrate them into runAuditJson as an async fallback-aware helper.
  • Introduce constants for pnpm audit/ls arguments, registry defaults, buffer limits, dependency section names, and the retired endpoint marker message.
  • Add JSON parsing helper with clearer error messages that is reused across pnpm audit, pnpm ls, and bulk advisory responses.
  • Refactor dependency traversal into walkDependencyTree and walkDependencySection with helpers to filter out local/workspace versions and aggregate versions by package.
  • Add collectInstalledPackageVersions to call pnpm ls --json --depth Infinity and produce a sorted map of installed versions per package for the bulk advisory body.
  • Add helpers to resolve the registry URL from env or pnpm config get registry, normalise it, and default to npmjs.org with a trailing slash.
  • Implement advisory normalisation helpers that derive a stable key (GHSA ID when present), attach github_advisory_id and package_name, and flatten bulk advisories into a single advisories map.
  • Implement runBulkAdvisoryAudit to POST installed package versions to the registry bulk advisory endpoint, normalise the response, and return a pnpm-like { json, status } shape.
  • Change runAuditJson to be async, reuse the JSON parser, detect retired audit endpoint responses, and fall back to runBulkAdvisoryAudit when needed while preserving the original status semantics for native audits.
security/audit-utils.js
Update frontend audit orchestration to support the async runAuditJson helper and keep CLI invocation semantics consistent.
  • Adjust run-audit.mjs JSDoc comments to reflect the generalized audit source rather than pnpm audit specifically.
  • Change the main function to be async, await runAuditJson, and return the evaluated exit code based on the collected advisories.
  • Update the direct-execution path to await main() before exiting, preserving error handling and stderr reporting.
frontend-pwa/scripts/run-audit.mjs
Align the central security validator with the async audit helper and clarify its documentation around advisory sources.
  • Reword validate-audit.js JSDoc comments to describe advisories as coming from the shared audit helper instead of raw pnpm audit.
  • Change the validation entry point to await the async runAuditJson helper before collecting advisories and enforcing exception/mitigation rules.
security/validate-audit.js
Wire CI and documentation to the new audit validation flow and document the bulk advisory fallback behavior.
  • Update the Makefile audit target to run pnpm run audit:validate instead of the previous audit script, matching the new validation entry point.
  • Extend docs/repository-structure.md to explain that the shared audit helper first tries pnpm audit --json and falls back to the bulk advisory endpoint when legacy endpoints are retired, while reiterating the Corepack setup requirement.
Makefile
docs/repository-structure.md
Add tests around the new audit utilities, including the bulk advisory fallback behavior and error handling.
  • Create a Vitest suite that mocks node:child_process and global fetch to simulate pnpm audit, pnpm ls, registry resolution, and bulk advisory responses.
  • Test the happy path where pnpm audit returns advisories and the bulk advisory endpoint is not used.
  • Test the fallback path where pnpm audit returns the retired endpoint error, then validate that pnpm ls, registry resolution, and bulk advisory POST are called with the expected shapes and that advisories are normalised correctly.
  • Test the failure path where the bulk advisory endpoint returns a non-OK status, asserting that a clear error including status and endpoint is thrown.
frontend-pwa/scripts/audit-utils.test.mjs

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 15, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Bumpy Road Ahead

security/audit-utils.js: walkDependencyTree

What lead to degradation?

walkDependencyTree has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

Why does this problem occur?

A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.
A bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is levels of conditionals.

How to fix it?

Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the Bumpy Road code health issue.
A Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The EXTRACT FUNCTION refactoring is the primary response.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 15, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Bumpy Road Ahead

security/audit-utils.js: normaliseBulkAdvisories

What lead to degradation?

normaliseBulkAdvisories has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

Why does this problem occur?

A Bumpy Road is a function that contains multiple chunks of nested conditional logic inside the same function. The deeper the nesting and the more bumps, the lower the code health.
A bumpy code road represents a lack of encapsulation which becomes an obstacle to comprehension. In imperative languages there’s also an increased risk for feature entanglement, which leads to complex state management. CodeScene considers the following rules for the code health impact: 1) The deeper the nested conditional logic of each bump, the higher the tax on our working memory. 2) The more bumps inside a function, the more expensive it is to refactor as each bump represents a missing abstraction. 3) The larger each bump – that is, the more lines of code it spans – the harder it is to build up a mental model of the function. The nesting depth for what is considered a bump is levels of conditionals.

How to fix it?

Bumpy Road implementations indicate a lack of encapsulation. Check out the detailed description of the Bumpy Road code health issue.
A Bumpy Road often suggests that the function/method does too many things. The first refactoring step is to identify the different possible responsibilities of the function. Consider extracting those responsibilities into smaller, cohesive, and well-named functions. The EXTRACT FUNCTION refactoring is the primary response.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 15, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

Complex Conditional

security/audit-utils.js: addPackageVersion

What lead to degradation?

addPackageVersion has 1 complex conditionals with 2 branches, threshold = 2

Why does this problem occur?

A complex conditional is an expression inside a branch such as an if-statmeent which consists of multiple, logical operations. Example: if (x.started() && y.running()).Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.

How to fix it?

Apply the DECOMPOSE CONDITIONAL refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# complex_conditional.js
 function messageReceived(message, timeReceived) {
-   // Ignore all messages which aren't from known customers:
-   if (!message.sender &&
-       customers.getId(message.name) == null) {
+   // Refactoring #1: encapsulate the business rule in a
+   // function. A clear name replaces the need for the comment:
+   if (!knownCustomer(message)) {
      log('spam received -- ignoring');
      return;
    }
 
-  // Provide an auto-reply when outside business hours:
-  if ((timeReceived.getHours() > 17) ||
-      (timeReceived.getHours() < 8)) {
+  // Refactoring #2: encapsulate the business rule.
+  // Again, note how a clear function name replaces the
+  // need for a code comment:
+  if (outsideBusinessHours(timeReceived)) {
     return autoReplyTo(message);
   }
 
   pingAgentFor(message);
+}
+
+function outsideBusinessHours(timeReceived) {
+  // Refactoring #3: replace magic numbers with
+  // symbols that communicate with the code reader:
+  const closingHour = 17;
+  const openingHour = 8;
+
+  const hours = timeReceived.getHours();
+
+  // Refactoring #4: simple conditional rules can
+  // be further clarified by introducing a variable:
+  const afterClosing = hours > closingHour;
+  const beforeOpening = hours < openingHour;
+
+  // Yeah -- look how clear the business rule is now!
+  return afterClosing || beforeOpening;
 }

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

… normalization

- Introduce DEPENDENCY_SECTION_NAMES constant for consistent dependency sections.
- Extract processDependencySection for clearer iteration over dependencies.
- Refactor walkDependencyTree and walkDependencySection for clarity and correctness.
- Add shouldSkipPackageVersion helper to simplify package version filtering logic.
- Extract deriveAdvisoryKey and addPackageAdvisories helper functions to clean up normaliseBulkAdvisories.
- Improve comments and examples to clarify APIs and internal logic.

These changes improve code readability and maintainability without changing functionality.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title CI: Add bulk advisory fallback for pnpm audit and tests Refactor walkDependencyTree and add audit-utils helpers Apr 15, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos leynos marked this pull request as ready for review April 15, 2026 22:41
Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 2 issues

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="security/audit-utils.js" line_range="204-206" />
<code_context>
+ * addPackageAdvisories('validator', [{ id: 1, url: 'https://github.com/advisories/GHSA-abcd-1234-efgh' }], advisories);
+ * console.log(Object.keys(advisories).length); // 1
+ */
+function addPackageAdvisories(packageName, packageAdvisories, advisories) {
+  if (!Array.isArray(packageAdvisories)) {
+    return;
+  }
+
+  for (const advisory of packageAdvisories) {
+    const { key, githubAdvisoryId } = deriveAdvisoryKey(packageName, advisory);
+
+    if (key in advisories) {
+      continue;
+    }
</code_context>
<issue_to_address>
**suggestion:** Use `Object.hasOwn` instead of `in` when checking advisory map keys.

`key in advisories` will return true for properties on the prototype chain. Even though `advisories` is currently a plain object you control, using `Object.hasOwn(advisories, key)` (or `Object.prototype.hasOwnProperty.call`) makes this check resilient if `advisories` is ever created with a different prototype.

```suggestion
    if (Object.hasOwn(advisories, key)) {
      continue;
    }
```
</issue_to_address>

### Comment 2
<location path="security/audit-utils.js" line_range="71" />
<code_context>
+  versionsByPackage.set(packageName, knownVersions);
+}
+
+function processDependencySection(section, versionsByPackage) {
+  for (const [packageName, dependency] of Object.entries(section)) {
+    if (!dependency || typeof dependency !== 'object') {
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the new audit helpers by flattening the dependency traversal, inlining single‑use helpers, and merging small wrapper functions to cut down on indirection and make the control flow easier to follow.

You can keep all new behavior but reduce structural complexity with a few small refactors:

### 1. Flatten dependency traversal

`walkDependencyTree``walkDependencySection``processDependencySection` can be collapsed into a single traversal that handles sections + recursion in one place:

```js
function walkDependencies(node, versionsByPackage) {
  if (!node || typeof node !== 'object') {
    return;
  }

  for (const sectionName of DEPENDENCY_SECTION_NAMES) {
    const section = node[sectionName];
    if (!section || typeof section !== 'object') continue;

    for (const [packageName, dependency] of Object.entries(section)) {
      if (!dependency || typeof dependency !== 'object') continue;

      const version = typeof dependency.version === 'string' ? dependency.version : undefined;
      if (version) {
        addPackageVersion(versionsByPackage, packageName, version);
      }

      walkDependencies(dependency, versionsByPackage);
    }
  }
}
```

Then use it from `collectInstalledPackageVersions`:

```js
for (const tree of Array.isArray(packageTrees) ? packageTrees : [packageTrees]) {
  walkDependencies(tree, versionsByPackage);
}
```

You can then remove `walkDependencySection` and `processDependencySection`.

### 2. Inline `shouldSkipPackageVersion` into `addPackageVersion`

`shouldSkipPackageVersion` is only used once and just wraps a couple of checks. You can inline it to cut a layer without losing readability:

```js
function addPackageVersion(versionsByPackage, packageName, version) {
  const isMissing = !packageName || !version;
  if (isMissing || isLocalWorkspaceVersion(version)) {
    return;
  }

  const knownVersions = versionsByPackage.get(packageName) ?? new Set();
  knownVersions.add(version);
  versionsByPackage.set(packageName, knownVersions);
}
```

This keeps `isLocalWorkspaceVersion` as the meaningful semantic check, but removes an extra function hop.

### 3. Merge `normaliseBulkAdvisories` and `addPackageAdvisories`

You can keep `deriveAdvisoryKey` (it carries useful intent) but inline `addPackageAdvisories` into `normaliseBulkAdvisories`:

```js
function normaliseBulkAdvisories(bulkPayload) {
  const advisories = {};

  for (const [packageName, packageAdvisories] of Object.entries(bulkPayload ?? {})) {
    if (!Array.isArray(packageAdvisories)) continue;

    for (const advisory of packageAdvisories) {
      const { key, githubAdvisoryId } = deriveAdvisoryKey(packageName, advisory);
      if (key in advisories) continue;

      advisories[key] = {
        ...advisory,
        github_advisory_id: githubAdvisoryId,
        package_name: packageName,
      };
    }
  }

  return advisories;
}
```

This keeps the behavior the same while reducing one level of indirection.

### 4. Clarify `parseJsonOutput` argument naming

To make its dual use (spawn vs HTTP) clearer, you can rename the parameter and adjust call sites:

```js
function parseJsonOutput(payloadText, commandLabel) {
  const text = payloadText?.trim?.() ?? '';
  if (!text) return {};

  try {
    return JSON.parse(text);
  } catch (error) {
    error.message = `Failed to parse ${commandLabel} JSON: ${error.message}`;
    throw error;
  }
}
```

Usage:

```js
const packageTrees = parseJsonOutput(result.stdout, 'pnpm ls');
const bulkPayload = parseJsonOutput(responseText, 'bulk advisory audit');
const json = parseJsonOutput(stdout, 'pnpm audit');
```

These small consolidations should reduce the mental overhead of following the code paths without changing any functionality.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread security/audit-utils.js Outdated
Comment thread security/audit-utils.js Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c4da648d53

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread security/audit-utils.js Outdated
Comment thread security/audit-utils.js Outdated
…t responses

- Updated advisory ID extraction to preserve original casing instead of forcing uppercase.
- Enhanced parseJsonOutput to optionally require non-empty JSON strings, adding validation for empty audit responses.
- Refactored bulk advisory normalization logic to prevent advisory ID case normalization.
- Added tests to ensure advisory casing is preserved from bulk payload URL.
- Added tests to verify rejection of blank bulk advisory responses.
- Improved dependency tree walking function for clarity and maintainability.

Co-authored-by: devboxerhub[bot] <devboxerhub[bot]@users.noreply.github.com>
@leynos leynos changed the title Refactor walkDependencyTree and add audit-utils helpers Refactor audit-utils and add bulk advisory fallback (async) Apr 15, 2026
codescene-delta-analysis[bot]

This comment was marked as outdated.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend-pwa/scripts/audit-utils.test.mjs`:
- Around line 73-269: Extract the repeated two-step pnpm retired-endpoint stub
into a helper (e.g., setupPnpmAuditRetired or createRetiredPnpmFixture) that
calls spawnSyncMock.mockReturnValueOnce with the ERR_PNPM_AUDIT_BAD_RESPONSE
result and a second mockReturnValueOnce with the pnpm ls result (using existing
createPnpmResult), then replace the duplicated blocks in the tests that
currently call spawnSyncMock.mockReturnValueOnce(...) (the blocks around the
first and second mockReturnValueOnce in the tests using runAuditJson) with a
single call to that helper; keep references to spawnSyncMock and
createPnpmResult inside the helper so the tests otherwise remain unchanged.

In `@security/audit-utils.js`:
- Around line 27-233: Add JSDoc comments with compact outcome-driven `@example`
blocks to every helper function introduced in this diff (parseJsonOutput,
isRetiredAuditEndpoint, isLocalWorkspaceVersion, addPackageVersion,
walkDependencies, collectInstalledPackageVersions, normaliseRegistryUrl,
readRegistryUrl, extractGithubAdvisoryId, deriveAdvisoryKey,
normaliseBulkAdvisories, runBulkAdvisoryAudit): for each function document its
purpose, parameters and return value and include a minimal `@example` showing a
concrete input and the expected output (e.g., parseJsonOutput showing input JSON
string → parsed object, walkDependencies showing a small dependency tree →
versions map, extractGithubAdvisoryId showing URL → GHSA id,
normaliseBulkAdvisories showing sample bulk payload → normalized advisories,
runBulkAdvisoryAudit showing successful response → { json, status }), keeping
examples terse and outcome-focused.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 33f09b4f-d8e5-4c42-8f80-b4ce54845f89

📥 Commits

Reviewing files that changed from the base of the PR and between c4da648 and 172c334.

📒 Files selected for processing (2)
  • frontend-pwa/scripts/audit-utils.test.mjs
  • security/audit-utils.js

Comment thread frontend-pwa/scripts/audit-utils.test.mjs Outdated
Comment thread security/audit-utils.js
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 16, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

frontend-pwa/scripts/audit-utils.test.mjs

Comment on lines +173 to +203

  it('throws a clear error when the bulk advisory endpoint fails', async () => {
    spawnSyncMock
      .mockReturnValueOnce(
        createPnpmResult({
          status: 1,
          stdout: JSON.stringify({
            error: {
              code: 'ERR_PNPM_AUDIT_BAD_RESPONSE',
              message:
                'The audit endpoint responded with 410: {"error":"This endpoint is being retired. Use the bulk advisory endpoint instead."}',
            },
          }),
        }),
      )
      .mockReturnValueOnce(
        createPnpmResult({
          stdout: JSON.stringify([{ name: 'frontend-pwa', dependencies: {} }]),
        }),
      );
    fetch.mockResolvedValueOnce({
      ok: false,
      status: 503,
      statusText: 'Service Unavailable',
      text: async () => '{"error":"upstream unavailable"}',
    });
    const { runAuditJson } = await loadAuditUtils();

    await expect(runAuditJson()).rejects.toThrow(
      'Bulk advisory audit failed (503 Service Unavailable)',
    );
  });

❌ New issue: Code Duplication
The module contains 2 functions with similar structure: 'rejects blank bulk advisory responses instead of treating them as empty JSON','throws a clear error when the bulk advisory endpoint fails'

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 16, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

security/audit-utils.js

Comment on lines +85 to +108

function walkDependencies(node, versionsByPackage) {
  if (!node || typeof node !== 'object') {
    return;
  }

  for (const sectionName of DEPENDENCY_SECTION_NAMES) {
    const section = node[sectionName];
    if (!section || typeof section !== 'object') {
      continue;
    }

    for (const [packageName, dependency] of Object.entries(section)) {
      if (!dependency || typeof dependency !== 'object') {
        continue;
      }

      if (typeof dependency.version === 'string') {
        addPackageVersion(versionsByPackage, packageName, dependency.version);
      }

      walkDependencies(dependency, versionsByPackage);
    }
  }
}

❌ New issue: Bumpy Road Ahead
walkDependencies has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 16, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

security/audit-utils.js

Comment on lines +179 to +203

function normaliseBulkAdvisories(bulkPayload) {
  const advisories = {};

  for (const [packageName, packageAdvisories] of Object.entries(bulkPayload ?? {})) {
    if (!Array.isArray(packageAdvisories)) {
      continue;
    }

    for (const advisory of packageAdvisories) {
      const { key, githubAdvisoryId } = deriveAdvisoryKey(packageName, advisory);

      if (Object.hasOwn(advisories, key)) {
        continue;
      }

      advisories[key] = {
        ...advisory,
        github_advisory_id: githubAdvisoryId,
        package_name: packageName,
      };
    }
  }

  return advisories;
}

❌ New issue: Bumpy Road Ahead
normaliseBulkAdvisories has 2 blocks with nested conditional logic. Any nesting of 2 or deeper is considered. Threshold is 2 blocks per function

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 16, 2026

@coderabbitai Please suggest a fix for the following concern and provide an AI coding agent prompt for the fix:

normaliseBulkAdvisories should be spelled normalizeBulkAdvisories()

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

leynos added 2 commits April 16, 2026 22:27
Extract version-map construction and advisory line formatting\nhelpers in security/audit-utils.js while preserving behaviour.\n\nThis complements the earlier dependency-section extraction and brings\nthe file's measured mean cyclomatic complexity below 4 without\npushing any individual function above 9.\n\nValidated with:\n- make check-fmt\n- make lint\n- make test\n- AST-based complexity check (mean 3.95, max 7)
Document the audit-utils test helpers with terse JSDoc so their\nmocking contracts stay explicit.\n\nValidate bulk advisory entries before spreading them into the\naccumulator, and add a fetch timeout to the bulk advisory fallback\nso the audit flow fails clearly instead of hanging indefinitely.\n\nAlso complete the addPackageAdvisories JSDoc with an explicit\n tag and outcome-focused example while keeping the file\nunder the repository line-limit rule.\n\nValidated with:\n- make check-fmt\n- make lint\n- make test
codescene-delta-analysis[bot]

This comment was marked as outdated.

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@security/audit-utils.js`:
- Around line 238-242: The JSDoc comment for the function
normalizeBulkAdvisories uses British spelling "Normalise" while the function
name uses American spelling "normalize"; update the JSDoc summary and any other
occurrences in that comment block to "Normalize" so the wording matches the
function name normalizeBulkAdvisories and remains consistent across the file.
- Around line 158-167: The function name and JSDoc use the en-GB "normalise"
spelling which conflicts with the project's en-GB-oxendict policy (and with
other functions like normalizeBulkAdvisories); rename the function
normaliseRegistryUrl to normalizeRegistryUrl, update its JSDoc/comment to
"Normalize", and update all call sites and any exports/imports that reference
normaliseRegistryUrl to use normalizeRegistryUrl so symbol names remain
consistent across the module.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 126c5b72-27e0-41d7-ad36-e6b6f67995f8

📥 Commits

Reviewing files that changed from the base of the PR and between d96bd63 and 1a6f8ac.

📒 Files selected for processing (2)
  • frontend-pwa/scripts/audit-utils.test.mjs
  • security/audit-utils.js

Comment thread security/audit-utils.js Outdated
Comment thread security/audit-utils.js Outdated
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 16, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

security/audit-utils.js

Comment on lines +259 to +265

async function runBulkAdvisoryAudit() {
  const registryUrl = readRegistryUrl();
  const endpoint = new URL(BULK_ADVISORY_PATH, registryUrl);
  const controller = new AbortController();
  const timeoutId = setTimeout(() => controller.abort(), BULK_AUDIT_TIMEOUT_MS);
  let response;
  let responseText;

❌ New issue: Complex Method
runBulkAdvisoryAudit has a cyclomatic complexity of 9, threshold = 9
❌ New issue: Overall Code Complexity
This module has a mean cyclomatic complexity of 4.45 across 20 functions. The mean complexity threshold is 4

@coderabbitai

This comment was marked as resolved.

Rename `normaliseRegistryUrl` to `normalizeRegistryUrl` and update
its local call sites so the helper spelling matches the rest of the
module.

Also align the `normalizeBulkAdvisories` JSDoc summary with the
function name.

Validated with:
- make check-fmt
- make lint
- make test
codescene-delta-analysis[bot]

This comment was marked as outdated.

coderabbitai[bot]

This comment was marked as resolved.

leynos added 2 commits April 16, 2026 23:42
Extract the bulk advisory fetch and result shaping logic out of
`runBulkAdvisoryAudit` so the fallback audit path stays easier to
read and remains below the repository complexity thresholds.

Validated with:
- make check-fmt
- make lint
- make test
- AST-based complexity check (mean 3.56, max 7)
Fail closed when normalized bulk advisory payload entries are not
arrays, reject empty `pnpm ls` output before parsing, and add the
requested outcome-focused examples to the remaining helper JSDoc
blocks.

Validated with:
- make check-fmt
- make lint
- make test
codescene-delta-analysis[bot]

This comment was marked as outdated.

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

♻️ Duplicate comments (1)
security/audit-utils.js (1)

240-245: ⚠️ Potential issue | 🟠 Major

Validate the bulk payload object before iterating it.

A primitive or array payload still falls through Object.entries(...) and turns into {}, which hides a broken registry response as a clean audit result.

🛠️ Proposed fix
 function normalizeBulkAdvisories(bulkPayload) {
+  const isPlainObject =
+    typeof bulkPayload === 'object' && bulkPayload !== null && !Array.isArray(bulkPayload);
+  if (!isPlainObject) {
+    throw new TypeError(
+      'Invalid bulk advisory payload: expected an object keyed by package name.',
+    );
+  }
+
   const advisories = {};
 
-  for (const [packageName, packageAdvisories] of Object.entries(bulkPayload ?? {})) {
+  for (const [packageName, packageAdvisories] of Object.entries(bulkPayload)) {
     if (!Array.isArray(packageAdvisories)) {
       throw new TypeError(`Invalid bulk advisory entry for package ${packageName}: expected array, received ${JSON.stringify(packageAdvisories)}`);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@security/audit-utils.js` around lines 240 - 245, In normalizeBulkAdvisories,
validate that bulkPayload is a plain object before iterating: check typeof
bulkPayload === 'object' && bulkPayload !== null && !Array.isArray(bulkPayload),
and if that check fails throw a TypeError like "Invalid bulk advisory payload:
expected an object keyed by package name."; then remove the fallback
Object.entries(bulkPayload ?? {}) and iterate Object.entries(bulkPayload) so
primitive/array inputs no longer silently become {}.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@security/audit-utils.js`:
- Around line 254-257: Add outcome-driven `@example` blocks to the JSDoc for
fetchBulkAdvisories and toAdvisoryResult: update the fetchBulkAdvisories comment
(function name fetchBulkAdvisories) to include an example showing calling it
with a registry URL and a sample packageVersions input and the expected returned
structure (e.g., { response, responseText: '{}' } when registry returns {}), and
update the toAdvisoryResult comment (function name toAdvisoryResult) to include
a simple example demonstrating input {} producing { json: { advisories: {} },
status: 0 }; keep examples concise and outcome-focused and place them in the
existing JSDoc blocks around the function signatures.
- Around line 120-123: Reject malformed pnpm ls JSON in buildVersionMap by
validating inputs before walking dependencies: add an isDependencyTreeNode check
that ensures packageTrees (or each element when an array) is a non-null object
and not an array, throw a TypeError('pnpm ls returned an invalid dependency tree
payload.') if validation fails, then assign trees = Array.isArray(packageTrees)
? packageTrees : [packageTrees] and iterate trees with walkDependencies to
populate versionsByPackage (instead of collapsing scalars to an empty map).
Apply the same validation logic to the other place where you currently
iterate/normalize packageTrees (the second occurrence mentioned) so both code
paths fail closed on malformed payloads.

---

Duplicate comments:
In `@security/audit-utils.js`:
- Around line 240-245: In normalizeBulkAdvisories, validate that bulkPayload is
a plain object before iterating: check typeof bulkPayload === 'object' &&
bulkPayload !== null && !Array.isArray(bulkPayload), and if that check fails
throw a TypeError like "Invalid bulk advisory payload: expected an object keyed
by package name."; then remove the fallback Object.entries(bulkPayload ?? {})
and iterate Object.entries(bulkPayload) so primitive/array inputs no longer
silently become {}.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a425acf7-4693-4c7c-b705-2156c47b5395

📥 Commits

Reviewing files that changed from the base of the PR and between 81aabee and d5e0ecb.

📒 Files selected for processing (1)
  • security/audit-utils.js

Comment thread security/audit-utils.js
Comment thread security/audit-utils.js
@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 17, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response.

security/audit-utils.js

Comment on file

 */
export function runAuditJson() {
  const result = spawnSync('pnpm', ['audit', '--json'], {
/** @file Shared helpers for dependency audits and advisory filtering. */

❌ New issue: Overall Code Complexity
This module has a mean cyclomatic complexity of 4.27 across 22 functions. The mean complexity threshold is 4

@coderabbitai

This comment was marked as resolved.

leynos added 2 commits April 17, 2026 02:07
Validate bulk advisory and pnpm dependency-tree payloads before\nprocessing them so malformed upstream JSON fails closed instead of\nquietly degrading to empty results.\n\nAdd the missing outcome-focused JSDoc examples for the extracted bulk\naudit helpers while keeping the file within the repository line limit.
Add three private predicate helpers in `security/audit-utils.js`
to reduce per-function branching and lower the module's mean
cyclomatic complexity.

This keeps behaviour unchanged while making the registry,
advisory-shape, and allow-list checks reusable and easier to
scan. A code-only complexity check puts the file at a mean of
3.58 with a maximum individual function complexity of 7.
codescene-delta-analysis[bot]

This comment was marked as outdated.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 17, 2026

@coderabbitai Please suggest a fix for this issue and supply a prompt for an AI coding agent to enable it to apply the fix. Include the file and symbol names indicated in the issue at the head of your response. Ensure that this is validated against the current version of the codegraph.

If further refinement to address this finding would be deleterious, please supply a clear explanatory one to two paragraph markdown message I can paste into the CodeScene web ui's diagnostic suppression function so this diagnostic can be silenced.

Overall Code Complexity

security/audit-utils.js:

Complex Conditional

security/audit-utils.js: normalizeBulkAdvisories

What lead to degradation?

normalizeBulkAdvisories has 1 complex conditionals with 2 branches, threshold = 2
This module has a mean cyclomatic complexity of 4.08 across 26 functions. The mean complexity threshold is 4

Why does this problem occur?

A complex conditional is an expression inside a branch such as an if-statmeent which consists of multiple, logical operations. Example: if (x.started() && y.running()).Complex conditionals make the code even harder to read, and contribute to the Complex Method code smell. Encapsulate them.

How to fix it?

Apply the DECOMPOSE CONDITIONAL refactoring so that the complex conditional is encapsulated in a separate function with a good name that captures the business rule. Optionally, for simple expressions, introduce a new variable which holds the result of the complex conditional.

Helpful refactoring examples

To get a general understanding of what this code health issue looks like - and how it might be addressed - we have prepared some diffs for illustrative purposes.

SAMPLE

# complex_conditional.js
 function messageReceived(message, timeReceived) {
-   // Ignore all messages which aren't from known customers:
-   if (!message.sender &&
-       customers.getId(message.name) == null) {
+   // Refactoring #1: encapsulate the business rule in a
+   // function. A clear name replaces the need for the comment:
+   if (!knownCustomer(message)) {
      log('spam received -- ignoring');
      return;
    }
 
-  // Provide an auto-reply when outside business hours:
-  if ((timeReceived.getHours() > 17) ||
-      (timeReceived.getHours() < 8)) {
+  // Refactoring #2: encapsulate the business rule.
+  // Again, note how a clear function name replaces the
+  // need for a code comment:
+  if (outsideBusinessHours(timeReceived)) {
     return autoReplyTo(message);
   }
 
   pingAgentFor(message);
+}
+
+function outsideBusinessHours(timeReceived) {
+  // Refactoring #3: replace magic numbers with
+  // symbols that communicate with the code reader:
+  const closingHour = 17;
+  const openingHour = 8;
+
+  const hours = timeReceived.getHours();
+
+  // Refactoring #4: simple conditional rules can
+  // be further clarified by introducing a variable:
+  const afterClosing = hours > closingHour;
+  const beforeOpening = hours < openingHour;
+
+  // Yeah -- look how clear the business rule is now!
+  return afterClosing || beforeOpening;
 }

@coderabbitai

This comment was marked as resolved.

@leynos
Copy link
Copy Markdown
Owner Author

leynos commented Apr 17, 2026

@coderabbitai pause

Replace the inline bulk advisory payload object check with the
existing `isPlainAdvisoryObject` helper.

This keeps the behaviour unchanged while reducing duplication in
`normalizeBulkAdvisories`. Repo formatting, lint, and test
gates all passed before commit.
codescene-delta-analysis[bot]

This comment was marked as outdated.

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@security/audit-utils.js`:
- Around line 314-316: The JSDoc for the async function runAuditJson incorrectly
declares a plain object return; update the `@returns` annotation to a Promise of
that object (e.g. change `@returns` {{ json: { advisories?: Record<string,
unknown> }, status: number }} to `@returns` {Promise<{ json: { advisories?:
Record<string, unknown> }, status: number }>} ) so editor hints and the awaited
example match the async runAuditJson() implementation.
- Around line 153-156: Add concise JSDoc `@example` blocks to the predicate
helpers isValidRegistryValue, isPlainAdvisoryObject, and isExpectedAdvisory
showing minimal input and the expected outcome; for each function include a one-
or two-line example that calls the function with representative input and shows
the return value (e.g., isValidRegistryValue('https://registry') -> true), place
the `@example` directly in the existing JSDoc above each function, and ensure
examples are outcome-driven (input followed by the expected returned value) and
consistent with the function's documented behavior.
- Around line 191-196: The extractGithubAdvisoryId function returns GHSA IDs
preserving original URL casing which breaks case-sensitive allowlist lookups;
change the matching to capture the hex groups and return a canonical lowercase
GHSA string (e.g., build "GHSA-" + capturedGroups.toLowerCase()) so extracted
IDs always match audit-exceptions.schema.json and isExpectedAdvisory Set
lookups; update any tests that asserted preserved casing.
🪄 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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 19f095fc-8b1b-4dd8-99a0-9ae8d0b0a331

📥 Commits

Reviewing files that changed from the base of the PR and between d5e0ecb and 2aa1769.

📒 Files selected for processing (1)
  • security/audit-utils.js

Comment thread security/audit-utils.js
Comment thread security/audit-utils.js Outdated
Comment thread security/audit-utils.js
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 17, 2026

✅ Actions performed

Reviews paused.

Canonicalize GHSA IDs extracted from bulk advisory URLs so\nallowlist lookups are case-stable.\n\nAlso fix the async runAuditJson() JSDoc return type and add\noutcome-driven examples to the predicate helper documentation.\n\nGates passed:\n- make check-fmt\n- make lint\n- make test
@leynos leynos merged commit 56d255f into main Apr 17, 2026
3 of 4 checks passed
@leynos leynos deleted the fix-ci-audit-failure-frontend-pwa-k31pyk branch April 17, 2026 11:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant