{"record":{"id":"b159fb2e3008cfb8","repo":"apereo/cas","slug":"throttled-submission-remains-throttled-submi","errorCode":null,"errorMessage":"Throttled submission [{}] remains throttled; submission expires at [{}]","messagePattern":"Throttled submission \\[(.+?)\\] remains throttled; submission expires at \\[(.+?)\\]","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"support/cas-server-support-throttle-core/src/main/java/org/apereo/cas/throttle/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java","lineNumber":65,"sourceCode":"            .clientIpAddress(ClientInfoHolder.getClientInfo().getClientIpAddress())\n            .build();\n        LOGGER.debug(\"Recording submission failure entry [{}]\", submission);\n        getConfigurationContext().getThrottledSubmissionStore().put(submission);\n        throttledSubmissionReceivers.forEach(Unchecked.consumer(receiver -> receiver.receive(submission)));\n        LOGGER.info(\"Recorded submission failure [{}] for [{}]\", submission, key);\n    }\n\n    @Override\n    public boolean exceedsThreshold(final HttpServletRequest request) {\n        val key = constructKey(request);\n        LOGGER.trace(\"Throttling threshold key is [{}] with calculated threshold [{}]\", key, getThresholdRate());\n        val store = getConfigurationContext().getThrottledSubmissionStore();\n\n        if (store.contains(key)) {\n            val submission = store.get(key);\n            LOGGER.trace(\"Found existing throttled submission [{}] for key [{}]\", submission, key);\n            if (!Objects.requireNonNull(submission).hasExpiredAlready()) {\n                LOGGER.warn(\"Throttled submission [{}] remains throttled; submission expires at [{}]\", key, submission.getExpiration());\n                request.setAttribute(ThrottledSubmission.class.getSimpleName(), submission);\n                return true;\n            }\n        }\n        if (store.exceedsThreshold(key, getThresholdRate())) {\n            val submission = store.get(key);\n            request.setAttribute(ThrottledSubmission.class.getSimpleName(), submission);\n            return true;\n        }\n        return false;\n    }\n\n    @Override\n    public void release() {\n        try {\n            LOGGER.debug(\"Beginning audit cleanup...\");\n            getConfigurationContext().getThrottledSubmissionStore().release(getThresholdRate());\n        } finally {","sourceCodeStart":47,"sourceCodeEnd":83,"githubUrl":"https://github.com/apereo/cas/blob/e7288fc434b4f4505b8452e1a57e8fb3111bb863/support/cas-server-support-throttle-core/src/main/java/org/apereo/cas/throttle/AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.java#L47-L83","documentation":"AbstractInMemoryThrottledSubmissionHandlerInterceptorAdapter.exceedsThreshold checks the in-memory throttle store for an existing entry for the failure key. If a record exists and has not expired, the request is still considered throttled; this warning reports the key and expiration time, and the request attribute ThrottledSubmission is set before the request is rejected.","triggerScenarios":"A client whose identifier (username/IP) was previously recorded as a failed authentication attempt submits again before the throttle interval defined by the failure threshold rate has elapsed.","commonSituations":"User repeatedly failing password attempts and being locked out; an automated client (script, monitoring probe) hammering the login endpoint from one IP; stale throttle entries after clock/config changes to threshold rate.","solutions":["Wait until the reported expiration time passes; the in-memory store expires entries automatically.","Restart CAS or clear the in-memory throttle store to immediately lift the block (dev/testing).","Adjust cas.authn.throttle.failure.threshold-rate / range to a less aggressive policy.","Allow-list or bypass the offending trusted client/IP if it is a legitimate automated caller."],"exampleFix":"// before (too aggressive)\ncas.authn.throttle.failure.threshold=3\ncas.authn.throttle.failure.range-seconds=3600\n// after\ncas.authn.throttle.failure.threshold=10\ncas.authn.throttle.failure.range-seconds=60","handlingStrategy":"fallback","validationCode":"// client-side: back off until expiration before retrying\nif (response.containsHeader(\"Retry-After\")) {\n    long waitSecs = Long.parseLong(response.getFirstHeader(\"Retry-After\").getValue());\n    Thread.sleep(TimeUnit.SECONDS.toMillis(waitSecs));\n}","typeGuard":null,"tryCatchPattern":"// treat HTTP 429 / throttled response as transient\ntry {\n    return authenticate(credentials);\n} catch (ThrottledSubmissionException e) {\n    LOGGER.warn(\"Throttled until {}\", e.getExpiration());\n    return AuthenticationResult.throttled(e.getExpiration());\n}","preventionTips":["Implement exponential backoff in clients after failed authentication.","Alert on repeated 'remains throttled' warnings per IP to detect attacks.","Size threshold/range settings against realistic user failure rates."],"tags":["throttling","rate-limiting","authentication","brute-force"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"e7288fc434b4f4505b8452e1a57e8fb3111bb863","analyzedAt":"2026-09-08T15:39:16.015Z","contentChangedAt":"2026-09-08T15:39:16.015Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}