@longarc/mdash 3.1.1 → 3.1.3
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/README.md +86 -23
- package/SECURITY.md +254 -0
- package/dist/accountability/engine.d.ts +27 -0
- package/dist/accountability/engine.d.ts.map +1 -0
- package/dist/accountability/engine.js +148 -0
- package/dist/accountability/engine.js.map +1 -0
- package/dist/accountability/types.d.ts +46 -0
- package/dist/accountability/types.d.ts.map +1 -0
- package/dist/accountability/types.js +8 -0
- package/dist/accountability/types.js.map +1 -0
- package/dist/checkpoint/engine.d.ts +2 -2
- package/dist/checkpoint/engine.d.ts.map +1 -1
- package/dist/checkpoint/engine.js +5 -1
- package/dist/checkpoint/engine.js.map +1 -1
- package/dist/context/compose.d.ts +62 -0
- package/dist/context/compose.d.ts.map +1 -0
- package/dist/context/compose.js +286 -0
- package/dist/context/compose.js.map +1 -0
- package/dist/context/crypto/hash.d.ts +100 -0
- package/dist/context/crypto/hash.d.ts.map +1 -0
- package/dist/context/crypto/hash.js +248 -0
- package/dist/context/crypto/hash.js.map +1 -0
- package/dist/context/crypto/hmac.d.ts +80 -0
- package/dist/context/crypto/hmac.d.ts.map +1 -0
- package/dist/context/crypto/hmac.js +192 -0
- package/dist/context/crypto/hmac.js.map +1 -0
- package/dist/context/crypto/index.d.ts +7 -0
- package/dist/context/crypto/index.d.ts.map +1 -0
- package/dist/context/crypto/index.js +7 -0
- package/dist/context/crypto/index.js.map +1 -0
- package/dist/context/engine-v3.0-backup.d.ts +197 -0
- package/dist/context/engine-v3.0-backup.d.ts.map +1 -0
- package/dist/context/engine-v3.0-backup.js +392 -0
- package/dist/context/engine-v3.0-backup.js.map +1 -0
- package/dist/context/engine.d.ts +2 -2
- package/dist/context/engine.d.ts.map +1 -1
- package/dist/context/engine.js +2 -2
- package/dist/context/engine.js.map +1 -1
- package/dist/context/fragment.d.ts +99 -0
- package/dist/context/fragment.d.ts.map +1 -0
- package/dist/context/fragment.js +316 -0
- package/dist/context/fragment.js.map +1 -0
- package/dist/context/index.d.ts +99 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +180 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/provenance.d.ts +80 -0
- package/dist/context/provenance.d.ts.map +1 -0
- package/dist/context/provenance.js +294 -0
- package/dist/context/provenance.js.map +1 -0
- package/dist/context/resolve.d.ts +106 -0
- package/dist/context/resolve.d.ts.map +1 -0
- package/dist/context/resolve.js +440 -0
- package/dist/context/resolve.js.map +1 -0
- package/dist/context/store.d.ts +156 -0
- package/dist/context/store.d.ts.map +1 -0
- package/dist/context/store.js +396 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/types.d.ts +463 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +94 -0
- package/dist/context/types.js.map +1 -0
- package/dist/context/utils/atomic.d.ts +76 -0
- package/dist/context/utils/atomic.d.ts.map +1 -0
- package/dist/context/utils/atomic.js +159 -0
- package/dist/context/utils/atomic.js.map +1 -0
- package/dist/context/utils/credit.d.ts +65 -0
- package/dist/context/utils/credit.d.ts.map +1 -0
- package/dist/context/utils/credit.js +164 -0
- package/dist/context/utils/credit.js.map +1 -0
- package/dist/context/utils/index.d.ts +13 -0
- package/dist/context/utils/index.d.ts.map +1 -0
- package/dist/context/utils/index.js +13 -0
- package/dist/context/utils/index.js.map +1 -0
- package/dist/context/utils/utility.d.ts +63 -0
- package/dist/context/utils/utility.d.ts.map +1 -0
- package/dist/context/utils/utility.js +141 -0
- package/dist/context/utils/utility.js.map +1 -0
- package/dist/core/commitment.d.ts +26 -3
- package/dist/core/commitment.d.ts.map +1 -1
- package/dist/core/commitment.js +45 -7
- package/dist/core/commitment.js.map +1 -1
- package/dist/core/crypto.d.ts +2 -0
- package/dist/core/crypto.d.ts.map +1 -1
- package/dist/core/crypto.js +12 -0
- package/dist/core/crypto.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -10
- package/dist/index.js.map +1 -1
- package/dist/mcca/engine.d.ts.map +1 -1
- package/dist/mcca/engine.js +5 -4
- package/dist/mcca/engine.js.map +1 -1
- package/dist/physics/engine.d.ts +3 -2
- package/dist/physics/engine.d.ts.map +1 -1
- package/dist/physics/engine.js +37 -3
- package/dist/physics/engine.js.map +1 -1
- package/dist/provenance/api-handler.d.ts +45 -0
- package/dist/provenance/api-handler.d.ts.map +1 -0
- package/dist/provenance/api-handler.js +223 -0
- package/dist/provenance/api-handler.js.map +1 -0
- package/dist/provenance/api-types.d.ts +108 -0
- package/dist/provenance/api-types.d.ts.map +1 -0
- package/dist/provenance/api-types.js +9 -0
- package/dist/provenance/api-types.js.map +1 -0
- package/dist/provenance/index.d.ts +6 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +3 -0
- package/dist/provenance/index.js.map +1 -0
- package/dist/provenance/provenance-engine.d.ts +63 -0
- package/dist/provenance/provenance-engine.d.ts.map +1 -0
- package/dist/provenance/provenance-engine.js +311 -0
- package/dist/provenance/provenance-engine.js.map +1 -0
- package/dist/provenance/types.d.ts +193 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +9 -0
- package/dist/provenance/types.js.map +1 -0
- package/dist/tee/engine.d.ts.map +1 -1
- package/dist/tee/engine.js +14 -0
- package/dist/tee/engine.js.map +1 -1
- package/dist/warrant/engine.d.ts +24 -1
- package/dist/warrant/engine.d.ts.map +1 -1
- package/dist/warrant/engine.js +76 -1
- package/dist/warrant/engine.js.map +1 -1
- package/dist/zk/engine.d.ts.map +1 -1
- package/dist/zk/engine.js +7 -4
- package/dist/zk/engine.js.map +1 -1
- package/docs/SECURITY-PATCHES.md +170 -0
- package/package.json +17 -5
- package/src/__tests__/accountability.test.ts +308 -0
- package/src/__tests__/l1-verification-modes.test.ts +424 -0
- package/src/__tests__/phase1.benchmark.test.ts +94 -0
- package/src/__tests__/phase1.test.ts +0 -77
- package/src/__tests__/phase2-4.benchmark.test.ts +60 -0
- package/src/__tests__/phase2-4.test.ts +1 -52
- package/src/__tests__/provenance/api-handler.test.ts +356 -0
- package/src/__tests__/provenance/provenance-engine.test.ts +628 -0
- package/src/__tests__/sa-2026-008.test.ts +45 -0
- package/src/__tests__/sa-2026-009.test.ts +86 -0
- package/src/__tests__/sa-2026-010.test.ts +72 -0
- package/src/__tests__/sa-2026-012.test.ts +65 -0
- package/src/__tests__/sa-2026-nfc.test.ts +40 -0
- package/src/__tests__/security.test.ts +786 -0
- package/src/accountability/engine.ts +230 -0
- package/src/accountability/types.ts +58 -0
- package/src/checkpoint/engine.ts +6 -2
- package/src/context/__tests__/caret-v0.2.0.test.ts +860 -0
- package/src/context/__tests__/integration.test.ts +356 -0
- package/src/context/compose.ts +388 -0
- package/src/context/crypto/hash.ts +277 -0
- package/src/context/crypto/hmac.ts +253 -0
- package/src/context/crypto/index.ts +29 -0
- package/src/context/engine-v3.0-backup.ts +598 -0
- package/src/context/engine.ts +2 -2
- package/src/context/fragment.ts +454 -0
- package/src/context/index.ts +427 -0
- package/src/context/provenance.ts +380 -0
- package/src/context/resolve.ts +581 -0
- package/src/context/store.ts +503 -0
- package/src/context/types.ts +679 -0
- package/src/context/utils/atomic.ts +207 -0
- package/src/context/utils/credit.ts +224 -0
- package/src/context/utils/index.ts +13 -0
- package/src/context/utils/utility.ts +200 -0
- package/src/core/commitment.ts +130 -68
- package/src/core/crypto.ts +13 -0
- package/src/index.ts +62 -10
- package/src/mcca/engine.ts +5 -4
- package/src/physics/engine.ts +42 -5
- package/src/provenance/api-handler.ts +248 -0
- package/src/provenance/api-types.ts +112 -0
- package/src/provenance/index.ts +19 -0
- package/src/provenance/provenance-engine.ts +387 -0
- package/src/provenance/types.ts +211 -0
- package/src/tee/engine.ts +16 -0
- package/src/warrant/engine.ts +89 -1
- package/src/zk/engine.ts +8 -4
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
# mdash v3.0 Security Patches
|
|
2
|
+
|
|
3
|
+
Applied: 2026-01-26
|
|
4
|
+
Auditor: Kai (Claude + security-reasoning skill)
|
|
5
|
+
|
|
6
|
+
## Summary
|
|
7
|
+
|
|
8
|
+
| Priority | Issue | Status | File(s) |
|
|
9
|
+
|----------|-------|--------|---------|
|
|
10
|
+
| **P1** | TOCTOU in execute flow | ✅ Fixed | `index.ts`, `warrant-engine.ts` |
|
|
11
|
+
| **P2a** | Cache seal verification | ✅ Fixed | `warrant-engine.ts` |
|
|
12
|
+
| **P2b** | Simulated TEE detection | ✅ Fixed | `tee-engine.ts` |
|
|
13
|
+
| **P3a** | Error message sanitization | ✅ Fixed | `index.ts` |
|
|
14
|
+
| **P3b** | Warrant creation rate limiting | ✅ Fixed | `warrant-engine.ts` |
|
|
15
|
+
| **P4** | Constraint presence validation | ✅ Fixed | `physics-engine.ts` |
|
|
16
|
+
| **P5** | Minimum seal key length | ✅ Fixed | `crypto.ts` |
|
|
17
|
+
|
|
18
|
+
**Test Results:** 251 passed (including 33 security tests)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Security Test Suite Added
|
|
23
|
+
|
|
24
|
+
New `security.test.ts` with comprehensive coverage:
|
|
25
|
+
|
|
26
|
+
### Adversarial Input Testing
|
|
27
|
+
- Prototype pollution resistance (`__proto__`, `constructor`)
|
|
28
|
+
- Nested pollution handling
|
|
29
|
+
- Boundary value fuzzing (MAX_SAFE_INTEGER, negative, NaN, Infinity)
|
|
30
|
+
- Timestamp manipulation
|
|
31
|
+
- String injection (SQL-like, null bytes, unicode normalization)
|
|
32
|
+
|
|
33
|
+
### Concurrent Access Testing (TOCTOU Verification)
|
|
34
|
+
- Concurrent warrant activations
|
|
35
|
+
- Revocation during execute flow
|
|
36
|
+
- High concurrency serialization
|
|
37
|
+
- Rate limiting under burst (100/min enforced)
|
|
38
|
+
|
|
39
|
+
### Replay Attack Resistance
|
|
40
|
+
- Expired attestation detection
|
|
41
|
+
- Tampered seal detection
|
|
42
|
+
- Missing L1 commitment detection
|
|
43
|
+
- Unique ID generation
|
|
44
|
+
- Timestamp verification
|
|
45
|
+
|
|
46
|
+
### Cross-Layer Failure Handling
|
|
47
|
+
- L1/L2 commitment reference verification
|
|
48
|
+
- Cross-layer reference mismatch detection
|
|
49
|
+
- Physics validation failure propagation
|
|
50
|
+
- Execute function failure handling
|
|
51
|
+
- Audit trail persistence on failure
|
|
52
|
+
|
|
53
|
+
### Security Edge Cases
|
|
54
|
+
- Empty/short seal key rejection (32 char minimum)
|
|
55
|
+
- Long agent ID handling (10,000 chars)
|
|
56
|
+
- Special characters in policy ID
|
|
57
|
+
- Constant-time seal comparison
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## P1: TOCTOU Fix (Critical)
|
|
62
|
+
|
|
63
|
+
**Problem:** Race condition between warrant authorization check and action execution.
|
|
64
|
+
|
|
65
|
+
**Fix:** Added warrant revocation re-check immediately before `params.execute()` call.
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// In index.ts execute() method, before calling params.execute():
|
|
69
|
+
if (this.warrant.isRevoked(warrant.id)) {
|
|
70
|
+
await this.checkpoint.onError({...});
|
|
71
|
+
throw new Error('Authorization denied');
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## P2a: Cache Seal Verification
|
|
78
|
+
|
|
79
|
+
**Problem:** Cache retrieval lacked seal verification.
|
|
80
|
+
|
|
81
|
+
**Fix:** Added `getVerified()` method with HMAC verification.
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## P2b: Simulated TEE Detection
|
|
86
|
+
|
|
87
|
+
**Problem:** Silent fallback to simulated mode in production.
|
|
88
|
+
|
|
89
|
+
**Fix:** Hard failure in `NODE_ENV=production` without explicit override.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## P3a: Error Message Sanitization
|
|
94
|
+
|
|
95
|
+
**Problem:** Errors leaked internal state.
|
|
96
|
+
|
|
97
|
+
**Fix:** Generic `"Authorization denied"` for all auth failures.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## P3b: Warrant Creation Rate Limiting
|
|
102
|
+
|
|
103
|
+
**Problem:** Unbounded warrant creation.
|
|
104
|
+
|
|
105
|
+
**Fix:** 100 warrants/minute per issuer.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## P4: Constraint Presence Validation
|
|
110
|
+
|
|
111
|
+
**Problem:** Nullish coalescing bypass with explicit undefined.
|
|
112
|
+
|
|
113
|
+
**Fix:** `Object.prototype.hasOwnProperty.call()` checks.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## P5: Minimum Seal Key Length (NEW)
|
|
118
|
+
|
|
119
|
+
**Problem:** Empty or weak seal keys accepted.
|
|
120
|
+
|
|
121
|
+
**Fix:** Enforce 32 character minimum in `deriveKey()`.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
if (!masterKey || masterKey.length < 32) {
|
|
125
|
+
throw new Error('Seal key must be at least 32 characters');
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## Files Included
|
|
132
|
+
|
|
133
|
+
1. `index.ts` - Protocol entry point with TOCTOU fix, error sanitization
|
|
134
|
+
2. `warrant-engine.ts` - isRevoked(), seal verification, rate limiting
|
|
135
|
+
3. `tee-engine.ts` - Simulated mode detection
|
|
136
|
+
4. `physics-engine.ts` - Constraint presence validation
|
|
137
|
+
5. `crypto.ts` - Minimum key length validation
|
|
138
|
+
6. `security.test.ts` - 33 new adversarial tests
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Application Instructions
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
# From mdash root
|
|
146
|
+
cp mdash-security-patches/index.ts src/index.ts
|
|
147
|
+
cp mdash-security-patches/warrant-engine.ts src/warrant/engine.ts
|
|
148
|
+
cp mdash-security-patches/tee-engine.ts src/tee/engine.ts
|
|
149
|
+
cp mdash-security-patches/physics-engine.ts src/physics/engine.ts
|
|
150
|
+
cp mdash-security-patches/crypto.ts src/core/crypto.ts
|
|
151
|
+
cp mdash-security-patches/security.test.ts src/__tests__/security.test.ts
|
|
152
|
+
|
|
153
|
+
# Also update phase2-4 test key (was 31 chars, now needs 32)
|
|
154
|
+
# In src/__tests__/phase2-4.test.ts, change:
|
|
155
|
+
# const TEST_SEAL_KEY = 'test-seal-key-phase-2-4-v3.0.0';
|
|
156
|
+
# to:
|
|
157
|
+
# const TEST_SEAL_KEY = 'test-seal-key-phase-2-4-v3.0.0-x';
|
|
158
|
+
|
|
159
|
+
npm test # Expect 157 passing
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## Remaining Considerations
|
|
165
|
+
|
|
166
|
+
1. **Warrant expiry during execution** - Current implementation may need stronger expiry checking mid-flow
|
|
167
|
+
2. **NaN handling in amounts** - NaN passes constraint checks (NaN > X is always false)
|
|
168
|
+
3. **Unicode normalization** - Consider normalizing strings before hashing for consistency
|
|
169
|
+
|
|
170
|
+
These are documented in the test suite with "documents current behavior" comments.
|
package/package.json
CHANGED
|
@@ -1,17 +1,28 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@longarc/mdash",
|
|
3
|
-
"version": "3.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "3.1.3",
|
|
4
|
+
"description": "Cryptographic attestation protocol for autonomous AI agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
7
7
|
"type": "module",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./context": "./dist/context/index.js",
|
|
11
|
+
"./warrant": "./dist/warrant/engine.js",
|
|
12
|
+
"./physics": "./dist/physics/engine.js",
|
|
13
|
+
"./checkpoint": "./dist/checkpoint/engine.js",
|
|
14
|
+
"./mcca": "./dist/mcca/engine.js",
|
|
15
|
+
"./tee": "./dist/tee/engine.js",
|
|
16
|
+
"./zk": "./dist/zk/engine.js"
|
|
17
|
+
},
|
|
8
18
|
"scripts": {
|
|
9
19
|
"build": "tsc",
|
|
10
20
|
"test": "vitest run",
|
|
11
21
|
"test:watch": "vitest",
|
|
12
22
|
"test:coverage": "vitest run --coverage",
|
|
13
23
|
"lint": "eslint src --ext .ts",
|
|
14
|
-
"clean": "rm -rf dist"
|
|
24
|
+
"clean": "rm -rf dist",
|
|
25
|
+
"typecheck": "tsc --noEmit"
|
|
15
26
|
},
|
|
16
27
|
"keywords": [
|
|
17
28
|
"ai",
|
|
@@ -20,9 +31,10 @@
|
|
|
20
31
|
"attestation",
|
|
21
32
|
"cryptography",
|
|
22
33
|
"tee",
|
|
23
|
-
"zk-proofs"
|
|
34
|
+
"zk-proofs",
|
|
35
|
+
"warrant"
|
|
24
36
|
],
|
|
25
|
-
"author": "Long Arc Studios
|
|
37
|
+
"author": "Long Arc Studios",
|
|
26
38
|
"license": "MIT",
|
|
27
39
|
"engines": {
|
|
28
40
|
"node": ">=18.0.0"
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AccountabilityEngine Test Suite
|
|
3
|
+
*
|
|
4
|
+
* Full lifecycle: issue warrant → check action → commit attestation → verify
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
8
|
+
import { AccountabilityEngine } from '../accountability/engine.js';
|
|
9
|
+
import type { WarrantPermissions } from '../accountability/types.js';
|
|
10
|
+
|
|
11
|
+
const SEAL_KEY = 'test-seal-key-minimum-32-characters-long';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PERMISSIONS: WarrantPermissions = {
|
|
14
|
+
allowedActions: ['read', 'write', 'query'],
|
|
15
|
+
forbiddenActions: ['delete', 'drop-table'],
|
|
16
|
+
ttlMs: 60_000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
describe('AccountabilityEngine', () => {
|
|
20
|
+
let engine: AccountabilityEngine;
|
|
21
|
+
|
|
22
|
+
beforeEach(async () => {
|
|
23
|
+
engine = new AccountabilityEngine();
|
|
24
|
+
await engine.initialize(SEAL_KEY);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// ── Authorization ──
|
|
28
|
+
|
|
29
|
+
it('should execute authorized actions with cryptographic attestation', async () => {
|
|
30
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
31
|
+
|
|
32
|
+
const result = await engine.executeAction('agent-1', 'read', { file: 'data.csv' });
|
|
33
|
+
|
|
34
|
+
expect(result.executed).toBe(true);
|
|
35
|
+
expect(result.commitment).toBeDefined();
|
|
36
|
+
expect(result.commitment!.content_hash).toBeTruthy();
|
|
37
|
+
expect(result.commitment!.seal).toBeTruthy();
|
|
38
|
+
expect(result.verified).toBe(true);
|
|
39
|
+
expect(result.violation).toBeUndefined();
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('should verify commitment seals are cryptographically valid', async () => {
|
|
43
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
44
|
+
const result = await engine.executeAction('agent-1', 'read', {});
|
|
45
|
+
|
|
46
|
+
const valid = await engine.verifyCommitment(result.commitment!);
|
|
47
|
+
expect(valid).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('should handle multiple authorized actions with distinct commitments', async () => {
|
|
51
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
52
|
+
|
|
53
|
+
const r1 = await engine.executeAction('agent-1', 'read', { id: 1 });
|
|
54
|
+
const r2 = await engine.executeAction('agent-1', 'write', { id: 2 });
|
|
55
|
+
const r3 = await engine.executeAction('agent-1', 'query', { sql: 'SELECT 1' });
|
|
56
|
+
|
|
57
|
+
expect(r1.executed).toBe(true);
|
|
58
|
+
expect(r2.executed).toBe(true);
|
|
59
|
+
expect(r3.executed).toBe(true);
|
|
60
|
+
|
|
61
|
+
// Each gets a unique commitment
|
|
62
|
+
expect(r1.commitment!.id).not.toBe(r2.commitment!.id);
|
|
63
|
+
expect(r2.commitment!.id).not.toBe(r3.commitment!.id);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ── Denial: Forbidden Actions ──
|
|
67
|
+
|
|
68
|
+
it('should deny forbidden actions with typed violation', async () => {
|
|
69
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
70
|
+
|
|
71
|
+
const result = await engine.executeAction('agent-1', 'delete', { target: 'users' });
|
|
72
|
+
|
|
73
|
+
expect(result.executed).toBe(false);
|
|
74
|
+
expect(result.violation).toBeDefined();
|
|
75
|
+
expect(result.violation!.type).toBe('FORBIDDEN_ACTION');
|
|
76
|
+
expect(result.violation!.agentId).toBe('agent-1');
|
|
77
|
+
expect(result.violation!.action).toBe('delete');
|
|
78
|
+
expect(result.commitment).toBeUndefined();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should deny forbidden actions even if also in allowed list', async () => {
|
|
82
|
+
// Forbidden takes precedence
|
|
83
|
+
await engine.issueWarrant('agent-1', {
|
|
84
|
+
allowedActions: ['delete'],
|
|
85
|
+
forbiddenActions: ['delete'],
|
|
86
|
+
ttlMs: 60_000,
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
const result = await engine.executeAction('agent-1', 'delete', {});
|
|
90
|
+
expect(result.executed).toBe(false);
|
|
91
|
+
expect(result.violation!.type).toBe('FORBIDDEN_ACTION');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ── Denial: Unauthorized Actions ──
|
|
95
|
+
|
|
96
|
+
it('should deny actions not in the allowed list', async () => {
|
|
97
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
98
|
+
|
|
99
|
+
const result = await engine.executeAction('agent-1', 'execute-shell', { cmd: 'rm -rf /' });
|
|
100
|
+
|
|
101
|
+
expect(result.executed).toBe(false);
|
|
102
|
+
expect(result.violation!.type).toBe('UNAUTHORIZED_ACTION');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
// ── Denial: No Warrant ──
|
|
106
|
+
|
|
107
|
+
it('should deny actions from agents with no warrant', async () => {
|
|
108
|
+
const result = await engine.executeAction('unknown-agent', 'read', {});
|
|
109
|
+
|
|
110
|
+
expect(result.executed).toBe(false);
|
|
111
|
+
expect(result.violation!.type).toBe('NO_WARRANT');
|
|
112
|
+
expect(result.violation!.agentId).toBe('unknown-agent');
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── Denial: Expired Warrants ──
|
|
116
|
+
|
|
117
|
+
it('should deny actions when warrant has expired', async () => {
|
|
118
|
+
await engine.issueWarrant('agent-1', {
|
|
119
|
+
allowedActions: ['read'],
|
|
120
|
+
forbiddenActions: [],
|
|
121
|
+
ttlMs: 1, // 1ms TTL — will expire immediately
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// Wait for expiry
|
|
125
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
126
|
+
|
|
127
|
+
const result = await engine.executeAction('agent-1', 'read', {});
|
|
128
|
+
|
|
129
|
+
expect(result.executed).toBe(false);
|
|
130
|
+
expect(result.violation!.type).toBe('WARRANT_EXPIRED');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
// ── Denial: Revoked Warrants ──
|
|
134
|
+
|
|
135
|
+
it('should deny actions after warrant revocation', async () => {
|
|
136
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
137
|
+
|
|
138
|
+
// Authorized before revocation
|
|
139
|
+
const before = await engine.executeAction('agent-1', 'read', {});
|
|
140
|
+
expect(before.executed).toBe(true);
|
|
141
|
+
|
|
142
|
+
engine.revokeWarrant('agent-1');
|
|
143
|
+
|
|
144
|
+
const after = await engine.executeAction('agent-1', 'read', {});
|
|
145
|
+
expect(after.executed).toBe(false);
|
|
146
|
+
expect(after.violation!.type).toBe('WARRANT_REVOKED');
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
// ── Denial: Execution Limit ──
|
|
150
|
+
|
|
151
|
+
it('should deny actions when execution limit is reached', async () => {
|
|
152
|
+
await engine.issueWarrant('agent-1', {
|
|
153
|
+
allowedActions: ['read'],
|
|
154
|
+
forbiddenActions: [],
|
|
155
|
+
ttlMs: 60_000,
|
|
156
|
+
maxExecutions: 2,
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const r1 = await engine.executeAction('agent-1', 'read', { n: 1 });
|
|
160
|
+
const r2 = await engine.executeAction('agent-1', 'read', { n: 2 });
|
|
161
|
+
const r3 = await engine.executeAction('agent-1', 'read', { n: 3 });
|
|
162
|
+
|
|
163
|
+
expect(r1.executed).toBe(true);
|
|
164
|
+
expect(r2.executed).toBe(true);
|
|
165
|
+
expect(r3.executed).toBe(false);
|
|
166
|
+
expect(r3.violation!.type).toBe('EXECUTION_LIMIT_REACHED');
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// ── Audit Trail ──
|
|
170
|
+
|
|
171
|
+
it('should produce correct audit summary', async () => {
|
|
172
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
173
|
+
|
|
174
|
+
await engine.executeAction('agent-1', 'read', {});
|
|
175
|
+
await engine.executeAction('agent-1', 'write', {});
|
|
176
|
+
await engine.executeAction('agent-1', 'delete', {});
|
|
177
|
+
await engine.executeAction('agent-1', 'execute-shell', {});
|
|
178
|
+
|
|
179
|
+
const audit = engine.getAuditSummary();
|
|
180
|
+
|
|
181
|
+
expect(audit.total).toBe(4);
|
|
182
|
+
expect(audit.authorized).toBe(2);
|
|
183
|
+
expect(audit.denied).toBe(2);
|
|
184
|
+
expect(audit.log).toHaveLength(4);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should include commit details in audit records for attested actions', async () => {
|
|
188
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
189
|
+
await engine.executeAction('agent-1', 'read', { file: 'test.txt' });
|
|
190
|
+
|
|
191
|
+
const audit = engine.getAuditSummary();
|
|
192
|
+
const record = audit.log[0]!;
|
|
193
|
+
|
|
194
|
+
expect(record.type).toBe('ATTESTED_ACTION');
|
|
195
|
+
expect(record.commitId).toBeTruthy();
|
|
196
|
+
expect(record.contentHash).toBeTruthy();
|
|
197
|
+
expect(record.verified).toBe(true);
|
|
198
|
+
expect(record.warrantId).toBeTruthy();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('should include violation details in audit records for denied actions', async () => {
|
|
202
|
+
await engine.issueWarrant('agent-1', DEFAULT_PERMISSIONS);
|
|
203
|
+
await engine.executeAction('agent-1', 'delete', {});
|
|
204
|
+
|
|
205
|
+
const audit = engine.getAuditSummary();
|
|
206
|
+
const record = audit.log[0]!;
|
|
207
|
+
|
|
208
|
+
expect(record.type).toBe('FORBIDDEN_ACTION');
|
|
209
|
+
expect(record.detail).toContain('forbidden');
|
|
210
|
+
expect(record.warrantId).toBeTruthy();
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// ── Multi-Agent Isolation ──
|
|
214
|
+
|
|
215
|
+
it('should isolate warrants between agents', async () => {
|
|
216
|
+
await engine.issueWarrant('agent-a', {
|
|
217
|
+
allowedActions: ['read'],
|
|
218
|
+
forbiddenActions: ['write'],
|
|
219
|
+
ttlMs: 60_000,
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
await engine.issueWarrant('agent-b', {
|
|
223
|
+
allowedActions: ['write'],
|
|
224
|
+
forbiddenActions: ['read'],
|
|
225
|
+
ttlMs: 60_000,
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
// agent-a can read but not write
|
|
229
|
+
expect((await engine.executeAction('agent-a', 'read', {})).executed).toBe(true);
|
|
230
|
+
expect((await engine.executeAction('agent-a', 'write', {})).executed).toBe(false);
|
|
231
|
+
|
|
232
|
+
// agent-b can write but not read
|
|
233
|
+
expect((await engine.executeAction('agent-b', 'write', {})).executed).toBe(true);
|
|
234
|
+
expect((await engine.executeAction('agent-b', 'read', {})).executed).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// ── Full Lifecycle ──
|
|
238
|
+
|
|
239
|
+
it('should support the full lifecycle: issue → execute → deny → revoke → audit', async () => {
|
|
240
|
+
// Issue
|
|
241
|
+
await engine.issueWarrant('lifecycle-agent', {
|
|
242
|
+
allowedActions: ['query-database', 'read-file'],
|
|
243
|
+
forbiddenActions: ['delete-database'],
|
|
244
|
+
ttlMs: 60_000,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Execute authorized
|
|
248
|
+
const authorized = await engine.executeAction('lifecycle-agent', 'query-database', {
|
|
249
|
+
table: 'users',
|
|
250
|
+
limit: 100,
|
|
251
|
+
});
|
|
252
|
+
expect(authorized.executed).toBe(true);
|
|
253
|
+
expect(authorized.verified).toBe(true);
|
|
254
|
+
|
|
255
|
+
// Deny forbidden
|
|
256
|
+
const denied = await engine.executeAction('lifecycle-agent', 'delete-database', {
|
|
257
|
+
target: 'users',
|
|
258
|
+
});
|
|
259
|
+
expect(denied.executed).toBe(false);
|
|
260
|
+
expect(denied.violation!.type).toBe('FORBIDDEN_ACTION');
|
|
261
|
+
|
|
262
|
+
// Revoke
|
|
263
|
+
engine.revokeWarrant('lifecycle-agent');
|
|
264
|
+
|
|
265
|
+
// Deny after revocation
|
|
266
|
+
const postRevoke = await engine.executeAction('lifecycle-agent', 'read-file', {});
|
|
267
|
+
expect(postRevoke.executed).toBe(false);
|
|
268
|
+
expect(postRevoke.violation!.type).toBe('WARRANT_REVOKED');
|
|
269
|
+
|
|
270
|
+
// Audit
|
|
271
|
+
const audit = engine.getAuditSummary();
|
|
272
|
+
expect(audit.authorized).toBe(1);
|
|
273
|
+
expect(audit.denied).toBe(2);
|
|
274
|
+
expect(audit.total).toBe(3);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// ── Warrant checks happen BEFORE execution ──
|
|
278
|
+
|
|
279
|
+
it('should check warrant before creating commitment (no partial attestation on denial)', async () => {
|
|
280
|
+
// No warrant — should not create any commitment
|
|
281
|
+
const result = await engine.executeAction('no-warrant-agent', 'read', {});
|
|
282
|
+
|
|
283
|
+
expect(result.executed).toBe(false);
|
|
284
|
+
expect(result.commitment).toBeUndefined();
|
|
285
|
+
expect(result.verified).toBeUndefined();
|
|
286
|
+
|
|
287
|
+
// Audit should have zero attested actions
|
|
288
|
+
const audit = engine.getAuditSummary();
|
|
289
|
+
expect(audit.authorized).toBe(0);
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// ── Revoking nonexistent warrant is safe ──
|
|
293
|
+
|
|
294
|
+
it('should handle revoking a warrant that does not exist', () => {
|
|
295
|
+
// Should not throw
|
|
296
|
+
expect(() => engine.revokeWarrant('nonexistent-agent')).not.toThrow();
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// ── Audit starts empty ──
|
|
300
|
+
|
|
301
|
+
it('should return empty audit summary when no actions have been taken', () => {
|
|
302
|
+
const audit = engine.getAuditSummary();
|
|
303
|
+
expect(audit.total).toBe(0);
|
|
304
|
+
expect(audit.authorized).toBe(0);
|
|
305
|
+
expect(audit.denied).toBe(0);
|
|
306
|
+
expect(audit.log).toHaveLength(0);
|
|
307
|
+
});
|
|
308
|
+
});
|