A command-line client for Kiuwan: query your applications and their security defects from the terminal, or from an AI coding agent.
$ cliuwan defects MY-APP --group-by cwe --format text
Analysis: A-7ea-19fd692b3eb (Complete delivery, 2026-08-06)
Application: MY-APP
Defects: 338 grouped by cwe
============================================================
168 CWE-89
103 CWE-79
67 CWE-798Not affiliated with Kiuwan or Idera, Inc. This is an independent, unofficial client that talks to the public Kiuwan REST API. "Kiuwan" is a trademark of its respective owner.
| Does | Read results of analyses that already exist: applications, analyses, and their defects |
| Does not | Run new analyses. That is the Kiuwan Local Analyzer (KLA), a separate and complementary tool |
The two fit together: KLA scans your source and uploads the analysis; cliuwan reads the
results back.
No Java required. Starts in about 20 ms.
# macOS (Apple Silicon)
curl -L https://github.com/serpean/cliuwan/releases/latest/download/cliuwan-darwin-arm64 -o cliuwan
# Linux x64
curl -L https://github.com/serpean/cliuwan/releases/latest/download/cliuwan-linux-x64 -o cliuwan
chmod +x cliuwan && sudo mv cliuwan /usr/local/bin/
cliuwan login| Platform | Asset |
|---|---|
| Linux x64 | cliuwan-linux-x64 |
| Linux arm64 | cliuwan-linux-arm64 |
| macOS Apple Silicon | cliuwan-darwin-arm64 |
| macOS Intel | cliuwan-darwin-x64 |
| Windows x64 | cliuwan-windows-x64.exe |
| Any (needs Java 21) | cliuwan.jar |
Every release ships a SHA256SUMS file to verify against.
macOS: the binaries are not notarised. Gatekeeper blocks anything downloaded through a browser, so either fetch it with
curlas above or clear the flag withxattr -d com.apple.quarantine cliuwan.
Requires Java 21 or newer.
git clone https://github.com/serpean/cliuwan.git
cd cliuwan
./install.shThat builds the project, installs the jar in ~/.local/share/cliuwan/, the launcher in
~/.local/bin/cliuwan, and offers to set up your credentials.
Without installing anything:
./gradlew build
java -jar build/libs/cliuwan.jar --helpTo build a native binary yourself, with a GraalVM JDK on JAVA_HOME:
./gradlew nativeCompile
./build/native/nativeCompile/cliuwan --versioncliuwan loginPrompts for your Kiuwan username and password (without echoing it), checks them against the
API, and only then stores them in ~/.config/cliuwan/credentials with 600 permissions. If
Kiuwan rejects them, nothing is written — so you never end up with a typo that fails days later.
cliuwan config # where credentials come from, without printing the password
cliuwan logout # delete the stored fileexport KIUWAN_USER=your_username
export KIUWAN_PASS=your_password
# or a non-interactive login
echo "$KIUWAN_PASS" | cliuwan login -u your_username --password-stdinEnvironment variables take precedence over the file. There is deliberately no --pass
option: a password passed as an argument lands in your shell history and is visible in ps.
Windows: POSIX permissions do not exist there, so the credentials file is written without the
600hardening applied on Linux and macOS. It inherits whatever your user profile directory grants. If that matters in your environment, useKIUWAN_USER/KIUWAN_PASSinstead, or restrict the file with ACLs.
cliuwan apps PAYMENTS # full JSON
cliuwan apps PAYMENTS --names-only # just names, one per line
cliuwan apps MY-APP --exact # exact matchThis matters more than it sounds. cliuwan reads the most recent successful analysis by
default, which is usually a delivery. The baseline can be months older and tell a completely
different story.
cliuwan analyses MY-APP --format textDATE TYPE STATUS CODE LABEL
2026-08-06 Complete delivery SUCCESS A-7ea-19fd692b3eb 1.0.0
2026-08-06 Complete delivery FAIL A-7ea-19fd67aedfa 1.0.0
2026-06-29 Baseline SUCCESS A-7ea-19f1258cc37 1.0.0
Failed analyses are skipped automatically — asking them for defects returns a 404. Pick one
explicitly with --analysis baseline, --analysis delivery, or an analysis code.
cliuwan defects MY-APP # every defect
cliuwan defects MY-APP -p 'Very high,High' # only the serious ones
cliuwan defects MY-APP --file src/main/auth # a specific path
cliuwan defects MY-APP -l Java -c Security # by language and characteristic
cliuwan defects MY-APP --cwe 89,79 # by CWEThe difference between reading 500 lines of JSON and reading three:
$ cliuwan defects MY-APP --group-by rule --format text
Application: MY-APP
Defects: 503 grouped by rule
============================================================
168 Cross-site scripting
168 SQL Injection
167 Use of Hard-coded CredentialsAvailable criteria: rule, cwe, file, priority, language, characteristic.
cliuwan defects MY-APP --count # just the total
cliuwan defects MY-APP --fields rule,file,line,cweId # only these fields
cliuwan defects MY-APP --limit 20 # first 20
cliuwan defects MY-APP --list-fields # what fields existcliuwan defects MY-APP -f json # default: pipe it into jq
cliuwan defects MY-APP -f md # markdown table for a PR
cliuwan defects MY-APP -f text # human-readable listing# The 10 files with the most critical defects
cliuwan defects MY-APP -p 'Very high' --fields file \
| jq -r 'group_by(.file) | map({f:.[0].file, n:length}) | sort_by(-.n) | .[:10][] | "\(.n)\t\(.f)"'cliuwan defects MY-APP -p 'Very high' --fail-on-found
# exit 4 if any critical defect exists, 0 if clean| Code | Meaning |
|---|---|
| 0 | Success |
| 1 | Kiuwan API or network error |
| 2 | Incorrect usage |
| 3 | Credentials missing or rejected |
| 4 | Defects found while --fail-on-found was set |
Errors go to stderr, so cliuwan ... | jq keeps working when something fails.
Responses are cached for 5 minutes under ~/.cache/cliuwan/ (or $XDG_CACHE_HOME/cliuwan/),
with 600 permissions since they can contain source paths and vulnerability details.
Entries are scoped by user, password and base URL, so switching accounts or instances never reuses the previous one's data. Rotating your password invalidates the cache, which is the behaviour you want.
cliuwan defects MY-APP --no-cache # bypass it
cliuwan defects MY-APP --cache-ttl 3600 # change the lifetime, in seconds
cliuwan cache clear # empty it
cliuwan cache info # where it livesThis is what the tool was built for. A CLI beats an MCP server here for two reasons: it only
occupies context when it is actually invoked, and its output can be trimmed with jq before
the model ever reads it.
install.sh registers a Claude Code skill in
~/.claude/skills/cliuwan/ that teaches the agent when to reach for the command and — more
importantly — to aggregate with --group-by before listing, so hundreds of defects never get
dumped into the conversation.
With it installed you can just ask, in any repository:
"What critical vulnerabilities does MY-APP have?" "Does the file I'm editing have any defects reported in Kiuwan?"
Two behaviours worth knowing, both handled here:
- Results are paginated (
count, 500 by default). Asking for a single page silently loses data on large applications.cliuwanwalks every page. /applications/defectsreports the latest baseline, not the latest analysis. On a project with an old baseline that means stale results.cliuwanresolves the most recent successful analysis and reads/apps/analysis/{code}/defectsinstead, and prints on stderr which analysis the data came from.
./gradlew test # 48 tests, no network required
./gradlew build # produces build/libs/cliuwan.jarTests spin up a JDK HttpServer that mimics the Kiuwan API rather than pulling in WireMock,
to keep the dependency list short. There is no Spring: the CLI is invoked once per query, so
startup dominates — Picocli plus the JDK HTTP client start in about 0.2s on the JVM, and about
0.02s once compiled ahead of time.
GraalVM strips record metadata unless it is registered, because static analysis cannot see
that Jackson and Class.getRecordComponents() reach for it reflectively. The registrations
live in src/main/resources/META-INF/native-image/; Picocli generates its own through
picocli-codegen.
This failure mode is worth knowing about: the image links cleanly and then throws
UnsupportedFeatureError on first use. That is why the release workflow runs every binary
it builds — exercising --list-fields, which goes through getRecordComponents() — before
publishing it. If you add a type that gets serialized, register it there and check the binary,
not just the test suite.
Contributions are welcome. Please keep the test suite green and add coverage for whatever you change.