Back to skill

Security audit

Oura Cli

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent Oura health-data CLI, but it stores sensitive OAuth credentials and health-data access tokens locally with weak protection and has OAuth implementation gaps users should review before installing.

Install only if you are comfortable with a local CLI storing Oura OAuth credentials that can access sensitive health data. Before use, restrict ~/.config/oura-cli and config.json permissions, avoid shared machines, consider token revocation after testing, and be aware that command output may expose health and profile data in terminal logs or shell history.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
internal/config/config.go:45
Finding

OAuth Credentials and Tokens Stored with Unsafe File Permissions

Content
View full analysis

Vulnerability Details

File Location: internal/config/config.go:45-66
Vulnerability Type: Insecure storage of sensitive OAuth credentials
Risk Level: High

Vulnerable Code

go
func SaveConfig(config *Config) error {
    dir, err := GetConfigDir()
    if err != nil {
        return err
    }

    if err := os.MkdirAll(dir, 0755); err != nil {
        return err
    }

    path := filepath.Join(dir, "config.json")
    file, err := os.Create(path)
    if err != nil {
        return err
    }
    defer file.Close()

    return json.NewEncoder(file).Encode(config)
}

The object serialized by this function contains all of the following sensitive values:

go
type Config struct {
    ClientID     string    `json:"client_id"`
    ClientSecret string    `json:"client_secret"`
    AccessToken  string    `json:"access_token"`
    RefreshToken string    `json:"refresh_token"`
    Expiry       time.Time `json:"expiry"`
}

Technical Analysis

os.Create creates a new file with permission mode 0666, modified by the process umask. With a common umask of 0022, the resulting config.json is mode 0644 and therefore readable by other local users. The containing configuration directory is also created with mode 0755.

The file stores the OAuth client secret, bearer access token, and refresh token in plaintext JSON. A refresh token is particularly sensitive because it may permit continued generation of access tokens after the original access token expires.

The network use of these credentials is otherwise consistent with the declared functionality: the tokens are sent to official Oura HTTPS endpoints. The vulnerability is the insufficient protection of the locally persisted credential material.

Attack Path

  1. The victim runs the OAuth authentication command.
  2. The application receives an access token and refresh token from Oura.
  3. SaveConfig seri ...[truncated 1076 chars]
Remediation
View remediation

Remediation Suggestions

  • Create the configuration directory with mode 0700 rather than 0755.

  • Create or truncate the file explicitly with mode 0600:

    go
    if err := os.MkdirAll(dir, 0700); err != nil {
        return err
    }
    if err := os.Chmod(dir, 0700); err != nil {
        return err
    }
    
    file, err := os.OpenFile(
        path,
        os.O_WRONLY|os.O_CREATE|os.O_TRUNC,
        0600,
    )
    if err != nil {
        return err
    }
    if err := file.Chmod(0600); err != nil {
        file.Close()
        return err
    }
    
  • Repair permissions on existing directories and files, since changing only creation modes does not necessarily fix previously created objects.

  • Prefer an operating-system credential manager or keychain for the client secret and refresh token.

  • Use atomic secure writes to prevent partial configuration files while preserving restrictive permissions.

  • Document the sensitivity and exact storage location of the credential file.

  • Recommend revoking and reissuing tokens if an existing configuration file was exposed.

T09 · Insecure Skill Coding Practices

Warning
Location
internal/auth/oauth.go:29
Finding

OAuth Login Does Not Validate the State Parameter

Content
View full analysis

Vulnerability Details

File Location: internal/auth/oauth.go:29-51
Vulnerability Type: OAuth login CSRF and authorization-response substitution
Risk Level: Medium

Vulnerable Code

The authorization request uses a constant state value:

go
url := oauthConfig.AuthCodeURL("state", oauth2.AccessTypeOffline)

The callback obtains and exchanges the authorization code without reading or validating the returned state:

