Permission Middleware¶
PermissionMiddleware is the first built-in inbound security gate in the Nalix network
pipeline. It enforces handler permission metadata before packet handlers run and denies
packets fail-closed when permission metadata is missing or insufficient.
Source Mapping¶
| Source | Responsibility |
|---|---|
src/Nalix.Runtime/Middleware/Standard/PermissionMiddleware.cs |
Runtime permission enforcement and unauthorized directive emission. |
src/Nalix.Abstractions/Middleware/MiddlewareOrderAttribute.cs |
Declares middleware order metadata. |
src/Nalix.Abstractions/Middleware/MiddlewareStageAttribute.cs |
Declares middleware stage metadata. |
src/Nalix.Abstractions/Networking/ConnectionAttributes.cs |
Stores per-connection directive throttle timestamps. |
src/Nalix.Runtime/Internal/RateLimiting/DirectiveGuard.cs |
Rate-gates repeated directive responses. |
src/Nalix.Codec/DataFrames/SignalFrames/Directive.cs |
Rejection signal frame sent to the client. |
Runtime Metadata¶
| Metadata | Value |
|---|---|
| Stage | MiddlewareStage.Inbound |
| Order | -50 |
| Packet type | IPacket |
The low order makes the permission guard run before the built-in throttling and timeout middleware in the current inbound stack.
Enforcement Rule¶
The middleware allows a packet only when both conditions are true:
context.Attributes.Permission is not null &&
context.Attributes.Permission.Level <= context.Connection.Level
Everything else is denied:
- missing
[PacketPermission]metadata; - a required permission level higher than the connection's current level.
Fail-closed behavior
This is fail-closed behavior. A handler without permission metadata is not treated as public by this middleware; it is rejected as unauthorized.
Permission Flow¶
flowchart TD
A["InvokeAsync(context, next)"] --> B{"Permission attribute exists?"}
B -- no --> D["deny"]
B -- yes --> C{"required <= connection level?"}
C -- yes --> N["await next(context.CancellationToken)"]
C -- no --> D
D --> G{"DirectiveGuard.TryAcquire unauthorized slot"}
G -- no --> S["return without sending duplicate directive"]
G -- yes --> R["acquire Directive via PacketFactory"]
R --> I["Initialize FAIL / UNAUTHORIZED"]
I --> X["SendAsync(directive)"]
X --> Z["return without next"]
Rejection Directive¶
On denial, the middleware acquires a Directive via PacketFactory<Directive>.Acquire() through a
PacketScope<Directive> and initializes it as:
| Field | Runtime value |
|---|---|
ControlType |
FAIL |
ProtocolReason |
UNAUTHORIZED |
ProtocolAdvice |
NONE |
sequenceId |
context.Packet.SequenceId |
controlFlags |
ControlFlags.NONE |
arg0 |
0 |
arg1 |
0 |
arg2 |
context.Attributes.PacketOpcode.OpCode |
arg2 carries the denied opcode so the peer can correlate the authorization failure
with the attempted operation.
Directive Rate Gate¶
Before sending the directive, the middleware calls:
DirectiveGuard.TryAcquire(
context.Connection,
ConnectionAttributes.InboundDirectiveUnauthorizedLastSentAtMs)
If the guard rejects, the middleware returns without sending another directive. This prevents unauthorized packet floods from producing unbounded failure responses.
Error Handling¶
SendAsync is wrapped in a non-fatal exception filter. If directive sending fails, the
middleware logs through context.Connection.ThrottledError(...) with key
middleware.permission.send_error. The original request remains denied and the pipeline
is not continued.
Registration¶
The parameterless constructor resolves an optional ILogger from InstanceManager. The
explicit constructor requires a non-null ILogger.
Securing a Packet¶
[PacketOpcode(0x2001)]
[PacketPermission(PermissionLevel.SYSTEM_ADMINISTRATOR)]
public sealed class AdminCommandPacket : IPacket
{
// packet members
}
Because the middleware fails closed, explicitly annotate every handler/packet that can
enter a pipeline containing PermissionMiddleware.