Merged upstream — mine and Breken's
100+
63 are mine, 12 of them found with Breken, the self-healing software I built at Brekfuz. 42 more came from Breken's own account.
Stars
800k+
Combined stars across the 51 projects that merged them.
My pull requests
350+
Every one in someone else's codebase.
Projects
50+
Where I opened them: ML frameworks, inference, agent tooling and developer infrastructure.

My merged fixes

12 of my 63, picked for depth: auth boundaries, tenant scoping, races that silently dropped writes, wrong numbers at tensor edges. Every one merged by the project's maintainers.

  1. Burnfix(nn): honor count_include_pad for asymmetric average pooling (opens in a new tab)

    Fixed wrong averages at tensor edges in Burn's AvgPool1d/2d: with asymmetric padding and count_include_pad=false, padded zeros were being counted in the divisor; now a pooled validity mask divides by the real element count.

    #5592+223 −4Merged 9 Sep 2026

  2. Supersetfix(web): require exact public auth route matches (opens in a new tab)

    Closed an auth-boundary hole: the web proxy treated any path that merely started with a public route (e.g. /sign-internal) as public; it now allows only the exact route or its children.

    #5466+82 −17Merged 20 Jul 2026

  3. Supersetfix(trpc): honor organization header for JWT callers (opens in a new tab)

    Made multi-tenant API calls run under the organization the caller asked for, validated against the verified JWT's org claims, instead of silently defaulting to the first org.

    #5468+33 −6Merged 22 Jul 2026

  4. Reticlefix(desktop): write captures to a private directory, not shared /tmp (opens in a new tab)

    Moved desktop screenshots out of the world-readable temp directory into a per-process 0700 directory, fixing both the Node writer CodeQL flagged and the Rust writer it cannot scan. The maintainer: “the diagnosis is right and it’s the part most people get wrong.”

    #245+390 −37Merged 12 Aug 2026

  5. Reticlefix(server): bound event journals and report durable write loss (opens in a new tab)

    Put a 64 MiB ceiling on each session's event journal and made any dropped evidence visible to assertions, so a long session can neither fill the disk nor pass on incomplete data.

    #1024+1,346 −43Merged 22 Sep 2026

  6. Magic Contextfix: recover completed authority handoffs after restart (opens in a new tab)

    Made an interrupted module-to-TypeScript ownership handoff heal itself on restart, inside the existing privileged transaction, instead of leaving memory writes fenced and retrying forever.

    #504+523 −10Merged 22 Sep 2026

  7. mcp-usefix(server): handle empty OpenAPI JSON responses (opens in a new tab)

    Stopped OpenAPI-generated MCP tools from crashing with 'Unexpected end of JSON input' on successful empty responses such as 204 No Content.

    #1821+31 −1Merged 31 Jul 2026

  8. Hugging Facejinja: return undefined for out-of-range string indexes (opens in a new tab)

    Made Hugging Face's JS Jinja engine (used for chat templates) return a true undefined for out-of-range string indexes, so templates stop printing the literal word 'undefined' and the default filter works again.

    #2460+14 −2Merged 8 Sep 2026

  9. vLLM GuideLLMfix(openai): validate non-streaming tool calls before yielding (opens in a new tab)

    Made vLLM's GuideLLM benchmark stop before emitting a 'successful' response when a model skips a required tool call, matching the streaming path.

    #1099+70 −1Merged 9 Sep 2026

  10. pgbotfix: interrupt canceled SSH handshakes (opens in a new tab)

    Made a cancelled request actually abort a stuck SSH handshake in pgbot's Postgres tunnel, without tearing down the shared tunnel other requests depend on.

    #66+138 −0Merged 22 Sep 2026

  11. DataMindfix: serialize graph persistence and preserve dirty state for mutations occurring during a save (opens in a new tab)

    Fixed a race where a graph edit landing mid-save was marked as saved and silently never written to disk; saves are now serialized and snapshot-based.

    #13+287 −85Merged 17 Sep 2026

  12. agent-workspace-linuxfix(npm): isolate concurrent installer staging files (opens in a new tab)

    Made parallel npm installs safe: each attempt stages and checksum-verifies its binary in its own directory, so one install can no longer overwrite bytes another is verifying.

    #94+424 −18Merged 19 Sep 2026

Every project

My full ledger: every public project where I have a merged or open pull request, merged work first. Each row opens my pull requests there.

Write-ups