go
http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
    code := r.URL.Query().Get("code")
    if code == "" {
        errChan <- fmt.Errorf("code not found")
        http.Error(w, "Code not found", http.StatusBadRequest)
        return
    }

    token, err := oauthConfig.Exchange(context.Background(), code)
    if err != nil {
        errChan <- err
        http.Error(w, "Failed to exchange token", http.StatusInternalServerError)
        return
    }

    tokenChan <- token
    fmt.Fprintf(w, "Login successful! You can close this window.")
})

Technical Analysis

OAuth state binds an authorization response to the login operation that initiated it. It must be unpredictable, associated with the pending login, and checked before exchanging the authorization code.

This implementation passes the literal string "state" to AuthCodeURL and never validates the callback's state parameter. Consequently, the callback accepts any authorization code that the Oura token endpoint considers valid for the configured client and redirect URI.

The manual fallback also accepts an authorization code without correlating it to the current authorization request. PKCE is not used, so there is no additional per-login verifier binding visible in the reviewed implementation.

Attack Path

  1. The victim starts an OAuth login and leaves the local callback server awaiting a response.
  2. An attacker obtains an authorization response ...[truncated 1358 chars]
Remediation
View remediation

Remediation Suggestions

  • Generate a cryptographically random state value for every login using crypto/rand.
  • Retain the expected state only for the lifetime of the pending authorization.
  • Read r.URL.Query().Get("state") in the callback and reject missing or mismatched values before exchanging the code.
  • Compare state values with subtle.ConstantTimeCompare where practical.
  • Reject OAuth error callbacks explicitly and return a generic browser response without exposing sensitive details.
  • Add PKCE using a fresh verifier and S256 challenge if supported by the OAuth provider and library.
  • Apply equivalent request correlation to the manual fallback, or remove that fallback if it cannot be implemented safely.
  • Ensure only the first valid callback can complete the login and invalidate pending state immediately afterward.

T09 · Insecure Skill Coding Practices

Note
Location
internal/auth/oauth.go:38
Finding

OAuth Callback Server Listens on All Network Interfaces

Content
View full analysis

Vulnerability Details

File Location: internal/auth/oauth.go:38
Vulnerability Type: Unnecessarily exposed local OAuth callback service
Risk Level: Low

Vulnerable Code

go
server := &http.Server{Addr: ":8080"}

Technical Analysis

An address of :8080 causes the HTTP server to listen on all available network interfaces rather than only the loopback interface. This exceeds the minimum network exposure needed for the configured redirect URI:

go
RedirectURL: "http://localhost:8080/callback",

The listener is temporary and exposes only the callback handler, but it is reachable by network peers when host firewall policy permits it. The exposure is more significant because the callback lacks OAuth state validation.

The server also has no explicit read-header, read, write, or idle timeouts. Although its short operational lifetime limits exposure, a remote peer could attempt to occupy connections or race the legitimate callback.

Attack Path

  1. The victim starts OAuth authentication while connected to a shared or untrusted network.
  2. The callback server binds port 8080 on every network interface.
  3. A network peer discovers or connects to the exposed port.
  4. The peer sends callback requests or holds connections open.
  5. The requests may disrupt authentication or race the legitimate authorization response.
  6. In combination with the missing state validation, a peer possessing a suitable authorization code may attempt authorization-response injection.

Exploitation requires network reachability to the host and generally must occur while the temporary authentication server is running.

Impact Assessment

The issue unnecessarily increases the callback service's attack surface. A network attacker may cause authentication failures, nuisance requests, or limited denial of service. In combination with the missing OAuth state check, it can increase the feasibility of callbac ...[truncated 225 chars]

Remediation
View remediation

Remediation Suggestions

  • Bind the callback server explicitly to a loopback address, such as 127.0.0.1:8080.

  • If IPv6 support is required, create controlled loopback listeners rather than binding to all interfaces.

  • Validate OAuth state before exchanging any authorization code.

  • Configure conservative server timeouts:

    go
    server := &http.Server{
        Addr:              "127.0.0.1:8080",
        Handler:           mux,
        ReadHeaderTimeout: 5 * time.Second,
        ReadTimeout:       10 * time.Second,
        WriteTimeout:      10 * time.Second,
        IdleTimeout:       15 * time.Second,
    }
    
  • Use a dedicated http.ServeMux instead of the process-global default handler registry.

  • Stop accepting requests immediately after the first fully validated callback.

  • Consider selecting a random available loopback port if the OAuth provider permits dynamically registered loopback redirect ports.

Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (11)

