CycloneDX / CycloneDX/specification

[Defect]: traverseSchema lacks cycle detection and produces ambiguous dot paths

Open
#832 0 comments 0 reactions 0 assignees View on GitHub
defect
Dominant language
XSLT
Stars
547
Forks
93
Avg merge
7h 11m
Merged PRs (30d)
37

Description

## Describe the defect

`traverseSchema` currently has two reliability/diagnostic issues:

- **Ambiguous path output:** It builds paths using dot-notation (`${path}.${k}`), which becomes ambiguous when a key contains `.` or other non-identifier characters. Example: a literal key `"a.b"` is reported as `$.a.b` (looks like nested keys).
- **No cycle detection:** If a schema object becomes circular in-memory (e.g., programmatic construction, loader back-references, alias graphs), traversal can recurse indefinitely and crash with `Maximum call stack size exceeded`.

**Scope:** `tools/src/main/js/linter/index.js` only.

## Proposed fix

- Add **stack-based cycle detection** using a `WeakSet` of nodes on the current recursion path (add on entry, remove on unwind) so only true cycles are skipped while shared references (DAGs) remain traversable.
- Introduce **safe path joining**:
- dot notation for simple identifiers
- bracket notation for non-identifier keys (e.g. `$["a.b"]`)

Optional hardening (kept backward-compatible):
- Add an opt-in `maxDepth` guard (default `Infinity`) with an `onDepthLimit` hook so truncation is never silent when enabled.

## Alternatives considered

- Implement cycle detection only (leave paths unchanged) — avoids crashes but keeps ambiguous diagnostics.
- Implement safe paths only (leave cycles unchanged) — improves diagnostics but doesn’t prevent crashes.
- Add depth-only protection without cycle detection — may help with deep recursion but does not address true cycles.

## Additional context
Observed while working on the 2.0 schema tooling
- This affects issue paths emitted by all checks that rely on `traverseSchema`.
- Backwards compatibility: path formatting changes only for keys that are not simple identifiers; default traversal behavior remains unchanged unless a finite `maxDepth` is explicitly set.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.