When a scoped service is injected into a singleton, the application can lose request isolation and produce runtime errors or unsafe cross-request behaviour. The singleton may retain references to data that should have expired at the end of a request. That creates a pathway for data leakage, privilege confusion, and hard to reproduce defects under concurrency.
Why This Matters for Security Teams
Injecting a scoped service into a singleton looks like a small dependency error, but it can undermine the trust boundary the application relies on to separate one request from another. In practice, the risk is not just a crash. It can expose cached user context, reuse expired credentials, or let authorization state drift beyond the lifetime it was meant to cover. That matters wherever a component handles identity, tokens, tenant context, or per-request security decisions.
This is especially dangerous in systems that mix web requests, background jobs, and asynchronous processing because the singleton often outlives the data it references. Security teams should treat the pattern as a design flaw with operational impact, not a harmless container quirk. The underlying control objective aligns closely with NIST SP 800-53 Rev 5 Security and Privacy Controls, particularly where lifecycle, least privilege, and separation of duties must be preserved across execution contexts. In practice, many security teams encounter the failure only after a concurrency issue or cross-request leak has already exposed inconsistent access decisions.
How It Works in Practice
A singleton is created once and reused for the application lifetime. A scoped service is created per request, per session, or per unit of work. When the container allows a singleton to hold a scoped dependency directly, the singleton may end up preserving a reference that was only valid for one scope. That breaks the assumption that request-bound state should disappear when the scope ends.
The problem often shows up in a few ways:
- The application throws a runtime resolution error because the container blocks an invalid lifetime combination.
- The singleton stores a stale instance and reuses it for later requests.
- Security context, tenant identifiers, or user claims bleed between concurrent operations.
- Background tasks access disposed objects after the original request completes.
That lifecycle mismatch is not limited to classic web apps. It can also affect event handlers, message consumers, and agentic workflows that keep long-lived orchestration components while pulling in per-request secrets or identity data. For identity-heavy systems, this intersects with the OWASP Non-Human Identity Top 10 because long-lived components can accidentally retain tokens, API keys, or service credentials far beyond their intended scope.
Best practice is to keep singleton components stateless or restrict them to singleton-safe dependencies. If a singleton needs scoped data, it should request that data indirectly through a factory, service locator pattern used carefully, or a per-operation abstraction that resolves inside the correct scope. Current guidance suggests also validating thread safety, because even when the dependency does not crash, shared mutable state can still produce race conditions. These controls tend to break down in high-throughput async systems with reused background workers because the original request scope is no longer present when the singleton later dereferences the service.
Common Variations and Edge Cases
Tighter lifetime rules often increase design overhead, requiring organisations to balance developer convenience against request isolation and security correctness. There is no universal standard for this yet across every framework, so implementation details vary, but the principle remains the same: a longer-lived component should not directly own shorter-lived security context.
Edge cases usually appear when the dependency is not obviously stateful. A scoped logger enrichment provider, tenant resolver, or token cache can still carry request-specific context even if the interface looks harmless. Another common exception is lazy resolution, which may seem safer but can still fail if the resolved object is used outside its scope. The safest pattern is to treat anything tied to authentication, authorisation, or request metadata as scope-bound until proven otherwise.
In systems using dependency injection frameworks with automatic validation, the error may surface early in development. In manually wired or loosely governed codebases, the defect can survive to production and manifest as cross-user data exposure or confusing access denials. Where agentic automation is present, the risk expands because a long-lived controller may reuse a short-lived identity token for tool access, creating an identity boundary problem as much as a code-quality problem.
If the application spans web requests, message queues, and scheduled jobs, the guidance becomes harder to apply consistently because each execution model may define scope differently and the same singleton can be invoked under multiple lifetimes.
Standards & Framework Alignment
This section maps relevant standards and security frameworks to the operational risks and controls described in this guidance.
OWASP Non-Human Identity Top 10 address the attack and risk surface, while NIST CSF 2.0 and NIST SP 800-53 Rev 5 set the governance and control requirements practitioners need to meet.
| Framework | Control / Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.AC-4 | Scoped-to-singleton misuse can break least-privilege access boundaries. |
| OWASP Non-Human Identity Top 10 | Long-lived components can retain secrets or tokens beyond their intended scope. | |
| NIST SP 800-53 Rev 5 | SC-4 | Information separation is threatened when singleton state crosses request boundaries. |
Keep access decisions tied to the correct request scope and prevent shared components from reusing stale entitlements.