Skip to content

feat: DI-aware call resolution for TypeScript/NestJS codebases#39

Draft
joshbouncesecurity wants to merge 2 commits intoknostic:masterfrom
joshbouncesecurity:feat/issue16-07-ts-di
Draft

feat: DI-aware call resolution for TypeScript/NestJS codebases#39
joshbouncesecurity wants to merge 2 commits intoknostic:masterfrom
joshbouncesecurity:feat/issue16-07-ts-di

Conversation

@joshbouncesecurity
Copy link
Copy Markdown
Contributor

@

Summary

The TypeScript parser doesn't extract constructor parameter types, so dependency-injected service calls (e.g., this.userService.findById()) end up unresolved in the call graph. This means security analysis silently misses data flow through injected services — a significant blind spot for typical NestJS apps.

This PR adds DI-aware resolution by:

  • Extracting constructorDeps metadata from the TypeScript AST.
  • Using that metadata to resolve this.service.method() calls to the correct target class.
  • Updating the agentic enhancer to consume the new metadata when resolving call edges.

Addresses item 7 from #16 (does not close the issue).

Test plan

  • Parse a small NestJS-style sample with a service injected via constructor; verify this.svc.method() resolves to the correct class in the call graph output.
  • Sample without DI: parser output unchanged.
  • Existing JS/TS tests still pass.
    @

* feat: DI-aware call resolution for TypeScript/NestJS codebases

The parser couldn't resolve dependency-injected service calls like
`this.callService.getById()` because it didn't know that `callService`
is an instance of `CallService`. This caused the agentic enhancer to
miss critical authorization checks in service layers, producing false
positive vulnerability findings.

Changes:
- typescript_analyzer.js: Extract constructor parameter types as
  `constructorDeps` metadata on class methods using ts-morph AST
- dependency_resolver.js: Use constructorDeps for DI-aware resolution
  in _resolveMethodCall, with prefix matching for versioned
  implementations (e.g., CallService -> CallServiceV1)
- Agentic enhancer: Add forward-tracing instructions to the prompt
  so the agent traces into called functions for auth/validation checks
- Agentic enhancer: Add get_static_dependencies tool to surface
  parsed call graph data to the exploration agent
- Agentic enhancer: Pass static deps to tool executor before analysis

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* test: add tests for DI-aware call resolution and enhancer tools

- test_di_resolution.py: Tests constructor deps extraction from
  TypeScript AST and DI-aware method resolution in call graphs,
  including versioned implementations and false positive prevention
- test_enhancer_tools.py: Tests resolve_dependencies and the
  get_static_dependencies tool via ToolExecutor

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
fix(tests): replace missing run_utf8 import with subprocess.run

test_di_resolution.py imported `run_utf8` from `utilities.file_io`,
which does not exist in this repo. The import made the test module
unimportable and broke pytest collection for the file (and any wider
collection that included it). Mirror the helper used in
test_js_parser.py and call subprocess.run directly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@
@joshbouncesecurity
Copy link
Copy Markdown
Contributor Author

Manual verification

Sample NestJS-style TypeScript:

@Injectable()
class UserService {
  findById(id: string) { return null; }
}

class UserController {
  constructor(private userService: UserService) {}
  get(id: string) { return this.userService.findById(id); }
}
  • openant parse <repo-with-above>: call graph for UserController.get includes an edge to UserService.findById (was empty before this PR).
  • Multi-dep constructor (constructor(private a: A, private b: B)): edges resolve for both this.a.x() and this.b.y().
  • Plain JS file (no type annotations): no crash; parser proceeds, no DI edges (graceful degradation).
  • Primitive-typed param (constructor(private name: string)): not treated as a DI target (the type guard skips primitives).
  • On a real NestJS app: visual sanity-check that this.<svc>.<method>() calls resolve to the right class in dataset.json.

@joshbouncesecurity
Copy link
Copy Markdown
Contributor Author

Local test results

Built a tiny inline NestJS-style fixture with a constructor-injected service and ran the JS analyzer + unit_generator from this branch.

Fixture (.worktrees/_fixtures/nest_di/):

// user.service.ts
export class UserService {
  findById(id: string) { return { id, name: "alice" }; }
}

// user.controller.ts
import { UserService } from "./user.service";
export class UserController {
  constructor(private userService: UserService) {}
  get(id: string) { return this.userService.findById(id); }
}

Commands run:

# Note: had to feed the analyzer a forward-slash file_list to bypass an unrelated
# Windows path bug in this branch (fixed by #46). On Linux/macOS test_pipeline.py
# would have driven this end-to-end with no extra steps.
node typescript_analyzer.js <repo> --files-from posix_list.txt --output analyzer_output.json
node unit_generator.js analyzer_output.json --output dataset.json

Outcome:

  • Analyzer extracts new constructorDeps metadata: 'constructorDeps': {'userService': 'UserService'} on UserController.get
  • In the resulting dataset.json, UserController.get.metadata.direct_calls includes 'user.service.ts:UserService.findById' — the DI-resolved edge that was missing pre-fix ✅
  • UserService.findById.metadata.direct_callers correspondingly includes 'user.controller.ts:UserController.get'
  • Did not separately exercise multi-dep / primitive-typed-param / plain-JS / real-NestJS-app paths — covered by the unit tests in the diff.

Side note (unrelated to this PR): there is a self-edge (UserController.get -> UserController.get) in the dataset that looks spurious. May be worth a quick look but doesn't affect this PR's correctness.

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.

1 participant