You've already forked hubmanager
feat: add AES-256-CBC encrypted password storage (v0.2.0)
Add `login --save --encrypt` flag: passwords are encrypted with openssl AES-256-CBC (PBKDF2) and stored as `enc:<base64>` in the config file. A master passphrase is prompted once per session and cached in memory. Both load_config() and resolve_registry_alias() detect the enc: prefix and decrypt transparently. The passphrase is passed to openssl via a temp file to avoid argv/env exposure. openssl is an optional dependency, checked on demand. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
50
CLAUDE.md
50
CLAUDE.md
@@ -10,11 +10,48 @@ authentication detection (bearer token or HTTP basic auth).
|
||||
|
||||
---
|
||||
|
||||
## What Changed (Session 2026-02-21)
|
||||
|
||||
### Encrypted config values (`--encrypt`)
|
||||
|
||||
Added `openssl` AES-256-CBC encryption for passwords stored in the config file.
|
||||
`openssl` is an **optional** dependency — it is only required when `enc:` prefixed values
|
||||
are present in the config, or when `login --encrypt` is used.
|
||||
|
||||
**New functions** (Encryption helpers section):
|
||||
|
||||
| Function | Purpose |
|
||||
| --- | --- |
|
||||
| `_require_openssl()` | Die with a clear message if `openssl` is not installed |
|
||||
| `_prompt_master_pass()` | Prompt once per session via `/dev/tty`; cache in `HM_MASTER_PASS` |
|
||||
| `_prompt_set_master_pass()` | Prompt + confirm a new passphrase (used by `login --encrypt`) |
|
||||
| `_encrypt_value(plaintext)` | AES-256-CBC encrypt → base64 ciphertext (no newlines) |
|
||||
| `_decrypt_value(ciphertext)` | Decrypt base64 ciphertext → plaintext; die on wrong passphrase |
|
||||
|
||||
**Passphrase security**: passed to `openssl` via a `mktemp` file (`-pass file:`) to avoid
|
||||
exposure in the process argument list (`ps aux`). The temp file is registered in
|
||||
`HM_TMPFILES` and removed on exit.
|
||||
|
||||
**Config format**: encrypted values use an `enc:` prefix, e.g.:
|
||||
|
||||
```txt
|
||||
PASSWORD=enc:U2FsdGVkX1+...base64ciphertext...
|
||||
REGISTRY_PROD_PASSWORD=enc:U2FsdGVkX1+...
|
||||
```
|
||||
|
||||
Both `load_config()` and `resolve_registry_alias()` detect the prefix and call
|
||||
`_decrypt_value` transparently.
|
||||
|
||||
**Version bump**: `0.1.0` → `0.2.0`
|
||||
|
||||
---
|
||||
|
||||
## What Was Built (Session 2025-02-21)
|
||||
|
||||
### Primary file
|
||||
|
||||
`hubmanager` — executable Bash script, ~600 lines, no dependencies beyond `curl`, `jq`, Bash 4+.
|
||||
`hubmanager` — executable Bash script, ~680 lines, no mandatory dependencies beyond `curl`, `jq`, Bash 4+.
|
||||
`openssl` is required only when encrypted config values are used.
|
||||
|
||||
### Subcommands implemented
|
||||
|
||||
@@ -47,6 +84,7 @@ set -euo pipefail
|
||||
# --- Global state ---
|
||||
# --- Output / Formatting helpers ---
|
||||
# --- Dependency check ---
|
||||
# --- Encryption helpers --- _encrypt_value(), _decrypt_value(), _prompt_master_pass()
|
||||
# --- Config loading ---
|
||||
# --- HTTP helpers --- raw_http(), get_response_header()
|
||||
# --- Authentication --- probe_registry_auth(), get_bearer_token(), make_auth_header()
|
||||
@@ -96,6 +134,15 @@ main "$@"
|
||||
- Blob already at destination (`HEAD` returns 200) → skip
|
||||
- Otherwise → download to temp file → `POST` initiate upload → `PUT` with digest
|
||||
|
||||
9. **Encrypted config values** (v0.2.0):
|
||||
- `login --save --encrypt` prompts for a master passphrase (with confirmation), encrypts
|
||||
the password with `openssl enc -aes-256-cbc -pbkdf2 -a`, and writes `PASSWORD=enc:<b64>`.
|
||||
- Passphrase is passed to `openssl` via a temp file (`-pass file:`) — never via argv or env.
|
||||
- `load_config` and `resolve_registry_alias` both check for the `enc:` prefix and call
|
||||
`_decrypt_value`, which triggers `_prompt_master_pass` (once per session, cached in
|
||||
`HM_MASTER_PASS`).
|
||||
- `openssl` is an optional dependency: not checked at startup, only on first `enc:` encounter.
|
||||
|
||||
---
|
||||
|
||||
## Global Variables (key ones)
|
||||
@@ -110,6 +157,7 @@ main "$@"
|
||||
| `HM_AUTH_REALM["registry"]` | Bearer token endpoint URL |
|
||||
| `HM_TOKEN_CACHE["registry\|scope"]` | Cached bearer token |
|
||||
| `HM_TOKEN_EXPIRY["registry\|scope"]` | Token expiry (epoch seconds) |
|
||||
| `HM_MASTER_PASS` | Master passphrase for `enc:` config values (session-cached) |
|
||||
| `HM_LAST_HTTP_CODE` | HTTP status of most recent request |
|
||||
| `HM_LAST_HEADERS_FILE` | Temp file path with response headers |
|
||||
| `HM_TMPFILES` | Array of temp files, cleaned up via `trap EXIT` |
|
||||
|
||||
Reference in New Issue
Block a user