Skip to content

feat: add encrypted credential store for headless Linux environments - #22

Merged
pentaoa merged 2 commits into
mainfrom
cursor/headless-linux-auth-7c36
Sep 15, 2026
Merged

pentaoa merged 2 commits into
mainfrom
cursor/headless-linux-auth-7c36

Conversation

@pentaoa

@pentaoa pentaoa commented Sep 15, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR enables sustech auth login to persist CAS credentials on Linux environments that do not have a usable desktop Secret Service (headless servers, containers, coding-agent boxes). Previously these hosts would fail with CREDENTIAL_STORE_UNAVAILABLE.

Problem

Current Linux credential storage strictly requires:

  • A desktop D-Bus session (DBUS_SESSION_BUS_ADDRESS)
  • The secret-tool executable from libsecret-tools
  • An unlocked Secret Service collection

Headless Linux hosts (containers, CI environments, remote servers) typically lack these requirements, forcing users to:

  • Re-inject SUSTECH_SID/SUSTECH_PASSWORD for every process
  • Use plaintext --credentials-file
  • Never persist credentials locally

Solution

When Linux Secret Service is unavailable, the CLI now automatically falls back to a new encrypted local file store (linux-encrypted-file backend):

Technical details

  • Encryption: AES-256-GCM (industry-standard authenticated encryption)
  • Key derivation: PBKDF2 with SHA-256, 600,000 iterations (OWASP recommended)
  • Storage location: ~/.config/sustech-cli/encrypted-credentials/ (file mode 0600)
  • Master password: Required on first use; never stored on disk
  • Per-entry security: Each credential uses a unique salt and initialization vector
  • Preference: Secret Service is still preferred when available

Behavior

  1. First login on headless Linux: User prompted for master password to initialize encrypted store
  2. Subsequent commands: Master password provided via prompt or SUSTECH_MASTER_PASSWORD env var
  3. Desktop Linux: Secret Service continues to be used when available (no change)
  4. macOS/Windows: No change, continue using native credential stores

Usage examples

# Interactive: prompted for master password
sustech auth login --sid 12410000 --password-stdin
# (enter password, then enter master password when prompted)

# Non-interactive: set environment variable
export SUSTECH_MASTER_PASSWORD="your-master-password"
sustech auth login --sid 12410000 --password-stdin < password.txt
sustech bb courses

# Alternative: use explicit credentials file (no master password needed)
echo "12410000:password" > credentials.txt
chmod 600 credentials.txt
sustech --credentials-file credentials.txt bb courses

Changes

Core implementation

  • src/core/encrypted-store.ts: New encrypted store implementation
  • src/core/keyring.ts: Updated to fall back to encrypted store when Secret Service unavailable
    • Added linux-encrypted-file backend type
    • Modified resolveLinuxSecretService() to fall back gracefully
    • New resolveLinuxEncryptedFile() function
  • src/core/prompt.ts: Added promptMasterPassword() for secure input
  • src/cli.ts: Updated auth login to prompt for master password when needed
  • src/core/credentials.ts: Updated to pass master password to keyring

Testing

  • src/test/encrypted-store.test.ts: Unit tests for encrypted store operations
  • src/test/keyring.test.ts: Integration tests for Linux fallback behavior
  • All 468 existing tests pass ✅

Documentation

  • docs/AUTHENTICATION.md: Updated platform backends table and added encrypted store details with usage examples
  • docs/ARCHITECTURE.md: Updated to reflect new credential storage strategy
  • CHANGELOG.md: Added release notes for this feature

Migration & backwards compatibility

  • Existing Secret Service users: No change, continues to work as before
  • Existing profiles: Can be migrated to encrypted store by logging in again on headless Linux
  • Environment variables: SUSTECH_SID/SUSTECH_PASSWORD and --credentials-file remain valid overrides
  • No breaking changes: All existing authentication methods continue to work

Security considerations

What's protected

  • Passwords are encrypted at rest using AES-256-GCM
  • Master password is never written to disk
  • Each credential uses unique encryption parameters (salt, IV)
  • File permissions are 0600 (owner read/write only)
  • No silent plaintext fallback
  • Master password can be provided via:
    • Interactive prompt (secure input, not echoed)
    • SUSTECH_MASTER_PASSWORD environment variable (non-interactive use)

What's NOT protected against

  • Master password theft from memory or keylogger
  • Root access to the host
  • Memory dumps while credentials are decrypted
  • User sharing their master password

Comparison to plaintext

This is significantly better than:

  • Plaintext --credentials-file (no encryption)
  • Hardcoded passwords in scripts
  • Unprotected environment variables in shell config

Comparison to Secret Service

Secret Service is still preferred when available because:

  • OS-managed keyring with hardware-backed encryption on some systems
  • Integration with desktop login passwords
  • System-level access control

Testing performed

  • ✅ All 468 existing tests pass
  • ✅ New encrypted store unit tests (store, retrieve, delete, wrong password)
  • ✅ Linux fallback integration tests
  • ✅ Verified file permissions (0600)
  • ✅ Cross-backend migration scenarios
  • ✅ Master password prompt functionality

Checklist

  • Implementation complete
  • Master password prompting added
  • Tests added and passing
  • Documentation updated (AUTHENTICATION.md, ARCHITECTURE.md)
  • CHANGELOG.md updated
  • No breaking changes
  • Backwards compatible with existing profiles
  • Security model documented

Related

Addresses the real-world pain point where agent/CI boxes have Node but no gnome-keyring/Secret Service, yet users want auth login once and then use bb/tis commands without re-injecting env vars every shell.

Open in Web Open in Cursor 

When Linux Secret Service is unavailable (no D-Bus session or secret-tool
missing), auth login now falls back to an encrypted local file store instead
of failing with CREDENTIAL_STORE_UNAVAILABLE.

Changes:
- Add linux-encrypted-file backend using AES-256-GCM encryption
- Use PBKDF2 (600k iterations) to derive keys from master password
- Store encrypted credentials at ~/.config/sustech-cli/encrypted-credentials/
- Maintain existing Secret Service preference when available
- Never store passwords in plaintext
- Add tests for encrypted store operations
- Update docs to reflect new headless Linux credential path

Fixes headless servers, containers, and CI environments where desktop
Secret Service is unavailable but persistent auth is still needed.
Add interactive master password prompting and SUSTECH_MASTER_PASSWORD
environment variable support for the encrypted credential store.

Changes:
- Add promptMasterPassword() function for secure password input
- Update auth login to prompt for master password when needed
- Support SUSTECH_MASTER_PASSWORD env var for non-interactive use
- Update resolveCredentials to pass master password to keyring
- Add documentation for master password usage patterns
- All 468 tests pass
@pentaoa
pentaoa merged commit c5677e7 into main Sep 15, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants