@grwnd/pi-governance 1.4.2 → 1.5.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.
@@ -23,6 +23,7 @@ __export(extensions_exports, {
23
23
  default: () => extensions_default
24
24
  });
25
25
  module.exports = __toCommonJS(extensions_exports);
26
+ var import_node_fs2 = require("fs");
26
27
 
27
28
  // src/lib/config/loader.ts
28
29
  var import_fs = require("fs");
@@ -102,12 +103,78 @@ var OrgUnitOverride = import_typebox.Type.Object({
102
103
  })
103
104
  )
104
105
  });
106
+ var DlpMaskingConfig = import_typebox.Type.Object({
107
+ strategy: import_typebox.Type.Union([import_typebox.Type.Literal("partial"), import_typebox.Type.Literal("full"), import_typebox.Type.Literal("hash")], {
108
+ default: "partial"
109
+ }),
110
+ show_chars: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 4, minimum: 0 })),
111
+ placeholder: import_typebox.Type.Optional(import_typebox.Type.String({ default: "***" }))
112
+ });
113
+ var DlpCustomPatternConfig = import_typebox.Type.Object({
114
+ name: import_typebox.Type.String(),
115
+ pattern: import_typebox.Type.String(),
116
+ severity: import_typebox.Type.Union([
117
+ import_typebox.Type.Literal("low"),
118
+ import_typebox.Type.Literal("medium"),
119
+ import_typebox.Type.Literal("high"),
120
+ import_typebox.Type.Literal("critical")
121
+ ]),
122
+ action: import_typebox.Type.Optional(
123
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
124
+ )
125
+ });
126
+ var DlpAllowlistEntryConfig = import_typebox.Type.Object({
127
+ pattern: import_typebox.Type.String()
128
+ });
129
+ var DlpRoleOverrideConfig = import_typebox.Type.Object({
130
+ enabled: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
131
+ mode: import_typebox.Type.Optional(
132
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
133
+ ),
134
+ on_input: import_typebox.Type.Optional(
135
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
136
+ ),
137
+ on_output: import_typebox.Type.Optional(
138
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
139
+ )
140
+ });
141
+ var DlpConfig = import_typebox.Type.Object({
142
+ enabled: import_typebox.Type.Boolean({ default: false }),
143
+ mode: import_typebox.Type.Optional(
144
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")], {
145
+ default: "audit"
146
+ })
147
+ ),
148
+ on_input: import_typebox.Type.Optional(
149
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
150
+ ),
151
+ on_output: import_typebox.Type.Optional(
152
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
153
+ ),
154
+ masking: import_typebox.Type.Optional(DlpMaskingConfig),
155
+ severity_threshold: import_typebox.Type.Optional(
156
+ import_typebox.Type.Union(
157
+ [import_typebox.Type.Literal("low"), import_typebox.Type.Literal("medium"), import_typebox.Type.Literal("high"), import_typebox.Type.Literal("critical")],
158
+ { default: "low" }
159
+ )
160
+ ),
161
+ built_in: import_typebox.Type.Optional(
162
+ import_typebox.Type.Object({
163
+ secrets: import_typebox.Type.Boolean({ default: true }),
164
+ pii: import_typebox.Type.Boolean({ default: true })
165
+ })
166
+ ),
167
+ custom_patterns: import_typebox.Type.Optional(import_typebox.Type.Array(DlpCustomPatternConfig)),
168
+ allowlist: import_typebox.Type.Optional(import_typebox.Type.Array(DlpAllowlistEntryConfig)),
169
+ role_overrides: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), DlpRoleOverrideConfig))
170
+ });
105
171
  var GovernanceConfigSchema = import_typebox.Type.Object({
106
172
  auth: import_typebox.Type.Optional(AuthConfig),
107
173
  policy: import_typebox.Type.Optional(PolicyConfig),
108
174
  templates: import_typebox.Type.Optional(TemplatesConfig),
109
175
  hitl: import_typebox.Type.Optional(HitlConfig),
110
176
  audit: import_typebox.Type.Optional(AuditConfig),
177
+ dlp: import_typebox.Type.Optional(DlpConfig),
111
178
  org_units: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), OrgUnitOverride))
