BOHICA-LABS / BOHICA-LABS/prism

DTU clones report healthy then panic per request when run outside the build tree

Ouverte
#285 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub
Langage dominant
Rust
Étoiles
1
Forks
0
Merge moyen
4 h 46 min
PR mergées (30 j)
37

Description

A `prism-dtu-demo-server` binary only works on the machine that built it. Move it anywhere else, run it in a container, or hand it to a colleague, and four of the six clones stop working. The Claroty clone is the worst of them: it starts, reports healthy, and then panics on the first real request, so a health check says the fake is fine while every query against it kills a worker thread.

This matters beyond containers. `release.yml` already builds the demo-server for four targets, strips it and tars it (`.github/workflows/release.yml:143-194`), so the artifact exists; it just cannot run away from home.

## Expected

The crates document this contract themselves. Every affected call site carries the comment:

> `// SAFETY: fixture files are bundled at build time; missing fixture is a build error, not runtime condition.`

`prism-dtu-crowdstrike` implements it: `crates/prism-dtu-crowdstrike/src/routes/detections.rs:49` uses `include_str!`, so its fixtures are in the binary. `prism-dtu-threatintel` has no runtime reads either. Both work anywhere.

## Actual

Four crates resolve fixtures at runtime through a path baked in at compile time, so they look for files at the build machine's absolute path. Two different failure shapes:

- **Claroty fails per request.** The clone constructs, `/dtu/health` returns 200, and the first data request panics in a tokio worker. The client sees a dropped connection.
- **Cyberint fails at construction**, taking the whole harness down: `Failed to build clone pairs: failed to construct CyberintClone`, zero listeners.

The panic also prints the builder's absolute path, so a binary handed to anyone else leaks the local directory layout and username of whoever built it.

## Smallest repro

Build, move one crate's fixtures aside to stand in for "any machine that is not the build machine", and make one request.

Transcript, fresh run on develop at 09e9b28d

```console
$ cargo build --release -p prism-dtu-demo-server
$ mv crates/prism-dtu-claroty/fixtures /tmp/fixtures-away

$ cat claroty-only.toml
[harness]
bind = "127.0.0.1"
[clones.claroty]
enabled = true
bind = "127.0.0.1"
port = 17091
[clones.crowdstrike]
enabled = false
[clones.cyberint]
enabled = false
[clones.armis]
enabled = false
[clones.threatintel]
enabled = false
[clones.nvd]
enabled = false

$ ./target/release/prism-dtu-demo-server start --config claroty-only.toml &

# positive control: the process is up and serving
$ curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:17091/dtu/health
200

# the failing trial
$ curl -s -o /dev/null -w '%{http_code}\n' -X POST -H 'Authorization: Bearer x' \
-H 'Content-Type: application/json' -d '{"page_size":2}' \
http://127.0.0.1:17091/api/v1/devices
000

# server log
thread 'tokio-rt-worker' panicked at crates/prism-dtu-claroty/src/routes/devices.rs:33:10:
fixtures/devices.json must exist: fixture file not found:
/Users//prism/crates/prism-dtu-claroty/fixtures/devices.json: No such file or directory (os error 2)

$ mv /tmp/fixtures-away crates/prism-dtu-claroty/fixtures
```

The same thing shows up without touching the tree at all: build in a container and the runtime stage has no source, so the paths point at the builder's layer.

## Mechanism

`load_fixture` joins its `crate_dir` argument with `fixtures/.json` and reads it at call time. Every affected call site passes `env!("CARGO_MANIFEST_DIR")`, which expands at compile time to an absolute path on the build machine, so the read happens at runtime against a path that only exists there.

Claroty differs from Cyberint only in where the call sits: Claroty loads inside route handlers, so the failure is deferred to the first request, which is why health checks pass.

Measured on develop `561d8bac`: 33 `load_fixture`/`load_fixture_as` invocations across the four crates, sitting in 17 functions that bind `crate_dir`.

| crate | functions binding `crate_dir` | `load_fixture*` invocations |
|---|---|---|
| claroty | 7 | 7 |
| armis | 4 | 12 |
| cyberint | 4 | 12 |
| nvd | 2 | 2 |

*Corrected 2026-09-12. This section first read "17 call sites in four crates: claroty 7, armis 4, cyberint 4, nvd 2", which mixed the two units above: the 7 counted invocations, the 4s counted enclosing functions. Claroty's 7 merged in #287, so 26 invocations remain.*

Relevant paths: `crates/prism-dtu-common/src/fixture.rs:15-27`, `crates/prism-dtu-claroty/src/routes/devices.rs:33`, `crates/prism-dtu-crowdstrike/src/routes/detections.rs:49`, `.github/workflows/release.yml:143-194`.

## Ask

Could the three remaining crates embed their fixtures with `include_str!`, the way `prism-dtu-crowdstrike` already did and `prism-dtu-claroty` now does after #287?

One wrinkle found while starting on it: all 26 remaining invocations are `load_fixture_as::(...)` propagating with `?`, and the helper added in #287, `embedded_fixture`, returns `serde_json::Value` and panics. So there is no relocatable counterpart to `load_fixture_as` yet, and a straight conversion would either hand-roll `serde_json::from_str::(include_str!(...))` at each site or turn ten constructors from `Result` into panicking ones. Adding `embedded_fixture_as(raw: &str, name: &str) -> anyhow::Result` alongside it keeps the error contract those constructors already have. A PR doing that plus the three conversions is on the way, which is also the other half of #286.

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Piste de recherche

The issue is in crates/prism-dtu-common/src/fixture.rs, where load_fixture uses a compile-time path. Look at the working example in crates/prism-dtu-crowdstrike/src/routes/detections.rs:49, which uses include_str!. You need to create an embedded_fixture_as helper that returns a Result and then update the three remaining crates (armis, cyberint, nvd) to use it. Run the repro steps in the issue to verify the fix.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
rust
Domaine
backend, cli, tooling
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
Active
Clarté
Clairement spécifiée
Accessibilité débutants
75/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.