Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/auto-diagnostic.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Auto Diagnostic Bundle

on:
push:
branches:
- 'feat/**'
- 'fix/**'
- 'chore/**'

# Skip bot commits to avoid infinite loop
concurrency:
group: diagnostic-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: write

jobs:
build-diagnostic:
name: Run build.py and commit diagnostic bundle
runs-on: ubuntu-latest
if: "!contains(github.event.head_commit.author.name, 'github-actions')"

steps:
- name: Checkout branch
uses: actions/checkout@v4
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}

- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '20'

- name: Set up Java
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'

- name: Install system dependencies
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
gcc g++ cmake make lua5.4 luajit ruby ghc

- name: Make encryptly executable
run: |
chmod +x tools/encryptly/linux-x64/encryptly
chmod +x tools/encryptly/linux-arm64/encryptly || true

- name: Configure git identity
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"

- name: Run build.py
run: python3 build.py
continue-on-error: true

- name: Commit and push diagnostic bundle
run: |
git add diagnostic/ || true
if git diff --cached --quiet; then
echo "No diagnostic files to commit"
exit 0
fi
git commit -m "ci: add diagnostic bundle [skip ci]"
git push origin HEAD
2 changes: 1 addition & 1 deletion build.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,7 +569,7 @@ def generate_logd(
"--include",
str(workspace),
"--max-file-size",
"10000",
"35840",
],
cwd=str(ROOT),
capture_output=True,
Expand Down
86 changes: 86 additions & 0 deletions diagnostic/build-051e0a01.json

Large diffs are not rendered by default.

Binary file added diagnostic/build-051e0a01.logd
Binary file not shown.
160 changes: 160 additions & 0 deletions market/gateway/middleware_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package gateway

import (
"encoding/json"
"net/http"
"net/http/ttptest"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix the httptest import typo (build blocker).

net/http/ttptest is not a valid stdlib package, so this test file will not compile.

Suggested patch
-	"net/http/ttptest"
+	"net/http/httptest"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"net/http/ttptest"
"net/http/httptest"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/gateway/middleware_test.go` at line 6, The import statement contains a
typo where "net/http/ttptest" is missing the first 'h' in the package name.
Change the import from "net/http/ttptest" to "net/http/httptest" to use the
correct standard library package for HTTP testing functionality.

"testing"
)

// ErrorResponse represents the expected JSON error shape from auth middleware
type ErrorResponse struct {
Error string `json:"error"`
Code int `json:"code,omitempty"`
Message string `json:"message,omitempty"`
}

// TestAuthMiddleware_MissingToken verifies that requests without a bearer token
// return 401 with the expected JSON error shape
func TestAuthMiddleware_MissingToken(t *testing.T) {
handler := AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
}))
Comment on lines +20 to +23

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Assert the wrapped handler is never called on unauthorized paths.

Both missing-token and invalid-token tests currently verify status/body, but they do not verify that next is not invoked, which is part of the acceptance criteria.

Suggested pattern
+	called := false
 	handler := AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		called = true
 		w.WriteHeader(http.StatusOK)
 		w.Write([]byte(`{"status":"ok"}`))
 	}))
...
 	if rr.Code != http.StatusUnauthorized {
 		t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
 	}
+	if called {
+		t.Error("wrapped handler must not be called for unauthorized requests")
+	}

Also applies to: 52-55

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/gateway/middleware_test.go` around lines 20 - 23, The missing-token
and invalid-token test cases for AuthMiddleware are only verifying the response
status and body, but not verifying that the wrapped handler itself is never
executed. Add a flag or counter variable to track whether the wrapped handler
(the next http.HandlerFunc passed to AuthMiddleware) is invoked, then in both
the missing-token and invalid-token tests, assert that this flag/counter
confirms the handler was never called. This applies to both test cases around
lines 20-23 and lines 52-55.


req := httptest.NewRequest("GET", "/api/test", nil)
rr := httptest.NewRecorder()

handler.ServeHTTP(rr, req)

if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}

var errResp ErrorResponse
if err := json.Unmarshal(rr.Body.Bytes(), &errResp); err != nil {
t.Fatalf("failed to parse error response: %v", err)
}

if errResp.Error == "" {
t.Error("expected error field to be present in JSON response")
}

contentType := rr.Header().Get("Content-Type")
if contentType != "application/json" {
t.Errorf("expected Content-Type application/json, got %s", contentType)
}
}

// TestAuthMiddleware_InvalidToken verifies that requests with an invalid token
// return 401 with the expected JSON error shape
func TestAuthMiddleware_InvalidToken(t *testing.T) {
handler := AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
}))

req := httptest.NewRequest("GET", "/api/test", nil)
req.Header.Set("Authorization", "Bearer invalid-token-12345")
rr := httptest.NewRecorder()

handler.ServeHTTP(rr, req)

if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d, got %d", http.StatusUnauthorized, rr.Code)
}

var errResp ErrorResponse
if err := json.Unmarshal(rr.Body.Bytes(), &errResp); err != nil {
t.Fatalf("failed to parse error response: %v", err)
}

if errResp.Error == "" {
t.Error("expected error field to be present in JSON response")
}
}

