Future AGI

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.

  • 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 change makes run-prompt rendering fail closed and name the placeholders that did not resolve.

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

  • 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 change 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)

  • 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 change scopes the observation-fields React Query cache key to the active route project.

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

  • 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 change 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)

  • 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 change 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)

  • 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 change points the frontend unit and integration scripts at the documented suite directories.

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

mem0

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 upload read with no size cap, 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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • OpenMemory's backup import read the uploaded zip, its memories.json, and an optional memories.jsonl.gz fully into memory with no cap on upload size, member count, record count, or decompressed stream length. A small crafted archive could expand without limit before a single row was written.

    The change bounds the upload, zip member count, central directory, manifest record count and decompressed gzip stream before any import write, returning 413 or 400 instead of reading on.

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

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

Superset

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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change moves the repository-wide axios override to a patched release and regenerates the lockfile.

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

  • 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 change adds a same-repository guard to every job in the preview deployment workflow.

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

  • 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 change adds a concurrency group to the production deployment workflow with cancel-in-progress off.

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

  • 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 change 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)

Morphik Core

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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change replaces PEP 604 and PEP 585 annotation syntax in the SDK with typing equivalents.

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

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

DeepEval

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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

TraceRoot

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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

Sourcebot

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.

Most of these are one-sided handling at a boundary: a value gets normalised, decoded or keyed on one side of a comparison and left alone on the other — config topics lowercased but not the project's, paths decoded on one connection route but not its twin, recents keyed by repository but read back by revision. The rest are expected conditions arriving at the interface as generic failures: an empty repository, a deleted file.

  • 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 change 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)

  • The setup wizard wrote local repository mounts into docker-compose using the short source:target:mode form, with the host path pasted in raw. A colon in the path splits the field, so a directory like /Users/me/repos/repo:two#main produces a mount pointing somewhere else; a space or a # misparses the same way. The failure lands at docker compose up, on the first thing a new user runs.

    The change emits the long-form bind syntax with quoted source paths from a helper covered by a fixture test.

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

  • Browsing a repository with no commits showed a tree-loading error. The tree endpoints run git ls-tree HEAD, and in a repository with no commit object that call is expected to fail, but every ls-tree failure was treated as unexpected. The obvious repair — treat empty output as an empty repository — is also wrong: ls-tree returns no rows for a pathspec that does not exist in a repository full of commits.

    The change confirms emptiness with git rev-list --count --all after a failure, and only at the default ref, so unresolved custom refs still error.

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

  • The browse file-search recents list was keyed in localStorage by repository name alone, but selecting an entry navigated using the currently selected revision. Switch branches and the recents panel offered file paths from the branch you left, which then resolved against the branch you are on.

    The change keys recents by repository and revision, escapes the key segments, and migrates existing entries into the default-revision context only.

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

  • Two sibling code paths derived repository names differently. A generic Git connection given a file-based origin decoded percent-escapes in the path; the same connection given an HTTP(S) URL did not. The same repository indexed through the two routes got two different names — Project%20Name versus Project Name — in the display name and in the zoekt index metadata.

    The change decodes the URL pathname before deriving name, display name and index metadata, keeping malformed escape sequences as-is rather than throwing.

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

  • The search filter panel rendered only the rows returned by its fuzzy match, so an already-selected filter disappeared as soon as the panel's search text stopped matching its name. Select PowerShell, type C to look for another language, and PowerShell vanishes from the list while still constraining the results — the visible filter state and the running query disagree, with no way to unset what you cannot see.

    The change merges selected entries back into the filtered set before the existing selected-first sort, and gives the rows button semantics with aria-pressed.

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

  • The example query links on the search landing page were built by concatenating the raw query text into the href. A &, # or + in an example truncates or corrupts the query parameter, so the link lands on a search that is not the one it displays.

    The change builds the hrefs with URLSearchParams, with a test covering reserved characters and the case-sensitivity flag.

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

  • Multi-branch indexing truncates to 64 revisions, and the order before the cut decides which refs survive. That order was hardcoded — branches by committer date, tags by creator date — with no way to change it. The documented limit was also wrong about the shape of the cap: it read as "64 branches and tags", while the runtime keeps a single 64-revision budget that includes the default branch and fills it with branches before tags, so tags can be dropped entirely without a word in the logs.

    The change adds branchSort and tagSort to the connection schema, threads them into ref discovery, and rewrites the docs to describe the actual global cap and ordering.

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

  • Browsing to a file that does not exist printed the raw service error, Error loading file source: ..., because every getFileSource failure took one path and FILE_NOT_FOUND had no case of its own. Reached after a branch switch or from an old link, which is when it happens, the page gave no way back. Preview mode compounded it by conflating two refs — the one the content was read from and the one the browse session is on — so a recovery link would have returned the user to the wrong revision.

    The change adds a missing-file panel gated on the FILE_NOT_FOUND code, keeping the active browse revision in the root and close-preview links.

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

