fix(security): close INFO ASVS findings — secret echo, TLS floor, mandatory tarball SHA, CORS/Content-Type guards, BUG-17

- V7.1.3: env known-insecure-default error no longer echoes the rejected secret value.
- V9.1.4: syslog-over-TLS forwarder + listener pin minimum_version=TLSv1_2.
- V12.1.2: updater tarball SHA-256 verification is now mandatory and fail-closed —
  /update and /update-self reject a missing digest (400), the executor rejects
  missing/mismatched digests before extract/apply. Every push path supplies it.
- V13.1.4: reject a wildcard '*' in DECNET_CORS_ORIGINS at startup.
- V13.1.5: enforce application/json on JSON write endpoints (415 otherwise),
  exempting multipart upload routes.
- BUG-17: SSE error log records the user uuid, not the resume cursor.

Also completes V2.1.7 consistently: the attacker-injectable PYTEST* env bypass is
replaced with explicit DECNET_TESTING=1 in the three remaining sites
(env.validate_public_binding, config logging, mysql url builder).

Tests added for every fix; unanimous adversarial review (no update-outage risk —
all push paths verified to send the digest).
This commit is contained in:
2026-06-10 13:50:06 -04:00
parent 245975a6dd
commit 337520c7ad
17 changed files with 520 additions and 73 deletions

View File

@@ -58,8 +58,10 @@ def _require_env(name: str) -> str:
return value
if value.lower() in _KNOWN_BAD:
# Do NOT echo the rejected value — that leaks the secret into logs /
# stderr / crash reporters (V7.1.3). Name the variable, not its value.
raise ValueError(
f"Environment variable '{name}' is set to an insecure default ('{value}'). "
f"Environment variable '{name}' is set to a known-insecure default. "
f"Choose a strong, unique value before starting DECNET."
)
_developer = os.environ.get("DECNET_DEVELOPER", "False").lower() == "true"
@@ -275,10 +277,14 @@ def validate_public_binding() -> None:
slip past unmentioned on a public binding.
Called from the FastAPI lifespan so it surfaces at startup, not on
first request. Skipped automatically when running under pytest so
the test suite doesn't have to set five env vars per fixture.
first request. Skipped automatically under the explicit, non-attacker-
injectable DECNET_TESTING=1 flag (set by the test harness) so the test
suite doesn't have to set five env vars per fixture. The old "any PYTEST*
var present" check was fail-open: PYTEST* is an attacker-controllable
namespace, so leaking one into a prod environment silently disabled the
public-binding guard. Fail closed (V2.1.7).
"""
if any(k.startswith("PYTEST") for k in os.environ):
if os.environ.get("DECNET_TESTING") == "1":
return
if DECNET_API_HOST in _LOOPBACK_HOSTS:
return # not exposed; nothing to validate