@netcatty/dsh-guard 0.0.0-stage → 0.2.0
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/CHANGELOG.md +23 -0
- package/LICENSE +21 -0
- package/README.md +595 -2
- package/cordis.patch.yml +3 -0
- package/lib/audit-log.js +64 -0
- package/lib/client.js +679 -0
- package/lib/diff-view.js +155 -0
- package/lib/index.js +1954 -0
- package/lib/jsonc.js +140 -0
- package/lib/mask.js +22 -0
- package/lib/match.js +119 -0
- package/lib/params.js +105 -0
- package/lib/paths.js +140 -0
- package/lib/policy-edit.js +74 -0
- package/lib/presets.js +50 -0
- package/lib/preview.js +152 -0
- package/lib/shell.js +127 -0
- package/lib/snapshots.js +198 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +72 -4
- package/policy.json +289 -0
- package/policy.schema.json +317 -0
package/lib/audit-log.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Reading the tail of the audit trail.
|
|
2
|
+
//
|
|
3
|
+
// `audit.jsonl` grows to multiple megabytes and is append-only, so the useful
|
|
4
|
+
// view is "the last N decisions", not the whole file. This reads a bounded
|
|
5
|
+
// window from the end instead of parsing the file: at 5 MiB the full read would
|
|
6
|
+
// allocate 5 MiB on every command.
|
|
7
|
+
|
|
8
|
+
import { closeSync, openSync, readSync, statSync } from 'node:fs';
|
|
9
|
+
|
|
10
|
+
const DEFAULT_LIMIT = 20;
|
|
11
|
+
const MAX_LIMIT = 200;
|
|
12
|
+
const WINDOW_BYTES = 256 * 1024;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @param {string} file
|
|
16
|
+
* @param {number} limit how many trailing lines to return
|
|
17
|
+
* @returns {Array<object>} parsed rows, newest last
|
|
18
|
+
*/
|
|
19
|
+
export function readTail(file, limit = DEFAULT_LIMIT) {
|
|
20
|
+
const wanted = Math.max(1, Math.min(Number.isFinite(limit) ? Math.trunc(limit) : DEFAULT_LIMIT, MAX_LIMIT));
|
|
21
|
+
let stat;
|
|
22
|
+
try {
|
|
23
|
+
stat = statSync(file);
|
|
24
|
+
} catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
if (!stat.isFile() || stat.size === 0) return [];
|
|
28
|
+
|
|
29
|
+
const size = Math.min(stat.size, WINDOW_BYTES);
|
|
30
|
+
const start = stat.size - size;
|
|
31
|
+
const buffer = Buffer.alloc(size);
|
|
32
|
+
let fd;
|
|
33
|
+
try {
|
|
34
|
+
fd = openSync(file, 'r');
|
|
35
|
+
readSync(fd, buffer, 0, size, start);
|
|
36
|
+
} finally {
|
|
37
|
+
if (fd !== undefined) closeSync(fd);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const lines = buffer.toString('utf8').split('\n').filter((line) => line.trim() !== '');
|
|
41
|
+
// A window that starts mid-file begins mid-line; drop that fragment rather
|
|
42
|
+
// than handing a truncated JSON object to the parser.
|
|
43
|
+
if (start > 0 && lines.length > 0) lines.shift();
|
|
44
|
+
|
|
45
|
+
const rows = [];
|
|
46
|
+
for (const line of lines.slice(-wanted)) {
|
|
47
|
+
try {
|
|
48
|
+
rows.push(JSON.parse(line));
|
|
49
|
+
} catch {
|
|
50
|
+
// A half-written trailing line (the append is not atomic) is skipped.
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return rows;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** One line for the command reply. */
|
|
57
|
+
export function describeDecision(row) {
|
|
58
|
+
const at = typeof row.at === 'string' ? row.at.slice(11, 19) : '--:--:--';
|
|
59
|
+
const state = String(row.state ?? 'allow').padEnd(5);
|
|
60
|
+
const tool = String(row.tool ?? '?');
|
|
61
|
+
const pattern = row.pattern === null || row.pattern === undefined ? '' : ` ${row.pattern}`;
|
|
62
|
+
const agent = typeof row.agent === 'string' && row.agent !== 'main' ? ` [${row.agent}]` : '';
|
|
63
|
+
return `${at} ${state} ${tool}${pattern}${agent}`;
|
|
64
|
+
}
|