@datacules/agent-identity-compliance 0.2.1
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 +188 -0
- package/bin/cli.js +319 -0
- package/package.json +43 -0
package/README.md
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# `@datacules/agent-identity-compliance`
|
|
2
|
+
|
|
3
|
+
Compliance report generation + tamper-evident audit log for [`@datacules/agent-identity`](../../core).
|
|
4
|
+
|
|
5
|
+
Answers regulatory audit questions directly from your audit logs — no custom queries.
|
|
6
|
+
Provides a SHA-256 hash chain logger and CLI verifier for SOC 2, GDPR, and HIPAA evidence.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @datacules/agent-identity-compliance
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Features
|
|
15
|
+
|
|
16
|
+
| Feature | Description |
|
|
17
|
+
|---------|-------------|
|
|
18
|
+
| `ComplianceReportGenerator` | Generate SOC 2 / GDPR / HIPAA reports from audit logs |
|
|
19
|
+
| `HashChainAuditLogger` | Wraps any audit sink — appends SHA-256 chain fields to every entry |
|
|
20
|
+
| `ChainVerifier` | Replays the chain and returns intact/broken status |
|
|
21
|
+
| CLI `agent-identity audit verify` | Verify a JSONL audit log file from the command line |
|
|
22
|
+
| CLI `agent-identity report` | Generate a compliance report from a JSONL audit log file |
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## Compliance Reports
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { ComplianceReportGenerator, MemoryReportStore } from '@datacules/agent-identity-compliance';
|
|
30
|
+
|
|
31
|
+
const generator = new ComplianceReportGenerator({
|
|
32
|
+
store: new MemoryReportStore(auditEntries), // or your own ReportStore
|
|
33
|
+
piiTags: ['pii', 'phi', 'personal', 'financial'],
|
|
34
|
+
businessHoursStart: 9,
|
|
35
|
+
businessHoursEnd: 18,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// SOC 2 CC6 — Logical and Physical Access Controls
|
|
39
|
+
const report = await generator.generate({
|
|
40
|
+
type: 'soc2',
|
|
41
|
+
from: '2026-01-01T00:00:00Z',
|
|
42
|
+
to: '2026-03-31T23:59:59Z',
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// GDPR Article 30 — Records of Processing Activities (Markdown output)
|
|
46
|
+
const gdprReport = await generator.generate({
|
|
47
|
+
type: 'gdpr',
|
|
48
|
+
from: '2026-01-01T00:00:00Z',
|
|
49
|
+
to: '2026-03-31T23:59:59Z',
|
|
50
|
+
format: 'markdown',
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
console.log(report.agentAccessSummary); // which agents used which credentials
|
|
54
|
+
console.log(report.piiResourceAccess); // all accesses to PII-tagged resources
|
|
55
|
+
console.log(report.offHoursAccess); // accesses outside business hours
|
|
56
|
+
console.log(report.credentialRotationHistory); // rotation events
|
|
57
|
+
console.log(report.anomalyEvents); // all flagged anomalies
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Report sections
|
|
61
|
+
|
|
62
|
+
| Section | Description |
|
|
63
|
+
|---------|-------------|
|
|
64
|
+
| `agentAccessSummary` | Per-agent resolution counts, credentials used, resources accessed |
|
|
65
|
+
| `piiResourceAccess` | All resolutions against resources tagged `pii`, `phi`, or `personal` |
|
|
66
|
+
| `offHoursAccess` | Resolutions outside configured business hours (includes weekends) |
|
|
67
|
+
| `credentialRotationHistory` | `credential.rotated` events — when, which credential |
|
|
68
|
+
| `anomalyEvents` | All `credential.anomaly` events with signal and severity |
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Tamper-Evident Audit Log (Hash Chain)
|
|
73
|
+
|
|
74
|
+
Wrap any existing audit logger to make every entry part of a SHA-256 linked chain:
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { HashChainAuditLogger } from '@datacules/agent-identity-compliance';
|
|
78
|
+
import { ConsoleAuditLogger } from '@datacules/agent-identity-audit';
|
|
79
|
+
import { createRouter } from '@datacules/agent-identity';
|
|
80
|
+
|
|
81
|
+
// 1. Wrap any existing logger
|
|
82
|
+
const base = new ConsoleAuditLogger();
|
|
83
|
+
const chained = new HashChainAuditLogger(base);
|
|
84
|
+
|
|
85
|
+
// 2. Use the chained logger with the router — everything else is unchanged
|
|
86
|
+
const router = createRouter(credentials, rules, chained);
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
The underlying sink receives entries with two extra fields:
|
|
90
|
+
|
|
91
|
+
```json
|
|
92
|
+
{
|
|
93
|
+
"userId": "user-abc",
|
|
94
|
+
"credentialId": "cred-openai",
|
|
95
|
+
"action": "read",
|
|
96
|
+
"timestamp": "2026-05-28T10:00:00.000Z",
|
|
97
|
+
"...": "...",
|
|
98
|
+
"prevHash": "a3f8...",
|
|
99
|
+
"hash": "9c12..."
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Any retroactive modification to any field in any entry breaks the chain from that point forward — detectable in O(n) time.
|
|
104
|
+
|
|
105
|
+
### Verifying the chain programmatically
|
|
106
|
+
|
|
107
|
+
```typescript
|
|
108
|
+
import { ChainVerifier } from '@datacules/agent-identity-compliance';
|
|
109
|
+
import { readFileSync } from 'node:fs';
|
|
110
|
+
|
|
111
|
+
const jsonl = readFileSync('./audit.jsonl', 'utf8');
|
|
112
|
+
const result = ChainVerifier.verifyJsonl(jsonl);
|
|
113
|
+
|
|
114
|
+
console.log(result.intact); // true / false
|
|
115
|
+
console.log(result.entryCount); // number of entries verified
|
|
116
|
+
console.log(result.rootHash); // SHA-256 of the last entry (publish to an anchor)
|
|
117
|
+
console.log(result.brokenAt); // entry index of first broken link (null if intact)
|
|
118
|
+
console.log(result.brokenReason); // human-readable reason (null if intact)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## CLI
|
|
124
|
+
|
|
125
|
+
The package ships a zero-dependency CLI (`agent-identity`) for offline log verification and report generation.
|
|
126
|
+
|
|
127
|
+
### Verify an audit log
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
agent-identity audit verify --file ./audit.jsonl
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Output:
|
|
134
|
+
```
|
|
135
|
+
Audit log verification — /path/to/audit.jsonl
|
|
136
|
+
Entries verified : 47382
|
|
137
|
+
Chain status : ✅ INTACT
|
|
138
|
+
Chain root hash : 9c12a3f8...b4e2
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
If a line has been modified:
|
|
142
|
+
```
|
|
143
|
+
Chain status : ❌ BROKEN
|
|
144
|
+
Broken at entry : 1204
|
|
145
|
+
Reason : Entry 1204: hash mismatch — entry data appears to have been modified
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Exit code 0 = intact, exit code 1 = broken or empty. Suitable for CI gates:
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
agent-identity audit verify --file ./audit.jsonl || { echo "Audit log tampered!"; exit 1; }
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Generate a compliance report
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
# SOC 2 CC6 — JSON output (default)
|
|
158
|
+
agent-identity report soc2 --file ./audit.jsonl
|
|
159
|
+
|
|
160
|
+
# GDPR Article 30 — Markdown, filtered to Q1 2026
|
|
161
|
+
agent-identity report gdpr \
|
|
162
|
+
--file ./audit.jsonl \
|
|
163
|
+
--from 2026-01-01 \
|
|
164
|
+
--to 2026-03-31 \
|
|
165
|
+
--format markdown
|
|
166
|
+
|
|
167
|
+
# HIPAA §164.312 — save to file
|
|
168
|
+
agent-identity report hipaa --file ./audit.jsonl > ./reports/hipaa-q2.json
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Custom ReportStore
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
import type { ReportStore } from '@datacules/agent-identity-compliance';
|
|
177
|
+
|
|
178
|
+
class PostgresReportStore implements ReportStore {
|
|
179
|
+
async queryEntries(from: string, to: string) {
|
|
180
|
+
return db.query(
|
|
181
|
+
'SELECT * FROM audit_log WHERE timestamp BETWEEN $1 AND $2 ORDER BY timestamp ASC',
|
|
182
|
+
[from, to]
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const generator = new ComplianceReportGenerator({ store: new PostgresReportStore() });
|
|
188
|
+
```
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* agent-identity CLI
|
|
4
|
+
*
|
|
5
|
+
* Commands:
|
|
6
|
+
*
|
|
7
|
+
* audit verify --file <path> [--quiet]
|
|
8
|
+
* Verify the SHA-256 chain of a JSONL audit log file.
|
|
9
|
+
* Exit 0 if intact, exit 1 if broken or empty.
|
|
10
|
+
*
|
|
11
|
+
* report <type> --file <path> [--from <ISO>] [--to <ISO>] [--format json|markdown]
|
|
12
|
+
* Generate a compliance report from a JSONL audit log file.
|
|
13
|
+
* <type> must be one of: soc2 | gdpr | hipaa | custom
|
|
14
|
+
* Output is written to stdout.
|
|
15
|
+
*
|
|
16
|
+
* Examples:
|
|
17
|
+
*
|
|
18
|
+
* agent-identity audit verify --file ./audit.jsonl
|
|
19
|
+
* agent-identity report soc2 --file ./audit.jsonl --format markdown
|
|
20
|
+
* agent-identity report gdpr --file ./audit.jsonl --from 2026-01-01 --to 2026-03-31
|
|
21
|
+
*/
|
|
22
|
+
'use strict';
|
|
23
|
+
|
|
24
|
+
const fs = require('node:fs');
|
|
25
|
+
const path = require('node:path');
|
|
26
|
+
|
|
27
|
+
// ─── Argument parsing ────────────────────────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
function parseArgs(argv) {
|
|
30
|
+
const args = {};
|
|
31
|
+
const positional = [];
|
|
32
|
+
for (let i = 0; i < argv.length; i++) {
|
|
33
|
+
const arg = argv[i];
|
|
34
|
+
if (arg.startsWith('--')) {
|
|
35
|
+
const key = arg.slice(2);
|
|
36
|
+
const next = argv[i + 1];
|
|
37
|
+
if (next && !next.startsWith('--')) {
|
|
38
|
+
args[key] = next;
|
|
39
|
+
i++;
|
|
40
|
+
} else {
|
|
41
|
+
args[key] = true;
|
|
42
|
+
}
|
|
43
|
+
} else {
|
|
44
|
+
positional.push(arg);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { args, positional };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
function die(msg) {
|
|
53
|
+
process.stderr.write(`\nError: ${msg}\n\n`);
|
|
54
|
+
printHelp();
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function printHelp() {
|
|
59
|
+
process.stdout.write(`
|
|
60
|
+
agent-identity — Datacules LLC
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
agent-identity audit verify --file <path> [--quiet]
|
|
64
|
+
agent-identity report <type> --file <path> [--from <ISO>] [--to <ISO>] [--format json|markdown]
|
|
65
|
+
|
|
66
|
+
Commands:
|
|
67
|
+
audit verify Verify the SHA-256 hash chain of a JSONL audit log file.
|
|
68
|
+
Exits 0 if intact, 1 if broken or empty.
|
|
69
|
+
|
|
70
|
+
report <type> Generate a compliance report from a JSONL audit log.
|
|
71
|
+
type: soc2 | gdpr | hipaa | custom
|
|
72
|
+
|
|
73
|
+
Options:
|
|
74
|
+
--file <path> Path to the JSONL audit log file (required)
|
|
75
|
+
--from <ISO> Report period start (ISO 8601, default: beginning of log)
|
|
76
|
+
--to <ISO> Report period end (ISO 8601, default: end of log)
|
|
77
|
+
--format Output format for report: json (default) | markdown
|
|
78
|
+
--quiet Suppress progress output (audit verify only)
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
agent-identity audit verify --file ./audit.jsonl
|
|
82
|
+
agent-identity report soc2 --file ./audit.jsonl --format markdown
|
|
83
|
+
agent-identity report gdpr --file ./audit.jsonl --from 2026-01-01 --to 2026-06-30
|
|
84
|
+
`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readJsonlFile(filePath) {
|
|
88
|
+
const resolved = path.resolve(filePath);
|
|
89
|
+
if (!fs.existsSync(resolved)) {
|
|
90
|
+
die(`File not found: ${resolved}`);
|
|
91
|
+
}
|
|
92
|
+
return fs.readFileSync(resolved, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ─── SHA-256 chain verification (inline — mirrors ChainVerifier from index.ts) ──
|
|
96
|
+
// We inline a plain-JS version so the CLI works without requiring the TS build.
|
|
97
|
+
|
|
98
|
+
const { createHash } = require('node:crypto');
|
|
99
|
+
|
|
100
|
+
function computeEntryHash(entry, prevHash) {
|
|
101
|
+
const { hash: _h, prevHash: _p, ...coreFields } = entry;
|
|
102
|
+
void _h; void _p;
|
|
103
|
+
const sortedKeys = Object.keys(coreFields).sort();
|
|
104
|
+
const payload = {};
|
|
105
|
+
for (const k of sortedKeys) payload[k] = coreFields[k];
|
|
106
|
+
return createHash('sha256')
|
|
107
|
+
.update(JSON.stringify(payload) + prevHash)
|
|
108
|
+
.digest('hex');
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function verifyJsonl(jsonl) {
|
|
112
|
+
const entries = [];
|
|
113
|
+
const lines = jsonl.split('\n');
|
|
114
|
+
for (let i = 0; i < lines.length; i++) {
|
|
115
|
+
const line = lines[i].trim();
|
|
116
|
+
if (!line) continue;
|
|
117
|
+
try {
|
|
118
|
+
entries.push(JSON.parse(line));
|
|
119
|
+
} catch {
|
|
120
|
+
return {
|
|
121
|
+
intact: false,
|
|
122
|
+
entryCount: entries.length,
|
|
123
|
+
rootHash: entries[entries.length - 1]?.hash ?? null,
|
|
124
|
+
brokenAt: entries.length,
|
|
125
|
+
brokenReason: `Line ${i + 1}: failed to parse as JSON — log file may be corrupted`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (entries.length === 0) {
|
|
131
|
+
return { intact: false, entryCount: 0, rootHash: null, brokenAt: null, brokenReason: 'Log is empty — nothing to verify' };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let prevHash = '';
|
|
135
|
+
for (let i = 0; i < entries.length; i++) {
|
|
136
|
+
const entry = entries[i];
|
|
137
|
+
if (entry.prevHash !== prevHash) {
|
|
138
|
+
return {
|
|
139
|
+
intact: false,
|
|
140
|
+
entryCount: entries.length,
|
|
141
|
+
rootHash: entries[i - 1]?.hash ?? null,
|
|
142
|
+
brokenAt: i,
|
|
143
|
+
brokenReason: `Entry ${i}: prevHash mismatch — expected ${prevHash.slice(0, 16)}… got ${String(entry.prevHash).slice(0, 16)}…`,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const expected = computeEntryHash(entry, prevHash);
|
|
147
|
+
if (entry.hash !== expected) {
|
|
148
|
+
return {
|
|
149
|
+
intact: false,
|
|
150
|
+
entryCount: entries.length,
|
|
151
|
+
rootHash: entries[i - 1]?.hash ?? null,
|
|
152
|
+
brokenAt: i,
|
|
153
|
+
brokenReason: `Entry ${i}: hash mismatch — entry data appears to have been modified`,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
prevHash = entry.hash;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return { intact: true, entryCount: entries.length, rootHash: prevHash, brokenAt: null, brokenReason: null };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ─── Compliance report (inline — mirrors ComplianceReportGenerator) ─────────────
|
|
163
|
+
|
|
164
|
+
function isOffHours(timestamp, startHour = 9, endHour = 18) {
|
|
165
|
+
const d = new Date(timestamp);
|
|
166
|
+
const hour = d.getUTCHours();
|
|
167
|
+
const day = d.getUTCDay();
|
|
168
|
+
if (day === 0 || day === 6) return true;
|
|
169
|
+
return hour < startHour || hour >= endHour;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function generateReport(type, entries, format) {
|
|
173
|
+
const agentMap = new Map();
|
|
174
|
+
const piiAccess = [];
|
|
175
|
+
const offHours = [];
|
|
176
|
+
const rotations = [];
|
|
177
|
+
const anomalies = [];
|
|
178
|
+
const piiTags = ['pii', 'phi', 'personal'];
|
|
179
|
+
|
|
180
|
+
for (const e of entries) {
|
|
181
|
+
if (!e.action.startsWith('credential.')) {
|
|
182
|
+
let s = agentMap.get(e.userId);
|
|
183
|
+
if (!s) {
|
|
184
|
+
s = { userId: e.userId, credentialIds: [], resourceIds: [], actionCounts: {}, resolutionCount: 0, firstSeen: e.timestamp, lastSeen: e.timestamp };
|
|
185
|
+
agentMap.set(e.userId, s);
|
|
186
|
+
}
|
|
187
|
+
if (!s.credentialIds.includes(e.credentialId)) s.credentialIds.push(e.credentialId);
|
|
188
|
+
if (!s.resourceIds.includes(e.resourceId)) s.resourceIds.push(e.resourceId);
|
|
189
|
+
s.actionCounts[e.action] = (s.actionCounts[e.action] ?? 0) + 1;
|
|
190
|
+
s.resolutionCount += 1;
|
|
191
|
+
if (e.timestamp < s.firstSeen) s.firstSeen = e.timestamp;
|
|
192
|
+
if (e.timestamp > s.lastSeen) s.lastSeen = e.timestamp;
|
|
193
|
+
|
|
194
|
+
if (piiTags.some(tag => e.resourceId.toLowerCase().includes(tag))) piiAccess.push(e);
|
|
195
|
+
if (isOffHours(e.timestamp)) offHours.push({ timestamp: e.timestamp, userId: e.userId, action: e.action, resourceId: e.resourceId, credentialId: e.credentialId });
|
|
196
|
+
}
|
|
197
|
+
if (e.action === 'credential.rotated') rotations.push({ credentialId: e.credentialId, rotatedAt: e.timestamp, triggeredBy: e.userId });
|
|
198
|
+
if (e.action === 'credential.anomaly') anomalies.push({ timestamp: e.timestamp, userId: e.userId, credentialId: e.credentialId, signal: e.signal ?? 'unknown', severity: e.severity ?? 'unknown' });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const summary = Array.from(agentMap.values()).sort((a, b) => b.resolutionCount - a.resolutionCount);
|
|
202
|
+
|
|
203
|
+
const report = {
|
|
204
|
+
type,
|
|
205
|
+
generatedAt: new Date().toISOString(),
|
|
206
|
+
periodFrom: entries[0]?.timestamp ?? 'n/a',
|
|
207
|
+
periodTo: entries[entries.length - 1]?.timestamp ?? 'n/a',
|
|
208
|
+
agentAccessSummary: summary,
|
|
209
|
+
piiResourceAccess: piiAccess,
|
|
210
|
+
offHoursAccess: offHours,
|
|
211
|
+
credentialRotationHistory: rotations,
|
|
212
|
+
anomalyEvents: anomalies,
|
|
213
|
+
totalEntries: entries.length,
|
|
214
|
+
summary: `${type.toUpperCase()} report: ${entries.length} total resolutions, ${piiAccess.length} PII accesses, ${offHours.length} off-hours accesses, ${anomalies.length} anomaly events`,
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (format === 'markdown') {
|
|
218
|
+
const lines = [
|
|
219
|
+
`# Agent Identity — ${type.toUpperCase()} Compliance Report`,
|
|
220
|
+
`**Period:** ${report.periodFrom} – ${report.periodTo}`,
|
|
221
|
+
`**Generated:** ${report.generatedAt}`,
|
|
222
|
+
`**Total resolutions:** ${report.totalEntries}`,
|
|
223
|
+
'',
|
|
224
|
+
'## Agent Access Summary',
|
|
225
|
+
'| Agent | Resolutions | Credentials | Resources | First Seen | Last Seen |',
|
|
226
|
+
'|-------|-------------|-------------|-----------|------------|-----------|',
|
|
227
|
+
...summary.map(a => `| ${a.userId} | ${a.resolutionCount} | ${a.credentialIds.length} | ${a.resourceIds.length} | ${a.firstSeen.slice(0,10)} | ${a.lastSeen.slice(0,10)} |`),
|
|
228
|
+
'',
|
|
229
|
+
`## PII Resource Access (${piiAccess.length} events)`,
|
|
230
|
+
piiAccess.length === 0 ? '_None_' : piiAccess.map(e => `- ${e.timestamp} | ${e.userId} | ${e.action} | ${e.resourceId}`).join('\n'),
|
|
231
|
+
'',
|
|
232
|
+
`## Off-Hours Access (${offHours.length} events)`,
|
|
233
|
+
offHours.length === 0 ? '_None_' : offHours.map(e => `- ${e.timestamp} | ${e.userId} | ${e.action} | ${e.resourceId}`).join('\n'),
|
|
234
|
+
'',
|
|
235
|
+
`## Credential Rotation History (${rotations.length} rotations)`,
|
|
236
|
+
rotations.length === 0 ? '_None_' : rotations.map(r => `- ${r.rotatedAt} | ${r.credentialId} | triggered by ${r.triggeredBy}`).join('\n'),
|
|
237
|
+
'',
|
|
238
|
+
`## Anomaly Events (${anomalies.length} events)`,
|
|
239
|
+
anomalies.length === 0 ? '_None_' : anomalies.map(a => `- ${a.timestamp} | ${a.userId} | ${a.credentialId} | ${a.signal} (${a.severity})`).join('\n'),
|
|
240
|
+
];
|
|
241
|
+
return lines.join('\n');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return JSON.stringify(report, null, 2);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// ─── Main ────────────────────────────────────────────────────────────────────────
|
|
248
|
+
|
|
249
|
+
const argv = process.argv.slice(2);
|
|
250
|
+
const { args, positional } = parseArgs(argv);
|
|
251
|
+
|
|
252
|
+
if (positional.length === 0 || positional[0] === 'help' || args.help || args.h) {
|
|
253
|
+
printHelp();
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const command = positional[0];
|
|
258
|
+
const subCommand = positional[1];
|
|
259
|
+
|
|
260
|
+
// ── agent-identity audit verify ──────────────────────────────────────────────────
|
|
261
|
+
if (command === 'audit' && subCommand === 'verify') {
|
|
262
|
+
if (!args.file) die('--file <path> is required');
|
|
263
|
+
|
|
264
|
+
const content = readJsonlFile(args.file);
|
|
265
|
+
const result = verifyJsonl(content);
|
|
266
|
+
|
|
267
|
+
if (!args.quiet) {
|
|
268
|
+
process.stdout.write(`\nAudit log verification — ${path.resolve(args.file)}\n`);
|
|
269
|
+
process.stdout.write(`Entries verified : ${result.entryCount}\n`);
|
|
270
|
+
process.stdout.write(`Chain status : ${result.intact ? '\u2705 INTACT' : '\u274C BROKEN'}\n`);
|
|
271
|
+
if (result.rootHash) {
|
|
272
|
+
process.stdout.write(`Chain root hash : ${result.rootHash}\n`);
|
|
273
|
+
}
|
|
274
|
+
if (!result.intact) {
|
|
275
|
+
process.stdout.write(`Broken at entry : ${result.brokenAt ?? 'n/a'}\n`);
|
|
276
|
+
process.stdout.write(`Reason : ${result.brokenReason}\n`);
|
|
277
|
+
}
|
|
278
|
+
process.stdout.write('\n');
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
process.exit(result.intact ? 0 : 1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// ── agent-identity report <type> ────────────────────────────────────────────────
|
|
285
|
+
if (command === 'report') {
|
|
286
|
+
const reportType = subCommand;
|
|
287
|
+
if (!reportType || !['soc2', 'gdpr', 'hipaa', 'custom'].includes(reportType)) {
|
|
288
|
+
die(`report type must be one of: soc2 | gdpr | hipaa | custom (got: ${reportType ?? 'none'})`);
|
|
289
|
+
}
|
|
290
|
+
if (!args.file) die('--file <path> is required');
|
|
291
|
+
|
|
292
|
+
const content = readJsonlFile(args.file);
|
|
293
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
294
|
+
let entries;
|
|
295
|
+
try {
|
|
296
|
+
entries = lines.map(l => JSON.parse(l));
|
|
297
|
+
} catch (err) {
|
|
298
|
+
die(`Failed to parse JSONL file: ${err.message}`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Filter by date range if provided
|
|
302
|
+
let filtered = entries;
|
|
303
|
+
if (args.from) {
|
|
304
|
+
const start = new Date(args.from).getTime();
|
|
305
|
+
filtered = filtered.filter(e => new Date(e.timestamp).getTime() >= start);
|
|
306
|
+
}
|
|
307
|
+
if (args.to) {
|
|
308
|
+
const end = new Date(args.to).getTime();
|
|
309
|
+
filtered = filtered.filter(e => new Date(e.timestamp).getTime() <= end);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const format = args.format === 'markdown' ? 'markdown' : 'json';
|
|
313
|
+
const output = generateReport(reportType, filtered, format);
|
|
314
|
+
process.stdout.write(output + '\n');
|
|
315
|
+
process.exit(0);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// ── Unknown command ──────────────────────────────────────────────────────────────
|
|
319
|
+
die(`Unknown command: ${command} ${subCommand ?? ''}`.trim());
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@datacules/agent-identity-compliance",
|
|
3
|
+
"version": "0.2.1",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Compliance report generator + tamper-evident audit log for @datacules/agent-identity — SOC 2, GDPR, HIPAA reports, SHA-256 chain verification CLI",
|
|
6
|
+
"author": "Datacules LLC",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/hvrcharon1/agent-identity.git",
|
|
11
|
+
"directory": "packages/integrations/compliance"
|
|
12
|
+
},
|
|
13
|
+
"main": "./dist/cjs/index.js",
|
|
14
|
+
"module": "./dist/esm/index.js",
|
|
15
|
+
"types": "./dist/types/index.d.ts",
|
|
16
|
+
"bin": {
|
|
17
|
+
"agent-identity": "./bin/cli.js"
|
|
18
|
+
},
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"import": "./dist/esm/index.js",
|
|
22
|
+
"require": "./dist/cjs/index.js",
|
|
23
|
+
"types": "./dist/types/index.d.ts"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"bin",
|
|
29
|
+
"README.md"
|
|
30
|
+
],
|
|
31
|
+
"scripts": {
|
|
32
|
+
"build": "tsc -p tsconfig.build.json",
|
|
33
|
+
"type-check": "tsc --noEmit",
|
|
34
|
+
"lint": "eslint src --ext .ts"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/node": "^20",
|
|
38
|
+
"typescript": "^5"
|
|
39
|
+
},
|
|
40
|
+
"peerDependencies": {
|
|
41
|
+
"@datacules/agent-identity": "^0.1.0"
|
|
42
|
+
}
|
|
43
|
+
}
|