mcp-use

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.

Every one of these is a convenient shortcut that quietly collapses a distinct input into a default: || 30000 erases an explicit zero, get(\"auth\", {}) turns an omitted field into a configured one, .type ignores a union, split(\" \") cannot hold a quoted path, and an HTTP request cannot see a TCP listener. The shortcut always answers; the case it cannot represent disappears without an error.

  • The CLI decided whether a port was free by sending it an HTTP request and treating a failed request as "nothing is listening." A non-HTTP TCP listener — a database, a redis, any custom service — fails that request, so the CLI reported the port as available and then died with EADDRINUSE when the server tried to bind it.

    The change probes the port by attempting a TCP bind instead of an HTTP fetch, and checks both 127.0.0.1 and ::1 so an IPv4-only loopback listener is not missed when the platform resolves localhost to IPv6.

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

  • 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 change 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)

  • The Python connector factory read the optional auth field as server_config.get("auth", {}), so a config with no auth key arrived downstream as an empty auth object rather than as nothing. The HTTP connector initialised OAuth state from that empty dict, and the WebSocket connector took its unsupported-auth warning path — a server configured with no authentication was handled as one that had it.

    The change preserves None for an omitted auth field so a missing key means unauthenticated, leaving explicit auth values untouched.

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

  • The CLI coerces positional key=value tool arguments to typed JSON using the tool's input schema, but read only a property's direct type field. MCP schemas express nullable values as anyOf/oneOf, so a property declared anyOf: [{type: "integer"}, {type: "null"}] counted as untyped and count=42 reached the tool as the string "42".

    The change collects the single concrete type out of anyOf/oneOf branches and applies the existing coercion rules to it.

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

  • BaseAdapter.fix_schema() normalised JSON Schema dictionaries in place, and adapter conversion passes the MCP tool's own inputSchema object into it. Converting a tool rewrote the schema object the caller still held, so nullable-type and enum normalisation leaked back into the original tool.

    The change deep-copies the schema once at the public boundary, keeping the recursive normalisation but confining it to the copy.

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

  • mcp-use client connect --stdio built its saved {command, args} pair with target.split(" "). Any executable path or argument containing a space — /opt/MCP Servers/server, a Windows C:\Program Files path, an intentionally empty quoted argument — was cut into pieces before being written to the config.

    The change adds a tokenizer that understands quoting and escaping for the saved stdio target string, without executing shell syntax.

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

  • The start command's explicit-port check scanned process.argv for both --port and -p, but start defines -p as the short alias for --path, not --port. Passing a project path made the CLI believe a port had been set explicitly, so PORT=4173 mcp-use start -p ./app ignored PORT and bound the default 3000.

    The change restricts explicit-port detection to --port and --port=<value>, keeping the --port > PORT > default precedence.

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

  • generateToolOutput() assigned the result of a widget's structuredContent callback directly. When that callback was async the tool result carried an unresolved Promise where the structured JSON should have been.

    The change makes the helper async and awaits function-valued structuredContent before assigning it.

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

  • The screenshot command parsed its timeout as parseInt(options.timeout, 10) || 30000. Zero is falsy, so an explicit --timeout 0 was replaced by the 30-second default with no error and no notice — the value the user typed was indistinguishable from not typing one.

    The change defaults only when parsing fails, so an explicit numeric zero survives.

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

