Skip to content

fix: RESOLVES_TO gated on project-internal signatures — 71% of callsites unresolved, all external/library calls dropped (CLDK-001) #53

Description

@rahlk

From the external quality audit of the Juice Shop v1 export (schema 1.0.0), finding CLDK-001 (severity Critical). The resolved edges are precise — the problem is coverage.

Audit evidence

  • Only 1,802 of 6,271 CallSite nodes have a RESOLVES_TO edge (28.7%). Of the resolved non-constructor calls, 1,800/1,800 pass target-name equality — so what resolves is right, and what doesn't resolve includes exactly the security-relevant surface: eval(...), Sequelize .query(...), res.redirect(...), model lookups like User.findOne(...).

Root cause

callee_signature is only kept when the ts-morph checker maps the call to a declaration inside the project's own symbol table:

  1. resolveCalleeSignature (src/schema/signatures.ts:85-118) resolves via the checker, then gates the result against allSignatures — the set of project-declared callables (src/semantic_analysis/callGraph.ts:48-51). Declarations in node_modules, ambient .d.ts, or lib globals are resolved and then rejected (signatures.ts:98, 106, 113-115).
  2. The phantom fallback (src/semantic_analysis/phantoms.ts:86-103) only rescues calls made through a directly-named bare-package import binding (import x from "pkg"; x.m() / require("pkg").m()). It returns null for globals (eval), and for any instance/property receiver (res.redirect, db.query, User.findOne) — the receiver base is a parameter/local/class instance, not an import binding.
  3. resolveCalleeDecl also returns nothing for element access (obj["m"]()) and any-typed receivers (signatures.ts:71, 73).

The projection then simply skips the edge (src/build/neo4j/project.ts:239), and RowBuilder.finish() drops any deferred edge whose target node was never materialized (src/build/neo4j/rows.ts:97-98).

Fix direction

Materialize checker-resolved external callees as :External ghost targets instead of discarding them (parity with the Python analyzer's external_symbols, cf. codeanalyzer-python#44), so library calls keep a resolution edge; report resolution coverage separately by callsite category (audit gate: ≥95% name precision on resolvable non-constructor calls, coverage reported per category). Verify the v2 pipeline (RESOLVES_TO from BodyNode) does not inherit the allSignatures gate.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions