Skip to content

Commit 8b34d25

Browse files
committed
examples: add supervisor middleware payment gate
Signed-off-by: Axiru <hello@axiru.com>
1 parent 5c0c9e4 commit 8b34d25

17 files changed

Lines changed: 3625 additions & 0 deletions
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
dist
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
Title: Add supervisor middleware example: payment gate for agents that move money
2+
3+
Branch: examples/supervisor-middleware-payment-gate
4+
5+
Commit message (sign with -s from the Axiru GitHub identity):
6+
7+
examples: add supervisor middleware payment gate
8+
9+
Adds a self-contained supervisor middleware example for sandboxes whose
10+
agents hold Stripe credentials. Network policy decides whether the
11+
sandbox may reach api.stripe.com; this middleware decides whether a
12+
specific money-moving call should happen, in the PRE_CREDENTIALS phase.
13+
14+
It parses refunds, credits, transfers, payouts, dispute updates and
15+
issuing approvals from the request body and evaluates a deterministic
16+
policy: per-transfer ceiling, hold threshold, daily cap per sandbox,
17+
duplicate window, optional counterparty allowlist. On allow it writes a
18+
Stripe Idempotency-Key bound to the decision id so a retry cannot become
19+
a second refund. On repeated denials it emits a quarantine_recommended
20+
finding. It fails closed. Protos are vendored from proto/ at this commit.
21+
22+
Signed-off-by: Axiru <hello@axiru.com>
23+
24+
PR description:
25+
26+
This example shows a supervisor middleware for agents that hold Stripe credentials. OpenShell already controls whether the sandbox may reach api.stripe.com. This middleware decides whether a specific money-moving call should happen: it parses refunds, credits, transfers, and payouts from the request body, evaluates a deterministic policy (per-transfer ceiling, hold threshold, daily cap per sandbox, duplicate window, counterparty allowlist), and returns DECISION_ALLOW or DECISION_DENY in the PRE_CREDENTIALS phase. On allow it writes a Stripe Idempotency-Key tied to the decision so a retry cannot become a second refund. On repeated denials it emits a quarantine_recommended finding. It fails closed.
27+
28+
It is included because payment tools are where an agent mistake becomes a loss with no attacker involved (a parsing error that refunds the whole balance, a duplicate refund after a retry), and the sandbox boundary alone does not see the amount. The evaluator is a pure function with no model in the decision path, so decisions replay bit for bit.
29+
30+
The example is self-contained (Node.js, two runtime dependencies for gRPC) and mirrors the layout of supervisor-middleware-content-guard. Tests cover the parser, pass-through, allow with idempotency pinning, duplicate denial, unspecified amount, and the quarantine signal. Protos are vendored from proto/ at this commit.
31+
32+
Checklist before opening:
33+
- Read CONTRIBUTING.md and STYLEGUIDE.md; add SPDX headers to src files if maintainers require them on examples (README already has one).
34+
- Run the local gateway smoke path from the content guard README with this service on port 50052 and this policy.yaml; paste the gateway log lines for one allow and one deny into the PR.
35+
- Commit with -s from the Axiru account. Do not sign with a personal name until after 19 Oct 2026.
36+
- Open the PR from a fork under the AxiruAI org.
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<!--
2+
SPDX-FileCopyrightText: Copyright (c) 2026 Axiru, Inc.
3+
SPDX-License-Identifier: Apache-2.0
4+
-->
5+
6+
# Supervisor Middleware Payment Gate
7+
8+
> [!WARNING]
9+
> Supervisor middleware is a research preview. Its policy and service contracts may change without compatibility guarantees. Use it only to prototype and evaluate middleware integrations.
10+
11+
This example is a supervisor middleware for sandboxes whose agents hold Stripe credentials. OpenShell network policy decides whether the sandbox may reach `api.stripe.com` at all. This middleware decides whether a specific money-moving call should happen.
12+
13+
It parses refunds, customer credits, transfers, payouts, dispute updates, and issuing approvals out of the Stripe request body, evaluates a deterministic policy (per-transfer ceiling, hold threshold, daily cap per sandbox, duplicate window for the same customer and charge, optional counterparty allowlist), and returns `DECISION_ALLOW` or `DECISION_DENY` in the `PRE_CREDENTIALS` phase. Reads and non-money endpoints pass through.
14+
15+
On allow it writes a Stripe `Idempotency-Key` bound to the decision id, so a retry of the same decision cannot become a second refund. On deny it returns a reason code and a one-sentence rationale. A refund with no `amount` is a full refund in Stripe's API; the middleware denies it and asks for an explicit amount. After three denials from one sandbox in 24 hours it adds a `quarantine_recommended` finding with severity `critical`. Any internal error is a deny, and `policy.yaml` sets `on_error: fail_closed`.
16+
17+
The evaluator is a pure function: policy, intent, prior decisions, and a timestamp in; verdict, reason codes, and a SHA-256 fingerprint out. There is no model in the decision path and the agent's free-text reason is recorded but never evaluated, so the decision cannot be talked into anything by a prompt. Same inputs, same fingerprint.
18+
19+
Why a payment gate belongs here: most agent money losses have no attacker. A parsing bug that refunds a whole balance, a duplicate refund after a retry that looked like a failure, a loop of small refunds. The sandbox boundary sees the host; it does not see the amount. This example adds the amount.
20+
21+
## Prerequisites
22+
23+
Node.js 20 or later on the host for the middleware service. For the smoke run, the same prerequisites as the content guard example: `cargo`, `curl`, `jq`, `mise`, Docker or Podman, and the repository's mise tools.
24+
25+
## Run the service
26+
27+
```shell
28+
cd examples/supervisor-middleware-payment-gate
29+
npm install && npm run build
30+
AXIRU_MW_BIND=0.0.0.0:50052 npm start
31+
```
32+
33+
Bind to all host interfaces so a local containerized gateway and sandbox supervisor can reach it.
34+
35+
Add the service registration to your local gateway TOML (see `gateway.toml.snippet`):
36+
37+
```toml
38+
[[openshell.supervisor.middleware]]
39+
name = "axiru-payment-gate"
40+
grpc_endpoint = "http://host.openshell.internal:50052"
41+
allow_insecure_transport = true
42+
max_payload_bytes = 262144
43+
timeout = "2s"
44+
```
45+
46+
Create a sandbox with the included policy:
47+
48+
```shell
49+
openshell sandbox create --name support-agent --policy examples/supervisor-middleware-payment-gate/policy.yaml
50+
```
51+
52+
The policy allows only the listed Stripe money-moving endpoints and reads, routes every allowed call through the middleware, and fails closed. Adjust the thresholds under `network_middlewares.axiru-payment-gate.config` (values are minor units, so `50000` is 500.00 USD) and list the binaries your agent actually uses.
53+
54+
## Try it from inside the sandbox
55+
56+
Inside the sandbox, with a Stripe test key available to the agent:
57+
58+
```shell
59+
# In policy: 45.00 USD refund, first one on this charge. Expect a normal Stripe response.
60+
curl -s https://api.stripe.com/v1/refunds -u "$STRIPE_KEY": -d charge=ch_test_1 -d amount=4500 -d customer=cus_test_1
61+
62+
# Same charge, same customer, inside the 30-day duplicate window. Expect the gateway to deny with reason_code AXIRU_DENY.
63+
curl -s https://api.stripe.com/v1/refunds -u "$STRIPE_KEY": -d charge=ch_test_1 -d amount=4500 -d customer=cus_test_1
64+
65+
# 250,000.00 USD transfer. Expect a deny with AMOUNT_EXCEEDS_TRANSFER_CEILING in the audit log metadata.
66+
curl -s https://api.stripe.com/v1/transfers -u "$STRIPE_KEY": -d amount=25000000 -d currency=usd -d destination=acct_test_x
67+
```
68+
69+
Every evaluation lands in the gateway audit log with `axiru.decision_id`, `axiru.verdict`, `axiru.policy`, `axiru.fingerprint`, and `axiru.reason_codes` metadata, and a finding of type `axiru.decision`.
70+
71+
## Tests
72+
73+
```shell
74+
npm test
75+
```
76+
77+
Covers the Stripe parser, pass-through for non-Stripe hosts, allow with idempotency pinning, duplicate denial inside the window, denial of an unspecified amount, and the quarantine signal after repeated denials.
78+
79+
## Layout
80+
81+
- `src/gate.ts`: types, the pure evaluator, and a small `Gate` class with a session ledger and optional hosted mode.
82+
- `src/stripe.ts`: maps Stripe API requests to payment intents.
83+
- `src/middleware.ts`: the evaluation logic, separated from gRPC so it can be unit tested.
84+
- `src/server.ts`, `src/cli.ts`: the `openshell.middleware.v1.SupervisorMiddleware` gRPC service.
85+
- `proto/`: vendored from this repository's `proto/` directory at the commit this example was added.
86+
- `policy.yaml`: reference sandbox policy.
87+
88+
## Limits
89+
90+
The local evaluator cannot see the original charge amount from a refund request, so the cumulative rule "refunds never exceed the charge" is only enforced when the middleware runs in hosted mode (set `AXIRU_API_KEY`) or when a Stripe read is placed in front of it. Only Stripe is mapped; adding a rail is one regex and one field mapping in `src/stripe.ts`. This example handles form-encoded and JSON request bodies for the listed endpoints and is not a general payments proxy.
91+
92+
A maintained version of this middleware, plus the same gate as an MCP server and as plugins for other agent runtimes, lives at https://github.com/AxiruAI/axiru-gate.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Add to the OpenShell gateway TOML to register the Axiru middleware service.
2+
# Run the service first: AXIRU_MW_BIND=0.0.0.0:50052 npx @axiru/openshell-middleware
3+
[[openshell.supervisor.middleware]]
4+
name = "axiru-payment-gate"
5+
grpc_endpoint = "http://host.openshell.internal:50052"
6+
allow_insecure_transport = true # local only; use TLS in production
7+
max_payload_bytes = 262144
8+
timeout = "2s"

0 commit comments

Comments
 (0)