55 defects I found across 10 codebases: what broke, why it mattered, and the fix.

  • Superset8 findings · 3 mergedAuth and tenant guards that ran, and compared the wrong value.

    superset-sh/superset (opens in a new tab)

    Superset is a macOS desktop app for running many CLI coding agents at once, each in its own git worktree, with a shared diff viewer, terminal and remote-host access.

    Guards that existed, ran, and compared the wrong value. A route allowlist matched a string prefix instead of a path boundary, a credential cache keyed on elapsed time instead of the credential it came from, an organization check read the first membership instead of the requested one, and a private image proxy declared its responses public. Most of them sit on an auth or tenant boundary, where the wrong comparison is the whole failure.

    1. The web auth proxy decided whether a request could skip the session redirect with pathname.startsWith(route) over a list of public auth paths. Any route sharing a prefix with a public one was therefore public too: /sign-internal, /sign-upgrade, /auth/desktopish and /accept-invitation-list all passed the check and bypassed authentication.

      The fix replaces the auth proxy's prefix test with exact-or-child path matching, extracted into a tested module.

      superset-sh/superset#5466 (opens in a new tab)Merged

    2. The tRPC middleware for bearer-JWT callers set the active organization to organizationIds[0] and ignored the x-superset-organization-id header entirely. Host-service and MCP clients send that header, so a user in more than one organization had every JWT-backed call silently executed against whichever organization happened to be first in the token claim, not the one requested.

      The fix validates the requested organization against the verified JWT membership claim and uses it as the active organization.

      superset-sh/superset#5468 (opens in a new tab)Merged

    3. The host service cached the JWT it exchanged for an API key or session token by expiry time alone. When the underlying credential rotated — a login refresh, a key rotation, a revocation — the cache had no way to notice, so the host kept authenticating to the cloud with a JWT minted from the replaced credential until the 55-minute window ran out.

      The fix binds the cached exchanged JWT to the source token it was minted from, so rotation invalidates it.

      superset-sh/superset#5473 (opens in a new tab)

    4. The authenticated Linear image proxy fetched organization-scoped uploads with that organization's Linear token and returned them under Cache-Control: public, max-age=31536000, immutable. Private per-organization images were therefore marked as publicly cacheable for a year by any shared cache or CDN in front of the API. The same route followed upstream redirects while carrying the Linear bearer token, and read the whole image into memory before responding.

      The fix switches the proxy to no-store, blocks redirects and non-raster content types, and streams the body instead of buffering it.

      superset-sh/superset#5467 (opens in a new tab)

    5. A root overrides.axios pin held every transitive consumer — the Slack and Tavily clients used by the API — at axios 1.14.0. The override outranked the ranges those packages declare, so the pin actively prevented resolution to a patched release while advisories against 1.14.0 included a Proxy-Authorization leak across an HTTP-to-HTTPS redirect and a NO_PROXY bypass reaching cloud metadata.

      The fix moves the repository-wide axios override to a patched release and regenerates the lockfile.

      superset-sh/superset#5874 (opens in a new tab)Merged

    6. The preview deployment workflow triggered on pull_request, which fires for forks, and its jobs create Neon branches and Vercel deployments using repository secrets. Fork runs do not receive those secrets, so every external contributor's pull request ran deployment jobs that could not complete, and the workflow's shape put secret-backed infrastructure steps on the untrusted-code side of the boundary.

      The fix adds a same-repository guard to every job in the preview deployment workflow.

      superset-sh/superset#5476 (opens in a new tab)

    7. The production deployment workflow runs database migrations and then deploys several Vercel surfaces, and had no concurrency group. A push to main and a manual dispatch could run it twice at once, interleaving migrations and racing deployment aliases across a partially rolled-out set of surfaces.

      The fix adds a concurrency group to the production deployment workflow with cancel-in-progress off.

      superset-sh/superset#5477 (opens in a new tab)

    8. The SDK's version.ts constant, which feeds the generated package metadata and the user-agent header, read 0.0.1-alpha.7 while the package itself had reached 0.0.1-alpha.12 — every request from the SDK identified itself as a five-release-old build. The package also shipped a client.tasks.statuses resource whose TaskStatus and TaskStatusListResponse types were not exported from the root, so consumers could call it but not type the result.

      The fix syncs the SDK version constant to the package version and exports the task-status types from the package root.

      superset-sh/superset#5471 (opens in a new tab)

  • Reticle3 findings · 2 mergedGuards right on one axis and empty on the axis beside it.

    reticlehq/reticle (opens in a new tab)

    Reticle is an open-source tool that lets an AI coding agent drive a running web or desktop application and read what actually happened inside it — network calls, store, console, routes — instead of inferring from the diff.

    Each of these is a guard that was right on the axis it was written for and empty on the one beside it: a stack filter that kept nothing when the stack was all runtime internals, temp-file hardening aimed at the filename while the shared parent directory was the exposure, and a set of dependency checks none of which could read a Cargo manifest. In every case the uncovered side failed silently rather than erroring, which is why nothing had reported it.

    1. Desktop screenshots were written straight into the shared OS temp directory under a name anyone could work out: a public constant prefix, a readable pid, and a counter starting at 0. The hardening that existed treated the filename as the thing to make safe; the shared parent was the actual exposure, and no filename fixes that. CodeQL flagged the JavaScript writer and does not scan Rust, so the Rust writer that ships in the published crate carried the same bug unflagged. The fix was also coupled to something the issue never mentioned: the daemon gated reads on dirname(path) === tmpdir(), an exact match, so putting the capture one directory deeper would have made every desktop screenshot return no image and say nothing about why.

      The fix gives each process its own 0700 capture directory and makes both the Node and Rust writers refuse an existing path, while widening the daemon's read gate to accept the new layout and the old one.

      reticlehq/reticle#245 (opens in a new tab)Merged

    2. Crash telemetry kept only stack frames inside Reticle packages, on the correct principle that the rest of a crash stack belongs to the user's application. A refused socket has a stack that is entirely Node internals, so the filter kept nothing and the commonest failure in a loopback-only tool arrived as frames: [] with no location at all. The structured cause — syscall, errno, address, port — was already on the error object; only the stack was ever read. Gating mattered as much as the fix: any error raised anywhere in Node carries node:internal frames, so an ungated rule would have attached a frame to every crash, including the ones that already had a location.

      The fix reads the syscall, errno and internal frame off the Node system error itself, only when no Reticle frame survived the filter, and asserts the address and port number never reach the payload.

      reticlehq/reticle#247 (opens in a new tab)Merged

    3. reticle-tauri is published to crates.io, so its dependency tree reaches users directly, and nothing in the repo watched it. Dependabot was configured for npm and GitHub Actions and cannot read a Cargo manifest. CodeQL does not scan Rust. The existing Rust CI jobs asked whether the crate compiles and lints, never whether a dependency was vulnerable. Three mechanisms, one uncovered language, and it was the one shipping a published artifact. Running the audit at --deny warnings was measured and rejected: it fails today on 17 advisories, all transitive through tauri, gtk and glib and none fixable in this repo, and a gate that is red on the day it merges gets muted. A job absent from the aggregate gate's needs list reports and cannot block a merge, so that line was part of the change.

      The fix adds a cargo ecosystem for both crates and a pinned cargo audit job, run at default severity and wired into the aggregate gate's needs list.

      reticlehq/reticle#246 (opens in a new tab)

  • mcp-use1 finding · 1 mergedA successful empty response handled as a malformed one.

    mcp-use/mcp-use (opens in a new tab)

    mcp-use is a TypeScript and Python framework for building and running Model Context Protocol servers and clients, with a CLI and an inspector, used to ship MCP apps for ChatGPT and Claude.

    A success status and a declared JSON content type were read as a promise of a JSON body. A 204 No Content keeps the first two and sends no bytes, so a call that worked reached the agent as a crash.

    1. OpenAPI-generated tools called response.json() on any successful response that declared a JSON content type. A 204 No Content, which declares the type and sends no bytes, threw SyntaxError: Unexpected end of JSON input — a successful upstream call surfaced to the agent as a crash.

      The fix reads the body as text first, parses it when non-empty, and returns an empty structured object when it is not.

      mcp-use/mcp-use#1821 (opens in a new tab)Merged

  • Sourcebot1 finding · 1 mergedValues normalised on one side of a comparison and left raw on the other.

    sourcebot-dev/sourcebot (opens in a new tab)

    Sourcebot is a self-hosted code search and code-intelligence server that indexes repositories from GitHub, GitLab, Gitea and plain Git remotes so that people and coding agents can search across them.

    One-sided handling at a boundary: a value normalised on one side of a comparison and left raw on the other, with a test that pinned the asymmetry as correct.

    1. GitLab topic filters lowercased the topics written in the connection config but compared them against the topics GitLab returned, untouched. A project tagged Backend never matched an include rule of backend, so it was dropped from the index; the same asymmetry let a project slip past an exclude rule. The repository's own test suite pinned the behaviour as correct, with a comment explaining that the function lowercases config topics but not project topics.

      The fix normalises the project side of the comparison too, and rewrites the test that asserted the old behaviour.

      sourcebot-dev/sourcebot#1393 (opens in a new tab)Merged

  • Future AGI6 findings · 1 mergedFailures caught, swallowed and replaced with plausible wrong data.

    future-agi/future-agi (opens in a new tab)

    An open-source, self-hostable platform for tracing, evaluating and guarding LLM and AI-agent applications, with a Django backend and a React frontend.

    Every one of these is a silent fallback: code catches a failure, or never receives a piece of context it needed, and then carries on with plausible-looking wrong data instead of stopping. Nothing raises, nothing reaches the user, and the wrong result is indistinguishable from the right one.

    1. populate_placeholders() wrapped substitution in a bare except Exception and returned the original messages on failure, so a prompt with unresolved {{column}} tokens was dispatched to the model provider verbatim and billed for. Two further paths hid the same failure: the per-column loop logged and continued, leaving the placeholder in place, and rendered output containing unreplaced UUID placeholders was stripped with re.sub, deleting the evidence. Jinja and Mustache separately render an unknown name as an empty string, so a missing value could vanish without leaving any token to detect.

      The fix makes run-prompt rendering fail closed and name the placeholders that did not resolve.

      future-agi/future-agi#2083 (opens in a new tab)

    2. HuggingFace dataset import had no mapping for the Pdf/Document feature types, so PDF columns were inferred as ordinary data and the decoded pdfplumber objects were stored as their string representation. An imported PDF cell held an object repr instead of a document URL, and the file itself was never uploaded.

      The fix maps HuggingFace PDF/document features to the document column type and routes those values through the existing S3 upload path.

      future-agi/future-agi#1294 (opens in a new tab)Merged

    3. The Add-to-dataset drawer cached observation fields under the global key ["observationFields"], with no project in it. Moving between Observe projects in the same tab returned the first project's cached column list while it was still fresh, so spans could be added to a dataset under another project's field mapping.

      The fix scopes the observation-fields React Query cache key to the active route project.

      future-agi/future-agi#1298 (opens in a new tab)

    4. The endpoint that supplies next/previous row IDs when the datapoint drawer crosses a loaded-row boundary computed them over the unfiltered table. The drawer's row universe and the table's visible row universe diverged at that boundary, so paging forward moved the user onto rows the active filter, search and sort had excluded.

      The fix makes /get-row-data/ compute boundary row IDs under the active filter, search and sort.

      future-agi/future-agi#1912 (opens in a new tab)

    5. Importing a saved prompt into an Agent Builder node discarded the prompt's configuration and substituted builder defaults: output format was accepted as string only, response format and response schema were collapsed into a single value, and the node's response output-port data_schema was hardcoded to { type: "string" } regardless of what the prompt actually returned. A JSON-returning prompt became a string-typed port with no error.

      The fix preserves an imported prompt's own configuration when it is loaded into an llm_prompt node.

      future-agi/future-agi#1916 (opens in a new tab)

    6. yarn test:unit and yarn test:integration selected tests with Vitest's --testNamePattern, matching on describe/it title text rather than path. Both lanes collected the entire configured test tree and then skipped whatever did not match by name, so the suite boundaries described in the repo's own testing guide did not correspond to what either command ran.

      The fix points the frontend unit and integration scripts at the documented suite directories.

      future-agi/future-agi#1237 (opens in a new tab)

  • TraceRoot8 findings · 1 mergedMissing or malformed state read as a valid answer instead of a stop.

    traceroot-ai/traceroot (opens in a new tab)

    TraceRoot is an open-source observability platform for AI agent systems: it collects OpenTelemetry traces from production, runs LLM detectors over them to flag hallucinations and tool failures, and root-causes the failures against the service's source code and GitHub history.

    Every one of these is a place where absent or malformed state was read as a valid answer instead of a stop. An empty model field became a pinned model. An unvalidated trigger condition became a stored filter. A failed queue write became 200 OK. A missing write role became permission to spend money on someone else's project.

    1. The New Detector form auto-selected a model from the selector's compiled-in fallback list before the workspace's live /llm-models response came back, so a self-hosted workspace with no system keys and no BYOK provider still displayed a Claude model as the detector's model and let it be saved. Neither the create nor the update route checked the model/provider/source tuple against the workspace, so a stale tab or a direct API client could persist a model the workspace held no credentials for. Underneath that, worker evaluation of legacy rows with a null source fell through to a scan of any enabled provider on the same adapter, so a detector could execute against a different credential, base URL and billing account than the one recorded on it.

      The fix is live-catalog-only model selection in detector create and edit, server-side validation of the model tuple against the workspace, and worker resolution that fails closed on ambiguous or unavailable legacy rows.

      traceroot-ai/traceroot#1409 (opens in a new tab)

    2. Detector create, update and delete required only project membership, never a write role. A VIEWER could create a detector, rewrite its prompt, or delete it — starting paid LLM and root-cause-analysis runs against the project, or switching detection off entirely. The UI presented the mutation controls to viewers as well, so nothing signalled the boundary either.

      The fix is MEMBER on detector create and update, ADMIN on delete, matching the policy already used for API-key writes, with UI affordances gated to the same roles.

      traceroot-ai/traceroot#1391 (opens in a new tab)

    3. The public trace ingest endpoint wrote the OTLP payload to S3 and then queued a Celery task to process it. When the queue call failed it logged the error and returned 200 OK, with a comment in the code saying S3 has the data and it can be retried later. Nothing retried it. The OTLP client read the 200 as acceptance and discarded the batch, and the S3 object stayed there unprocessed with no path back into the pipeline.

      The fix is a 503 with Retry-After when the enqueue fails, plus deletion of the just-uploaded object so it does not sit orphaned.

      traceroot-ai/traceroot#1389 (opens in a new tab)

    4. Detector POST and PATCH checked only that triggerConditions was an array. Entries were stored without validating the field name, the operator, or the value type, so unsupported conditions were persisted and only failed later, inside the worker's trigger evaluation. The worker did not fail closed on them: a malformed stored container, including a JSON null, was not treated as a non-match. Request and condition fields were also read without an own-property check, so names off the prototype chain such as __proto__ and constructor were accepted.

      The fix is write-time normalization and validation of trigger conditions, own-property reads on request bodies, worker evaluation that treats malformed legacy conditions as non-matches, and a UI path that quarantines unrepresentable stored rows instead of silently rewriting them.

      traceroot-ai/traceroot#1392 (opens in a new tab)

    5. The info icon next to Project API Keys rendered as an interactive control but had no handler, so a credential setup screen looked broken. The same panel had two credential problems behind it: the masked .env hint on an existing key carried a copy button, so the copied value was a masked string and not a usable secret, and the one-time full secret of a newly created key lived in component state that was scoped to neither the project nor the request — a create that resolved after the user switched projects, or a return to the originating project, could surface and copy that secret in the wrong project's dialog.

      The fix is a real tooltip on the icon, removal of the copy affordance from masked hints, and project- and request-scoped state for the one-time secret and for in-flight create, update and delete callbacks.

      traceroot-ai/traceroot#1406 (opens in a new tab)

    6. nanos_to_datetime called int() on the raw OTEL timestamp and datetime.fromtimestamp() on the result with no guard. One span carrying a non-numeric startTimeUnixNano raised inside the transform and aborted the entire S3 ingest batch, so every other span in that object was lost rather than just the malformed one.

      The fix is treating unparseable, wrongly-typed and out-of-range timestamps as missing, so the existing required-start-time check drops only the bad span and a malformed end time leaves the span open.

      traceroot-ai/traceroot#1377 (opens in a new tab)

    7. A detector that follows the system default is stored with empty model and provider fields. The shared model selector auto-selects a concrete default whenever the value it is given is empty, so opening such a detector in the edit panel filled those fields in, and saving an edit to the name or the prompt pinned the detector to that model. The runtime model changed as a side effect of an unrelated edit, with no indication in the form that it had.

      The fix is an opt-out of auto-selection for detector editing, an explicit label for the inherited default, and patching of model, provider and source as one tuple only when the user actually changes the selector.

      traceroot-ai/traceroot#1428 (opens in a new tab)

    8. The detectors list showed the literal word Default in the Model column for detectors tracking the system default, so the page never said which model would run. The worker resolved that case through its own availability-aware fallback rather than a shared value, so the label on the page and the model the worker selected were not tied to each other.

      The fix is the resolved model id in the list column and a shared DETECTOR_SYSTEM_DEFAULT_MODEL_ID that worker evaluation resolves through, with a test pinning that default into the system model catalog.

      traceroot-ai/traceroot#1427 (opens in a new tab)Merged

  • Mem06 findingsConstraints handed along and never enforced at the boundary that mattered.

    mem0ai/mem0 (opens in a new tab)

    mem0 is an open-source memory layer for AI agents, storing and retrieving per-user and per-session context across a set of pluggable vector stores, with Python and TypeScript SDKs and a self-hostable server.

    Every one of these is a constraint that was passed along but never enforced at the boundary that mattered: a scope handed to the vector store and then trusted on the way back, an empty filter object that meant "no filter" instead of "match nothing", an audit query whose allowlist quietly dropped most of the traffic it was meant to show. The bugs are not in the logic that runs; they are in the case where the check is absent, empty, or someone else's job.

    1. The TypeScript SDK passed user_id, agent_id and run_id down to the vector store as filters and then trusted whatever came back. Nothing re-checked the scope of returned rows, so a loose or buggy provider filter let out-of-scope memories reach search() results, keyword scoring, add()-time dedupe context, and deleteAll()'s delete set. deleteAll() also accepted a wildcard scope such as {userId: "*"} and deleted on a single unbounded provider page, so a partial page could delete a partial set and report success.

      The fix re-checks requested user/agent/run scope at the Memory layer after every vector-store read, and rejects wildcard or non-equality scope filters on reads and on deleteAll().

      mem0ai/mem0#6786 (opens in a new tab)

    2. Entity linking applied the user/agent/run scope to the entity search but not to the merge decision: any candidate scoring at or above 0.95 was merged, so linkedMemoryIds from different users accumulated in one entity record and skewed entity boosts across scopes. The semantic path also asked the store for exactly one candidate, so adding a scope check alone would have silently stopped linking whenever the nearest neighbour belonged to another scope.

      The fix re-checks the stored scope on an entity row before exact-text reuse, semantic reuse, or search-time entity boosting, and widens the semantic lookup so an in-scope match can still be found.

      mem0ai/mem0#6018 (opens in a new tab)

    3. The Azure AI Search vector store built an OData filter string whenever the filters argument was truthy. An empty object is truthy in JavaScript, so {} produced an empty filter expression that was sent to Azure rather than omitted — search(), keywordSearch() and list() issued a filtered query with no predicate.

      The fix returns undefined instead of an empty OData string when the filter object is empty, and rejects malformed containers, unsupported keys, and non-string values before the expression is built.

      mem0ai/mem0#6785 (opens in a new tab)

    4. The self-hosted server writes request logs tagged bearer, api_key, admin_api_key, disabled and none, but the admin /requests endpoint selected only the two api_key variants. Dashboard and SDK traffic authenticated with JWT bearer tokens, and all traffic on an AUTH_DISABLED deployment, was recorded to the database and then never shown in the audit view.

      The fix widens the admin /requests query to the auth modes the server actually persists, still excluding unauthenticated entries.

      mem0ai/mem0#6062 (opens in a new tab)

    5. The Node CLI's agent-mode JSON envelope for delete-all passed the backend response straight through as data, unlike every other destructive command, which returns a fixed command-level shape. The envelope also carried no scope, so a machine consumer reading the output could not tell whether one user's memories or the whole project had been deleted, nor whether deletion had merely been started in the background.

      The fix emits a stable delete-all result and the requested scope in the agent envelope, preserving the backend's asynchronous-deletion signal.

      mem0ai/mem0#6784 (opens in a new tab)

    6. The REST documentation described one API surface while two exist: MemoryClient calls Platform routes, and the self-hosted server serves a different set. Users following the docs pointed Platform paths and Platform API keys at their own OSS server, which is what the reported issue was asking about.

      The fix states that the hosted client targets Platform memory routes while the OSS server exposes its own FastAPI endpoints, and points self-hosted users at the OSS entry points and the server's /docs.

      mem0ai/mem0#6051 (opens in a new tab)

  • FastAPI Template1 findingAn anti-enumeration control that held only on the success path.

    fastapi/full-stack-fastapi-template (opens in a new tab)

    The official FastAPI project template: a batteries-included starter pairing a FastAPI/SQLModel/PostgreSQL backend with a React frontend, including auth, password recovery and Docker Compose deployment.

    A security control that only holds on the success path. The password-recovery endpoint was written to give the same answer for every email so an attacker cannot learn which addresses are registered, but the check sat before code that can throw — and the throw itself became the answer.

    1. POST /password-recovery/{email} looked the user up first and only sent mail inside the if user: branch, so anything that could throw while sending mail could only throw for a registered address. send_email() opens with assert settings.emails_enabled, and SMTP_HOST defaults to None — so on a stock deployment of the template a registered email returned 500 and an unregistered one returned 200 with the generic "if that email is registered" message. The endpoint whose own comment reads "Always return the same response to prevent email enumeration attacks" was an email enumeration oracle by default, and stayed one whenever the SMTP host was unreachable or the reset-password template failed to render.

      The fix returns the generic recovery response before any user lookup when email delivery is not configured, and on any failure while generating or sending the reset mail.

      fastapi/full-stack-fastapi-template#2370 (opens in a new tab)

  • DeepEval10 findingsDocumented contracts the code behind them did not keep.

    confident-ai/deepeval (opens in a new tab)

    DeepEval is an open-source framework for evaluating LLM applications, scoring model output against metrics like faithfulness, hallucination and tool-call correctness from inside a pytest-style test suite.

    Every one of these is a place where what DeepEval declares and what DeepEval does had come apart: a flag that says it disables writes and still writes, a model that advertises image support its request builder never implemented, a cache key that omits the arguments that decide the score, an export map missing the imports the README tells you to use. The bugs live in the seam between the documented contract and the code behind it.

    1. Async arena comparison was only scheduled inside the Rich progress-bar branch of compare(), so setting DisplayConfig(show_indicator=False) skipped every test case and returned an empty result dict. Turning off the progress display silently turned off the evaluation, which is exactly what CI, notebooks and log-sensitive servers do.

      The fix moves async arena task scheduling into a helper both the visible and quiet paths call, and cancels in-flight tasks after the first failure.

      confident-ai/deepeval#2846 (opens in a new tab)

    2. The test-run cache compared a fixed list of metric fields before reusing a cached score, and constructor arguments were not on it. Changing GEval.criteria or PatternMatchMetric.pattern — the inputs that decide the score — produced a cache hit on the old result.

      The fix adds constructor and free-form config metadata to the cache key, and misses closed for values that cannot be represented safely rather than reusing them.

      confident-ai/deepeval#2840 (opens in a new tab)

    3. CacheConfig(write_cache=False) and DEEPEVAL_FILE_SYSTEM=READ_ONLY still wrote hidden local files during cache finalization, latest-run persistence and the deepeval inspect rolling fallback. The escape hatch for read-only environments like Lambda and Azure Functions did not actually stop the writes it exists to stop.

      The fix scopes the write-cache policy through the evaluation and iterator paths, restores it on every exit including exceptions, and tags hidden snapshots with an owner token so invalidation cannot delete files it did not write.

      confident-ai/deepeval#2845 (opens in a new tab)

    4. CLI dotenv persistence assembled KEY=value lines by hand. Values containing embedded double quotes, newlines or backslash escapes — service-account JSON, some key material — were written as invalid dotenv syntax and parsed back by python-dotenv as None, truncated, or with characters changed. Saved credentials came back wrong on the next run.

      The fix rewrites CLI dotenv writes around python-dotenv parsing with parser-compatible escaping, keeps the write atomic and 0600, rejects symlinked save targets, and rolls back in-memory state when a save fails.

      confident-ai/deepeval#2847 (opens in a new tab)

    5. deepeval view read the cached last-test-run link out of the local .deepeval state file and handed it straight to the browser with no validation, so anything that could write that file could choose the URL a user's browser opened.

      The fix gates the cached link behind an origin and path check against the configured Confident AI deployment, rejecting userinfo, look-alike hosts, base-path escapes and encoded dot-segments, and falling back to a fresh upload.

      confident-ai/deepeval#2834 (opens in a new tab)

    6. The TypeScript AmazonBedrockModel returned true from supportsMultimodal() while its Converse request builder only ever emitted text content blocks. Callers use that flag to decide whether an image prompt is safe to route to a provider, so image slugs went to Bedrock as literal text. Separately, partial AWS credentials were mixed across explicit and environment sources instead of being rejected.

      The fix reports the wrapper's real capability until image content blocks exist, and resolves Bedrock credentials as one bundle that fails closed on partial config.

      confident-ai/deepeval#2841 (opens in a new tab)

    7. The pytest entry point pointed at deepeval.plugins.plugin, so pytest auto-load imported the whole deepeval package before any runtime guard could run. Installing DeepEval meant unrelated pytest suites loaded dotenv files, created local state, inherited a stale CONFIDENT_AI_RUN_TEST_NAME marker and printed DeepEval output. The import-time error-reporting firewall probe also ran before the telemetry opt-out was checked.

      The fix moves the entry point to a small import-safe package that imports DeepEval internals only once the DEEPEVAL runtime gate is set, and checks the opt-out before the probe.

      confident-ai/deepeval#2836 (opens in a new tab)

    8. The TypeScript package's README examples import deepeval/metrics, deepeval/models, deepeval/test-case and deepeval/evaluate, and none of those subpaths were in the package export map. Every documented import failed against the published package. deepeval/annotation was worse: it shipped type metadata through typesVersions for a subpath with no runtime export, so it type-checked and then failed at run time.

      The fix adds the missing exports and declaration paths, keeps the ./testCase alias, and adds a build-backed smoke test that resolves each subpath from a packed install.

      confident-ai/deepeval#2818 (opens in a new tab)

    9. The Typer floor was >=0.9 while Click was allowed up to <8.4. Those bounds let a fresh install resolve a Typer/Click pair that raises while rendering CLI help, so deepeval --help — the first thing a new user runs — crashed before listing any command.

      The fix raises the Typer floor to the first release that covers the allowed Click range, and adds help-rendering regression tests for the root command and representative subcommands.

      confident-ai/deepeval#2848 (opens in a new tab)

    10. ArgumentCorrectnessMetric judges tool call arguments, but its generate_verdicts prompt told the judge model to return one verdict per statement. The instruction named a unit the metric does not evaluate, so the verdict count the model was asked for did not match the tool calls it was given.

      The fix corrects the instruction to tool calls, regenerates the Python and TypeScript template bundles, and adds a compiled-template regression test that needs no model call.

      confident-ai/deepeval#2820 (opens in a new tab)

  • Morphik Core11 findingsDeclared contracts that a code path quietly broke.

    morphik-org/morphik-core (opens in a new tab)

    Morphik Core is the open-source multimodal retrieval engine behind Morphik, used to ingest, search, and extract structure from visually rich documents such as PDFs, charts, and diagrams.

    Every finding is the same shape: the repository declares something — a supported Python floor, a Redis DSN, a byte budget, an API route, an environment variable, a license, a build command — and some code path does not honor it. He read the declaration and the implementation side by side and reported where they disagreed.

    1. docker-compose.yml defaulted the auth signing key to a literal string with JWT_SECRET_KEY=${JWT_SECRET_KEY:-your-secret-key-here}, so a self-hosted deployment with auth enabled started and signed tokens with a published development secret instead of failing. The installers never generated SESSION_SECRET_KEY at all, while core/api.py passes it straight to Starlette's SessionMiddleware as the cookie signing key.

      The fix requires non-placeholder JWT and session signing secrets at startup when auth bypass is off, and generates a session secret in the Docker installers.

      morphik-org/morphik-core#425 (opens in a new tab)

    2. The ARQ ingestion worker built its Redis connection from REDIS_URL, but the API lifespan and the startup readiness path did not. A managed Redis DSN carrying credentials, a database index, a non-default port, a Unix socket, or TLS was honored by the worker and ignored by the API, so the two halves of the ingestion queue could connect to different Redis targets. An empty REDIS_URL silently fell back to localhost.

      The fix is a shared Redis settings helper built from the DSN, used by API startup, the ARQ worker, and the CLI readiness check.

      morphik-org/morphik-core#429 (opens in a new tab)

    3. The web UI logged full Morphik connection URIs, the parsed connection object, the auth token, token prefixes, and the connection-derived API base URL to the browser console. Morphik connection URIs carry bearer credentials, so a user pasting console output into a bug report was pasting a working token.

      The fix removes credential-bearing values from the UI's console output, keeping only event-level signals.

      morphik-org/morphik-core#418 (opens in a new tab)

    4. The Python SDK declares requires-python = ">=3.8" but used str | PILImage and tuple[str, Document] in annotations evaluated at import. On Python 3.9 import morphik raised TypeError: unsupported operand type(s) for |: 'type' and 'type' before a client could be constructed, so the package was uninstallable-in-practice on interpreters its own metadata advertised.

      The fix replaces PEP 604 and PEP 585 annotation syntax in the SDK with typing equivalents.

      morphik-org/morphik-core#427 (opens in a new tab)

    5. TELEMETRY_MAX_LOCAL_BYTES was only applied after a telemetry upload succeeded. When the telemetry proxy was down or rejecting payloads — precisely when local files accumulate fastest — the configured disk budget was never enforced, so local telemetry grew without bound on self-hosted deployments.

      The fix enforces the telemetry byte budget after failed uploads too, scoped to logs/telemetry/ so an outage does not prune unrelated logs.

      morphik-org/morphik-core#426 (opens in a new tab)

    6. core/services/ingestion_service.py resolved configuration at module scope with settings = get_settings(). Because the default config selects the Postgres provider, merely importing the module during pytest collection raised ValueError: 'POSTGRES_URI' needed if 'database.provider' is set to 'postgres', so unit tests that never touch a database could not be collected without a database URI.

      The fix defers settings resolution to IngestionService construction, keeping a lazy module-level proxy for the existing attribute seam.

      morphik-org/morphik-core#413 (opens in a new tab)

    7. install_docker.sh told operators who set LOCAL_URI_PASSWORD to call /generate_local_uri. No such route exists; core/api.py registers POST /local/generate_uri. The first-run path for a self-hosted deployment sent users to a 404 at the moment they needed an authorized connection URI.

      The fix points the installer guidance at the route the API actually serves, and names the password_token form field.

      morphik-org/morphik-core#417 (opens in a new tab)

    8. The Python SDK docs told contributors to point live tests at a custom server with MORPHIK_TEST_URL. The sync and async test modules read MORPHIK_TEST_URI. Setting the documented variable had no effect and the tests quietly ran against the default http://localhost:8000 instead.

      The fix corrects the SDK test docs to the variable the tests read, and the example script's async flag.

      morphik-org/morphik-core#414 (opens in a new tab)

    9. pytest.ini set testpaths = tests, a directory that does not exist in the repository, so every root-level run emitted PytestConfigWarning: No files were found in testpaths and fell back to recursive discovery. The pythonpath entry pointed at ../sdks/python, outside the repo, so root collection imported whichever morphik SDK happened to be installed rather than the one in the tree.

      The fix points testpaths at core/tests and sdks/python/morphik/tests, and fixes the SDK pythonpath entry.

      morphik-org/morphik-core#420 (opens in a new tab)

    10. DOCKER.md stated "License: MIT" and the Dockerfile carried org.opencontainers.image.licenses="MIT". The repository's own LICENSE is Business Source License 1.1 and ee/LICENSE is a proprietary enterprise license, both of which ship inside that image, so the machine-readable label fed the wrong license into any registry view or compliance scan.

      The fix removes the MIT claim from the Docker guide and the OCI image label, directing readers to the repository license files.

      morphik-org/morphik-core#415 (opens in a new tab)

    11. CLAUDE.md instructed anyone working on the UI to run npm run build:package, a script ee/ui-component/package.json does not define. The UI README documented installing and importing @morphik/ui with a prop table including an onUriChange prop, but the checked-in package is private, named morphik-ui, and is a Next.js app rather than a published component library.

      The fix replaces the missing command with the scripts the UI package defines and drops the installable-package framing.

      morphik-org/morphik-core#416 (opens in a new tab)