@grwnd/pi-governance 1.4.1 → 1.5.0
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 +549 -1
- package/dist/extensions/index.cjs.map +1 -1
- package/dist/extensions/index.js +549 -1
- package/dist/extensions/index.js.map +1 -1
- package/dist/index.cjs +375 -2
- 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 +368 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -102,12 +102,78 @@ var OrgUnitOverride = import_typebox.Type.Object({
|
|
|
102
102
|
})
|
|
103
103
|
)
|
|
104
104
|
});
|
|
105
|
+
var DlpMaskingConfig = import_typebox.Type.Object({
|
|
106
|
+
strategy: import_typebox.Type.Union([import_typebox.Type.Literal("partial"), import_typebox.Type.Literal("full"), import_typebox.Type.Literal("hash")], {
|
|
107
|
+
default: "partial"
|
|
108
|
+
}),
|
|
109
|
+
show_chars: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 4, minimum: 0 })),
|
|
110
|
+
placeholder: import_typebox.Type.Optional(import_typebox.Type.String({ default: "***" }))
|
|
111
|
+
});
|
|
112
|
+
var DlpCustomPatternConfig = import_typebox.Type.Object({
|
|
113
|
+
name: import_typebox.Type.String(),
|
|
114
|
+
pattern: import_typebox.Type.String(),
|
|
115
|
+
severity: import_typebox.Type.Union([
|
|
116
|
+
import_typebox.Type.Literal("low"),
|
|
117
|
+
import_typebox.Type.Literal("medium"),
|
|
118
|
+
import_typebox.Type.Literal("high"),
|
|
119
|
+
import_typebox.Type.Literal("critical")
|
|
120
|
+
]),
|
|
121
|
+
action: import_typebox.Type.Optional(
|
|
122
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
123
|
+
)
|
|
124
|
+
});
|
|
125
|
+
var DlpAllowlistEntryConfig = import_typebox.Type.Object({
|
|
126
|
+
pattern: import_typebox.Type.String()
|
|
127
|
+
});
|
|
128
|
+
var DlpRoleOverrideConfig = import_typebox.Type.Object({
|
|
129
|
+
enabled: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
|
|
130
|
+
mode: import_typebox.Type.Optional(
|
|
131
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
132
|
+
),
|
|
133
|
+
on_input: import_typebox.Type.Optional(
|
|
134
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
135
|
+
),
|
|
136
|
+
on_output: import_typebox.Type.Optional(
|
|
137
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
138
|
+
)
|
|
139
|
+
});
|
|
140
|
+
var DlpConfig = import_typebox.Type.Object({
|
|
141
|
+
enabled: import_typebox.Type.Boolean({ default: false }),
|
|
142
|
+
mode: import_typebox.Type.Optional(
|
|
143
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")], {
|
|
144
|
+
default: "audit"
|
|
145
|
+
})
|
|
146
|
+
),
|
|
147
|
+
on_input: import_typebox.Type.Optional(
|
|
148
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
149
|
+
),
|
|
150
|
+
on_output: import_typebox.Type.Optional(
|
|
151
|
+
import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
|
|
152
|
+
),
|
|
153
|
+
masking: import_typebox.Type.Optional(DlpMaskingConfig),
|
|
154
|
+
severity_threshold: import_typebox.Type.Optional(
|
|
155
|
+
import_typebox.Type.Union(
|
|
156
|
+
[import_typebox.Type.Literal("low"), import_typebox.Type.Literal("medium"), import_typebox.Type.Literal("high"), import_typebox.Type.Literal("critical")],
|
|
157
|
+
{ default: "low" }
|
|
158
|
+
)
|
|
159
|
+
),
|
|
160
|
+
built_in: import_typebox.Type.Optional(
|
|
161
|
+
import_typebox.Type.Object({
|
|
162
|
+
secrets: import_typebox.Type.Boolean({ default: true }),
|
|
163
|
+
pii: import_typebox.Type.Boolean({ default: true })
|
|
164
|
+
})
|
|
165
|
+
),
|
|
166
|
+
custom_patterns: import_typebox.Type.Optional(import_typebox.Type.Array(DlpCustomPatternConfig)),
|
|
167
|
+
allowlist: import_typebox.Type.Optional(import_typebox.Type.Array(DlpAllowlistEntryConfig)),
|
|
168
|
+
role_overrides: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), DlpRoleOverrideConfig))
|
|
169
|
+
});
|
|
105
170
|
var GovernanceConfigSchema = import_typebox.Type.Object({
|
|
106
171
|
auth: import_typebox.Type.Optional(AuthConfig),
|
|
107
172
|
policy: import_typebox.Type.Optional(PolicyConfig),
|
|
108
173
|
templates: import_typebox.Type.Optional(TemplatesConfig),
|
|
109
174
|
hitl: import_typebox.Type.Optional(HitlConfig),
|
|
110
175
|
audit: import_typebox.Type.Optional(AuditConfig),
|
|
176
|
+
dlp: import_typebox.Type.Optional(DlpConfig),
|
|
111
177
|
org_units: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), OrgUnitOverride))
|
|
112
178
|
});
|
|
113
179
|
|
|
@@ -138,6 +204,9 @@ var DEFAULTS = {
|
|
|
138
204
|
},
|
|
139
205
|
audit: {
|
|
140
206
|
sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
|
|
207
|
+
},
|
|
208
|
+
dlp: {
|
|
209
|
+
enabled: false
|
|
141
210
|
}
|
|
142
211
|
};
|
|
143
212
|
|
|
@@ -847,6 +916,297 @@ var ConfigWatcher = class {
|
|
|
847
916
|
}
|
|
848
917
|
};
|
|
849
918
|
|
|
919
|
+
// src/lib/dlp/patterns.ts
|
|
920
|
+
var SECRET_PATTERNS = [
|
|
921
|
+
// AWS
|
|
922
|
+
{
|
|
923
|
+
name: "aws_access_key",
|
|
924
|
+
pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
|
|
925
|
+
severity: "critical",
|
|
926
|
+
category: "secret"
|
|
927
|
+
},
|
|
928
|
+
{
|
|
929
|
+
name: "aws_secret_key",
|
|
930
|
+
pattern: /\b([A-Za-z0-9/+=]{40})(?=\s|$|"|')/g,
|
|
931
|
+
severity: "critical",
|
|
932
|
+
category: "secret"
|
|
933
|
+
},
|
|
934
|
+
// GitHub
|
|
935
|
+
{
|
|
936
|
+
name: "github_pat",
|
|
937
|
+
pattern: /\b(ghp_[A-Za-z0-9]{36,})\b/g,
|
|
938
|
+
severity: "critical",
|
|
939
|
+
category: "secret"
|
|
940
|
+
},
|
|
941
|
+
{
|
|
942
|
+
name: "github_oauth",
|
|
943
|
+
pattern: /\b(gho_[A-Za-z0-9]{36,})\b/g,
|
|
944
|
+
severity: "high",
|
|
945
|
+
category: "secret"
|
|
946
|
+
},
|
|
947
|
+
{
|
|
948
|
+
name: "github_app_token",
|
|
949
|
+
pattern: /\b(ghu_[A-Za-z0-9]{36,})\b/g,
|
|
950
|
+
severity: "high",
|
|
951
|
+
category: "secret"
|
|
952
|
+
},
|
|
953
|
+
// Anthropic
|
|
954
|
+
{
|
|
955
|
+
name: "anthropic_api_key",
|
|
956
|
+
pattern: /\b(sk-ant-api03-[A-Za-z0-9_-]{90,})\b/g,
|
|
957
|
+
severity: "critical",
|
|
958
|
+
category: "secret"
|
|
959
|
+
},
|
|
960
|
+
// OpenAI
|
|
961
|
+
{
|
|
962
|
+
name: "openai_api_key",
|
|
963
|
+
pattern: /\b(sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,})\b/g,
|
|
964
|
+
severity: "critical",
|
|
965
|
+
category: "secret"
|
|
966
|
+
},
|
|
967
|
+
// JWT
|
|
968
|
+
{
|
|
969
|
+
name: "jwt_token",
|
|
970
|
+
pattern: /\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
|
971
|
+
severity: "high",
|
|
972
|
+
category: "secret"
|
|
973
|
+
},
|
|
974
|
+
// Private key headers
|
|
975
|
+
{
|
|
976
|
+
name: "private_key",
|
|
977
|
+
pattern: /-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
978
|
+
severity: "critical",
|
|
979
|
+
category: "secret"
|
|
980
|
+
},
|
|
981
|
+
// Database connection strings
|
|
982
|
+
{
|
|
983
|
+
name: "database_url",
|
|
984
|
+
pattern: /\b((?:postgres|mysql|mongodb|redis):\/\/[^\s'"]{10,})\b/g,
|
|
985
|
+
severity: "high",
|
|
986
|
+
category: "secret"
|
|
987
|
+
},
|
|
988
|
+
// Slack
|
|
989
|
+
{
|
|
990
|
+
name: "slack_token",
|
|
991
|
+
pattern: /\b(xox[bpras]-[A-Za-z0-9-]{10,})\b/g,
|
|
992
|
+
severity: "high",
|
|
993
|
+
category: "secret"
|
|
994
|
+
},
|
|
995
|
+
// Stripe
|
|
996
|
+
{
|
|
997
|
+
name: "stripe_key",
|
|
998
|
+
pattern: /\b([rs]k_(?:live|test)_[A-Za-z0-9]{20,})\b/g,
|
|
999
|
+
severity: "critical",
|
|
1000
|
+
category: "secret"
|
|
1001
|
+
},
|
|
1002
|
+
// npm
|
|
1003
|
+
{
|
|
1004
|
+
name: "npm_token",
|
|
1005
|
+
pattern: /\b(npm_[A-Za-z0-9]{36,})\b/g,
|
|
1006
|
+
severity: "high",
|
|
1007
|
+
category: "secret"
|
|
1008
|
+
},
|
|
1009
|
+
// SendGrid
|
|
1010
|
+
{
|
|
1011
|
+
name: "sendgrid_key",
|
|
1012
|
+
pattern: /\b(SG\.[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{22,})\b/g,
|
|
1013
|
+
severity: "high",
|
|
1014
|
+
category: "secret"
|
|
1015
|
+
},
|
|
1016
|
+
// Generic API key patterns (env-var style assignments)
|
|
1017
|
+
{
|
|
1018
|
+
name: "generic_api_key",
|
|
1019
|
+
pattern: /\b(?:API_KEY|API_SECRET|ACCESS_TOKEN|AUTH_TOKEN|SECRET_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{16,})['"]?/gi,
|
|
1020
|
+
severity: "medium",
|
|
1021
|
+
category: "secret"
|
|
1022
|
+
},
|
|
1023
|
+
// High-entropy string near keyword context
|
|
1024
|
+
{
|
|
1025
|
+
name: "generic_secret_assignment",
|
|
1026
|
+
pattern: /\b(?:password|passwd|secret|token|credential)\s*[=:]\s*['"]([^'"]{8,})['"]?/gi,
|
|
1027
|
+
severity: "medium",
|
|
1028
|
+
category: "secret"
|
|
1029
|
+
}
|
|
1030
|
+
];
|
|
1031
|
+
var PII_PATTERNS = [
|
|
1032
|
+
// SSN (US)
|
|
1033
|
+
{
|
|
1034
|
+
name: "ssn",
|
|
1035
|
+
pattern: /\b(\d{3}-\d{2}-\d{4})\b/g,
|
|
1036
|
+
severity: "critical",
|
|
1037
|
+
category: "pii"
|
|
1038
|
+
},
|
|
1039
|
+
// Credit card numbers
|
|
1040
|
+
{
|
|
1041
|
+
name: "credit_card",
|
|
1042
|
+
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,
|
|
1043
|
+
severity: "critical",
|
|
1044
|
+
category: "pii"
|
|
1045
|
+
},
|
|
1046
|
+
// Email address
|
|
1047
|
+
{
|
|
1048
|
+
name: "email",
|
|
1049
|
+
pattern: /\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
|
|
1050
|
+
severity: "low",
|
|
1051
|
+
category: "pii"
|
|
1052
|
+
},
|
|
1053
|
+
// US phone number
|
|
1054
|
+
{
|
|
1055
|
+
name: "phone_us",
|
|
1056
|
+
pattern: /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g,
|
|
1057
|
+
severity: "medium",
|
|
1058
|
+
category: "pii"
|
|
1059
|
+
},
|
|
1060
|
+
// IPv4 address
|
|
1061
|
+
{
|
|
1062
|
+
name: "ipv4",
|
|
1063
|
+
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,
|
|
1064
|
+
severity: "low",
|
|
1065
|
+
category: "pii"
|
|
1066
|
+
}
|
|
1067
|
+
];
|
|
1068
|
+
|
|
1069
|
+
// src/lib/dlp/scanner.ts
|
|
1070
|
+
var SEVERITY_ORDER = {
|
|
1071
|
+
low: 0,
|
|
1072
|
+
medium: 1,
|
|
1073
|
+
high: 2,
|
|
1074
|
+
critical: 3
|
|
1075
|
+
};
|
|
1076
|
+
var DlpScanner = class {
|
|
1077
|
+
patterns;
|
|
1078
|
+
allowlistRegexps;
|
|
1079
|
+
severityThreshold;
|
|
1080
|
+
config;
|
|
1081
|
+
constructor(config) {
|
|
1082
|
+
this.config = config;
|
|
1083
|
+
this.severityThreshold = SEVERITY_ORDER[config.severity_threshold];
|
|
1084
|
+
this.patterns = [];
|
|
1085
|
+
this.allowlistRegexps = [];
|
|
1086
|
+
if (config.built_in.secrets) {
|
|
1087
|
+
for (const def of SECRET_PATTERNS) {
|
|
1088
|
+
this.patterns.push({ def });
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
if (config.built_in.pii) {
|
|
1092
|
+
for (const def of PII_PATTERNS) {
|
|
1093
|
+
this.patterns.push({ def });
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
for (const cp of config.custom_patterns) {
|
|
1097
|
+
const def = {
|
|
1098
|
+
name: cp.name,
|
|
1099
|
+
pattern: new RegExp(cp.pattern, "g"),
|
|
1100
|
+
severity: cp.severity,
|
|
1101
|
+
category: "custom"
|
|
1102
|
+
};
|
|
1103
|
+
this.patterns.push({ def, action: cp.action });
|
|
1104
|
+
}
|
|
1105
|
+
for (const compiled of this.patterns) {
|
|
1106
|
+
const override = config.pattern_overrides.get(compiled.def.name);
|
|
1107
|
+
if (override) {
|
|
1108
|
+
compiled.action = override;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
for (const entry of config.allowlist) {
|
|
1112
|
+
this.allowlistRegexps.push(new RegExp(entry.pattern));
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
scan(text) {
|
|
1116
|
+
if (!this.config.enabled || text.length === 0) {
|
|
1117
|
+
return { hasMatches: false, matches: [] };
|
|
1118
|
+
}
|
|
1119
|
+
const matches = [];
|
|
1120
|
+
for (const compiled of this.patterns) {
|
|
1121
|
+
if (SEVERITY_ORDER[compiled.def.severity] < this.severityThreshold) {
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
const regex = new RegExp(compiled.def.pattern.source, compiled.def.pattern.flags);
|
|
1125
|
+
let match;
|
|
1126
|
+
while ((match = regex.exec(text)) !== null) {
|
|
1127
|
+
const matched = match[1] ?? match[0];
|
|
1128
|
+
const start = match[1] ? match.index + match[0].indexOf(match[1]) : match.index;
|
|
1129
|
+
const end = start + matched.length;
|
|
1130
|
+
if (this.isAllowlisted(matched)) {
|
|
1131
|
+
continue;
|
|
1132
|
+
}
|
|
1133
|
+
matches.push({
|
|
1134
|
+
patternName: compiled.def.name,
|
|
1135
|
+
category: compiled.def.category,
|
|
1136
|
+
severity: compiled.def.severity,
|
|
1137
|
+
start,
|
|
1138
|
+
end,
|
|
1139
|
+
matched
|
|
1140
|
+
});
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return { hasMatches: matches.length > 0, matches };
|
|
1144
|
+
}
|
|
1145
|
+
getAction(direction) {
|
|
1146
|
+
if (direction === "input" && this.config.on_input) {
|
|
1147
|
+
return this.config.on_input;
|
|
1148
|
+
}
|
|
1149
|
+
if (direction === "output" && this.config.on_output) {
|
|
1150
|
+
return this.config.on_output;
|
|
1151
|
+
}
|
|
1152
|
+
return this.config.mode;
|
|
1153
|
+
}
|
|
1154
|
+
getPatternAction(match, direction) {
|
|
1155
|
+
const compiled = this.patterns.find((p) => p.def.name === match.patternName);
|
|
1156
|
+
if (compiled?.action) {
|
|
1157
|
+
return compiled.action;
|
|
1158
|
+
}
|
|
1159
|
+
return this.getAction(direction);
|
|
1160
|
+
}
|
|
1161
|
+
isAllowlisted(value) {
|
|
1162
|
+
for (const re of this.allowlistRegexps) {
|
|
1163
|
+
if (re.test(value)) return true;
|
|
1164
|
+
}
|
|
1165
|
+
return false;
|
|
1166
|
+
}
|
|
1167
|
+
};
|
|
1168
|
+
|
|
1169
|
+
// src/lib/dlp/masker.ts
|
|
1170
|
+
var import_node_crypto2 = require("crypto");
|
|
1171
|
+
var DEFAULT_CONFIG = {
|
|
1172
|
+
strategy: "partial",
|
|
1173
|
+
show_chars: 4,
|
|
1174
|
+
placeholder: "***"
|
|
1175
|
+
};
|
|
1176
|
+
var DlpMasker = class {
|
|
1177
|
+
config;
|
|
1178
|
+
constructor(config) {
|
|
1179
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1180
|
+
}
|
|
1181
|
+
maskValue(value) {
|
|
1182
|
+
switch (this.config.strategy) {
|
|
1183
|
+
case "full":
|
|
1184
|
+
return this.config.placeholder;
|
|
1185
|
+
case "hash": {
|
|
1186
|
+
const hash = (0, import_node_crypto2.createHash)("sha256").update(value).digest("hex").slice(0, 8);
|
|
1187
|
+
return `[REDACTED:${hash}]`;
|
|
1188
|
+
}
|
|
1189
|
+
case "partial":
|
|
1190
|
+
default: {
|
|
1191
|
+
if (value.length <= this.config.show_chars) {
|
|
1192
|
+
return this.config.placeholder;
|
|
1193
|
+
}
|
|
1194
|
+
return this.config.placeholder + value.slice(-this.config.show_chars);
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
maskText(text, matches) {
|
|
1199
|
+
if (matches.length === 0) return text;
|
|
1200
|
+
const sorted = [...matches].sort((a, b) => b.start - a.start);
|
|
1201
|
+
let result = text;
|
|
1202
|
+
for (const match of sorted) {
|
|
1203
|
+
const masked = this.maskValue(match.matched);
|
|
1204
|
+
result = result.slice(0, match.start) + masked + result.slice(match.end);
|
|
1205
|
+
}
|
|
1206
|
+
return result;
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1209
|
+
|
|
850
1210
|
// src/extensions/index.ts
|
|
851
1211
|
var PATH_TOOLS = {
|
|
852
1212
|
read: "path",
|
|
@@ -884,6 +1244,72 @@ function extractPath(toolName, input) {
|
|
|
884
1244
|
const val = input[key];
|
|
885
1245
|
return typeof val === "string" ? val : void 0;
|
|
886
1246
|
}
|
|
1247
|
+
var ACTION_PRIORITY = { audit: 0, mask: 1, block: 2 };
|
|
1248
|
+
function extractDlpFields(toolName, input) {
|
|
1249
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1250
|
+
switch (toolName) {
|
|
1251
|
+
case "bash": {
|
|
1252
|
+
const cmd = input["command"];
|
|
1253
|
+
if (typeof cmd === "string") fields.set("command", cmd);
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
case "write": {
|
|
1257
|
+
const content = input["content"];
|
|
1258
|
+
if (typeof content === "string") fields.set("content", content);
|
|
1259
|
+
const path = input["path"];
|
|
1260
|
+
if (typeof path === "string") fields.set("path", path);
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
case "edit": {
|
|
1264
|
+
const newStr = input["new_string"];
|
|
1265
|
+
if (typeof newStr === "string") fields.set("new_string", newStr);
|
|
1266
|
+
const oldStr = input["old_string"];
|
|
1267
|
+
if (typeof oldStr === "string") fields.set("old_string", oldStr);
|
|
1268
|
+
break;
|
|
1269
|
+
}
|
|
1270
|
+
default: {
|
|
1271
|
+
for (const [key, val] of Object.entries(input)) {
|
|
1272
|
+
if (typeof val === "string") fields.set(key, val);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return fields;
|
|
1277
|
+
}
|
|
1278
|
+
function resolveHighestAction(scanner, matches, direction) {
|
|
1279
|
+
let highest = "audit";
|
|
1280
|
+
for (const match of matches) {
|
|
1281
|
+
const action = scanner.getPatternAction(match, direction);
|
|
1282
|
+
if (ACTION_PRIORITY[action] > ACTION_PRIORITY[highest]) {
|
|
1283
|
+
highest = action;
|
|
1284
|
+
}
|
|
1285
|
+
}
|
|
1286
|
+
return highest;
|
|
1287
|
+
}
|
|
1288
|
+
function resolveDlpConfig(dlpConfig, role) {
|
|
1289
|
+
if (!dlpConfig?.enabled) return void 0;
|
|
1290
|
+
const roleOverride = dlpConfig.role_overrides?.[role];
|
|
1291
|
+
if (roleOverride?.enabled === false) return void 0;
|
|
1292
|
+
const patternOverrides = /* @__PURE__ */ new Map();
|
|
1293
|
+
return {
|
|
1294
|
+
enabled: true,
|
|
1295
|
+
mode: roleOverride?.mode ?? dlpConfig.mode ?? "audit",
|
|
1296
|
+
on_input: roleOverride?.on_input ?? dlpConfig.on_input,
|
|
1297
|
+
on_output: roleOverride?.on_output ?? dlpConfig.on_output,
|
|
1298
|
+
severity_threshold: dlpConfig.severity_threshold ?? "low",
|
|
1299
|
+
built_in: {
|
|
1300
|
+
secrets: dlpConfig.built_in?.secrets ?? true,
|
|
1301
|
+
pii: dlpConfig.built_in?.pii ?? true
|
|
1302
|
+
},
|
|
1303
|
+
custom_patterns: (dlpConfig.custom_patterns ?? []).map((cp) => ({
|
|
1304
|
+
name: cp.name,
|
|
1305
|
+
pattern: cp.pattern,
|
|
1306
|
+
severity: cp.severity,
|
|
1307
|
+
action: cp.action
|
|
1308
|
+
})),
|
|
1309
|
+
allowlist: dlpConfig.allowlist ?? [],
|
|
1310
|
+
pattern_overrides: patternOverrides
|
|
1311
|
+
};
|
|
1312
|
+
}
|
|
887
1313
|
var piGovernance = (pi) => {
|
|
888
1314
|
let config;
|
|
889
1315
|
let policyEngine;
|
|
@@ -895,7 +1321,18 @@ var piGovernance = (pi) => {
|
|
|
895
1321
|
let sessionId;
|
|
896
1322
|
let budgetTracker;
|
|
897
1323
|
let configWatcher;
|
|
898
|
-
|
|
1324
|
+
let dlpScanner;
|
|
1325
|
+
let dlpMasker;
|
|
1326
|
+
const stats = {
|
|
1327
|
+
allowed: 0,
|
|
1328
|
+
denied: 0,
|
|
1329
|
+
approvals: 0,
|
|
1330
|
+
dryRun: 0,
|
|
1331
|
+
budgetExceeded: 0,
|
|
1332
|
+
dlpBlocked: 0,
|
|
1333
|
+
dlpDetected: 0,
|
|
1334
|
+
dlpMasked: 0
|
|
1335
|
+
};
|
|
899
1336
|
pi.on("session_start", async (_event, ctx) => {
|
|
900
1337
|
sessionId = ctx.sessionId;
|
|
901
1338
|
const loaded = loadConfig();
|
|
@@ -924,6 +1361,11 @@ var piGovernance = (pi) => {
|
|
|
924
1361
|
}
|
|
925
1362
|
const budget = policyEngine.getTokenBudget(identity.role);
|
|
926
1363
|
budgetTracker = new BudgetTracker(budget);
|
|
1364
|
+
const dlpCfg = resolveDlpConfig(config.dlp, identity.role);
|
|
1365
|
+
if (dlpCfg) {
|
|
1366
|
+
dlpScanner = new DlpScanner(dlpCfg);
|
|
1367
|
+
dlpMasker = new DlpMasker(config.dlp?.masking);
|
|
1368
|
+
}
|
|
927
1369
|
if (loaded.source !== "built-in") {
|
|
928
1370
|
configWatcher = new ConfigWatcher(
|
|
929
1371
|
loaded.source,
|
|
@@ -933,6 +1375,14 @@ var piGovernance = (pi) => {
|
|
|
933
1375
|
policyEngine = new YamlPolicyEngine(newRulesFile);
|
|
934
1376
|
const newOverrides = policyEngine.getBashOverrides(identity.role);
|
|
935
1377
|
bashClassifier = new BashClassifier(newOverrides);
|
|
1378
|
+
const newDlpCfg = resolveDlpConfig(newConfig.dlp, identity.role);
|
|
1379
|
+
if (newDlpCfg) {
|
|
1380
|
+
dlpScanner = new DlpScanner(newDlpCfg);
|
|
1381
|
+
dlpMasker = new DlpMasker(newConfig.dlp?.masking);
|
|
1382
|
+
} else {
|
|
1383
|
+
dlpScanner = void 0;
|
|
1384
|
+
dlpMasker = void 0;
|
|
1385
|
+
}
|
|
936
1386
|
audit.log({
|
|
937
1387
|
sessionId,
|
|
938
1388
|
event: "config_reloaded",
|
|
@@ -1080,6 +1530,63 @@ var piGovernance = (pi) => {
|
|
|
1080
1530
|
return { block: true, reason: `Access denied to path: ${path}` };
|
|
1081
1531
|
}
|
|
1082
1532
|
}
|
|
1533
|
+
if (dlpScanner && dlpMasker) {
|
|
1534
|
+
const fields = extractDlpFields(toolName, input);
|
|
1535
|
+
const allMatches = [];
|
|
1536
|
+
for (const [, fieldValue] of fields) {
|
|
1537
|
+
const result = dlpScanner.scan(fieldValue);
|
|
1538
|
+
allMatches.push(...result.matches);
|
|
1539
|
+
}
|
|
1540
|
+
if (allMatches.length > 0) {
|
|
1541
|
+
const action = resolveHighestAction(dlpScanner, allMatches, "input");
|
|
1542
|
+
const patternNames = [...new Set(allMatches.map((m) => m.patternName))];
|
|
1543
|
+
const severities = [...new Set(allMatches.map((m) => m.severity))];
|
|
1544
|
+
const dlpMeta = {
|
|
1545
|
+
patterns: patternNames,
|
|
1546
|
+
severities,
|
|
1547
|
+
direction: "input",
|
|
1548
|
+
count: allMatches.length
|
|
1549
|
+
};
|
|
1550
|
+
if (action === "block") {
|
|
1551
|
+
stats.dlpBlocked++;
|
|
1552
|
+
await audit.log({
|
|
1553
|
+
...baseRecord,
|
|
1554
|
+
event: "dlp_blocked",
|
|
1555
|
+
decision: "denied",
|
|
1556
|
+
reason: `DLP: ${patternNames.join(", ")} detected in input`,
|
|
1557
|
+
metadata: dlpMeta
|
|
1558
|
+
});
|
|
1559
|
+
return {
|
|
1560
|
+
block: true,
|
|
1561
|
+
reason: `DLP blocked: sensitive data detected (${patternNames.join(", ")})`
|
|
1562
|
+
};
|
|
1563
|
+
}
|
|
1564
|
+
if (action === "mask") {
|
|
1565
|
+
stats.dlpMasked++;
|
|
1566
|
+
for (const [fieldKey, fieldValue] of fields) {
|
|
1567
|
+
const fieldResult = dlpScanner.scan(fieldValue);
|
|
1568
|
+
if (fieldResult.hasMatches) {
|
|
1569
|
+
input[fieldKey] = dlpMasker.maskText(
|
|
1570
|
+
fieldValue,
|
|
1571
|
+
fieldResult.matches
|
|
1572
|
+
);
|
|
1573
|
+
}
|
|
1574
|
+
}
|
|
1575
|
+
await audit.log({
|
|
1576
|
+
...baseRecord,
|
|
1577
|
+
event: "dlp_masked",
|
|
1578
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1579
|
+
});
|
|
1580
|
+
} else {
|
|
1581
|
+
stats.dlpDetected++;
|
|
1582
|
+
await audit.log({
|
|
1583
|
+
...baseRecord,
|
|
1584
|
+
event: "dlp_detected",
|
|
1585
|
+
metadata: dlpMeta
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
}
|
|
1083
1590
|
if (toolName !== "bash" && policyEngine.requiresApproval(identity.role, toolName)) {
|
|
1084
1591
|
if (approvalFlow) {
|
|
1085
1592
|
stats.approvals++;
|
|
@@ -1107,6 +1614,44 @@ var piGovernance = (pi) => {
|
|
|
1107
1614
|
return void 0;
|
|
1108
1615
|
});
|
|
1109
1616
|
pi.on("tool_result", async (event, _ctx) => {
|
|
1617
|
+
if (dlpScanner && dlpMasker && event.output) {
|
|
1618
|
+
const result = dlpScanner.scan(event.output);
|
|
1619
|
+
if (result.hasMatches) {
|
|
1620
|
+
const action = resolveHighestAction(dlpScanner, result.matches, "output");
|
|
1621
|
+
const patternNames = [...new Set(result.matches.map((m) => m.patternName))];
|
|
1622
|
+
const severities = [...new Set(result.matches.map((m) => m.severity))];
|
|
1623
|
+
const dlpMeta = {
|
|
1624
|
+
patterns: patternNames,
|
|
1625
|
+
severities,
|
|
1626
|
+
direction: "output",
|
|
1627
|
+
count: result.matches.length
|
|
1628
|
+
};
|
|
1629
|
+
if (action === "mask" || action === "block") {
|
|
1630
|
+
stats.dlpMasked++;
|
|
1631
|
+
event.output = dlpMasker.maskText(event.output, result.matches);
|
|
1632
|
+
await audit.log({
|
|
1633
|
+
sessionId,
|
|
1634
|
+
event: "dlp_masked",
|
|
1635
|
+
userId: identity.userId,
|
|
1636
|
+
role: identity.role,
|
|
1637
|
+
orgUnit: identity.orgUnit,
|
|
1638
|
+
tool: event.toolName,
|
|
1639
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1640
|
+
});
|
|
1641
|
+
} else {
|
|
1642
|
+
stats.dlpDetected++;
|
|
1643
|
+
await audit.log({
|
|
1644
|
+
sessionId,
|
|
1645
|
+
event: "dlp_detected",
|
|
1646
|
+
userId: identity.userId,
|
|
1647
|
+
role: identity.role,
|
|
1648
|
+
orgUnit: identity.orgUnit,
|
|
1649
|
+
tool: event.toolName,
|
|
1650
|
+
metadata: dlpMeta
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1110
1655
|
await audit.log({
|
|
1111
1656
|
sessionId,
|
|
1112
1657
|
event: "tool_result",
|
|
@@ -1154,6 +1699,9 @@ var piGovernance = (pi) => {
|
|
|
1154
1699
|
` Approvals: ${stats.approvals}`,
|
|
1155
1700
|
` Dry-run blocks: ${stats.dryRun}`,
|
|
1156
1701
|
` Budget exceeded: ${stats.budgetExceeded}`,
|
|
1702
|
+
` DLP blocked: ${stats.dlpBlocked}`,
|
|
1703
|
+
` DLP detected: ${stats.dlpDetected}`,
|
|
1704
|
+
` DLP masked: ${stats.dlpMasked}`,
|
|
1157
1705
|
"",
|
|
1158
1706
|
"Audit Events:",
|
|
1159
1707
|
...[...summary.entries()].map(([k, v]) => ` ${k}: ${v}`)
|