@de-otio/repo-aegis-core 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/dist/age.d.ts +32 -0
- package/dist/age.d.ts.map +1 -0
- package/dist/age.js +98 -0
- package/dist/age.js.map +1 -0
- package/dist/audit-log.d.ts +50 -0
- package/dist/audit-log.d.ts.map +1 -0
- package/dist/audit-log.js +183 -0
- package/dist/audit-log.js.map +1 -0
- package/dist/audit-log.test.d.ts +2 -0
- package/dist/audit-log.test.d.ts.map +1 -0
- package/dist/audit-log.test.js +181 -0
- package/dist/audit-log.test.js.map +1 -0
- package/dist/deny-set.d.ts +43 -0
- package/dist/deny-set.d.ts.map +1 -0
- package/dist/deny-set.js +165 -0
- package/dist/deny-set.js.map +1 -0
- package/dist/deny-set.test.d.ts +2 -0
- package/dist/deny-set.test.d.ts.map +1 -0
- package/dist/deny-set.test.js +155 -0
- package/dist/deny-set.test.js.map +1 -0
- package/dist/exceptions.d.ts +96 -0
- package/dist/exceptions.d.ts.map +1 -0
- package/dist/exceptions.js +143 -0
- package/dist/exceptions.js.map +1 -0
- package/dist/exit-codes.d.ts +4 -0
- package/dist/exit-codes.d.ts.map +1 -0
- package/dist/exit-codes.js +6 -0
- package/dist/exit-codes.js.map +1 -0
- package/dist/first-touch.d.ts +57 -0
- package/dist/first-touch.d.ts.map +1 -0
- package/dist/first-touch.js +112 -0
- package/dist/first-touch.js.map +1 -0
- package/dist/import-graph.test.d.ts +2 -0
- package/dist/import-graph.test.d.ts.map +1 -0
- package/dist/import-graph.test.js +210 -0
- package/dist/import-graph.test.js.map +1 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/lock.d.ts +22 -0
- package/dist/lock.d.ts.map +1 -0
- package/dist/lock.js +86 -0
- package/dist/lock.js.map +1 -0
- package/dist/lock.test.d.ts +2 -0
- package/dist/lock.test.d.ts.map +1 -0
- package/dist/lock.test.js +125 -0
- package/dist/lock.test.js.map +1 -0
- package/dist/paths.d.ts +22 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +46 -0
- package/dist/paths.js.map +1 -0
- package/dist/paths.test.d.ts +2 -0
- package/dist/paths.test.d.ts.map +1 -0
- package/dist/paths.test.js +78 -0
- package/dist/paths.test.js.map +1 -0
- package/dist/redaction.d.ts +29 -0
- package/dist/redaction.d.ts.map +1 -0
- package/dist/redaction.js +48 -0
- package/dist/redaction.js.map +1 -0
- package/dist/redaction.test.d.ts +2 -0
- package/dist/redaction.test.d.ts.map +1 -0
- package/dist/redaction.test.js +67 -0
- package/dist/redaction.test.js.map +1 -0
- package/dist/regex-safety.d.ts +87 -0
- package/dist/regex-safety.d.ts.map +1 -0
- package/dist/regex-safety.js +322 -0
- package/dist/regex-safety.js.map +1 -0
- package/dist/regex-safety.test.d.ts +2 -0
- package/dist/regex-safety.test.d.ts.map +1 -0
- package/dist/regex-safety.test.js +149 -0
- package/dist/regex-safety.test.js.map +1 -0
- package/dist/registry-mutate.d.ts +35 -0
- package/dist/registry-mutate.d.ts.map +1 -0
- package/dist/registry-mutate.js +149 -0
- package/dist/registry-mutate.js.map +1 -0
- package/dist/registry-mutate.test.d.ts +2 -0
- package/dist/registry-mutate.test.d.ts.map +1 -0
- package/dist/registry-mutate.test.js +96 -0
- package/dist/registry-mutate.test.js.map +1 -0
- package/dist/registry.d.ts +64 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +120 -0
- package/dist/registry.js.map +1 -0
- package/dist/registry.test.d.ts +2 -0
- package/dist/registry.test.d.ts.map +1 -0
- package/dist/registry.test.js +316 -0
- package/dist/registry.test.js.map +1 -0
- package/dist/remote-url.d.ts +18 -0
- package/dist/remote-url.d.ts.map +1 -0
- package/dist/remote-url.js +66 -0
- package/dist/remote-url.js.map +1 -0
- package/dist/remote-url.test.d.ts +2 -0
- package/dist/remote-url.test.d.ts.map +1 -0
- package/dist/remote-url.test.js +116 -0
- package/dist/remote-url.test.js.map +1 -0
- package/dist/render.d.ts +54 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +182 -0
- package/dist/render.js.map +1 -0
- package/dist/render.test.d.ts +2 -0
- package/dist/render.test.d.ts.map +1 -0
- package/dist/render.test.js +152 -0
- package/dist/render.test.js.map +1 -0
- package/dist/repo.d.ts +40 -0
- package/dist/repo.d.ts.map +1 -0
- package/dist/repo.js +214 -0
- package/dist/repo.js.map +1 -0
- package/dist/repo.test.d.ts +2 -0
- package/dist/repo.test.d.ts.map +1 -0
- package/dist/repo.test.js +234 -0
- package/dist/repo.test.js.map +1 -0
- package/dist/scan.d.ts +103 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +436 -0
- package/dist/scan.js.map +1 -0
- package/dist/scan.test.d.ts +2 -0
- package/dist/scan.test.d.ts.map +1 -0
- package/dist/scan.test.js +437 -0
- package/dist/scan.test.js.map +1 -0
- package/dist/schemas.d.ts +50 -0
- package/dist/schemas.d.ts.map +1 -0
- package/dist/schemas.js +190 -0
- package/dist/schemas.js.map +1 -0
- package/dist/secret-markers.d.ts +34 -0
- package/dist/secret-markers.d.ts.map +1 -0
- package/dist/secret-markers.js +118 -0
- package/dist/secret-markers.js.map +1 -0
- package/dist/secret-markers.test.d.ts +2 -0
- package/dist/secret-markers.test.d.ts.map +1 -0
- package/dist/secret-markers.test.js +154 -0
- package/dist/secret-markers.test.js.map +1 -0
- package/dist/trust-boundary.d.ts +33 -0
- package/dist/trust-boundary.d.ts.map +1 -0
- package/dist/trust-boundary.js +77 -0
- package/dist/trust-boundary.js.map +1 -0
- package/dist/trust-boundary.test.d.ts +2 -0
- package/dist/trust-boundary.test.d.ts.map +1 -0
- package/dist/trust-boundary.test.js +170 -0
- package/dist/trust-boundary.test.js.map +1 -0
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +8 -0
- package/dist/types.js.map +1 -0
- package/dist/working-tree.d.ts +38 -0
- package/dist/working-tree.d.ts.map +1 -0
- package/dist/working-tree.js +133 -0
- package/dist/working-tree.js.map +1 -0
- package/dist/working-tree.test.d.ts +2 -0
- package/dist/working-tree.test.d.ts.map +1 -0
- package/dist/working-tree.test.js +162 -0
- package/dist/working-tree.test.js.map +1 -0
- package/package.json +40 -0
- package/src/age.ts +113 -0
- package/src/audit-log.test.ts +222 -0
- package/src/audit-log.ts +215 -0
- package/src/deny-set.test.ts +208 -0
- package/src/deny-set.ts +231 -0
- package/src/exceptions.ts +134 -0
- package/src/exit-codes.ts +5 -0
- package/src/first-touch.ts +172 -0
- package/src/import-graph.test.ts +239 -0
- package/src/index.ts +191 -0
- package/src/lock.test.ts +151 -0
- package/src/lock.ts +88 -0
- package/src/paths.test.ts +94 -0
- package/src/paths.ts +55 -0
- package/src/redaction.test.ts +81 -0
- package/src/redaction.ts +49 -0
- package/src/regex-safety.test.ts +194 -0
- package/src/regex-safety.ts +349 -0
- package/src/registry-mutate.test.ts +134 -0
- package/src/registry-mutate.ts +185 -0
- package/src/registry.test.ts +460 -0
- package/src/registry.ts +178 -0
- package/src/remote-url.test.ts +121 -0
- package/src/remote-url.ts +78 -0
- package/src/render.test.ts +206 -0
- package/src/render.ts +215 -0
- package/src/repo.test.ts +275 -0
- package/src/repo.ts +245 -0
- package/src/scan.test.ts +580 -0
- package/src/scan.ts +531 -0
- package/src/schemas.ts +207 -0
- package/src/secret-markers.test.ts +183 -0
- package/src/secret-markers.ts +145 -0
- package/src/trust-boundary.test.ts +198 -0
- package/src/trust-boundary.ts +98 -0
- package/src/types.ts +55 -0
- package/src/working-tree.test.ts +193 -0
- package/src/working-tree.ts +130 -0
package/dist/age.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export declare class AgeNotFoundError extends Error {
|
|
2
|
+
readonly code: "AGE_NOT_FOUND";
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
export declare class AgeError extends Error {
|
|
6
|
+
action: "encrypt" | "decrypt";
|
|
7
|
+
stderr: string;
|
|
8
|
+
readonly code: "AGE_ERROR";
|
|
9
|
+
constructor(action: "encrypt" | "decrypt", stderr: string);
|
|
10
|
+
}
|
|
11
|
+
export interface EncryptOptions {
|
|
12
|
+
/** Recipients (pubkey strings starting with `age1...` or `ssh-ed25519 ...`). */
|
|
13
|
+
recipients?: string[];
|
|
14
|
+
/** Path to a file holding one recipient per line. */
|
|
15
|
+
recipientFile?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Encrypt a file with age. Returns the ciphertext as a Buffer (binary
|
|
19
|
+
* format by default; ASCII-armoured if -a is added later). Caller is
|
|
20
|
+
* responsible for writing it.
|
|
21
|
+
*/
|
|
22
|
+
export declare function encryptFile(plainPath: string, opts: EncryptOptions): Buffer;
|
|
23
|
+
export interface DecryptOptions {
|
|
24
|
+
/** Path to an age identity file (one or more identities). */
|
|
25
|
+
identityFile: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Decrypt a file with age. Returns the cleartext as a Buffer.
|
|
29
|
+
*/
|
|
30
|
+
export declare function decryptFile(cipherPath: string, opts: DecryptOptions): Buffer;
|
|
31
|
+
export declare function writeBufferTo(target: string, buf: Buffer): void;
|
|
32
|
+
//# sourceMappingURL=age.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"age.d.ts","sourceRoot":"","sources":["../src/age.ts"],"names":[],"mappings":"AAWA,qBAAa,gBAAiB,SAAQ,KAAK;IACzC,QAAQ,CAAC,IAAI,EAAG,eAAe,CAAU;;CAO1C;AAED,qBAAa,QAAS,SAAQ,KAAK;IAEd,MAAM,EAAE,SAAS,GAAG,SAAS;IAAS,MAAM,EAAE,MAAM;IADvE,QAAQ,CAAC,IAAI,EAAG,WAAW,CAAU;gBAClB,MAAM,EAAE,SAAS,GAAG,SAAS,EAAS,MAAM,EAAE,MAAM;CAGxE;AAaD,MAAM,WAAW,cAAc;IAC7B,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,qDAAqD;IACrD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CA6B3E;AAED,MAAM,WAAW,cAAc;IAC7B,6DAA6D;IAC7D,YAAY,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,GAAG,MAAM,CAkB5E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAE/D"}
|
package/dist/age.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Richard Myers and contributors.
|
|
3
|
+
// Thin wrapper around the `age` CLI for symmetric file encryption /
|
|
4
|
+
// decryption. Lives in core so multiple packages (scan,
|
|
5
|
+
// cli/registry-encrypt) can share the same machinery without each
|
|
6
|
+
// shelling out to age independently. The `age` binary is a runtime
|
|
7
|
+
// dependency: callers receive `AgeNotFoundError` if it isn't on PATH.
|
|
8
|
+
import { spawnSync } from "node:child_process";
|
|
9
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
10
|
+
export class AgeNotFoundError extends Error {
|
|
11
|
+
code = "AGE_NOT_FOUND";
|
|
12
|
+
constructor() {
|
|
13
|
+
super("the `age` binary is required but was not found on PATH; " +
|
|
14
|
+
"install from https://age-encryption.org");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
export class AgeError extends Error {
|
|
18
|
+
action;
|
|
19
|
+
stderr;
|
|
20
|
+
code = "AGE_ERROR";
|
|
21
|
+
constructor(action, stderr) {
|
|
22
|
+
super(`age ${action} failed: ${stderr.trim() || "(no stderr)"}`);
|
|
23
|
+
this.action = action;
|
|
24
|
+
this.stderr = stderr;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function ageBinary() {
|
|
28
|
+
const probe = spawnSync("age", ["--version"], {
|
|
29
|
+
encoding: "utf8",
|
|
30
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
31
|
+
});
|
|
32
|
+
if (probe.error || probe.status !== 0) {
|
|
33
|
+
throw new AgeNotFoundError();
|
|
34
|
+
}
|
|
35
|
+
return "age";
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Encrypt a file with age. Returns the ciphertext as a Buffer (binary
|
|
39
|
+
* format by default; ASCII-armoured if -a is added later). Caller is
|
|
40
|
+
* responsible for writing it.
|
|
41
|
+
*/
|
|
42
|
+
export function encryptFile(plainPath, opts) {
|
|
43
|
+
if (!existsSync(plainPath)) {
|
|
44
|
+
throw new Error(`source file not found: ${plainPath}`);
|
|
45
|
+
}
|
|
46
|
+
if ((!opts.recipients || opts.recipients.length === 0) && !opts.recipientFile) {
|
|
47
|
+
throw new Error("encrypt requires at least one --recipient or --recipient-file");
|
|
48
|
+
}
|
|
49
|
+
const args = [];
|
|
50
|
+
for (const r of opts.recipients ?? []) {
|
|
51
|
+
args.push("-r", r);
|
|
52
|
+
}
|
|
53
|
+
if (opts.recipientFile) {
|
|
54
|
+
if (!existsSync(opts.recipientFile)) {
|
|
55
|
+
throw new Error(`recipient file not found: ${opts.recipientFile}`);
|
|
56
|
+
}
|
|
57
|
+
args.push("-R", opts.recipientFile);
|
|
58
|
+
}
|
|
59
|
+
const bin = ageBinary();
|
|
60
|
+
const plaintext = readFileSync(plainPath);
|
|
61
|
+
const result = spawnSync(bin, args, {
|
|
62
|
+
input: plaintext,
|
|
63
|
+
timeout: 30_000,
|
|
64
|
+
});
|
|
65
|
+
if (result.error)
|
|
66
|
+
throw result.error;
|
|
67
|
+
if (result.status !== 0) {
|
|
68
|
+
throw new AgeError("encrypt", (result.stderr ?? Buffer.from("")).toString("utf8"));
|
|
69
|
+
}
|
|
70
|
+
return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Decrypt a file with age. Returns the cleartext as a Buffer.
|
|
74
|
+
*/
|
|
75
|
+
export function decryptFile(cipherPath, opts) {
|
|
76
|
+
if (!existsSync(cipherPath)) {
|
|
77
|
+
throw new Error(`source file not found: ${cipherPath}`);
|
|
78
|
+
}
|
|
79
|
+
if (!existsSync(opts.identityFile)) {
|
|
80
|
+
throw new Error(`identity file not found: ${opts.identityFile}`);
|
|
81
|
+
}
|
|
82
|
+
const bin = ageBinary();
|
|
83
|
+
const ciphertext = readFileSync(cipherPath);
|
|
84
|
+
const result = spawnSync(bin, ["-d", "-i", opts.identityFile], {
|
|
85
|
+
input: ciphertext,
|
|
86
|
+
timeout: 30_000,
|
|
87
|
+
});
|
|
88
|
+
if (result.error)
|
|
89
|
+
throw result.error;
|
|
90
|
+
if (result.status !== 0) {
|
|
91
|
+
throw new AgeError("decrypt", (result.stderr ?? Buffer.from("")).toString("utf8"));
|
|
92
|
+
}
|
|
93
|
+
return Buffer.isBuffer(result.stdout) ? result.stdout : Buffer.from(result.stdout ?? "");
|
|
94
|
+
}
|
|
95
|
+
export function writeBufferTo(target, buf) {
|
|
96
|
+
writeFileSync(target, buf, { mode: 0o600 });
|
|
97
|
+
}
|
|
98
|
+
//# sourceMappingURL=age.js.map
|
package/dist/age.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"age.js","sourceRoot":"","sources":["../src/age.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export interface AuditRecord {
|
|
2
|
+
/** ISO 8601 timestamp; auto-populated by appendAuditRecord. */
|
|
3
|
+
ts: string;
|
|
4
|
+
/** Action name, e.g. "engagements-add", "allow", "deny". */
|
|
5
|
+
action: string;
|
|
6
|
+
/** Operator identity from `process.env.USER` (falls back to "unknown"). */
|
|
7
|
+
actor: string;
|
|
8
|
+
/** Process working directory at time of action. */
|
|
9
|
+
cwd?: string;
|
|
10
|
+
/** Git toplevel for the repo the action targeted, when applicable. */
|
|
11
|
+
repo?: string;
|
|
12
|
+
/** Single engagement id (allow/deny single, engagements add/end/remove). */
|
|
13
|
+
engagement?: string;
|
|
14
|
+
/** Multiple engagement ids (variadic allow/deny). */
|
|
15
|
+
engagements?: string[];
|
|
16
|
+
/** Free-form structural details. NEVER include literal marker patterns. */
|
|
17
|
+
details?: Record<string, unknown>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Write `{ enabled }` to the opts file (preserving the rotateBytes
|
|
21
|
+
* setting if one was configured). Used by the CLI's
|
|
22
|
+
* `audit-log on`/`audit-log off` subcommands. Creates the state/ dir
|
|
23
|
+
* with chmod 700 if missing; the file itself is chmod 600.
|
|
24
|
+
*/
|
|
25
|
+
export declare function setAuditLogEnabled(enabled: boolean): void;
|
|
26
|
+
/**
|
|
27
|
+
* Read the on/off bit for inspection (used by `audit-log path` and the
|
|
28
|
+
* CLI's "show" subcommand to surface the active state). Does NOT enable
|
|
29
|
+
* the log; that's setAuditLogEnabled's job.
|
|
30
|
+
*/
|
|
31
|
+
export declare function isAuditLogEnabled(): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Path to the active audit-log file. Re-exported here as well as from
|
|
34
|
+
* paths.ts so callers reaching for the writer don't have to hop modules.
|
|
35
|
+
*/
|
|
36
|
+
export declare function activeAuditLogPath(): string;
|
|
37
|
+
/**
|
|
38
|
+
* Append one record to the active audit log. ts and actor are filled in
|
|
39
|
+
* here so callers don't have to plumb them through.
|
|
40
|
+
*
|
|
41
|
+
* Fast-path no-op when the audit log is OFF (config absent or
|
|
42
|
+
* `enabled: false`). When ON, runs under withLockSync so concurrent
|
|
43
|
+
* writers can't interleave a half-written line.
|
|
44
|
+
*
|
|
45
|
+
* Marker safety contract: callers MUST NOT pass literal marker patterns
|
|
46
|
+
* or matched substrings in `details`. Engagement ids, counts, file
|
|
47
|
+
* paths, and class names are fine; pattern source strings are not.
|
|
48
|
+
*/
|
|
49
|
+
export declare function appendAuditRecord(rec: Omit<AuditRecord, "ts" | "actor">): void;
|
|
50
|
+
//# sourceMappingURL=audit-log.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.d.ts","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"AA4CA,MAAM,WAAW,WAAW;IAC1B,+DAA+D;IAC/D,EAAE,EAAE,MAAM,CAAC;IACX,4DAA4D;IAC5D,MAAM,EAAE,MAAM,CAAC;IACf,2EAA2E;IAC3E,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,qDAAqD;IACrD,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAuCD;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAYzD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAE3C;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AA8BD;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,WAAW,EAAE,IAAI,GAAG,OAAO,CAAC,GAAG,IAAI,CAqC9E"}
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Richard Myers and contributors.
|
|
3
|
+
// Operator audit log for repo-aegis.
|
|
4
|
+
//
|
|
5
|
+
// What this is: an append-only JSON Lines file that records
|
|
6
|
+
// state-changing operations (allow / deny / engagements add ... /
|
|
7
|
+
// install hooks / etc) so the operator can answer compliance questions
|
|
8
|
+
// like "did I allow customer-A in this repo on date X" from a single
|
|
9
|
+
// file rather than a git-archaeology session across .git/config and
|
|
10
|
+
// the engagement registry.
|
|
11
|
+
//
|
|
12
|
+
// Default state: OFF. The function is a fast-path no-op if the config
|
|
13
|
+
// file (`<home>/state/audit-log.json`) is absent or has
|
|
14
|
+
// `enabled: false`. Existing users see no behaviour change after
|
|
15
|
+
// upgrade.
|
|
16
|
+
//
|
|
17
|
+
// Marker safety: this module never writes literal marker patterns or
|
|
18
|
+
// matched substrings. Callers pass structural metadata only (engagement
|
|
19
|
+
// ids, action names, counts). The reviewer-test in `audit-log.test.ts`
|
|
20
|
+
// asserts that registry-resident literal patterns never appear in the
|
|
21
|
+
// audit log even when the recorded engagement-id matches.
|
|
22
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync, } from "node:fs";
|
|
23
|
+
import { dirname } from "node:path";
|
|
24
|
+
import { auditLogPath as defaultAuditLogPath, statePath } from "./paths.js";
|
|
25
|
+
import { withLockSync } from "./lock.js";
|
|
26
|
+
/**
|
|
27
|
+
* Default rotation threshold: 10 MiB. Tuned so the active log stays
|
|
28
|
+
* readable end-to-end with `cat` / `tail` without paging tools, but big
|
|
29
|
+
* enough that a year of routine operator work fits in a handful of
|
|
30
|
+
* rotated files.
|
|
31
|
+
*/
|
|
32
|
+
const DEFAULT_ROTATE_BYTES = 10 * 1024 * 1024;
|
|
33
|
+
/**
|
|
34
|
+
* Path to the audit-log opts file. Lives under `state/` alongside the
|
|
35
|
+
* other operator-controlled flags (leak-context-mode, registry.encrypted).
|
|
36
|
+
*/
|
|
37
|
+
function auditLogConfigPath() {
|
|
38
|
+
return `${statePath()}/audit-log.json`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read the opts file. Absent / unparseable / `enabled: false` all map
|
|
42
|
+
* to `{ enabled: false }`. Returning a typed shape keeps the call site
|
|
43
|
+
* tidy: the caller checks one boolean.
|
|
44
|
+
*/
|
|
45
|
+
function readConfig() {
|
|
46
|
+
const path = auditLogConfigPath();
|
|
47
|
+
if (!existsSync(path))
|
|
48
|
+
return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
|
|
55
|
+
}
|
|
56
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
57
|
+
return { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
|
|
58
|
+
}
|
|
59
|
+
const obj = parsed;
|
|
60
|
+
const enabled = obj["enabled"] === true;
|
|
61
|
+
const rb = obj["rotateBytes"];
|
|
62
|
+
const rotateBytes = typeof rb === "number" && rb > 0 ? rb : DEFAULT_ROTATE_BYTES;
|
|
63
|
+
return { enabled, rotateBytes };
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Write `{ enabled }` to the opts file (preserving the rotateBytes
|
|
67
|
+
* setting if one was configured). Used by the CLI's
|
|
68
|
+
* `audit-log on`/`audit-log off` subcommands. Creates the state/ dir
|
|
69
|
+
* with chmod 700 if missing; the file itself is chmod 600.
|
|
70
|
+
*/
|
|
71
|
+
export function setAuditLogEnabled(enabled) {
|
|
72
|
+
const path = auditLogConfigPath();
|
|
73
|
+
mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
|
|
74
|
+
let prev = { enabled: false, rotateBytes: DEFAULT_ROTATE_BYTES };
|
|
75
|
+
if (existsSync(path))
|
|
76
|
+
prev = readConfig();
|
|
77
|
+
const next = { enabled, rotateBytes: prev.rotateBytes };
|
|
78
|
+
writeFileSync(path, JSON.stringify(next, null, 2) + "\n", { mode: 0o600 });
|
|
79
|
+
try {
|
|
80
|
+
chmodSync(path, 0o600);
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
/* platform-restricted */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Read the on/off bit for inspection (used by `audit-log path` and the
|
|
88
|
+
* CLI's "show" subcommand to surface the active state). Does NOT enable
|
|
89
|
+
* the log; that's setAuditLogEnabled's job.
|
|
90
|
+
*/
|
|
91
|
+
export function isAuditLogEnabled() {
|
|
92
|
+
return readConfig().enabled;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Path to the active audit-log file. Re-exported here as well as from
|
|
96
|
+
* paths.ts so callers reaching for the writer don't have to hop modules.
|
|
97
|
+
*/
|
|
98
|
+
export function activeAuditLogPath() {
|
|
99
|
+
return defaultAuditLogPath();
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Rotate the active log if it exceeds rotateBytes. The rotated copy is
|
|
103
|
+
* named `audit.log.<iso>` (colons stripped so the filename is portable
|
|
104
|
+
* across filesystems). A fresh empty file is created in place.
|
|
105
|
+
*
|
|
106
|
+
* Caller must hold the registry lock; rotation reads + renames the
|
|
107
|
+
* active log and is racy without it.
|
|
108
|
+
*/
|
|
109
|
+
function maybeRotate(activePath, rotateBytes) {
|
|
110
|
+
if (!existsSync(activePath))
|
|
111
|
+
return;
|
|
112
|
+
let size = 0;
|
|
113
|
+
try {
|
|
114
|
+
size = statSync(activePath).size;
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (size < rotateBytes)
|
|
120
|
+
return;
|
|
121
|
+
// Filename-safe ISO: 2026-05-02T12-34-56.789Z
|
|
122
|
+
const isoStamp = new Date().toISOString().replace(/:/g, "-");
|
|
123
|
+
const rotated = `${activePath}.${isoStamp}`;
|
|
124
|
+
renameSync(activePath, rotated);
|
|
125
|
+
try {
|
|
126
|
+
chmodSync(rotated, 0o600);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
/* platform-restricted */
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Append one record to the active audit log. ts and actor are filled in
|
|
134
|
+
* here so callers don't have to plumb them through.
|
|
135
|
+
*
|
|
136
|
+
* Fast-path no-op when the audit log is OFF (config absent or
|
|
137
|
+
* `enabled: false`). When ON, runs under withLockSync so concurrent
|
|
138
|
+
* writers can't interleave a half-written line.
|
|
139
|
+
*
|
|
140
|
+
* Marker safety contract: callers MUST NOT pass literal marker patterns
|
|
141
|
+
* or matched substrings in `details`. Engagement ids, counts, file
|
|
142
|
+
* paths, and class names are fine; pattern source strings are not.
|
|
143
|
+
*/
|
|
144
|
+
export function appendAuditRecord(rec) {
|
|
145
|
+
const cfg = readConfig();
|
|
146
|
+
if (!cfg.enabled)
|
|
147
|
+
return;
|
|
148
|
+
const full = {
|
|
149
|
+
ts: new Date().toISOString(),
|
|
150
|
+
action: rec.action,
|
|
151
|
+
actor: process.env["USER"] ?? "unknown",
|
|
152
|
+
...(rec.cwd !== undefined && { cwd: rec.cwd }),
|
|
153
|
+
...(rec.repo !== undefined && { repo: rec.repo }),
|
|
154
|
+
...(rec.engagement !== undefined && { engagement: rec.engagement }),
|
|
155
|
+
...(rec.engagements !== undefined && { engagements: rec.engagements }),
|
|
156
|
+
...(rec.details !== undefined && { details: rec.details }),
|
|
157
|
+
};
|
|
158
|
+
const path = activeAuditLogPath();
|
|
159
|
+
const dir = dirname(path);
|
|
160
|
+
try {
|
|
161
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
/* dir already exists or platform-restricted; appendFileSync will surface real errors */
|
|
165
|
+
}
|
|
166
|
+
withLockSync(() => {
|
|
167
|
+
maybeRotate(path, cfg.rotateBytes);
|
|
168
|
+
const line = JSON.stringify(full) + "\n";
|
|
169
|
+
if (!existsSync(path)) {
|
|
170
|
+
writeFileSync(path, line, { mode: 0o600 });
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
appendFileSync(path, line);
|
|
174
|
+
}
|
|
175
|
+
try {
|
|
176
|
+
chmodSync(path, 0o600);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
/* platform-restricted */
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
//# sourceMappingURL=audit-log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.js","sourceRoot":"","sources":["../src/audit-log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.test.d.ts","sourceRoot":"","sources":["../src/audit-log.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
2
|
+
// Copyright (C) 2026 Richard Myers and contributors.
|
|
3
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, rmSync, writeFileSync, } from "node:fs";
|
|
6
|
+
import { tmpdir } from "node:os";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { appendAuditRecord, isAuditLogEnabled, setAuditLogEnabled, activeAuditLogPath, } from "./audit-log.js";
|
|
9
|
+
import { auditLogPath, statePath } from "./paths.js";
|
|
10
|
+
let home;
|
|
11
|
+
let originalHome;
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
home = mkdtempSync(join(tmpdir(), "repo-aegis-audit-log-"));
|
|
14
|
+
originalHome = process.env["REPO_AEGIS_HOME"];
|
|
15
|
+
process.env["REPO_AEGIS_HOME"] = home;
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
if (originalHome === undefined)
|
|
19
|
+
delete process.env["REPO_AEGIS_HOME"];
|
|
20
|
+
else
|
|
21
|
+
process.env["REPO_AEGIS_HOME"] = originalHome;
|
|
22
|
+
rmSync(home, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
describe("appendAuditRecord — disabled by default", () => {
|
|
25
|
+
it("is a no-op when no config file exists", () => {
|
|
26
|
+
assert.equal(isAuditLogEnabled(), false);
|
|
27
|
+
appendAuditRecord({ action: "allow", engagement: "customer-a" });
|
|
28
|
+
// Active log path must NOT have been created.
|
|
29
|
+
assert.equal(existsSync(auditLogPath()), false, "audit.log should not exist when disabled");
|
|
30
|
+
});
|
|
31
|
+
it("is a no-op when config sets enabled: false", () => {
|
|
32
|
+
mkdirSync(statePath(), { recursive: true, mode: 0o700 });
|
|
33
|
+
writeFileSync(join(statePath(), "audit-log.json"), JSON.stringify({ enabled: false }));
|
|
34
|
+
assert.equal(isAuditLogEnabled(), false);
|
|
35
|
+
appendAuditRecord({ action: "deny", engagement: "customer-a" });
|
|
36
|
+
assert.equal(existsSync(auditLogPath()), false);
|
|
37
|
+
});
|
|
38
|
+
it("is a no-op when config file is malformed JSON", () => {
|
|
39
|
+
mkdirSync(statePath(), { recursive: true, mode: 0o700 });
|
|
40
|
+
writeFileSync(join(statePath(), "audit-log.json"), "{this is not json");
|
|
41
|
+
assert.equal(isAuditLogEnabled(), false);
|
|
42
|
+
appendAuditRecord({ action: "allow", engagement: "customer-a" });
|
|
43
|
+
assert.equal(existsSync(auditLogPath()), false);
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
describe("setAuditLogEnabled / isAuditLogEnabled", () => {
|
|
47
|
+
it("turns the audit log on and off", () => {
|
|
48
|
+
assert.equal(isAuditLogEnabled(), false);
|
|
49
|
+
setAuditLogEnabled(true);
|
|
50
|
+
assert.equal(isAuditLogEnabled(), true);
|
|
51
|
+
setAuditLogEnabled(false);
|
|
52
|
+
assert.equal(isAuditLogEnabled(), false);
|
|
53
|
+
});
|
|
54
|
+
it("creates state/ with the right permissions", () => {
|
|
55
|
+
setAuditLogEnabled(true);
|
|
56
|
+
assert.ok(existsSync(statePath()));
|
|
57
|
+
assert.ok(existsSync(join(statePath(), "audit-log.json")));
|
|
58
|
+
});
|
|
59
|
+
it("activeAuditLogPath matches paths.ts auditLogPath()", () => {
|
|
60
|
+
assert.equal(activeAuditLogPath(), auditLogPath());
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
describe("appendAuditRecord — enabled", () => {
|
|
64
|
+
beforeEach(() => {
|
|
65
|
+
setAuditLogEnabled(true);
|
|
66
|
+
});
|
|
67
|
+
it("appends a JSONL record with auto-populated ts + actor", () => {
|
|
68
|
+
appendAuditRecord({
|
|
69
|
+
action: "allow",
|
|
70
|
+
engagement: "customer-a",
|
|
71
|
+
cwd: "/tmp/some-repo",
|
|
72
|
+
});
|
|
73
|
+
const body = readFileSync(auditLogPath(), "utf8");
|
|
74
|
+
assert.match(body, /\n$/, "JSONL records must end with a newline");
|
|
75
|
+
const lines = body.split("\n").filter(l => l.length > 0);
|
|
76
|
+
assert.equal(lines.length, 1);
|
|
77
|
+
const rec = JSON.parse(lines[0]);
|
|
78
|
+
assert.equal(rec["action"], "allow");
|
|
79
|
+
assert.equal(rec["engagement"], "customer-a");
|
|
80
|
+
assert.equal(rec["cwd"], "/tmp/some-repo");
|
|
81
|
+
// ts must parse as a date
|
|
82
|
+
assert.ok(typeof rec["ts"] === "string");
|
|
83
|
+
const t = new Date(rec["ts"]);
|
|
84
|
+
assert.ok(!Number.isNaN(t.getTime()), "ts must be a parseable ISO 8601 timestamp");
|
|
85
|
+
// actor is process.env.USER or "unknown"
|
|
86
|
+
assert.ok(typeof rec["actor"] === "string");
|
|
87
|
+
});
|
|
88
|
+
it("appends multiple records as separate lines", () => {
|
|
89
|
+
appendAuditRecord({ action: "allow", engagement: "customer-a" });
|
|
90
|
+
appendAuditRecord({ action: "deny", engagement: "customer-a" });
|
|
91
|
+
appendAuditRecord({
|
|
92
|
+
action: "engagements-add",
|
|
93
|
+
engagement: "customer-b",
|
|
94
|
+
details: { markerCount: 2 },
|
|
95
|
+
});
|
|
96
|
+
const body = readFileSync(auditLogPath(), "utf8");
|
|
97
|
+
const lines = body.split("\n").filter(l => l.length > 0);
|
|
98
|
+
assert.equal(lines.length, 3);
|
|
99
|
+
const actions = lines.map(l => JSON.parse(l).action);
|
|
100
|
+
assert.deepEqual(actions, ["allow", "deny", "engagements-add"]);
|
|
101
|
+
});
|
|
102
|
+
it("preserves the engagements (plural) array", () => {
|
|
103
|
+
appendAuditRecord({
|
|
104
|
+
action: "allow",
|
|
105
|
+
engagements: ["customer-a", "customer-b"],
|
|
106
|
+
});
|
|
107
|
+
const body = readFileSync(auditLogPath(), "utf8");
|
|
108
|
+
const rec = JSON.parse(body.trim());
|
|
109
|
+
assert.deepEqual(rec.engagements, ["customer-a", "customer-b"]);
|
|
110
|
+
});
|
|
111
|
+
it("falls back to actor 'unknown' when USER env is unset", () => {
|
|
112
|
+
const prev = process.env["USER"];
|
|
113
|
+
delete process.env["USER"];
|
|
114
|
+
try {
|
|
115
|
+
appendAuditRecord({ action: "allow", engagement: "customer-a" });
|
|
116
|
+
const body = readFileSync(auditLogPath(), "utf8");
|
|
117
|
+
const rec = JSON.parse(body.trim());
|
|
118
|
+
assert.equal(rec.actor, "unknown");
|
|
119
|
+
}
|
|
120
|
+
finally {
|
|
121
|
+
if (prev !== undefined)
|
|
122
|
+
process.env["USER"] = prev;
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
describe("appendAuditRecord — rotation", () => {
|
|
127
|
+
it("rotates when file size meets or exceeds rotateBytes", () => {
|
|
128
|
+
// Configure an aggressively small threshold so a couple of records
|
|
129
|
+
// trip the rotation logic.
|
|
130
|
+
mkdirSync(statePath(), { recursive: true, mode: 0o700 });
|
|
131
|
+
writeFileSync(join(statePath(), "audit-log.json"), JSON.stringify({ enabled: true, rotateBytes: 200 }));
|
|
132
|
+
// Each record is ~120 bytes; two of them push past 200.
|
|
133
|
+
appendAuditRecord({
|
|
134
|
+
action: "engagements-add",
|
|
135
|
+
engagement: "customer-aaaaaaaaaaaaaaaa",
|
|
136
|
+
details: { padding: "x".repeat(50) },
|
|
137
|
+
});
|
|
138
|
+
appendAuditRecord({
|
|
139
|
+
action: "engagements-add",
|
|
140
|
+
engagement: "customer-bbbbbbbbbbbbbbbb",
|
|
141
|
+
details: { padding: "y".repeat(50) },
|
|
142
|
+
});
|
|
143
|
+
// The third call should detect the size threshold and rotate before
|
|
144
|
+
// the third record is written. After the call we expect (a) a
|
|
145
|
+
// rotated `audit.log.*` file and (b) a fresh `audit.log` containing
|
|
146
|
+
// only the third record.
|
|
147
|
+
appendAuditRecord({
|
|
148
|
+
action: "engagements-add",
|
|
149
|
+
engagement: "customer-c",
|
|
150
|
+
});
|
|
151
|
+
const files = readdirSync(statePath());
|
|
152
|
+
const rotated = files.filter(f => f.startsWith("audit.log."));
|
|
153
|
+
assert.ok(rotated.length >= 1, `expected at least one rotated log; got: ${files.join(",")}`);
|
|
154
|
+
const active = readFileSync(auditLogPath(), "utf8");
|
|
155
|
+
const lines = active.split("\n").filter(l => l.length > 0);
|
|
156
|
+
assert.equal(lines.length, 1, "fresh active log should hold exactly the post-rotation record");
|
|
157
|
+
assert.equal(JSON.parse(lines[0]).engagement, "customer-c");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
describe("appendAuditRecord — marker scrub (reviewer test)", () => {
|
|
161
|
+
it("never persists a literal marker pattern even when the engagement-id matches", () => {
|
|
162
|
+
setAuditLogEnabled(true);
|
|
163
|
+
// Simulate a real-looking marker pattern from the registry. The
|
|
164
|
+
// contract: even though we record the engagement id, the literal
|
|
165
|
+
// pattern itself must NOT appear in the audit log.
|
|
166
|
+
const literalMarker = "Z9LITERAL-MARKER-PATTERN-Z9";
|
|
167
|
+
// The caller is supposed to pass structural metadata only. Pass
|
|
168
|
+
// engagement-id (allowed) and a marker COUNT in details (allowed);
|
|
169
|
+
// do NOT pass the literal pattern itself.
|
|
170
|
+
appendAuditRecord({
|
|
171
|
+
action: "engagements-add",
|
|
172
|
+
engagement: "customer-z",
|
|
173
|
+
details: { markerCount: 1 },
|
|
174
|
+
});
|
|
175
|
+
const body = readFileSync(auditLogPath(), "utf8");
|
|
176
|
+
assert.ok(!body.includes(literalMarker), `audit log must not contain the literal marker pattern; got:\n${body}`);
|
|
177
|
+
// The engagement id is fine (it's an opaque identifier the operator picks).
|
|
178
|
+
assert.match(body, /customer-z/);
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
//# sourceMappingURL=audit-log.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-log.test.js","sourceRoot":"","sources":["../src/audit-log.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { RepoConfig } from "./repo.js";
|
|
2
|
+
export declare const ALWAYS_FILE_STEM = "_always";
|
|
3
|
+
export interface DenySetFile {
|
|
4
|
+
stem: string;
|
|
5
|
+
path: string;
|
|
6
|
+
}
|
|
7
|
+
export interface DenySet {
|
|
8
|
+
files: DenySetFile[];
|
|
9
|
+
patterns: string[];
|
|
10
|
+
/**
|
|
11
|
+
* Parallel to `patterns`: `patternSources[i]` is the file stem (engagement
|
|
12
|
+
* id or `_always`) that pattern i was loaded from. Used by scanText to
|
|
13
|
+
* attribute each hit to its source engagement, surfaced as
|
|
14
|
+
* {@link ScanHit.engagement}.
|
|
15
|
+
*
|
|
16
|
+
* Optional for backward compatibility with fixtures and ad-hoc DenySet
|
|
17
|
+
* literals; runtime callers (computeDenySet) always populate it. When
|
|
18
|
+
* absent or length-mismatched, scanText falls back to no-attribution.
|
|
19
|
+
*/
|
|
20
|
+
patternSources?: string[];
|
|
21
|
+
combinedRegex: string;
|
|
22
|
+
warnings: string[];
|
|
23
|
+
}
|
|
24
|
+
export interface DenySetOptions {
|
|
25
|
+
markersDir?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Path to the cache file. Default: `<home>/state/deny-set.cache.json`.
|
|
28
|
+
* Pass `null` to disable caching entirely (useful for tests).
|
|
29
|
+
*/
|
|
30
|
+
cachePath?: string | null;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Compute the per-repo deny set. Class-aware:
|
|
34
|
+
*
|
|
35
|
+
* - `public-eligible` / `private-strict`: full union (every marker file).
|
|
36
|
+
* Engagement field on the repo is ignored; if set, a warning is emitted.
|
|
37
|
+
* - `customer-coupled`: union of `_always.txt` + every per-engagement file
|
|
38
|
+
* whose stem is NOT in this repo's `engagements` list.
|
|
39
|
+
* - `scratch`: same set as `customer-coupled`, but the caller (the CLI's
|
|
40
|
+
* `check`) treats hits as advisory and exits 0.
|
|
41
|
+
*/
|
|
42
|
+
export declare function computeDenySet(repo: RepoConfig, opts?: DenySetOptions): DenySet;
|
|
43
|
+
//# sourceMappingURL=deny-set.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"deny-set.d.ts","sourceRoot":"","sources":["../src/deny-set.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAE5C,eAAO,MAAM,gBAAgB,YAAY,CAAC;AAE1C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,OAAO;IACtB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB;;;;;;;;;OASG;IACH,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAsED;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,UAAU,EAAE,IAAI,GAAE,cAAmB,GAAG,OAAO,CAoGnF"}
|