{"record":{"id":"cf6b2f814786db2c","repo":"JuliusBrussee/caveman","slug":"invalid-native-session-identity","errorCode":null,"errorMessage":"invalid native session identity","messagePattern":"invalid native session identity","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/store/store.go","lineNumber":624,"sourceCode":"\t\t\treturn nil, err\n\t\t}\n\t\tout = append(out, item)\n\t}\n\treturn out, rows.Err()\n}\n\n// SessionUsage returns content-blind provider telemetry for one exact correlated\n// native session. Dollar values remain catalog list-price subtotals/inferred\n// standalone savings; neither is a provider invoice or verified savings.\nfunc (s *Store) SessionUsage(sessionID string) (sessionusage.Snapshot, error) {\n\tout := sessionusage.Snapshot{\n\t\tStatus:       \"not_observed\",\n\t\tSessionID:    sessionID,\n\t\tCostBasis:    \"catalog_list_price_subtotal_provider_complete_priced_rows\",\n\t\tSavingsBasis: \"inferred_standalone_not_verified\",\n\t}\n\tif !validEvidenceToken(sessionID, 256) {\n\t\treturn out, fmt.Errorf(\"invalid native session identity\")\n\t}\n\tvar compressionBases, correlationBases string\n\terr := s.db.QueryRow(\n\t\t`SELECT COUNT(*),\n\t\t        COALESCE(SUM(CASE WHEN token_usage_basis = 'provider_complete' THEN 1 ELSE 0 END),0),\n\t\t        COALESCE(SUM(input_tokens),0), COALESCE(SUM(output_tokens),0),\n\t\t        COALESCE(SUM(cached_input_tokens),0), COALESCE(SUM(cache_creation_input_tokens),0),\n\t\t        COALESCE(SUM(reasoning_tokens),0), COALESCE(SUM(total_cost_usd),0),\n\t\t        COALESCE(SUM(savings_usd),0), COALESCE(SUM(compression_tokens_before),0),\n\t\t        COALESCE(SUM(compression_tokens_after),0),\n\t\t        COALESCE(GROUP_CONCAT(DISTINCT NULLIF(compression_token_count_basis,'')),''),\n\t\t        COALESCE(GROUP_CONCAT(DISTINCT NULLIF(session_correlation_basis,'')),'')\n\t\t   FROM requests WHERE session_id = ?`,\n\t\tsessionID,\n\t).Scan(\n\t\t&out.Requests, &out.ProviderCompleteRequests, &out.InputTokens, &out.OutputTokens,\n\t\t&out.CachedInputTokens, &out.CacheCreationInputTokens, &out.ReasoningTokens,\n\t\t&out.CatalogListPriceSubtotalUSD, &out.InferredSavingsUSD,","sourceCodeStart":606,"sourceCodeEnd":642,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/store/store.go#L606-L642","documentation":"Returned by Store.SessionUsage when sessionID fails validEvidenceToken: empty, longer than 256 characters, or containing characters outside [A-Za-z0-9._:-]. Like the evidence query it fails closed — the snapshot comes back with Status 'not_observed' plus the error, and no telemetry is reported for an untrusted identifier.","triggerScenarios":"Calling SessionUsage with a session id containing spaces, slashes, commas, or quotes; an id taken from an HTTP header without sanitization (it may carry CR/LF or unicode); an empty string; a value longer than 256 chars (e.g. an entire token pasted by mistake).","commonSituations":"Correlating native sessions where the id originates from user input or logs and includes quoting/whitespace; copy-paste artifacts (curly quotes, trailing newline); passing a provider request id or other free-form string where the caveman session id is expected.","solutions":["Pass the exact session id the runtime recorded — alphanumeric plus . _ : - only","Trim whitespace and strip surrounding quotes from user-supplied ids before calling","Reject empty and over-long (>256) ids at the caller boundary","Check the error before reading the Snapshot — on failure Status stays 'not_observed' and the numbers are meaningless"],"exampleFix":"// before\nsnap, err := st.SessionUsage(r.URL.Query().Get(\"session\"))\n\n// after\nid := strings.TrimSpace(r.URL.Query().Get(\"session\"))\nif !validSessionID(id) { http.Error(w, \"invalid session id\", 400); return }\nsnap, err := st.SessionUsage(id)\nif err != nil { http.Error(w, err.Error(), 400); return }","handlingStrategy":"type-guard","validationCode":"id = strings.TrimSpace(id)\nid = strings.Trim(id, \"'\\\"\")\nif id == \"\" || len(id) > 256 { /* reject */ }","typeGuard":"func isValidSessionID(s string) bool {\n    if s == \"\" || len(s) > 256 { return false }\n    for _, c := range s {\n        ok := (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || strings.ContainsRune(\"._:-\", c)\n        if !ok { return false }\n    }\n    return true\n}","tryCatchPattern":"snap, err := st.SessionUsage(id)\nif err != nil {\n    if strings.Contains(err.Error(), \"invalid native session identity\") {\n        return fmt.Errorf(\"session id %q contains disallowed characters; use [A-Za-z0-9._:-] only\", id)\n    }\n    return err\n}\nif snap.Status == \"not_observed\" { /* no data, not an error */ }","preventionTips":["Sanitize any user-supplied session id (trim, unquote) at the boundary","Never feed raw HTTP header values into SessionUsage","Treat Status 'not_observed' plus an error as 'reject input', not 'no data'"],"tags":["validation","security","api-misuse","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}