The SECURITY.md contract said "use gopass" but the code only used
os.getenv. The deploy at /srv/pry/ had an .env file with secrets in
it, which violates the SECURITY.md threat model.
New module secrets_backend.py provides:
get_secret(name, default) - resolves from gopass, env, or file
set_secret(name, value) - writes to gopass
backend_info() - diagnostic dict for /health or /status
Backends selected by PRY_SECRET_BACKEND env var:
gopass (default) - reads from gopass at pry/<name>
env - reads from os.environ (PRY_<NAME> or PRY_<name>)
file - reads from PRY_ENV_FILE (default: PRY_DATA_DIR/.env)
auto - tries gopass, falls back to env
Refactored call sites:
auth.py: JWT_SECRET (was: os.getenv + ephemeral random default)
x402.py: X402_WALLET, X402_FACILITATOR_URL (was: os.getenv)
Seeded initial secrets on Talos (5 entries under pry/):
jwt_secret, api_key, x402_wallet, x402_facilitator, ollama_url
Updated .env.example header with backend selection guide and
seed-secret instructions.
Tests: 9/9 in test_secrets_backend.py pass. 36 tests in
test_x402_mcp_spec.py + test_secrets_backend.py all pass.
Verified end-to-end:
>>> import x402
>>> x402.X402_WALLET
'0xYourWalletAddressHere'
>>> import auth
>>> auth.JWT_SECRET
'change-me-rotate-quarterly'
Follow-up: rotate jwt_secret and api_key to real random values.
Document the rotation cadence in SECURITY.md.
Each module did:
X_DIR = Path(os.path.expanduser("~/.pry/x"))
After:
from paths import PRY_DATA_DIR
X_DIR = PRY_DATA_DIR / "x"
The module-level Path construction is preserved, so the rest of the
code is unchanged. PRY_DATA_DIR is read once at import (overridable via
the env var of the same name).
Verified:
- 407 tests collect (was 5 collection errors from a misplaced import)
- 83 sampled tests pass (intelligence, proxy_manager, x402, agency,
gdpr, referrals, marketplace, api)
- 0 remaining hardcoded ~/.pry references in .py files
Follow-up: paths.py adds subdir(name) helper for new code that wants
auto-mkdir; existing modules still call .mkdir(exist_ok=True) themselves
to preserve the eager-init behavior they had before.
The data root was hardcoded as Path(os.path.expanduser("~/.pry")) in
25+ modules, making it impossible to point Pry at a different data
directory (production systemd, Docker volumes, CI scratch, tests).
Changes:
- New module paths.py: single source of truth
PRY_DATA_DIR: Path # read once at import, overridable via env var
subdir(name) -> Path # mkdir+return helper
ensure_data_dir() -> Path # eager init
- 25 modules: replace
X_DIR = Path(os.path.expanduser("~/.pry/x"))
with
X_DIR = PRY_DATA_DIR / "x"
(plus the import; total 53 changes across 26 files)
- .env.example: document PRY_DATA_DIR with examples
- Verified:
- 407 tests collect (was 5 collection errors before fix)
- 83 sampled tests pass
- 0 remaining hardcoded ~/.pry references in py files
The deploy at /srv/pry/ had a thin proxy_referrals.py with 5 curated
proxy provider affiliate entries (Bright Data, Oxylabs, Smartproxy,
IPRoyal, Webshare). The repo was missing this file, so deploy and repo
were out of sync.
Changes:
- Add proxy_referrals.py (MIT) with the 5-provider curated catalog
- proxy_manager.py: import PROVIDER_REFERRALS, add 4 helper methods:
get_proxy_referral(tag)
get_proxy_referral_url(tag)
list_proxy_referrals()
get_proxy_referral_summary() - per-tier breakdown
- api.py: expose 2 new endpoints
GET /v1/proxy/referrals - full catalog + summary
GET /v1/proxy/referrals/{tag} - single provider
- 12/12 existing proxy_manager tests still pass
- Total routes: 195 -> 197
The previous CI had a single build job with mypy marked as
continue-on-error, and no secret scanning or security audit. The
pre-commit config has gitleaks + bandit but they were never enforced
in CI.
Changes:
- Split monolithic build job into focused jobs: lint, typecheck, test,
secrets (gitleaks), security (bandit)
- Promote mypy to a real required job (not continue-on-error)
- Add gitleaks scan (v8.24.0) against the full git history
- Add bandit scan at high+medium severity (low severity is too noisy
for an OSS project)
- Each job has its own container to keep failures isolated
The committed openapi.json was a 293-line sample covering only 11 paths
and 7 tags. The actual FastAPI app exposes 183 paths across 50 tags.
This was making API documentation, SDK generation, and any external
integration effectively guess at the real surface.
Re-generate via:
python3 -c "import json; from api import app; print(json.dumps(app.openapi(), indent=2))" > openapi.json
Going forward, regenerate before each release. Add a Makefile target.
Squashed from chore/license-relicense. Full message preserved in the
original branch commit bb77eb5. See ADR-0002 for the decision rationale.
Refs: ADR-0002, commit bb77eb5
Python CI: uv setup, ruff lint+format, mypy (warn), pytest (non-integration).
Runs on docker-x64 runner on Talos.
55 tests exist, now they will run in CI.
- .forgejo/ISSUE_TEMPLATE/bug.yml
- .forgejo/ISSUE_TEMPLATE/feature.yml
- .forgejo/ISSUE_TEMPLATE/config.yml: blank_issues_enabled=false,
contact_links to discussions (Q&A, Feature Requests, Announcements)
Discussions per repo land in a separate follow-up commit.