@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
package/dist/extensions/index.js
CHANGED
|
@@ -76,12 +76,78 @@ var OrgUnitOverride = Type.Object({
|
|
|
76
76
|
})
|
|
77
77
|
)
|
|
78
78
|
});
|
|
79
|
+
var DlpMaskingConfig = Type.Object({
|
|
80
|
+
strategy: Type.Union([Type.Literal("partial"), Type.Literal("full"), Type.Literal("hash")], {
|
|
81
|
+
default: "partial"
|
|
82
|
+
}),
|
|
83
|
+
show_chars: Type.Optional(Type.Number({ default: 4, minimum: 0 })),
|
|
84
|
+
placeholder: Type.Optional(Type.String({ default: "***" }))
|
|
85
|
+
});
|
|
86
|
+
var DlpCustomPatternConfig = Type.Object({
|
|
87
|
+
name: Type.String(),
|
|
88
|
+
pattern: Type.String(),
|
|
89
|
+
severity: Type.Union([
|
|
90
|
+
Type.Literal("low"),
|
|
91
|
+
Type.Literal("medium"),
|
|
92
|
+
Type.Literal("high"),
|
|
93
|
+
Type.Literal("critical")
|
|
94
|
+
]),
|
|
95
|
+
action: Type.Optional(
|
|
96
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
97
|
+
)
|
|
98
|
+
});
|
|
99
|
+
var DlpAllowlistEntryConfig = Type.Object({
|
|
100
|
+
pattern: Type.String()
|
|
101
|
+
});
|
|
102
|
+
var DlpRoleOverrideConfig = Type.Object({
|
|
103
|
+
enabled: Type.Optional(Type.Boolean()),
|
|
104
|
+
mode: Type.Optional(
|
|
105
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
106
|
+
),
|
|
107
|
+
on_input: Type.Optional(
|
|
108
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
109
|
+
),
|
|
110
|
+
on_output: Type.Optional(
|
|
111
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
112
|
+
)
|
|
113
|
+
});
|
|
114
|
+
var DlpConfig = Type.Object({
|
|
115
|
+
enabled: Type.Boolean({ default: false }),
|
|
116
|
+
mode: Type.Optional(
|
|
117
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")], {
|
|
118
|
+
default: "audit"
|
|
119
|
+
})
|
|
120
|
+
),
|
|
121
|
+
on_input: Type.Optional(
|
|
122
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
123
|
+
),
|
|
124
|
+
on_output: Type.Optional(
|
|
125
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
126
|
+
),
|
|
127
|
+
masking: Type.Optional(DlpMaskingConfig),
|
|
128
|
+
severity_threshold: Type.Optional(
|
|
129
|
+
Type.Union(
|
|
130
|
+
[Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("critical")],
|
|
131
|
+
{ default: "low" }
|
|
132
|
+
)
|
|
133
|
+
),
|
|
134
|
+
built_in: Type.Optional(
|
|
135
|
+
Type.Object({
|
|
136
|
+
secrets: Type.Boolean({ default: true }),
|
|
137
|
+
pii: Type.Boolean({ default: true })
|
|
138
|
+
})
|
|
139
|
+
),
|
|
140
|
+
custom_patterns: Type.Optional(Type.Array(DlpCustomPatternConfig)),
|
|
141
|
+
allowlist: Type.Optional(Type.Array(DlpAllowlistEntryConfig)),
|
|
142
|
+
role_overrides: Type.Optional(Type.Record(Type.String(), DlpRoleOverrideConfig))
|
|
143
|
+
});
|
|
79
144
|
var GovernanceConfigSchema = Type.Object({
|
|
80
145
|
auth: Type.Optional(AuthConfig),
|
|
81
146
|
policy: Type.Optional(PolicyConfig),
|
|
82
147
|
templates: Type.Optional(TemplatesConfig),
|
|
83
148
|
hitl: Type.Optional(HitlConfig),
|
|
84
149
|
audit: Type.Optional(AuditConfig),
|
|
150
|
+
dlp: Type.Optional(DlpConfig),
|
|
85
151
|
org_units: Type.Optional(Type.Record(Type.String(), OrgUnitOverride))
|
|
86
152
|
});
|
|
87
153
|
|
|
@@ -112,6 +178,9 @@ var DEFAULTS = {
|
|
|
112
178
|
},
|
|
113
179
|
audit: {
|
|
114
180
|
sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
|
|
181
|
+
},
|
|
182
|
+
dlp: {
|
|
183
|
+
enabled: false
|
|
115
184
|
}
|
|
116
185
|
};
|
|
117
186
|
|
|
@@ -821,6 +890,297 @@ var ConfigWatcher = class {
|
|
|
821
890
|
}
|
|
822
891
|
};
|
|
823
892
|
|
|
893
|
+
// src/lib/dlp/patterns.ts
|
|
894
|
+
var SECRET_PATTERNS = [
|
|
895
|
+
// AWS
|
|
896
|
+
{
|
|
897
|
+
name: "aws_access_key",
|
|
898
|
+
pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
|
|
899
|
+
severity: "critical",
|
|
900
|
+
category: "secret"
|
|
901
|
+
},
|
|
902
|
+
{
|
|
903
|
+
name: "aws_secret_key",
|
|
904
|
+
pattern: /\b([A-Za-z0-9/+=]{40})(?=\s|$|"|')/g,
|
|
905
|
+
severity: "critical",
|
|
906
|
+
category: "secret"
|
|
907
|
+
},
|
|
908
|
+
// GitHub
|
|
909
|
+
{
|
|
910
|
+
name: "github_pat",
|
|
911
|
+
pattern: /\b(ghp_[A-Za-z0-9]{36,})\b/g,
|
|
912
|
+
severity: "critical",
|
|
913
|
+
category: "secret"
|
|
914
|
+
},
|
|
915
|
+
{
|
|
916
|
+
name: "github_oauth",
|
|
917
|
+
pattern: /\b(gho_[A-Za-z0-9]{36,})\b/g,
|
|
918
|
+
severity: "high",
|
|
919
|
+
category: "secret"
|
|
920
|
+
},
|
|
921
|
+
{
|
|
922
|
+
name: "github_app_token",
|
|
923
|
+
pattern: /\b(ghu_[A-Za-z0-9]{36,})\b/g,
|
|
924
|
+
severity: "high",
|
|
925
|
+
category: "secret"
|
|
926
|
+
},
|
|
927
|
+
// Anthropic
|
|
928
|
+
{
|
|
929
|
+
name: "anthropic_api_key",
|
|
930
|
+
pattern: /\b(sk-ant-api03-[A-Za-z0-9_-]{90,})\b/g,
|
|
931
|
+
severity: "critical",
|
|
932
|
+
category: "secret"
|
|
933
|
+
},
|
|
934
|
+
// OpenAI
|
|
935
|
+
{
|
|
936
|
+
name: "openai_api_key",
|
|
937
|
+
pattern: /\b(sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,})\b/g,
|
|
938
|
+
severity: "critical",
|
|
939
|
+
category: "secret"
|
|
940
|
+
},
|
|
941
|
+
// JWT
|
|
942
|
+
{
|
|
943
|
+
name: "jwt_token",
|
|
944
|
+
pattern: /\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
|
945
|
+
severity: "high",
|
|
946
|
+
category: "secret"
|
|
947
|
+
},
|
|
948
|
+
// Private key headers
|
|
949
|
+
{
|
|
950
|
+
name: "private_key",
|
|
951
|
+
pattern: /-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
952
|
+
severity: "critical",
|
|
953
|
+
category: "secret"
|
|
954
|
+
},
|
|
955
|
+
// Database connection strings
|
|
956
|
+
{
|
|
957
|
+
name: "database_url",
|
|
958
|
+
pattern: /\b((?:postgres|mysql|mongodb|redis):\/\/[^\s'"]{10,})\b/g,
|
|
959
|
+
severity: "high",
|
|
960
|
+
category: "secret"
|
|
961
|
+
},
|
|
962
|
+
// Slack
|
|
963
|
+
{
|
|
964
|
+
name: "slack_token",
|
|
965
|
+
pattern: /\b(xox[bpras]-[A-Za-z0-9-]{10,})\b/g,
|
|
966
|
+
severity: "high",
|
|
967
|
+
category: "secret"
|
|
968
|
+
},
|
|
969
|
+
// Stripe
|
|
970
|
+
{
|
|
971
|
+
name: "stripe_key",
|
|
972
|
+
pattern: /\b([rs]k_(?:live|test)_[A-Za-z0-9]{20,})\b/g,
|
|
973
|
+
severity: "critical",
|
|
974
|
+
category: "secret"
|
|
975
|
+
},
|
|
976
|
+
// npm
|
|
977
|
+
{
|
|
978
|
+
name: "npm_token",
|
|
979
|
+
pattern: /\b(npm_[A-Za-z0-9]{36,})\b/g,
|
|
980
|
+
severity: "high",
|
|
981
|
+
category: "secret"
|
|
982
|
+
},
|
|
983
|
+
// SendGrid
|
|
984
|
+
{
|
|
985
|
+
name: "sendgrid_key",
|
|
986
|
+
pattern: /\b(SG\.[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{22,})\b/g,
|
|
987
|
+
severity: "high",
|
|
988
|
+
category: "secret"
|
|
989
|
+
},
|
|
990
|
+
// Generic API key patterns (env-var style assignments)
|
|
991
|
+
{
|
|
992
|
+
name: "generic_api_key",
|
|
993
|
+
pattern: /\b(?:API_KEY|API_SECRET|ACCESS_TOKEN|AUTH_TOKEN|SECRET_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{16,})['"]?/gi,
|
|
994
|
+
severity: "medium",
|
|
995
|
+
category: "secret"
|
|
996
|
+
},
|
|
997
|
+
// High-entropy string near keyword context
|
|
998
|
+
{
|
|
999
|
+
name: "generic_secret_assignment",
|
|
1000
|
+
pattern: /\b(?:password|passwd|secret|token|credential)\s*[=:]\s*['"]([^'"]{8,})['"]?/gi,
|
|
1001
|
+
severity: "medium",
|
|
1002
|
+
category: "secret"
|
|
1003
|
+
}
|
|
1004
|
+
];
|
|
1005
|
+
var PII_PATTERNS = [
|
|
1006
|
+
// SSN (US)
|
|
1007
|
+
{
|
|
1008
|
+
name: "ssn",
|
|
1009
|
+
pattern: /\b(\d{3}-\d{2}-\d{4})\b/g,
|
|
1010
|
+
severity: "critical",
|
|
1011
|
+
category: "pii"
|
|
1012
|
+
},
|
|
1013
|
+
// Credit card numbers
|
|
1014
|
+
{
|
|
1015
|
+
name: "credit_card",
|
|
1016
|
+
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,
|
|
1017
|
+
severity: "critical",
|
|
1018
|
+
category: "pii"
|
|
1019
|
+
},
|
|
1020
|
+
// Email address
|
|
1021
|
+
{
|
|
1022
|
+
name: "email",
|
|
1023
|
+
pattern: /\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
|
|
1024
|
+
severity: "low",
|
|
1025
|
+
category: "pii"
|
|
1026
|
+
},
|
|
1027
|
+
// US phone number
|
|
1028
|
+
{
|
|
1029
|
+
name: "phone_us",
|
|
1030
|
+
pattern: /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g,
|
|
1031
|
+
severity: "medium",
|
|
1032
|
+
category: "pii"
|
|
1033
|
+
},
|
|
1034
|
+
// IPv4 address
|
|
1035
|
+
{
|
|
1036
|
+
name: "ipv4",
|
|
1037
|
+
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,
|
|
1038
|
+
severity: "low",
|
|
1039
|
+
category: "pii"
|
|
1040
|
+
}
|
|
1041
|
+
];
|
|
1042
|
+
|
|
1043
|
+
// src/lib/dlp/scanner.ts
|
|
1044
|
+
var SEVERITY_ORDER = {
|
|
1045
|
+
low: 0,
|
|
1046
|
+
medium: 1,
|
|
1047
|
+
high: 2,
|
|
1048
|
+
critical: 3
|
|
1049
|
+
};
|
|
1050
|
+
var DlpScanner = class {
|
|
1051
|
+
patterns;
|
|
1052
|
+
allowlistRegexps;
|
|
1053
|
+
severityThreshold;
|
|
1054
|
+
config;
|
|
1055
|
+
constructor(config) {
|
|
1056
|
+
this.config = config;
|
|
1057
|
+
this.severityThreshold = SEVERITY_ORDER[config.severity_threshold];
|
|
1058
|
+
this.patterns = [];
|
|
1059
|
+
this.allowlistRegexps = [];
|
|
1060
|
+
if (config.built_in.secrets) {
|
|
1061
|
+
for (const def of SECRET_PATTERNS) {
|
|
1062
|
+
this.patterns.push({ def });
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
if (config.built_in.pii) {
|
|
1066
|
+
for (const def of PII_PATTERNS) {
|
|
1067
|
+
this.patterns.push({ def });
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
for (const cp of config.custom_patterns) {
|
|
1071
|
+
const def = {
|
|
1072
|
+
name: cp.name,
|
|
1073
|
+
pattern: new RegExp(cp.pattern, "g"),
|
|
1074
|
+
severity: cp.severity,
|
|
1075
|
+
category: "custom"
|
|
1076
|
+
};
|
|
1077
|
+
this.patterns.push({ def, action: cp.action });
|
|
1078
|
+
}
|
|
1079
|
+
for (const compiled of this.patterns) {
|
|
1080
|
+
const override = config.pattern_overrides.get(compiled.def.name);
|
|
1081
|
+
if (override) {
|
|
1082
|
+
compiled.action = override;
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
for (const entry of config.allowlist) {
|
|
1086
|
+
this.allowlistRegexps.push(new RegExp(entry.pattern));
|
|
1087
|
+
}
|
|
1088
|
+
}
|
|
1089
|
+
scan(text) {
|
|
1090
|
+
if (!this.config.enabled || text.length === 0) {
|
|
1091
|
+
return { hasMatches: false, matches: [] };
|
|
1092
|
+
}
|
|
1093
|
+
const matches = [];
|
|
1094
|
+
for (const compiled of this.patterns) {
|
|
1095
|
+
if (SEVERITY_ORDER[compiled.def.severity] < this.severityThreshold) {
|
|
1096
|
+
continue;
|
|
1097
|
+
}
|
|
1098
|
+
const regex = new RegExp(compiled.def.pattern.source, compiled.def.pattern.flags);
|
|
1099
|
+
let match;
|
|
1100
|
+
while ((match = regex.exec(text)) !== null) {
|
|
1101
|
+
const matched = match[1] ?? match[0];
|
|
1102
|
+
const start = match[1] ? match.index + match[0].indexOf(match[1]) : match.index;
|
|
1103
|
+
const end = start + matched.length;
|
|
1104
|
+
if (this.isAllowlisted(matched)) {
|
|
1105
|
+
continue;
|
|
1106
|
+
}
|
|
1107
|
+
matches.push({
|
|
1108
|
+
patternName: compiled.def.name,
|
|
1109
|
+
category: compiled.def.category,
|
|
1110
|
+
severity: compiled.def.severity,
|
|
1111
|
+
start,
|
|
1112
|
+
end,
|
|
1113
|
+
matched
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
return { hasMatches: matches.length > 0, matches };
|
|
1118
|
+
}
|
|
1119
|
+
getAction(direction) {
|
|
1120
|
+
if (direction === "input" && this.config.on_input) {
|
|
1121
|
+
return this.config.on_input;
|
|
1122
|
+
}
|
|
1123
|
+
if (direction === "output" && this.config.on_output) {
|
|
1124
|
+
return this.config.on_output;
|
|
1125
|
+
}
|
|
1126
|
+
return this.config.mode;
|
|
1127
|
+
}
|
|
1128
|
+
getPatternAction(match, direction) {
|
|
1129
|
+
const compiled = this.patterns.find((p) => p.def.name === match.patternName);
|
|
1130
|
+
if (compiled?.action) {
|
|
1131
|
+
return compiled.action;
|
|
1132
|
+
}
|
|
1133
|
+
return this.getAction(direction);
|
|
1134
|
+
}
|
|
1135
|
+
isAllowlisted(value) {
|
|
1136
|
+
for (const re of this.allowlistRegexps) {
|
|
1137
|
+
if (re.test(value)) return true;
|
|
1138
|
+
}
|
|
1139
|
+
return false;
|
|
1140
|
+
}
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
// src/lib/dlp/masker.ts
|
|
1144
|
+
import { createHash } from "crypto";
|
|
1145
|
+
var DEFAULT_CONFIG = {
|
|
1146
|
+
strategy: "partial",
|
|
1147
|
+
show_chars: 4,
|
|
1148
|
+
placeholder: "***"
|
|
1149
|
+
};
|
|
1150
|
+
var DlpMasker = class {
|
|
1151
|
+
config;
|
|
1152
|
+
constructor(config) {
|
|
1153
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1154
|
+
}
|
|
1155
|
+
maskValue(value) {
|
|
1156
|
+
switch (this.config.strategy) {
|
|
1157
|
+
case "full":
|
|
1158
|
+
return this.config.placeholder;
|
|
1159
|
+
case "hash": {
|
|
1160
|
+
const hash = createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
1161
|
+
return `[REDACTED:${hash}]`;
|
|
1162
|
+
}
|
|
1163
|
+
case "partial":
|
|
1164
|
+
default: {
|
|
1165
|
+
if (value.length <= this.config.show_chars) {
|
|
1166
|
+
return this.config.placeholder;
|
|
1167
|
+
}
|
|
1168
|
+
return this.config.placeholder + value.slice(-this.config.show_chars);
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
maskText(text, matches) {
|
|
1173
|
+
if (matches.length === 0) return text;
|
|
1174
|
+
const sorted = [...matches].sort((a, b) => b.start - a.start);
|
|
1175
|
+
let result = text;
|
|
1176
|
+
for (const match of sorted) {
|
|
1177
|
+
const masked = this.maskValue(match.matched);
|
|
1178
|
+
result = result.slice(0, match.start) + masked + result.slice(match.end);
|
|
1179
|
+
}
|
|
1180
|
+
return result;
|
|
1181
|
+
}
|
|
1182
|
+
};
|
|
1183
|
+
|
|
824
1184
|
// src/extensions/index.ts
|
|
825
1185
|
var PATH_TOOLS = {
|
|
826
1186
|
read: "path",
|
|
@@ -858,6 +1218,72 @@ function extractPath(toolName, input) {
|
|
|
858
1218
|
const val = input[key];
|
|
859
1219
|
return typeof val === "string" ? val : void 0;
|
|
860
1220
|
}
|
|
1221
|
+
var ACTION_PRIORITY = { audit: 0, mask: 1, block: 2 };
|
|
1222
|
+
function extractDlpFields(toolName, input) {
|
|
1223
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1224
|
+
switch (toolName) {
|
|
1225
|
+
case "bash": {
|
|
1226
|
+
const cmd = input["command"];
|
|
1227
|
+
if (typeof cmd === "string") fields.set("command", cmd);
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
case "write": {
|
|
1231
|
+
const content = input["content"];
|
|
1232
|
+
if (typeof content === "string") fields.set("content", content);
|
|
1233
|
+
const path = input["path"];
|
|
1234
|
+
if (typeof path === "string") fields.set("path", path);
|
|
1235
|
+
break;
|
|
1236
|
+
}
|
|
1237
|
+
case "edit": {
|
|
1238
|
+
const newStr = input["new_string"];
|
|
1239
|
+
if (typeof newStr === "string") fields.set("new_string", newStr);
|
|
1240
|
+
const oldStr = input["old_string"];
|
|
1241
|
+
if (typeof oldStr === "string") fields.set("old_string", oldStr);
|
|
1242
|
+
break;
|
|
1243
|
+
}
|
|
1244
|
+
default: {
|
|
1245
|
+
for (const [key, val] of Object.entries(input)) {
|
|
1246
|
+
if (typeof val === "string") fields.set(key, val);
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
return fields;
|
|
1251
|
+
}
|
|
1252
|
+
function resolveHighestAction(scanner, matches, direction) {
|
|
1253
|
+
let highest = "audit";
|
|
1254
|
+
for (const match of matches) {
|
|
1255
|
+
const action = scanner.getPatternAction(match, direction);
|
|
1256
|
+
if (ACTION_PRIORITY[action] > ACTION_PRIORITY[highest]) {
|
|
1257
|
+
highest = action;
|
|
1258
|
+
}
|
|
1259
|
+
}
|
|
1260
|
+
return highest;
|
|
1261
|
+
}
|
|
1262
|
+
function resolveDlpConfig(dlpConfig, role) {
|
|
1263
|
+
if (!dlpConfig?.enabled) return void 0;
|
|
1264
|
+
const roleOverride = dlpConfig.role_overrides?.[role];
|
|
1265
|
+
if (roleOverride?.enabled === false) return void 0;
|
|
1266
|
+
const patternOverrides = /* @__PURE__ */ new Map();
|
|
1267
|
+
return {
|
|
1268
|
+
enabled: true,
|
|
1269
|
+
mode: roleOverride?.mode ?? dlpConfig.mode ?? "audit",
|
|
1270
|
+
on_input: roleOverride?.on_input ?? dlpConfig.on_input,
|
|
1271
|
+
on_output: roleOverride?.on_output ?? dlpConfig.on_output,
|
|
1272
|
+
severity_threshold: dlpConfig.severity_threshold ?? "low",
|
|
1273
|
+
built_in: {
|
|
1274
|
+
secrets: dlpConfig.built_in?.secrets ?? true,
|
|
1275
|
+
pii: dlpConfig.built_in?.pii ?? true
|
|
1276
|
+
},
|
|
1277
|
+
custom_patterns: (dlpConfig.custom_patterns ?? []).map((cp) => ({
|
|
1278
|
+
name: cp.name,
|
|
1279
|
+
pattern: cp.pattern,
|
|
1280
|
+
severity: cp.severity,
|
|
1281
|
+
action: cp.action
|
|
1282
|
+
})),
|
|
1283
|
+
allowlist: dlpConfig.allowlist ?? [],
|
|
1284
|
+
pattern_overrides: patternOverrides
|
|
1285
|
+
};
|
|
1286
|
+
}
|
|
861
1287
|
var piGovernance = (pi) => {
|
|
862
1288
|
let config;
|
|
863
1289
|
let policyEngine;
|
|
@@ -869,7 +1295,18 @@ var piGovernance = (pi) => {
|
|
|
869
1295
|
let sessionId;
|
|
870
1296
|
let budgetTracker;
|
|
871
1297
|
let configWatcher;
|
|
872
|
-
|
|
1298
|
+
let dlpScanner;
|
|
1299
|
+
let dlpMasker;
|
|
1300
|
+
const stats = {
|
|
1301
|
+
allowed: 0,
|
|
1302
|
+
denied: 0,
|
|
1303
|
+
approvals: 0,
|
|
1304
|
+
dryRun: 0,
|
|
1305
|
+
budgetExceeded: 0,
|
|
1306
|
+
dlpBlocked: 0,
|
|
1307
|
+
dlpDetected: 0,
|
|
1308
|
+
dlpMasked: 0
|
|
1309
|
+
};
|
|
873
1310
|
pi.on("session_start", async (_event, ctx) => {
|
|
874
1311
|
sessionId = ctx.sessionId;
|
|
875
1312
|
const loaded = loadConfig();
|
|
@@ -898,6 +1335,11 @@ var piGovernance = (pi) => {
|
|
|
898
1335
|
}
|
|
899
1336
|
const budget = policyEngine.getTokenBudget(identity.role);
|
|
900
1337
|
budgetTracker = new BudgetTracker(budget);
|
|
1338
|
+
const dlpCfg = resolveDlpConfig(config.dlp, identity.role);
|
|
1339
|
+
if (dlpCfg) {
|
|
1340
|
+
dlpScanner = new DlpScanner(dlpCfg);
|
|
1341
|
+
dlpMasker = new DlpMasker(config.dlp?.masking);
|
|
1342
|
+
}
|
|
901
1343
|
if (loaded.source !== "built-in") {
|
|
902
1344
|
configWatcher = new ConfigWatcher(
|
|
903
1345
|
loaded.source,
|
|
@@ -907,6 +1349,14 @@ var piGovernance = (pi) => {
|
|
|
907
1349
|
policyEngine = new YamlPolicyEngine(newRulesFile);
|
|
908
1350
|
const newOverrides = policyEngine.getBashOverrides(identity.role);
|
|
909
1351
|
bashClassifier = new BashClassifier(newOverrides);
|
|
1352
|
+
const newDlpCfg = resolveDlpConfig(newConfig.dlp, identity.role);
|
|
1353
|
+
if (newDlpCfg) {
|
|
1354
|
+
dlpScanner = new DlpScanner(newDlpCfg);
|
|
1355
|
+
dlpMasker = new DlpMasker(newConfig.dlp?.masking);
|
|
1356
|
+
} else {
|
|
1357
|
+
dlpScanner = void 0;
|
|
1358
|
+
dlpMasker = void 0;
|
|
1359
|
+
}
|
|
910
1360
|
audit.log({
|
|
911
1361
|
sessionId,
|
|
912
1362
|
event: "config_reloaded",
|
|
@@ -1054,6 +1504,63 @@ var piGovernance = (pi) => {
|
|
|
1054
1504
|
return { block: true, reason: `Access denied to path: ${path}` };
|
|
1055
1505
|
}
|
|
1056
1506
|
}
|
|
1507
|
+
if (dlpScanner && dlpMasker) {
|
|
1508
|
+
const fields = extractDlpFields(toolName, input);
|
|
1509
|
+
const allMatches = [];
|
|
1510
|
+
for (const [, fieldValue] of fields) {
|
|
1511
|
+
const result = dlpScanner.scan(fieldValue);
|
|
1512
|
+
allMatches.push(...result.matches);
|
|
1513
|
+
}
|
|
1514
|
+
if (allMatches.length > 0) {
|
|
1515
|
+
const action = resolveHighestAction(dlpScanner, allMatches, "input");
|
|
1516
|
+
const patternNames = [...new Set(allMatches.map((m) => m.patternName))];
|
|
1517
|
+
const severities = [...new Set(allMatches.map((m) => m.severity))];
|
|
1518
|
+
const dlpMeta = {
|
|
1519
|
+
patterns: patternNames,
|
|
1520
|
+
severities,
|
|
1521
|
+
direction: "input",
|
|
1522
|
+
count: allMatches.length
|
|
1523
|
+
};
|
|
1524
|
+
if (action === "block") {
|
|
1525
|
+
stats.dlpBlocked++;
|
|
1526
|
+
await audit.log({
|
|
1527
|
+
...baseRecord,
|
|
1528
|
+
event: "dlp_blocked",
|
|
1529
|
+
decision: "denied",
|
|
1530
|
+
reason: `DLP: ${patternNames.join(", ")} detected in input`,
|
|
1531
|
+
metadata: dlpMeta
|
|
1532
|
+
});
|
|
1533
|
+
return {
|
|
1534
|
+
block: true,
|
|
1535
|
+
reason: `DLP blocked: sensitive data detected (${patternNames.join(", ")})`
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1538
|
+
if (action === "mask") {
|
|
1539
|
+
stats.dlpMasked++;
|
|
1540
|
+
for (const [fieldKey, fieldValue] of fields) {
|
|
1541
|
+
const fieldResult = dlpScanner.scan(fieldValue);
|
|
1542
|
+
if (fieldResult.hasMatches) {
|
|
1543
|
+
input[fieldKey] = dlpMasker.maskText(
|
|
1544
|
+
fieldValue,
|
|
1545
|
+
fieldResult.matches
|
|
1546
|
+
);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
await audit.log({
|
|
1550
|
+
...baseRecord,
|
|
1551
|
+
event: "dlp_masked",
|
|
1552
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1553
|
+
});
|
|
1554
|
+
} else {
|
|
1555
|
+
stats.dlpDetected++;
|
|
1556
|
+
await audit.log({
|
|
1557
|
+
...baseRecord,
|
|
1558
|
+
event: "dlp_detected",
|
|
1559
|
+
metadata: dlpMeta
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
}
|
|
1057
1564
|
if (toolName !== "bash" && policyEngine.requiresApproval(identity.role, toolName)) {
|
|
1058
1565
|
if (approvalFlow) {
|
|
1059
1566
|
stats.approvals++;
|
|
@@ -1081,6 +1588,44 @@ var piGovernance = (pi) => {
|
|
|
1081
1588
|
return void 0;
|
|
1082
1589
|
});
|
|
1083
1590
|
pi.on("tool_result", async (event, _ctx) => {
|
|
1591
|
+
if (dlpScanner && dlpMasker && event.output) {
|
|
1592
|
+
const result = dlpScanner.scan(event.output);
|
|
1593
|
+
if (result.hasMatches) {
|
|
1594
|
+
const action = resolveHighestAction(dlpScanner, result.matches, "output");
|
|
1595
|
+
const patternNames = [...new Set(result.matches.map((m) => m.patternName))];
|
|
1596
|
+
const severities = [...new Set(result.matches.map((m) => m.severity))];
|
|
1597
|
+
const dlpMeta = {
|
|
1598
|
+
patterns: patternNames,
|
|
1599
|
+
severities,
|
|
1600
|
+
direction: "output",
|
|
1601
|
+
count: result.matches.length
|
|
1602
|
+
};
|
|
1603
|
+
if (action === "mask" || action === "block") {
|
|
1604
|
+
stats.dlpMasked++;
|
|
1605
|
+
event.output = dlpMasker.maskText(event.output, result.matches);
|
|
1606
|
+
await audit.log({
|
|
1607
|
+
sessionId,
|
|
1608
|
+
event: "dlp_masked",
|
|
1609
|
+
userId: identity.userId,
|
|
1610
|
+
role: identity.role,
|
|
1611
|
+
orgUnit: identity.orgUnit,
|
|
1612
|
+
tool: event.toolName,
|
|
1613
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1614
|
+
});
|
|
1615
|
+
} else {
|
|
1616
|
+
stats.dlpDetected++;
|
|
1617
|
+
await audit.log({
|
|
1618
|
+
sessionId,
|
|
1619
|
+
event: "dlp_detected",
|
|
1620
|
+
userId: identity.userId,
|
|
1621
|
+
role: identity.role,
|
|
1622
|
+
orgUnit: identity.orgUnit,
|
|
1623
|
+
tool: event.toolName,
|
|
1624
|
+
metadata: dlpMeta
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1084
1629
|
await audit.log({
|
|
1085
1630
|
sessionId,
|
|
1086
1631
|
event: "tool_result",
|
|
@@ -1128,6 +1673,9 @@ var piGovernance = (pi) => {
|
|
|
1128
1673
|
` Approvals: ${stats.approvals}`,
|
|
1129
1674
|
` Dry-run blocks: ${stats.dryRun}`,
|
|
1130
1675
|
` Budget exceeded: ${stats.budgetExceeded}`,
|
|
1676
|
+
` DLP blocked: ${stats.dlpBlocked}`,
|
|
1677
|
+
` DLP detected: ${stats.dlpDetected}`,
|
|
1678
|
+
` DLP masked: ${stats.dlpMasked}`,
|
|
1131
1679
|
"",
|
|
1132
1680
|
"Audit Events:",
|
|
1133
1681
|
...[...summary.entries()].map(([k, v]) => ` ${k}: ${v}`)
|