signalapp/Signal-Server · error · ForbiddenException

must not use authenticated connection for call quality…

Error message

must not use authenticated connection for call quality survey submissions

What it means

CallQualitySurveyController.submitCallQualitySurvey is designed for anonymous submissions only. If the request contains an authenticated device (session/basic auth), the controller throws ForbiddenException (HTTP 403) to guarantee survey submissions cannot be linked to an account.

Solutions

  1. Submit the survey from a connection without any account credentials
  2. Use a separate anonymous HTTP client for survey endpoints
  3. Verify no auth cookies/headers are auto-attached by the transport layer

Example fix

// before
accountClient.post("/v1/call_quality/...", surveyBytes);
// after
newAnonymousClient().post("/v1/call_quality/...", surveyBytes);
Defensive patterns

Strategy: validation

Validate before calling

if (client.hasStoredCredentials()) throw new IllegalStateException("survey submission must be anonymous");

Try / catch

try { submitSurvey(bytes); } catch (ForbiddenException e) { switchToAnonymousClientAndResubmit(); }

Prevention

When it happens

Trigger: POST to the call quality survey endpoint while logged in over the same connection — authenticatedDevice is present.

Common situations: In-app clients reusing the main account's authenticated HTTP client for the survey upload; proxies injecting credentials.

Understand the failure class

Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.

Related errors


AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09). Data as JSON: /api/errors/841473677d535cf8. Report an issue: GitHub.

Appendix: source

Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/CallQualitySurveyController.java:62

  @PUT
  @Consumes(MediaType.APPLICATION_OCTET_STREAM)
  @Produces(MediaType.APPLICATION_JSON)
  @Operation(summary = "Submit survey response", description = "Submits a call quality survey response")
  @ApiResponse(responseCode = "204", description = "The survey response was submitted successfully")
  @ApiResponse(responseCode = "422", description = "The survey response could not be parsed")
  @ApiResponse(responseCode = "429", description = "Too many attempts", headers = @Header(
      name = "Retry-After",
      description = "If present, an positive integer indicating the number of seconds before a subsequent attempt could succeed"))
  @RateLimitedByIp(RateLimiters.For.SUBMIT_CALL_QUALITY_SURVEY)
  public void submitCallQualitySurvey(@Auth final Optional<AuthenticatedDevice> authenticatedDevice,
      @RequestBody(description = "A serialized survey response protobuf entity")
      @NotNull final byte[] surveyResponse,
      @HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString,
      @Context final ContainerRequestContext requestContext) {

    if (authenticatedDevice.isPresent()) {
      throw new ForbiddenException("must not use authenticated connection for call quality survey submissions");
    }

    final SubmitCallQualitySurveyRequest submitCallQualitySurveyRequest;

    try {
      submitCallQualitySurveyRequest = SubmitCallQualitySurveyRequest.parseFrom(surveyResponse);
    } catch (final InvalidProtocolBufferException e) {
      throw new WebApplicationException("Invalid protobuf entity", 422);
    }

    final String remoteAddress = (String) requestContext.getProperty(RemoteAddressFilter.REMOTE_ADDRESS_ATTRIBUTE_NAME);

    try {
      callQualitySurveyManager.submitCallQualitySurvey(submitCallQualitySurveyRequest, remoteAddress, userAgentString);
    } catch (final CallQualityInvalidArgumentsException e) {
      throw new WebApplicationException(e.getMessage(), 422);
    }
  }

View on GitHub (pinned to 100ab61c82)