@aporthq/aport-agent-guardrails 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +217 -0
- package/README.md +481 -0
- package/bin/agent-guardrails +133 -0
- package/bin/aport-create-passport.sh +444 -0
- package/bin/aport-cursor-hook.sh +90 -0
- package/bin/aport-guardrail-api.sh +108 -0
- package/bin/aport-guardrail-bash.sh +394 -0
- package/bin/aport-guardrail-v2.sh +5 -0
- package/bin/aport-guardrail.sh +5 -0
- package/bin/aport-resolve-paths.sh +71 -0
- package/bin/aport-status.sh +276 -0
- package/bin/frameworks/crewai.sh +49 -0
- package/bin/frameworks/cursor.sh +95 -0
- package/bin/frameworks/langchain.sh +48 -0
- package/bin/frameworks/n8n.sh +36 -0
- package/bin/frameworks/openclaw.sh +19 -0
- package/bin/lib/allowlist.sh +18 -0
- package/bin/lib/common.sh +28 -0
- package/bin/lib/config.sh +46 -0
- package/bin/lib/constants.sh +232 -0
- package/bin/lib/detect.sh +65 -0
- package/bin/lib/error.sh +269 -0
- package/bin/lib/passport.sh +19 -0
- package/bin/lib/templates/.gitkeep +1 -0
- package/bin/lib/templates/config.yaml +6 -0
- package/bin/lib/validation.sh +206 -0
- package/bin/openclaw +660 -0
- package/docs/ADDING_A_FRAMEWORK.md +87 -0
- package/docs/AGENTS.md.example +40 -0
- package/docs/CODE_REVIEW.md +192 -0
- package/docs/DEPLOYMENT_READINESS.md +81 -0
- package/docs/FAQ_SECURITY_SCANNERS.md +373 -0
- package/docs/FRAMEWORK_ROADMAP.md +41 -0
- package/docs/HOSTED_PASSPORT_SETUP.md +362 -0
- package/docs/IMPLEMENTING_YOUR_OWN_EVALUATOR.md +433 -0
- package/docs/OPENCLAW_COMPATIBILITY.md +73 -0
- package/docs/OPENCLAW_LOCAL_INTEGRATION.md +596 -0
- package/docs/OPENCLAW_TOOLS_AND_POLICIES.md +54 -0
- package/docs/QUICKSTART.md +470 -0
- package/docs/QUICKSTART_OPENCLAW_PLUGIN.md +470 -0
- package/docs/README.md +28 -0
- package/docs/RELEASE.md +87 -0
- package/docs/REPO_LAYOUT.md +47 -0
- package/docs/SKILLS_ECOSYSTEM_ANALYSIS_FEB17.md +1260 -0
- package/docs/TOOL_POLICY_MAPPING.md +46 -0
- package/docs/UPGRADE.md +46 -0
- package/docs/VERIFICATION_METHODS.md +97 -0
- package/docs/assets/README.md +8 -0
- package/docs/assets/porter.svg +54 -0
- package/docs/development/ERROR_CODES.md +616 -0
- package/docs/frameworks/GITHUB_ISSUE_PROPOSALS.md +1105 -0
- package/docs/frameworks/crewai.md +114 -0
- package/docs/frameworks/cursor.md +159 -0
- package/docs/frameworks/langchain.md +72 -0
- package/docs/frameworks/n8n.md +40 -0
- package/docs/frameworks/openclaw.md +40 -0
- package/docs/launch/ADD_APORT_AWESOME_LISTS_INSTRUCTIONS.md +146 -0
- package/docs/launch/ANNOUNCEMENT_GUIDE.md +266 -0
- package/docs/launch/AWESOME_REPOS.md +53 -0
- package/docs/launch/CURSOR_VSCODE_HOOKS_RESEARCH.md +77 -0
- package/docs/launch/DEMO_TERMINAL_OUTPUT.txt +48 -0
- package/docs/launch/DRY_AND_PLAN_CHECKLIST.md +47 -0
- package/docs/launch/EVIDENCE_README.md +61 -0
- package/docs/launch/EVIDENCE_TERMINAL_CAPTURE.txt +10 -0
- package/docs/launch/FRAMEWORK_SUPPORT_PLAN.md +1640 -0
- package/docs/launch/LAUNCH_READINESS_CHECKLIST.md +237 -0
- package/docs/launch/LAUNCH_STRATEGY_SUMMARY.md +464 -0
- package/docs/launch/OPENCLAW_FEEDBACK_AND_FIXES.md +85 -0
- package/docs/launch/POST_1_VALENTINE_IMPROVED.md +233 -0
- package/docs/launch/POST_2_GUARDRAIL_IMPROVED.md +369 -0
- package/docs/launch/PRE_LAUNCH_FIXES.md +766 -0
- package/docs/launch/QUICK_LAUNCH_CHECKLIST.md +400 -0
- package/docs/launch/READINESS_SUMMARY.md +262 -0
- package/docs/launch/README.md +68 -0
- package/docs/launch/USER_STORIES.md +327 -0
- package/docs/launch/scripts/add-aport-awesome-pr.sh +69 -0
- package/docs/operations/MONITORING.md +588 -0
- package/docs/reviews/2026-02-18-staff-review.md +268 -0
- package/extensions/openclaw-aport/README.md +415 -0
- package/extensions/openclaw-aport/index.js +625 -0
- package/extensions/openclaw-aport/openclaw-aport.js +7 -0
- package/extensions/openclaw-aport/openclaw.plugin.json +46 -0
- package/extensions/openclaw-aport/package.json +36 -0
- package/extensions/openclaw-aport/test.js +307 -0
- package/external/aport-policies/README.md +363 -0
- package/external/aport-policies/agent.session.create.v1/README.md +345 -0
- package/external/aport-policies/agent.session.create.v1/policy.json +162 -0
- package/external/aport-policies/agent.tool.register.v1/README.md +361 -0
- package/external/aport-policies/agent.tool.register.v1/policy.json +172 -0
- package/external/aport-policies/code.release.publish.v1/README.md +51 -0
- package/external/aport-policies/code.release.publish.v1/policy.json +121 -0
- package/external/aport-policies/code.repository.merge.v1/README.md +287 -0
- package/external/aport-policies/code.repository.merge.v1/express.example.js +332 -0
- package/external/aport-policies/code.repository.merge.v1/fastapi.example.py +370 -0
- package/external/aport-policies/code.repository.merge.v1/policy.json +162 -0
- package/external/aport-policies/data.export.create.v1/README.md +226 -0
- package/external/aport-policies/data.export.create.v1/express.example.js +172 -0
- package/external/aport-policies/data.export.create.v1/fastapi.example.py +165 -0
- package/external/aport-policies/data.export.create.v1/policy.json +133 -0
- package/external/aport-policies/data.report.ingest.v1/README.md +134 -0
- package/external/aport-policies/data.report.ingest.v1/express.example.js +105 -0
- package/external/aport-policies/data.report.ingest.v1/minimal-example.js +68 -0
- package/external/aport-policies/data.report.ingest.v1/policy.json +174 -0
- package/external/aport-policies/finance.crypto.trade.v1/README.md +146 -0
- package/external/aport-policies/finance.crypto.trade.v1/express.example.js +109 -0
- package/external/aport-policies/finance.crypto.trade.v1/minimal-example.js +65 -0
- package/external/aport-policies/finance.crypto.trade.v1/policy.json +176 -0
- package/external/aport-policies/finance.payment.charge.v1/README.md +326 -0
- package/external/aport-policies/finance.payment.charge.v1/express.example.js +250 -0
- package/external/aport-policies/finance.payment.charge.v1/fastapi.example.py +227 -0
- package/external/aport-policies/finance.payment.charge.v1/minimal-example.js +64 -0
- package/external/aport-policies/finance.payment.charge.v1/policy.json +224 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/passport.template.json +40 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/payments-charge-policy.test.js +817 -0
- package/external/aport-policies/finance.payment.charge.v1/tests/test_payments_charge_policy.py +486 -0
- package/external/aport-policies/finance.payment.payout.v1/README.md +78 -0
- package/external/aport-policies/finance.payment.payout.v1/policy.json +181 -0
- package/external/aport-policies/finance.payment.refund.v1/README.md +275 -0
- package/external/aport-policies/finance.payment.refund.v1/express.example.js +167 -0
- package/external/aport-policies/finance.payment.refund.v1/fastapi.example.py +136 -0
- package/external/aport-policies/finance.payment.refund.v1/minimal-example.js +183 -0
- package/external/aport-policies/finance.payment.refund.v1/policy.json +216 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/refunds-policy.test.js +924 -0
- package/external/aport-policies/finance.payment.refund.v1/tests/test_refunds_policy.py +778 -0
- package/external/aport-policies/finance.transaction.execute.v1/README.md +309 -0
- package/external/aport-policies/finance.transaction.execute.v1/express.example.js +261 -0
- package/external/aport-policies/finance.transaction.execute.v1/fastapi.example.py +231 -0
- package/external/aport-policies/finance.transaction.execute.v1/minimal-example.js +78 -0
- package/external/aport-policies/finance.transaction.execute.v1/policy.json +189 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.instance.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/passport.template.json +42 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/test_transactions_policy.py +214 -0
- package/external/aport-policies/finance.transaction.execute.v1/tests/transactions-policy.test.js +306 -0
- package/external/aport-policies/governance.data.access.v1/README.md +292 -0
- package/external/aport-policies/governance.data.access.v1/express.example.js +321 -0
- package/external/aport-policies/governance.data.access.v1/fastapi.example.py +279 -0
- package/external/aport-policies/governance.data.access.v1/minimal-example.js +65 -0
- package/external/aport-policies/governance.data.access.v1/policy.json +208 -0
- package/external/aport-policies/governance.data.access.v1/tests/contexts.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/data-access-policy.test.js +308 -0
- package/external/aport-policies/governance.data.access.v1/tests/expected.jsonl +12 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.instance.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/passport.template.json +56 -0
- package/external/aport-policies/governance.data.access.v1/tests/test_data_access_policy.py +214 -0
- package/external/aport-policies/legal.contract.review.v1/README.md +109 -0
- package/external/aport-policies/legal.contract.review.v1/policy.json +378 -0
- package/external/aport-policies/legal.contract.review.v1/tests/legal-contract-review-policy.test.js +609 -0
- package/external/aport-policies/legal.contract.review.v1/tests/passport.template.json +49 -0
- package/external/aport-policies/mcp.tool.execute.v1/README.md +301 -0
- package/external/aport-policies/mcp.tool.execute.v1/policy.json +141 -0
- package/external/aport-policies/messaging.message.send.v1/README.md +230 -0
- package/external/aport-policies/messaging.message.send.v1/express.example.js +183 -0
- package/external/aport-policies/messaging.message.send.v1/fastapi.example.py +193 -0
- package/external/aport-policies/messaging.message.send.v1/policy.json +144 -0
- package/external/aport-policies/policy-template.json +107 -0
- package/external/aport-policies/system.command.execute.v1/README.md +275 -0
- package/external/aport-policies/system.command.execute.v1/policy.json +146 -0
- package/external/aport-spec/CONTRIBUTING.md +273 -0
- package/external/aport-spec/LICENSE +21 -0
- package/external/aport-spec/README.md +168 -0
- package/external/aport-spec/conformance/README.md +294 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/allow_users.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/contexts/deny_pii.json +6 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/allow_users.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/expected/deny_pii.decision.json +19 -0
- package/external/aport-spec/conformance/cases/data.export.v1/passports/template.json +29 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/allow_50usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_150usd.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/contexts/deny_currency.json +9 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/allow_50usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_150usd.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/expected/deny_currency.decision.json +19 -0
- package/external/aport-spec/conformance/cases/payments.refunds.v1/passports/template.json +42 -0
- package/external/aport-spec/conformance/package.json +44 -0
- package/external/aport-spec/conformance/pnpm-lock.yaml +642 -0
- package/external/aport-spec/conformance/src/cases.ts +371 -0
- package/external/aport-spec/conformance/src/ed25519.ts +167 -0
- package/external/aport-spec/conformance/src/jcs.ts +85 -0
- package/external/aport-spec/conformance/src/runner.ts +533 -0
- package/external/aport-spec/conformance/src/validators.ts +185 -0
- package/external/aport-spec/conformance/test-runner.js +315 -0
- package/external/aport-spec/conformance/tsconfig.json +21 -0
- package/external/aport-spec/error-schema.json +192 -0
- package/external/aport-spec/index.json +12 -0
- package/external/aport-spec/integrations/clawmoat/README.md +12 -0
- package/external/aport-spec/integrations/shield/README.md +245 -0
- package/external/aport-spec/integrations/shield/adapters/index.js +116 -0
- package/external/aport-spec/integrations/shield/adapters/system-command-execute.js +133 -0
- package/external/aport-spec/integrations/shield/test/README.md +58 -0
- package/external/aport-spec/integrations/shield/test/shield.md +40 -0
- package/external/aport-spec/integrations/shield/test/test-shield-to-verify.js +274 -0
- package/external/aport-spec/metrics-schema.json +504 -0
- package/external/aport-spec/oap/CHANGELOG.md +54 -0
- package/external/aport-spec/oap/VERSION.md +40 -0
- package/external/aport-spec/oap/capability-registry.md +229 -0
- package/external/aport-spec/oap/conformance.md +257 -0
- package/external/aport-spec/oap/decision-schema.json +114 -0
- package/external/aport-spec/oap/examples/context.refund.usd.50.json +9 -0
- package/external/aport-spec/oap/examples/decision.allow.sample.json +20 -0
- package/external/aport-spec/oap/examples/decision.deny.sample.json +23 -0
- package/external/aport-spec/oap/examples/passport.instance.v1.json +50 -0
- package/external/aport-spec/oap/examples/passport.template.v1.json +71 -0
- package/external/aport-spec/oap/oap-spec.md +426 -0
- package/external/aport-spec/oap/passport-schema.json +396 -0
- package/external/aport-spec/oap/security.md +213 -0
- package/external/aport-spec/oap/vc/context-oap-v1.jsonld +137 -0
- package/external/aport-spec/oap/vc/examples/oap-decision-vc.json +37 -0
- package/external/aport-spec/oap/vc/examples/oap-passport-vc.json +68 -0
- package/external/aport-spec/oap/vc/tools/INTEGRATION.md +375 -0
- package/external/aport-spec/oap/vc/tools/README.md +278 -0
- package/external/aport-spec/oap/vc/tools/examples/decision-to-vc.js +66 -0
- package/external/aport-spec/oap/vc/tools/examples/passport-to-vc.js +83 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-decision.js +77 -0
- package/external/aport-spec/oap/vc/tools/examples/vc-to-passport.js +94 -0
- package/external/aport-spec/oap/vc/tools/package.json +38 -0
- package/external/aport-spec/oap/vc/tools/pnpm-lock.yaml +472 -0
- package/external/aport-spec/oap/vc/tools/src/cli.ts +226 -0
- package/external/aport-spec/oap/vc/tools/src/crypto-utils.ts +427 -0
- package/external/aport-spec/oap/vc/tools/src/index.ts +653 -0
- package/external/aport-spec/oap/vc/tools/src/test.ts +148 -0
- package/external/aport-spec/oap/vc/tools/src/vp.ts +382 -0
- package/external/aport-spec/oap/vc/tools/test-simple.js +214 -0
- package/external/aport-spec/oap/vc/tools/tsconfig.json +19 -0
- package/external/aport-spec/oap/vc/vc-mapping.md +443 -0
- package/external/aport-spec/passport-schema.json +586 -0
- package/external/aport-spec/rate-limiting.md +136 -0
- package/external/aport-spec/transport-profile.md +325 -0
- package/external/aport-spec/webhook-spec.md +314 -0
- package/package.json +70 -0
- package/skills/aport-agent-guardrail/SKILL.md +314 -0
- package/src/evaluator.js +252 -0
- package/src/server/index.js +72 -0
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
# Payment Charge Policy Pack v1
|
|
2
|
+
|
|
3
|
+
Protect your payment charge endpoints with APort's standardized policy pack. This pack ensures only verified agents with proper capabilities, assurance levels, and transaction limits can initiate payments.
|
|
4
|
+
|
|
5
|
+
## What This Pack Protects
|
|
6
|
+
|
|
7
|
+
- **Route**: `/payments/charge/*` (POST)
|
|
8
|
+
- **Risk**: Financial transactions, fraud prevention, regulatory compliance
|
|
9
|
+
- **Impact**: Direct monetary loss, chargeback disputes, audit findings
|
|
10
|
+
|
|
11
|
+
## Requirements
|
|
12
|
+
|
|
13
|
+
| Requirement | Value | Description |
|
|
14
|
+
|-------------|-------|-------------|
|
|
15
|
+
| **Capability** | `payments.charge` | Agent must have charge capability |
|
|
16
|
+
| **Assurance** | `L2` or higher | Email + GitHub verification minimum |
|
|
17
|
+
| **Limits** | `currency_limits.{ISO4217}.{max_per_tx,daily_cap}` | Per-currency transaction and daily limits |
|
|
18
|
+
| **Limits** | `allowed_merchant_ids[]` | Merchant allowlist (optional) |
|
|
19
|
+
| **Limits** | `allowed_countries[]` | Country allowlist (optional) |
|
|
20
|
+
| **Limits** | `blocked_categories[]` | Category blocklist (optional) |
|
|
21
|
+
| **Limits** | `max_items_per_tx` | Maximum items per transaction (optional) |
|
|
22
|
+
| **Regions** | Must match | Agent must be authorized in caller's region |
|
|
23
|
+
| **Idempotency** | Required | Prevents duplicate charges |
|
|
24
|
+
|
|
25
|
+
## Implementation
|
|
26
|
+
|
|
27
|
+
### Express.js
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const { requirePolicy } = require('@aporthq/middleware-express');
|
|
31
|
+
|
|
32
|
+
// Option 1: Explicit agent ID (preferred)
|
|
33
|
+
app.post('/payments/charge',
|
|
34
|
+
requirePolicy('finance.payment.charge.v1', 'ap_a2d10232c6534523812423eec8a1425c45678'),
|
|
35
|
+
async (req, res) => {
|
|
36
|
+
// Your charge logic here
|
|
37
|
+
// req.policyResult contains the verified passport
|
|
38
|
+
const { amount, currency, merchant_id, items } = req.body;
|
|
39
|
+
const passport = req.policyResult.passport;
|
|
40
|
+
|
|
41
|
+
// Process charge...
|
|
42
|
+
res.json({ success: true, charge_id: generateId() });
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Option 2: Header fallback (backward compatible)
|
|
47
|
+
app.post('/payments/charge',
|
|
48
|
+
requirePolicy('finance.payment.charge.v1'),
|
|
49
|
+
async (req, res) => {
|
|
50
|
+
// Your charge logic here
|
|
51
|
+
// req.policyResult contains the verified passport
|
|
52
|
+
const { amount, currency, merchant_id, items } = req.body;
|
|
53
|
+
const passport = req.policyResult.passport;
|
|
54
|
+
|
|
55
|
+
// Process charge...
|
|
56
|
+
res.json({ success: true, charge_id: generateId() });
|
|
57
|
+
}
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Client Request Example:**
|
|
62
|
+
```javascript
|
|
63
|
+
// The client must include the agent ID in the header (for Option 2)
|
|
64
|
+
fetch('/payments/charge', {
|
|
65
|
+
method: 'POST',
|
|
66
|
+
headers: {
|
|
67
|
+
'Content-Type': 'application/json',
|
|
68
|
+
'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' // ← Agent ID passed here
|
|
69
|
+
},
|
|
70
|
+
body: JSON.stringify({
|
|
71
|
+
amount: 1299,
|
|
72
|
+
currency: "USD",
|
|
73
|
+
merchant_id: "merch_abc",
|
|
74
|
+
region: "US",
|
|
75
|
+
shipping_country: "US",
|
|
76
|
+
items: [
|
|
77
|
+
{ sku: "SKU-1", qty: 1, category: "electronics" }
|
|
78
|
+
],
|
|
79
|
+
idempotency_key: "charge-ord-123"
|
|
80
|
+
})
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### FastAPI
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
from aport.middleware import require_policy
|
|
88
|
+
|
|
89
|
+
@app.post("/payments/charge")
|
|
90
|
+
@require_policy("finance.payment.charge.v1")
|
|
91
|
+
async def process_charge(request: Request, charge_data: ChargeRequest):
|
|
92
|
+
# Your charge logic here
|
|
93
|
+
# request.state.policy_result contains the verified passport
|
|
94
|
+
passport = request.state.policy_result.passport
|
|
95
|
+
|
|
96
|
+
# Process charge...
|
|
97
|
+
return {"success": True, "charge_id": generate_id()}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**Client Request Example:**
|
|
101
|
+
```python
|
|
102
|
+
import requests
|
|
103
|
+
|
|
104
|
+
# The client must include the agent ID in the header
|
|
105
|
+
response = requests.post('/payments/charge',
|
|
106
|
+
headers={
|
|
107
|
+
'Content-Type': 'application/json',
|
|
108
|
+
'X-Agent-Passport-Id': 'ap_a2d10232c6534523812423eec8a1425c45678' # ← Agent ID passed here
|
|
109
|
+
},
|
|
110
|
+
json={
|
|
111
|
+
'amount': 1299,
|
|
112
|
+
'currency': 'USD',
|
|
113
|
+
'merchant_id': 'merch_abc',
|
|
114
|
+
'region': 'US',
|
|
115
|
+
'shipping_country': 'US',
|
|
116
|
+
'items': [
|
|
117
|
+
{'sku': 'SKU-1', 'qty': 1, 'category': 'electronics'}
|
|
118
|
+
],
|
|
119
|
+
'idempotency_key': 'charge-ord-123'
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## How It Works
|
|
125
|
+
|
|
126
|
+
The `requirePolicy('finance.payment.charge.v1', agentId?)` middleware implements a flexible approach:
|
|
127
|
+
|
|
128
|
+
1. **Agent ID Resolution** (in order of preference):
|
|
129
|
+
- **Function Parameter**: Uses explicit `agentId` if provided
|
|
130
|
+
- **Header Fallback**: Reads `X-Agent-Passport-Id` header from request
|
|
131
|
+
- **Validation**: Ensures agent ID format is valid (`ap_xxxxxxxxxxxxx`)
|
|
132
|
+
- **Failure**: Returns 400 error if neither provided
|
|
133
|
+
|
|
134
|
+
2. **Policy Validation**:
|
|
135
|
+
- **Format Check**: Validates policy ID format (`finance.payment.charge.v1`)
|
|
136
|
+
- **API Call**: Calls `/api/verify/policy/finance.payment.charge.v1` with agent ID and context
|
|
137
|
+
- **Requirements Check**: Validates agent meets finance.payment.charge.v1 requirements:
|
|
138
|
+
- Has `payments.charge` capability
|
|
139
|
+
- Meets minimum assurance level (L2+)
|
|
140
|
+
- Within per-currency transaction and daily limits
|
|
141
|
+
- Merchant is allowed (if allowlist configured)
|
|
142
|
+
- Country is allowed (if allowlist configured)
|
|
143
|
+
- No blocked categories in items
|
|
144
|
+
- Item count within limits
|
|
145
|
+
- Idempotency key is unique
|
|
146
|
+
- Authorized in the request region
|
|
147
|
+
|
|
148
|
+
3. **Result Handling**:
|
|
149
|
+
- **Success**: Adds `req.policyResult` with verified data and continues
|
|
150
|
+
- **Failure**: Returns 403 with detailed error information
|
|
151
|
+
- **Logging**: Logs violations for monitoring and debugging
|
|
152
|
+
|
|
153
|
+
## Error Responses
|
|
154
|
+
|
|
155
|
+
When policy checks fail, you'll receive a `403 Forbidden` with detailed error information:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"error": "policy_violation",
|
|
160
|
+
"code": "oap.limit_exceeded",
|
|
161
|
+
"message": "Amount $25.00 exceeds per-transaction limit $20.00",
|
|
162
|
+
"policy_id": "finance.payment.charge.v1",
|
|
163
|
+
"agent_id": "ap_a2d10232c6534523812423eec8a1425c45678",
|
|
164
|
+
"upgrade_instructions": "Request higher limits in your passport"
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Test Payloads
|
|
169
|
+
|
|
170
|
+
### Valid Request
|
|
171
|
+
```json
|
|
172
|
+
{
|
|
173
|
+
"amount": 1299,
|
|
174
|
+
"currency": "USD",
|
|
175
|
+
"merchant_id": "merch_abc",
|
|
176
|
+
"region": "US",
|
|
177
|
+
"shipping_country": "US",
|
|
178
|
+
"items": [
|
|
179
|
+
{ "sku": "SKU-1", "qty": 1, "category": "electronics" }
|
|
180
|
+
],
|
|
181
|
+
"idempotency_key": "charge-ord-123"
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Invalid Request (exceeds limit)
|
|
186
|
+
```json
|
|
187
|
+
{
|
|
188
|
+
"amount": 25000,
|
|
189
|
+
"currency": "USD",
|
|
190
|
+
"merchant_id": "merch_abc",
|
|
191
|
+
"region": "US",
|
|
192
|
+
"items": [
|
|
193
|
+
{ "sku": "SKU-1", "qty": 1, "category": "electronics" }
|
|
194
|
+
],
|
|
195
|
+
"idempotency_key": "charge-ord-124"
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
*Returns 403: "Amount $250.00 exceeds per-transaction limit $200.00"*
|
|
199
|
+
|
|
200
|
+
## Best Practices
|
|
201
|
+
|
|
202
|
+
1. **Cache Verification**: Cache `/verify` responses with ETag for 60 seconds
|
|
203
|
+
2. **Webhook Integration**: Subscribe to `status.changed` webhooks for instant suspension
|
|
204
|
+
3. **Verifiable Attestation**: Log all charge attempts for compliance
|
|
205
|
+
4. **Daily Tracking**: Implement daily spend tracking per currency to prevent abuse
|
|
206
|
+
5. **Idempotency**: Always use unique idempotency keys to prevent duplicate charges
|
|
207
|
+
6. **Error Handling**: Provide clear error messages to help agents self-remediate
|
|
208
|
+
7. **Merchant Validation**: Maintain merchant allowlists for trusted partners
|
|
209
|
+
8. **Category Filtering**: Block high-risk categories (weapons, illicit goods)
|
|
210
|
+
|
|
211
|
+
## Compliance Badge
|
|
212
|
+
|
|
213
|
+
Agents that meet this policy's requirements can display the "Charge-Ready" badge:
|
|
214
|
+
|
|
215
|
+
```markdown
|
|
216
|
+
[](https://aport.io/agents/ap_a2d10232c6534523812423eec8a1425c45678)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
## Support
|
|
220
|
+
|
|
221
|
+
- [Documentation](https://aport.io/docs/policies/finance.payment.charge.v1)
|
|
222
|
+
- [Community](https://github.com/aporthq/community)
|
|
223
|
+
- [Support](https://aport.io/support)
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
**Last Updated**: 2025-09-30 00:00:00 UTC
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
## Required Context
|
|
230
|
+
|
|
231
|
+
This policy requires the following context (JSON Schema):
|
|
232
|
+
|
|
233
|
+
```json
|
|
234
|
+
{
|
|
235
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
236
|
+
"type": "object",
|
|
237
|
+
"required": [
|
|
238
|
+
"amount",
|
|
239
|
+
"currency",
|
|
240
|
+
"merchant_id",
|
|
241
|
+
"region",
|
|
242
|
+
"items",
|
|
243
|
+
"idempotency_key"
|
|
244
|
+
],
|
|
245
|
+
"properties": {
|
|
246
|
+
"amount": {
|
|
247
|
+
"type": "integer",
|
|
248
|
+
"minimum": 1,
|
|
249
|
+
"description": "Minor units (e.g., cents)"
|
|
250
|
+
},
|
|
251
|
+
"currency": {
|
|
252
|
+
"type": "string",
|
|
253
|
+
"pattern": "^[A-Z]{3}$",
|
|
254
|
+
"description": "ISO 4217 currency code"
|
|
255
|
+
},
|
|
256
|
+
"merchant_id": {
|
|
257
|
+
"type": "string",
|
|
258
|
+
"description": "Merchant identifier"
|
|
259
|
+
},
|
|
260
|
+
"region": {
|
|
261
|
+
"type": "string",
|
|
262
|
+
"description": "Geographic region"
|
|
263
|
+
},
|
|
264
|
+
"shipping_country": {
|
|
265
|
+
"type": "string",
|
|
266
|
+
"description": "Shipping country code"
|
|
267
|
+
},
|
|
268
|
+
"items": {
|
|
269
|
+
"type": "array",
|
|
270
|
+
"minItems": 1,
|
|
271
|
+
"description": "Array of items being purchased",
|
|
272
|
+
"items": {
|
|
273
|
+
"type": "object",
|
|
274
|
+
"required": [
|
|
275
|
+
"sku",
|
|
276
|
+
"qty",
|
|
277
|
+
"price"
|
|
278
|
+
],
|
|
279
|
+
"properties": {
|
|
280
|
+
"sku": {
|
|
281
|
+
"type": "string",
|
|
282
|
+
"description": "Stock keeping unit"
|
|
283
|
+
},
|
|
284
|
+
"qty": {
|
|
285
|
+
"type": "integer",
|
|
286
|
+
"minimum": 1,
|
|
287
|
+
"description": "Quantity"
|
|
288
|
+
},
|
|
289
|
+
"name": {
|
|
290
|
+
"type": "string",
|
|
291
|
+
"maxLength": 200,
|
|
292
|
+
"description": "Item name for audit and compliance"
|
|
293
|
+
},
|
|
294
|
+
"price": {
|
|
295
|
+
"type": "integer",
|
|
296
|
+
"minimum": 1,
|
|
297
|
+
"description": "Price in minor currency units (e.g., cents)"
|
|
298
|
+
},
|
|
299
|
+
"category": {
|
|
300
|
+
"type": "string",
|
|
301
|
+
"description": "Item category"
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"risk_score": {
|
|
307
|
+
"type": "number",
|
|
308
|
+
"minimum": 0,
|
|
309
|
+
"maximum": 100,
|
|
310
|
+
"description": "Risk score (0-100)"
|
|
311
|
+
},
|
|
312
|
+
"idempotency_key": {
|
|
313
|
+
"type": "string",
|
|
314
|
+
"minLength": 8,
|
|
315
|
+
"description": "Idempotency key for duplicate prevention"
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
You can also fetch this live via the discovery endpoint:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
curl -s "https://aport.io/api/policies/finance.payment.charge.v1?format=schema"
|
|
325
|
+
```
|
|
326
|
+
|
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const { requirePolicy } = require("@aporthq/middleware-express");
|
|
3
|
+
|
|
4
|
+
const app = express();
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
// Apply payments.charge policy to all charge routes
|
|
8
|
+
app.post(
|
|
9
|
+
"/payments/charge",
|
|
10
|
+
requirePolicy("finance.payment.charge.v1"),
|
|
11
|
+
async (req, res) => {
|
|
12
|
+
try {
|
|
13
|
+
const {
|
|
14
|
+
amount,
|
|
15
|
+
currency,
|
|
16
|
+
merchant_id,
|
|
17
|
+
region,
|
|
18
|
+
shipping_country,
|
|
19
|
+
items,
|
|
20
|
+
risk_score,
|
|
21
|
+
idempotency_key,
|
|
22
|
+
} = req.body;
|
|
23
|
+
|
|
24
|
+
const passport = req.policyResult.passport;
|
|
25
|
+
|
|
26
|
+
// Additional business logic validation
|
|
27
|
+
if (amount <= 0) {
|
|
28
|
+
return res.status(400).json({ error: "Invalid charge amount" });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check if items are provided
|
|
32
|
+
if (!items || items.length === 0) {
|
|
33
|
+
return res.status(400).json({ error: "Items are required" });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Process charge using your payment processor
|
|
37
|
+
const charge_id = await processCharge({
|
|
38
|
+
amount,
|
|
39
|
+
currency,
|
|
40
|
+
merchant_id,
|
|
41
|
+
region,
|
|
42
|
+
shipping_country,
|
|
43
|
+
items,
|
|
44
|
+
risk_score,
|
|
45
|
+
idempotency_key,
|
|
46
|
+
agent_id: passport.passport_id,
|
|
47
|
+
agent_name: passport.metadata?.template_name || "Unknown Agent",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Log the transaction
|
|
51
|
+
console.log(
|
|
52
|
+
`Charge processed: ${charge_id} for ${amount} ${currency} by agent ${passport.passport_id}`
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
res.json({
|
|
56
|
+
success: true,
|
|
57
|
+
charge_id,
|
|
58
|
+
amount,
|
|
59
|
+
currency,
|
|
60
|
+
status: "processed",
|
|
61
|
+
decision_id: req.policyResult.decision_id,
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error("Charge processing error:", error);
|
|
65
|
+
res.status(500).json({ error: "Internal server error" });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Batch charges endpoint
|
|
71
|
+
app.post(
|
|
72
|
+
"/payments/charge/batch",
|
|
73
|
+
requirePolicy("finance.payment.charge.v1"),
|
|
74
|
+
async (req, res) => {
|
|
75
|
+
try {
|
|
76
|
+
const { charges } = req.body;
|
|
77
|
+
const passport = req.policyResult.passport;
|
|
78
|
+
|
|
79
|
+
// Group charges by currency for daily cap checking
|
|
80
|
+
const currencyTotals = {};
|
|
81
|
+
for (const charge of charges) {
|
|
82
|
+
const currency = charge.currency || "USD";
|
|
83
|
+
currencyTotals[currency] =
|
|
84
|
+
(currencyTotals[currency] || 0) + (charge.amount || 0);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check daily caps per currency
|
|
88
|
+
for (const [currency, totalAmount] of Object.entries(currencyTotals)) {
|
|
89
|
+
const currencyLimits =
|
|
90
|
+
passport.limits?.payments?.charge?.currency_limits?.[currency];
|
|
91
|
+
if (
|
|
92
|
+
currencyLimits?.daily_cap &&
|
|
93
|
+
totalAmount > currencyLimits.daily_cap
|
|
94
|
+
) {
|
|
95
|
+
return res.status(403).json({
|
|
96
|
+
error: "Batch total exceeds daily cap",
|
|
97
|
+
currency,
|
|
98
|
+
total: totalAmount,
|
|
99
|
+
limit: currencyLimits.daily_cap,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Process batch charges
|
|
105
|
+
const results = await Promise.all(
|
|
106
|
+
charges.map((charge) =>
|
|
107
|
+
processCharge({
|
|
108
|
+
...charge,
|
|
109
|
+
agent_id: passport.passport_id,
|
|
110
|
+
})
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
res.json({
|
|
115
|
+
success: true,
|
|
116
|
+
processed: results.length,
|
|
117
|
+
currency_totals: currencyTotals,
|
|
118
|
+
decision_id: req.policyResult.decision_id,
|
|
119
|
+
});
|
|
120
|
+
} catch (error) {
|
|
121
|
+
console.error("Batch charge error:", error);
|
|
122
|
+
res.status(500).json({ error: "Internal server error" });
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
// Get charge status
|
|
128
|
+
app.get(
|
|
129
|
+
"/payments/charge/:charge_id",
|
|
130
|
+
requirePolicy("finance.payment.charge.v1"),
|
|
131
|
+
async (req, res) => {
|
|
132
|
+
try {
|
|
133
|
+
const { charge_id } = req.params;
|
|
134
|
+
const passport = req.policyResult.passport;
|
|
135
|
+
|
|
136
|
+
const charge_info = await getChargeStatus(
|
|
137
|
+
charge_id,
|
|
138
|
+
passport.passport_id
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
if (!charge_info) {
|
|
142
|
+
return res.status(404).json({ error: "Charge not found" });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
res.json(charge_info);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error("Charge status error:", error);
|
|
148
|
+
res.status(500).json({ error: "Internal server error" });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
// Refund charge endpoint
|
|
154
|
+
app.post(
|
|
155
|
+
"/payments/charge/:charge_id/refund",
|
|
156
|
+
requirePolicy("finance.payment.charge.v1"),
|
|
157
|
+
async (req, res) => {
|
|
158
|
+
try {
|
|
159
|
+
const { charge_id } = req.params;
|
|
160
|
+
const { amount, reason } = req.body;
|
|
161
|
+
const passport = req.policyResult.passport;
|
|
162
|
+
|
|
163
|
+
const refund_id = await processRefund({
|
|
164
|
+
charge_id,
|
|
165
|
+
amount,
|
|
166
|
+
reason,
|
|
167
|
+
agent_id: passport.passport_id,
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
res.json({
|
|
171
|
+
success: true,
|
|
172
|
+
refund_id,
|
|
173
|
+
charge_id,
|
|
174
|
+
amount,
|
|
175
|
+
status: "refunded",
|
|
176
|
+
decision_id: req.policyResult.decision_id,
|
|
177
|
+
});
|
|
178
|
+
} catch (error) {
|
|
179
|
+
console.error("Refund processing error:", error);
|
|
180
|
+
res.status(500).json({ error: "Internal server error" });
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
// Mock charge processing function
|
|
186
|
+
async function processCharge({
|
|
187
|
+
amount,
|
|
188
|
+
currency,
|
|
189
|
+
merchant_id,
|
|
190
|
+
region,
|
|
191
|
+
shipping_country,
|
|
192
|
+
items,
|
|
193
|
+
risk_score,
|
|
194
|
+
idempotency_key,
|
|
195
|
+
agent_id,
|
|
196
|
+
}) {
|
|
197
|
+
// Simulate payment processor call
|
|
198
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
199
|
+
|
|
200
|
+
// Log charge details for audit
|
|
201
|
+
console.log(`Processing charge:`, {
|
|
202
|
+
amount,
|
|
203
|
+
currency,
|
|
204
|
+
merchant_id,
|
|
205
|
+
region,
|
|
206
|
+
shipping_country,
|
|
207
|
+
items: items?.length || 0,
|
|
208
|
+
risk_score,
|
|
209
|
+
idempotency_key,
|
|
210
|
+
agent_id,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
return `chg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Mock charge status lookup
|
|
217
|
+
async function getChargeStatus(charge_id, agent_id) {
|
|
218
|
+
// Simulate charge status lookup
|
|
219
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
220
|
+
return {
|
|
221
|
+
charge_id,
|
|
222
|
+
status: "completed",
|
|
223
|
+
created_at: new Date().toISOString(),
|
|
224
|
+
amount: 1299,
|
|
225
|
+
currency: "USD",
|
|
226
|
+
merchant_id: "merch_abc",
|
|
227
|
+
items: [{ sku: "SKU-1", qty: 1, category: "electronics" }],
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Mock refund processing function
|
|
232
|
+
async function processRefund({ charge_id, amount, reason, agent_id }) {
|
|
233
|
+
// Simulate refund processing
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
235
|
+
|
|
236
|
+
console.log(`Processing refund:`, {
|
|
237
|
+
charge_id,
|
|
238
|
+
amount,
|
|
239
|
+
reason,
|
|
240
|
+
agent_id,
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
return `ref_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const PORT = process.env.PORT || 3000;
|
|
247
|
+
app.listen(PORT, () => {
|
|
248
|
+
console.log(`Payment charge service running on port ${PORT}`);
|
|
249
|
+
console.log("Protected by APort finance.payment.charge.v1 policy pack");
|
|
250
|
+
});
|