{"id":"6f3d95759efb03dd","repo":"aio-libs/aiohttp","slug":"none-is-not-allowed-as-password-value","errorCode":null,"errorMessage":"None is not allowed as password value","messagePattern":"None is not allowed as password value","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"aiohttp/client_middleware_digest_auth.py","lineNumber":206,"sourceCode":"    - RFC 1945: Section 11.1 (username restrictions)\n\n    Implementation notes:\n    The core digest calculation is inspired by the implementation in\n    https://github.com/requests/requests/blob/v2.18.4/requests/auth.py\n    with added support for modern digest auth features and error handling.\n    \"\"\"\n\n    def __init__(\n        self,\n        login: str,\n        password: str,\n        preemptive: bool = True,\n    ) -> None:\n        if login is None:\n            raise ValueError(\"None is not allowed as login value\")\n\n        if password is None:\n            raise ValueError(\"None is not allowed as password value\")\n\n        if \":\" in login:\n            raise ValueError('A \":\" is not allowed in username (RFC 1945#section-11.1)')\n\n        self._login_str: Final[str] = login\n        self._login_bytes: Final[bytes] = login.encode(\"utf-8\")\n        self._password_bytes: Final[bytes] = password.encode(\"utf-8\")\n\n        self._last_nonce_bytes = b\"\"\n        self._nonce_count = 0\n        self._challenge: DigestAuthChallenge = {}\n        self._preemptive: bool = preemptive\n        # Set of URLs defining the protection space\n        self._protection_space: list[str] = []\n        # Origin the credentials are scoped to; set on the first request.\n        self._origin: URL | None = None\n\n    async def _encode(self, method: str, url: URL, body: Payload | Literal[b\"\"]) -> str:","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/aio-libs/aiohttp/blob/c0ef574e29109210e96e652771ae4e7b88615fa4/aiohttp/client_middleware_digest_auth.py#L188-L224","documentation":"Raised in `DigestAuthMiddleware.__init__` (client_middleware_digest_auth.py:205-206) when `password` is None. Like the login check, it precedes the `.encode('utf-8')` at line 213. The password is required material for the digest hash, so None is rejected explicitly rather than crashing on encode.","triggerScenarios":"Constructing `DigestAuthMiddleware(login='u', password=None)`; reading a password from a secret store that returned None; using `getpass.getpass()` in a non-interactive context that returned None.","commonSituations":"Secret manager / vault call returning None on missing key; CI environment without the password env var set; test fixtures that forgot to populate the password.","solutions":["Ensure `password` is a non-None string (empty string `''` is technically allowed since only None is rejected, though a real secret should be non-empty).","Load from env with a guard: `pw = os.environ.get('DIGEST_PASS'); if pw is None: raise ...`.","Use a secret manager that raises on missing keys instead of returning None."],"exampleFix":"// before\nmw = DigestAuthMiddleware(login='u', password=os.environ.get('PW'))\n// after\npw = os.environ.get('DIGEST_PASS')\nif pw is None:\n    raise RuntimeError('DIGEST_PASS must be set')\nmw = DigestAuthMiddleware(login='u', password=pw)","handlingStrategy":"validation","validationCode":"def require_password(pw: str | None) -> str:\n    if pw is None:\n        raise ValueError('password must not be None')\n    return pw","typeGuard":"def is_valid_password(v) -> bool:\n    return isinstance(v, str)","tryCatchPattern":null,"preventionTips":["Load secrets from a store that raises on missing keys rather than returning None.","Fail at startup, not at request time, when credentials are missing.","Keep credentials out of logs (log only presence, not value)."],"tags":["client","digest-auth","authentication","validation","secrets"],"analyzedSha":"c0ef574e29109210e96e652771ae4e7b88615fa4","analyzedAt":"2026-08-04T19:51:05.467Z","schemaVersion":2}