Tracecat

TracecatHQ/tracecat (opens in a new tab)

Tracecat is an open-source security automation platform — workflows, case management, and an agent runtime that security teams self-host to automate incident response.

Each one is a boundary the code assumed instead of enforcing: an empty allowlist read as "no preference" rather than "no", a sanitiser that rebuilt the exact substring it existed to remove, a quota checked outside the lock that would have made it true, a CI input trusted because a maintainer typed it. None came from a bug report; all came from reading the trust boundaries of a codebase whose job is security.

  • FileSecurityValidator resolved its policy with allowed_extensions or config.TRACECAT__ALLOWED_ATTACHMENT_EXTENSIONS, and the attachment service passed None whenever the stored workspace list was falsy. An empty list is falsy. So a workspace that had explicitly allowed no extensions and no MIME types inherited the global defaults and accepted txt, pdf, png, jpeg, gif and csv uploads — the setting meant to turn attachments off turned them all on. The same path also ran the per-case quota check ahead of the workspace policy check, so a deny-all workspace returned a quota error rather than a policy error.

    The change makes an explicitly empty workspace attachment allowlist deny every upload instead of falling back to the global defaults.

    TracecatHQ/tracecat#2968 (opens in a new tab)

  • The secret create and edit forms called console.log("Submitting new secret", values) and console.log("Submitting edit secret", params) with the submitted form object, so plaintext secret values were written to the browser console on every save. The delete flows logged the selected secret object, and the create, update and delete error handlers logged the generated client's raw API error object. Console output persists in DevTools and gets copied into support and debug artifacts.

    The change removes browser-console output from the organization and workspace secret-management flows.

    TracecatHQ/tracecat#2979 (opens in a new tab)

  • safe_url() is the helper the registry and admin paths call before logging or storing a repository URL, and it carries the comment XXX(safety): Reconstruct url without credentials. It rebuilt the URL from urlparse(...).netloc, which still contains any user:password@ prefix. It dropped the query string and the fragment and kept the credentials, so a custom registry URL with an embedded token was returned intact to the surfaces meant to be safe to log.

    The change strips embedded userinfo from the URL that safe_url() returns.

    TracecatHQ/tracecat#2980 (opens in a new tab)

  • Per-case attachment count and storage limits were enforced from aggregate reads taken with no lock on the case row. Two uploads to the same case that overlapped both read the same pre-insert totals, both passed _assert_case_limits, and both committed, leaving the case past its configured maximum attachment count or storage quota. The internal executor route also surfaced those quota failures as unhandled errors instead of 409 and 413.

    The change locks the case row with FOR UPDATE before the attachment count and storage-limit checks.

    TracecatHQ/tracecat#2971 (opens in a new tab)

  • build-push-images.yml used the raw workflow_dispatch.inputs.tag string as the GHCR tag for the API and UI images, and for any non-nightly value the manifest jobs also moved latest. Nothing checked that the dispatch ran from a tag ref or that the ref name matched the input, so a manual run started from a branch could publish that branch's commit under a release-shaped tag and as latest.

    The change requires a manual image publish to run from the git tag whose name matches the tag input.

    TracecatHQ/tracecat#2978 (opens in a new tab)

  • The lint, test, frontend, Python, integration and build-test workflows left actions/checkout at its default, which writes the job's GITHUB_TOKEN into the local git config where every later step in the job can read it. None of those jobs push anything; only the two release jobs that create branches and tags need the token to persist. The repo's own .github/AGENTS.md already stated the rule that the workflows did not follow.

    The change sets persist-credentials: false on the read-only actions/checkout steps.

    TracecatHQ/tracecat#2976 (opens in a new tab)