// TestMiddlewareChain_ContextPropagation verifies that authenticated user
// context is properly propagated through the middleware chain
func TestMiddlewareChain_ContextPropagation(t *testing.T) {
var capturedUserID string

innerHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if userID := r.Context().Value(UserIDKey); userID != nil {
capturedUserID = userID.(string)
Comment on lines +83 to +84

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Use the middleware’s actual context key contract for user ID.

The middleware writes user ID under ContextKeyUserID (market/gateway/middleware.go:59-69), but this test reads UserIDKey. This likely breaks the check (or fails compile if undefined).

Suggested patch
-		if userID := r.Context().Value(UserIDKey); userID != nil {
+		if userID := r.Context().Value(ContextKeyUserID); userID != nil {
 			capturedUserID = userID.(string)
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if userID := r.Context().Value(UserIDKey); userID != nil {
capturedUserID = userID.(string)
if userID := r.Context().Value(ContextKeyUserID); userID != nil {
capturedUserID = userID.(string)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/gateway/middleware_test.go` around lines 83 - 84, The test is
attempting to read the user ID from the request context using UserIDKey, but the
middleware actually stores the user ID under ContextKeyUserID as defined in the
middleware code. Replace the UserIDKey reference with ContextKeyUserID in the
r.Context().Value() call on line 83 to ensure the test reads from the same
context key that the middleware writes to.

}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})

// Chain: RateLimit -> Auth -> Handler
// This order is critical per the documented incident
handler := RateLimitMiddleware(AuthMiddleware(innerHandler))

req := httptest.NewRequest("GET", "/api/test", nil)
// Use a valid test token format (assuming test token prefix is accepted)
req.Header.Set("Authorization", "Bearer test-valid-token")
rr := httptest.NewRecorder()

handler.ServeHTTP(rr, req)

// If auth passed, check context propagation
if rr.Code == http.StatusOK && capturedUserID == "" {
t.Error("expected user ID to be propagated in context")
}
Comment on lines +95 to +104

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

This context-propagation test can pass without exercising propagation.

The assertion is gated on rr.Code == http.StatusOK, so a 401 path (e.g., token rejected) makes the test pass silently. Assert status first, then assert context value so the test fails when auth/context setup regresses.

Suggested patch
-	// If auth passed, check context propagation
-	if rr.Code == http.StatusOK && capturedUserID == "" {
-		t.Error("expected user ID to be propagated in context")
-	}
+	if rr.Code != http.StatusOK {
+		t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code)
+	}
+	if capturedUserID == "" {
+		t.Error("expected user ID to be propagated in context")
+	}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Use a valid test token format (assuming test token prefix is accepted)
req.Header.Set("Authorization", "Bearer test-valid-token")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
// If auth passed, check context propagation
if rr.Code == http.StatusOK && capturedUserID == "" {
t.Error("expected user ID to be propagated in context")
}
req.Header.Set("Authorization", "Bearer test-valid-token")
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
if rr.Code != http.StatusOK {
t.Fatalf("expected status %d, got %d", http.StatusOK, rr.Code)
}
if capturedUserID == "" {
t.Error("expected user ID to be propagated in context")
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/gateway/middleware_test.go` around lines 95 - 104, The test assertion
for context propagation is gated on a successful HTTP status code, which means a
401 authentication failure would cause the test to pass silently without
checking if the user ID was propagated. Refactor the assertion by first
asserting that the HTTP response code equals http.StatusOK with a separate
t.Error or t.Fatal call, then separately assert that capturedUserID is not
empty. This ensures the test fails when either the authentication fails or the
context propagation is not working correctly.

}

// TestRateLimitMiddleware_WithAuth verifies rate limiting respects user identity
func TestRateLimitMiddleware_WithAuth(t *testing.T) {
requestCount := 0
innerHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
requestCount++
w.WriteHeader(http.StatusOK)
})

handler := RateLimitMiddleware(innerHandler)

// Make multiple requests without auth to test IP-based rate limiting
for i := 0; i < 5; i++ {
req := httptest.NewRequest("GET", "/api/test", nil)
req.RemoteAddr = "192.168.1.1:12345"
rr := httptest.NewRecorder()
handler.ServeHTTP(rr, req)
}

// Verify at least some requests were processed
if requestCount == 0 {
t.Error("rate limit middleware blocked all requests")
}
}
Comment on lines +107 to +129

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

TestRateLimitMiddleware_WithAuth does not validate authenticated rate-limit keying.

This test sends only unauthenticated requests and only checks requestCount > 0, so it doesn’t cover the stated requirement that authenticated traffic is keyed separately from anonymous/IP-only traffic.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@market/gateway/middleware_test.go` around lines 107 - 129, The
TestRateLimitMiddleware_WithAuth test does not actually validate that
authenticated traffic is rate-limited separately from unauthenticated traffic.
Modify the test to send both authenticated requests (with user identity/auth
headers) and unauthenticated requests, then verify that the RateLimitMiddleware
applies different rate limits based on user identity rather than just IP
address. You should add authenticated requests with a user identifier and track
their requests separately from IP-based requests to confirm that the middleware
respects user identity for rate-limit keying as the test name and docstring
suggest.


// TestAuthMiddleware_MalformedHeader verifies handling of malformed auth headers
func TestAuthMiddleware_MalformedHeader(t *testing.T) {
testCases := []struct {
name string
authHeader string
}{
{"no_bearer_prefix", "some-token"},
{"basic_auth", "Basic dGVzdDp0ZXN0"},
{"empty_bearer", "Bearer "},
{"lowercase_bearer", "bearer some-token"},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
handler := AuthMiddleware(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
}))

req := httptest.NewRequest("GET", "/api/test", nil)
req.Header.Set("Authorization", tc.authHeader)
rr := httptest.NewRecorder()

handler.ServeHTTP(rr, req)

if rr.Code != http.StatusUnauthorized {
t.Errorf("expected status %d for malformed header, got %d", http.StatusUnauthorized, rr.Code)
}
})
}
}
Binary file modified tools/encryptly/linux-arm64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/linux-x64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/macos-arm64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/windows-arm64/encryptly.exe
Binary file not shown.
Binary file modified tools/encryptly/windows-x64/encryptly.exe
Binary file not shown.