Call Graph

Qryon generates call graphs to visualize function relationships with security context, helping you understand how data flows through your codebase.

What is a Call Graph?

A call graph is a directed graph showing which functions call which other functions. Qryon enhances this with security annotations, marking functions as sources, sinks, or sanitizers based on their security role.

Security Classifications

ClassificationColorDescription
SourceGreenFunctions that introduce untrusted data
SinkRedFunctions that perform sensitive operations
SanitizerBlueFunctions that clean or validate data
PropagatorYellowFunctions that pass data through
NeutralGrayFunctions without security significance

Generating Call Graphs

# Generate call graph during scan
rma scan . --call-graph

# View in interactive TUI
rma scan --interactive
# Press 'g' to switch to call graph tab

# Export call graph
rma scan . --call-graph --format dot > callgraph.dot
rma scan . --call-graph --format json > callgraph.json

# Generate SVG visualization
rma scan . --call-graph --format dot | dot -Tsvg > callgraph.svg

Interactive TUI Features

The TUI provides an interactive call graph explorer with the following controls:

KeyAction
j/kNavigate between functions
EnterView function details and callers/callees
hCollapse to callers only
lExpand to callees only
fFilter by security classification
/Search for function by name
eView edge details (taint flows)

Call Graph Output Formats

DOT Format (Graphviz)

digraph callgraph {
  rankdir=LR;
  node [shape=box];

  // Sources (green)
  "getUserInput" [style=filled, fillcolor="#90EE90"];

  // Sinks (red)
  "db.query" [style=filled, fillcolor="#FFB6C1"];

  // Sanitizers (blue)
  "validateId" [style=filled, fillcolor="#ADD8E6"];

  // Edges
  "handleRequest" -> "getUserInput";
  "handleRequest" -> "processUser";
  "processUser" -> "validateId";
  "processUser" -> "db.query";
}

JSON Format

{
  "nodes": [
    {
      "id": "src/api.ts:handleRequest",
      "name": "handleRequest",
      "file": "src/api.ts",
      "line": 15,
      "classification": "neutral",
      "callers": ["src/server.ts:main"],
      "callees": ["src/api.ts:getUserInput", "src/api.ts:processUser"]
    },
    {
      "id": "src/api.ts:getUserInput",
      "name": "getUserInput",
      "file": "src/api.ts",
      "line": 25,
      "classification": "source",
      "source_type": "http_parameter"
    }
  ],
  "edges": [
    {
      "from": "src/api.ts:handleRequest",
      "to": "src/api.ts:getUserInput",
      "tainted": false
    },
    {
      "from": "src/api.ts:processUser",
      "to": "src/db.ts:query",
      "tainted": true,
      "taint_source": "src/api.ts:getUserInput"
    }
  ]
}

Understanding Edge Details

In the TUI, pressing e on an edge shows detailed security information:

Edge: processUser -> db.query

Caller: processUser (src/services/user.ts:45)
Callee: db.query (src/db/connection.ts:12)

Security Context:
  - Tainted: Yes
  - Source: req.params.id (src/routes/users.ts:8)
  - Sink Type: SQL Query
  - Vulnerability: CWE-89 SQL Injection

Data Flow:
  1. req.params.id (HTTP Parameter)
  2. getUserById(id) (Parameter passing)
  3. processUser(user) (Object property)
  4. db.query(sql) (SQL concatenation)

Remediation:
  Use parameterized queries instead of string concatenation.

Filtering the Graph

# Show only security-relevant nodes
rma scan . --call-graph --filter security

# Show only paths from sources to sinks
rma scan . --call-graph --filter vulnerable-paths

# Filter by file
rma scan . --call-graph --filter "src/api/**"

# Filter by function pattern
rma scan . --call-graph --filter "handle*"

Use Cases

Security Auditing

Find all paths from user input to database queries:

rma scan . --call-graph --show-paths \
  --from "source:http" \
  --to "sink:sql"

Code Review

Understand the impact of changing a function:

rma scan . --call-graph --callers-of "validateUser" --depth 3

Attack Surface Analysis

List all entry points that reach sensitive operations:

rma scan . --call-graph --entry-points-to "sink:*"

Cross-File Resolution

Qryon resolves imports across files to build accurate cross-file call graphs:

// src/routes/users.ts
import { UserService } from '../services/userService';

app.get('/user/:id', async (req, res) => {
  const user = await UserService.findById(req.params.id);
  res.json(user);
});

// src/services/userService.ts
import { Database } from '../db';

export class UserService {
  static async findById(id: string) {
    return Database.query('users', { id });
  }
}

Qryon builds a complete graph:

routes/users.ts:handler
    -> calls
services/userService.ts:UserService.findById
    -> calls
db/index.ts:Database.query

Performance Considerations

  • Large codebases: Use --max-depth to limit analysis depth
  • Incremental updates: Qryon caches call graph analysis
  • Memory: Large graphs may require more memory
# Limit call graph depth
rma scan . --call-graph --max-depth 5

# Focus on specific directories
rma scan ./src/api --call-graph

Next Steps