--- name: credential-management description: "Centralized credential management for 9Webs automation scripts — single .env file, Python loader, migration pattern. No more hardcoded passwords in scripts." version: 1.0.0 author: Hermes Agent license: MIT metadata: hermes: tags: [credentials, security, secrets, env, dotenv] related_skills: [miaoshou-erp, atomk-platform, tongtool-workflows] --- # Credential Management Centralized credential infrastructure for all 9Webs automation scripts. Scripts read credentials from a single `~/.hermes/credentials.env` file via a Python loader module — no hardcoded passwords anywhere. ## When to use - Writing a new automation script that needs AtomK, Ozon, Gitea, Miaoshou, or email credentials - Migrating an existing hardcoded-credential script to the centralized pattern - Rotating credentials across all scripts at once - Debugging a `KeyError` from `atomk_credentials.get_credential()` ## Architecture ``` ~/.hermes/ ├── credentials.env # Single source of truth (chmod 600, never committed) └── scripts/ └── atomk_credentials.py # Python loader with typed helpers ``` ### credentials.env format ``` ATOMLISTING_USERNAME=admincao ATOMLISTING_PASSWORD=actual-value-here ATOMLISTING_API_BASE=https://www.atomlisting.com MIAOSHOU_USERNAME=... MIAOSHOU_PASSWORD=... OZON1_EMAIL=... OZON1_PASSWORD=... GITEA_USERNAME=admin9webs GITEA_PASSWORD=... ``` **Rules:** - `chmod 600` — owner read/write only - Never committed to Git (add to `.gitignore`) - `FILL_ME` placeholder means the value is not yet set - Override path with `ATOMK_CREDENTIALS_FILE` env var ### Python loader API ```python import sys, os sys.path.insert(0, os.path.expanduser("~/.hermes/scripts")) from atomk_credentials import ( get_credential, # key → value get_atomk_auth, # → (username, password) get_miaoshou_auth, # → (username, password) get_ozon_auth, # → (email, password) — store=1 or 2 get_gitea_auth, # → (username, password) ) ``` The loader caches on first access. `get_credential()` raises `KeyError` if the value is unset or still `FILL_ME`. ## Migration pattern When moving a script from hardcoded credentials to the loader: **Before (BAD):** ```python ATOMK_USERNAME = "admincao" ATOMK_PASSWORD = "Tt123456!" ``` **After (GOOD):** ```python import sys, os sys.path.insert(0, os.path.expanduser("~/.hermes/scripts")) from atomk_credentials import get_atomk_auth ATOMK_USERNAME, ATOMK_PASSWORD = get_atomk_auth() ``` **Steps for each script:** 1. Add `import sys, os` if not present 2. Add the `sys.path.insert` line before the credential import 3. Import the appropriate helper function 4. Replace hardcoded values with the function call 5. Run `python3 -c "import ast; ast.parse(open('script.py').read())"` to verify syntax 6. Test the script with the credential file populated ## Adding new credentials 1. Add the variable to `~/.hermes/credentials.env` with an initial `FILL_ME` placeholder 2. Add a typed helper function to `atomk_credentials.py` (e.g., `get_new_service_auth()`) 3. Fill in the actual value in `credentials.env` ## Pitfalls 1. **`KeyError` on first run** — the credential file ships with `FILL_ME` placeholders. The user must replace them with real values. 2. **`ModuleNotFoundError: atomk_credentials`** — the script doesn't have `sys.path.insert(0, ...)` before the import. The `scripts/` directory is not on Python's default path. 3. **Syntax error after migration** — verify with `ast.parse()` as shown above. The refactored lines must be valid Python. 4. **Python 3.12+ f-string backslash restriction** — if the script uses backslash-escaped quotes inside f-strings (e.g., `f'{\"embedded\"}'`), extract the replacement string to a variable first. This is a Python version constraint, not credential-specific. 5. **Credentials on command line** — never pass credentials as command-line arguments. They are visible in `ps` output and shell history. Always read from env or file. 6. **MongoDB URI encoding with special chars** — passwords containing `@` (`%40`) and `#` (`%23`) can fail authentication when passed via URI string, even after `quote_plus()`. Workaround: pass `username=` and `password=` directly to `MongoClient()` constructor parameters instead of encoding them into the URI. See `references/mongodb-auth-patterns.md` for detailed examples. 7. **Python venv/PEP 668** — this host enforces externally-managed Python (`--system` flag rejected). Use `python3 -m venv /tmp/venv && /tmp/venv/bin/pip install ...` for one-off tool deps (e.g., `pymongo`). Do not waste time fighting `uv pip install --system`. ## Related credential stores - **Tencent Cloud / tccli:** `~/.tccli/default.credential` (INI format). Separate from `~/.hermes/credentials.env`. Used by `tencent-cloud-operations` skill for TAT, Lighthouse, and CVM API access. Managed independently. - **Production server .env:** `/root/AtomK_Operation_Tools/.env` on SG3 (43.134.190.229). Contains MongoDB, MySQL, API keys, and AI service credentials for the running AtomK_Server backend. - **premiumproducts MongoDB:** `mongodb://root:***@43.134.190.229:27018/premiumproducts?authSource=admin` (password contains `@` and `#`). New database (2026-07) for premium product management. Use direct constructor params — not URI encoding. See `references/mongodb-auth-patterns.md`. ## Security rules 1. Never `print()` or `log()` a credential value 2. Never pass credentials on the command line (visible in `ps`) 3. Never commit `credentials.env` to Git 4. After rotating a password, update `credentials.env` — no script-level changes needed 5. The loader checks for `FILL_ME` and raises `KeyError` rather than silently using a placeholder 6. TAT (Tencent Automation Tools) aggressively masks passwords in remote command output — use `xxd` hex dump or char-by-char `ord()` extraction to read credentials from remote `.env` files ## Support files - `templates/credentials.env` — clean template to copy to `~/.hermes/credentials.env` - `references/audit-findings.md` — 2026-06-22 security audit credential exposure summary - `scripts/scan_hardcoded_secrets.py` — scan a directory for known hardcoded secrets and suspicious patterns