{"record":{"id":"4951887998e8903c","repo":"signalapp/Signal-Server","slug":"operation-requires-unauthenticated-access","errorCode":null,"errorMessage":"Operation requires unauthenticated access","messagePattern":"Operation requires unauthenticated access","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"error","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java","lineNumber":542,"sourceCode":"  }\n\n  private void clearUsernameLink(final UUID accountIdentifier) {\n    updateUsernameLink(accountIdentifier, null, null);\n  }\n\n  private void updateUsernameLink(\n      final UUID accountIdentifier,\n      @Nullable final UUID usernameLinkHandle,\n      @Nullable final byte[] encryptedUsername) {\n    if ((encryptedUsername == null) ^ (usernameLinkHandle == null)) {\n      throw new IllegalStateException(\"Both or neither arguments must be null\");\n    }\n    accounts.update(accountIdentifier, a -> a.setUsernameLinkDetails(usernameLinkHandle, encryptedUsername));\n  }\n\n  private void requireNotAuthenticated(final Optional<AuthenticatedDevice> authenticatedAccount) {\n    if (authenticatedAccount.isPresent()) {\n      throw new BadRequestException(\"Operation requires unauthenticated access\");\n    }\n  }\n}\n","sourceCodeStart":524,"sourceCodeEnd":546,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/AccountController.java#L524-L546","documentation":"requireNotAuthenticated enforces that username-hash lookup, username-link lookup, and account-existence checks are only performed anonymously. If the request carries credentials for an authenticated device, the server throws BadRequestException('Operation requires unauthenticated access') to prevent accounts from probing other usernames while logged in.","triggerScenarios":"Calling GET /v1/accounts/username_hash/{hash}, GET /v1/accounts/username_link/{uuid}, or the account-exists endpoint while including an Authorization header / authenticated device credentials.","commonSituations":"Shared HTTP client that automatically attaches the auth token to every request, SDK wrappers that inject credentials globally, or tests replaying an authenticated session for anonymous lookups.","solutions":["Issue these lookups from an unauthenticated client that does not attach the Authorization header.","Configure the HTTP client to strip credentials for anonymous endpoints.","Use a separate, credential-free client instance for username lookup/account-existence probes."],"exampleFix":"// before\nhttpClient.get(\"/v1/accounts/username_hash/\" + hash, withAuthHeader(token));\n// after\nanonymousHttpClient.get(\"/v1/accounts/username_hash/\" + hash); // no Authorization header","handlingStrategy":"validation","validationCode":"if (request.headers['authorization']) throw new Error('strip Authorization header for anonymous lookups');","typeGuard":"const isAnonymousRequest = (opts) => opts.headers && !('Authorization' in opts.headers);","tryCatchPattern":"try { return await lookupUsernameHash(hash); } catch (e) { if (e.status === 400 && e.message.includes('unauthenticated access')) { return anonymousClient.lookupUsernameHash(hash); } throw e; }","preventionTips":["Use a separate client instance without credential injection for anonymous endpoints","Audit HTTP interceptors that auto-attach auth tokens","Document which endpoints must remain unauthenticated"],"tags":["authentication","bad-request","anonymous"],"backgroundTag":"authentication-required","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}