Skip to content

fix(analyzers): resolve import aliases in AST and taint analyzers#115

Merged
rng1995 merged 1 commit into
NVIDIA:mainfrom
zied-jlassi:fix/import-alias-evasion-ast-taint
Jun 22, 2026
Merged

fix(analyzers): resolve import aliases in AST and taint analyzers#115
rng1995 merged 1 commit into
NVIDIA:mainfrom
zied-jlassi:fix/import-alias-evasion-ast-taint

Conversation

@zied-jlassi

Copy link
Copy Markdown
Contributor

Closes #114.

Problem

behavioral_ast and behavioral_taint_tracking resolved call names purely
syntactically, so dangerous calls imported under an alias were missed — a skill could
evade detection just by renaming the import:

from os import system; system("id")          # AST5 missed
import os as o; o.system("id")                # AST5 missed
from subprocess import run; run([...])        # AST4 missed
import subprocess as sp; sp.run(secret)       # AST4 / taint flow missed

Fix

  • Expose the existing import-alias scan as build_import_aliases() — a thin wrapper over
    _build_import_aliases, which build_type_map already uses.
  • Add an apply_import_aliases(name, aliases) helper.
  • Thread an optional aliases argument through resolve_call_name() and
    resolve_call_name_typed() (backward compatible — defaults to None).
  • Both analyzers compute the alias map once per file and pass it through.
  • Aliases are normalized before the type-map lookup so already-canonical names are
    not re-expanded (e.g. from socket import socket must not yield
    socket.socket.socket.recv).

Tests

  • New TestImportAliasEvasion in both analyzer test modules: aliased dangerous forms
    are now detected (AST4/AST5/AST8, TT3/TT5), while aliased-but-safe imports
    produce no findings.
  • The existing behavioral suites continue to pass (33 AST + 46 taint).

Scope / follow-ups (intentionally out of this PR)

  • Reflection-based calls such as getattr(os, "system")(...) remain out of scope
    (pre-existing; AST7 only flags non-constant attribute access).
  • The alias map is module-level (no per-scope shadowing analysis). It can over-detect in
    contrived variable-shadowing cases, which errs on the safe side for a security tool.

behavioral_ast and behavioral_taint_tracking resolved call names purely
syntactically, so dangerous calls imported under an alias were missed:

    from os import system        # system("id")        -> AST5 missed
    import os as o               # o.system("id")       -> AST5 missed
    from subprocess import run   # run([...])           -> AST4 missed
    import subprocess as sp      # sp.run(secret)       -> AST4 / TT missed

A skill could evade detection simply by importing the primitive under
another name. This reuses the existing import-alias scan
(_build_import_aliases), exposes it as build_import_aliases(), adds an
apply_import_aliases() helper, and threads an optional `aliases` argument
through resolve_call_name() and resolve_call_name_typed(). Aliases are
normalized before the type-map lookup so already-canonical names are not
re-expanded (e.g. `from socket import socket` must not yield
socket.socket.socket.recv).

Adds TestImportAliasEvasion coverage to both analyzers: aliased dangerous
forms are now detected, while aliased-but-safe imports produce no findings.

Signed-off-by: Zied Jlassi <6190550+zied-jlassi@users.noreply.github.com>

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Nice, well-scoped fix for import-alias evasion. Approving.

  • build_import_aliases() is a clean public wrapper over the existing _build_import_aliases already used by build_type_map, and threading an optional aliases arg through resolve_call_name() / resolve_call_name_typed() keeps this fully backward compatible (defaults to None).
  • The ordering fix in resolve_call_name_typed — normalize aliases before the type-map lookup — is the subtle-but-correct part, and the inline comment about avoiding socket.socket.socket.recv re-expansion explains why. apply_import_aliases handles both from x import y and import x as y (root-of-dotted-name) forms.
  • Both from ... import and import ... as are covered for AST4/AST5/AST8 and TT3/TT5, including the aliased credential→network and aliased-subscript (o.environ[...]) flows, plus negative tests that aliased-but-safe imports don't fire.

The acknowledged limitations (no per-scope shadowing; reflection like getattr(os, "system")(...) still out of scope) are reasonable and err on the safe side for a security tool. LGTM.

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.

[BUG] Import-alias evasion: behavioral_ast & behavioral_taint_tracking miss dangerous calls imported via 'from X import Y' / 'import X as Y'

2 participants