feat: OTLP trace receiver, live tracing, and Open WebUI chat module - #6
Conversation
Following the README recipe verbatim on a fresh clone leaves the stack
half-up: the web container exits 1 during bootstrap with
DJANGO_SECRET_KEY must be set to a strong unique value.
CommandError: Refusing to bootstrap with unsafe configuration.
validate_startup_environment() blocks on three values, but the recipe only
told you to change two. Postgres, Hatchet and the worker all report healthy,
so nothing looks wrong until you go read the web logs.
Name DJANGO_SECRET_KEY in the recipe, say what the check enforces, and flag
it in .env.example next to the value with a command to generate one.
Also give web the restart policy the worker already has. Its whole command
is idempotent -- migrate, bootstrap and seed each skip what already exists --
so a restart is safe, and without one a gunicorn crash or a Postgres blip
leaves the UI down permanently. A bad .env now fails repeatedly in the logs
rather than once into a container nobody thinks to look at.
run_due_monitors claimed due monitors with select_for_update(skip_locked=True)
while select_related pulled in last_run and created_by. Both are nullable, so
the ORM joins them with a LEFT OUTER JOIN, and PostgreSQL rejects FOR UPDATE
against the nullable side of one:
FOR UPDATE cannot be applied to the nullable side of an outer join
Every tick therefore raised NotSupportedError on any PostgreSQL deployment, so
no monitor ever launched a run. The sweeper catches and logs the failure, so the
only symptom was a warning once a minute:
Monitor tick failed: FOR UPDATE cannot be applied to the nullable side of an
outer join
Observed on a Container Apps deployment against Azure PostgreSQL 16.
Lock only the monitor rows with of=("self",), which keeps select_related and the
skip_locked guarantee that two workers never claim the same tick.
The test suite runs on SQLite, which drops FOR UPDATE entirely and so cannot
reproduce this. The claim query moves into due_monitors() so the lock target can
be asserted on any backend, and a PostgreSQL-only execution test covers the
regression itself. CI is SQLite-only, so that test is skipped there; running the
suite against PostgreSQL reproduces the failure without this fix.
User-creation paths (admin add-user, self-registration, demo signup) made accounts with no project membership, so request.project resolved to None and every project-scoped view 500'd. Centralize the grant in accounts.services.grant_default_project (viewer on the 'default' slug) and call it from all four creation paths, matching the existing WorkOS behavior. Add a middleware backstop so a membership-less user still resolves the Default workspace instead of crashing.
Adds an off-by-default module that embeds Open WebUI in Studio at /chat/,
signed in as the Studio user. With SIMPLEAUDIT_CHAT unset nothing changes:
the routes 404, the sidebar has no Chat entry and no extra process runs.
Open WebUI serves from the root of an origin only — it has no base-path
setting and its HTML references /static, /api and /ws absolutely — so it
cannot be proxied under Studio's own /chat/ path. It gets its own origin,
which Studio embeds in an iframe.
Sign-on uses Open WebUI's trusted-header mode. A proxy in front of it asks
Studio who the browser is (GET /chat/authz, the standard forward-auth
contract) and injects the answer as X-Studio-Email/-Name/-Role. Django stays
the only authority on identity; nothing outside it reads sessions or user
tables. Superusers and workspace admins map to Open WebUI's admin role,
everyone else to user.
Two deployments, same contract:
embedded infra/chat_proxy.py, a stdlib HTTP proxy started by
`uvx simpleaudit-studio --chat` alongside Open WebUI on loopback
docker `docker compose --profile chat up`, where Caddy does forward_auth
and Open WebUI publishes no port
Open WebUI must be reachable only from the proxy: it believes the identity
headers on any request it receives. Both proxies strip client-supplied
X-Studio-* headers before adding their own. Note that `open-webui serve`
ignores HOST/PORT and defaults to 0.0.0.0, so the bind address is passed as
CLI flags; it also writes its signing key to the working directory, so the
process runs from its own data folder.
WebSockets are not proxied in embedded mode; Socket.IO falls back to HTTP
long-polling and SSE streaming is unaffected.
The chat module now runs by default where it can: `uvx simpleaudit-studio` starts Open WebUI alongside Studio, and `.env.example` sets both switches the Compose deployment needs, so a plain `docker compose up -d` brings the chat containers up without an extra --profile flag. Opting out: uvx simpleaudit-studio --disable-chat (--no-chat also accepted) SIMPLEAUDIT_CHAT=disabled (off/false/no/0/none too) comment out SIMPLEAUDIT_CHAT + COMPOSE_PROFILES in .env for Compose SIMPLEAUDIT_CHAT previously recognised only "embedded" and "docker", so any other spelling — including "disabled" — silently meant off. It now reads a documented set of disabling values and treats anything else as on, which is also what makes the CLI default work. Deployments that set neither the variable nor the Compose profile are unaffected: the routes still 404 and nothing extra runs.
Open WebUI takes minutes to come up on a first run — it is downloaded, then it migrates its database — and until now the console said only "first start downloads it" before the chat's own log poured into the terminal. The CLI now prints that chat is starting, warns on a first run that this means a ~1 GB download, and names the folders its data and log live in. A background thread polls until Open WebUI answers and prints either the ready line with the /chat/ URL, or why it stopped, pointing at the log. Studio and the worker come up meanwhile, as before. Open WebUI's own output now goes to openwebui/server.log instead of the console, which also makes a failure readable after the fact. The readiness poll uses urllib rather than httpx so it does not log a request line every two seconds.
Chat was started with a bare Popen and stopped with terminate(), which left two ways to orphan it. `uvx open-webui` is a launcher with the real server as its child, so terminate() signalled the launcher and left the server holding the port; and a hard-killed run (SIGKILL, crash, closed terminal) never got to stop anything at all, so the next start failed to bind. Open WebUI now follows the same pattern as the embedded engine: - one instance per process, guarded by a lock; a second start returns the first - started in its own process group, so stopping it reaches the real server - stopped by the CLI's shutdown path and by atexit, SIGTERM then SIGKILL - its PID recorded in openwebui/open-webui.pid, so the next start can stop a leftover from a killed run — only when its parent is gone, never one that belongs to another running Studio Verified against the real Open WebUI: explicit stop, exit without stopping, and SIGKILL of the Studio process followed by a restart all end with no surviving process and a free port.
It is printed from a background thread minutes after startup, and stdout is block-buffered when the CLI's output is a file or a pipe rather than a terminal, so the line could sit unwritten for the rest of the run.
Open WebUI brings its own sidebar, header and settings, so wrapping it in the Studio shell put two navigations on the same edges and left the chat itself in a boxed iframe. The page now stands alone: a slim bar with a centred "Back to Studio" link, and the chat filling everything below it. The page no longer extends base.html, so it carries its own small stylesheet rather than pulling in Tailwind, htmx and the sidebar for one link.
The chat now fills the viewport and the "Back to Studio" link sits on top of it as a small translucent pill, centred at the top. Nothing is reserved for a bar, so Open WebUI keeps the full height it expects. Only the pill takes clicks — the strip around it passes them through to the chat — and it sits at 75% opacity until hovered or focused, so it stays out of the way of Open WebUI's own header.
…both directions
The module was spread across infra/ (chat.py, chat_proxy.py, chat_api.py), two
management command folders, infra/tests and the shared templates folder, which is
the wrong shape for something that is meant to be optional and removable. It is
now one app:
chat/
config.py what the module is configured to do, and who you are
views.py urls.py the iframe page and /chat/authz
proxy.py the forward-auth proxy + Open WebUI's lifecycle
api.py talking to Open WebUI's API, both directions
management/ sync_chat_models, chat_knowledge
templates/ tests/
The app is installed either way so its templates, commands and tests resolve;
SIMPLEAUDIT_CHAT still decides whether anything runs. Removing the feature is now
deleting a folder and four one-line references.
chat/api.py is new, and is the scaffolding for tighter integration. It
authenticates the way the proxy makes the browser authenticate — the trusted
headers to /api/v1/auths/signin, then the token it returns — so there is no API
key to provision and every call runs as a real Open WebUI user.
Push: a Studio model connection is a base URL plus a key, which is exactly Open
WebUI's OpenAI-compatible provider config, so `sync_chat_models` writes
connections into OPENAI_API_BASE_URLS/KEYS/CONFIGS. Those lists are hand-editable
in Open WebUI, so each pushed entry carries a simpleaudit_connection_id marker: a
sync replaces the marked entries and keeps the rest, which the merge is tested
for in both directions.
Pull: `chat_knowledge` lists knowledge bases and their files, normalised to plain
dicts so Studio code never sees Open WebUI's schema.
Nothing syncs automatically yet — both directions are explicit commands until the
shape settles.
…rowser console Connections now reach Open WebUI on their own: chat/signals.py follows ModelConnection and RegisteredModel, and the CLI syncs once as soon as chat answers, which covers whatever changed while it was off. The management command stays for a manual run and for --dry-run, and now shares the same code path. The push deliberately stays off the request's path — on_commit so a rolled-back row is never pushed, in a background thread so a save does not wait on a second service, debounced so an edit that writes a connection and its models is one push, and best-effort so a chat that is down is logged and forgotten. Studio's data is the source of truth; the next push catches up. A connection's registered models are pushed as that provider's model_ids, so chat offers what Studio registered rather than everything the provider lists. No registered models means no restriction. Two things the browser console complained about: - WebSocket upgrades were answered with 501, so Socket.IO retried and fell back to polling. The proxy now tunnels them: forward the handshake with the identity headers, and once Open WebUI answers 101, pipe the two sockets together. - Open WebUI polled Ollama on every page load (a 500 each time) and showed an empty Ollama section in its settings. Nothing in a Studio deployment serves Ollama, so it is off in the environment for a fresh instance and turned off through the API on every sync for one that already had it on. Verified against the running instance: a rename reached chat within the debounce window, a real handshake through the proxy returned 101 followed by socket.io's OPEN frame, and ENABLE_OLLAMA_API is now false.
Opening Studio on 127.0.0.1 left /chat/ blank. The iframe always pointed at localhost:8801 while the session cookie was on 127.0.0.1 — cookies are per host, not per port — so the proxy saw no cookie, sent the frame back to Studio, and Studio embedded the frame again. A loop that renders as nothing. SIMPLEAUDIT_CHAT_URL still wins, for a deployment that knows its own hostname. With it unset, the origin is now derived from the request: the host the browser is already on, plus the proxy's port. 127.0.0.1 embeds 127.0.0.1, localhost embeds localhost, and a LAN address works without configuring anything. The proxy's signed-out reply also breaks out of the frame to Studio's login page instead of redirecting inside it, so a session that really is missing shows a login page rather than a blank frame.
The proxy held a single httpx.Client for the life of the process. httpx clients keep a cookie jar, so the Set-Cookie headers coming back from Studio and from Open WebUI were stored and sent again on the next request — whoever it came from. A cookieless request to the proxy was answered as the last signed-in user: /chat/authz returned 200 with that user's identity headers, and the chat opened as them. Clients are now built per request over a shared connection pool, so nothing is carried between requests. Every identity the proxy forwards comes from the request it is handling. Also adds chat/tests/test_proxy.py, covering identity forwarding, dropping a client-supplied identity, the signed-out page, and that neither Studio's nor Open WebUI's cookies survive into a later request.
…ngo per asset The session-leak tests passed against a shared client, so they were documenting the fix rather than guarding it. The stub Studio compared the whole Cookie header for equality, and a leaked jar carries three cookies, so the leak it was supposed to catch came back as a 401 and looked like correct behaviour. It now matches the session cookie within the header, and a control test puts the shared client back and asserts the leak reappears — if that control ever passes silently, the guard below it has stopped working. Five mutations of the proxy now fail the suite: a shared client, a cache that ignores the cookie, dropping the trusted-header stripping, keeping X-Frame-Options, and serving the chat to a signed-out browser. The third needed a new test: forged headers were only checked in our own casing, which dict.update happens to overwrite, while a lowercase one would be sent alongside ours and read first by the upstream. Identity is also cached for a few seconds now, keyed on the exact cookie header. Open WebUI's page pulls dozens of assets and each one asked Django who the browser was: 20 requests cost 20 round trips, now 2. A sign-out takes effect within the TTL (SIMPLEAUDIT_CHAT_IDENTITY_TTL, 0 disables it), and an unreachable Studio is never cached, so a blip does not sign everyone out.
Three cases said nothing the tests around them did not already say, and one missed the case it was named for: - the proxy's same-casing forgery test is now one case of the casing test that replaced it, which also counts the identity headers that arrived — a duplicate is what a casing mismatch produces, and a plain lookup never sees it; - "a different cookie is a different answer" repeated the session-leak test above it, which fails the same way if the cache stops keying on the cookie; - the view test for the iframe origin was written before ChatOriginTests, which covers the same thing per host; - the cache's TTL test only turned the cache off, so a cache that never expired passed it. It now waits for an entry to age out, and turning the cache off is its own case. Checked by mutation: a shared client, a cache that ignores the cookie, a cache that never expires, dropping the header stripping, keeping X-Frame-Options and serving the chat to a signed-out browser all fail the suite.
Open WebUI has no embed mode, but its app shell loads /static/custom.css on every page. Answer that one request with chat/embed.css instead of forwarding it, so the iframe renders without the sidebar (collapsed rail, expanded panel, and resizer). The rule lives in this repo and survives Open WebUI upgrades; both modes share the file — the proxy reads it directly, Caddy mounts it read-only.
The panel was hidden but the #sidebar-toggle-button in the top bar survived — with the panel gone it only opened a blank gap, so it goes with the rest.
…hats The embedded chat is a throwaway, single-model surface, so: - pin the model via a ?model= URL param (chat.config.MODEL) - hide the "..." (Chat actions) menu button in embed.css - force every chat to be temporary so nothing piles up in Open WebUI history: USER_PERMISSIONS_CHAT_TEMPORARY_ENFORCED seeds a fresh instance, and ChatAPI.enforce_temporary_chats() updates the stored user.permissions config on each sync (the stored value otherwise wins over the env var)
The temporary_enforced permission is dead for Studio users: the Open WebUI frontend skips it for the admin role, and Studio superusers/workspace-admins map to admin. Instead the iframe URL now always carries ?temporary-chat=true, which the frontend checks unconditionally on load. Since the New Chat button is hidden, a fresh chat only ever starts from a full page load, so the param covers every case. Also hide the two escape hatches in the top bar so the embed cannot be switched back to a persisted chat: - #temporary-chat-button (toggle temporary mode off) - #save-temporary-chat-button (persist a temporary chat) Drops the now-redundant enforce_temporary_chats() permission push from api/sync/proxy and its tests.
The top-bar model picker now: - closes when clicking outside (a transparent backdrop sits above the iframe, which otherwise swallows all clicks so the document handler could never fire) or pressing Escape - has a search box that filters models by name/id, hiding empty groups - flips to the right edge when it would overflow the viewport, and caps its width to the window so it never overflows on narrow screens - dedupes selected model ids (the same model can be registered under more than one connection)
The dropdown was anchored to the centered toggle with a fixed 256px width, so on narrow windows it overflowed the right edge (and flipping to the right edge overflowed the left instead). Make the menu position:fixed and compute its width and position in JS: cap the width to the window, shift it left when it would overflow the right edge, and open upward when it would overflow the bottom. Re-runs on resize so a live window resize keeps it on screen.
Horizontal centering and the width cap are now pure CSS (left: 50% + translateX(-50%), width capped to the viewport), so the menu can never overflow the left or right edge at any window size — no JS measurement to race a live resize. JS only sets the vertical position, flipping the menu above the toggle when it would overflow the bottom.
Each model row on /connections/ now offers a chat icon (before the edit/delete actions) that links to /chat/?model=<id>, opening the embedded chat already pinned to that model. The icon only renders when the chat module is enabled (SIMPLEAUDIT_CHAT), and stays visible in read-only mode since chatting is a read-only action. ChatView reads a ?model= param from the Studio page URL and pins the iframe to it, overriding the configured default.
A hand-typed /chat/?model=o3 could pin the chat to any model id. The /connections chat icon now links to /chat/with/<model_id>, a view that validates the model is visible and enabled for the user, stashes it in the session, and redirects to /chat/. ChatView consumes that session value exactly once (pop), so the pin survives the redirect but not a refresh, and a query param can never pin the chat.
Open WebUI identifies a model by model_id alone, so the same id registered under two connections collides in its model list. Push a prefix_id per connection (keyed on the connection id) so every pushed id is globally unique; Open WebUI strips it before the upstream call. The handoff, picker, iframe ?models= param and saved preference all carry the prefixed id, and the /chat/with/ handoff now takes connection + model. The default model is no longer a config value: the user's saved chat_model preference wins (stale ids dropped), else the first model they can see, else nothing. The picker persists its selection to /me/preferences/.
The proxy piped the upstream's handshake reply straight through, so a refused upgrade (a 401 when trusted-header auth is not applied to the Socket.IO path, a 500, ...) reached the browser as raw bytes dressed up as WebSocket frames and failed opaquely. The handshake reply is now read first: only a 101 is piped, anything else is forwarded as a plain HTTP response (the upstream's own status and safe headers when well-formed, a clean 502 otherwise. Bytes the upstream sent alongside the 101 are replayed before the pipe starts so nothing is lost. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> EOF )
The ?models= pin only takes effect when the id exactly matches a model Open WebUI knows; otherwise the chat silently falls back to its default model. OpenAI-compatible providers register models by the id their /v1/models endpoint returns, which can differ from Studio's model_id, so a mismatch is easy to create and invisible at chat time. push_now now lists the registered models and logs a warning naming the connection and the missing ids. Reconciliation is a diagnostic: it never raises, and a models-endpoint failure just skips it. plan_openai_config now pairs the parallel URL/key/config lists strictly by index over the union of indices present in any of the three, instead of padding keys to the URL length. A hand edit or a partially failed write can leave the lists out of sync; the old padding could attach one provider's key to another provider's URL. Entries without a base URL are dropped, since a URL is what makes a provider usable. Also split NoAdminError out of ChatAPIError: a missing superuser is a misconfiguration, not a transient state, so the background worker logs it loudly with an actionable message instead of a quiet skip. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
A user with no project at all got an empty model picker and a dead iframe. The chat page now renders a centered "No models available" state with a link to /connections/ instead, and skips the picker and iframe entirely. A user who has a project but no models yet still gets the normal page: the picker shows its own "No models yet" hint and the iframe loads. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Merges 'Generate' and 'Rotate secret' into one button. The button creates a credential if none exists, or rotates (with confirm) if one does. For 'None' auth mode it just sets up the open endpoint — no secret, no rotate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Merged 'Generate' and 'Rotate secret' into one button - Backend create_credential now upserts: reuses existing target_id, updates auth mode and regenerates secret in place - Switching between None/Basic/Bearer works without orphaned creds - JS simplified: always calls create, no rotate logic in UI Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The demo server binds 0.0.0.0, so the credential-less /auto-login/ endpoint let any machine on the LAN sign in as the bootstrap user. - CLI generates a secrets.token_urlsafe(32) token at startup and prints the full one-time sign-in link in the banner - auto_login_view checks the ?token=... param in constant time (hmac.compare_digest) and consumes it on first use, so the URL cannot be replayed - browser readiness polling now hits /healthz instead of the token URL so the probe does not consume the one-time token - if no browser opens, the printed link is the manual fallback Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
# Conflicts: # simpleaudit_studio/cli.py
Brings in the full Open WebUI chat integration (41 commits): - Chat module with model sync, proxy, and per-connection pinning - Port conflict detection with force-kill option - One-time /auto-login/?token=... (cherry-picked + merged) - sr-only label scroll fix, favicon branding, start.sh entrypoint Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Switch CI from manage.py test to pytest --testmon -n auto with a .testmondata cache for affected-test selection - Update README test section to document pytest commands (fast, full, affected-only, targeting) - Fix 21 pre-existing ruff errors (unused imports, unsorted imports, Optional[X] -> X | None, f-strings, startswith tuple, nested with) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The engine multi-rep path does not forward trace correlation, so a trace_config on a repeated run was silently ignored. Log a warning so users are not misled into thinking trace evidence was collected. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
verify_bearer hashed the presented token against every enabled bearer credential on each request — O(N) HMAC work per span export. Add a short, indexed token_prefix (first 16 chars of the token) to OTLPCredential so the candidate set is narrowed by an indexed query before the constant-time hash compare. - Add OTLPCredential.token_prefix (db_index) + migration 0006 - Set the prefix in create_credential and rotate_credential (bearer) - verify_bearer filters by prefix first, falls back to a full scan for legacy rows with an empty prefix so verification never regresses - Tests: prefix is stored and used; a same-prefix/different-hash token is rejected; legacy empty-prefix rows still verify
The studio kept its own hand-rolled in-memory span store (_SPAN_STORE, a dict of raw span lists) in otlp_views.py, duplicating the engine's SpanStore and storing un-normalized spans. Route /otlp/v1/traces through the engine's parse_otlp_json + SpanStore so ingested spans use the same normalized schema the run path and the judge's evidence selection use. - Replace _SPAN_STORE dict-of-lists with a dict of engine SpanStore - Ingestion now normalizes via SpanStore.add_many (single source of truth) - Drop the dead _store_spans helper - get_spans_for_target / clear_target_spans now operate on SpanStore Full fast loop: 543 passed, 1 skipped (unrelated DEMO_MODE).
The multi-rep path previously warned and silently dropped trace_config. Now run_scenario_repeated accepts trace_config, builds a provider, and shares a per-rep TraceCorrelation (minted at each rep boundary) so each rep's turn->trace links are attributable. After the run, evidence spans are fetched per rep and attached under judgment["evidence_spans"], matching the single-rep path. - engine.run_scenario_repeated: accept trace_config, forward audit_run_id + a per-rep correlation callable to the engine - worker: pass trace_config to the multi-rep path; drop the warning - Tests: forwarding contract (with/without trace_config) Full studio fast loop: 545 passed, 1 skipped.
- model_registry/otlp_services.py: credential logic (hash, verify, header parsing, secret/token generation) now delegates to simpleaudit.tracing.auth; studio keeps only the persistence layer. - audits/monitors.py: wilson/two_proportion_z delegate to simpleaudit.stats. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The OTLP ingestion endpoint and credential-management API were wired unconditionally, so a deployment that does not want the listener had no way to turn it off. Gate the /otlp/* and /api/otlp/* routes, the credential issue/rotate endpoints, and the per-connection OTLP button behind a new SIMPLEAUDIT_OTLP flag (on by default, preserving current behavior). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
When enabled, Studio starts Open WebUI with OpenTelemetry tracing on and pointed at its own OTLP listener (POST /otlp/v1/traces), so the chat module's spans land in the same place as any other target's. Off by default. Wired for both embedded (proxy) and docker modes, with docs and tests. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…aders - CLI now defaults DJANGO_DEBUG=false so local dev exercises real production behavior (host checks, static serving, no debug pages). Override with DJANGO_DEBUG=true to get the debug tooling back. - Add SECURE_SSL_REDIRECT, HSTS, secure cookies, and nosniff for real HTTPS deployments; gated off for local/minimal/demo mode. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Brings in the .github/agents/* role definitions (Researcher, Verifier, Test Reviewer, Regression Reviewer, Orchestrator) and the parallel-orchestrate prompt recovered from the archived vscode-custom-endpoint-optimization session branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 37641729 | Triggered | Generic Password | 581f420 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
1 similar comment
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 37641729 | Triggered | Generic Password | 581f420 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
| 37641729 | Triggered | Generic Password | 8338945 | infra/tests/test_user_project_assignment.py | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
The pyproject.toml points simpleaudit at ../SimpleAudit (editable) so the studio can use the tracing layer not yet in the published wheel. CI was failing at uv sync because that path did not exist. Clone the core repo into ../SimpleAudit before syncing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
actions/checkout cannot write outside the workspace. Use git clone to place the core repo at ../SimpleAudit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The delegation commit removed parse_basic_header/parse_bearer_header from otlp_services but the views and tests still called them. Import the core parsers (simpleaudit.tracing.auth) in the views and tests. Also fix the unused TraceCorrelation import in engine.py and sort imports. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
SECURE_SSL_REDIRECT was enabled in CI (DEBUG=false), causing 301 redirects that broke chat and audit tests. Add a _TESTING gate (ci-secret-key, PYTEST_CURRENT_TEST, or SIMPLEAUDIT_TESTING) so the headers stay off in the test suite. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
CsrfCookieMiddleware called the private _add_new_csrf_cookie(), which does not populate request.META["CSRF_COOKIE"]; CsrfViewMiddleware.process_response then raised KeyError: 'CSRF_COOKIE'. Switch to the public get_token() API, which sets the META key and flags the cookie for update. Keeps the existing "only set if missing" guard to avoid token/cookie drift. Absorbs the sentry[bot] fix from the seer/fix/csrf-keyerror branch. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The Dockerfiles run uv sync which fails because pyproject.toml points simpleaudit at ../SimpleAudit (editable) for the tracing layer. Clone the core repo into /SimpleAudit (which is ../SimpleAudit relative to /app) before uv sync. Add git to the base image. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
OtlpEnvTests called proxy._spawn which reads the real pid file (~/.simpleaudit-studio/openwebui/open-webui.pid) that does not exist in CI. Patch pid_file to point at the temp dir, matching the other test class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Adds the OTLP trace receiver (with none/basic/bearer auth), live tracing on multi-repetition runs, and the Open WebUI chat module. The OTLP credential + drift-stats logic now delegates to the core
simpleauditlibrary (merged in SimulaMet/SimpleAudit#91).What's in this branch
SpanStore, bearer-token verification indexed by lookup prefix.SIMPLEAUDIT_OTLPon/off switch andSIMPLEAUDIT_CHAT_OTLPto export Open WebUI spans to Studio.model_registry/otlp_services.pyandaudits/monitors.pynow usesimpleaudit.tracing.authandsimpleaudit.statsinstead of local copies.Notes
mainis an ancestor ofdev, so this merges cleanly.