Skip to content
This repository was archived by the owner on Feb 6, 2026. It is now read-only.
This repository was archived by the owner on Feb 6, 2026. It is now read-only.

Standardize credential handling: env vars first, document secure storage options #93

@rianjs

Description

@rianjs

Summary

Standardize credential handling across open-cli-collective CLIs with:

  1. Environment variables as the primary/preferred credential source
  2. Support for shared ATLASSIAN_API_TOKEN env var (same token works for Confluence and Jira)
  3. Documentation for secure token storage methods
  4. Clear documentation of credential resolution order

Current Behavior

Currently, cfl loads config file first, then env vars override (LoadWithEnv in internal/config/config.go):

func LoadWithEnv(path string) (*Config, error) {
    cfg, err := Load(path)  // File first
    // ...
    cfg.LoadFromEnv()       // Env vars override
    return cfg, nil
}

Current env vars: CFL_URL, CFL_EMAIL, CFL_API_TOKEN, CFL_DEFAULT_SPACE

Proposed Changes

1. Change credential resolution order

Check env vars first, fall back to config file:

1. Environment variable (CFL_API_TOKEN)
2. Shared Atlassian env var (ATLASSIAN_API_TOKEN) - NEW
3. Config file (~/.config/cfl/config.yml)

This matches how jira-ticket-cli already works and enables secure credential injection.

2. Add ATLASSIAN_API_TOKEN support

Since Confluence and Jira use the same Atlassian API token, support a shared env var:

func GetAPIToken() string {
    if v := os.Getenv("CFL_API_TOKEN"); v != "" {
        return v
    }
    if v := os.Getenv("ATLASSIAN_API_TOKEN"); v != "" {
        return v
    }
    // Fall back to config file...
}

3. Document secure storage in README

Add a "Secure Token Storage" section:

## Secure Token Storage

Your API token is sensitive. Rather than storing it in a config file, we recommend 
using environment variables with a secret manager:

### 1Password CLI (Recommended)

```bash
# In your .zshrc or .bashrc
export ATLASSIAN_API_TOKEN="$(op read 'op://Vault/Atlassian API Token/password')"

# Or create a wrapper function for lazy loading
cfl() {
  export ATLASSIAN_API_TOKEN="$(op read 'op://Vault/Atlassian API Token/password')"
  command cfl "$@"
}

macOS Keychain

# Store token
security add-generic-password -s "atlassian-api" -a "api_token" -w "your-token-here"

# Retrieve in shell config
export ATLASSIAN_API_TOKEN="$(security find-generic-password -s 'atlassian-api' -a 'api_token' -w)"

Windows Credential Manager

# Store (using PowerShell)
cmdkey /generic:atlassian-api /user:api_token /pass:your-token-here

# Retrieve (requires additional tooling or script)

Credential Resolution Order

cfl checks for credentials in this order:

  1. CFL_API_TOKEN environment variable
  2. ATLASSIAN_API_TOKEN environment variable (shared with jira-ticket-cli)
  3. api_token in config file (~/.config/cfl/config.yml)

Environment variables are preferred as they enable secure secret management
without storing credentials in plaintext files.


### 4. Add `cfl config show` credential source indicator

Show where credentials are coming from:

$ cfl config show
URL: https://mycompany.atlassian.net/wiki
Email: user@example.com
API Token: redacted (source: environment variable)


## Related

- This should be consistent with jira-ticket-cli and other open-cli-collective tools
- jira-ticket-cli already checks env vars first (good pattern to follow)
- Aligns with 12-factor app principles for configuration

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions