Skip to the content.

xerrors/Yuxi — security scan

Repository: xerrors/Yuxi — 5.6k★, MIT, a multi-tenant Agent Harness platform with knowledge-base + knowledge-graph management (Chinese-language project: “结合知识库、知识图谱管理的 多租户 Agent Harness 平台”). FastAPI backend + Vue web UI, with built-in agent skills (incl. a MySQL reporter) and a RAG/chunking pipeline. Commit scanned: 3eac017d3e69 (HEAD of main at scan time) Scan date: 2026-06-19 Disclosure status:Resolved. Public courtesy issue (#780) filed — focused on the one app-code best-practice item (wildcard CORS) + a reachable dependency refresh (the auth-path pyjwt, langchain), with explicit credit for the parts the developer built correctly. The maintainer landed both within ~2 days: a YUXI_CORS_ORIGINS env-allowlist (locked down by default in production) with an explicit allow_credentials=False downgrade when * is present, and a pyjwt bump to 2.13.0. Non-strict-norm (no SECURITY.md); the items were best-practice / public-CVE, not exploit-shaped, so a focused public issue was the right channel.

Summary

Severity Count
Critical 0
High 22
Medium 48
Low 0
Info 0 (filtered)

70 findings, all first-party. After curation: a well-built multi-tenant harness where the developer anticipated every obvious agent/tenant-injection vector — and a reachability discipline (does tenant/agent input reach a sensitive sink?) came back clean on every source-code candidate. The residual actionable surface is one genuine best-practice item (wildcard CORS + credentials on the multi-tenant server) and a dependency tail that includes a reachable-but-correctly-used pyjwt on the auth path. Every guard I traced held.

Where the developer did it right (the credits, because they’re the story)

This scan was picked specifically as a multi-tenant target to apply a sharp reachability discipline — trace whether tenant/agent-controlled input reaches a sensitive sink. Three obvious candidates, three correct defenses:

When the obvious sinks are all correctly guarded, the residual findings are the best-practice edges — which is exactly the shape below.

Top findings (curated)

1. backend/server/main.py:46 — wildcard CORS with credentials on the multi-tenant server

Tool: Semgrep (wildcard-cors) Verdict: Real best-practice — the one genuine app-code item.

app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

All four knobs at maximum on a multi-tenant API server. Browsers reject Access-Control-Allow-Origin: * together with credentials, so this is partially self-defeating in a browser — but on a multi-tenant platform the intent (“any origin may send credentialed requests”) is exactly what cross-tenant CSRF/credential-leak protections exist to prevent, and non-browser clients don’t enforce the rejection. Same unmitigated shape as ReMe (vs the agency-swarm inline-downgrade pattern). Fix: read an explicit allowlist from config; when * is present, downgrade allow_credentials to False.

2. backend/{,package/}uv.lock — reachable dependency tail (no Dependabot)

Tool: Trivy / pip-audit Verdict: Real version-matches; the pyjwt ones are on the auth path (reachable) but the app’s usage is already correct, so these are pure version bumps.

3-N. By-design / FP / out-of-runtime

Finding Files Verdict
avoid-v-html MarkdownPreview.vue (DOMPurify), AgentFilePreview.vue (Shiki-escaped), docs/.vitepress/* ×3 (out of runtime) FP — see credits above
4× SQL text(f) / formatted mysql-reporter/.../describe_table.py Gated — strict validate_table_name allowlist (see credits)
5× Dockerfile root + 3 missing-user + apt-get Dockerfiles Real container-hardening best-practice (add non-root USER)
2× gitleaks generic-api-key .env.template:7 (placeholder), md_parser_utils.py:131 (chunking code, no secret) FP
non-literal-import agent built-in registry, knowledge parser factory By-design — plugin/skill discovery

Patterns observed

The reachability discipline cuts toward “safe” as often as “severe” — and confirming the guards is the work. Tracing tenant/agent input to a sink can raise a by-design line to a high-severity finding as readily as it clears one. Here, the discipline applied to three multi-tenant sinks (agent-SQL, tenant-JWT, agent-markdown→HTML) came back clean each time — but only because I read the guard, not because the scanner said so. The scanner flagged all three (f-string SQL, v-html). The verdict “safe” required reading validate_table_name’s regex, the jwt.decode options dict, and the DOMPurify import. A curated report’s value is the same in both directions: a report that stopped at the rule would have been wrong here (over-stating Yuxi’s SQL and v-html), just as one would under-state a case where the guard didn’t hold.

A multi-tenant harness whose developer pre-empted the obvious injections is a “what right looks like” reference for the agent-tool-SQL + tenant-JWT surface. The validate_table_name allowlist in front of an LLM-controlled DESCRIBE is the cleanest agent-tool-SQL defense in the series; the jwt.decode call is the cleanest tenant-auth call. Where prior scans surfaced the unguarded version of these (the recurring text(f) class, the airweave JWT items), Yuxi shows the guarded version of both in one codebase.

usedforsecurity-style intent markers, continued: DOMPurify import is the XSS equivalent. Just as mistral-vibe’s hashlib.sha1(..., usedforsecurity=False) was the machine-readable “this hash isn’t security,” a DOMPurify.sanitize in the render path is the machine-readable “this v-html is sanitized.” A scanner that traced the v-html binding back to a DOMPurify-wrapped value could suppress the avoid-v-html finding — the same call-graph-aware suppression proposed for the sha1 and phc_-key cases.

Notes on the tool

Disclosure timeline

Reproduce

git clone https://github.com/elfrost/ai-patchlab
cd ai-patchlab
pip install -e ".[dev]"
python scanner/run_scan.py \
  --from-git-url "https://github.com/xerrors/Yuxi" \
  --reports-dir reports/xerrors-yuxi \
  --min-severity medium \
  --ignore-samples

External tools (Semgrep, Gitleaks, Trivy, pip-audit) need to be installed separately — see the project README.