DEV Community

Anusha Mukka
Anusha Mukka

Posted on

The Policy Was Right. The Data Wasn’t.

Security infrastructure looks clean in architecture diagrams. Production is messier. Stale data, delayed events, service failures, and emergency exceptions all affect real access decisions.

This is Part 1 of Security Infrastructure in Practice, a series about what happens when security design meets production systems.

The policy looked correct.

Employees in the support function could view customer cases. Contractors could view only the cases assigned to them. Unmanaged devices were blocked from downloading attachments.

Then an employee received the contractor experience.

The first instinct was to inspect the authorization rule. Nothing was wrong with it. The identity service had an old employment type, the assignment service had the current case mapping, and the device service had timed out. The policy engine had evaluated exactly what it received.

This is the part of attribute-based access control that diagrams tend to skip. A policy decision is only as good as the data present at that moment.

The Rule Is One Piece of the Decision

An ABAC request usually combines attributes from different systems. Identity data may come from a directory. Resource classification may live with the application. Device posture may come from a security service.
Those systems update on different schedules. They fail differently too.

{
"subject": {
"employment_type": "contractor",
"source": "identity-cache",
"observed_at": "2026-09-09T08:00:00Z"
},
"resource": {
"assigned_to": "user-1842",
"source": "case-service",
"observed_at": "2026-09-09T18:04:00Z"
},
"device": {
"status": "unknown",
"source": "posture-service",
"observed_at": null
}
}

Looking only at the values hides the problem. The employment type is stale. The device value is not false; it is unknown.

Give Every Attribute a Receipt

I like to carry a small amount of provenance with each resolved attribute:

@dataclass(frozen=True)
class AttributeValue:
name: str
value: object
source: str
observed_at: datetime | None
expires_at: datetime | None
status: str # present, missing, unavailable, invalid

This may feel heavy compared with passing a dictionary. It pays for itself when a decision is questioned. The engine can distinguish a real value from a default and an expired value from a current one.
It also stops a subtle bug: treating every missing value as false.

if not attributes["device_compliant"]:
deny()

That condition combines at least two states. The device may be known to be noncompliant. The posture service may have failed to answer. The enforcement result can still be deny, but the reason should be different.

Freshness Is a Policy Question

Engineers often put cache expiration in infrastructure configuration. That is reasonable for ordinary performance caching. Authorization data is different because acceptable age depends on the decision.
A department value might be acceptable for several hours when opening an internal dashboard. It might need to be much fresher when approving a sensitive export. The cache library cannot choose that risk tolerance.

attribute_requirements:
device_compliant:
maximum_age_seconds: 300
required: true
department:
maximum_age_seconds: 14400
required: true

Put freshness requirements beside the policy that depends on them. Then a policy review includes the age of the evidence, not merely the expected value.

Debug the Input Before Rewriting the Rule

When a decision looks wrong, I check four things in order:
Which policy version ran?
Which exact attributes did it evaluate?
Where did each attribute come from?
Was each value still valid?

Only then do I change policy logic.
Without that discipline, teams often weaken a correct rule to compensate for bad data. The immediate ticket goes away. The authorization boundary becomes less trustworthy.

Test the Resolver as Seriously as the Policy

Policy tests tend to use clean fixtures. Every attribute is present and current. That proves the rule works in a world the production system will never inhabit.
Add cases for an old identity value, a timed-out device service, and conflicting sources. Make the expected behavior explicit.

def test_unknown_device_is_not_reported_as_noncompliant():
decision = authorize(
subject=current_employee(),
device=unavailable_attribute("device_compliant")
)
assert decision.effect == "deny"
assert decision.reason == "DEVICE_STATUS_UNAVAILABLE"

That final assertion matters. A denial for unavailable data should not tell a user to repair a device that may be perfectly healthy.
The next time an authorization rule looks wrong, resist editing it for a few minutes. Inspect the data receipt first. The bug may have happened long before the policy engine received the request.
Have you debugged a permission problem that turned out to be stale or missing data? What finally gave it away?

Top comments (0)