Safeguard
Application Security•

ASVS 5.0 V16.5.2: Stay Secure When External Resources Fail

ASVS V16.5.2 asks the app to keep operating securely when a dependency fails, using circuit breakers or graceful degradation. Patterns, code and tests.

Safeguard Security Team
5 min read

ASVS 5.0 V16.5.2 requires that the application continues to operate securely when access to an external resource fails. Circuit breakers and graceful degradation are the patterns the standard names. It is a Level 2 requirement.

Two words matter: continues and securely. A service that falls over because a feature flag server is slow has an availability problem. A service that skips authorization because the policy service timed out has a security problem. This requirement covers both.

Why it matters

Every external call is a place where the network can fail: identity providers, payment APIs, policy engines, geo lookups, fraud scoring. Without limits, a slow dependency ties up threads, exhausts connection pools and takes the whole application down. An attacker who can degrade one dependency can degrade everything that waits on it.

The second risk is the fallback. Teams add "if the call fails, allow" to keep the page loading. That turns an outage into a bypass.

How to implement it

Set timeouts on every outbound call. Add a circuit breaker so a failing dependency is skipped quickly. Decide up front, per dependency, what the safe fallback is.

  • Security decisions (authentication, authorization, fraud blocks on money movement): fail closed. Deny, or serve a cached decision only if its freshness rule is documented.
  • Non security enrichment (recommendations, avatars): degrade by hiding the feature.

A minimal breaker in Python:

import time

class Breaker:
    def __init__(self, max_fails=5, reset_after=30):
        self.max_fails, self.reset_after = max_fails, reset_after
        self.fails, self.opened_at = 0, None

    def call(self, fn, fallback):
        if self.opened_at and time.monotonic() - self.opened_at < self.reset_after:
            return fallback()
        try:
            result = fn()
            self.fails, self.opened_at = 0, None
            return result
        except Exception:
            self.fails += 1
            if self.fails >= self.max_fails:
                self.opened_at = time.monotonic()
            return fallback()

def deny():
    return {"allowed": False}

def check_policy(user, action):
    return policy_breaker.call(lambda: policy_client.check(user, action, timeout=1.5), deny)

In production use a maintained library such as Resilience4j for Java or Polly for .NET instead of writing your own. Also bound retries with backoff and jitter, and cap concurrency per dependency with a bulkhead.

How to test it

Run fault injection in a test environment:

  1. Block the dependency with a firewall rule or a proxy such as Toxiproxy, and confirm requests return within the timeout.
  2. Add 10 seconds of latency and confirm the app stays responsive for unrelated routes.
  3. Take the policy or identity dependency down and confirm protected actions are denied, not allowed.
  4. Restore it and confirm the breaker closes and normal service resumes.

Record which fallback each dependency uses in a short table the team reviews.

Choosing a fallback for each dependency

A table works better than a principle. List every external dependency, then write what the application does when it is slow, down, or returning garbage. Example rows: identity provider down means existing sessions continue until expiry but new logins show a maintenance message. Policy engine down means deny sensitive actions and allow read only views from a short lived cache. Email provider down means queue and retry, with no user facing failure. Fraud service down means route payments above a threshold to manual review.

Each row should say whether the fallback is secure, who approved it, and how it is tested. This turns an abstract requirement into something reviewable.

Timeouts, retries and queues

Timeouts should be tight and tied to a budget: if the user waits at most three seconds, the sum of all downstream timeouts must fit inside it. Retries must use exponential backoff with jitter and a retry budget, such as no more than ten percent extra requests, or you create a storm when the dependency is already struggling. Idempotency keys make retries safe for writes.

Use queues for work that does not need an immediate answer. A broker absorbs bursts and outages, and the application keeps accepting requests. Bound queue length and age so a long outage does not turn into a flood on recovery.

Observability

Emit metrics for breaker state, timeout counts and fallback usage, and alert when a fallback is active longer than expected. A degraded mode that nobody notices for a week is a hidden risk, especially if the fallback is less secure than the normal path.

Common mistakes

  • No timeout, so the default is infinite or minutes.
  • Retrying instantly in a loop, which multiplies load on a struggling service.
  • A catch block that returns "allowed" or an empty permission list that is treated as full access.
  • Fallbacks that were never tested and break when finally used.

An example of getting it wrong

A checkout service calls a tax calculation API. The code wraps the call in a try block and, on any failure, falls back to a tax rate of zero so the order can complete. In an outage the company undercharges every customer, and an attacker who can trigger timeouts picks the cheap path on demand. The better design queues the order for later tax finalization, or shows a retry message, because correctness of money is part of secure operation.

Each fallback deserves the same question: what could someone gain if they could force it to run? Put that question in the design review template, and require an answer for every external call that sits on a security or financial decision path.

Quick check

The authorization service times out. Allow or deny?

Deny. Availability of non critical features can degrade; access decisions must fail closed.

Sources

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.

Self-healing security runs on Safeguard.

Your first fix PR is minutes away.

No sales call required, even your agent can complete the purchase over MCP.