@cybedefend/vibedefend 1.1.1 → 1.1.2
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/dist/hook-runner.js
CHANGED
|
@@ -6237,21 +6237,50 @@ function makeViolationsBuffer(opts = {}) {
|
|
|
6237
6237
|
const trimmed = line.trim();
|
|
6238
6238
|
if (!trimmed) continue;
|
|
6239
6239
|
try {
|
|
6240
|
-
|
|
6240
|
+
const parsed = JSON.parse(trimmed);
|
|
6241
|
+
if (typeof parsed.projectId === "string" && parsed.projectId) {
|
|
6242
|
+
records.push(parsed);
|
|
6243
|
+
}
|
|
6241
6244
|
} catch {
|
|
6242
6245
|
}
|
|
6243
6246
|
}
|
|
6244
|
-
if (records.length === 0)
|
|
6245
|
-
const BATCH_SIZE = 100;
|
|
6246
|
-
try {
|
|
6247
|
-
for (let i = 0; i < records.length; i += BATCH_SIZE) {
|
|
6248
|
-
const batch = records.slice(i, i + BATCH_SIZE);
|
|
6249
|
-
await postFn(ctx, batch);
|
|
6250
|
-
}
|
|
6247
|
+
if (records.length === 0) {
|
|
6251
6248
|
try {
|
|
6252
6249
|
writeFileSync4(bufferFile, "", "utf8");
|
|
6253
6250
|
} catch {
|
|
6254
6251
|
}
|
|
6252
|
+
return;
|
|
6253
|
+
}
|
|
6254
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6255
|
+
for (const r of records) {
|
|
6256
|
+
const arr = groups.get(r.projectId);
|
|
6257
|
+
if (arr) arr.push(r);
|
|
6258
|
+
else groups.set(r.projectId, [r]);
|
|
6259
|
+
}
|
|
6260
|
+
const BATCH_SIZE = 100;
|
|
6261
|
+
const failed = [];
|
|
6262
|
+
for (const [projectId, group] of groups) {
|
|
6263
|
+
let groupFailed = false;
|
|
6264
|
+
for (let i = 0; i < group.length; i += BATCH_SIZE) {
|
|
6265
|
+
const batch = group.slice(i, i + BATCH_SIZE);
|
|
6266
|
+
try {
|
|
6267
|
+
await postFn(ctx, projectId, batch);
|
|
6268
|
+
} catch {
|
|
6269
|
+
groupFailed = true;
|
|
6270
|
+
break;
|
|
6271
|
+
}
|
|
6272
|
+
}
|
|
6273
|
+
if (groupFailed) {
|
|
6274
|
+
failed.push(...group);
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
try {
|
|
6278
|
+
if (failed.length === 0) {
|
|
6279
|
+
writeFileSync4(bufferFile, "", "utf8");
|
|
6280
|
+
} else {
|
|
6281
|
+
const serialised = failed.map((r) => JSON.stringify(r)).join("\n") + "\n";
|
|
6282
|
+
writeFileSync4(bufferFile, serialised, "utf8");
|
|
6283
|
+
}
|
|
6255
6284
|
} catch {
|
|
6256
6285
|
}
|
|
6257
6286
|
}
|
|
@@ -6598,36 +6627,60 @@ async function runPreToolUseGuard() {
|
|
|
6598
6627
|
apiBaseResolved
|
|
6599
6628
|
});
|
|
6600
6629
|
const buffer = makeViolationsBuffer();
|
|
6601
|
-
|
|
6602
|
-
buffer.flushViolations(
|
|
6603
|
-
{ projectId, token, apiBaseResolved },
|
|
6604
|
-
async (ctx, batch) => {
|
|
6605
|
-
const f = globalThis.fetch;
|
|
6606
|
-
const url = `${ctx.apiBaseResolved}/project/${encodeURIComponent(ctx.projectId)}/action-guards/violations`;
|
|
6607
|
-
await f(url, {
|
|
6608
|
-
method: "POST",
|
|
6609
|
-
headers: {
|
|
6610
|
-
Authorization: `Bearer ${ctx.token}`,
|
|
6611
|
-
"Content-Type": "application/json"
|
|
6612
|
-
},
|
|
6613
|
-
body: JSON.stringify({ violations: batch }),
|
|
6614
|
-
signal: AbortSignal.timeout(5e3)
|
|
6615
|
-
});
|
|
6616
|
-
}
|
|
6617
|
-
).catch(() => {
|
|
6618
|
-
});
|
|
6619
|
-
});
|
|
6620
|
-
return await runPreToolUseGuardWith({
|
|
6630
|
+
const exitCode = await runPreToolUseGuardWith({
|
|
6621
6631
|
stdinEnvelope: envelope,
|
|
6622
6632
|
projectContext,
|
|
6623
6633
|
rulesOutcome,
|
|
6624
6634
|
evaluateFn: evaluate,
|
|
6625
6635
|
bufferViolationFn: buffer.bufferViolation.bind(buffer)
|
|
6626
6636
|
});
|
|
6637
|
+
try {
|
|
6638
|
+
await buffer.flushViolations(
|
|
6639
|
+
{ token, apiBaseResolved },
|
|
6640
|
+
makeGuardViolationsPostFn()
|
|
6641
|
+
);
|
|
6642
|
+
} catch {
|
|
6643
|
+
}
|
|
6644
|
+
return exitCode;
|
|
6627
6645
|
} catch {
|
|
6628
6646
|
return 0;
|
|
6629
6647
|
}
|
|
6630
6648
|
}
|
|
6649
|
+
function makeGuardViolationsPostFn() {
|
|
6650
|
+
return async (ctx, projectId, batch) => {
|
|
6651
|
+
const url = `${ctx.apiBaseResolved}/project/${encodeURIComponent(projectId)}/action-guards/violations`;
|
|
6652
|
+
const resp = await globalThis.fetch(url, {
|
|
6653
|
+
method: "POST",
|
|
6654
|
+
headers: {
|
|
6655
|
+
Authorization: `Bearer ${ctx.token}`,
|
|
6656
|
+
"Content-Type": "application/json"
|
|
6657
|
+
},
|
|
6658
|
+
body: JSON.stringify({ violations: batch.map(toApiViolation) }),
|
|
6659
|
+
signal: AbortSignal.timeout(2e3)
|
|
6660
|
+
});
|
|
6661
|
+
if (!resp.ok) {
|
|
6662
|
+
throw new Error(
|
|
6663
|
+
`POST /action-guards/violations rejected by gateway: ${resp.status}`
|
|
6664
|
+
);
|
|
6665
|
+
}
|
|
6666
|
+
};
|
|
6667
|
+
}
|
|
6668
|
+
function toApiViolation(r) {
|
|
6669
|
+
const out = {
|
|
6670
|
+
rule_id: r.ruleId,
|
|
6671
|
+
agent: r.agent,
|
|
6672
|
+
tool: r.tool,
|
|
6673
|
+
outcome: r.outcome,
|
|
6674
|
+
// Backend enforces max_length=256 on target_summary — clamp here so a
|
|
6675
|
+
// verbose redactor output never costs us the whole batch.
|
|
6676
|
+
target_summary: (r.targetSummary ?? "").slice(0, 256),
|
|
6677
|
+
target_hash: r.targetHash
|
|
6678
|
+
};
|
|
6679
|
+
if (r.sessionId) {
|
|
6680
|
+
out.session_id = r.sessionId;
|
|
6681
|
+
}
|
|
6682
|
+
return out;
|
|
6683
|
+
}
|
|
6631
6684
|
|
|
6632
6685
|
// src/hooks/runtime/index.ts
|
|
6633
6686
|
async function main() {
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Violations (deny/warn outcomes) are appended as JSON lines to a local
|
|
5
5
|
* buffer file (`~/.cybedefend/guards-violations-buffer.jsonl`) and flushed
|
|
6
|
-
* to the gateway on demand
|
|
7
|
-
* `process.on('beforeExit')`).
|
|
6
|
+
* to the gateway on demand by the hook before it returns.
|
|
8
7
|
*
|
|
9
8
|
* Why JSONL + append semantics?
|
|
10
9
|
* - Append to a file is atomic at the OS level for small writes.
|
|
@@ -13,6 +12,17 @@
|
|
|
13
12
|
* - The hook NEVER blocks on the audit write — `bufferViolation` is
|
|
14
13
|
* synchronous (sync appendFileSync with minimal data).
|
|
15
14
|
*
|
|
15
|
+
* Per-project semantics:
|
|
16
|
+
* - Each buffered record carries its own `projectId`. When the buffer is
|
|
17
|
+
* flushed, records are grouped by `projectId` and each group is POSTed
|
|
18
|
+
* to its own project's endpoint. This is required because a developer
|
|
19
|
+
* may switch projects between hook invocations, and the user's Logto
|
|
20
|
+
* token (same across all their projects) can authorise calls to any
|
|
21
|
+
* project the Permify policy allows.
|
|
22
|
+
* - On partial failure (e.g. permission denied for one project, success
|
|
23
|
+
* for another) only the failed groups are kept in the buffer; the
|
|
24
|
+
* successful ones are purged.
|
|
25
|
+
*
|
|
16
26
|
* Violation semantics:
|
|
17
27
|
* - `deny` → outcome = 'denied'
|
|
18
28
|
* - `warn` → outcome = 'warned'
|
|
@@ -53,7 +63,7 @@ export function makeViolationsBuffer(opts = {}) {
|
|
|
53
63
|
content = readFileSync(bufferFile, 'utf8').trim();
|
|
54
64
|
}
|
|
55
65
|
catch {
|
|
56
|
-
return;
|
|
66
|
+
return;
|
|
57
67
|
}
|
|
58
68
|
if (!content)
|
|
59
69
|
return;
|
|
@@ -63,30 +73,70 @@ export function makeViolationsBuffer(opts = {}) {
|
|
|
63
73
|
if (!trimmed)
|
|
64
74
|
continue;
|
|
65
75
|
try {
|
|
66
|
-
|
|
76
|
+
const parsed = JSON.parse(trimmed);
|
|
77
|
+
// Drop records that pre-date the projectId field or are malformed
|
|
78
|
+
// beyond recovery — keeping them would block the flush forever
|
|
79
|
+
// since they cannot be addressed to a project.
|
|
80
|
+
if (typeof parsed.projectId === 'string' && parsed.projectId) {
|
|
81
|
+
records.push(parsed);
|
|
82
|
+
}
|
|
67
83
|
}
|
|
68
84
|
catch {
|
|
69
|
-
// Corrupt line —
|
|
85
|
+
// Corrupt line — drop it
|
|
70
86
|
}
|
|
71
87
|
}
|
|
72
|
-
if (records.length === 0)
|
|
88
|
+
if (records.length === 0) {
|
|
89
|
+
// Nothing salvageable — clear the file so corrupt content doesn't
|
|
90
|
+
// grow unboundedly.
|
|
91
|
+
try {
|
|
92
|
+
writeFileSync(bufferFile, '', 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
// Best-effort
|
|
96
|
+
}
|
|
73
97
|
return;
|
|
98
|
+
}
|
|
99
|
+
// Group by projectId, preserving original insertion order within each group.
|
|
100
|
+
const groups = new Map();
|
|
101
|
+
for (const r of records) {
|
|
102
|
+
const arr = groups.get(r.projectId);
|
|
103
|
+
if (arr)
|
|
104
|
+
arr.push(r);
|
|
105
|
+
else
|
|
106
|
+
groups.set(r.projectId, [r]);
|
|
107
|
+
}
|
|
74
108
|
const BATCH_SIZE = 100;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
109
|
+
const failed = [];
|
|
110
|
+
for (const [projectId, group] of groups) {
|
|
111
|
+
let groupFailed = false;
|
|
112
|
+
for (let i = 0; i < group.length; i += BATCH_SIZE) {
|
|
113
|
+
const batch = group.slice(i, i + BATCH_SIZE);
|
|
114
|
+
try {
|
|
115
|
+
await postFn(ctx, projectId, batch);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
groupFailed = true;
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
79
121
|
}
|
|
80
|
-
|
|
81
|
-
|
|
122
|
+
if (groupFailed) {
|
|
123
|
+
failed.push(...group);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Rewrite the file with only the failed groups (or wipe it on full success).
|
|
127
|
+
try {
|
|
128
|
+
if (failed.length === 0) {
|
|
82
129
|
writeFileSync(bufferFile, '', 'utf8');
|
|
83
130
|
}
|
|
84
|
-
|
|
85
|
-
|
|
131
|
+
else {
|
|
132
|
+
const serialised = failed.map((r) => JSON.stringify(r)).join('\n') + '\n';
|
|
133
|
+
writeFileSync(bufferFile, serialised, 'utf8');
|
|
86
134
|
}
|
|
87
135
|
}
|
|
88
136
|
catch {
|
|
89
|
-
//
|
|
137
|
+
// Best-effort — if we can't rewrite, the worst case is that successful
|
|
138
|
+
// groups are re-flushed on the next attempt (the API push is idempotent
|
|
139
|
+
// at the (project_id, rule_id, target_hash, session_id) level).
|
|
90
140
|
}
|
|
91
141
|
}
|
|
92
142
|
return { bufferViolation, flushViolations };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"guard-violations-buffer.js","sourceRoot":"","sources":["../../../src/hooks/runtime/guard-violations-buffer.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"guard-violations-buffer.js","sourceRoot":"","sources":["../../../src/hooks/runtime/guard-violations-buffer.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED