Skip to content

Commit 58189cf

Browse files
Merge upstream/main (auto-sync feat/copilot)
- d2c5f27 feat(logging): add request_id handling in HomeAppLogForwarder and tests - 86cb9c1 feat(signature): upgrade provider signature checks - aee7a5f feat: intercept incompatible signature replay - e9dafc7 fix(openai): dedupe response websocket input item IDs - 034d2b6 Merge pull request router-for-me#3615 from sususu98/codex/signature-intercept-on-pr - 96b6f7e Merge pull request router-for-me#3612 from router-for-me/log - fc0615b test(oauth): ensure missing auth directories are created and callback payloads are validated - 55901f0 Merge pull request router-for-me#3620 from iBenzene/fix/responses-input-id-dedupe
2 parents 75ada28 + 55901f0 commit 58189cf

43 files changed

Lines changed: 2711 additions & 256 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

internal/api/handlers/management/oauth_callback.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"strings"
88

99
"github.com/gin-gonic/gin"
10+
log "github.com/sirupsen/logrus"
1011
)
1112

1213
type oauthCallbackRequest struct {
@@ -97,6 +98,7 @@ func (h *Handler) PostOAuthCallback(c *gin.Context) {
9798
c.JSON(http.StatusConflict, gin.H{"status": "error", "error": "oauth flow is not pending"})
9899
return
99100
}
101+
log.WithError(errWrite).Error("failed to persist oauth callback")
100102
c.JSON(http.StatusInternalServerError, gin.H{"status": "error", "error": "failed to persist oauth callback"})
101103
return
102104
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package management
2+
3+
import (
4+
"encoding/json"
5+
"net/http"
6+
"net/http/httptest"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"testing"
11+
12+
"github.com/gin-gonic/gin"
13+
"github.com/router-for-me/CLIProxyAPI/v7/internal/config"
14+
)
15+
16+
func TestPostOAuthCallbackCreatesMissingAuthDir(t *testing.T) {
17+
gin.SetMode(gin.TestMode)
18+
19+
authDir := filepath.Join(t.TempDir(), "missing-auth")
20+
state := "test-antigravity-state"
21+
RegisterOAuthSession(state, "antigravity")
22+
defer CompleteOAuthSession(state)
23+
24+
h := NewHandlerWithoutConfigFilePath(&config.Config{AuthDir: authDir}, nil)
25+
router := gin.New()
26+
router.POST("/v0/management/oauth-callback", h.PostOAuthCallback)
27+
28+
body := `{"provider":"antigravity","redirect_url":"http://localhost:59788/oauth-callback?state=test-antigravity-state&code=test-code"}`
29+
req := httptest.NewRequest(http.MethodPost, "/v0/management/oauth-callback", strings.NewReader(body))
30+
req.Header.Set("Content-Type", "application/json")
31+
w := httptest.NewRecorder()
32+
33+
router.ServeHTTP(w, req)
34+
35+
if w.Code != http.StatusOK {
36+
t.Fatalf("expected status %d, got %d with body %s", http.StatusOK, w.Code, w.Body.String())
37+
}
38+
39+
callbackPath := filepath.Join(authDir, ".oauth-antigravity-"+state+".oauth")
40+
data, errRead := os.ReadFile(callbackPath)
41+
if errRead != nil {
42+
t.Fatalf("expected callback file to be written: %v", errRead)
43+
}
44+
45+
var payload oauthCallbackFilePayload
46+
if errUnmarshal := json.Unmarshal(data, &payload); errUnmarshal != nil {
47+
t.Fatalf("failed to decode callback payload: %v", errUnmarshal)
48+
}
49+
if payload.State != state || payload.Code != "test-code" || payload.Error != "" {
50+
t.Fatalf("unexpected callback payload: %+v", payload)
51+
}
52+
}
53+
54+
func TestWriteOAuthCallbackFileForPendingSessionCreatesMissingAuthDirForCallbackProviders(t *testing.T) {
55+
providers := []string{"anthropic", "codex", "gemini", "antigravity", "xai"}
56+
for _, provider := range providers {
57+
t.Run(provider, func(t *testing.T) {
58+
authDir := filepath.Join(t.TempDir(), "missing-auth")
59+
state := provider + "-state"
60+
RegisterOAuthSession(state, provider)
61+
defer CompleteOAuthSession(state)
62+
63+
path, errWrite := WriteOAuthCallbackFileForPendingSession(authDir, provider, state, "code-"+provider, "")
64+
if errWrite != nil {
65+
t.Fatalf("expected callback file write to succeed: %v", errWrite)
66+
}
67+
68+
data, errRead := os.ReadFile(path)
69+
if errRead != nil {
70+
t.Fatalf("expected callback file to be written: %v", errRead)
71+
}
72+
73+
var payload oauthCallbackFilePayload
74+
if errUnmarshal := json.Unmarshal(data, &payload); errUnmarshal != nil {
75+
t.Fatalf("failed to decode callback payload: %v", errUnmarshal)
76+
}
77+
if payload.State != state || payload.Code != "code-"+provider || payload.Error != "" {
78+
t.Fatalf("unexpected callback payload: %+v", payload)
79+
}
80+
})
81+
}
82+
}

internal/api/handlers/management/oauth_sessions.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,9 @@ func WriteOAuthCallbackFile(authDir, provider, state, code, errorMessage string)
269269

270270
fileName := fmt.Sprintf(".oauth-%s-%s.oauth", canonicalProvider, state)
271271
filePath := filepath.Join(authDir, fileName)
272+
if err := os.MkdirAll(authDir, 0o700); err != nil {
273+
return "", fmt.Errorf("create oauth callback dir: %w", err)
274+
}
272275
payload := oauthCallbackFilePayload{
273276
Code: strings.TrimSpace(code),
274277
State: strings.TrimSpace(state),

internal/logging/home_app_log_forwarder.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type homeAppLogPayload struct {
2424
Line string `json:"line"`
2525
Level string `json:"level,omitempty"`
2626
Timestamp string `json:"timestamp,omitempty"`
27+
RequestID string `json:"request_id,omitempty"`
2728
}
2829

2930
var currentHomeAppLogClient = func() homeAppLogClient {
@@ -92,6 +93,7 @@ func (f *HomeAppLogForwarder) Fire(entry *log.Entry) error {
9293
Line: line,
9394
Level: entry.Level.String(),
9495
Timestamp: entry.Time.Format(time.RFC3339Nano),
96+
RequestID: appLogRequestID(entry),
9597
}
9698
select {
9799
case f.queue <- payload:
@@ -100,6 +102,18 @@ func (f *HomeAppLogForwarder) Fire(entry *log.Entry) error {
100102
return nil
101103
}
102104

105+
func appLogRequestID(entry *log.Entry) string {
106+
if entry == nil {
107+
return ""
108+
}
109+
requestID, _ := entry.Data["request_id"].(string)
110+
requestID = strings.TrimSpace(requestID)
111+
if requestID == "--------" {
112+
return ""
113+
}
114+
return requestID
115+
}
116+
103117
func (f *HomeAppLogForwarder) formatEntry(entry *log.Entry) (string, error) {
104118
formatter := f.formatter
105119
if formatter == nil {

internal/logging/home_app_log_forwarder_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func TestHomeAppLogForwarder_ForwardsFormattedLogWhenHomeHealthy(t *testing.T) {
7272
entry.Time = time.Date(2026, 5, 29, 8, 0, 0, 0, time.Local)
7373
entry.Level = log.DebugLevel
7474
entry.Message = "debug details"
75+
entry.Data["request_id"] = "req-app-1"
7576

7677
if errFire := forwarder.Fire(entry); errFire != nil {
7778
t.Fatalf("Fire error: %v", errFire)
@@ -92,14 +93,29 @@ func TestHomeAppLogForwarder_ForwardsFormattedLogWhenHomeHealthy(t *testing.T) {
9293
if got.Level != "debug" {
9394
t.Fatalf("level = %q, want debug", got.Level)
9495
}
96+
if got.RequestID != "req-app-1" {
97+
t.Fatalf("request_id = %q, want req-app-1", got.RequestID)
98+
}
9599
if !strings.Contains(got.Line, "debug details") {
96100
t.Fatalf("line %q missing log message", got.Line)
97101
}
102+
if !strings.Contains(got.Line, "[req-app-1]") {
103+
t.Fatalf("line %q missing matching request id", got.Line)
104+
}
98105
if strings.TrimSpace(got.Timestamp) == "" {
99106
t.Fatal("timestamp empty, want non-empty")
100107
}
101108
}
102109

110+
func TestHomeAppLogForwarder_OmitsPlaceholderRequestID(t *testing.T) {
111+
entry := log.NewEntry(log.StandardLogger())
112+
entry.Data["request_id"] = "--------"
113+
114+
if got := appLogRequestID(entry); got != "" {
115+
t.Fatalf("request id = %q, want empty for placeholder", got)
116+
}
117+
}
118+
103119
func TestHomeAppLogForwarder_SkipsWhenHomeHeartbeatIsDown(t *testing.T) {
104120
original := currentHomeAppLogClient
105121
defer func() {

internal/runtime/executor/antigravity_executor.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,9 @@ func validateAntigravityRequestSignatures(from sdktranslator.Format, rawJSON []b
245245
return rawJSON, nil
246246
}
247247
// Always strip thinking blocks with invalid signatures (empty or non-Claude-format).
248+
before := countClaudeThinkingBlocks(rawJSON)
248249
rawJSON = antigravityclaude.StripEmptySignatureThinkingBlocks(rawJSON)
250+
logAntigravitySignatureStrip(before, countClaudeThinkingBlocks(rawJSON), "prefix_cleanup", "empty_or_non_claude_signature")
249251
if cache.SignatureCacheEnabled() {
250252
return rawJSON, nil
251253
}
@@ -254,12 +256,51 @@ func validateAntigravityRequestSignatures(from sdktranslator.Format, rawJSON []b
254256
// by dropping unsigned thinking blocks silently (no 400).
255257
return rawJSON, nil
256258
}
257-
if err := antigravityclaude.ValidateClaudeBypassSignatures(rawJSON); err != nil {
258-
return rawJSON, statusErr{code: http.StatusBadRequest, msg: err.Error()}
259-
}
259+
before = countClaudeThinkingBlocks(rawJSON)
260+
rawJSON = antigravityclaude.StripInvalidBypassSignatureThinkingBlocks(rawJSON)
261+
logAntigravitySignatureStrip(before, countClaudeThinkingBlocks(rawJSON), "strict_bypass", "invalid_antigravity_claude_signature")
260262
return rawJSON, nil
261263
}
262264

265+
func countClaudeThinkingBlocks(rawJSON []byte) int {
266+
messages := gjson.GetBytes(rawJSON, "messages")
267+
if !messages.IsArray() {
268+
return 0
269+
}
270+
271+
count := 0
272+
messages.ForEach(func(_, message gjson.Result) bool {
273+
content := message.Get("content")
274+
if !content.IsArray() {
275+
return true
276+
}
277+
content.ForEach(func(_, part gjson.Result) bool {
278+
if part.Get("type").String() == "thinking" {
279+
count++
280+
}
281+
return true
282+
})
283+
return true
284+
})
285+
return count
286+
}
287+
288+
func logAntigravitySignatureStrip(before, after int, stage, reason string) {
289+
removed := before - after
290+
if removed <= 0 {
291+
return
292+
}
293+
log.WithFields(log.Fields{
294+
"component": "signature_sanitizer",
295+
"executor": "antigravity",
296+
"target_provider": "claude",
297+
"action": "drop_thinking_blocks",
298+
"stage": stage,
299+
"reason": reason,
300+
"count": removed,
301+
}).Debug("antigravity executor: dropped Claude thinking blocks with invalid signatures")
302+
}
303+
263304
// Identifier returns the executor identifier.
264305
func (e *AntigravityExecutor) Identifier() string { return antigravityAuthType }
265306

0 commit comments

Comments
 (0)