LLM-native vulnerability scanning.

Traditional SAST tools use pattern matching and taint analysis โ€” rule engines that flag code based on signatures written by security engineers. They are fast, deterministic, and brittle. A new vulnerability class means new rules. An unusual framework means no coverage.

The AI Scanner takes a different approach: it uses a generative language model to read code and reason about its security properties, the same way a senior security engineer would. It understands context, control flow semantics, and the relationship between a code pattern and the class of attack it enables โ€” without being constrained to a predefined rule set.

Model ID: AquilaX-AI/Ai Scanner โ€” available on HuggingFace. Base: unsloth/Qwen2.5-Coder-3B-Instruct. Fine-tuning: LoRA rank 512, 4-bit quantization. Trained on 100K source files, 5 epochs. Max sequence length: 8192 tokens.

Architecture and training.

3B
Parameters
100K
Training Files
8192
Max Tokens

The AI Scanner is built on unsloth/Qwen2.5-Coder-3B-Instruct โ€” a 3 billion parameter code-specialised language model. The base model's instruction-following capability is retained and extended via LoRA fine-tuning specifically for vulnerability detection tasks.

Key training configuration:

  • Base model: unsloth/Qwen2.5-Coder-3B-Instruct (3B parameters)
  • Fine-tuning method: LoRA (Low-Rank Adaptation)
  • LoRA rank: 512 โ€” substantially higher than typical LoRA applications, enabling the model to deeply adapt its code analysis behaviour
  • Quantization: 4-bit (NF4) โ€” enables deployment on standard GPU hardware without large VRAM requirements
  • Training corpus: 100,000 source files across Python, JavaScript, TypeScript, Java, Go, PHP, Ruby, and C/C++
  • Training epochs: 5
  • Max sequence length: 8,192 tokens โ€” handles files up to ~600 lines of code in a single pass

Training data composition: The 100K training files are drawn from real-world open-source repositories, manually reviewed and annotated with ground-truth vulnerability labels. Vulnerable files include confirmed CVEs, CWE-labelled code patterns, and AquilaX scan findings validated by security engineers.

Output schema.

The AI Scanner outputs a JSON array of vulnerability objects. Each finding in the array contains the following fields:

Field Type Description
vulnerabilitystringVulnerability class name (e.g. "SQL Injection", "Path Traversal", "XSS")
cwe_idstringCWE identifier as a string (e.g. "CWE-89", "CWE-22", "CWE-79")
descriptionstringPlain-English description of why this code pattern is vulnerable and what attack it enables
vuln_codestringThe specific line or expression identified as the vulnerability source
code_snippedstringBroader code context surrounding the vulnerability (ยฑ5 lines)

Note on field name: The output field is code_snipped (not code_snippet) โ€” this is intentional for backwards compatibility with the AquilaX findings schema.

Input / Output example.

The model receives source code as input and returns a JSON array of findings. Below is a full example:

Input โ€” Python source file
import sqlite3
from flask import request

def get_user(user_id):
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    query = (
        "SELECT * FROM users "
        "WHERE id = " + user_id
    )
    cursor.execute(query)
    return cursor.fetchone()

def upload_file():
    filename = request.form['name']
    path = '/uploads/' + filename
    with open(path, 'wb') as f:
        f.write(request.data)
    return path
Output โ€” JSON findings
[
  {
    "vulnerability":
      "SQL Injection",
    "cwe_id": "CWE-89",
    "description":
      "User-controlled input
       concatenated directly
       into SQL query string.
       Enables full DB
       compromise.",
    "vuln_code":
      "'WHERE id = '+user_id",
    "code_snipped":
      "query = (\"SELECT *
       FROM users WHERE id
       = \" + user_id)\n
       cursor.execute(query)"
  },
  {
    "vulnerability":
      "Path Traversal",
    "cwe_id": "CWE-22",
    "description":
      "Unsanitised filename
       from HTTP form data
       used to construct
       filesystem path.",
    "vuln_code":
      "'/uploads/'+filename",
    "code_snipped":
      "filename = request
       .form['name']\npath
       = '/uploads/'+filename"
  }
]

Batch inference.

For scanning entire repositories, the AI Scanner supports batch mode โ€” processing multiple files in a single model session. The AquilaX pipeline uses batch inference to scan all files in a repository concurrently across multiple worker threads.

from transformers import AutoTokenizer, AutoModelForCausalLM
import json

model_id = "AquilaX-AI/Ai Scanner"
tokenizer = AutoTokenizer.from_pretrained(model_id)
model = AutoModelForCausalLM.from_pretrained(
    model_id,
    load_in_4bit=True,
    device_map="auto"
)

SYSTEM_PROMPT = """You are a security scanner. Analyse the provided source code
and return a JSON array of vulnerability findings. Each finding must include:
vulnerability, cwe_id, description, vuln_code, code_snipped.
Return an empty array [] if no vulnerabilities are found."""

def scan_file(source_code: str) -> list:
    messages = [
        {"role": "system", "content": SYSTEM_PROMPT},
        {"role": "user",   "content": f"Scan this code:\n\n{source_code}"}
    ]
    text = tokenizer.apply_chat_template(
        messages, tokenize=False, add_generation_prompt=True
    )
    inputs = tokenizer([text], return_tensors="pt").to("cuda")
    outputs = model.generate(
        **inputs,
        max_new_tokens=2048,
        temperature=0.1,
        do_sample=True,
        pad_token_id=tokenizer.eos_token_id
    )
    response = tokenizer.decode(
        outputs[0][inputs["input_ids"].shape[-1]:],
        skip_special_tokens=True
    )
    try:
        findings = json.loads(response)
    except json.JSONDecodeError:
        findings = []
    return findings

Streaming inference.

For interactive use cases โ€” such as the AquilaX IDE extension scanning files on save โ€” the AI Scanner supports streaming token output. The JSON response is built incrementally and parsed once the full generation is complete.

from transformers import TextIteratorStreamer
from threading import Thread

streamer = TextIteratorStreamer(
    tokenizer,
    skip_prompt=True,
    skip_special_tokens=True
)

generation_kwargs = {
    **inputs,
    "streamer": streamer,
    "max_new_tokens": 2048,
    "temperature": 0.1,
    "do_sample": True,
    "pad_token_id": tokenizer.eos_token_id
}

# Run generation in a background thread
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()

# Consume tokens as they are generated
full_output = ""
for token in streamer:
    full_output += token
    print(token, end="", flush=True)

thread.join()
findings = json.loads(full_output)

"We're not replacing rule-based scanners โ€” we're augmenting them with a model that can reason about code the way a security engineer does, catching vulnerabilities that patterns can never anticipate."

In the AquilaX pipeline, the AI Scanner runs in parallel with rule-based engines. Findings from all sources are deduplicated and passed through the Review model before reaching the dashboard โ€” ensuring that the AI Scanner's generative outputs are held to the same false-positive elimination standard as every other engine in the platform.