## Summary
Authenticated Kite users with any role can request `/api/v1/overview` for a cluster that their roles do not permit by selecting that cluster with `x-cluster-name`. The overview route is registered before `middleware.RBACMiddleware()` and `GetOverview` only checks `len(user.Roles) > 0`, so it returns aggregate Kubernetes inventory and capacity data from unauthorized clusters.
The issue is present on current main commit `38c9bb9d4b746c0d2a8252f3c35cdfa07ab01c21` and latest release `v0.12.2` at commit `0aae35abb2d6a8adf623fe60349261aa48753ccc`.
## Impact
A low-privileged user who only has access to one cluster can set `x-cluster-name` to another configured cluster and retrieve aggregate inventory and resource sizing data for that cluster. The response includes total node, pod, namespace, service, CPU, and memory values. This bypasses the cluster membership boundary used elsewhere in Kite.
The validated impact is confidentiality only. I did not prove Kubernetes mutation, pod names, secret values, kubeconfig contents, or bearer token exposure through this endpoint.
## Technical details
`routes.go` registers `/api/v1/overview` before the global RBAC middleware is applied:
- `routes.go:131-133`: `/api/v1` gets `RequireAuth()` and `ClusterMiddleware(cm)`.
- `routes.go:135`: `/api/v1/overview` is registered.
- `routes.go:171`: `api.Use(middleware.RBACMiddleware())` is applied only after overview and several other routes are registered.
`pkg/middleware/cluster.go:21-40` accepts the target cluster name from `x-cluster-name`, query, or cookie and injects the matching `ClientSet` without checking whether the user can access that cluster.
`pkg/system/handler.go:47-52` retrieves the selected cluster and user, but only rejects users with zero roles:
```go
cs := c.MustGet("cluster").(*cluster.ClientSet)
user := c.MustGet("user").(model.User)
if len(user.Roles) == 0 {
c.JSON(http.StatusForbidden, gin.H{"error": "Access denied"})
return
}
```
It then lists nodes, pods, namespaces, and services for the selected cluster at `pkg/system/handler.go:63-137` and returns aggregate data at `pkg/system/handler.go:147-169`.
The intended cluster boundary exists elsewhere. `pkg/cluster/cluster_handler.go:19-47` filters `/api/v1/clusters` with `rbac.CanAccessCluster(user, name)`, and `pkg/rbac/rbac.go:32-40` implements that cluster check. The vulnerable overview path skips the same check.
## Reproduction
1. Configure Kite with at least two clusters, for example `dev-cluster` and `prod-cluster`.
2. Create a user with a role that allows only `dev-cluster` and does not match `prod-cluster`.
3. Authenticate as that user.
4. Send `GET /api/v1/overview` with header `x-cluster-name: prod-cluster`.
5. Observe that the response includes aggregate inventory and capacity data for `prod-cluster` instead of returning 403.
I also validated this locally with a Go proof test. The test constructs a fake `prod-cluster` containing one node, namespace, service, and pod. The user has a role limited to `dev-cluster` and `dev-ns` only. Before calling the handler, both controls return false:
- `rbac.CanAccess(user, "pods", "get", "prod-cluster", "_all")`
- `rbac.CanAccessCluster(user, "prod-cluster")`
The direct handler call then succeeds and returns the unauthorized production cluster aggregate data.
Command run:
```bash
cd /home/unkn0wn/security_audit/kite
go test ./pkg/system -run TestOverviewAllowsUserWithoutTargetClusterRBAC -v
```
Key output:
```text
=== RUN TestOverviewAllowsUserWithoutTargetClusterRBAC
overview_rbac_poc_test.go:74: unauthorized overview response: {"totalNodes":1,"readyNodes":0,"totalPods":1,"runningPods":0,"totalNamespaces":1,"totalServices":1,"prometheusEnabled":false,"resource":{"cpu":{"allocatable":0,"requested":0,"limited":0},"memory":{"allocatable":0,"requested":0,"limited":0}}}
--- PASS: TestOverviewAllowsUserWithoutTargetClusterRBAC (0.49s)
PASS
ok github.com/zxh326/kite/pkg/system 0.711s
```
## Suggested remediation
Add an explicit cluster and resource authorization check before any overview data is queried. At minimum, reject users without `rbac.CanAccessCluster(user, cs.Name)`. A stricter fix should require the same resource permissions used by the AI `get_cluster_overview` tool:
- `get nodes` at cluster scope
- `get pods` across all namespaces
- `get namespaces` at cluster scope
- `get services` across all namespaces
Also consider moving every route that lacks its own complete authorization below `api.Use(middleware.RBACMiddleware())`, or adding per-handler authorization tests for all pre-RBAC routes.
### Summary
RaTeX’s recursive-descent parser recurses one (or more) native stack frame per nesting level at `{`, `\left`, `\sqrt{`, `^{`, etc, with **no maximum depth limit**. A short, ~10 KB input of nested groups overflows the 8 MB main-thread stack and aborts the process. With `panic = "abort"` (`Cargo.toml:48`), and because a Rust stack overflow is always a fatal `SIGABRT` regardless of panic strategy this is an unrecoverable, whole-process denial of service reachable from a single untrusted LaTeX string.
### Details
The mutual recursion has no depth guard (`crates/ratex-parser/src/parser.rs`):
```
parse_expression (:113) -> parse_atom (:281/285) -> parse_group (:451)
^ |
| on '{' (:459) recurse |
+--------------------------+
```
`\left` adds another recursive edge: `handle_left` → `parse_expression` (`crates/ratex-parser/src/functions/left_right.rs:47`). The only counters present are unrelated to depth: `leftright_depth` (a `\right`-matching counter, `parser.rs:24`) and the macro expander’s `max_expand = 1000` (`macro_expander.rs:64`), which does **not** gate brace / `\left` recursion (those tokens never pass through `expand_once`). There is no `recursion_limit`/depth parameter on `parse_group`, `parse_expression`, or `parse_atom`.
### PoC
<img width="1097" height="158" alt="image" src="https://github.com/user-attachments/assets/29b837a2-c455-4cb6-a055-514b31c999c6" />
```
$ python3 -c 'import sys;sys.stdout.write("{"*200000+"x"+"}"*200000)' | ./target/release/parse
thread 'main' has overflowed its stack
fatal runtime error: stack overflow, aborting
Aborted (core dumped) # exit 134
```
(Other nesting forms work equally, e.g. `\left(`×N, `\sqrt{`×N, `^{`×N.)
### Impact
A single small request crashes the whole RaTeX process. In a typical server-side math-rendering service this is a reliable, unauthenticated DoS; on smaller worker-thread stacks (e.g. a 512 KB async runtime thread) only a few hundred bytes of nesting are required.
### Summary
The public parser entrypoint `ratex_parser::parse(&str)` panics on the **9-byte** input `\verbéxé` (i.e. `\verb` followed by the non-ASCII delimiter `é`). When handling a `\verb` command, the parser slices the verbatim argument with **byte** indices (`arg[1..arg.len() - 1]`); if the delimiter character is multibyte UTF-8, index `1` lands inside that character and Rust panics with *“byte index 1 is not a char boundary”*. Because RaTeX’s release profile sets `panic = "abort"` (`Cargo.toml:48`), the panic aborts the **entire process** — not just the current request/thread — making this a hard denial of service for any service that renders untrusted LaTeX.
### Details
## Affected code
`crates/ratex-parser/src/parser.rs`, `parse_symbol_inner`:
```rust
if let Some(stripped) = text.strip_prefix("\\verb") { // parser.rs:901
self.consume();
let arg = stripped.to_string(); // e.g. "éxé"
let star = arg.starts_with('*');
let arg = if star { &arg[1..] } else { &arg }; // parser.rs:905 (also byte-sliced)
if arg.len() < 2 { // byte length
return Err(ParseError::new("\\verb assertion failed", Some(&nucleus)));
}
let body = arg[1..arg.len() - 1].to_string(); // parser.rs:910 <-- PANIC on multibyte delimiter
...
}
```
For input `\verbéxé`: `arg = "éxé"`, where `é` = `U+00E9` (bytes `C3 A9`). `arg.len()` is the **byte** length (5), the `< 2` guard passes, and `arg[1..4]` starts at byte index 1 — inside the first `é` (bytes 0..2) — so the slice panics. The lexer groups `\verb<delim>…<delim>` correctly with char semantics (`lexer.rs` `lex_verb`); only the parser mishandles it.
### PoC
<img width="1109" height="205" alt="image" src="https://github.com/user-attachments/assets/cd4bc6ae-23dd-458f-826c-6ce4e85c7005" />
```
$ printf '\\verb\xc3\xa9x\xc3\xa9\n' | ./target/release/parse
thread 'main' panicked at crates/ratex-parser/src/parser.rs:910:27:
start byte index 1 is not a char boundary; it is inside 'é' (bytes 0..2 of string)
Aborted (core dumped) # exit 134 — panic=abort kills the whole process
```
### Impact
Any application that renders untrusted LaTeX through RaTeX (web “render this math” endpoint, WASM in-browser use, the FFI embedded in another app) can be crashed by a tiny string. With `panic = "abort"` in release builds, the crash takes down the whole process / server, so a single malicious formula causes a full-service DoS (and, in batch pipelines, drops all queued work).
## Remediation
Slice by character boundaries instead of byte indices, mirroring the UTF-8-correct logic the lexer already uses. For example:
```rust
let chars: Vec<char> = arg.chars().collect();
if chars.len() < 2 { return Err(ParseError::new("\\verb assertion failed", Some(&nucleus))); }
let body: String = chars[1..chars.len() - 1].iter().collect();
```
(Apply the same char-aware handling to the `*` strip at `parser.rs:905`.) More broadly, consider not using `panic = "abort"` for builds embedded in long-running services, and/or wrapping parsing in `catch_unwind` at the FFI/WASM boundary — but the byte-slice fix is the direct correction.
---
_-= Per source details. Do not edit below this line.=-_
## Source: amazon-inspector (eae82aa94c358f1f00f8a12e8b583cee82efdcf09d2f80263e6851ac0fec98f3)
The PyPI project `jsonschemavalidation` builds a wheel whose installed top-level package is literally `jsonschema` (`[tool.hatch.build.targets.wheel] packages = ["src/jsonschema"]`). Project metadata (author name, homepage pointing at github.com/python-jsonschema/jsonschema, README, description) mirrors the legitimate upstream `jsonschema` project. Installing this distribution places files into the real `jsonschema` import namespace on the installer, so `import jsonschema` in downstream code would resolve to the code shipped by this distribution rather than the genuine one. The current 4.26.0 contents appear to be an unmodified copy of upstream and no exfiltration, install-time fetch-and-execute, credential harvesting, silent-relay, backdoor, or other installer-harm payload is present in this version. The concern is the namespace-collision channel itself: a name-confusion distribution that populates a widely-used import name is a plausible squat/hijack setup, but name-similarity and namespace collision alone are subjective signals without a payload, so human review is appropriate rather than an automatic block.
## Source: kam193 (b7a6e1ff837eac0382559815111106e3e85b3825b72f38355551d5874cc24b0e)
A clone of a legitimate package with an embeded reverse shell. In the published version, the reverse shell was connecting to a private IP address suggesting the package was not yet fully weaponized.
---
Category: MALICIOUS - The campaign has clearly malicious intent, like infostealers.
Campaign: 2026-07-jsonschemavalidation
Reasons (based on the campaign):
- The package contains code to create a reverse shell, allowing an attacker to execute any commands on the victim's machine.
- clones-real-package
- obfuscation
ActiveState explains how GitHub Actions attack chains can evade traditional CI security scanners, why passing a scan doesn't guarantee a secure pipeline, and how organizations can better govern their CI/CD workflows. [...]
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (e68008eb00de03f4b1fc2f80ab7930b59a0305ace0a681864a1d025186bcd1e9)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp12/MAL-2026-9999.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (ae6a5afcedcdfdc53b2404bb13a6b30de93c9d71d0697e4a58bed9ee490042eb)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp11/MAL-2026-9998.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (a9c06fa97c290e6c44e357ba895d7ff66733519f29fc592e94e7149de63ceeaa)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp10/MAL-2026-9997.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (a0b1df77ae67924b6d063d2da58a43c8e2253dde93222a037a98f49376b359c7)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp08/MAL-2026-9995.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (fa30ff936894b20c6a757ae0e6b0fc98f6ad23cd6c12fd9896a34fe6d2038ea3)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp09/MAL-2026-9996.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (06edcf6baef91d8eee16da3b1170b5ac25b047490ee3ad8570d639d6c57ab04d)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp06/MAL-2026-9993.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (710b2b5878e3104ee22de0059795fdc6b309be8b8c30100291f6097feb2acf48)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp07/MAL-2026-9994.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (395a2747617485255b7f6566c59ca148b5d59b3acd44e256f943aa8875474f99)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp05/MAL-2026-9992.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (d0ea687d400a3a0e9ce3b50e73fa061c80dc716665ea50f27f1238a26c2529ea)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp03/MAL-2026-9990.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (889ebf2a26e92fcb8ec7ddf08da0804f0f46db582463b7504c2175d5075bfdde)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp04/MAL-2026-9991.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (2284c54b67ee670e1710e2cf7442dbdcbe2aaee2471d82edb4178e8af04d983c)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp01/MAL-2026-9988.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (f61a5a990ac9c442440841875510ad83f3c73e8bcbd6a07f1709f26a77a3cec3)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztxtwtmp02/MAL-2026-9989.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (a383d0093156c17acbf957c922b7f7ceaaec7fa4297bfba17aa4bef32e75787f)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztestproxyfooabcxyz/MAL-2026-9987.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (26917fcf22dd1f686e736442782825a7cddde41d9ad8ffd1d905d7d957501fd5)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztestno44/MAL-2026-9985.json))
---
_-= Per source details. Do not edit below this line.=-_
## Source: ghsa-malware (52cfa5738ed40f927e55086f736010d3c8801d40adf6bd67c9e46f109d534682)
---
Credit: [OpenSSF](https://github.com/ossf/malicious-packages) ([source](https://github.com/ossf/malicious-packages/blob/9fbb5fba02ba2f02395d3d2d43c31b2c482b7a9d/osv/malicious/rubygems/zztestownedtmp1/MAL-2026-9986.json))