- permission-gate: block refs/tags/v* pushes, rm -r -f separated flags, git reset --hard, git clean (verified against actual bypasses) - prompts: /bugfix /feature /review no longer hardcode go-standards — reference profile-gated <lang>-standards instead - company-context: drop hardcoded Go stack, note TRACKER_URL priority, warn on context truncation instead of silently dropping rules - repo-map/docs-map: graceful degradation when config values are TODO - /kit-doctor: warn on unfilled config fields (repoMap/trackerUrl/docsUrl) - audit: retry POSTs to endpoint (3 attempts, backoff), still best-effort - install.sh: remove TODO course URL from cheat sheet - tests: expand guardrails (43 node checks), add shell tests for create-mr.sh (scp/https origin parse, GITLAB_HOST override, protected branch refusal), cover company-context lib (normalize/fetch/truncation) and mcp-bridge - commit package-lock.json for reproducible installs - document npm test Node >= 22.6 requirement (type stripping)
66 lines
2.1 KiB
TypeScript
66 lines
2.1 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
|
|
* with a few retries. Posting never blocks or breaks the agent.
|
|
*
|
|
* 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;
|
|
const POST_ATTEMPTS = 3;
|
|
|
|
export interface AuditEntry {
|
|
kind: string; // e.g. "guardrail_block", "command", "session_start"
|
|
[key: string]: unknown;
|
|
}
|
|
|
|
/** POST a line best-effort with a couple of retries (never awaited). */
|
|
function postWithRetry(url: string, body: string): void {
|
|
const attempt = (n: number): Promise<void> =>
|
|
fetch(url, {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body,
|
|
signal: AbortSignal.timeout(POST_TIMEOUT_MS),
|
|
})
|
|
.then((res) => {
|
|
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
})
|
|
.catch((err) => {
|
|
if (n < POST_ATTEMPTS) {
|
|
const backoff = 200 * 2 ** (n - 1);
|
|
return new Promise<void>((resolve) => setTimeout(resolve, backoff)).then(() => attempt(n + 1));
|
|
}
|
|
throw err;
|
|
});
|
|
attempt(1).catch(() => {}); // fire-and-forget; failures are ignored
|
|
}
|
|
|
|
/** 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) {
|
|
postWithRetry(url, line);
|
|
}
|
|
}
|