Import Resolver System¶
Overview¶
The import resolver (internal/imports/) enables cross-file analysis by resolving import statements to their target files and symbols. This is essential for inter-procedural taint analysis across module boundaries.
Architecture¶
┌──────────────────────────────────────────────────────────────┐
│ Import Resolver │
├──────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ IndexFiles │──▶│ moduleMap │──▶│ ResolveImport │ │
│ │ (parse all) │ │ (name→path) │ │ (get symbols) │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌─────────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ fileIndex │ │ exports │ │ ResolvedImport │ │
│ │ (path→file) │ │ (path→syms) │ │ (target+syms) │ │
│ └─────────────┘ └──────────────┘ └─────────────────┘ │
│ │
└──────────────────────────────────────────────────────────────┘
Key Types¶
ImportResolver¶
Main resolver struct:
type ImportResolver struct {
rootPath string // Project root
fileIndex map[string]*ast.File // path → parsed file
moduleMap map[string]string // module name → file path
exports map[string]*ModuleExports // path → exported symbols
}
ModuleExports¶
Symbols exported by a module:
type ModuleExports struct {
Functions map[string]*ast.Function // Exported functions
Classes map[string]*ast.Class // Exported classes
Variables map[string]ast.Expression // Exported variables
AllExports []string // For wildcard exports
}
ResolvedImport¶
Result of import resolution:
type ResolvedImport struct {
SourceFile string // File containing the import
TargetFile string // File being imported from
SourceModule string // Module name as written
ImportedAs string // Alias if renamed
Symbols []ImportedSymbol // Imported symbols
IsWildcard bool // True for "import *"
}
ImportedSymbol¶
A single imported symbol:
type ImportedSymbol struct {
Name string // Original name
Alias string // Local alias (if any)
Kind SymbolKind // function, class, variable
Definition interface{} // AST node (Function, Class, etc.)
Location types.Location
}
Language-Specific Resolution¶
Python¶
Python imports follow these resolution rules:
# Absolute import
from foo.bar import baz
# Resolves to: {root}/foo/bar.py or {root}/foo/bar/__init__.py
# Relative import
from .utils import helper
# Resolves to: {current_dir}/utils.py
# Package import
from foo import bar
# Resolves to: {root}/foo/bar.py or {root}/foo/__init__.py (bar in __init__)
Resolution order:
1. Relative to current file
2. Relative to project root
3. Package __init__.py files
TypeScript/JavaScript¶
JavaScript imports follow these rules:
// Relative import
import { foo } from './utils';
// Resolves to: ./utils.ts, ./utils.tsx, ./utils.js, ./utils/index.ts
// Package import (node_modules)
import express from 'express';
// Not resolved (external package)
// Path alias (requires tsconfig)
import { api } from '@/services/api';
// Requires configuration (not yet supported)
Resolution order:
1. Exact path with extensions: .ts, .tsx, .js, .jsx
2. Index files: index.ts, index.tsx, index.js
API Reference¶
Creating Resolver¶
Indexing Files¶
// Index all files for resolution
files := []*ast.File{file1, file2, file3}
resolver.IndexFiles(files)
Resolving Imports¶
// Resolve a single import
imp := &ast.Import{
Module: "utils",
Names: []ast.ImportName{{Name: "helper"}},
}
resolved, err := resolver.ResolveImport(imp, "/path/to/main.py")
// Check resolution result
if resolved != nil {
fmt.Printf("Imported from: %s\n", resolved.TargetFile)
for _, sym := range resolved.Symbols {
fmt.Printf(" Symbol: %s (kind: %s)\n", sym.Name, sym.Kind)
}
}
Finding Symbols¶
// Find a function across imports
fn := resolver.GetFunction("helper", "/path/to/main.py")
if fn != nil {
fmt.Printf("Found: %s at %s:%d\n", fn.Name, fn.Location.File, fn.Location.StartLine)
}
// Find a class
cls := resolver.GetClass("UserModel", "/path/to/main.py")
Getting All Imports¶
// Get all resolved imports for a file
imports := resolver.GetAllImportsForFile("/path/to/main.py")
for _, imp := range imports {
fmt.Printf("%s imports from %s\n", imp.SourceFile, imp.TargetFile)
}
Topological Sort¶
// Sort files by dependency order (dependencies first)
sortedFiles := resolver.TopologicalSort()
// Process in order for bottom-up analysis
for _, file := range sortedFiles {
analyze(file) // Dependencies already analyzed
}
Use in Taint Analysis¶
Cross-File Taint Tracking¶
# utils.py
def sanitize(s):
return shlex.quote(s) # This sanitizer breaks command taint
# main.py
from utils import sanitize
user_input = request.args.get("q") # tainted
safe_input = sanitize(user_input) # sanitized (resolved cross-file)
os.system(f"echo {safe_input}") # Safe
The import resolver enables the taint engine to:
1. Resolve sanitize to utils.py:sanitize
2. Look up the function summary for sanitize
3. Apply sanitization rules
Function Summaries¶
// Build summaries in dependency order
sortedFiles := resolver.TopologicalSort()
for _, file := range sortedFiles {
for _, fn := range file.Functions {
summary := computeFunctionSummary(fn, existingSummaries)
summaries[fn.ID] = summary
}
}
// Use summaries for inter-procedural analysis
for _, file := range files {
analyzeWithSummaries(file, summaries)
}
Configuration¶
# mcp-scan.yaml
analysis:
cross_file:
enabled: true
max_depth: 5 # Maximum import chain depth
resolve_external: false # Don't resolve node_modules, etc.
Limitations¶
- No External Packages: Imports from
node_modules, PyPI packages not resolved - No Dynamic Imports:
importlib.import_module(),require()at runtime - No Path Aliases: TypeScript
pathsconfiguration not supported - Limited Wildcard:
from x import *exports not fully tracked
Error Handling¶
resolved, err := resolver.ResolveImport(imp, fromFile)
if err != nil {
// Resolution failed (file not found, parse error)
log.Warnf("Could not resolve %s: %v", imp.Module, err)
}
if resolved == nil {
// External import (not in project)
// Treat as opaque for taint analysis
}
Debugging¶
Enable debug logging to trace resolution:
Examples¶
Python Cross-File Analysis¶
# File: /project/models/user.py
class User:
def __init__(self, name):
self.name = name
# File: /project/handlers/auth.py
from models.user import User
def create_user(request):
name = request.json.get("name") # tainted
user = User(name) # User.__init__ called with tainted arg
return user
Import resolution enables:
1. Resolving models.user → /project/models/user.py
2. Finding User class definition
3. Analyzing User.__init__ with tainted name parameter
TypeScript Cross-File Analysis¶
// File: /project/utils/validators.ts
export function sanitizeInput(s: string): string {
return s.replace(/[<>]/g, ''); // Partial sanitizer
}
// File: /project/handlers/user.ts
import { sanitizeInput } from '../utils/validators';
function handleRequest(req: Request) {
const input = req.body.query; // tainted
const safe = sanitizeInput(input); // Resolved cross-file
// Taint analysis checks sanitizeInput's effect
}
Performance¶
Caching¶
The resolver caches: - Module name → path mappings - Parsed file ASTs - Export symbol tables
Incremental Updates¶
// When files change, update incrementally
changedFiles := detectChanges(oldHashes, newHashes)
for _, path := range changedFiles {
newFile := parser.ParseFile(path)
resolver.UpdateFile(path, newFile)
}
Memory Usage¶
For large projects, consider: - Lazy loading of file ASTs - LRU cache for exports - Pruning unused import chains