- 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.
52 lines
1.9 KiB
TypeScript
52 lines
1.9 KiB
TypeScript
/**
|
|
* Audit Log Extension
|
|
*
|
|
* WHAT: Records session starts and slash-command usage to the local audit log
|
|
* (~/.config/pi-kit/audit.jsonl). Guardrail blocks are recorded by the
|
|
* guardrail extensions themselves via the shared audit() helper. If an
|
|
* audit endpoint is configured it is forwarded best-effort (see below).
|
|
*
|
|
* CONFIGURE: set `auditEndpoint` in config/company.json, or the PI_KIT_AUDIT_URL
|
|
* env var, to forward events. Turn everything off with PI_KIT_AUDIT_OFF=1.
|
|
*
|
|
* DISABLE: remove/rename this file from the package `extensions/` folder.
|
|
*/
|
|
|
|
import * as fs from "node:fs";
|
|
import * as path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
import { audit } from "./lib/audit.ts";
|
|
|
|
const HERE = path.dirname(fileURLToPath(import.meta.url));
|
|
const CONFIG_PATH = path.join(HERE, "..", "config", "company.json");
|
|
|
|
/** Wire company.json `auditEndpoint` into the env var read by audit() (unless already set). */
|
|
function initEndpointFromConfig(): void {
|
|
if (process.env.PI_KIT_AUDIT_URL) return;
|
|
try {
|
|
const cfg = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf8"));
|
|
if (typeof cfg.auditEndpoint === "string" && cfg.auditEndpoint) {
|
|
process.env.PI_KIT_AUDIT_URL = cfg.auditEndpoint;
|
|
}
|
|
} catch {
|
|
// No config / unreadable — local-only auditing.
|
|
}
|
|
}
|
|
|
|
export default function auditLogExtension(pi: ExtensionAPI) {
|
|
initEndpointFromConfig();
|
|
|
|
pi.on("session_start", async (event) => {
|
|
audit({ kind: "session_start", reason: (event as { reason?: string }).reason });
|
|
});
|
|
|
|
// Record slash-command usage (name only, no arguments — args may contain secrets).
|
|
pi.on("input", async (event) => {
|
|
const text = (event as { text?: string }).text ?? "";
|
|
const m = text.match(/^\/([a-z0-9:_-]+)/i);
|
|
if (m) audit({ kind: "command", command: m[1] });
|
|
return { action: "continue" };
|
|
});
|
|
}
|