Skip to content

Various Bugfixes - #484

Merged
JabbaKadabra merged 8 commits into
masterfrom
bugfixes
Jul 27, 2026
Merged

Various Bugfixes#484
JabbaKadabra merged 8 commits into
masterfrom
bugfixes

Conversation

@JabbaKadabra

Copy link
Copy Markdown
Collaborator

No description provided.

Comment thread Proxytrace.Api/Middleware/ExceptionHandlingMiddleware.cs Fixed
Comment thread Proxytrace.Proxy/Controllers/OpenAiProxyController.cs Fixed
if (!group.Status.IsTerminal())
// Same check-then-act as the cancel path: a user cancelling while the group is
// failing must not collide with this transition or its broadcast.
using (IDisposable sync = await asyncLock.LockAsync(group.Id, CancellationToken.None))
group = await group.ReloadAsync(cancellationToken);
group = await group.SetCompleted(cancellationToken);
broadcaster.PublishGroupComplete(GroupRunCompleteEvent.Create(group));
using (IDisposable sync = await asyncLock.LockAsync(group.Id, cancellationToken))
{
var agent = await agentRepository.FindAsync(aid, cancellationToken);
return agent is not null && accessible.Contains(agent.Project.Id);
if (agent is null || !scope.Admits(agent.Project.Id))
{
var agent = await agents.FindAsync(aid, cancellationToken);
return agent is not null && accessible.Contains(agent.Project.Id);
if (agent is null || !scope.Admits(agent.Project.Id))
Comment thread Proxytrace.Api.Tests/TrustedProxyConfigurationTests.cs
Comment thread Proxytrace.Api.Tests/TrustedProxyConfigurationTests.cs

var byKey = await providers.FindByApiKeyAsync("sk-legacy-789", CancellationToken);

byKey.Should().NotBeNull();

// The row is still recognised as un-backfilled and is hashed, so the key authenticates again.
var found = await keys.FindByKeyAsync(keyPlaintext, CancellationToken);
found.Should().NotBeNull();
var latest = await repo.GetLatestByEvaluatorAsync(mine.Id, CancellationToken);

recent.Should().ContainSingle("this evaluator has exactly one result, however much else ran since");
latest.Should().NotBeNull();
JabbaKadabra and others added 3 commits July 27, 2026 20:15
The new per-IP `auth-login` limiter (30/min) counts the whole e2e suite
against a single partition: every spec authenticates its own
ProxytraceApiClient, so a run drives ~80 real logins from one source
address in ~3.4 minutes. The tail of the run 429'd — 25 failures, all
`login failed: 429`.

Raise the budget for the e2e stack only, via the operator override seam
AuthRateLimiterConfigurator already exposes. The shipped default in
appsettings.json is untouched.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DusPcXLc8APo1tUWKDHjmW
CodeQL flagged two `cs/log-forging` sites (alerts #38, #39). The request
line cannot carry a raw newline, but a percent-encoded one survives URL
decoding: `/x%0D%0AINFO:%20admin%20logged%20in` reaches `Request.Path` as
two lines, and a flat-file or console sink renders the second as a log
entry of its own.

Add `ToSingleLogLine()` in Proxytrace.Common.Text and apply it at both
sites — the "response already started" warning in
ExceptionHandlingMiddleware and the missing-Passthrough-scope warning in
OpenAiProxyController.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DusPcXLc8APo1tUWKDHjmW
Sweeps the seven pre-existing `cs/log-forging` alerts (#15#21) with the
`ToSingleLogLine()` helper added for #38/#39: the upstream-failure
warnings in OpenAiProxyController and TraceyChatController, the
Not-implemented sibling in ExceptionHandlingMiddleware, the two
TestSupportController message logs, and both password-reset fallback
warnings in PasswordResetService.

These sites are on master rather than new to this branch, so they were
not blocking the merge — but they are the same defect and are now shipped
code, hence the CHANGELOG entry.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DusPcXLc8APo1tUWKDHjmW
Comment thread Proxytrace.Application/Auth/Local/Internal/PasswordResetService.cs Fixed
The SARIF dataflow for the surviving cs/log-forging alert (#40) shows the
tainted value on that line is `issued.ResetLink.Link`, not `user.Email`:
it originates at AuthController.BuildResetUrl, where the configured
frontend origin falls back to `Request.Scheme`/`Request.Host`, and is
carried through PasswordResetLink into the emergency-log branch.

Sanitize the link too. The email keeps its sanitizer as defence in depth
(it is user-supplied at signup/invite); the redacted branch logs only a
token hash, which is why CodeQL flags one branch and not the other.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DusPcXLc8APo1tUWKDHjmW
@JabbaKadabra
JabbaKadabra merged commit 9d89f91 into master Jul 27, 2026
14 checks passed
@JabbaKadabra
JabbaKadabra deleted the bugfixes branch July 27, 2026 19:08
JabbaKadabra added a commit that referenced this pull request Jul 28, 2026
Fixes every bug open on the tracker, plus the three found while doing so.

Security / access control
- #474 ProjectsController delegated to IProjectAccessGuard. Its inline
  CanAccessAsync never read ApiKeyAuthenticationHandler.ProjectIdItemKey, so a
  REST key minted for project A could read project B's detail and its members'
  email addresses whenever the key's owner (usually an admin) was a member.
- #473 User.PrintMembers masks PasswordHash. ExternalSubject stays visible: the
  existing five redact credentials, not identifiers.
- #479 HostEnvironmentName resolves the environment as the host does
  (DOTNET_ENVIRONMENT ahead of ASPNETCORE_ENVIRONMENT) and the module layers in
  appsettings.{Environment}.json. The reversed order made a Production host
  compute Development and drop Secure from the session cookie.

Proxy
- #475 ResponseHeadersRead is kept, but ProxyBufferedResponseAsync re-arms the
  bound at its copy loop from client.Timeout, so a stalled upstream is a 504
  instead of a request held open until the client gives up.
- #480 The SSE splitter treats a lone CR as a terminator, carrying the CR/LF
  seam across a chunk boundary so CRLF is never counted twice.

Aggregates and scoping
- #483 StatisticsFilter gained ProjectIds, applied in both the EF path and the
  raw SQL one (= ANY(@projectIDS), a single uuid[] parameter, never
  interpolated). The single-project path is preserved, so the web UI and every
  REST key keep their indexed equality predicate and its plan.
  EvaluatorsController's sparklines had the identical constraint and got the
  identical treatment.

Runner and middleware
- #476 The success-path optimizer/anomaly enqueues take CancellationToken.None,
  matching the failure path: a cancel landing as the group completed skipped
  both jobs silently.
- #486 The generic catch only broadcasts and enqueues for a group it actually
  transitioned. The two enqueues also absorb their own failures, so losing one
  no longer costs the group the other.
- #477 A fault after Response.HasStarted aborts the connection. Returning
  signalled success, framing a truncated body as complete.
- #485 A client that hung up mid-stream is classified before the error capture,
  so a closed tab no longer persists an ApplicationError nobody can act on.

Pricing
- #478/#487 The LiteLLM catalog and the Frankfurter FX feed each arm a 30s
  negative cache. Priced per model, an outage previously cost one full fetch
  per discovered model, serialized behind the gate.

Misc
- #481 IsAzure trims one trailing DNS root dot before the exact suffix match,
  keeping the domain-boundary guarantee (my-azure.com.example.net still fails).

#482 needed no change: PR #484 already added ResolveListScopeAsync and moved all
eight list endpoints onto it.

Verified: dotnet build Proxytrace.sln clean (24 projects, 0 warnings); scoped
test runs green (Domain 398, Storage 815, Proxy 96, Infrastructure 73,
Application 523, Api 543); manual VitePress build clean.

Not verified: the two perf metrics added for #483 carry uncalibrated placeholder
budgets. perf/run.sh --size 1000000 is still owed to set them from a real p95.


Claude-Session: https://claude.ai/code/session_01RTyMUobDLv1aEAqHwtUtQg

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
JabbaKadabra added a commit that referenced this pull request Jul 30, 2026
Resolves the conflicts between the cost-control feature and the bug-backlog
and security work merged to master (#484, #488, 86f72d8).

- AuditAction / NotificationKind: both sides appended members. Master's
  already-merged values keep their numbering; the cost actions shift to 72-76
  and CostBudget moves after TraceQuotaReached. Neither shifted value has
  shipped, so no stored row changes meaning.
- ResolvedApiKey: each side added a member for the same reason (attributing a
  proxied call to the Proxytrace-issued key). The record now carries both
  ApiKeyId (per-key spend and budget blocking) and Scopes (the pass-through
  capability check), both null on the upstream-key path.
- OpenAiProxyController: keeps the ResolvedApiKey-shaped capture signatures and
  takes master's additions - the body-download timeout (#475) and capturedStatus,
  so a stalled upstream is still traced as a 504.
- IApiKeyRepository / ApiKeyRepository: additive, GetByProjectAsync alongside
  GetKeyNamesByOwnerAsync.
- CHANGELOG: merged section by section under [Unreleased]. Master's two separate
  ### Fixed headings are folded into one; entry order and text are unchanged.

Verified: dotnet build (0 warnings), dotnet test Proxytrace.sln (2930 passed,
0 failed), frontend build/lint/test (1140 passed), manual docs:build, and
`ef migrations has-pending-model-changes` (none).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U9NmoPS88gGTwTKAuUcbNb
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants