Overview

How a scan works

A scan enters as a repository, an upload, a deployed address, or a proven web target, runs deterministic tools before any model sees it, and erases the source when it is done.

Six ways in

The New scan screen has six tabs: code, web, DNS, container, IaC, and dynamic. Each tab maps to a different entry path on the server, and each path has its own input rules.

  • Code accepts an https:// git URL (cloned into a throwaway directory), an uploaded .tar.gz or .zip archive, or — for admin / single-operator installs only — a local filesystem path. A path already on the machine discloses whatever the host can read, so ordinary members never see that field; they submit a URL, an upload, or an on-chain address instead.
  • Smart contracts take a chain and a deployed address. The verified source is pulled from that chain's own explorer and a proxy is followed to its implementation. See Smart contracts for what happens after the source is in hand.
  • Web and DNS targets additionally need proof of possession — a DNS TXT record or a well-known file — before anything is scanned.
  • Container takes an image reference; IaC takes a path to Terraform, Kubernetes, Helm, Docker Compose or Ansible files (again local-path-only for admin / single-operator installs).
  • Dynamic starts your own application inside a container and attacks it from a second, network-isolated container — see Dynamic scans below. It mounts a directory on the scanning host, so like local paths it is admin / single-operator only.
The New scan screen showing the six scan-type tabs
The six scan tabs. Local-path inputs (code, IaC) are shown only to admin or single-operator sessions.

Submit, analyze, erase

Whatever the entry path, a scan follows the same three stages: the target is fetched into a transient location, deterministic tools run against it and produce findings, and the transient copy is removed. The findings — not the source — are what the database keeps.

The path of a scan: entered, analyzed, erased.Three stages of a Sentinelleai scan: submitting a target (repository, archive, contract address, or verified web/DNS target), analyzing it with deterministic scanners followed by a multi-model AI panel, and erasing the checked-out source while keeping the findings — severity, file, line, and reasoning.01 SubmitSources accepted:• https git repository• uploaded archive• contract address• verified web / DNS target02 AnalyzeDeterministic scanners first:• SAST & taint• Dependencies & supply chain• Secrets• Contract enginesThen, second pass:• Multi-model AI panel03 EraseEvery case:• Source checkout removedFindings kept:• Severity, file, line, reasoninginline protection never waits on a model
The path of a scan: entered, analyzed, erased.

A repository or an archive can also be queued rather than run in the same request: when the operator has turned queuing on, the route inserts a job and returns immediately, and a separate worker process claims it, runs it, and writes the result. From the outside a queued scan looks the same — it moves through pending → running → a terminal status (completed, degraded, or failed) — and a report page polls the scan by id either way.

What is erased and what is kept

A cloned repository or an extracted upload lives in a temporary directory for the duration of the scan and is removed once the scan ends, whether it succeeded or failed. Nothing about that removal depends on the scan going well.

  • Uploaded archives are checked before a single byte is extracted: every entry name is listed and any absolute path or .. component is refused (the classic zip-slip attack), the archive itself is size-capped, and the extracted tree is measured afterward against its own limit — so a compressed header that lies about its true size cannot smuggle past the first check. The workspace is deleted in a finally block, including on an error path.
  • What is kept is the findings themselves — severity, file, line, the tool's reasoning — deduplicated and tracked from scan to scan, plus the scan's summary counts. The source tree, the archive bytes, and the cloned checkout are not retained anywhere.
A residual gap, stated rather than hidden
Extraction happens on the host running the scan, so an archive whose compressed headers lie can still spend real disk space during extraction, before the post-extraction size check catches it. A hard per-scan disk quota is a stronger control than a check that runs after the fact; today the size check closes the traversal attack outright and bounds the damage after it, but it does not prevent a brief spike during extraction itself.

Deterministic tools first, a model after

Every scan family runs its deterministic tools first — static analysis, dependency and supply-chain checks, secrets detection, and (for contracts) on-chain engines across six ecosystems. Those tools produce the findings on their own, with no model in the loop. A multi-model AI panel reviews the results only afterward, as a second pass, and only where the operator has switched it on for that surface.

