kiyooo

Documentation

The system behind the signal.

How to actually drive it — the console page by page, a real CLI walkthrough — then the architecture, model policy, and the boundaries that keep automated triage useful without letting it become an unreviewed authority.

self-hostedlocal-firsthuman-reviewedopen source · MIT

01 / THE CONSOLE

Using the console

uv run kiyooo serve starts the FastAPI backend on :8000; cd web && npm run dev starts the Next.js console on :3000, proxying /api/* to it — one origin for the whole product. There's no auth yet, so run it behind a trusted network. All 17 pages live in one flat sidebar nav, grouped here only for the tour:

Recon & scope

Change feed (the root page) — ASSET_NEW/PORT_OPENED/TAKEOVER_RISK and friends, newest first.

Scope — orgs, seeds, exclusions; the DB-backed twin of hand-editing scope.yaml.

Ingest — a config-driven connector for any REST/JSON ASM/VM tool you already pay for, plus the unmapped-vendor-type backlog.

Attack surface

Cloud posture · Containers · Repos · Mobile — each pairs an account/scan-target form with a real scanner (Prowler, Trivy, TruffleHog, apktool+TruffleHog).

AI attack surface — the native fingerprint categories: MCP servers, inference endpoints, vector stores, model registries, notebooks, shadow AI tenants.

Attack paths — correlated chains from a weakness to a sensitive resource, computed on demand, not stored.

Triage & assets

Triage queue — verdict + evidence + Agree/Disagree in one click; select rows to bulk-assign an owner or resolve several at once.

Asset explorer — searchable inventory; click through to an asset's ownership and graph edges.

Coverage · Scan runs · Categories — orphan-asset reporting, a per-run audit trail, and a live "would this match" predicate tester.

Console

Model configuration — pin bulk/escalation/embedding roles to Ollama, Anthropic, or any OpenAI-compatible endpoint; test a connection before pinning.

Settings — turn optional modules on/off; never touches data already ingested.

Dashboard — the triage funnel, model/human agreement by category, and LLM spend.

Try it without running anything

The product page has the real thing embedded, live-clickable — same nav, same on-page copy, same CSS tokens as the real app, populated with example data — no server, no Docker, no network calls, and nothing shown is live. Open it full-page if you'd rather not scroll.

02 / THE CLI

Using the CLI

Every command below actually ran against the real database, LLM, and detection engine while writing this page — not copied from a spec. First run:

# setup + a health check that touches your own infra only, no target
uv sync
cp .env.example .env
make dev                 # docker compose up -d + alembic upgrade head
uv run kiyooo doctor      # DB / redis / minio / LLM provider / org-context — all green before anything else

Then the full loop, one org, start to a drafted ticket:

kiyooo org create acme --name "Acme Corporation" --created-by you@acme.example
kiyooo seed add --org acme --domain acme.example --added-by you@acme.example
kiyooo seed add --org acme --wildcard "*.acme.example" --added-by you@acme.example

echo acme.example > seeds.txt
kiyooo scan --seeds seeds.txt --org acme --profile standard
# -> prints a scan_run_id, call it $RUN

kiyooo detect --scan-run $RUN     # category + control predicates — pure logic, no LLM call
kiyooo triage --scan-run $RUN     # LLM adjudication over every NEW finding, evidence-cited
kiyooo route run --scan-run $RUN  # resolves owner + SLA, drafts/files per the autonomy ladder

kiyooo route approvals list                      # see what's drafted before anything sends
kiyooo route approvals send <id> --reviewer you@acme.example

Confirmed live in this exact run: on a freshly created org with no --active flag, the httpx/tlsx probe stage still executes but every check comes back DENY from ScopeGuard — safely denied, not silently skipped. That's invariant #3, not a documentation claim.

A human can skip the CLI for the review step entirely — it's the same call the console's Agree/Disagree buttons make:

kiyooo review <finding-id> --verdict true_positive \
  --rationale "confirmed via console access" --reviewer you@acme.example

And see exactly what a scan would touch before it touches anything:

kiyooo scan --explain                    # every adapter, what it reads, its rate cap — no network touched
kiyooo scan --seeds seeds.txt --dry-run   # prints the exact commands, runs none of them
kiyooo categories test                    # every category's should/should-not-match fixtures — 95 passed, 0 failed

The full reference — every command group (scope, scan, triage, route, ingest, enrich, categories, eval, feedback, metrics, audit) with every flag — is CLI.md in the repository, kept in sync with --help by policy.

03 / FOUNDATION

Architecture

An org-context repository loads at the start of every run. It is PR-reviewed and contains scope.yaml, categories/*.yaml, controls.yaml, and teams.yaml. That context governs every downstream decision — a malformed category fails loudly, never silently skipped.

A scheduler triggers the recon orchestrator, a staged DAG that shells out to existing open-source tools — subfinder, amass, dnsx, httpx, naabu, katana, nuclei, tlsx — plus Shodan, Censys, and CT-log APIs for passive discovery. Five further domain modules wrap Prowler, Trivy, TruffleHog, and apktool for cloud, containers, source code, and mobile. Discovery is orchestrated, not reimplemented.

Raw output lands in an evidence store (MinIO), then becomes an asset graph in Postgres. Immutable per-run snapshots feed a diff engine that emits changes such as ASSET_NEW, PORT_OPENED, TAKEOVER_RISK, and DECOMMISSION_CANDIDATE. A change feed is more useful operationally than a static inventory.

A parallel external finding import path — a generic, config-driven REST/JSON connector, plus hand-written Tenable/Mandiant-style adapters — maps output from tools an org already pays for onto the same schema. Their severity is stored as an attribute, never inherited; kiyooo re-derives its own verdict through rules and triage.

04 / BUILD

Tech stack

LayerChoiceReason
CorePython 3.12 + asyncioWorkers are subprocesses anyway — orchestration benefits most from async I/O.
DataPostgres 16 + pgvectorGraph relationships and embeddings without a second datastore.
Queuearq + RedisSmall, async-native job execution.
Object storeMinIO (S3-compatible)Raw evidence, response bodies, screenshots.
APIFastAPI + Pydantic v2Schema types stay aligned with structured model output.
ConsoleNext.js 15 + React 19 + TypeScriptPlain CSS, no framework dependency — one custom stylesheet, no build-step surprises.
ModelsOllama / Anthropic / any OpenAI-compatible endpointLocal by default, with an explicit bring-your-own-model escalation path.
ConfigYAML in git, Pydantic-validatedReviewable, diffable, and auditable.

05 / DECISIONING

Model routing policy

Bulk pass uses a small local model for the majority of findings with a clear verdict.

Escalation pass uses a stronger model when confidence is low, severity is high or critical, or a category is marked always_escalate.

Prompts and model digests are pinned artifacts — a pin is always a new row, never an edit. A model change is treated as a change to detection logic.

Every version must clear an eval harness measuring precision, recall, and critical-miss rate on a labelled corpus before it ships.

06 / BOUNDARIES

Design commitments

  • The model never invents facts. Every claim cites an evidence ID present in the bundle; uncited claims are rejected by the validator.
  • Evidence is untrusted input. Response bodies and certificate fields are data, never instructions — an instruction found inside evidence is a finding to report, not a command to follow.
  • The model never sends packets. It requests verification through a typed call; a scope-enforcing executor performs it, passive-only unless explicitly authorized.
  • Scope is a hard boundary. Out-of-scope targets are blocked at the network executor layer, not a config hint.
  • Suppression is explicit. False-positive reasoning is stored with the finding and is reversible, never a silent drop.
  • Nothing reaches an owner unread. Outbound routing has an approval gate that relaxes only as agreement earns it, per category.

07 / RECORDS

Data model, briefly

finding

Stable fingerprint survives rescans; a status that only ever moves forward through review, never closed by a model.

verdict

Model, confidence, reasoning, a business_impact_hypothesis, and citations — one row per adjudication pass.

human_review

Reviewer, agreement, rationale — this is where a business justification for accepting or overriding a call actually lives.

ownership

Multi-source, confidence-scored — cloud tag, IaC, CODEOWNERS, DNS audit, or a manual override.

approval

The human gate on every outbound ticket, email, or Slack draft before it sends.

model_pin

A pinned model + prompt version, changed only through an eval run and a changelog entry.

08 / OPERATIONS

Autonomy is earned, not granted

Every category begins held for a human. A critical miss demotes that category one step and files an incident.

LevelBehaviour
ShadowVerdicts computed and stored; nothing sent.
Draft for approvalEvery outbound message drafted and held for a named human to send.
Auto-fileFiles automatically once a category has earned it through sustained agreement — sampled audit continues.

09 / SCOPE

Non-goals

  • Not a pentest tool: no exploitation, writes, DoS, or credential brute force.
  • Not a scanner rewrite: it orchestrates nuclei, Prowler, Trivy, TruffleHog and others rather than replacing them.
  • Not a compliance report generator, although the data can support one later.
  • Not multi-tenant SaaS in v1: single organisation, self-hosted.

Current status

The repository is public and MIT-licensed. This page and the product page reflect the same code, tests, and labs referenced throughout — not a separate demo build.