Summary

  • LACNIC’s public PAI client gives permission-cache entries a two-minute duration. A qualifying refresh failure can extend the old entry and return its unchanged authorization data.
  • The supplied malformed-JSON test deliberately expects that continuity. A fresh, successfully decoded denial takes a different path and replaces the cached entry.
  • The source does not establish production adoption, a revoked token remaining usable, or an authorization bypass. It does reveal that a renewed cache timestamp is not necessarily a renewed permission decision.

The second clock

Imagine an application receiving an authorization response at noon. It remembers the response rather than asking the service on every request. A little later it tries to refresh the answer, but the replacement cannot be decoded. The application faces a legitimate choice: interrupt work, or rely briefly on what it already knows.

The consequential detail is what happens to time. If the application treats the old answer as having been touched just now, it may no longer have a convenient way to distinguish a recent decision from a recently renewed container holding an older decision. The two can produce the same return value. They cannot support the same claim about freshness.

That distinction is visible in LACNIC’s public pai-auth-ws-client. The inspection here is pinned to commit b85523718bfdbd834c85add8e06c3b4a1b813e59, observed on the repository’s main branch on 14 September 2026. The repository describes a Java client for PAI authentication, authorization and session-related functions. It is evidence about a published implementation, not a catalogue of which versions LACNIC or its members run. The project description does not settle that deployment question.

What is actually renewed

In PortalWSClient, the permission cache is a static, process-local ConcurrentHashMap, indexed by the token supplied to the method. CACHE_DURATION_MS sets its duration to two minutes. A cache entry holds a TokenData object and a mutable timestamp. Expiry is assessed against that timestamp using the current wall-clock time.

An unexpired entry returns its saved data directly. This method does not fetch another /authorization response on that path. That is the ordinary point of a cache: fewer remote calls and less exposure to brief service interruptions. It is not, by itself, evidence of weak access control.

Once an entry has expired, the method removes it from the map. Crucially, the local variable still refers to the removed entry while the method attempts to obtain and decode a replacement. Removal from the shared map has not erased the old response from that particular execution.

When a new response is successfully decoded, the method creates a new cache entry and returns the new data. This includes decoded negative authorization data. It would be wrong to describe the mechanism as always preferring yesterday’s permission over today’s denial. A legible denial is not the failure demonstrated by the supplied test.

The other branch is an IOException catch. If the method retains a reference to an earlier entry, it calls cached.extend(), places that entry back in the map and returns its saved data. The extension resets the entry’s timestamp to the current time. It does not obtain a new decoded authorization response or create new TokenData for that fallback.

The cache has therefore acquired another two-minute window while the saved permission response has acquired no new decision. Repeated qualifying failures can follow the same renewal path again. In this class there is no separate immutable timestamp for the original successful response, no absolute age ceiling measured from that response, and no extension counter. Those absences describe this implementation only; they do not prove that a consuming application lacks its own limits.

A test that is more useful than an accusation

The included PortalWSClientTest makes the intended continuity unusually clear. In testGetTokenDataReturnsCachedDataWhenRefreshFails, a mocked first response produces authenticated sample data. The test then artificially moves the cache timestamp five minutes into the past. A subsequent mocked response contains malformed JSON. The assertions expect the same authenticated object back and two HTTP executions.

Five minutes here is a value assigned to a test fixture, not five minutes observed on a live system. The test was inspected, not executed for this article. No production endpoint was contacted and no real token was supplied. Nevertheless, the test is strong evidence that returning the earlier object after this sort of refresh failure is deliberate, rather than merely an accidental reading of one branch.

Credit is due to the purpose. A permission service can become temporarily unreadable while a user’s entitlement remains perfectly legitimate. Cutting off every established session immediately may turn a small dependency fault into a much larger operational interruption. Preserving continuity can be a sensible engineering decision. The unresolved question is how long that continuity remains acceptable, and for which operation.

Nor is the fallback wholly silent. The client emits a warning that it is using an extended cache temporarily. Any account that claims there is no warning or no possible operational visibility would omit counterevidence in the same source. What the warning does not do is place the original response age inside the returned permission object.

The caller receives permissions, not their age

TokenData includes authentication status, a token, roles, an error field and ipAllowed. It does not expose a last-successful-response timestamp, a freshness classification or a stale-fallback flag. The caller receiving the same object cannot infer those properties from those fields alone.

A surrounding application may keep its own account of age. It may validate a token’s cryptographic expiry, recheck permissions before sensitive writes, enforce IP restrictions or consult another authoritative service. None of those possibilities is excluded by the client source. Conversely, none can be assumed simply because the object contains a token or an authentication boolean.

Three claims must stay separate. A token can be within its own validity period. A cached response can be within a newly renewed cache window. A role assignment can still be current at the issuer. These are different propositions with potentially different clocks. The source settles the second mechanism; it does not independently prove the first or third in a running application.

There is an additional boundary to “success”. The HTTP helper constructs its client with TrustAllStrategy and NoopHostnameVerifier. Thus a decoded reply in this source is not, merely by virtue of its HTTPS URL, proof that the issuer’s identity was cryptographically verified by this helper. That is a separate, bounded observation, not evidence of interception or the TLS configuration of a live deployment. A design that records freshness should also define what counts as an authenticated successful reply.

Malformed JSON is not every outage

The demonstration concerns a particular refresh path. readUrlToken catches broad exceptions and can return null, while the enclosing fallback catches IOException. A malformed body and a transport helper returning no body should not casually be described as identical failures. This article does not execute those paths or establish that every connection error renews the cache.

That qualification matters commercially as well as technically. “Keeps working whenever authorization is down” would overstate the continuity guarantee. “Every outage retains revoked access” would overstate the alleged risk. Both erase the distinction between what the code catches, what a consuming application checks and what a production environment actually runs.

The defensible conclusion is narrower and more useful: for the supplied malformed-response case, the old object survives a refresh and its cache-touch clock restarts. The implementation permits repetition of that qualifying path without preserving an original-response-age limit in this class. Whether that can affect a real operation depends on deployment, issuer semantics and the caller’s additional controls.

A grace period needs a beginning

A clearer design would preserve two timestamps: when an authenticated authorization response was last successfully decoded, and when a cache entry was last touched. Touching the latter would never silently move the former. The caller could then distinguish fresh, grace-period and unavailable states, rather than receiving an unqualified permission object in all successful-return cases.

An absolute grace horizon would be measured from the successful response, not from the most recent failed attempt. Different operations could have different tolerances. A permitted read during a short disruption is not necessarily a reason to accept a change to an administrator, a delegated account or an irreversible operational instruction. Those are illustrative decision classes, not a finding that any named LACNIC feature uses this client path.

Logs remain useful. They become more useful when an operator can associate a warning with response age and a declared degraded mode without recording raw tokens or personal session material. A resumed fresh response should end the grace state; a fresh decoded denial should remain a denial. The proposals here are editorial recommendations, not a published LACNIC undertaking.

The important engineering question is consequently not whether caching should exist. It is whether a consumer can tell which kind of continuity it is receiving. Two minutes is a cache duration. Without another clock, it is not necessarily the maximum age of the permission decision behind the result.

Sources

The five linked primary sources are the pinned project README, PortalWSClient, TokenData, PortalWSClientTest and PortalHttpClient. They support a source-level assessment only. The published repository was inspected; no test execution, production adoption, compromise, revocation bypass or unauthorized operation has been established.