A drag-and-drop security scanner for AI-generated applications.
Drop in an app, and Halation recovers what source it can, checks it against the vulnerability classes AI code generators actually produce, and reports what it found and what it could not reach.
All analysis happens on your machine. The only thing that ever leaves it is the list of package names and versions the application declares, sent to check them against published advisories, and that can be turned off entirely. Your code is never uploaded. Halation also asks GitHub what the newest release is when it starts, which sends nothing at all and is likewise switchable.
Before you ship. Run your own app through it as a final check. AI-generated code has been measured at roughly 2.7x the vulnerability rate of hand-written code, and the failures are predictable: keys committed to the bundle, database row-level security left off, API routes with no authentication, dependencies years out of date.
Before you install. Run something you downloaded through it. For Electron and .NET applications, Halation recovers the real application code from the shipped binary and reads it, rather than trusting the description on a download page.
It cannot tell you an application is safe. Static analysis can demonstrate that bad patterns are present; it can never demonstrate that none are. A deliberately malicious app will read cleaner than a sloppy honest one.
So Halation does not print a verdict of "safe". It reports:
- A score out of 100, capped by the single worst issue found. Fifty passing checks cannot lift an app that ships a live API key out of the red band. There is one score, and it is the worse of the two readings: the artifact is scored both as a question about shipping it and as a question about running it, and the harsher answer is the one both reports print. Otherwise an author could scan their own work, switch to the reader it treats more kindly, and screenshot a number produced for a question they were not asking. Which findings appear, how they are worded, what can be done about them and what order they come in all still change with the reader. Only the number does not, and the report says what both readings were.
- A number that means the same thing in everybody's report. The score comes from the deterministic checks alone. Whatever the optional AI pass finds is reported in full and never moves it, because a number that changed with whichever model you had configured could not be compared with anyone else's. What the AI keeps is the power to withhold the all-clear: a result cannot be labelled clean while its suggestions are sitting underneath it.
- A coverage figure, kept deliberately separate from the score, saying how much of the
application could actually be read. A clean result at 12% coverage is a different claim
from a clean result at 95%. Coverage measures what was understood, not what was written
out: an obfuscated application decompiles into thousands of files of
a.b(c), none of which count, so it gets no score at all rather than a good one. - What could not be checked, listed explicitly, so a short findings list is never mistaken for a clean bill of health. When a whole class of check could not run at all, such as dependencies in an application that ships no lock file, that is said beside the score rather than four sections below it: a shipped application can otherwise score 100 under "no known issues found", beside a coverage meter reading 100% readable, while nothing whatever is known about the packages inside it.
- What the application can do, listed separately and scored nowhere. Updating itself and starting with Windows are how a great many correct programs work, so charging them a band of score was a scanner calling a feature a fault. They are still reported, and for somebody deciding whether to run a download they can be the most useful lines in it: an application that replaces its own code is one whose future behaviour no scan of it describes.
An explicit "do not install" is triggered only by specific, high-confidence deterministic rules, never by the score alone.
| Artifact | Recovery | Depth |
|---|---|---|
| Source folder, zip, or repository | read directly | full |
| .NET executable or library | decompiled (ILSpy) | full, near-original C# |
| .NET single-file bundle | unpacked from memory, then decompiled | full |
Electron application or .asar |
unpacked | full, often unminified |
| NSIS installer | unpacked, then as above | full for Electron and .NET payloads |
| Java archive | decompiled | good |
| Python bundle | readable modules only | partial, and said so |
| Native Windows binary | not possible | signing and hardening flags only |
Installers matter more than that table makes them look, because almost nothing is downloaded
as a bare executable. An installer is a native stub with the application attached, so reading
only the stub writes off everything worth checking. Halation unpacks NSIS installers, which
is what electron-builder produces, and hands each payload to the recovery it deserves: an
asar to the Electron reader, a .NET assembly or single-file bundle to the decompiler.
Nothing is executed and nothing is written to disk: the installer is read, never run.
A payload that is a native binary still yields nothing, because nothing can decompile one. The report says that in those words rather than reporting an empty result, so a low coverage figure on an installer means "this was not examined" and never "this was examined and was clean".
Installers built with Inno Setup, or NSIS installers using solid compression, cannot be unpacked yet. Those say so rather than reporting an empty result.
Source is read for C#, JavaScript, TypeScript, Python, Java and Kotlin, plus JSON, YAML, TOML,
XML, HTML, Vue, Svelte, XAML, shell scripts and .env files. Go, Rust, PHP, Ruby, Swift, Dart,
C, C++, Objective-C, SQL, GraphQL, Razor, Astro, Scala, Elixir, Clojure, F# and Haskell are read
too, in a bucket with no rules written for their idioms.
That last distinction matters and is worth stating plainly. 33 of the 39 rules carry no
language filter, so a Go or Rust file gets every secret, configuration and malicious-behaviour
check. What it does not get is the injection rules keyed to C# and JavaScript syntax: SQL built
by joining strings is caught in Go, and missed in PHP, because PHP concatenates with . and the
pattern was written for +. More patterns, not a closed door.
A compiled native binary remains unreadable, whatever produced it. A Go or Rust executable has nothing to decompile, and the report says so rather than reporting an empty result.
Dependencies are resolved from package-lock.json, yarn.lock (classic and Berry),
pnpm-lock.yaml, vendored node_modules manifests, *.deps.json, packages.lock.json,
requirements.txt, Pipfile.lock, go.sum, Cargo.lock, poetry.lock, composer.lock,
Gemfile.lock and gradle.lockfile, covering npm, NuGet, PyPI, Go, crates.io, Packagist,
RubyGems and Maven. A
lock file is the only artifact that says what a project actually installed rather than what it
asked for, which is the difference between a dependency that can be checked and one that cannot.
Out-of-date dependencies are one of the most common real problems in shipped applications, so Halation checks them against OSV.dev at the moment you scan. A vulnerability database bundled into a release is out of date the day it ships, and findings cite CVE identifiers and link through to the NVD entry.
What is sent: the package names and versions the application declares, and nothing else. No source, no file contents, nothing identifying you or the artifact.
This is the one part of a scan that needs a network. With no connection, dependencies are not checked and the report says so rather than reporting no known vulnerabilities. There is no offline database to download: bundling one, or shipping a mirror to keep in sync, was tried and removed, because a vulnerability database is out of date the day it ships and a stale answer here is worse than an absent one.
Every report states which source it used and when the data was current, so a check that could not run is never mistaken for one that came back clean.
The main scan is free, needs no account, and sends nothing but package names. Optionally, a second deep pass reads the code and reasons about it, which is what catches the things a pattern cannot express: a guard that exists but is incomplete, whether untrusted input can actually reach a dangerous operation, two individually harmless pieces of code that are unsafe together.
It is off unless you turn it on, per scan, and there are two ways to power it. Both use Claude Opus. They differ in whose account pays and in what that costs you.
Halation looks for Claude Code when it starts, including the copy bundled inside the Claude desktop app, and asks it whether it is signed in. That happens on its own, with nothing to click and nothing to configure, and Claude Code does not need to be running. If it is installed and signed in, the app says so and the route is ready; if it is installed but signed out, a Sign in button appears and opens Claude Code's own sign-in for you. Halation never sees your credentials. The CLI holds them, exactly as it does when you use it directly.
Detection still does not mean a scan will use it. The deep pass is a tick box, off by default, and no scan sends anything anywhere until you tick it. What being signed in buys you is that ticking the box is the only thing you have to do: the route is chosen for you, so there is no second setting to find.
One thing worth knowing: the check runs once, at startup. Sign in to Claude Code while Halation is already open and it will not notice until you press Sign in or restart it.
This spends the usage allowance of the Claude subscription you already pay for. Nothing is charged on top. A deep pass is a handful of requests, so on a Pro or Max plan it is a small share of a day's allowance, but it does come out of the same pot as your own work with Claude, and a large application read at the file ceiling will make a dent in it.
The report says which installation answered, and states plainly that no money was charged.
Paste an API key from the Anthropic Console and Halation calls the API directly.
This costs real money, every scan. The API is billed per token against credit you buy up front; it is a separate product from a Claude subscription, with separate billing and no bridge between the two, so a Pro or Max plan does not cover it and its allowance is not touched. A typical pass over a dozen files runs to a few cents, and the report prints the estimate with the tokens it is based on, so you can see what a scan cost rather than find out at the end of the month.
Your key is encrypted to your Windows account and stored outside the application folder. It is never written to a report, and the interface only ever shows it masked.
The AI pass reasons about intent, and decompiling a binary destroys every comment in it. On a real application whose source was to hand, three findings from a frontier model against the decompiled release were checked line by line and all three were wrong, each one answered by a comment the author had already written and the decompiler had thrown away. The deterministic checks are unaffected, since a pattern does not care about comments.
So scan the source tree when you have it. The pattern checks give the same answer either way; the AI half is a much better instrument when the reasoning is still in the file.
The deep pass is offered in developer mode only. Checking something you downloaded runs entirely on this machine, with no account, no key and nothing leaving it.
For the API route that is a decision about what the window offers. For the Claude Code route it is a refusal enforced in the core, and the reason is worth stating: Claude Code is an agent with shell and filesystem access, and the API is an endpoint that cannot execute anything. Feeding source recovered from software you do not trust into something that can act on your machine is the attack this tool exists to warn people about. The scanned program never has to run, because getting Halation to read it becomes the attack instead.
Where it is used, that agent is fenced in: no tools, safe mode, an empty working directory, no session persistence, and the file content arrives on standard input rather than on a command line other processes can read.
What is sent. The files that handle input the application does not control, plus any file that calls into one a rule flagged. Not the whole application, and not only the lines a rule matched. Both of those bounds are deliberate:
- Sending only flagged lines would mean the pass could deepen findings you already have and never discover one in code no rule happened to hit. Tested against a real application, the two issues the pattern rules missed were both in files with zero findings.
- Reading one hop of callers is what makes reachability answerable. The same application had an unbounded stack allocation recorded as a local-only crash risk; it was reachable from a remote HTTP response, and the file that proved it was one call away.
The report lists exactly which files were read, what answered, and what the pass cost.
Bounds on it. At most 40 files per scan, whichever route answers, so neither your allowance
nor your credit can run away on one large application. Findings from this pass are labelled AI,
carry a confidence level, and low-confidence ones are dropped rather than hedged. None of them
can trigger a "do not install" verdict, because the strongest claim in a report must not
depend on whether the reader happened to have a key or a subscription.
One Export report button on the results screen, which asks which of four you want and says what is in each. Nothing is written until you choose one and nothing is sent by any of them; they save a file, and what happens to it next is yours.
| Export | For | Contains your code |
|---|---|---|
| Markdown | Reading, keeping, pasting into your own notes or a private ticket | Yes |
| JSON | Feeding to something else. Every field, structured, versioned | Yes |
| Markdown for sharing | Posting in public, or sending to someone who should not see the source | No |
| Scorecard image | Attaching to a readme or a post, for showing rather than reading | No |
The scorecard is a 1200x630 image carrying the score, the band it falls in, how much of the application could be read, what was found by severity, and, when a file was scanned, the SHA-256 of that file. It also names the version that produced it, because rules are added between releases and a score is only reproducible against the build that made it.
The one above is Halation scanning its own release build. The coverage figure sits beside the score at the same size on purpose: 99 out of 100 against a tenth of an application is not the claim that number makes on its own.
Checking somebody else's card is three steps, because an image is not evidence and nothing about a PNG is signed:
- Get the file the card names.
- Hash it and confirm it matches:
Get-FileHash TheirApp.exe -Algorithm SHA256. Anything other than an exact match means you are not holding the file that was scanned. - Scan that file with the version named on the card, and compare.
Scanning a folder produces no usable hash: there is no single stream of bytes to digest, and two different folders can share a digest of names and sizes. Rather than print a number that looks like verification and is not, a card from a folder scan carries no hash and says so on its face. Scan the built file if you want a card somebody else can check.
The hash is safe to publish. It is one-way, and a file has far too much entropy to work backwards from. It is the same thing projects publish beside a download.
Why the sharing export exists. Every finding in an ordinary report quotes the line of code it was found in and names the file and line. That is the most valuable thing in it, because it is what lets you check a claim rather than believe it, and it also makes the ordinary export a document full of your own source. Sending one to anybody is publishing that source.
That gets in the way of something this project actually needs. Nearly every real improvement here came from somebody pointing the tool at a real application and finding it wrong, and a report is how you show that. Nobody should have to choose between helping and keeping their code private.
The sharing copy removes the quoted lines entirely, the file names and paths, the line numbers, and the name and SHA-256 of what you scanned. It keeps every finding, its severity and its rule, the score and coverage, which checks ran or could not, the timings, and your hardware and model if one ran locally.
Nothing is summarised or dropped. The same findings appear in the same order at the same
severities. A finding that read Hardcoded credential · src/Auth/Vault.cs:19 above a quoted line
becomes Hardcoded credential · file 1 (.cs) with no quotation. Paths become labels rather than
disappearing, and the same file keeps the same label, so nine findings in one file still read as
nine findings in one file.
Two things it does not pretend. It states at the top that it is the redacted copy, because a shortened report that stayed quiet about it would let somebody read the missing parts as findings that were not there, which is the mistake the rest of this tool exists to prevent. And wording an AI model wrote is kept, which can name a method or a class it read: a far smaller disclosure than a quoted line, and without it there is nothing to discuss, so it stays and the file says so.
Secrets are masked in both copies. A credential the scanner found is never printed back in full in any export.
A detection tool is a program whose source contains, in quotation marks, every string it looks for. Pointed at its own published build Halation used to score 16/100 and advise against installing itself, on nine findings that were all its own rule table read as the behaviour the rules describe. Antivirus signatures, WAF rules and linter configurations all have this shape.
A match is discounted when it sits inside a string literal and that string is either being
handed to a regex constructor or surrounded by enough other pattern definitions to be a
catalogue of them. Ordinary code satisfies neither: a real new BinaryFormatter() is not in
quotes, and a real registry path in quotes does not sit in a file of forty regexes. Measured
across four hand-written applications, nothing was discounted in any of them.
The count is on the receipt. A tool that quietly removes its own findings is asking to be trusted about the one thing nobody can check, so the report says how many matches it discounted and why. Secrets are exempt: a credential in quotation marks is a leaked credential wherever it lives, including in the source of a security tool.
On startup Halation asks GitHub for the public release list and compares it with the build you are running. A newer one gets a strip across the top of the window. Nothing about you or the machine is sent, the comparison happens locally, and the whole check can be switched off on the drop screen.
It will not install a build it cannot verify. Halation's own VC-MAL-007 tells other
applications that an updater must check what it downloaded against a signature, and not
against a hash served from the same place as the file, so this one is held to that: a download
is installed only when Windows validates its Authenticode signature and the signer matches the
publisher who signed the copy already running. Until releases are code-signed there is no such
publisher, so the strip announces the version and links to the release page rather than
offering to install it. The refusal is stated on screen instead of the button quietly being
absent.
When an update is installed, the old build is renamed aside, the new one takes its place, and Halation restarts into it. The next launch deletes what was moved. A build running from its own build output, or from a folder it cannot write to, declines to replace itself and says so.
Prereleases are offered only to somebody already running one. A release build is never moved onto a beta because the number happens to be larger.
Early development. The analysis core, the rule set, the desktop interface and the optional deep pass are implemented and tested. Releases are not yet code-signed, which is also what stops the updater installing anything on its own; see above.
Requires the .NET 10 SDK.
dotnet test
dotnet run --project Halation.App
A release build is a single self-contained Halation.exe with no runtime to install:
dotnet publish Halation.App -p:PublishProfile=win-x64 -o <output folder>
Ko-fi, if you would like to. Nothing is behind it and nothing will be: there is one build, everything in it is unlocked, and the licence is MIT either way. This is one person's work, and what it costs is time and the occasional certificate.
The most useful thing you can send costs nothing at all, which is an application this tool got wrong. See CONTRIBUTING.md for why that beats a patch here.