GitHub Security Advisory: GHSA-mrqc-3276-74f8
Package: github.com/pinchtab/pinchtab (GO)
Severity: MODERATE (CVSS 4.3)
CWE: CWE-598: Use of GET Request Method With Sensitive Query Strings
Vulnerable versions: >= 0.7.8, < 0.8.4
Patched in: 0.8.4

Summary: PinchTab: API Bearer Token Exposed in URL Query Parameter via Server Logs and Intermediary Systems

Details:
### Summary
PinchTab `v0.7.8` through `v0.8.3` accepted the API token from a `token` URL query parameter in addition to the `Authorization` header. When a valid API credential is sent in the URL, it can be exposed through request URIs recorded by intermediaries or client-side tooling, such as reverse proxy access logs, browser history, shell history, clipboard history, and tracing systems that capture full URLs.

This issue is an unsafe credential transport pattern rather than a direct authentication bypass. It only affects deployments where a token is configured and a client actually uses the query-parameter form. PinchTab's security guidance already recommended `Authorization: Bearer <token>`, but `v0.8.3` still accepted `?token=` and included first-party flows that generated and consumed URLs containing the token.

This was addressed in v0.8.4 by removing query-string token authentication and requiring safer header- or session-based authentication flows.

### Details
**Issue 1 — Query-string token accepted in `v0.7.8` through `v0.8.3` (`internal/handlers/middleware.go`):**
The `v0.8.3` authentication middleware accepted credentials from the URL query string:

```
// internal/handlers/middleware.go — v0.8.3
auth := r.Header.Get("Authorization")
qToken := r.URL.Query().Get("token")

if auth == "" && qToken == "" {
    web.ErrorCode(w, 401, "missing_token", "unauthorized", false, nil)
    return
}

provided := strings.TrimPrefix(auth, "Bearer ")
if provided == auth {
    if qToken != "" {
        provided = qToken
    } else {
        provided = auth
    }
}

if subtle.ConstantTimeCompare([]byte(provided), []byte(cfg.Token)) != 1 {
    web.ErrorCode(w, 401, "bad_token", "unauthorized", false, nil)
    return
}
```

This means any client sending `GET /health?token=<secret>` in `v0.8.3` would authenticate successfully without using the `Authorization` header. I verified the same query-token auth pattern is present in the historical tag range starting at `v0.7.8`, and it is removed in `v0.8.4`.

**Issue 2 — First-party setup and dashboard flows in `v0.8.3` generated and consumed `?token=` URLs:**
The `v0.8.3` setup flow generated dashboard URLs containing the token in the query string:

```
// cmd/pinchtab/cmd_wizard.go — v0.8.3
func dashboardURL(cfg *config.FileConfig, path string) string {
    host := orDefault(cfg.Server.Bind, "127.0.0.1")
    port := orDefault(cfg.Server.Port, "9867")
    url := fmt.Sprintf("http://%s:%s%s", host, port, path)
    if cfg.Server.Token != "" {
        url += "?token=" + cfg.Server.Token
    }
    return url
}
```

The `v0.8.3` dashboard frontend also supported one-click login from that same query-string token:

```
// dashboard/src/App.tsx — v0.8.3
const params = new URLSearchParams(window.location.search);
const urlToken = params.get("token");
if (urlToken) {
    setStoredAuthToken(urlToken);
    clean.searchParams.delete("token");
    window.history.replaceState({}, "", clean.pathname + clean.hash);
    window.lo