{"record":{"id":"e4a0e9732492e724","repo":"phacility/phabricator","slug":"duo-api-credential-s-has-no-secret-key","errorCode":null,"errorMessage":"Duo API credential (\"%s\") has no secret key.","messagePattern":"Duo API credential \\(\"(.+?)\"\\) has no secret key\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/auth/factor/PhabricatorDuoAuthFactor.php","lineNumber":742,"sourceCode":"\n    $omnipotent = PhabricatorUser::getOmnipotentUser();\n\n    $credential = id(new PassphraseCredentialQuery())\n      ->setViewer($omnipotent)\n      ->withPHIDs(array($credential_phid))\n      ->needSecrets(true)\n      ->executeOne();\n    if (!$credential) {\n      throw new Exception(\n        pht(\n          'Unable to load Duo API credential (\"%s\").',\n          $credential_phid));\n    }\n\n    $duo_key = $credential->getUsername();\n    $duo_secret = $credential->getSecret();\n    if (!$duo_secret) {\n      throw new Exception(\n        pht(\n          'Duo API credential (\"%s\") has no secret key.',\n          $credential_phid));\n    }\n\n    $duo_host = $provider->getAuthFactorProviderProperty(\n      self::PROP_HOSTNAME);\n    self::requireDuoAPIHostname($duo_host);\n\n    return id(new PhabricatorDuoFuture())\n      ->setIntegrationKey($duo_key)\n      ->setSecretKey($duo_secret)\n      ->setAPIHostname($duo_host)\n      ->setTimeout(10)\n      ->setHTTPMethod('POST');\n  }\n\n  private function getDuoUsername(","sourceCodeStart":724,"sourceCodeEnd":760,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/auth/factor/PhabricatorDuoAuthFactor.php#L724-L760","documentation":"The Passphrase credential referenced by the Duo provider exists, but $credential->getSecret() returns empty: the credential has no usable secret (never set, cleared, or destroyed). Duo's signed API requests require the secret key (S-key), so newDuoFuture() cannot construct PhabricatorDuoFuture and throws immediately.","triggerScenarios":"Creating the provider's credential without a secret value, destroying the secret while keeping the credential row, or pointing the provider at a credential of the wrong shape. Any Duo API call for that provider then throws before any network traffic.","commonSituations":"Admin creates the credential with only the integration key and skips the secret; rotates keys and blanks the old secret; destroys the credential in Passphere/Passphrase UI but leaves the provider pointing at it.","solutions":["Edit the Passphrase credential and set its secret to the Duo S-key.","If the credential is destroyed, create a new credential with both the integration key (username) and secret key, then re-select it on the provider.","Confirm the credential is a password-type credential as expected by the provider form.","Test by issuing a Duo challenge after saving."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Run when saving the provider config.\n$credential = id(new PassphraseCredentialQuery())\n  ->setViewer(PhabricatorUser::getOmnipotentUser())\n  ->withPHIDs(array($phid))\n  ->needSecrets(true)\n  ->executeOne();\n$secret = $credential ? $credential->getSecret() : null;\nif (!$secret || !strlen($secret->openEnvelope())) {\n  // reject the save: Duo requires integration key AND secret key\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always set both integration key (username) and secret key when creating the Duo credential.","Do not destroy credentials still referenced by providers; rotate instead.","After any credential edit, run a Duo challenge to verify end-to-end."],"tags":["duo","passphrase-credential","secret-key","phabricator"],"backgroundTag":"credential-secret-missing","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}