Skip to the content.

Kiln-AI/Kiln - security scan

Repository: Kiln-AI/Kiln Commit scanned: 4e9bad4fb59d846950fa8d29de9b94c7feb20c2b Scan date: 2026-06-25 Disclosure status: public — post-only (commercial-backed / open-core; quality gate not met, no upstream issue filed)

Summary

Severity Count
Critical 4
High 46
Medium 100
Low 0 (filtered at --min-severity medium)
Info 0

Total findings: 150 (0 real, reachable, undefended after curation)

Kiln is a mature open-core “AI workbench” — evals, prompt optimization, RAG, fine-tuning, synthetic data, agents, and tools, with a desktop GUI, a local REST server, and an MIT-licensed kiln-ai Python library. It runs locally and brings your own API keys. The repo is a monorepo: a Python core (libs/core), a Python server (libs/server), a desktop app with a Svelte/Vite frontend (app/web_ui), and AI-agent dev tooling (.agents/).

The 150-finding count is almost entirely a dependency tail spread across four lockfiles, and the headline of this scan is a reachability story: the two “critical” findings and the scariest highs are a langchain cluster that is a test-only dependency — not present in any shipped runtime path. What’s left is a transitive DoS/decompression tail and one genuinely useful process observation about lockfile coverage.

Top findings

1. The two criticals (langchain-core arbitrary-code-execution) are test-only

2. langsmith CVEs are transitive, not directly used

3. starlette advisories are real server deps, but the server is localhost-bound

4. Local-server hardening: MCP DNS-rebinding default + a localhost API

5. Hardcoded GitHub App client secret — documented by-design

Patterns observed

The count is a reachability illusion, and the criticals are the clearest case yet. Trivy returns 4 critical / 46 high, and the two criticals are langchain-core RCE — on a repo whose production code never imports LangChain. This is the same lesson as the Ouroboros, mistral-vibe, and openmed scans, but sharper: here the dangerous dependency isn’t just unreachable, it’s test-only. The first curation move on a fat dependency tail is always “is the flagged package in a shipped import path?” — and for the scariest items on Kiln, the answer is no.

The real signal is a lockfile-coverage asymmetry. Kiln has no .github/dependabot.yml; the Dependabot PRs that are landing (undici, dompurify, vite) come from GitHub’s automatic security updates against the npm app/web_ui/package-lock.json. Those updates don’t parse uv.lock, so the three Python lockfiles — root, libs/core, libs/server — drift uncovered while the JS frontend stays patched. Most of what’s drifting is a transitive DoS/decompression tail (urllib3 ×4, aiohttp, orjson, h11, pillow, nltk) that is low-risk for a local BYO-keys tool, but starlette and mcp are runtime server deps where staying current actually matters. Adding pip/uv ecosystems to a Dependabot config would close the gap with the same hands-off cadence the frontend already enjoys.

The maintainer is clearly security-aware where it counts. The OAuth secret carries a correct PKCE rationale; the frontend is on auto-updates; the code that matters (the LiteLLM adapter layer) is clean of the flagged patterns. This is a well-run open-core project — the residual is process hygiene on the Python side, not a vulnerability. Combined with the commercial backing (Kiln Pro), the disciplined call is a post-only write-up, not a courtesy issue: there is no real, reachable, undefended item that clears the bar.

Notes on the tool

Disclosure timeline

This is a post-only write-up. Curation found no real, reachable, undefended item that clears the quality gate — the criticals are a test-only LangChain dependency, the server CVEs are localhost-bound, and the OAuth secret is a documented PKCE-protected tradeoff. The one actionable residual (wiring Dependabot for the three Python lockfiles) is process hygiene on a commercial-backed project that already runs frontend auto-updates, below the bar for an unsolicited issue.

Reproduce

git clone https://github.com/Kiln-AI/Kiln /tmp/scan-target
python scanner/run_scan.py --repo /tmp/scan-target \
  --reports-dir ./reports/kiln-ai-kiln \
  --min-severity medium --ignore-samples