fullshield 0.1.0 → 0.1.3

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.
Files changed (55) hide show
  1. package/CHANGELOG.md +109 -3
  2. package/README.md +48 -6
  3. package/dist/agent.js +3424 -601
  4. package/dist/bench/score.js +118 -0
  5. package/dist/cli/index.js +5857 -2033
  6. package/dist/compliance.js +31 -2
  7. package/dist/dashboard.js +50 -9
  8. package/dist/index.d.ts +152 -2
  9. package/dist/index.js +5733 -2611
  10. package/dist/mcp.js +3452 -613
  11. package/dist/runtime/auto.js +100 -3
  12. package/dist/runtime/dlp.js +313 -0
  13. package/dist/runtime/express.js +351 -1
  14. package/dist/runtime/fastify.js +339 -1
  15. package/dist/runtime/hono.js +570 -0
  16. package/dist/runtime/index.js +14 -1
  17. package/dist/runtime/koa.js +573 -0
  18. package/dist/runtime/rasp.js +100 -3
  19. package/dist/runtime/register.js +100 -3
  20. package/dist/supply.js +87 -1
  21. package/dist/vex.js +178 -0
  22. package/package.json +25 -6
  23. package/rules/CONFIG-DIRLIST-001.md +9 -0
  24. package/rules/CONFIG-TRUSTPROXY-001.md +11 -0
  25. package/rules/DLP-CARD-001.md +9 -0
  26. package/rules/DLP-INTERNAL-001.md +9 -0
  27. package/rules/DLP-PII-001.md +9 -0
  28. package/rules/DLP-SECRET-001.md +9 -0
  29. package/rules/DLP-STACK-001.md +9 -0
  30. package/rules/HOST-SSH-001.md +13 -0
  31. package/rules/HOST-SSH-002.md +11 -0
  32. package/rules/HOST-SSH-003.md +11 -0
  33. package/rules/MCP-001.md +7 -0
  34. package/rules/MCP-002.md +7 -0
  35. package/rules/MCP-003.md +7 -0
  36. package/rules/MCP-004.md +7 -0
  37. package/rules/MCP-005.md +7 -0
  38. package/rules/README.md +47 -1
  39. package/rules/SAST-COOKIE-001.md +11 -0
  40. package/rules/SAST-CSRF-001.md +11 -0
  41. package/rules/SAST-DESER-001.md +11 -0
  42. package/rules/SAST-GQL-001.md +11 -0
  43. package/rules/SAST-HASH-001.md +11 -0
  44. package/rules/SAST-MASSASSIGN-001.md +11 -0
  45. package/rules/SAST-NOSQLI-001.md +6 -2
  46. package/rules/SAST-REDOS-001.md +11 -0
  47. package/rules/SAST-TIMING-001.md +11 -0
  48. package/rules/SAST-UPLOAD-001.md +11 -0
  49. package/rules/SAST-XXE-001.md +11 -0
  50. package/rules/SECRET-BUNDLE-001.md +11 -0
  51. package/rules/SECRET-SESSION-001.md +9 -0
  52. package/rules/SUPPLY-WORKFLOW-001.md +9 -0
  53. package/rules/SUPPLY-WORM-001.md +9 -0
  54. package/templates/github-watch.yml +3 -3
  55. package/templates/github-workflow.yml +3 -3
@@ -450,9 +450,25 @@ function sinkTexts(sink) {
450
450
  if (typeof sink === "number" || typeof sink === "boolean") return [String(sink)];
451
451
  return [];
452
452
  }
