HeadyZhang/agent-audit

typescript_scanner: TS AST path silently disabled due to stale `tree_sitter_typescript` language-module API

Open

#12 opened on Jul 4, 2026

View on GitHub
 (0 comments) (0 reactions) (0 assignees)Python (21 forks)github user discovery
bughelp wanted

Repository metrics

Stars
 (180 stars)
PR merge metrics
 (PR metrics pending)

Description

Summary

TreeSitterParser._init_tree_sitter in packages/audit/agent_audit/parsers/treesitter_parser.py recognizes two language-module APIs: a LANGUAGE module attribute (used by tree_sitter_python ≥0.23) and a language() module function (used by tree_sitter_javascript). It does not recognize the API exposed by tree_sitter_typescript ≥0.23, which uses language_typescript() and language_tsx() (two separate functions for the TypeScript / TSX dialects) instead of a single language symbol.

As a result, when tree_sitter_typescript is installed and a .ts/.tsx file is scanned, _init_tree_sitter falls through to else: return and is_tree_sitter_available remains False. The TypeScript scanner then transparently falls back to its regex path.

User impact

None end-to-end. The regex fallback in typescript_scanner._scan_dangerous_calls_regex implements the same detection surface as the tree-sitter AST path — including the 0.19.2 Redis EVAL false-positive fix (bare eval(...) via negative-lookbehind, explicit window / globalThis / global / self.eval aliases, eval(args): Type declaration skip). Smoke-tested on the PyPI 0.19.2 install: redisClient.eval(luaScript) is not flagged; eval(userInput) is flagged with AGENT-034 BLOCK. So users are not currently missing any real detection because of this bug.

This issue is scoped to (a) TS AST branches being dead code across all environments (including CI, developer laptops, and end-user installs), (b) the fact that the downgrade happens with no user-visible signal, and (c) the resulting under-reported coverage on Codecov even after the tree-sitter extra is installed (see companion CI issue).

Root cause

packages/audit/agent_audit/parsers/treesitter_parser.py:191-196:

# tree-sitter-python 0.23+ uses LANGUAGE attribute
if hasattr(parser_module, 'LANGUAGE'):
    lang = parser_module.LANGUAGE
elif hasattr(parser_module, 'language'):
    lang = parser_module.language()
else:
    return

Reproduction:

>>> import tree_sitter_typescript as mod
>>> [x for x in dir(mod) if not x.startswith('_')]
['language_tsx', 'language_typescript']
>>> hasattr(mod, 'LANGUAGE'), hasattr(mod, 'language')
(False, False)

Neither the LANGUAGE attribute nor the language() function exists on the module. The else: return at line 195 is taken, _use_tree_sitter stays False, and no log line is emitted (this early return is not the except block at line 203-205, which uses logger.debug — this branch is silent).

Proposed fix

Two changes are needed. The exact language-key wiring is a design choice that should be confirmed against the LANGUAGE_EXTENSIONS mapping in treesitter_parser.py:128-137 before implementation.

1. Handle the tree_sitter_typescript API in _init_tree_sitter

Verified facts:

  • tree_sitter_typescript (≥0.23) exposes language_typescript() and language_tsx().
  • The current LANGUAGE_EXTENSIONS mapping sends both .ts and .tsx files to self.language == 'typescript'.

Given the current mapping, the minimal, non-speculative fix is to add a single branch that resolves the typescript key to language_typescript():

# Sketch — exact key(s) to be confirmed against LANGUAGE_EXTENSIONS and any
# dialect handling elsewhere in the parser.
elif self.language == 'typescript' and hasattr(parser_module, 'language_typescript'):
    lang = parser_module.language_typescript()

This makes both .ts and .tsx files use the pure-TypeScript grammar. Pure TypeScript parses correctly; JSX embedded in .tsx may not (tree_sitter_typescript ships a separate language_tsx() grammar for that dialect precisely because JSX is not valid TS).

Whether to split .tsx into its own self.language == 'tsx' key and add a language_tsx() branch is a follow-up design decision — it requires splitting the LANGUAGE_EXTENSIONS mapping and confirming no other code assumes .tsx → 'typescript'. Not blocking for this issue; call it out and defer.

2. Elevate the silent-downgrade signal

  • Change the silent return at line 195 into logger.warning(f"tree-sitter language module for {self.language} does not expose a known language accessor; falling back to regex"). As-is, an integration bug like this cannot be caught in production without directly inspecting is_tree_sitter_available.
  • Consider changing the except Exception fallback at line 203-205 from logger.debug to logger.warning for the same reason.

Verification plan

After the fix:

  1. poetry install -E tree-sitter && poetry run python -c "from agent_audit.parsers.treesitter_parser import TreeSitterParser; p = TreeSitterParser(source='eval(x);', file_path='x.ts'); assert p.is_tree_sitter_available" — should not raise.
  2. Re-run pytest tests/test_typescript_scanner.py --cov=agent_audit.scanners.typescript_scanner --cov-report=term-missing after installing the tree-sitter extra — the missing ranges 381-454 and 459-520 (AST paths) should shrink significantly.
  3. TestEvalMemberCallDistinction should continue to pass — the AST path implements the same fix as the regex fallback.

Target

0.20.0. This is not a hotfix candidate for a patch release: user-visible behavior is correct, and the fix requires (a) the CI-side install change in the companion issue to be measurable, and (b) a light review of the .tsx dialect handling before choosing between the single-branch and split-mapping fixes.

Contributor guide