{"record":{"id":"9ddcfc3e9ef73175","repo":"JuliusBrussee/caveman","slug":"invalid-claude-organization-id","errorCode":null,"errorMessage":"invalid Claude organization id","messagePattern":"invalid Claude organization id","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/usage_import.go","lineNumber":134,"sourceCode":"\tif err := json.Unmarshal([]byte(raw), &v); err != nil {\n\t\treturn ImportSummary{}, fmt.Errorf(\"parse Claude usage JSON: %w\", err)\n\t}\n\tquotas := quotaEventsFromAny(\"anthropic\", \"claude_usage_link\", \"linked_api\", time.Now().UTC().Format(time.RFC3339), v)\n\tn, err := s.InsertQuotaEvents(quotas)\n\tif err != nil {\n\t\treturn ImportSummary{}, err\n\t}\n\treturn ImportSummary{Source: \"claude\", QuotaImported: n, Basis: \"linked_api\"}, nil\n}\n\nfunc fetchClaudeUsageJSON() ([]byte, error) {\n\tsessionKey := os.Getenv(\"CAVEMAN_CLAUDE_SESSION_KEY\")\n\torgID := os.Getenv(\"CAVEMAN_CLAUDE_ORG_ID\")\n\tif sessionKey == \"\" || orgID == \"\" {\n\t\treturn nil, fmt.Errorf(\"Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVEMAN_CLAUDE_SESSION_KEY plus CAVEMAN_CLAUDE_ORG_ID\")\n\t}\n\tif strings.Contains(orgID, \"/\") || strings.Contains(orgID, \"..\") {\n\t\treturn nil, fmt.Errorf(\"invalid Claude organization id\")\n\t}\n\treq, err := http.NewRequest(\"GET\", \"https://claude.ai/api/organizations/\"+orgID+\"/usage\", nil)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treq.Header.Set(\"accept\", \"application/json\")\n\treq.Header.Set(\"cookie\", \"sessionKey=\"+sessionKey)\n\tclient := http.Client{Timeout: 15 * time.Second}\n\tresp, err := client.Do(req)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer resp.Body.Close()\n\tif resp.StatusCode < 200 || resp.StatusCode >= 300 {\n\t\treturn nil, fmt.Errorf(\"Claude usage request failed with HTTP %d\", resp.StatusCode)\n\t}\n\treturn io.ReadAll(io.LimitReader(resp.Body, 4<<20))\n}","sourceCodeStart":116,"sourceCodeEnd":152,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/usage_import.go#L116-L152","documentation":"The org id is interpolated directly into the request path https://claude.ai/api/organizations/<orgID>/usage. Because it builds a URL from untrusted env input, values containing '/' or '..' are rejected — they could escape the intended path segment (path traversal / hitting a different endpoint). This is a validation guard, not a formatting nicety.","triggerScenarios":"Setting CAVEMAN_CLAUDE_ORG_ID to a URL, a path fragment ('orgs/123'), a value with a trailing slash, or a copied string that includes '../' — for example pasting the whole organizations URL instead of just the uuid.","commonSituations":"Pasting 'https://claude.ai/settings/orgs/<uuid>' or '<uuid>/' from the browser address bar instead of the bare uuid; env files that append stray characters; attempting to target a nested API path via the org variable.","solutions":["Set CAVEMAN_CLAUDE_ORG_ID to the bare organization uuid with no scheme, slashes, or dots: export CAVEMAN_CLAUDE_ORG_ID=0f1e2d3c-....","Strip accidental slashes before export: CAVEMAN_CLAUDE_ORG_ID=${CAVEMAN_CLAUDE_ORG_ID#/}; verify with echo.","If you only have the full URL, extract the uuid segment (e.g. with parameter expansion or sed) and export just that."],"exampleFix":"# before\nexport CAVEMAN_CLAUDE_ORG_ID=\"https://claude.ai/api/organizations/0f1e.../usage\"\n\n# after\nexport CAVEMAN_CLAUDE_ORG_ID=\"0f1e2d3c-4b5a-6978-8796-a5b4c3d2e1f0\"","handlingStrategy":"validation","validationCode":"orgID := os.Getenv(\"CAVEMAN_CLAUDE_ORG_ID\")\nif strings.Contains(orgID, \"/\") || strings.Contains(orgID, \"..\") || orgID == \"\" {\n    return fmt.Errorf(\"CAVEMAN_CLAUDE_ORG_ID must be a bare org uuid, got %q\", orgID)\n}","typeGuard":"func isBareOrgID(v string) bool {\n    return v != \"\" && !strings.Contains(v, \"/\") && !strings.Contains(v, \"..\")\n}","tryCatchPattern":"if _, err := s.RefreshClaudeUsageFromEnv(); err != nil {\n    if strings.Contains(err.Error(), \"invalid Claude organization id\") {\n        return configError(\"CAVEMAN_CLAUDE_ORG_ID must be the bare uuid — no URL, no slashes\")\n    }\n    return err\n}","preventionTips":["Export only the uuid segment, never a pasted URL.","Strip trailing slashes in env files.","Add a startup assertion rejecting '/' and '..' in the org id."],"tags":["go","security","path-traversal","validation","usage-import"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}