@grwnd/pi-governance 1.8.0 → 1.9.1

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.
@@ -1533,15 +1533,15 @@ function sendJson(res, status, data) {
1533
1533
  res.end(JSON.stringify(data));
1534
1534
  }
1535
1535
  function readBody(req) {
1536
- return new Promise((resolve, reject) => {
1536
+ return new Promise((resolve2, reject) => {
1537
1537
  const chunks = [];
1538
1538
  req.on("data", (chunk) => chunks.push(chunk));
1539
- req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
1539
+ req.on("end", () => resolve2(Buffer.concat(chunks).toString("utf-8")));
1540
1540
  req.on("error", reject);
1541
1541
  });
1542
1542
  }
1543
1543
  function startWizardServer(options) {
1544
- return new Promise((resolve, reject) => {
1544
+ return new Promise((resolve2, reject) => {
1545
1545
  let shutdownTimer;
1546
1546
  const server = createServer((req, res) => {
1547
1547
  setCorsHeaders(res);
@@ -1631,7 +1631,7 @@ function startWizardServer(options) {
1631
1631
  shutdownTimer = setTimeout(() => {
1632
1632
  closeServer();
1633
1633
  }, AUTO_SHUTDOWN_MS);
1634
- resolve({ port: addr.port, close: closeServer });
1634
+ resolve2({ port: addr.port, close: closeServer });
1635
1635
  });
1636
1636
  });
1637
1637
  }
@@ -1681,6 +1681,7 @@ var init_wizard = __esm({
1681
1681
 
1682
1682
  // src/extensions/index.ts
1683
1683
  import { existsSync as existsSync2 } from "fs";
1684
+ import { resolve } from "path";
1684
1685
 
1685
1686
  // src/lib/config/loader.ts
1686
1687
  import { existsSync, readFileSync } from "fs";
@@ -2154,7 +2155,16 @@ var DANGEROUS_PATTERNS = [
2154
2155
  // Compiler/build (can execute arbitrary code)
2155
2156
  /\bmake\s/,
2156
2157
  /\bgcc\b/,
2157
- /\bg\+\+/
2158
+ /\bg\+\+/,
2159
+ // Governance config tampering — shell-based writes to governance files
2160
+ /(cat|echo|printf)\s.*>\s*.*governance(-rules)?\.yaml/,
2161
+ /\btee\s+.*governance(-rules)?\.yaml/,
2162
+ /sed\s+-i.*governance(-rules)?\.yaml/,
2163
+ /(cp|mv|rm)\s.*governance(-rules)?\.yaml/,
2164
+ /(cat|echo|printf)\s.*>\s*.*\.pi\/governance/,
2165
+ /\btee\s+.*\.pi\/governance/,
2166
+ /sed\s+-i.*\.pi\/governance/,
2167
+ /(cp|mv|rm)\s.*\.pi\/governance/
2158
2168
  ];
2159
2169
 
2160
2170
  // src/lib/bash/classifier.ts
@@ -2949,7 +2959,9 @@ var piGovernance = (pi) => {
2949
2959
  let configWatcher;
2950
2960
  let dlpScanner;
2951
2961
  let dlpMasker;
2962
+ let protectedPaths = /* @__PURE__ */ new Set();
2952
2963
  const stats = {
2964
+ configTampered: 0,
2953
2965
  allowed: 0,
2954
2966
  denied: 0,
2955
2967
  approvals: 0,
@@ -2963,6 +2975,15 @@ var piGovernance = (pi) => {
2963
2975
  sessionId = ctx.sessionId;
2964
2976
  const loaded = loadConfig();
2965
2977
  config = loaded.config;
2978
+ const paths = /* @__PURE__ */ new Set();
2979
+ if (loaded.source !== "built-in") {
2980
+ paths.add(resolve(loaded.source));
2981
+ }
2982
+ const rulesFileCfg = config.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
2983
+ paths.add(resolve(rulesFileCfg));
2984
+ paths.add(resolve(ctx.workingDirectory, ".pi/governance.yaml"));
2985
+ paths.add(resolve(ctx.workingDirectory, "governance-rules.yaml"));
2986
+ protectedPaths = paths;
2966
2987
  const chain = createIdentityChain(config.auth);
2967
2988
  identity = await chain.resolve();
2968
2989
  const rulesFile = config.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
@@ -3092,6 +3113,22 @@ var piGovernance = (pi) => {
3092
3113
  tool: toolName,
3093
3114
  input: params
3094
3115
  };
3116
+ if (WRITE_TOOLS.has(toolName)) {
3117
+ const filePath = extractPath(toolName, input);
3118
+ if (filePath && protectedPaths.has(resolve(filePath))) {
3119
+ stats.configTampered++;
3120
+ await audit.log({
3121
+ ...baseRecord,
3122
+ event: "config_tampered",
3123
+ decision: "denied",
3124
+ reason: `Config self-protection: write to governance file blocked (${filePath})`
3125
+ });
3126
+ return {
3127
+ block: true,
3128
+ reason: `Governance config files are protected and cannot be modified by agents`
3129
+ };
3130
+ }
3131
+ }
3095
3132
  if (executionMode === "dry_run") {
3096
3133
  stats.dryRun++;
3097
3134
  await audit.log({
@@ -3367,6 +3404,7 @@ var piGovernance = (pi) => {
3367
3404
  ` Approvals: ${stats.approvals}`,
3368
3405
  ` Dry-run blocks: ${stats.dryRun}`,
3369
3406
  ` Budget exceeded: ${stats.budgetExceeded}`,
3407
+ ` Config tampered: ${stats.configTampered}`,
3370
3408
  ` DLP blocked: ${stats.dlpBlocked}`,
3371
3409
  ` DLP detected: ${stats.dlpDetected}`,
3372
3410
  ` DLP masked: ${stats.dlpMasked}`,