You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Gap: no pre-sign policy validation between "authorized to act" and "should this action execute" #96
Right now x402Serve() checks that a request carries a valid, correctly signed payment. It doesn't check anything about whether that specific action should go through in the first place — who's acting, how much value is at risk, whether the destination is one the account owner would actually approve, or any rule beyond "is this signature valid."
Those are two different questions. Holding a valid signing key proves you can request an action. It doesn't prove that action should execute under the rules the account owner actually wants.
We don't have an answer for this yet, just the gap. If you're interested, a design doc or a set of adversarial test cases for a pre-sign policy layer (something like an allow/warn/wait/deny model, evaluated against actor identity, value, destination, and exposure, before the payment settles) would be genuinely useful here. No commitment on our side or yours — just an open problem worth working through in public.
Agent-side pre-sign policy — first integrated result
I mapped the three cases we discussed fragmentation, concurrent requests against the same observed capacity, and self-escalation against Nirium’s actual agent-side x402 signing path.
The scope of this first pass is intentionally narrow: owner-defined policy, spending authority separated from policy administration, and enforcement at the agent-side pre-sign boundary. x402Serve() is out of scope here, and Nirium is not treated as the policy owner or custodian.
I think it is important to distinguish the guarantees from the beginning.
G1 — Pre-sign integrity
Before Nirium emits an authorization, the exact authorization being considered must be evaluated against the owner’s policy.
For the cases tested here:
ALLOW may reach the signer only for the authorization that was evaluated.
DENY must not reach the signer.
WAIT must not reach the signer.
policy failure must not silently fail open.
a late decision must not produce a signature after the decision deadline.
a decision produced for one authorization must not authorize another.
G2 — Account enforcement
Even if Nirium is bypassed entirely, the spending credential must still be unable to execute consequences outside the owner’s policy.
G2 is a different security claim and is not proven by G1.
Integrated G1 result
G1 has now been exercised against the real Nirium/x402 signing path in a controlled harness.
A pre-sign policy adapter can prevent Nirium from emitting an authorization.
That is not the same claim as making the policy unavoidable at the account layer.
Next adversarial case: stale ALLOW / TOCTOU
One additional case I think belongs directly in the pre-sign model is a stale ALLOW.
For example:
T0:
policy v7 evaluates request A
→ ALLOW
T1:
owner changes policy
or revokes the spending authority
or relevant policy state changes
T2:
the old ALLOW arrives at the signing boundary
The question is:
Can an approval produced under an old policy/state still produce a signature after that policy/state is no longer authoritative?
I do not think ALLOW should behave like an indefinite reusable approval.
A future version of the interface should therefore be able to bind a decision not only to the exact authorization context, but also to the policy/state under which that decision was produced and to an explicit validity boundary.
The exact API fields are still a design question; the invariant is more important than the field names.
Possible economic-authority conformance profile
Longer term, I think #96 could define a small conformance model so that different policy engines do not all mean something different when they claim an agent is “policy protected.”
For example:
G1 — PRE-SIGN
Forbidden authorization is not emitted by the Nirium signing path.
G2 — ACCOUNT ENFORCEMENT
Bypassing Nirium still cannot execute the forbidden consequence.
G3 — PENDING CAPACITY
Outstanding authorizations cannot collectively oversubscribe
the authority/budget the owner intended to expose.
G4 — GOVERNANCE
Spending authority cannot modify the rules that constrain it.
Only G1 is claimed/tested in this result.
The purpose of separating these levels is to prevent a local pre-sign check from being described as if it were equivalent to account-level or concurrency-safe enforcement.
Optional policy-decision receipt
I also think it may be useful to keep room for a small, non-secret policy-decision receipt.
and prevents those guarantees from being collapsed into one ambiguous “protected” claim.
If this direction matches what you had in mind for #96, my next step can be to clean up the design and adversarial harness for review, while keeping G2/G3/G4 as explicit follow-on guarantees rather than silently expanding the G1 claim.
Thanks @CodeDeityX, this is the shape I hoped #96 would take. Keeping G1 separate from G2, and writing L01/L02/G2 down as NOT_SOLVED/NOT_RUN instead of folding them into a PASS, is what makes this useful.
My vote for the next step is to clean up the harness for review first. Right now the 58/58 and 25/25 can't be reproduced by anyone else, and that's what turns this from a claim into evidence.
To keep this issue finishable, I'd scope it to three things for now:
The harness, public and runnable by anyone.
Stale ALLOW / TOCTOU inside G1: an ALLOW bound to the policy version it was produced under, with an expiry.
On Nirium's side, exposing the pre-sign point as a supported, documented hook, so the adapter doesn't depend on instrumenting the signer. We'll pick this up once the harness is public.
G2 to G4 and the decision receipt stay written down here as follow-ups. Not dropped, just not started yet.
Disclosure: drafted with AI assistance under my direction and reviewed by hand.
I would love to take this on. My planned approach:
Implement Gap: no pre-sign policy validation between "authorized to act" and "should this action execute" with modular design, clear documentation, and clean commit history.
Ensure the change passes all existing lint, format, and CI verification pipelines.
I am ready to start immediately—please feel free to assign this to me!
The original 25 G1 cases are preserved. The additional 10 cases cover the freshness / stale-ALLOW boundary discussed below.
No real keys are used, no transaction is broadcast, no KINE dependency is required, and no Nirium production source is modified by the harness.
2. Stale ALLOW / TOCTOU inside G1
The G1 candidate now binds an ALLOW to:
the exact request context;
the policy version under which it was produced;
an explicit expiry.
At the final tested pre-sign freshness check:
current + unexpired ALLOW may reach the signer;
stale policy version fails closed;
expired decision fails closed;
unavailable / invalid freshness information fails closed;
a concurrent request that becomes stale before its own final check does not reach the signer.
The claim remains deliberately bounded:
Within the tested Nirium/x402 pre-sign path, ALLOW is bound to the exact request, policy version and explicit expiry, and fails closed at the final tested freshness check when stale, expired or freshness cannot be established.
This does not claim that the freshness observation is atomic with cryptographic signing, nor does it establish account-level / on-chain enforcement.
This should close the first two items from the scope you proposed.
The remaining item is the supported/documented pre-sign hook you mentioned on the Nirium side. Once that exists, I can move the adapter from signer instrumentation onto that supported boundary and rerun the same evidence contract against it.
I would especially value an independent reproduction of the public harness before we treat this phase as closed.
Resolved by #98 (merged 2026-10-03), which added the pre-sign policy hook: initX402({ policy }) lets a caller run an evaluate/decide step (allow/warn/wait/deny) against actor identity, value, and destination before x402Fetch signs anything — exactly the "should this execute" layer this issue asked for, on top of the existing "is this signature valid" check.
Closing as completed rather than leaving it open for the design doc originally requested; the implementation shipped instead.
Activity
CodeDeityX commentedon Sep 29, 2026
Agent-side pre-sign policy — first integrated result
I mapped the three cases we discussed fragmentation, concurrent requests against the same observed capacity, and self-escalation against Nirium’s actual agent-side x402 signing path.
The scope of this first pass is intentionally narrow: owner-defined policy, spending authority separated from policy administration, and enforcement at the agent-side pre-sign boundary.
x402Serve()is out of scope here, and Nirium is not treated as the policy owner or custodian.I think it is important to distinguish the guarantees from the beginning.
G1 — Pre-sign integrity
Before Nirium emits an authorization, the exact authorization being considered must be evaluated against the owner’s policy.
For the cases tested here:
ALLOWmay reach the signer only for the authorization that was evaluated.DENYmust not reach the signer.WAITmust not reach the signer.G2 — Account enforcement
Even if Nirium is bypassed entirely, the spending credential must still be unable to execute consequences outside the owner’s policy.
G2 is a different security claim and is not proven by G1.
Integrated G1 result
G1 has now been exercised against the real Nirium/x402 signing path in a controlled harness.
The integrated run exercised:
HTTP transport, RPC, ledger timing, and policy state were deliberately simulated so the adversarial cases could be controlled and reproduced.
No real key was used.
No transaction was broadcast.
No production Nirium source change was required for the integration.
The U0 adapter tests remain separate from the G1 integration result; they are not being counted as proof of Nirium integration.
Adversarial cases
1. Fragmentation
An individually acceptable payment should not automatically be treated as economically independent from previous payments.
Example:
The next request is still below the per-action limit, but would push aggregate exposure above the owner’s policy.
In the G1 harness, when the controlled policy rejected that next fragmented request, the authorization did not reach the signer.
This demonstrates the pre-sign policy path.
It does not yet prove that an account enforces the same aggregate exposure rule if Nirium is bypassed.
2. Concurrent requests
Two requests can observe the same apparent remaining capacity at nearly the same time.
For example:
A simple read of
400 availabledoes not reserve that capacity.The G1 tests demonstrated that concurrent requests remain bound to their own policy decisions, authorization inputs, and signer results.
No cross-request decision reuse was observed.
However, this does not solve shared pending-capacity reservation.
Two requests may still observe the same available capacity before either one consumes it.
That limitation remains explicit rather than being hidden inside a G1 PASS.
3. Self-escalation
A spending authority should not automatically inherit the authority to modify the policy that constrains it.
In the G1 harness, a spending credential requesting a policy-expansion / administrative action was rejected before signing.
That means the pre-sign integration can distinguish the economic action from the authority required to change policy.
This remains a G1 boundary result only.
It does not yet prove that the same spending credential cannot attempt the administrative action through another client or execution path.
That belongs to account-level enforcement.
Explicitly unresolved
The distinction matters:
A pre-sign policy adapter can prevent Nirium from emitting an authorization.
That is not the same claim as making the policy unavoidable at the account layer.
Next adversarial case: stale ALLOW / TOCTOU
One additional case I think belongs directly in the pre-sign model is a stale
ALLOW.For example:
The question is:
I do not think
ALLOWshould behave like an indefinite reusable approval.A future version of the interface should therefore be able to bind a decision not only to the exact authorization context, but also to the policy/state under which that decision was produced and to an explicit validity boundary.
The exact API fields are still a design question; the invariant is more important than the field names.
Possible economic-authority conformance profile
Longer term, I think #96 could define a small conformance model so that different policy engines do not all mean something different when they claim an agent is “policy protected.”
For example:
Only G1 is claimed/tested in this result.
The purpose of separating these levels is to prevent a local pre-sign check from being described as if it were equivalent to account-level or concurrency-safe enforcement.
Optional policy-decision receipt
I also think it may be useful to keep room for a small, non-secret policy-decision receipt.
For example:
This would not be a reusable payment authorization and should not contain private keys or secrets.
Its purpose would be evidence:
That could make policy decisions easier to reproduce, debug, audit, and explain without making Nirium the policy authority.
Design boundary
The policy integration should remain engine-neutral.
Nirium’s responsibility would be to expose and preserve a trustworthy pre-sign integration point.
The owner chooses the policy engine and authority model.
A future account-level implementation can provide G2 independently of Nirium’s servers, while a stronger admission model can address G3 separately.
That keeps:
and prevents those guarantees from being collapsed into one ambiguous “protected” claim.
If this direction matches what you had in mind for #96, my next step can be to clean up the design and adversarial harness for review, while keeping G2/G3/G4 as explicit follow-on guarantees rather than silently expanding the G1 claim.
Eras256 commentedon Sep 29, 2026
Thanks @CodeDeityX, this is the shape I hoped #96 would take. Keeping G1 separate from G2, and writing L01/L02/G2 down as NOT_SOLVED/NOT_RUN instead of folding them into a PASS, is what makes this useful.
My vote for the next step is to clean up the harness for review first. Right now the 58/58 and 25/25 can't be reproduced by anyone else, and that's what turns this from a claim into evidence.
To keep this issue finishable, I'd scope it to three things for now:
G2 to G4 and the decision receipt stay written down here as follow-ups. Not dropped, just not started yet.
Disclosure: drafted with AI assistance under my direction and reviewed by hand.
guptakumarranjeet150 commentedon Sep 30, 2026
Hello! I would like to work on this issue.
I would love to take this on. My planned approach:
Gap: no pre-sign policy validation between "authorized to act" and "should this action execute"with modular design, clear documentation, and clean commit history.I am ready to start immediately—please feel free to assign this to me!
CodeDeityX commentedon Sep 30, 2026
Public reproducibility harness — G1 closure candidate
Following the scope we agreed on, the first two items are now ready for maintainer reproduction.
1. Public / reproducible harness
The harness is now public:
https://github.com/CodeDeityX/nirium-96-agent-policy-harness
Canonical publication commit:
65c7ca2ffc18811c28e79f0fd74b054407aa4b09
A clean reproduction exercises the pinned Nirium/x402 path and currently produces:
The original 25 G1 cases are preserved. The additional 10 cases cover the freshness / stale-ALLOW boundary discussed below.
No real keys are used, no transaction is broadcast, no KINE dependency is required, and no Nirium production source is modified by the harness.
2. Stale ALLOW / TOCTOU inside G1
The G1 candidate now binds an ALLOW to:
At the final tested pre-sign freshness check:
The claim remains deliberately bounded:
This does not claim that the freshness observation is atomic with cryptographic signing, nor does it establish account-level / on-chain enforcement.
The unresolved boundaries therefore remain:
This should close the first two items from the scope you proposed.
The remaining item is the supported/documented pre-sign hook you mentioned on the Nirium side. Once that exists, I can move the adapter from signer instrumentation onto that supported boundary and rerun the same evidence contract against it.
I would especially value an independent reproduction of the public harness before we treat this phase as closed.
rupesh-kumar-sah commentedon Oct 2, 2026
Hi team,
Happy to tackle this. Here is how I plan to implement it:
Implementation approach:
Could you please assign this issue to me? Looking forward to delivering a clean PR.
ranjeet150 commentedon Oct 2, 2026
Hi,
I have experience with similar implementations and would like to take this on.
Plan:
Could you please assign this issue to me? Looking forward to delivering a clean PR.
Ranjeet2063 commentedon Oct 2, 2026
Hi maintainers,
I reviewed this issue and would like to contribute the fix.
Implementation approach:
Please assign this to me if the plan looks good. Thanks!
shobhamerabacha-star commentedon Oct 2, 2026
Hi maintainers,
I reviewed this issue and would like to contribute the fix.
Plan:
Ready to implement immediately upon assignment. Please feel free to assign!
ravimahatonp commentedon Oct 2, 2026
Hi maintainers,
I reviewed this issue and would like to contribute the fix.
Implementation approach:
I can deliver a clean, tested PR within 24 hours. Please assign!
Eras256 commentedon Oct 10, 2026
Resolved by #98 (merged 2026-10-03), which added the pre-sign policy hook:
initX402({ policy })lets a caller run an evaluate/decide step (allow/warn/wait/deny) against actor identity, value, and destination beforex402Fetchsigns anything — exactly the "should this execute" layer this issue asked for, on top of the existing "is this signature valid" check.Closing as completed rather than leaving it open for the design doc originally requested; the implementation shipped instead.
🤖 Generated with Claude Code