453
+ function whereJsChangesStructure(sink, ctx) {
454
+ let code = "";
455
+ if (typeof sink === "string") code = sink;
456
+ else if (sink !== null && typeof sink === "object" && !Array.isArray(sink) && "$where" in sink) {
457
+ const where = sink.$where;
458
+ if (typeof where === "string") code = where;
459
+ }
460
+ if (!code) return false;
461
+ for (const input of ctx.strings) {
462
+ if (input && sqlInputChangesStructure(code, input)) return true;
463
+ }
464
+ return false;
465
+ }
453
466
  function detectStructureChange(kind, sink, ctx, base) {
454
467
  if (kind === "proto") return protoPolluted(ctx);
455
- if (kind === "nosql") return nosqlUserOperators(sink, ctx);
468
+ if (kind === "nosql") {
469
+ if (nosqlUserOperators(sink, ctx)) return true;
470
+ return whereJsChangesStructure(sink, ctx);
471
+ }
456
472
  for (const text of sinkTexts(sink)) {
457
473
  for (const input of ctx.strings) {
458
474
  if (!input) continue;
@@ -754,11 +770,38 @@ function patchMethod(obj, key, wrapper) {
754
770
  record[key] = orig;
755
771
  });
756
772
  }
773
+ function sqlTextFrom(sql) {
774
+ if (typeof sql === "string") return sql;
775
+ if (Array.isArray(sql)) {
776
+ return sql.filter((part) => typeof part === "string").join(" ");
777
+ }
778
+ if (sql !== null && typeof sql === "object") {
779
+ const rec = sql;
780
+ if (typeof rec.text === "string") return rec.text;
781
+ if (typeof rec.sql === "string") return rec.sql;
782
+ if (Array.isArray(rec.strings)) {
783
+ return rec.strings.filter((part) => typeof part === "string").join(" ");
784
+ }
785
+ }
786
+ return "";
787
+ }
757
788
  function wrapSqlMethod(obj, key) {
758
789
  patchMethod(obj, key, (orig) => {
759
790
  return function(sql, ...rest) {
760
- const text = typeof sql === "string" ? sql : sql && typeof sql === "object" && "text" in sql && typeof sql.text === "string" ? sql.text : "";
761
- return applySink("sql", text, void 0, () => orig.call(this, sql, ...rest));
791
+ return applySink(
792
+ "sql",
793
+ sqlTextFrom(sql),
794
+ void 0,
795
+ () => orig.call(this, sql, ...rest)
796
+ );
797
+ };
798
+ });
799
+ }
800
+ function wrapNosqlWhere(obj, key) {
801
+ patchMethod(obj, key, (orig) => {
802
+ return function(expr, ...rest) {
803
+ const sink = typeof expr === "string" ? { $where: expr } : expr ?? {};
804
+ return applySink("nosql", sink, void 0, () => orig.call(this, expr, ...rest));
762
805
  };
763
806
  });
764
807
  }
@@ -1049,6 +1092,57 @@ function wrapKnownPackage(request, exported) {
1049
1092
  wrappedPackages.add(obj);
1050
1093
  return exported;
1051
1094
  }
1095
+ if (name === "@prisma/client" || name === "prisma" || name.endsWith("/@prisma/client")) {
1096
+ const client = exported.PrismaClient?.prototype;
1097
+ if (client) {
1098
+ wrapSqlMethod(client, "$queryRawUnsafe");
1099
+ wrapSqlMethod(client, "$executeRawUnsafe");
1100
+ wrapSqlMethod(client, "$queryRaw");
1101
+ wrapSqlMethod(client, "$executeRaw");
1102
+ }
1103
+ const Prisma = exported.Prisma;
1104
+ if (Prisma) wrapSqlMethod(Prisma, "raw");
1105
+ wrappedPackages.add(obj);
1106
+ return exported;
1107
+ }
1108
+ if (name === "sequelize" || name.endsWith("/sequelize")) {
1109
+ const rec = exported;
1110
+ const proto = rec.Sequelize?.prototype ?? rec.prototype;
1111
+ if (proto) wrapSqlMethod(proto, "query");
1112
+ if (typeof rec.literal === "function") wrapSqlMethod(rec, "literal");
1113
+ if (rec.Sequelize && typeof rec.Sequelize.literal === "function") wrapSqlMethod(rec.Sequelize, "literal");
1114
+ wrappedPackages.add(obj);
1115
+ return exported;
1116
+ }
1117
+ if (name === "knex" || name.endsWith("/knex")) {
1118
+ if (typeof exported.raw === "function") wrapSqlMethod(obj, "raw");
1119
+ const client = exported.Client?.prototype;
1120
+ if (client) {
1121
+ wrapSqlMethod(client, "raw");
1122
+ wrapSqlMethod(client, "whereRaw");
1123
+ wrapSqlMethod(client, "orderByRaw");
1124
+ wrapSqlMethod(client, "query");
1125
+ }
1126
+ wrappedPackages.add(obj);
1127
+ return exported;
1128
+ }
1129
+ if (name === "typeorm" || name.endsWith("/typeorm")) {
1130
+ const rec = exported;
1131
+ for (const cls of ["DataSource", "EntityManager", "QueryRunner", "Connection"]) {
1132
+ const proto = rec[cls]?.prototype;
1133
+ if (proto) wrapSqlMethod(proto, "query");
1134
+ }
1135
+ const qb = rec.SelectQueryBuilder?.prototype ?? rec.QueryBuilder?.prototype;
1136
+ if (qb) wrapSqlMethod(qb, "where");
1137
+ wrappedPackages.add(obj);
1138
+ return exported;
1139
+ }
1140
+ if (name === "drizzle-orm" || name.endsWith("/drizzle-orm")) {
1141
+ const sql = exported.sql;
1142
+ if (sql) wrapSqlMethod(sql, "raw");
1143
+ wrappedPackages.add(obj);
1144
+ return exported;
1145
+ }
1052
1146
  if (name === "mongodb" || name.includes("mongodb")) {
1053
1147
  const col = exported.Collection?.prototype;
1054
1148
  if (col) {
@@ -1069,7 +1163,10 @@ function wrapKnownPackage(request, exported) {
1069
1163
  return applySink("nosql", query ?? {}, void 0, () => orig.call(this, query, ...rest));
1070
1164
  };
1071
1165
  });
1166
+ wrapNosqlWhere(q, "$where");
1072
1167
  }
