Spaces:
Restarting
Restarting
chore(sync): mirror backend .py + Dockerfile to Space (hf-sync-backend)
Browse filesAutomated backend sync from szl-holdings/a11oy main via hf-sync-backend.
Updated (differed from the Space): szl_connectors/base.py, szl_connectors/oauth.py
Deleted (gone from the repo + Dockerfile COPY set): (none)
Keeps the Space-built backend (serve.py + the Dockerfile-COPY'd .py
modules) identical to GitHub main so the Space never rebuilds from a
stale backend, new endpoints don't 404 there, and orphaned modules
removed from the repo don't linger in the Space tree.
- szl_connectors/base.py +20 -2
- szl_connectors/oauth.py +16 -1
szl_connectors/base.py
CHANGED
|
@@ -154,9 +154,27 @@ def http_text(url: str, headers: dict | None = None, timeout: float = _TIMEOUT)
|
|
| 154 |
return 0, str(e)
|
| 155 |
|
| 156 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
def cred_fingerprint(value: str) -> str:
|
| 158 |
-
"""
|
| 159 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
|
| 162 |
# ── the honesty core — resolve_state() ─────────────────────────────────────
|
|
|
|
| 154 |
return 0, str(e)
|
| 155 |
|
| 156 |
|
| 157 |
+
# Credential fingerprints are derived with PBKDF2-HMAC-SHA256, not a single
|
| 158 |
+
# SHA-256 pass. A bare hash of a (possibly low-entropy) credential is cheap to
|
| 159 |
+
# brute-force / rainbow-table from the fingerprint alone (CodeQL
|
| 160 |
+
# py/weak-sensitive-data-hashing). The fixed domain-separation salt keeps the
|
| 161 |
+
# fingerprint DETERMINISTIC — the same credential always maps to the same
|
| 162 |
+
# fingerprint, which is the whole point of a correlation fingerprint — while the
|
| 163 |
+
# work factor makes recovery expensive. An optional deployment pepper
|
| 164 |
+
# (SZL_FINGERPRINT_PEPPER) adds a keyed secret an attacker cannot precompute.
|
| 165 |
+
_CRED_FP_SALT = b"szl.killinchu.cred-fingerprint.v1"
|
| 166 |
+
_CRED_FP_ITERATIONS = 200_000
|
| 167 |
+
|
| 168 |
+
|
| 169 |
def cred_fingerprint(value: str) -> str:
|
| 170 |
+
"""Brute-force-resistant fingerprint of a credential — what a receipt may
|
| 171 |
+
carry. NEVER the value. PBKDF2-HMAC-SHA256 over a deterministic salt (+ the
|
| 172 |
+
optional SZL_FINGERPRINT_PEPPER) so a low-entropy credential cannot be
|
| 173 |
+
recovered from its fingerprint, yet the same credential always maps to the
|
| 174 |
+
same fingerprint."""
|
| 175 |
+
salt = _CRED_FP_SALT + os.environ.get("SZL_FINGERPRINT_PEPPER", "").encode("utf-8")
|
| 176 |
+
dk = hashlib.pbkdf2_hmac("sha256", value.encode("utf-8"), salt, _CRED_FP_ITERATIONS)
|
| 177 |
+
return "pbkdf2-sha256:" + dk.hex()[:32]
|
| 178 |
|
| 179 |
|
| 180 |
# ── the honesty core — resolve_state() ─────────────────────────────────────
|
szl_connectors/oauth.py
CHANGED
|
@@ -92,11 +92,26 @@ PROVIDER_OAUTH: dict[str, dict[str, str]] = {
|
|
| 92 |
}
|
| 93 |
|
| 94 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 95 |
def _state_secret() -> bytes:
|
| 96 |
# signed-state nonce key: reuse the cosign-adjacent secret if present, else a
|
| 97 |
# per-process ephemeral key (state still verifiable within the process).
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
s = os.environ.get("SZL_OAUTH_STATE_SECRET") or os.environ.get("SZL_COSIGN_PRIVATE_PEM") or "szl-oauth-ephemeral"
|
| 99 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
|
| 101 |
|
| 102 |
def _b64u(b: bytes) -> str:
|
|
|
|
| 92 |
}
|
| 93 |
|
| 94 |
|
| 95 |
+
# Domain-separation salt + work factor for deriving the state-signing key.
|
| 96 |
+
_STATE_KDF_SALT = b"szl.killinchu.oauth-state.v1"
|
| 97 |
+
_STATE_KDF_ITERATIONS = 200_000
|
| 98 |
+
_STATE_KEY_CACHE: dict[str, bytes] = {}
|
| 99 |
+
|
| 100 |
+
|
| 101 |
def _state_secret() -> bytes:
|
| 102 |
# signed-state nonce key: reuse the cosign-adjacent secret if present, else a
|
| 103 |
# per-process ephemeral key (state still verifiable within the process).
|
| 104 |
+
# Stretched with PBKDF2-HMAC-SHA256 rather than a single SHA-256 pass so a
|
| 105 |
+
# low-entropy secret (e.g. the ephemeral fallback) cannot be cheaply
|
| 106 |
+
# brute-forced from a leaked state signature (CodeQL
|
| 107 |
+
# py/weak-sensitive-data-hashing). Cached per secret so the stretch is a
|
| 108 |
+
# one-time cost while still honouring a changed secret.
|
| 109 |
s = os.environ.get("SZL_OAUTH_STATE_SECRET") or os.environ.get("SZL_COSIGN_PRIVATE_PEM") or "szl-oauth-ephemeral"
|
| 110 |
+
key = _STATE_KEY_CACHE.get(s)
|
| 111 |
+
if key is None:
|
| 112 |
+
key = hashlib.pbkdf2_hmac("sha256", s.encode(), _STATE_KDF_SALT, _STATE_KDF_ITERATIONS)
|
| 113 |
+
_STATE_KEY_CACHE[s] = key
|
| 114 |
+
return key
|
| 115 |
|
| 116 |
|
| 117 |
def _b64u(b: bytes) -> str:
|