Online protection is rules; the AI reasons afterwards, over what the rules recorded.Two lanes separated by a wall. In the request path, only deterministic steps run — rules, virtual patches, rate limits, tripwire — in milliseconds, with no model involved. After the fact, an AI pass reads what was recorded — scan findings, a multi-model panel, a judge, a report — in minutes, read-only, billed per event.In the request path (always deterministic)RulesVirtual patchesRate limitsTripwiremilliseconds, no modelno model in the inline pathAfter the fact (AI, billed per event)Scan findingsMulti-model panelJudgeReportminutes, read-only
Online protection is rules; the AI reasons afterwards, over what the rules recorded.

This ordering is not cosmetic. The web reasoning panel, for example, ships armed off by default: until the operator explicitly turns it on, a web scan behaves exactly as if the panel did not exist, and the module that would call a model is not even imported into a disarmed scan's code path. When it is armed, it is read-only — it reasons over findings a deterministic scan already produced, correlating them into possible exploit chains and flagging business-logic questions the pattern-based tools cannot ask, without sending any new request to your target itself.

Inline protection never waits on a model
The same ordering applies to Sentinelleai's own inline defenses: nothing that sits in the request path to decide pass/block is a model call. Deterministic rules run first and alone for anything time-critical; an AI pass, where one exists, is always a later, asynchronous read over what already happened — never a gate a live request has to wait on.

Queued or synchronous, and how status moves

A code, web, DNS, container, or IaC scan can run either synchronously inside the request or through a queue, depending on how the operator has configured the installation. Either way the scan row moves through the same states: pending (queued, not yet claimed), running, then one of completed, degraded (some required tool did not run, so the result is partial and unscored), or failed (nothing usable came back). The report page reads this status directly rather than assuming a scan that returned an id has finished.

A deployed-contract scan that has the deeper, armed pipeline turned on (static analysis plus the multi-model panel plus an on-chain simulation) is treated as complete only if every required stage finished — see Smart contracts for what "complete" gates there.

Dynamic scans: a hand-written plan, not a crawler

The dynamic scan tab is different from the other five: it does not crawl your application to discover what to test. There is no route discovery at all. Instead you supply a JSON plan by hand — an authorization probe naming two real identities and an anonymous one, and/or a fuzz seed request — and only the endpoints named in that plan are exercised. An endpoint nobody wrote a plan for is not tested, and its absence from the report says nothing about whether it is safe.

The two identities and the marker are what make an authorization finding decidable at all: the scan logs in as the resource's owner and as a second, unrelated account, and checks whether the second account can read a marker value only the owner should see. The anonymous identity is optional, and its absence is reported rather than silently skipped, because without it "both accounts saw it" cannot be told apart from "the resource is simply public".

A worked example planjson
{
  "authz": [
    {
      "id": "INV-7",
      "resource": "invoice 7, owned by alice",
      "request": { "method": "GET", "path": "/api/invoice/7" },
      "marker": "SECRET-7",
      "owner": {
        "name": "alice",
        "login": { "method": "POST", "path": "/login",
                   "body": "{\"user\":\"alice\",\"pass\":\"...\"}" },
        "carry": "cookie"
      },
      "other": {
        "name": "bob",
        "login": { "method": "POST", "path": "/login",
                   "body": "{\"user\":\"bob\",\"pass\":\"...\"}" },
        "carry": "cookie"
      },
      "anonymous": { "name": "anon", "login": null, "carry": "cookie" }
    }
  ],
  "fuzz": {
    "seedRequest": { "method": "POST", "path": "/api/items", "body": { "id": 1 } },
    "budget": 60
  }
}

Your application directory is mounted read-only into one container, started with the command you give, and attacked from a second container on a network with no route out — it never reaches a hostname you did not hand it, and it never touches a running production system. Disarmed by default: without the operator's explicit arming switch set, the scan reports exactly what it would have mounted and sent, and starts nothing.