FM4 — Data Consistency Failure
Misapplying De Morgan’s Laws causes inconsistent boolean evaluation across system boundaries.
The canonical failure: a server enforces an access rule using one boolean expression. A client enforces the same rule using a supposedly equivalent expression. The expressions are not actually equivalent — De Morgan’s was applied incorrectly. The server allows requests the client thinks it blocked. The inconsistency is undetected until a security audit.
# Server: allow if not (read_only and no_write_grant)
# Intent: "block only if BOTH conditions hold"
server_allows = not (user.is_read_only and not user.has_write_grant)
# By De Morgan: (not is_read_only) OR (has_write_grant)
# Client: blocks if read_only OR no write grant
# Intent: "block if EITHER condition holds"
client_blocks = user.is_read_only or not user.has_write_grant
# client_allows = not client_blocks = (not read_only) AND (has_write_grant)server_allows and client_allows are
different. A user who is read-only but has a write grant is allowed by
the server (because has_write_grant is True, the OR fires)
but blocked by the client (because is_read_only is True,
the AND fails). The server and client disagree. Requests that reach the
server get processed. The client never sends them. Or worse: a different
client sends them directly.
The prevention is shared predicate functions. Define the access rule once. Call it from both sides.
# Shared predicate: single source of truth for the access rule
def user_can_write(user: User) -> bool:
# Explicit statement: user is permitted to write
return not user.is_read_only or user.has_write_grant
# Server and client both call user_can_write(user)
# They cannot disagree because they call the same function