1168
+ const model = exported.Model?.prototype;
1169
+ if (model) wrapNosqlWhere(model, "$where");
1073
1170
  wrappedPackages.add(obj);
1074
1171
  return exported;
1075
1172
  }
@@ -0,0 +1,313 @@
1
+ // src/runtime/dlp/index.ts
2
+ var DLP_FLAG = "FULLSHIELD_EXPERIMENTAL_DLP";
3
+ var DLP_MAX_BYTES = 64 * 1024;
4
+ var DLP_GENERIC = { error: "internal error" };
5
+ var SECRET_RES = [
6
+ /\bAKIA[0-9A-Z]{16}\b/g,
7
+ /\bsk_(?:live|test)_[A-Za-z0-9]{16,}\b/g,
8
+ /\b(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{36}\b/g,
9
+ /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/g,
10
+ /\bAIza[0-9A-Za-z_-]{35}\b/g,
11
+ /\bsk-ant-[A-Za-z0-9_-]{16,}\b|\bsk-[A-Za-z0-9_-]{20,}\b/g,
12
+ /-----BEGIN (?:[A-Z]+ )*PRIVATE KEY-----/g,
13
+ /\b(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\+srv)?|redis|amqp):\/\/[^\s'"@/:]+:[^\s'"@]+@/g
14
+ ];
15
+ var STACK_RE = /(?:Error|Exception|TypeError|ReferenceError|SyntaxError|RangeError)[^\n]{0,240}(?:\n|\r\n|\\n)[ \t]*at[ \t]+\S+[^\n]{0,320}(?:\n|\r\n|\\n)[ \t]*at[ \t]+/g;
16
+ var INTERNAL_IP_RE = /\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|127\.\d{1,3}\.\d{1,3}\.\d{1,3}|169\.254\.\d{1,3}\.\d{1,3})\b/g;
17
+ var HOST_TLDS = [".internal", ".corp", ".lan", ".local"];
18
+ var BULK_EMAILS = 5;
19
+ var PII_LEARN = 2;
20
+ var REDACT = "[REDACTED]";
21
+ var EMPTY_HITS = [];
22
+ function isDlpEnabled() {
23
+ return process.env[DLP_FLAG] === "1";
24
+ }
25
+ function allMatches(re, text) {
26
+ re.lastIndex = 0;
27
+ return [...text.matchAll(re)];
28
+ }
29
+ function maskSample(value) {
30
+ if (value.length <= 4) return "****";
31
+ return `${value.slice(0, 4)}****`.slice(0, 40);
32
+ }
33
+ function luhnOk(digits) {
34
+ if (digits.length < 13 || digits.length > 19) return false;
35
+ const first = digits.charCodeAt(0) - 48;
36
+ if (first < 3 || first > 6) return false;
37
+ if (digits.split("").every((c) => c === digits[0])) return false;
38
+ let sum = 0;
39
+ let alt = false;
40
+ for (let i = digits.length - 1; i >= 0; i -= 1) {
41
+ let n = digits.charCodeAt(i) - 48;
42
+ if (alt) {
43
+ n *= 2;
44
+ if (n > 9) n -= 9;
45
+ }
46
+ sum += n;
47
+ alt = !alt;
48
+ }
49
+ return sum % 10 === 0;
50
+ }
51
+ var SECRET_MARKERS = [
52
+ "AKIA",
53
+ "sk_",
54
+ "ghp_",
55
+ "gho_",
56
+ "ghu_",
57
+ "ghs_",
58
+ "ghr_",
59
+ "xox",
60
+ "AIza",
61
+ "sk-",
62
+ "BEGIN ",
63
+ "postgres",
64
+ "mysql",
65
+ "mariadb",
66
+ "mongodb",
67
+ "redis",
68
+ "amqp"
69
+ ];
70
+ function hasAny(text, markers) {
71
+ for (const m of markers) {
72
+ if (text.includes(m)) return true;
73
+ }
74
+ return false;
75
+ }
76
+ function collectSpans(text) {
77
+ const spans = [];
78
+ if (hasAny(text, SECRET_MARKERS)) {
79
+ for (const re of SECRET_RES) {
80
+ for (const m of allMatches(re, text)) {
81
+ if (m[0] === void 0 || m.index === void 0) continue;
82
+ if (re.source.includes("postgres") && /\$\{|^<[^>]+>$|^\{[^}]+\}$/.test(m[0])) continue;
83
+ spans.push({ start: m.index, end: m.index + m[0].length, ruleId: "DLP-SECRET-001", text: m[0] });
84
+ }
85
+ }
86
+ }
87
+ if (text.includes(" at ") || text.includes("Error") || text.includes("Exception")) {
88
+ for (const m of allMatches(STACK_RE, text)) {
89
+ if (m[0] === void 0 || m.index === void 0) continue;
90
+ spans.push({ start: m.index, end: m.index + m[0].length, ruleId: "DLP-STACK-001", text: m[0] });
91
+ }
92
+ }
93
+ if (/\d/.test(text)) {
94
+ for (const m of allMatches(INTERNAL_IP_RE, text)) {
95
+ if (m[0] === void 0 || m.index === void 0) continue;
96
+ spans.push({ start: m.index, end: m.index + m[0].length, ruleId: "DLP-INTERNAL-001", text: m[0] });
97
+ }
98
+ spans.push(...findCards(text));
99
+ }
100
+ if (text.includes("localhost") || text.includes("LOCALHOST")) {
101
+ spans.push(...findLocalhost(text));
102
+ }
103
+ if (text.includes(".internal") || text.includes(".corp") || text.includes(".lan") || text.includes(".local") || text.includes(".INTERNAL") || text.includes(".CORP") || text.includes(".LAN") || text.includes(".LOCAL")) {
104
+ spans.push(...findInternalHosts(text));
105
+ }
106
+ return spans;
107
+ }
108
+ function isWord(c) {
109
+ return c !== void 0 && /[A-Za-z0-9-]/.test(c);
110
+ }
111
+ function findIgnoreCase(hay, needle, from) {
112
+ const n = needle.length;
113
+ outer: for (let i = from; i <= hay.length - n; i += 1) {
114
+ for (let j = 0; j < n; j += 1) {
115
+ const a = hay.charCodeAt(i + j);
116
+ const b = needle.charCodeAt(j);
117
+ if (a !== b && a !== b - 32 && a !== b + 32) continue outer;
118
+ }
119
+ return i;
120
+ }
121
+ return -1;
122
+ }
123
+ function findLocalhost(text) {
124
+ const spans = [];
125
+ let from = 0;
126
+ while (from < text.length) {
127
+ const idx = findIgnoreCase(text, "localhost", from);
128
+ if (idx < 0) break;
129
+ if (!isWord(text[idx - 1]) && !isWord(text[idx + 9])) {
130
+ spans.push({ start: idx, end: idx + 9, ruleId: "DLP-INTERNAL-001", text: text.slice(idx, idx + 9) });
131
+ }
132
+ from = idx + 9;
133
+ }
134
+ return spans;
135
+ }
136
+ function findInternalHosts(text) {
137
+ const spans = [];
138
+ for (const tld of HOST_TLDS) {
139
+ let from = 0;
140
+ while (from < text.length) {
141
+ const idx = findIgnoreCase(text, tld, from);
142
+ if (idx < 0) break;
143
+ let start = idx - 1;
144
+ while (start >= 0 && /[A-Za-z0-9.-]/.test(text[start] ?? "")) start -= 1;
145
+ start += 1;
146
+ const label = text.slice(start, idx).toLowerCase();
147
+ const end = idx + tld.length;
148
+ if (label.length > 0 && /^[a-z0-9-]+(?:\.[a-z0-9-]+)*$/.test(label) && !isWord(text[end])) {
149
+ spans.push({ start, end, ruleId: "DLP-INTERNAL-001", text: text.slice(start, end) });
150
+ }
151
+ from = idx + tld.length;
152
+ }
153
+ }
154
+ return spans;
155
+ }
156
+ function findCards(text) {
157
+ const spans = [];
158
+ let i = 0;
159
+ while (i < text.length) {
160
+ const code = text.charCodeAt(i);
161
+ if (code < 48 || code > 57) {
162
+ i += 1;
163
+ continue;
164
+ }
165
+ let j = i;
166
+ let digits = "";
167
+ while (j < text.length) {
168
+ const c = text.charCodeAt(j);
169
+ if (c >= 48 && c <= 57) {
170
+ digits += text[j] ?? "";
171
+ j += 1;
172
+ } else if (c === 32 || c === 9 || c === 45) {
173
+ j += 1;
174
+ } else {
175
+ break;
176
+ }
177
+ }
178
+ if (luhnOk(digits)) {
179
+ spans.push({ start: i, end: j, ruleId: "DLP-CARD-001", text: text.slice(i, j) });
180
+ }
181
+ i = j > i ? j : i + 1;
182
+ }
183
+ return spans;
184
+ }
185
+ function findEmails(text) {
186
+ const spans = [];
187
+ let from = 0;
188
+ while (from < text.length) {
189
+ const at = text.indexOf("@", from);
190
+ if (at < 0) break;
191
+ let start = at - 1;
192
+ while (start >= 0 && /[A-Za-z0-9._%+-]/.test(text[start] ?? "")) start -= 1;
193
+ start += 1;
194
+ let end = at + 1;
195
+ while (end < text.length && /[A-Za-z0-9.-]/.test(text[end] ?? "")) end += 1;
196
+ const local = text.slice(start, at);
197
+ const domain = text.slice(at + 1, end);
198
+ const dot = domain.lastIndexOf(".");
199
+ const tld = dot > 0 ? domain.slice(dot + 1) : "";
200
+ if (local.length >= 1 && local.length <= 64 && domain.length >= 3 && domain.length <= 255 && /^[A-Za-z]{2,24}$/.test(tld)) {
201
+ spans.push({ start, end, ruleId: "DLP-PII-001", text: text.slice(start, end) });
202
+ }
203
+ from = at + 1;
204
+ }
205
+ return spans;
206
+ }
207
+ function countEmails(text) {
208
+ if (!text.includes("@")) return { count: 0, spans: [] };
209
+ const spans = findEmails(text);
210
+ return { count: spans.length, spans };
211
+ }
212
+ function redact(text, spans) {
213
+ const ordered = [...spans].sort((a, b) => b.start - a.start);
214
+ let out = text;
215
+ const taken = [];
216
+ for (const s of ordered) {
217
+ if (taken.some(([a, b]) => s.start < b && s.end > a)) continue;
218
+ taken.push([s.start, s.end]);
219
+ out = `${out.slice(0, s.start)}${REDACT}${out.slice(s.end)}`;
220
+ }
221
+ return out;
222
+ }
223
+ function uniqueHits(spans) {
224
+ const seen = /* @__PURE__ */ new Set();
225
+ const hits = [];
226
+ for (const s of spans) {
227
+ if (seen.has(s.ruleId)) continue;
228
+ seen.add(s.ruleId);
229
+ hits.push({ ruleId: s.ruleId, sample: maskSample(s.text) });
230
+ }
231
+ return hits;
232
+ }
233
+ function toText(body) {
234
+ if (body === null || body === void 0) return void 0;
235
+ if (typeof body === "string") return { text: body, kind: "string" };
236
+ if (typeof body === "number" || typeof body === "boolean") return { text: String(body), kind: "string" };
237
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(body)) return void 0;
238
+ try {
239
+ return { text: JSON.stringify(body), kind: "json" };
240
+ } catch {
241
+ return void 0;
242
+ }
243
+ }
244
+ function createDlpInspector(options = {}) {
245
+ if (!isDlpEnabled() || options.dlp === void 0) return void 0;
246
+ const mode = options.dlp.mode;
247
+ const maxBytes = options.dlp.maxBytes ?? DLP_MAX_BYTES;
248
+ const routes = /* @__PURE__ */ new Map();
249
+ let skipped = 0;
250
+ const inspect = (body, ctx) => {
251
+ const ct = ctx.contentType ?? "";
252
+ if (ct.length > 0 && !/json|text|javascript|xml/i.test(ct)) {
253
+ return { action: "allow", body, hits: [], skippedOverCap: false };
254
+ }
255
+ const serialized = toText(body);
256
+ if (serialized === void 0) {
257
+ return { action: "allow", body, hits: [], skippedOverCap: false };
258
+ }
259
+ if (serialized.text.length > maxBytes) {
260
+ skipped += 1;
261
+ return { action: "skip", body, hits: [], skippedOverCap: true };
262
+ }
263
+ const spans = collectSpans(serialized.text);
264
+ const emails = countEmails(serialized.text);
265
+ let pii = routes.get(ctx.path);
266
+ if (pii === void 0) {
267
+ pii = { samples: 0, seenLow: false, seenHigh: false };
268
+ routes.set(ctx.path, pii);
269
+ }
270
+ if (pii.samples < PII_LEARN) {
271
+ pii.samples += 1;
272
+ if (emails.count <= 1) pii.seenLow = true;
273
+ if (emails.count >= BULK_EMAILS) pii.seenHigh = true;
274
+ } else if (pii.seenLow && !pii.seenHigh && emails.count >= BULK_EMAILS) {
275
+ spans.push(...emails.spans);
276
+ }
277
+ const hits = uniqueHits(spans);
278
+ if (hits.length === 0) {
279
+ return { action: "allow", body, hits: EMPTY_HITS, skippedOverCap: false };
280
+ }
281
+ const ip = ctx.ip ?? "unknown";
282
+ for (const hit of hits) {
283
+ const event = { ruleId: hit.ruleId, path: ctx.path, ip, sample: hit.sample };
284
+ options.onEvent?.(event);
285
+ }
286
+ if (mode === "monitor") {
287
+ return { action: "allow", body, hits, skippedOverCap: false };
288
+ }
289
+ if (mode === "block") {
290
+ return { action: "block", body: { ...DLP_GENERIC }, hits, skippedOverCap: false };
291
+ }
292
+ const maskedText = redact(serialized.text, spans);
293
+ let masked = maskedText;
294
+ if (serialized.kind === "json") {
295
+ try {
296
+ masked = JSON.parse(maskedText);
297
+ } catch {
298
+ masked = maskedText;
299
+ }
300
+ }
301
+ return { action: "mask", body: masked, hits, skippedOverCap: false };
302
+ };
303
+ const api = inspect;
304
+ Object.defineProperty(api, "inspect", { value: inspect });
305
+ Object.defineProperty(api, "skippedOverCap", { get: () => skipped });
306
+ return api;
307
+ }
308
+ function applyDlpWrite(dlp, body, ctx) {
309
+ const result = dlp.inspect(body, ctx);
310
+ return { body: result.body, block: result.action === "block" };
311
+ }
312
+
313
+ export { DLP_FLAG, DLP_GENERIC, DLP_MAX_BYTES, applyDlpWrite, createDlpInspector, isDlpEnabled };