@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
package/dist/extensions/index.js
CHANGED
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/extensions/index.ts
|
|
2
|
+
import { existsSync as existsSync2 } from "fs";
|
|
3
|
+
|
|
1
4
|
// src/lib/config/loader.ts
|
|
2
5
|
import { existsSync, readFileSync } from "fs";
|
|
3
6
|
import { parse as parseYaml } from "yaml";
|
|
@@ -76,12 +79,78 @@ var OrgUnitOverride = Type.Object({
|
|
|
76
79
|
})
|
|
77
80
|
)
|
|
78
81
|
});
|
|
82
|
+
var DlpMaskingConfig = Type.Object({
|
|
83
|
+
strategy: Type.Union([Type.Literal("partial"), Type.Literal("full"), Type.Literal("hash")], {
|
|
84
|
+
default: "partial"
|
|
85
|
+
}),
|
|
86
|
+
show_chars: Type.Optional(Type.Number({ default: 4, minimum: 0 })),
|
|
87
|
+
placeholder: Type.Optional(Type.String({ default: "***" }))
|
|
88
|
+
});
|
|
89
|
+
var DlpCustomPatternConfig = Type.Object({
|
|
90
|
+
name: Type.String(),
|
|
91
|
+
pattern: Type.String(),
|
|
92
|
+
severity: Type.Union([
|
|
93
|
+
Type.Literal("low"),
|
|
94
|
+
Type.Literal("medium"),
|
|
95
|
+
Type.Literal("high"),
|
|
96
|
+
Type.Literal("critical")
|
|
97
|
+
]),
|
|
98
|
+
action: Type.Optional(
|
|
99
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
100
|
+
)
|
|
101
|
+
});
|
|
102
|
+
var DlpAllowlistEntryConfig = Type.Object({
|
|
103
|
+
pattern: Type.String()
|
|
104
|
+
});
|
|
105
|
+
var DlpRoleOverrideConfig = Type.Object({
|
|
106
|
+
enabled: Type.Optional(Type.Boolean()),
|
|
107
|
+
mode: Type.Optional(
|
|
108
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
109
|
+
),
|
|
110
|
+
on_input: Type.Optional(
|
|
111
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
112
|
+
),
|
|
113
|
+
on_output: Type.Optional(
|
|
114
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
115
|
+
)
|
|
116
|
+
});
|
|
117
|
+
var DlpConfig = Type.Object({
|
|
118
|
+
enabled: Type.Boolean({ default: false }),
|
|
119
|
+
mode: Type.Optional(
|
|
120
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")], {
|
|
121
|
+
default: "audit"
|
|
122
|
+
})
|
|
123
|
+
),
|
|
124
|
+
on_input: Type.Optional(
|
|
125
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
126
|
+
),
|
|
127
|
+
on_output: Type.Optional(
|
|
128
|
+
Type.Union([Type.Literal("audit"), Type.Literal("mask"), Type.Literal("block")])
|
|
129
|
+
),
|
|
130
|
+
masking: Type.Optional(DlpMaskingConfig),
|
|
131
|
+
severity_threshold: Type.Optional(
|
|
132
|
+
Type.Union(
|
|
133
|
+
[Type.Literal("low"), Type.Literal("medium"), Type.Literal("high"), Type.Literal("critical")],
|
|
134
|
+
{ default: "low" }
|
|
135
|
+
)
|
|
136
|
+
),
|
|
137
|
+
built_in: Type.Optional(
|
|
138
|
+
Type.Object({
|
|
139
|
+
secrets: Type.Boolean({ default: true }),
|
|
140
|
+
pii: Type.Boolean({ default: true })
|
|
141
|
+
})
|
|
142
|
+
),
|
|
143
|
+
custom_patterns: Type.Optional(Type.Array(DlpCustomPatternConfig)),
|
|
144
|
+
allowlist: Type.Optional(Type.Array(DlpAllowlistEntryConfig)),
|
|
145
|
+
role_overrides: Type.Optional(Type.Record(Type.String(), DlpRoleOverrideConfig))
|
|
146
|
+
});
|
|
79
147
|
var GovernanceConfigSchema = Type.Object({
|
|
80
148
|
auth: Type.Optional(AuthConfig),
|
|
81
149
|
policy: Type.Optional(PolicyConfig),
|
|
82
150
|
templates: Type.Optional(TemplatesConfig),
|
|
83
151
|
hitl: Type.Optional(HitlConfig),
|
|
84
152
|
audit: Type.Optional(AuditConfig),
|
|
153
|
+
dlp: Type.Optional(DlpConfig),
|
|
85
154
|
org_units: Type.Optional(Type.Record(Type.String(), OrgUnitOverride))
|
|
86
155
|
});
|
|
87
156
|
|
|
@@ -112,6 +181,9 @@ var DEFAULTS = {
|
|
|
112
181
|
},
|
|
113
182
|
audit: {
|
|
114
183
|
sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
|
|
184
|
+
},
|
|
185
|
+
dlp: {
|
|
186
|
+
enabled: false
|
|
115
187
|
}
|
|
116
188
|
};
|
|
117
189
|
|
|
@@ -821,6 +893,297 @@ var ConfigWatcher = class {
|
|
|
821
893
|
}
|
|
822
894
|
};
|
|
823
895
|
|
|
896
|
+
// src/lib/dlp/patterns.ts
|
|
897
|
+
var SECRET_PATTERNS = [
|
|
898
|
+
// AWS
|
|
899
|
+
{
|
|
900
|
+
name: "aws_access_key",
|
|
901
|
+
pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
|
|
902
|
+
severity: "critical",
|
|
903
|
+
category: "secret"
|
|
904
|
+
},
|
|
905
|
+
{
|
|
906
|
+
name: "aws_secret_key",
|
|
907
|
+
pattern: /\b([A-Za-z0-9/+=]{40})(?=\s|$|"|')/g,
|
|
908
|
+
severity: "critical",
|
|
909
|
+
category: "secret"
|
|
910
|
+
},
|
|
911
|
+
// GitHub
|
|
912
|
+
{
|
|
913
|
+
name: "github_pat",
|
|
914
|
+
pattern: /\b(ghp_[A-Za-z0-9]{36,})\b/g,
|
|
915
|
+
severity: "critical",
|
|
916
|
+
category: "secret"
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
name: "github_oauth",
|
|
920
|
+
pattern: /\b(gho_[A-Za-z0-9]{36,})\b/g,
|
|
921
|
+
severity: "high",
|
|
922
|
+
category: "secret"
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
name: "github_app_token",
|
|
926
|
+
pattern: /\b(ghu_[A-Za-z0-9]{36,})\b/g,
|
|
927
|
+
severity: "high",
|
|
928
|
+
category: "secret"
|
|
929
|
+
},
|
|
930
|
+
// Anthropic
|
|
931
|
+
{
|
|
932
|
+
name: "anthropic_api_key",
|
|
933
|
+
pattern: /\b(sk-ant-api03-[A-Za-z0-9_-]{90,})\b/g,
|
|
934
|
+
severity: "critical",
|
|
935
|
+
category: "secret"
|
|
936
|
+
},
|
|
937
|
+
// OpenAI
|
|
938
|
+
{
|
|
939
|
+
name: "openai_api_key",
|
|
940
|
+
pattern: /\b(sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,})\b/g,
|
|
941
|
+
severity: "critical",
|
|
942
|
+
category: "secret"
|
|
943
|
+
},
|
|
944
|
+
// JWT
|
|
945
|
+
{
|
|
946
|
+
name: "jwt_token",
|
|
947
|
+
pattern: /\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
|
|
948
|
+
severity: "high",
|
|
949
|
+
category: "secret"
|
|
950
|
+
},
|
|
951
|
+
// Private key headers
|
|
952
|
+
{
|
|
953
|
+
name: "private_key",
|
|
954
|
+
pattern: /-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
|
|
955
|
+
severity: "critical",
|
|
956
|
+
category: "secret"
|
|
957
|
+
},
|
|
958
|
+
// Database connection strings
|
|
959
|
+
{
|
|
960
|
+
name: "database_url",
|
|
961
|
+
pattern: /\b((?:postgres|mysql|mongodb|redis):\/\/[^\s'"]{10,})\b/g,
|
|
962
|
+
severity: "high",
|
|
963
|
+
category: "secret"
|
|
964
|
+
},
|
|
965
|
+
// Slack
|
|
966
|
+
{
|
|
967
|
+
name: "slack_token",
|
|
968
|
+
pattern: /\b(xox[bpras]-[A-Za-z0-9-]{10,})\b/g,
|
|
969
|
+
severity: "high",
|
|
970
|
+
category: "secret"
|
|
971
|
+
},
|
|
972
|
+
// Stripe
|
|
973
|
+
{
|
|
974
|
+
name: "stripe_key",
|
|
975
|
+
pattern: /\b([rs]k_(?:live|test)_[A-Za-z0-9]{20,})\b/g,
|
|
976
|
+
severity: "critical",
|
|
977
|
+
category: "secret"
|
|
978
|
+
},
|
|
979
|
+
// npm
|
|
980
|
+
{
|
|
981
|
+
name: "npm_token",
|
|
982
|
+
pattern: /\b(npm_[A-Za-z0-9]{36,})\b/g,
|
|
983
|
+
severity: "high",
|
|
984
|
+
category: "secret"
|
|
985
|
+
},
|
|
986
|
+
// SendGrid
|
|
987
|
+
{
|
|
988
|
+
name: "sendgrid_key",
|
|
989
|
+
pattern: /\b(SG\.[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{22,})\b/g,
|
|
990
|
+
severity: "high",
|
|
991
|
+
category: "secret"
|
|
992
|
+
},
|
|
993
|
+
// Generic API key patterns (env-var style assignments)
|
|
994
|
+
{
|
|
995
|
+
name: "generic_api_key",
|
|
996
|
+
pattern: /\b(?:API_KEY|API_SECRET|ACCESS_TOKEN|AUTH_TOKEN|SECRET_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{16,})['"]?/gi,
|
|
997
|
+
severity: "medium",
|
|
998
|
+
category: "secret"
|
|
999
|
+
},
|
|
1000
|
+
// High-entropy string near keyword context
|
|
1001
|
+
{
|
|
1002
|
+
name: "generic_secret_assignment",
|
|
1003
|
+
pattern: /\b(?:password|passwd|secret|token|credential)\s*[=:]\s*['"]([^'"]{8,})['"]?/gi,
|
|
1004
|
+
severity: "medium",
|
|
1005
|
+
category: "secret"
|
|
1006
|
+
}
|
|
1007
|
+
];
|
|
1008
|
+
var PII_PATTERNS = [
|
|
1009
|
+
// SSN (US)
|
|
1010
|
+
{
|
|
1011
|
+
name: "ssn",
|
|
1012
|
+
pattern: /\b(\d{3}-\d{2}-\d{4})\b/g,
|
|
1013
|
+
severity: "critical",
|
|
1014
|
+
category: "pii"
|
|
1015
|
+
},
|
|
1016
|
+
// Credit card numbers
|
|
1017
|
+
{
|
|
1018
|
+
name: "credit_card",
|
|
1019
|
+
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,
|
|
1020
|
+
severity: "critical",
|
|
1021
|
+
category: "pii"
|
|
1022
|
+
},
|
|
1023
|
+
// Email address
|
|
1024
|
+
{
|
|
1025
|
+
name: "email",
|
|
1026
|
+
pattern: /\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
|
|
1027
|
+
severity: "low",
|
|
1028
|
+
category: "pii"
|
|
1029
|
+
},
|
|
1030
|
+
// US phone number
|
|
1031
|
+
{
|
|
1032
|
+
name: "phone_us",
|
|
1033
|
+
pattern: /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g,
|
|
1034
|
+
severity: "medium",
|
|
1035
|
+
category: "pii"
|
|
1036
|
+
},
|
|
1037
|
+
// IPv4 address
|
|
1038
|
+
{
|
|
1039
|
+
name: "ipv4",
|
|
1040
|
+
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,
|
|
1041
|
+
severity: "low",
|
|
1042
|
+
category: "pii"
|
|
1043
|
+
}
|
|
1044
|
+
];
|
|
1045
|
+
|
|
1046
|
+
// src/lib/dlp/scanner.ts
|
|
1047
|
+
var SEVERITY_ORDER = {
|
|
1048
|
+
low: 0,
|
|
1049
|
+
medium: 1,
|
|
1050
|
+
high: 2,
|
|
1051
|
+
critical: 3
|
|
1052
|
+
};
|
|
1053
|
+
var DlpScanner = class {
|
|
1054
|
+
patterns;
|
|
1055
|
+
allowlistRegexps;
|
|
1056
|
+
severityThreshold;
|
|
1057
|
+
config;
|
|
1058
|
+
constructor(config) {
|
|
1059
|
+
this.config = config;
|
|
1060
|
+
this.severityThreshold = SEVERITY_ORDER[config.severity_threshold];
|
|
1061
|
+
this.patterns = [];
|
|
1062
|
+
this.allowlistRegexps = [];
|
|
1063
|
+
if (config.built_in.secrets) {
|
|
1064
|
+
for (const def of SECRET_PATTERNS) {
|
|
1065
|
+
this.patterns.push({ def });
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (config.built_in.pii) {
|
|
1069
|
+
for (const def of PII_PATTERNS) {
|
|
1070
|
+
this.patterns.push({ def });
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
for (const cp of config.custom_patterns) {
|
|
1074
|
+
const def = {
|
|
1075
|
+
name: cp.name,
|
|
1076
|
+
pattern: new RegExp(cp.pattern, "g"),
|
|
1077
|
+
severity: cp.severity,
|
|
1078
|
+
category: "custom"
|
|
1079
|
+
};
|
|
1080
|
+
this.patterns.push({ def, action: cp.action });
|
|
1081
|
+
}
|
|
1082
|
+
for (const compiled of this.patterns) {
|
|
1083
|
+
const override = config.pattern_overrides.get(compiled.def.name);
|
|
1084
|
+
if (override) {
|
|
1085
|
+
compiled.action = override;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
for (const entry of config.allowlist) {
|
|
1089
|
+
this.allowlistRegexps.push(new RegExp(entry.pattern));
|
|
1090
|
+
}
|
|
1091
|
+
}
|
|
1092
|
+
scan(text) {
|
|
1093
|
+
if (!this.config.enabled || text.length === 0) {
|
|
1094
|
+
return { hasMatches: false, matches: [] };
|
|
1095
|
+
}
|
|
1096
|
+
const matches = [];
|
|
1097
|
+
for (const compiled of this.patterns) {
|
|
1098
|
+
if (SEVERITY_ORDER[compiled.def.severity] < this.severityThreshold) {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
const regex = new RegExp(compiled.def.pattern.source, compiled.def.pattern.flags);
|
|
1102
|
+
let match;
|
|
1103
|
+
while ((match = regex.exec(text)) !== null) {
|
|
1104
|
+
const matched = match[1] ?? match[0];
|
|
1105
|
+
const start = match[1] ? match.index + match[0].indexOf(match[1]) : match.index;
|
|
1106
|
+
const end = start + matched.length;
|
|
1107
|
+
if (this.isAllowlisted(matched)) {
|
|
1108
|
+
continue;
|
|
1109
|
+
}
|
|
1110
|
+
matches.push({
|
|
1111
|
+
patternName: compiled.def.name,
|
|
1112
|
+
category: compiled.def.category,
|
|
1113
|
+
severity: compiled.def.severity,
|
|
1114
|
+
start,
|
|
1115
|
+
end,
|
|
1116
|
+
matched
|
|
1117
|
+
});
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
return { hasMatches: matches.length > 0, matches };
|
|
1121
|
+
}
|
|
1122
|
+
getAction(direction) {
|
|
1123
|
+
if (direction === "input" && this.config.on_input) {
|
|
1124
|
+
return this.config.on_input;
|
|
1125
|
+
}
|
|
1126
|
+
if (direction === "output" && this.config.on_output) {
|
|
1127
|
+
return this.config.on_output;
|
|
1128
|
+
}
|
|
1129
|
+
return this.config.mode;
|
|
1130
|
+
}
|
|
1131
|
+
getPatternAction(match, direction) {
|
|
1132
|
+
const compiled = this.patterns.find((p) => p.def.name === match.patternName);
|
|
1133
|
+
if (compiled?.action) {
|
|
1134
|
+
return compiled.action;
|
|
1135
|
+
}
|
|
1136
|
+
return this.getAction(direction);
|
|
1137
|
+
}
|
|
1138
|
+
isAllowlisted(value) {
|
|
1139
|
+
for (const re of this.allowlistRegexps) {
|
|
1140
|
+
if (re.test(value)) return true;
|
|
1141
|
+
}
|
|
1142
|
+
return false;
|
|
1143
|
+
}
|
|
1144
|
+
};
|
|
1145
|
+
|
|
1146
|
+
// src/lib/dlp/masker.ts
|
|
1147
|
+
import { createHash } from "crypto";
|
|
1148
|
+
var DEFAULT_CONFIG = {
|
|
1149
|
+
strategy: "partial",
|
|
1150
|
+
show_chars: 4,
|
|
1151
|
+
placeholder: "***"
|
|
1152
|
+
};
|
|
1153
|
+
var DlpMasker = class {
|
|
1154
|
+
config;
|
|
1155
|
+
constructor(config) {
|
|
1156
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
1157
|
+
}
|
|
1158
|
+
maskValue(value) {
|
|
1159
|
+
switch (this.config.strategy) {
|
|
1160
|
+
case "full":
|
|
1161
|
+
return this.config.placeholder;
|
|
1162
|
+
case "hash": {
|
|
1163
|
+
const hash = createHash("sha256").update(value).digest("hex").slice(0, 8);
|
|
1164
|
+
return `[REDACTED:${hash}]`;
|
|
1165
|
+
}
|
|
1166
|
+
case "partial":
|
|
1167
|
+
default: {
|
|
1168
|
+
if (value.length <= this.config.show_chars) {
|
|
1169
|
+
return this.config.placeholder;
|
|
1170
|
+
}
|
|
1171
|
+
return this.config.placeholder + value.slice(-this.config.show_chars);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
maskText(text, matches) {
|
|
1176
|
+
if (matches.length === 0) return text;
|
|
1177
|
+
const sorted = [...matches].sort((a, b) => b.start - a.start);
|
|
1178
|
+
let result = text;
|
|
1179
|
+
for (const match of sorted) {
|
|
1180
|
+
const masked = this.maskValue(match.matched);
|
|
1181
|
+
result = result.slice(0, match.start) + masked + result.slice(match.end);
|
|
1182
|
+
}
|
|
1183
|
+
return result;
|
|
1184
|
+
}
|
|
1185
|
+
};
|
|
1186
|
+
|
|
824
1187
|
// src/extensions/index.ts
|
|
825
1188
|
var PATH_TOOLS = {
|
|
826
1189
|
read: "path",
|
|
@@ -858,6 +1221,72 @@ function extractPath(toolName, input) {
|
|
|
858
1221
|
const val = input[key];
|
|
859
1222
|
return typeof val === "string" ? val : void 0;
|
|
860
1223
|
}
|
|
1224
|
+
var ACTION_PRIORITY = { audit: 0, mask: 1, block: 2 };
|
|
1225
|
+
function extractDlpFields(toolName, input) {
|
|
1226
|
+
const fields = /* @__PURE__ */ new Map();
|
|
1227
|
+
switch (toolName) {
|
|
1228
|
+
case "bash": {
|
|
1229
|
+
const cmd = input["command"];
|
|
1230
|
+
if (typeof cmd === "string") fields.set("command", cmd);
|
|
1231
|
+
break;
|
|
1232
|
+
}
|
|
1233
|
+
case "write": {
|
|
1234
|
+
const content = input["content"];
|
|
1235
|
+
if (typeof content === "string") fields.set("content", content);
|
|
1236
|
+
const path = input["path"];
|
|
1237
|
+
if (typeof path === "string") fields.set("path", path);
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
case "edit": {
|
|
1241
|
+
const newStr = input["new_string"];
|
|
1242
|
+
if (typeof newStr === "string") fields.set("new_string", newStr);
|
|
1243
|
+
const oldStr = input["old_string"];
|
|
1244
|
+
if (typeof oldStr === "string") fields.set("old_string", oldStr);
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
default: {
|
|
1248
|
+
for (const [key, val] of Object.entries(input)) {
|
|
1249
|
+
if (typeof val === "string") fields.set(key, val);
|
|
1250
|
+
}
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
return fields;
|
|
1254
|
+
}
|
|
1255
|
+
function resolveHighestAction(scanner, matches, direction) {
|
|
1256
|
+
let highest = "audit";
|
|
1257
|
+
for (const match of matches) {
|
|
1258
|
+
const action = scanner.getPatternAction(match, direction);
|
|
1259
|
+
if (ACTION_PRIORITY[action] > ACTION_PRIORITY[highest]) {
|
|
1260
|
+
highest = action;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
return highest;
|
|
1264
|
+
}
|
|
1265
|
+
function resolveDlpConfig(dlpConfig, role) {
|
|
1266
|
+
if (!dlpConfig?.enabled) return void 0;
|
|
1267
|
+
const roleOverride = dlpConfig.role_overrides?.[role];
|
|
1268
|
+
if (roleOverride?.enabled === false) return void 0;
|
|
1269
|
+
const patternOverrides = /* @__PURE__ */ new Map();
|
|
1270
|
+
return {
|
|
1271
|
+
enabled: true,
|
|
1272
|
+
mode: roleOverride?.mode ?? dlpConfig.mode ?? "audit",
|
|
1273
|
+
on_input: roleOverride?.on_input ?? dlpConfig.on_input,
|
|
1274
|
+
on_output: roleOverride?.on_output ?? dlpConfig.on_output,
|
|
1275
|
+
severity_threshold: dlpConfig.severity_threshold ?? "low",
|
|
1276
|
+
built_in: {
|
|
1277
|
+
secrets: dlpConfig.built_in?.secrets ?? true,
|
|
1278
|
+
pii: dlpConfig.built_in?.pii ?? true
|
|
1279
|
+
},
|
|
1280
|
+
custom_patterns: (dlpConfig.custom_patterns ?? []).map((cp) => ({
|
|
1281
|
+
name: cp.name,
|
|
1282
|
+
pattern: cp.pattern,
|
|
1283
|
+
severity: cp.severity,
|
|
1284
|
+
action: cp.action
|
|
1285
|
+
})),
|
|
1286
|
+
allowlist: dlpConfig.allowlist ?? [],
|
|
1287
|
+
pattern_overrides: patternOverrides
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
861
1290
|
var piGovernance = (pi) => {
|
|
862
1291
|
let config;
|
|
863
1292
|
let policyEngine;
|
|
@@ -869,7 +1298,18 @@ var piGovernance = (pi) => {
|
|
|
869
1298
|
let sessionId;
|
|
870
1299
|
let budgetTracker;
|
|
871
1300
|
let configWatcher;
|
|
872
|
-
|
|
1301
|
+
let dlpScanner;
|
|
1302
|
+
let dlpMasker;
|
|
1303
|
+
const stats = {
|
|
1304
|
+
allowed: 0,
|
|
1305
|
+
denied: 0,
|
|
1306
|
+
approvals: 0,
|
|
1307
|
+
dryRun: 0,
|
|
1308
|
+
budgetExceeded: 0,
|
|
1309
|
+
dlpBlocked: 0,
|
|
1310
|
+
dlpDetected: 0,
|
|
1311
|
+
dlpMasked: 0
|
|
1312
|
+
};
|
|
873
1313
|
pi.on("session_start", async (_event, ctx) => {
|
|
874
1314
|
sessionId = ctx.sessionId;
|
|
875
1315
|
const loaded = loadConfig();
|
|
@@ -877,7 +1317,45 @@ var piGovernance = (pi) => {
|
|
|
877
1317
|
const chain = createIdentityChain(config.auth);
|
|
878
1318
|
identity = await chain.resolve();
|
|
879
1319
|
const rulesFile = config.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
|
|
880
|
-
|
|
1320
|
+
if (existsSync2(rulesFile)) {
|
|
1321
|
+
policyEngine = new YamlPolicyEngine(rulesFile);
|
|
1322
|
+
} else {
|
|
1323
|
+
policyEngine = new YamlPolicyEngine({
|
|
1324
|
+
roles: {
|
|
1325
|
+
admin: {
|
|
1326
|
+
allowed_tools: ["all"],
|
|
1327
|
+
blocked_tools: [],
|
|
1328
|
+
prompt_template: "admin",
|
|
1329
|
+
execution_mode: "autonomous",
|
|
1330
|
+
human_approval: { required_for: [] },
|
|
1331
|
+
token_budget_daily: -1,
|
|
1332
|
+
allowed_paths: ["**"],
|
|
1333
|
+
blocked_paths: []
|
|
1334
|
+
},
|
|
1335
|
+
project_lead: {
|
|
1336
|
+
allowed_tools: ["all"],
|
|
1337
|
+
blocked_tools: [],
|
|
1338
|
+
prompt_template: "project-lead",
|
|
1339
|
+
execution_mode: "supervised",
|
|
1340
|
+
human_approval: { required_for: ["bash", "write"] },
|
|
1341
|
+
token_budget_daily: -1,
|
|
1342
|
+
allowed_paths: ["**"],
|
|
1343
|
+
blocked_paths: []
|
|
1344
|
+
},
|
|
1345
|
+
analyst: {
|
|
1346
|
+
allowed_tools: ["read", "grep", "find", "ls"],
|
|
1347
|
+
blocked_tools: ["write", "edit", "bash"],
|
|
1348
|
+
prompt_template: "analyst",
|
|
1349
|
+
execution_mode: "supervised",
|
|
1350
|
+
human_approval: { required_for: ["all"] },
|
|
1351
|
+
token_budget_daily: -1,
|
|
1352
|
+
allowed_paths: ["**"],
|
|
1353
|
+
blocked_paths: []
|
|
1354
|
+
}
|
|
1355
|
+
}
|
|
1356
|
+
});
|
|
1357
|
+
ctx.ui.notify(`Rules file not found: ${rulesFile} \u2014 using built-in defaults`, "warning");
|
|
1358
|
+
}
|
|
881
1359
|
executionMode = policyEngine.getExecutionMode(identity.role);
|
|
882
1360
|
const bashOverrides = policyEngine.getBashOverrides(identity.role);
|
|
883
1361
|
bashClassifier = new BashClassifier(bashOverrides);
|
|
@@ -898,15 +1376,30 @@ var piGovernance = (pi) => {
|
|
|
898
1376
|
}
|
|
899
1377
|
const budget = policyEngine.getTokenBudget(identity.role);
|
|
900
1378
|
budgetTracker = new BudgetTracker(budget);
|
|
1379
|
+
const dlpCfg = resolveDlpConfig(config.dlp, identity.role);
|
|
1380
|
+
if (dlpCfg) {
|
|
1381
|
+
dlpScanner = new DlpScanner(dlpCfg);
|
|
1382
|
+
dlpMasker = new DlpMasker(config.dlp?.masking);
|
|
1383
|
+
}
|
|
901
1384
|
if (loaded.source !== "built-in") {
|
|
902
1385
|
configWatcher = new ConfigWatcher(
|
|
903
1386
|
loaded.source,
|
|
904
1387
|
(newConfig) => {
|
|
905
1388
|
config = newConfig;
|
|
906
1389
|
const newRulesFile = newConfig.policy?.yaml?.rules_file ?? "./governance-rules.yaml";
|
|
907
|
-
|
|
1390
|
+
if (existsSync2(newRulesFile)) {
|
|
1391
|
+
policyEngine = new YamlPolicyEngine(newRulesFile);
|
|
1392
|
+
}
|
|
908
1393
|
const newOverrides = policyEngine.getBashOverrides(identity.role);
|
|
909
1394
|
bashClassifier = new BashClassifier(newOverrides);
|
|
1395
|
+
const newDlpCfg = resolveDlpConfig(newConfig.dlp, identity.role);
|
|
1396
|
+
if (newDlpCfg) {
|
|
1397
|
+
dlpScanner = new DlpScanner(newDlpCfg);
|
|
1398
|
+
dlpMasker = new DlpMasker(newConfig.dlp?.masking);
|
|
1399
|
+
} else {
|
|
1400
|
+
dlpScanner = void 0;
|
|
1401
|
+
dlpMasker = void 0;
|
|
1402
|
+
}
|
|
910
1403
|
audit.log({
|
|
911
1404
|
sessionId,
|
|
912
1405
|
event: "config_reloaded",
|
|
@@ -1054,6 +1547,63 @@ var piGovernance = (pi) => {
|
|
|
1054
1547
|
return { block: true, reason: `Access denied to path: ${path}` };
|
|
1055
1548
|
}
|
|
1056
1549
|
}
|
|
1550
|
+
if (dlpScanner && dlpMasker) {
|
|
1551
|
+
const fields = extractDlpFields(toolName, input);
|
|
1552
|
+
const allMatches = [];
|
|
1553
|
+
for (const [, fieldValue] of fields) {
|
|
1554
|
+
const result = dlpScanner.scan(fieldValue);
|
|
1555
|
+
allMatches.push(...result.matches);
|
|
1556
|
+
}
|
|
1557
|
+
if (allMatches.length > 0) {
|
|
1558
|
+
const action = resolveHighestAction(dlpScanner, allMatches, "input");
|
|
1559
|
+
const patternNames = [...new Set(allMatches.map((m) => m.patternName))];
|
|
1560
|
+
const severities = [...new Set(allMatches.map((m) => m.severity))];
|
|
1561
|
+
const dlpMeta = {
|
|
1562
|
+
patterns: patternNames,
|
|
1563
|
+
severities,
|
|
1564
|
+
direction: "input",
|
|
1565
|
+
count: allMatches.length
|
|
1566
|
+
};
|
|
1567
|
+
if (action === "block") {
|
|
1568
|
+
stats.dlpBlocked++;
|
|
1569
|
+
await audit.log({
|
|
1570
|
+
...baseRecord,
|
|
1571
|
+
event: "dlp_blocked",
|
|
1572
|
+
decision: "denied",
|
|
1573
|
+
reason: `DLP: ${patternNames.join(", ")} detected in input`,
|
|
1574
|
+
metadata: dlpMeta
|
|
1575
|
+
});
|
|
1576
|
+
return {
|
|
1577
|
+
block: true,
|
|
1578
|
+
reason: `DLP blocked: sensitive data detected (${patternNames.join(", ")})`
|
|
1579
|
+
};
|
|
1580
|
+
}
|
|
1581
|
+
if (action === "mask") {
|
|
1582
|
+
stats.dlpMasked++;
|
|
1583
|
+
for (const [fieldKey, fieldValue] of fields) {
|
|
1584
|
+
const fieldResult = dlpScanner.scan(fieldValue);
|
|
1585
|
+
if (fieldResult.hasMatches) {
|
|
1586
|
+
input[fieldKey] = dlpMasker.maskText(
|
|
1587
|
+
fieldValue,
|
|
1588
|
+
fieldResult.matches
|
|
1589
|
+
);
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
await audit.log({
|
|
1593
|
+
...baseRecord,
|
|
1594
|
+
event: "dlp_masked",
|
|
1595
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1596
|
+
});
|
|
1597
|
+
} else {
|
|
1598
|
+
stats.dlpDetected++;
|
|
1599
|
+
await audit.log({
|
|
1600
|
+
...baseRecord,
|
|
1601
|
+
event: "dlp_detected",
|
|
1602
|
+
metadata: dlpMeta
|
|
1603
|
+
});
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1057
1607
|
if (toolName !== "bash" && policyEngine.requiresApproval(identity.role, toolName)) {
|
|
1058
1608
|
if (approvalFlow) {
|
|
1059
1609
|
stats.approvals++;
|
|
@@ -1081,6 +1631,44 @@ var piGovernance = (pi) => {
|
|
|
1081
1631
|
return void 0;
|
|
1082
1632
|
});
|
|
1083
1633
|
pi.on("tool_result", async (event, _ctx) => {
|
|
1634
|
+
if (dlpScanner && dlpMasker && event.output) {
|
|
1635
|
+
const result = dlpScanner.scan(event.output);
|
|
1636
|
+
if (result.hasMatches) {
|
|
1637
|
+
const action = resolveHighestAction(dlpScanner, result.matches, "output");
|
|
1638
|
+
const patternNames = [...new Set(result.matches.map((m) => m.patternName))];
|
|
1639
|
+
const severities = [...new Set(result.matches.map((m) => m.severity))];
|
|
1640
|
+
const dlpMeta = {
|
|
1641
|
+
patterns: patternNames,
|
|
1642
|
+
severities,
|
|
1643
|
+
direction: "output",
|
|
1644
|
+
count: result.matches.length
|
|
1645
|
+
};
|
|
1646
|
+
if (action === "mask" || action === "block") {
|
|
1647
|
+
stats.dlpMasked++;
|
|
1648
|
+
event.output = dlpMasker.maskText(event.output, result.matches);
|
|
1649
|
+
await audit.log({
|
|
1650
|
+
sessionId,
|
|
1651
|
+
event: "dlp_masked",
|
|
1652
|
+
userId: identity.userId,
|
|
1653
|
+
role: identity.role,
|
|
1654
|
+
orgUnit: identity.orgUnit,
|
|
1655
|
+
tool: event.toolName,
|
|
1656
|
+
metadata: { ...dlpMeta, strategy: dlpMasker["config"].strategy }
|
|
1657
|
+
});
|
|
1658
|
+
} else {
|
|
1659
|
+
stats.dlpDetected++;
|
|
1660
|
+
await audit.log({
|
|
1661
|
+
sessionId,
|
|
1662
|
+
event: "dlp_detected",
|
|
1663
|
+
userId: identity.userId,
|
|
1664
|
+
role: identity.role,
|
|
1665
|
+
orgUnit: identity.orgUnit,
|
|
1666
|
+
tool: event.toolName,
|
|
1667
|
+
metadata: dlpMeta
|
|
1668
|
+
});
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1084
1672
|
await audit.log({
|
|
1085
1673
|
sessionId,
|
|
1086
1674
|
event: "tool_result",
|
|
@@ -1128,6 +1716,9 @@ var piGovernance = (pi) => {
|
|
|
1128
1716
|
` Approvals: ${stats.approvals}`,
|
|
1129
1717
|
` Dry-run blocks: ${stats.dryRun}`,
|
|
1130
1718
|
` Budget exceeded: ${stats.budgetExceeded}`,
|
|
1719
|
+
` DLP blocked: ${stats.dlpBlocked}`,
|
|
1720
|
+
` DLP detected: ${stats.dlpDetected}`,
|
|
1721
|
+
` DLP masked: ${stats.dlpMasked}`,
|
|
1131
1722
|
"",
|
|
1132
1723
|
"Audit Events:",
|
|
1133
1724
|
...[...summary.entries()].map(([k, v]) => ` ${k}: ${v}`)
|