[object Object]

← back to Cli Printing Press

fix(cli): scorer recognizes composed/cookie auth and apiKey header matching

e9d9daa53aed760d6051ccf584016389c50631f2 · 2026-04-03 14:04:45 -0700 · Trevin Chow

Auth Protocol scored 3/10 for composed cookie auth because the scorer
only credited Bearer/Basic/Bot prefix patterns, not custom apiKey
headers or browser cookie auth tooling.

Fixes:
- apiKey schemes with in=header get authHeaderMatched when the client
  sets the correct header name (regardless of value format)
- Browser cookie auth (detectCookieTool, --chrome, chrome-composed)
  credits envMatched since Chrome cookies replace env vars as the
  credential source
- Reads auth.go alongside client.go and config.go for scoring

Also adds AGENTS.md guidance: when adding a capability that affects
scoring, update the scorer in the same change to fairly assess it.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

Files touched

Diff

commit e9d9daa53aed760d6051ccf584016389c50631f2
Author: Trevin Chow <trevin@trevinchow.com>
Date:   Fri Apr 3 14:04:45 2026 -0700

    fix(cli): scorer recognizes composed/cookie auth and apiKey header matching
    
    Auth Protocol scored 3/10 for composed cookie auth because the scorer
    only credited Bearer/Basic/Bot prefix patterns, not custom apiKey
    headers or browser cookie auth tooling.
    
    Fixes:
    - apiKey schemes with in=header get authHeaderMatched when the client
      sets the correct header name (regardless of value format)
    - Browser cookie auth (detectCookieTool, --chrome, chrome-composed)
      credits envMatched since Chrome cookies replace env vars as the
      credential source
    - Reads auth.go alongside client.go and config.go for scoring
    
    Also adds AGENTS.md guidance: when adding a capability that affects
    scoring, update the scorer in the same change to fairly assess it.
    
    Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---
 AGENTS.md                      |  2 ++
 internal/pipeline/scorecard.go | 26 ++++++++++++++++++++++----
 2 files changed, 24 insertions(+), 4 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 7de64dde..db1a0775 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -13,6 +13,8 @@ This repo contains **the machine** (generator, templates, binary, skills) that p
 
 **When iterating on a printed CLI to discover issues**, note which problems are systemic (retro findings) vs specific. The retro → plan → implement loop exists to feed discoveries from individual CLIs back into the machine.
 
+**When adding a capability that affects scoring**, update the scorer in the same change. The goal is not to inflate scores — it's to ensure the scorer accurately reflects the capability. If you add composed cookie auth but the scorer only recognizes Bearer/Basic, it will penalize a correctly-implemented CLI. Fix the scorer to recognize the new pattern, not to give it a free pass.
+
 ## Build, Test & Lint
 
 ```bash
diff --git a/internal/pipeline/scorecard.go b/internal/pipeline/scorecard.go
index c9382b98..82181b15 100644
--- a/internal/pipeline/scorecard.go
+++ b/internal/pipeline/scorecard.go
@@ -1056,6 +1056,7 @@ func evaluateAuthProtocol(dir string, spec *openAPISpecInfo) dimensionScore {
 
 	clientContent := readFileContent(filepath.Join(dir, "internal", "client", "client.go"))
 	configContent := readFileContent(filepath.Join(dir, "internal", "config", "config.go"))
+	authContent := readFileContent(filepath.Join(dir, "internal", "cli", "auth.go"))
 	if clientContent == "" {
 		return dimensionScore{scored: true}
 	}
@@ -1070,7 +1071,7 @@ func evaluateAuthProtocol(dir string, spec *openAPISpecInfo) dimensionScore {
 		bestScore := -1
 		scoreable := false
 		for _, alternative := range requirementSet.Alternatives {
-			score, ok := scoreAuthAlternative(clientContent, configContent, spec.SecuritySchemes, alternative)
+			score, ok := scoreAuthAlternative(clientContent, configContent, authContent, spec.SecuritySchemes, alternative)
 			if !ok {
 				continue
 			}
@@ -1141,7 +1142,7 @@ func parseSecurityRequirementSet(value any) (securityRequirementSet, bool) {
 	return set, true
 }
 
-func scoreAuthAlternative(clientContent, configContent string, schemes map[string]openAPISecurityScheme, alternative []string) (int, bool) {
+func scoreAuthAlternative(clientContent, configContent, authContent string, schemes map[string]openAPISecurityScheme, alternative []string) (int, bool) {
 	if len(alternative) == 0 {
 		return 0, false
 	}
@@ -1153,7 +1154,7 @@ func scoreAuthAlternative(clientContent, configContent string, schemes map[strin
 		if !ok {
 			continue
 		}
-		score, scoreable := scoreAuthScheme(clientContent, configContent, scheme)
+		score, scoreable := scoreAuthScheme(clientContent, configContent, authContent, scheme)
 		if !scoreable {
 			continue
 		}
@@ -1166,7 +1167,7 @@ func scoreAuthAlternative(clientContent, configContent string, schemes map[strin
 	return total / scoreableSchemes, true
 }
 
-func scoreAuthScheme(clientContent, configContent string, scheme openAPISecurityScheme) (int, bool) {
+func scoreAuthScheme(clientContent, configContent, authContent string, scheme openAPISecurityScheme) (int, bool) {
 	nameLower := strings.ToLower(scheme.Key)
 	headerName := "Authorization"
 	authHeaderMatched := false
@@ -1197,6 +1198,15 @@ func scoreAuthScheme(clientContent, configContent string, scheme openAPISecurity
 		}
 	case strings.EqualFold(scheme.Type, "apikey"):
 		scoreable = true
+		// For apiKey schemes, the header value format varies (Bearer, Bot, custom).
+		// Credit authHeaderMatched if the client sets the correct header name,
+		// since that proves the auth plumbing is wired correctly regardless of format.
+		if scheme.In == "header" && headerName != "" {
+			if strings.Contains(clientContent, `Header.Set("`+headerName+`"`) ||
+				strings.Contains(clientContent, `Header.Add("`+headerName+`"`) {
+				authHeaderMatched = true
+			}
+		}
 	case strings.EqualFold(scheme.Type, "oauth2"), strings.EqualFold(scheme.Type, "openidconnect"):
 		scoreable = true
 		if strings.Contains(clientContent, `"Bearer "`) || strings.Contains(clientContent, "`Bearer `") {
@@ -1220,6 +1230,14 @@ func scoreAuthScheme(clientContent, configContent string, scheme openAPISecurity
 	if envNeedle != "" && strings.Contains(strings.ToUpper(configContent), envNeedle) {
 		envMatched = true
 	}
+	// Browser cookie auth (composed or cookie type) uses Chrome cookie extraction
+	// instead of env vars. Credit envMatched if the auth code has cookie tooling.
+	if !envMatched && (strings.Contains(authContent, "detectCookieTool") ||
+		strings.Contains(authContent, "--chrome") ||
+		strings.Contains(configContent, "chrome-composed") ||
+		strings.Contains(configContent, `"browser"`)) {
+		envMatched = true
+	}
 
 	score := 0
 	if authHeaderMatched {

← 6d2d059d feat(cli): browser auth, composed cookies, smart output, and  ·  back to Cli Printing Press  ·  fix(skills): publish skill supports fork-based PRs for exter 5026f516 →