apereo/cas · warning

Primary ticket-granting ticket expiration policy is set to…

Error message

Primary ticket-granting ticket expiration policy is set to NEVER expire tickets.

What it means

When building the TGT expiration policy from cas.ticket.tgt.* properties, TicketGrantingTicketExpirationPolicyBuilder checks Beans.isInfinitelyDurable on both primary maxTimeToLiveInSeconds and timeToKillInSeconds. If both are effectively infinite (e.g. PT-forever sentinel values), it warns that tickets will NEVER expire and returns NeverExpiresExpirationPolicy.INSTANCE. This is a deliberate configuration warning about a security-sensitive setup.

Solutions

  1. Set finite values for cas.ticket.tgt.primary.max-time-to-live-in-seconds and time-to-kill-in-seconds (e.g. 28800 / 3600).
  2. If never-expiring TGTs are truly intended, accept the warning and ensure explicit cleanup (eviction/TTL at the ticket registry level, e.g. Redis/TTL sweeps) exists.
  3. Verify you did not accidentally use the 'forever' sentinel meant for other settings.
  4. Review remember-me vs primary settings; use toRememberMeTicketExpirationPolicy semantics rather than infinite primary TGTs.

Example fix

// before
cas.ticket.tgt.primary.max-time-to-live-in-seconds=-1
cas.ticket.tgt.primary.time-to-kill-in-seconds=-1
// after
cas.ticket.tgt.primary.max-time-to-live-in-seconds=28800
cas.ticket.tgt.primary.time-to-kill-in-seconds=14400
Defensive patterns

Strategy: validation

Validate before calling

if (cas.ticket.tgt.primary.maxTimeToLiveInSeconds <= 0 || cas.ticket.tgt.primary.timeToKillInSeconds <= 0) { fail("TGT expiration values must be positive finite seconds"); }

Prevention

When it happens

Trigger: Setting both cas.ticket.tgt.primary.max-time-to-live-in-seconds and cas.ticket.tgt.primary.time-to-kill-in-seconds to an 'infinite' value (Beans.isInfinitelyDurable, e.g. -1 or the forever sentinel) so toTicketGrantingTicketExpirationPolicy() builds NeverExpiresExpirationPolicy.

Common situations: Copy-pasted 'make sessions last forever' configuration; sample configs with -1 values left in production; operators surprised that logout alone no longer cleans stale TGTs, leading to unbounded ticket-registry growth.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of apereo/cas@e7288fc434 (2026-09-08). Data as JSON: /api/errors/7f0cc8950d48f4d0. Report an issue: GitHub.

Appendix: source

Thrown at core/cas-server-core-tickets-api/src/main/java/org/apereo/cas/ticket/expiration/builder/TicketGrantingTicketExpirationPolicyBuilder.java:72

     * @return the expiration policy
     */
    public ExpirationPolicy toRememberMeTicketExpirationPolicy() {
        val tgt = casProperties.getTicket().getTgt();
        val timeToKillInSeconds = Beans.newDuration(tgt.getRememberMe().getTimeToKillInSeconds()).toSeconds();
        LOGGER.debug("Remember me expiration policy is being configured based on hard timeout of [{}] seconds", timeToKillInSeconds);
        val rememberMePolicy = new HardTimeoutExpirationPolicy(timeToKillInSeconds);
        val policy = new RememberMeDelegatingExpirationPolicy();
        policy.addPolicy(RememberMeDelegatingExpirationPolicy.POLICY_NAME_REMEMBER_ME, rememberMePolicy);
        policy.addPolicy(BaseDelegatingExpirationPolicy.POLICY_NAME_DEFAULT, toTicketGrantingTicketExpirationPolicy());
        return policy;
    }

    private ExpirationPolicy toTicketGrantingTicketExpirationPolicy() {
        val tgt = casProperties.getTicket().getTgt();

        if (Beans.isInfinitelyDurable(tgt.getPrimary().getMaxTimeToLiveInSeconds())
            && Beans.isInfinitelyDurable(tgt.getPrimary().getTimeToKillInSeconds())) {
            LOGGER.warn("Primary ticket-granting ticket expiration policy is set to NEVER expire tickets.");
            return NeverExpiresExpirationPolicy.INSTANCE;
        }
        if (Beans.isNeverDurable(tgt.getPrimary().getMaxTimeToLiveInSeconds())
            && Beans.isNeverDurable(tgt.getPrimary().getTimeToKillInSeconds())) {
            LOGGER.warn("Ticket-granting ticket expiration policy is set to ALWAYS expire tickets.");
            return AlwaysExpiresExpirationPolicy.INSTANCE;
        }

        if (StringUtils.isNotBlank(tgt.getTimeout().getMaxTimeToLiveInSeconds())) {
            val seconds = Beans.newDuration(tgt.getTimeout().getMaxTimeToLiveInSeconds()).toSeconds();
            LOGGER.debug("Ticket-granting ticket expiration policy is based on a timeout of [{}] seconds", seconds);
            return new TimeoutExpirationPolicy(seconds);
        }

        if (StringUtils.isNotBlank(tgt.getThrottledTimeout().getTimeInBetweenUsesInSeconds())
            && StringUtils.isNotBlank(tgt.getThrottledTimeout().getTimeToKillInSeconds())) {
            val policy = new ThrottledUseAndTimeoutExpirationPolicy();
            val seconds = Beans.newDuration(tgt.getThrottledTimeout().getTimeToKillInSeconds()).toSeconds();

View on GitHub (pinned to e7288fc434)