Piggy Banker

clayraterman/piggy-banker

Piggy Banker is a finance operating system for fractional CFO firms: it connects client books from QuickBooks, Xero, Plaid and Merge, and runs scheduled agents over them to produce reports.

The defects are all in the branch nobody exercises: the empty case, the retry path, the refusal, the argument the model got slightly wrong. Several of them latch — one bad input disables a subsystem and it stays disabled, because the failed state is the same state the gate reads to decide whether to try again.

The repository is private. Nothing in this group opens on to a diff, so a reader cannot check any of it.

  • Scheduled agent runs stopped in production on 2026-07-13 and none ran for six weeks. The runtime handed the model a bare list of tool names — no schemas, no parameter lists — then failed the entire run on any argument name outside a hardcoded set. The name that killed it was priority, a real task attribute the workspace API validates as low|medium|high and the task board reads back with a default; only the agent tool could not express it. The failure then latched: a non-succeeded latest run made readiness report blocked, the scheduler denied a blocked deployment and deleted its queued slots, so no run could occur and the latest run stayed failed. Three enabled deployments with crons, zero agent.run jobs queued, no operator signal.

    The change strips and reports unrecognised tool-argument names instead of failing the whole run, and teaches the task tool the priority field the rest of the product already had.

  • Nothing had been exercised end to end in a real browser against a production build, so a whole class of defect was invisible to every check the repository ran. Every agent deployment sat permanently at "Needs setup": activation set enabled = true, readiness turned "no successful run yet" into a blocker from that moment, and the only reachable run type was barred from recording the report readiness required. The planning URL carried no page identity, so a reload fell back to pages[0] — author a page, refresh, and you are silently editing a different document. A deployment with schedule_cron NULL still read "Runs every day at 9:00 AM UTC", because rehydration trusted cached UI state over the field the scheduler reads. Client-access refusals escaped as 500 from ten routes while a malformed id answered not-found, telling a prober which client ids were real. The "Set opening cash" action had no case in its route's switch, so every save fell through to 400. Two checks in the audit harness itself could not fail — one counted column headers as data cells and silently reported not-applicable on every run.

    The change closes defects that only appear when the shipped surfaces are exercised end to end in a browser against a production build, and repairs audit checks that could never fail.

  • POST /api/jobs/process drains the job queue for every tenant — it schedules runs across all enabled deployments, spends model budget and dispatches approved external effects — and it accepted a second authorization path: a static marker header that was a public constant with no entropy, a same-origin check, and whatever admin session the browser attached automatically. Origin is only unforgeable for browsers; the repository's own test suite authorized a hand-built Request with a set Origin header. The role gate was wrong in both directions: it demanded a global platform role on a tenant-scoped screen, so real firm operators had always received a silent 403, while the operation it authorized was cross-tenant. In the scheduler, the readiness rule existed twice at the two decision points, written in opposite polarity over the same fields with one condition hard-coded on one side only; the enabled/cron and readiness gates sat inside a branch taken only when no run row existed yet, so a stale-reclaimed retry executed on a deployment the operator had since disabled; the orphan sweep failed every run older than the fifteen-minute reclaim timeout without checking whether a live worker still owned it; and two overlapping cron passes could both see one schedule slot as empty and fill it — two runs, two model generations, two charges for one slot.

    The change deletes the browser-reachable job-processing path, moves the scheduler's readiness rule to one place both decision points import, and adds a versioned finance read seam behind a default-off flag.

Reticle

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.

  • 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 change 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)

  • 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 change 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)

  • 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 change 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)

Full Stack FastAPI Template

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.

  • 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 change 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)