Skip to content

Commit b740845

Browse files
committed
docs: Rewrite concise README covering policy proxy, structuring detection, and audit chain
1 parent 618cdc2 commit b740845

1 file changed

Lines changed: 57 additions & 83 deletions

File tree

‎README.md‎

Lines changed: 57 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,66 @@
11
# Structura
22

3-
Razorpay's official MCP server has one access control: `--read-only`. Structura adds a deterministic policy layer with the one check no generic MCP gateway does — detecting when an agent splits a transaction across multiple calls to stay under a per-call limit.
4-
5-
## Verified Ground Truth
6-
- The Razorpay MCP server provides 14 tools mapping to core payment APIs (payments, refunds, orders, customers).
7-
- Security controls are binary: `--read-only` disables all mutating tools, or default mode allows unrestricted access.
8-
- Hooks system (PR #89) allows custom headers but not payload inspection or call interception.
9-
- Issue #134 requests native policy guardrails, validating the need for this layer.
10-
- The competitive landscape consists of generic API gateways that cannot inspect inside JSON-RPC MCP frames or maintain cross-call memory for structuring detection.
11-
12-
## Upstream Contributions
13-
To demonstrate that this proxy fills a very real gap in the ecosystem, I have pushed the insights from this architecture directly back upstream to the official Razorpay MCP server:
14-
- **Fixing Issue #134 (Truncation Bug)**: [razorpay/razorpay-mcp-server/pull/140](https://github.com/razorpay/razorpay-mcp-server/pull/140). A PR fixing a silent fractional subunit truncation bug at the input boundary, ensuring precision integrity before the SDK boundary.
15-
- **Native Policy Guardrails (The Core Gap)**: [razorpay/razorpay-mcp-server/discussions/141](https://github.com/razorpay/razorpay-mcp-server/discussions/141). A formal proposal for a `PolicyHook` interface, linking this repository as the reference architecture for why an in-process hook is necessary to avoid requiring an external `stdio` proxy wrapper.
16-
17-
## Architecture
18-
Structura acts as a transparent `stdio` proxy that wraps the upstream Razorpay MCP Go binary.
19-
20-
```text
21-
structura/
22-
├── packages/
23-
│ ├── proxy/ # The core stdio wrapper, policy engine, structuring detector, and SQLite DB
24-
│ │ ├── src/
25-
│ │ │ ├── proxy/ # Stdio Bridge (transparent raw-byte passthrough)
26-
│ │ │ ├── policy/ # Deterministic rules evaluation (ALLOW/DENY/REQUIRE_APPROVAL)
27-
│ │ │ ├── structuring/ # Cross-call state tracking (memory) and splitting detection
28-
│ │ │ ├── approval/ # SQLite queue + HTTP API for human-in-the-loop approvals
29-
│ │ │ ├── audit/ # Tamper-evident hash-chained audit log
30-
│ │ │ └── cli.ts # `structura verify-audit-log` bin tool
31-
│ ├── dashboard/ # React + Vite static UI for reviewing/approving pending actions
32-
├── scripts/ # Acceptance and regression tests
33-
├── policy.yaml # Rules definition
34-
└── structura.db # SQLite DB (Approvals & Hash-chained Audit Log)
3+
A transparent `stdio` proxy that sits between AI clients and Razorpay's official Model Context Protocol (MCP) server to enforce deterministic runtime safety on financial tool execution.
4+
5+
**Live Demo:** [https://structura-mlsv.onrender.com](https://structura-mlsv.onrender.com)
6+
7+
---
8+
9+
## Why It Exists
10+
11+
Autonomous AI agents executing financial actions cannot rely on prompt-based guardrails. Furthermore, Razorpay's official MCP server offers only binary access (`--read-only` or unrestricted execution), and generic API gateways cannot inspect inside JSON-RPC frames or track state across multiple calls.
12+
13+
Structura fills this gap by providing:
14+
1. **Deterministic Policy Enforcement:** Evaluates tool calls against YAML-defined rules without an LLM in the decision loop. Schema-validated via Zod; fails closed on missing or invalid configurations.
15+
2. **Transaction Structuring Detection:** Detects smurfing and structuring patterns across calls. For example, five ₹10,000 refunds on the same `payment_id` trip a ₹15,000 threshold on the 2nd call and trigger `REQUIRE_APPROVAL`, whereas five ₹10,000 refunds spread across five different payments pass (`ALLOW`).
16+
3. **Human Approval Queue:** Intercepted calls enter an SQLite queue reviewable in an embedded React + Vite dashboard before downstream execution.
17+
4. **Tamper-Evident Audit Chain:** Every inbound frame, decision, and payload hash is committed to an SQLite hash-chain (`SHA-256`). A standalone CLI verifier (`structura verify-audit-log`) detects edited rows, deleted rows, and recomputed hashes.
18+
19+
---
20+
21+
## Policy Rules Example (`policy.yaml`)
22+
23+
```yaml
24+
rules:
25+
- tool: create_refund
26+
action: REQUIRE_APPROVAL
27+
conditions:
28+
amount: { gt: 1000000 } # Subunits: > ₹10,000 triggers human review
29+
30+
structuring:
31+
- tool: create_refund
32+
group_by: payment_id
33+
threshold: 1500000 # Cumulative > ₹15,000 triggers REQUIRE_APPROVAL
34+
window_hours: 24
3535
```
3636
37-
## ADR-style Decisions
38-
1. **Deterministic-only policy path:** No LLM anywhere in the decision path. Fast, predictable, zero hallucination risk on security bounds.
39-
2. **Fail-closed config loading:** If `policy.yaml` is missing or fails Zod schema validation, the proxy refuses to start.
40-
3. **REQUIRE_APPROVAL for structuring flags:** Suspicious structuring limits trigger an approval queue rather than a hard `DENY`, avoiding false-positive deadlocks for legitimate burst traffic.
41-
4. **In-memory-only structuring state for v1:** Rolling window memory lives in process state to avoid DB bottleneck during evaluation.
42-
5. **Raw-byte passthrough instead of re-serialization:** Avoids mangling JSON responses. The proxy intercepts via `id` correlation on stdout but does not parse the underlying payload.
43-
44-
## Failure-semantics
45-
| Failure Mode | Result | Tested Behavior |
46-
| --- | --- | --- |
47-
| Malformed JSON-RPC frame | Passthrough | Ignored by policy engine, passed directly to upstream which returns proper `-32700 Parse error`. Proxy recovers instantly. |
48-
| Missing/invalid policy.yaml | Fail Closed | Proxy refuses to boot on startup, stdout logs the error. |
49-
| Upstream process crash mid-session | Teardown | Proxy monitors `process.on('exit')` and cleans up pipe bindings. |
50-
| Double-approval attempt | Rejected | API returns HTTP 400. Proxy intercepts the injection attempt, resulting in no upstream S2S call. |
51-
| Tampered/deleted audit log row | Detected | Standalone CLI catches content hashes, linkage mismatches, and `seq` gaps exactly at the point of corruption. |
52-
53-
## Adversarial Test Results
54-
- **True Positive:** 5x ₹10,000 refunds against one `payment_id` successfully flagged on the 2nd call.
55-
- **False Positive:** 5x ₹10,000 refunds against 5 different `payment_ids` successfully allowed.
56-
- **Cross-key Evasion:** (Known Limitation / Incidental Catch) Detection groups by `payment_id` regardless of API key. Cross-key splitting targeting the same payment is caught, but it is incidental to the group key, not explicit API key tracking.
57-
- **Window-boundary Evasion:** (Known Limitation) Calls explicitly spaced out right outside the 24h rolling fixed window evade detection.
58-
- **Audit Log Tamper - Content:** Caught content hash mismatch at `seq 2`.
59-
- **Audit Log Tamper - Row Deletion:** Caught `seq` gap at `seq 2`.
60-
- **Audit Log Tamper - Smart Attacker:** Caught broken linkage hash at `seq 3` when attacker manually recomputed the hash of tampered `seq 2` without rewriting the rest of the chain.
61-
62-
## Live Deployment
63-
The proxy and dashboard are currently live on a Render free-tier Web Service at:
64-
**https://structura-mlsv.onrender.com**
65-
66-
## Production Gaps & Protocol Constraints
67-
- **Heuristic Ceilings on Structuring Detection:** While migrating from a fixed-window to a sliding-window reduces trivial boundary evasion, rule-based detection inherently hits a ceiling. A patient adversary can always pace transactions under the threshold. True resolution requires moving from single-signal rules to clustering analysis (counterparty diversity, velocity) and anomaly scoring.
68-
- **State Fragmentation vs. Availability Tradeoffs:** The proxy currently uses an in-memory `Map` for structuring counters. Scaling horizontally requires an external store like Redis. However, introducing Redis introduces a critical availability question for a security control plane: if Redis is unreachable, the system must **fail closed** (deny/hold), explicitly prioritizing security over availability.
69-
- **The "Dangling State" MCP Protocol Limitation:** MCP is strictly synchronous per-call over `stdio`, lacking a native push channel. When a tool hits `REQUIRE_APPROVAL`, the agent receives an immediate failure. The standard workaround within MCP constraints is a **status-poll tool**—giving the agent a ticket ID to query asynchronously, rather than treating this as a proxy bug.
70-
- **Tamper-Evident vs. Tamper-Proof:** The SQLite hash-chain mathematically catches row tampering or deletion. However, an attacker with full filesystem access can drop the table and rewrite history entirely. The pragmatic V2 fix short of full blockchain anchoring is periodically publishing hash checkpoints to an external append-only store (e.g., an S3 bucket with Object Lock or a signed git commit).
71-
- **Free-tier deployment's disk persistence:** The Render free-tier Web Service does not support attaching persistent disks (Volumes). This means `structura.db` operates on an ephemeral filesystem and the audit log / approval queue will be wiped on every container restart or sleep cycle.
72-
73-
## Setup / running locally
37+
Decisions resolve strictly to `ALLOW`, `DENY`, or `REQUIRE_APPROVAL`.
38+
39+
---
40+
41+
## Tech Stack
42+
43+
- **Runtime & Language:** Node.js 22, TypeScript
44+
- **Validation:** Zod (fail-closed schema enforcement)
45+
- **Database & Storage:** SQLite (`better-sqlite3`)
46+
- **Dashboard UI:** React, Vite, Tailwind CSS
47+
- **Protocol:** Model Context Protocol (MCP) JSON-RPC 2.0 over `stdio`
48+
49+
---
50+
51+
## Setup & Quick Start
52+
7453
```bash
75-
# Clone the repository
54+
# 1. Clone & install
7655
git clone https://github.com/CodeWithRJ006/Structura.git
77-
cd Structura
56+
cd Structura && npm install
7857
79-
# Configure env vars
80-
echo "RZP_KEY_ID=mock" > .env
81-
echo "RZP_KEY_SECRET=mock" >> .env
82-
echo "RZP_TOOLSETS=payments,refunds" >> .env
58+
# 2. Build proxy & dashboard
59+
npm run build
8360
84-
# Start with Docker Compose
85-
docker compose up --build
86-
```
87-
The Dashboard will be reachable at `http://localhost:4000`.
61+
# 3. Run proxy (wraps Razorpay MCP on stdio)
62+
npm start
8863
89-
## Roadmap (v2)
90-
- Multi-merchant SaaS console
91-
- AP2/UAP mandate ingestion
92-
- ML anomaly scoring
64+
# 4. Verify cryptographic audit log integrity
65+
npx structura verify-audit-log
66+
```

0 commit comments

Comments
 (0)