Credential Access

High
Category
Privilege Escalation
Confidence
92% confidence
Finding

An access token is a bearer credential, so anyone who obtains the locally stored token may be able to query the user's Oura API data without needing the user's password. In this context, the token grants access to sensitive personal and health-related information, which makes accidental disclosure through plaintext config files particularly risky.

Content

Scanner excerpt · README.md (reported line 58)May include surrounding context.

Paste your Client ID and Secret when asked

text

A browser window will open asking you to authorize the application. Once approved, the access token is stored locally in `~/.config/oura-cli/config.json`.

### 2. Retrieve Data

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · README.md (reported line 17)May include surrounding context.

md
1.  **Visit the Developer Console**:
    Go to the [Oura Cloud Developer Portal](https://cloud.ouraring.com/oauth/developer).

2.  **Create a New Application**:
    -   Click on "Create New Application".
    -   **Application Name**: Give it a name (e.g., "My CLI Tool").
    -   **Website URL**: You can use `http://localhost` if you don't have a website.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The README explicitly states that an OAuth access token is stored locally in ~/.config/oura-cli/config.json but does not warn users that this file contains sensitive credentials or recommend protecting it. Because the CLI handles highly personal health data, unprotected local token storage increases the risk of account and data exposure if the host is shared, compromised, or backed up insecurely.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This skill handles highly sensitive health and personal profile data, including sleep, heart rate, blood oxygen, stress, and identity-related profile fields, but provides no privacy warning or consent guidance before retrieval. In an agent setting, that omission can normalize collecting and exposing sensitive biometrics without making the user aware of the sensitivity or encouraging data minimization.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The client secret is read from standard input using normal terminal echo, so anyone observing the screen or terminal recording/logging can capture the credential. In a CLI handling OAuth client credentials, this unnecessarily exposes a sensitive secret during authentication.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The helper emits API responses as formatted JSON to standard output, and the commands use it for personal information, sleep, heart rate, stress, and other sensitive health data. There is no user-facing disclosure that this data will be displayed in the terminal and may be captured by logs, pipes, or shell redirection.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · internal/api/client.go (reported line 14)May include surrounding context.

go
"golang.org/x/oauth2"
)

const BaseURL = "https://api.ouraring.com/v2/usercollection/"

type Client struct {
	httpClient *http.Client

External Transmission

Medium
Category
Data Exfiltration
Confidence
50% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · internal/auth/oauth.go (reported line 15)May include surrounding context.

go
"golang.org/x/oauth2"
)

const BaseURL = "https://api.ouraring.com/v2/usercollection/"

type Client struct {
	httpClient *http.Client

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This Go code file defines and retrieves highly sensitive personal and health information such as email, heart rate, sleep, stress, and cardiovascular metrics via repeated c.get(...) network calls. Although the methods are named clearly for developers, there is no user-facing prompt, logging, comment, or docstring in this file disclosing that these operations send or retrieve sensitive data over the network.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The code persists OAuth access and refresh tokens directly into configuration, which creates a credential-at-rest risk if the config file is stored insecurely, checked into source control, or readable by other local users/processes. In this context the behavior appears functional rather than malicious, but storing long-lived refresh tokens without explicit protection or user warning can lead to account compromise if the file is exposed.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

This code persists highly sensitive OAuth material, including client secrets, access tokens, and refresh tokens, to a JSON file on disk. It creates the configuration directory with mode 0755 and uses os.Create for the file without explicitly restricting permissions, which can expose secrets to other local users depending on the system umask and environment; the code also provides no warning, encryption, or use of an OS credential store.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.