- lib/secrets.ts: shared SECRET_PATTERNS + scanSecrets/redact (AWS/GitLab/GitHub/Slack tokens, private keys, JWT, generic credentials) - lib/audit.ts: local JSONL audit (~/.config/pi-kit/audit.jsonl, 600), optional PI_KIT_AUDIT_URL POST; never logs secret values - secret-scanner: block write/edit whose content matches a secret (complements protected-paths) - commit-guard: enforce Conventional Commits on 'git commit -m' + scan message for secrets - llm-redaction: redact secrets from outbound payload via before_provider_request - audit-log: session_start + slash-command usage; wires company.json auditEndpoint into env - config: auditEndpoint (null); package.json: npm test; test/guardrails.test.ts (16 cases, all pass) Verified: all extensions load via pi (lib/ not loaded as extensions); npm test green.
48 lines
1.5 KiB
TypeScript
48 lines
1.5 KiB
TypeScript
/**
|
|
* Shared local audit log used by the guardrail extensions.
|
|
*
|
|
* WHAT: appends JSONL events to ~/.config/pi-kit/audit.jsonl (chmod 600).
|
|
* Never logs secret values — only pattern names, paths, command shape.
|
|
* If PI_KIT_AUDIT_URL is set (or company.json auditEndpoint, wired by
|
|
* audit-log.ts into that env var), each event is also POSTed best-effort.
|
|
*
|
|
* DISABLE: remove the audit-log.ts extension, or set PI_KIT_AUDIT_OFF=1.
|
|
*/
|
|
|
|
import * as fs from "node:fs";
|
|
import * as os from "node:os";
|
|
import * as path from "node:path";
|
|
|
|
const DIR = path.join(os.homedir(), ".config", "pi-kit");
|
|
const FILE = path.join(DIR, "audit.jsonl");
|
|
const POST_TIMEOUT_MS = 1500;
|
|
|
|
export interface AuditEntry {
|
|
kind: string; // e.g. "guardrail_block", "command", "session_start"
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/** Append an audit entry locally and optionally forward it to an endpoint. */
|
|
export function audit(entry: AuditEntry): void {
|
|
if (process.env.PI_KIT_AUDIT_OFF === "1") return;
|
|
const line = JSON.stringify({ t: new Date().toISOString(), ...entry });
|
|
|
|
try {
|
|
fs.mkdirSync(DIR, { recursive: true, mode: 0o700 });
|
|
fs.appendFileSync(FILE, `${line}\n`, { mode: 0o600 });
|
|
} catch {
|
|
// Auditing must never break the agent; swallow local write errors.
|
|
}
|
|
|
|
const url = process.env.PI_KIT_AUDIT_URL;
|
|
if (url) {
|
|
// Best-effort, fire-and-forget. Never awaited; failures are ignored.
|
|
fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: line,
|
|
signal: AbortSignal.timeout(POST_TIMEOUT_MS),
|
|
}).catch(() => {});
|
|
}
|
|
}
|