@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.
- package/README.md +75 -9
- package/dist/extensions/index.cjs +592 -3
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +594 -3
- package/dist/extensions/index.js.map +1 -1
- package/dist/index.cjs +387 -707
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -2
- package/dist/index.d.ts +105 -2
- package/dist/index.js +380 -705
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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}`)
|