112
179
  });
113
180
 
@@ -138,6 +205,9 @@ var DEFAULTS = {
138
205
  },
139
206
  audit: {
140
207
  sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
208
+ },
209
+ dlp: {
210
+ enabled: false
141
211
  }
142
212
  };
143
213
 
@@ -847,6 +917,297 @@ var ConfigWatcher = class {
847
917
  }
848
918
  };
849
919
 
920
+ // src/lib/dlp/patterns.ts
921
+ var SECRET_PATTERNS = [
922
+ // AWS
923
+ {
924
+ name: "aws_access_key",
925
+ pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
926
+ severity: "critical",
927
+ category: "secret"
928
+ },
929
+ {
930
+ name: "aws_secret_key",
931
+ pattern: /\b([A-Za-z0-9/+=]{40})(?=\s|$|"|')/g,
932
+ severity: "critical",
933
+ category: "secret"
934
+ },
935
+ // GitHub
936
+ {
937
+ name: "github_pat",
938
+ pattern: /\b(ghp_[A-Za-z0-9]{36,})\b/g,
939
+ severity: "critical",
940
+ category: "secret"
941
+ },
942
+ {
943
+ name: "github_oauth",
944
+ pattern: /\b(gho_[A-Za-z0-9]{36,})\b/g,
945
+ severity: "high",
946
+ category: "secret"
947
+ },
948
+ {
949
+ name: "github_app_token",
950
+ pattern: /\b(ghu_[A-Za-z0-9]{36,})\b/g,
951
+ severity: "high",
952
+ category: "secret"
953
+ },
954
+ // Anthropic
955
+ {
956
+ name: "anthropic_api_key",
957
+ pattern: /\b(sk-ant-api03-[A-Za-z0-9_-]{90,})\b/g,
958
+ severity: "critical",
959
+ category: "secret"
960
+ },
961
+ // OpenAI
962
+ {
963
+ name: "openai_api_key",
964
+ pattern: /\b(sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,})\b/g,
965
+ severity: "critical",
966
+ category: "secret"
967
+ },
968
+ // JWT
969
+ {
970
+ name: "jwt_token",
971
+ pattern: /\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
972
+ severity: "high",
973
+ category: "secret"
974
+ },
975
+ // Private key headers
976
+ {
977
+ name: "private_key",
978
+ pattern: /-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
979
+ severity: "critical",
980
+ category: "secret"
981
+ },
982
+ // Database connection strings
983
+ {
984
+ name: "database_url",
985
+ pattern: /\b((?:postgres|mysql|mongodb|redis):\/\/[^\s'"]{10,})\b/g,
986
+ severity: "high",
987
+ category: "secret"
988
+ },
989
+ // Slack
990
+ {
991
+ name: "slack_token",
992
+ pattern: /\b(xox[bpras]-[A-Za-z0-9-]{10,})\b/g,
993
+ severity: "high",
994
+ category: "secret"
995
+ },
996
+ // Stripe
997
+ {
998
+ name: "stripe_key",
999
+ pattern: /\b([rs]k_(?:live|test)_[A-Za-z0-9]{20,})\b/g,
1000
+ severity: "critical",
1001
+ category: "secret"
1002
+ },
1003
+ // npm
1004
+ {
1005
+ name: "npm_token",
1006
+ pattern: /\b(npm_[A-Za-z0-9]{36,})\b/g,
1007
+ severity: "high",
1008
+ category: "secret"
1009
+ },
1010
+ // SendGrid
1011
+ {
1012
+ name: "sendgrid_key",
1013
+ pattern: /\b(SG\.[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{22,})\b/g,
1014
+ severity: "high",
1015
+ category: "secret"
1016
+ },
1017
+ // Generic API key patterns (env-var style assignments)
1018
+ {
1019
+ name: "generic_api_key",
1020
+ pattern: /\b(?:API_KEY|API_SECRET|ACCESS_TOKEN|AUTH_TOKEN|SECRET_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{16,})['"]?/gi,
1021
+ severity: "medium",
1022
+ category: "secret"
1023
+ },
1024
+ // High-entropy string near keyword context
1025
+ {
1026
+ name: "generic_secret_assignment",
1027
+ pattern: /\b(?:password|passwd|secret|token|credential)\s*[=:]\s*['"]([^'"]{8,})['"]?/gi,
1028
+ severity: "medium",
1029
+ category: "secret"
1030
+ }
1031
+ ];
1032
+ var PII_PATTERNS = [
1033
+ // SSN (US)
1034
+ {
1035
+ name: "ssn",
1036
+ pattern: /\b(\d{3}-\d{2}-\d{4})\b/g,
1037
+ severity: "critical",
1038
+ category: "pii"
1039
+ },
1040
+ // Credit card numbers
1041
+ {
1042
+ name: "credit_card",
1043
+ pattern: /\b(4\d{3}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}|5[1-5]\d{2}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}|3[47]\d{2}[\s-]?\d{6}[\s-]?\d{5}|6(?:011|5\d{2})[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4})\b/g,
1044
+ severity: "critical",
1045
+ category: "pii"
1046
+ },
1047
+ // Email address
1048
+ {
1049
+ name: "email",
1050
+ pattern: /\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
1051
+ severity: "low",
1052
+ category: "pii"
1053
+ },
1054
+ // US phone number
1055
+ {
1056
+ name: "phone_us",
1057
+ pattern: /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g,
1058
+ severity: "medium",
1059
+ category: "pii"
1060
+ },
1061
+ // IPv4 address
1062
+ {
1063
+ name: "ipv4",
1064
+ pattern: /\b((?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.(?:25[0-5]|2[0-4]\d|[01]?\d\d?))\b/g,
1065
+ severity: "low",
1066
+ category: "pii"
1067
+ }
1068
+ ];
1069
+
1070
+ // src/lib/dlp/scanner.ts
1071
+ var SEVERITY_ORDER = {
1072
+ low: 0,
1073
+ medium: 1,
1074
+ high: 2,
1075
+ critical: 3
1076
+ };
1077
+ var DlpScanner = class {
1078
+ patterns;
1079
+ allowlistRegexps;
1080
+ severityThreshold;
1081
+ config;
1082
+ constructor(config) {
1083
+ this.config = config;
1084
+ this.severityThreshold = SEVERITY_ORDER[config.severity_threshold];
1085
+ this.patterns = [];
1086
+ this.allowlistRegexps = [];
1087
+ if (config.built_in.secrets) {
1088
+ for (const def of SECRET_PATTERNS) {
1089
+ this.patterns.push({ def });
1090
+ }
1091
+ }
1092
+ if (config.built_in.pii) {
1093
+ for (const def of PII_PATTERNS) {
1094
+ this.patterns.push({ def });
1095
+ }
1096
+ }
1097
+ for (const cp of config.custom_patterns) {
1098
+ const def = {
1099
+ name: cp.name,
1100
+ pattern: new RegExp(cp.pattern, "g"),
1101
+ severity: cp.severity,
1102
+ category: "custom"
1103
+ };
1104
+ this.patterns.push({ def, action: cp.action });
1105
+ }
1106
+ for (const compiled of this.patterns) {
1107
+ const override = config.pattern_overrides.get(compiled.def.name);
1108
+ if (override) {
1109
+ compiled.action = override;
1110
+ }
1111
+ }
1112
+ for (const entry of config.allowlist) {
1113
+ this.allowlistRegexps.push(new RegExp(entry.pattern));
1114
+ }
1115
+ }
1116
+ scan(text) {
1117
+ if (!this.config.enabled || text.length === 0) {
1118
+ return { hasMatches: false, matches: [] };
1119
+ }
1120
+ const matches = [];
1121
+ for (const compiled of this.patterns) {
1122
+ if (SEVERITY_ORDER[compiled.def.severity] < this.severityThreshold) {
1123
+ continue;
1124
+ }
1125
+ const regex = new RegExp(compiled.def.pattern.source, compiled.def.pattern.flags);
1126
+ let match;
1127
+ while ((match = regex.exec(text)) !== null) {
1128
+ const matched = match[1] ?? match[0];
1129
+ const start = match[1] ? match.index + match[0].indexOf(match[1]) : match.index;
1130
+ const end = start + matched.length;
1131
+ if (this.isAllowlisted(matched)) {
1132
+ continue;
1133
+ }
1134
+ matches.push({
1135
+ patternName: compiled.def.name,
1136
+ category: compiled.def.category,
1137
+ severity: compiled.def.severity,
1138
+ start,
1139
+ end,
1140
+ matched
1141
+ });
1142
+ }
1143
+ }
1144
+ return { hasMatches: matches.length > 0, matches };
1145
+ }
1146
+ getAction(direction) {
1147
+ if (direction === "input" && this.config.on_input) {
1148
+ return this.config.on_input;
1149
+ }
1150
+ if (direction === "output" && this.config.on_output) {
1151
+ return this.config.on_output;
1152
+ }
1153
+ return this.config.mode;
1154
+ }
1155
+ getPatternAction(match, direction) {
1156
+ const compiled = this.patterns.find((p) => p.def.name === match.patternName);
1157
+ if (compiled?.action) {
1158
+ return compiled.action;
1159
+ }
1160
+ return this.getAction(direction);
1161
+ }
1162
+ isAllowlisted(value) {
1163
+ for (const re of this.allowlistRegexps) {
1164
+ if (re.test(value)) return true;
1165
+ }
1166
+ return false;
1167
+ }
1168
+ };
1169
+
1170
+ // src/lib/dlp/masker.ts
1171
+ var import_node_crypto2 = require("crypto");
1172
+ var DEFAULT_CONFIG = {
1173
+ strategy: "partial",
1174
+ show_chars: 4,
1175
+ placeholder: "***"
1176
+ };
1177
+ var DlpMasker = class {
1178
+ config;
1179
+ constructor(config) {
1180
+ this.config = { ...DEFAULT_CONFIG, ...config };
1181
+ }
1182
+ maskValue(value) {
1183
+ switch (this.config.strategy) {
1184
+ case "full":
1185
+ return this.config.placeholder;
1186
+ case "hash": {
1187
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex").slice(0, 8);
1188
+ return `[REDACTED:${hash}]`;
1189
+ }
1190
+ case "partial":
1191
+ default: {
1192
+ if (value.length <= this.config.show_chars) {
1193
+ return this.config.placeholder;
1194
+ }
1195
+ return this.config.placeholder + value.slice(-this.config.show_chars);
1196
+ }
1197
+ }
1198
+ }
1199
+ maskText(text, matches) {
1200
+ if (matches.length === 0) return text;
1201
+ const sorted = [...matches].sort((a, b) => b.start - a.start);
1202
+ let result = text;
1203
+ for (const match of sorted) {
1204
+ const masked = this.maskValue(match.matched);
1205
+ result = result.slice(0, match.start) + masked + result.slice(match.end);
1206
+ }
1207
+ return result;
1208
+ }
1209
+ };
1210
+
850
1211
  // src/extensions/index.ts
851
1212
  var PATH_TOOLS = {
852
1213
  read: "path",
@@ -884,6 +1245,72 @@ function extractPath(toolName, input) {
884
1245
  const val = input[key];
885
1246
  return typeof val === "string" ? val : void 0;
886
1247
  }
1248
+ var ACTION_PRIORITY = { audit: 0, mask: 1, block: 2 };
1249
+ function extractDlpFields(toolName, input) {
1250
+ const fields = /* @__PURE__ */ new Map();
1251
+ switch (toolName) {
1252
+ case "bash": {
1253
+ const cmd = input["command"];
1254
+ if (typeof cmd === "string") fields.set("command", cmd);
1255
+ break;
1256
+ }
1257
+ case "write": {
1258
+ const content = input["content"];
1259
+ if (typeof content === "string") fields.set("content", content);
1260
+ const path = input["path"];
1261
+ if (typeof path === "string") fields.set("path", path);
1262
+ break;
1263
+ }
1264
+ case "edit": {
1265
+ const newStr = input["new_string"];
1266
+ if (typeof newStr === "string") fields.set("new_string", newStr);
1267
+ const oldStr = input["old_string"];
1268
+ if (typeof oldStr === "string") fields.set("old_string", oldStr);
1269
+ break;
1270
+ }
1271
+ default: {
1272
+ for (const [key, val] of Object.entries(input)) {
1273
+ if (typeof val === "string") fields.set(key, val);
1274
+ }
1275
+ }
1276
+ }
1277
+ return fields;
1278
+ }
1279
+ function resolveHighestAction(scanner, matches, direction) {
1280
+ let highest = "audit";
1281
+ for (const match of matches) {
1282
+ const action = scanner.getPatternAction(match, direction);
1283
+ if (ACTION_PRIORITY[action] > ACTION_PRIORITY[highest]) {
1284
+ highest = action;
1285
+ }
1286
+ }
1287
+ return highest;
1288
+ }
1289
+ function resolveDlpConfig(dlpConfig, role) {
1290
+ if (!dlpConfig?.enabled) return void 0;
1291
+ const roleOverride = dlpConfig.role_overrides?.[role];
1292
+ if (roleOverride?.enabled === false) return void 0;
1293
+ const patternOverrides = /* @__PURE__ */ new Map();
1294
+ return {
1295
+ enabled: true,
1296
+ mode: roleOverride?.mode ?? dlpConfig.mode ?? "audit",
1297
+ on_input: roleOverride?.on_input ?? dlpConfig.on_input,
1298
+ on_output: roleOverride?.on_output ?? dlpConfig.on_output,
1299
+ severity_threshold: dlpConfig.severity_threshold ?? "low",
1300
+ built_in: {
1301
+ secrets: dlpConfig.built_in?.secrets ?? true,
1302
+ pii: dlpConfig.built_in?.pii ?? true
1303
+ },
1304
+ custom_patterns: (dlpConfig.custom_patterns ?? []).map((cp) => ({
1305
+ name: cp.name,
1306
+ pattern: cp.pattern,
1307
+ severity: cp.severity,
1308
+ action: cp.action
1309
+ })),
1310
+ allowlist: dlpConfig.allowlist ?? [],
1311
+ pattern_overrides: patternOverrides
1312
+ };
1313
+ }
887
1314
  var piGovernance = (pi) => {
888
1315
  let config;
889
1316
  let policyEngine;
@@ -895,7 +1322,18 @@ var piGovernance = (pi) => {
895
1322
  let sessionId;
896
1323
  let budgetTracker;
897
1324
  let configWatcher;
898
- const stats = { allowed: 0, denied: 0, approvals: 0, dryRun: 0, budgetExceeded: 0 };
1325
+ let dlpScanner;
1326
+ let dlpMasker;
1327
+ const stats = {
1328
+ allowed: 0,
1329
+ denied: 0,
1330
+ approvals: 0,
1331
+ dryRun: 0,
1332
+ budgetExceeded: 0,
1333
+ dlpBlocked: 0,
1334
+ dlpDetected: 0,
1335
+ dlpMasked: 0
1336
+ };
899
1337
  pi.on("session_start", async (_event, ctx) => {
900
1338
  sessionId = ctx.sessionId;
901
1339
  const loaded = loadConfig();
@@ -903,7 +1341,45 @@ var piGovernance = (pi) => {
903
1341
  const chain = createIdentityChain(config.auth);
904
1342
  identity = await chain.resolve();
905
1343
  const rulesFile = config.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
906
- policyEngine = new YamlPolicyEngine(rulesFile);
1344
+ if ((0, import_node_fs2.existsSync)(rulesFile)) {
1345
+ policyEngine = new YamlPolicyEngine(rulesFile);
1346
+ } else {
1347
+ policyEngine = new YamlPolicyEngine({
1348
+ roles: {
1349
+ admin: {
1350
+ allowed_tools: ["all"],
1351
+ blocked_tools: [],
1352
+ prompt_template: "admin",
1353
+ execution_mode: "autonomous",
1354
+ human_approval: { required_for: [] },
1355
+ token_budget_daily: -1,
1356
+ allowed_paths: ["**"],
1357
+ blocked_paths: []
1358
+ },
1359
+ project_lead: {
1360
+ allowed_tools: ["all"],
1361
+ blocked_tools: [],
1362
+ prompt_template: "project-lead",
1363
+ execution_mode: "supervised",
1364
+ human_approval: { required_for: ["bash", "write"] },
1365
+ token_budget_daily: -1,
1366
+ allowed_paths: ["**"],
1367
+ blocked_paths: []
1368
+ },
1369
+ analyst: {
1370
+ allowed_tools: ["read", "grep", "find", "ls"],
1371
+ blocked_tools: ["write", "edit", "bash"],
1372
+ prompt_template: "analyst",
1373
+ execution_mode: "supervised",
1374
+ human_approval: { required_for: ["all"] },
1375
+ token_budget_daily: -1,
1376
+ allowed_paths: ["**"],
1377
+ blocked_paths: []
1378
+ }
1379
+ }
1380
+ });
1381
+ ctx.ui.notify(`Rules file not found: ${rulesFile} \u2014 using built-in defaults`, "warning");
1382
+ }
907
1383
  executionMode = policyEngine.getExecutionMode(identity.role);
908
1384
  const bashOverrides = policyEngine.getBashOverrides(identity.role);
909
1385
  bashClassifier = new BashClassifier(bashOverrides);
@@ -924,15 +1400,30 @@ var piGovernance = (pi) => {
924
1400
  }
925
1401
  const budget = policyEngine.getTokenBudget(identity.role);
926
1402
  budgetTracker = new BudgetTracker(budget);
1403
+ const dlpCfg = resolveDlpConfig(config.dlp, identity.role);
1404
+ if (dlpCfg) {
1405
+ dlpScanner = new DlpScanner(dlpCfg);
1406
+ dlpMasker = new DlpMasker(config.dlp?.masking);
1407
+ }
927
1408
  if (loaded.source !== "built-in") {
928
1409
  configWatcher = new ConfigWatcher(
929
1410
  loaded.source,
930
1411
  (newConfig) => {
931
1412
  config = newConfig;
932
1413
  const newRulesFile = newConfig.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
933
- policyEngine = new YamlPolicyEngine(newRulesFile);
1414
+ if ((0, import_node_fs2.existsSync)(newRulesFile)) {
1415
+ policyEngine = new YamlPolicyEngine(newRulesFile);
1416
+ }
934
1417
  const newOverrides = policyEngine.getBashOverrides(identity.role);
935
1418
  bashClassifier = new BashClassifier(newOverrides);
1419
+ const newDlpCfg = resolveDlpConfig(newConfig.dlp, identity.role);
1420
+ if (newDlpCfg) {
1421
+ dlpScanner = new DlpScanner(newDlpCfg);
1422
+ dlpMasker = new DlpMasker(newConfig.dlp?.masking);
1423
+ } else {
1424
+ dlpScanner = void 0;
1425
+ dlpMasker = void 0;
1426
+ }
936
1427
  audit.log({
937
1428
  sessionId,
938
1429
  event: "config_reloaded",
@@ -1080,6 +1571,63 @@ var piGovernance = (pi) => {
1080
1571
  return { block: true, reason: `Access denied to path: ${path}` };
1081
1572
  }
1082
1573
  }
1574
+ if (dlpScanner && dlpMasker) {
1575
+ const fields = extractDlpFields(toolName, input);
1576
+ const allMatches = [];
1577
+ for (const [, fieldValue] of fields) {
1578
+ const result = dlpScanner.scan(fieldValue);
1579
+ allMatches.push(...result.matches);
1580
+ }
1581
+ if (allMatches.length > 0) {
1582
+ const action = resolveHighestAction(dlpScanner, allMatches, "input");
1583
+ const patternNames = [...new Set(allMatches.map((m) => m.patternName))];
1584
+ const severities = [...new Set(allMatches.map((m) => m.severity))];
1585
+ const dlpMeta = {
1586
+ patterns: patternNames,
1587
+ severities,
1588
+ direction: "input",
1589
+ count: allMatches.length
1590
+ };
1591
+ if (action === "block") {
1592
+ stats.dlpBlocked++;
1593
+ await audit.log({
1594
+ ...baseRecord,
1595
+ event: "dlp_blocked",
1596
+ decision: "denied",
1597
+ reason: `DLP: ${patternNames.join(", ")} detected in input`,
1598
+ metadata: dlpMeta
1599
+ });
1600
+ return {
1601
+ block: true,
1602
+ reason: `DLP blocked: sensitive data detected (${patternNames.join(", ")})`
1603
+ };
1604
+ }
1605
+ if (action === "mask") {
1606
+ stats.dlpMasked++;
1607
+ for (const [fieldKey, fieldValue] of fields) {
1608
+ const fieldResult = dlpScanner.scan(fieldValue);
1609
+ if (fieldResult.hasMatches) {
1610
+ input[fieldKey] = dlpMasker.maskText(
1611
+ fieldValue,
1612
+ fieldResult.matches
1613
+ );
1614
+ }
1615
+ }
1616
+ await audit.log({
1617
+ ...baseRecord,
1618
+ event: "dlp_masked",
1619
+ metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
1620
+ });
1621
+ } else {
1622
+ stats.dlpDetected++;
1623
+ await audit.log({
1624
+ ...baseRecord,
1625
+ event: "dlp_detected",
1626
+ metadata: dlpMeta
1627
+ });
1628
+ }
1629
+ }
1630
+ }
1083
1631
  if (toolName !== "bash" && policyEngine.requiresApproval(identity.role, toolName)) {
1084
1632
  if (approvalFlow) {
1085
1633
  stats.approvals++;
@@ -1107,6 +1655,44 @@ var piGovernance = (pi) => {
1107
1655
  return void 0;
1108
1656
  });
1109
1657
  pi.on("tool_result", async (event, _ctx) => {
1658
+ if (dlpScanner && dlpMasker && event.output) {
1659
+ const result = dlpScanner.scan(event.output);
1660
+ if (result.hasMatches) {
1661
+ const action = resolveHighestAction(dlpScanner, result.matches, "output");
1662
+ const patternNames = [...new Set(result.matches.map((m) => m.patternName))];
1663
+ const severities = [...new Set(result.matches.map((m) => m.severity))];
1664
+ const dlpMeta = {
1665
+ patterns: patternNames,
1666
+ severities,
1667
+ direction: "output",
1668
+ count: result.matches.length
1669
+ };
1670
+ if (action === "mask" || action === "block") {
1671
+ stats.dlpMasked++;
1672
+ event.output = dlpMasker.maskText(event.output, result.matches);
1673
+ await audit.log({
1674
+ sessionId,
1675
+ event: "dlp_masked",
1676
+ userId: identity.userId,
1677
+ role: identity.role,
1678
+ orgUnit: identity.orgUnit,
1679
+ tool: event.toolName,
1680
+ metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
1681
+ });
1682
+ } else {
1683
+ stats.dlpDetected++;
1684
+ await audit.log({
1685
+ sessionId,
1686
+ event: "dlp_detected",
1687
+ userId: identity.userId,
1688
+ role: identity.role,
1689
+ orgUnit: identity.orgUnit,
1690
+ tool: event.toolName,
1691
+ metadata: dlpMeta
1692
+ });
1693
+ }
1694
+ }
1695
+ }
1110
1696
  await audit.log({
1111
1697
  sessionId,
1112
1698
  event: "tool_result",
@@ -1154,6 +1740,9 @@ var piGovernance = (pi) => {
1154
1740
  ` Approvals: ${stats.approvals}`,
1155
1741
  ` Dry-run blocks: ${stats.dryRun}`,
1156
1742
  ` Budget exceeded: ${stats.budgetExceeded}`,
1743
+ ` DLP blocked: ${stats.dlpBlocked}`,
1744
+ ` DLP detected: ${stats.dlpDetected}`,
1745
+ ` DLP masked: ${stats.dlpMasked}`,
1157
1746
  "",
1158
1747
  "Audit Events:",
1159
1748
  ...[...summary.entries()].map(([k, v]) => ` ${k}: ${v}`)