palizade 0.1.1 → 0.2.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/dist/index.cjs CHANGED
@@ -25387,6 +25387,7 @@ var capabilitySchema = external_exports.enum([
25387
25387
  "reads_untrusted_content",
25388
25388
  "reads_sensitive_data",
25389
25389
  "network_egress",
25390
+ "file_write",
25390
25391
  "writes_local",
25391
25392
  "writes_remote",
25392
25393
  "deletes_data",
@@ -25404,9 +25405,48 @@ var serverConfigSchema = external_exports.object({
25404
25405
  trust: trustSchema.default("untrusted"),
25405
25406
  toolClasses: external_exports.record(external_exports.string(), toolClassSchema).default({}),
25406
25407
  toolCapabilities: external_exports.record(external_exports.string(), external_exports.array(capabilitySchema)).default({}),
25408
+ sensitive: external_exports.boolean().default(false),
25409
+ sensitiveTools: external_exports.record(external_exports.string(), external_exports.boolean()).default({}),
25410
+ sensitivePathPatterns: external_exports.array(external_exports.string()).default([]),
25407
25411
  shell: external_exports.boolean().default(false),
25408
25412
  allowShell: external_exports.boolean().default(false)
25409
25413
  }).strict();
25414
+ var secretDetectorConfigSchema = external_exports.object({
25415
+ enabled: external_exports.boolean().default(false),
25416
+ aws: external_exports.boolean().default(true),
25417
+ generic: external_exports.boolean().default(true),
25418
+ jwt: external_exports.boolean().default(true),
25419
+ privateKey: external_exports.boolean().default(true),
25420
+ googleApiKey: external_exports.boolean().default(true),
25421
+ stripe: external_exports.boolean().default(true),
25422
+ slack: external_exports.boolean().default(true),
25423
+ github: external_exports.boolean().default(true),
25424
+ openai: external_exports.boolean().default(true)
25425
+ }).default({
25426
+ enabled: false,
25427
+ aws: true,
25428
+ generic: true,
25429
+ jwt: true,
25430
+ privateKey: true,
25431
+ googleApiKey: true,
25432
+ stripe: true,
25433
+ slack: true,
25434
+ github: true,
25435
+ openai: true
25436
+ });
25437
+ var piiDetectorConfigSchema = external_exports.object({
25438
+ enabled: external_exports.boolean().default(false),
25439
+ email: external_exports.boolean().default(true),
25440
+ ssn: external_exports.boolean().default(true),
25441
+ creditCard: external_exports.boolean().default(true),
25442
+ phone: external_exports.boolean().default(true)
25443
+ }).default({
25444
+ enabled: false,
25445
+ email: true,
25446
+ ssn: true,
25447
+ creditCard: true,
25448
+ phone: true
25449
+ });
25410
25450
  var palizadeConfigSchema = external_exports.object({
25411
25451
  stateDir: external_exports.string().default(".palizade"),
25412
25452
  policy: external_exports.string().default("policies/default.yaml"),
@@ -25429,10 +25469,22 @@ var palizadeConfigSchema = external_exports.object({
25429
25469
  model: external_exports.string().default("sinatras/Llama-Prompt-Guard-2-86M-ONNX"),
25430
25470
  cacheDir: external_exports.string().optional(),
25431
25471
  device: external_exports.string().default("cpu")
25432
- }).default({ enabled: false, model: "sinatras/Llama-Prompt-Guard-2-86M-ONNX", device: "cpu" })
25472
+ }).default({ enabled: false, model: "sinatras/Llama-Prompt-Guard-2-86M-ONNX", device: "cpu" }),
25473
+ secrets: secretDetectorConfigSchema,
25474
+ pii: piiDetectorConfigSchema
25433
25475
  }).default({
25434
25476
  heuristic: true,
25435
- promptGuard2: { enabled: false, model: "sinatras/Llama-Prompt-Guard-2-86M-ONNX", device: "cpu" }
25477
+ promptGuard2: { enabled: false, model: "sinatras/Llama-Prompt-Guard-2-86M-ONNX", device: "cpu" },
25478
+ secrets: { enabled: false, aws: true, generic: true, jwt: true, privateKey: true, googleApiKey: true, stripe: true, slack: true, github: true, openai: true },
25479
+ pii: { enabled: false, email: true, ssn: true, creditCard: true, phone: true }
25480
+ }),
25481
+ egress: external_exports.object({
25482
+ allowlist: external_exports.object({
25483
+ hosts: external_exports.array(external_exports.string()).default([]),
25484
+ emails: external_exports.array(external_exports.string()).default([])
25485
+ }).default({ hosts: [], emails: [] })
25486
+ }).default({
25487
+ allowlist: { hosts: [], emails: [] }
25436
25488
  }),
25437
25489
  transport: external_exports.object({
25438
25490
  maxMessageBytes: external_exports.number().int().min(1024).default(64 * 1024 * 1024),
@@ -25520,7 +25572,7 @@ var SOURCE_RE = /\b(read|get|fetch|search|browse|list|download|crawl|open|load|q
25520
25572
  var CAPABILITY_RULES = [
25521
25573
  [/\b(fetch|http|post|put|patch|request|webhook|url|browser|crawl)\b/iu, ["network_egress", "reads_untrusted_content"]],
25522
25574
  [/\b(email|mail|send|sms|slack|discord|message|publish)\b/iu, ["sends_message", "writes_remote", "network_egress"]],
25523
- [/\b(write|save|edit|create|move|append)\b/iu, ["writes_local"]],
25575
+ [/\b(write|save|edit|create|move|append)\b/iu, ["file_write", "writes_local"]],
25524
25576
  [/\b(delete|remove|rm|destroy)\b/iu, ["deletes_data"]],
25525
25577
  [/\b(exec|shell|run|spawn|command|script|terminal)\b/iu, ["executes_code"]],
25526
25578
  [/\b(secret|credential|token|key|env|password)\b/iu, ["accesses_credentials", "reads_sensitive_data"]],
@@ -25534,7 +25586,7 @@ function classifyToolDetailed(toolName, server, tool) {
25534
25586
  for (const capability of capabilitiesFromAnnotations(tool?.annotations)) {
25535
25587
  capabilities.add(capability);
25536
25588
  }
25537
- const searchable = `${toolName} ${tool?.title ?? ""} ${tool?.description ?? ""}`;
25589
+ const searchable = `${toolName} ${tool?.title ?? ""} ${tool?.description ?? ""}`.replace(/[_-]+/gu, " ");
25538
25590
  for (const [regex, matched] of CAPABILITY_RULES) {
25539
25591
  if (regex.test(searchable)) {
25540
25592
  matched.forEach((capability) => capabilities.add(capability));
@@ -25564,6 +25616,7 @@ function capabilitiesFromAnnotations(annotations) {
25564
25616
  const capabilities = /* @__PURE__ */ new Set();
25565
25617
  if (annotations.destructiveHint === true) {
25566
25618
  capabilities.add("writes_remote");
25619
+ capabilities.add("file_write");
25567
25620
  capabilities.add("writes_local");
25568
25621
  }
25569
25622
  if (annotations.openWorldHint === true) {
@@ -25578,6 +25631,7 @@ function capabilitiesFromAnnotations(annotations) {
25578
25631
  function deriveClass(toolName, capabilities) {
25579
25632
  if ([...capabilities].some((capability) => [
25580
25633
  "network_egress",
25634
+ "file_write",
25581
25635
  "writes_local",
25582
25636
  "writes_remote",
25583
25637
  "deletes_data",
@@ -26480,8 +26534,176 @@ function promptGuardMaliciousScore(rows) {
26480
26534
  return rows[0]?.score ?? 0;
26481
26535
  }
26482
26536
 
26537
+ // ../detectors/dist/sensitive.js
26538
+ var SECRET_RULES = [
26539
+ { label: "secret:aws-access-key-id", family: "aws", kind: "secret", score: 0.9, regex: /\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/gu },
26540
+ { label: "secret:aws-secret-key", family: "aws", kind: "secret", score: 0.95, regex: /\baws[_-]?secret[_-]?access[_-]?key\s*[:=]\s*["']?([A-Za-z0-9/+=]{40})["']?/giu },
26541
+ { label: "secret:openai", family: "openai", kind: "secret", score: 0.9, regex: /\bsk-[A-Za-z0-9]{20,}\b/gu },
26542
+ { label: "secret:github", family: "github", kind: "secret", score: 0.9, regex: /\bgh[pousr]_[A-Za-z0-9_]{30,}\b/gu },
26543
+ { label: "secret:slack", family: "slack", kind: "secret", score: 0.9, regex: /\bxox[abprs]-[A-Za-z0-9-]{20,}\b/gu },
26544
+ { label: "secret:jwt", family: "jwt", kind: "secret", score: 0.85, regex: /\beyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\b/gu },
26545
+ { label: "secret:jwt", family: "jwt", kind: "secret", score: 0.85, regex: /\bBearer\s+(eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,})\b/giu },
26546
+ { label: "secret:private-key", family: "privateKey", kind: "secret", score: 1, regex: /-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]{24,}?-----END [A-Z ]*PRIVATE KEY-----/gu },
26547
+ { label: "secret:google-api-key", family: "googleApiKey", kind: "secret", score: 0.9, regex: /\bAIza[0-9A-Za-z_-]{35}\b/gu },
26548
+ { label: "secret:stripe", family: "stripe", kind: "secret", score: 0.9, regex: /\b[sp]k_live_[0-9A-Za-z]{16,}\b/gu },
26549
+ {
26550
+ label: "secret:assignment",
26551
+ family: "generic",
26552
+ kind: "secret",
26553
+ score: 0.8,
26554
+ regex: /\b(?:password|passwd|api[_-]?key|secret|token|access[_-]?token|client[_-]?secret)\s*[:=]\s*["']?([A-Za-z0-9_./+=-]{12,})["']?/giu,
26555
+ validate: (match) => shannonEntropy(match[1] ?? "") >= 3.2
26556
+ }
26557
+ ];
26558
+ var PII_RULES = [
26559
+ { label: "pii:email", family: "email", kind: "pii", score: 0.55, regex: /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/giu },
26560
+ { label: "pii:ssn", family: "ssn", kind: "pii", score: 0.75, regex: /\b(?!000|666|9\d\d)\d{3}-(?!00)\d{2}-(?!0000)\d{4}\b/gu },
26561
+ {
26562
+ label: "pii:credit-card",
26563
+ family: "creditCard",
26564
+ kind: "pii",
26565
+ score: 0.75,
26566
+ regex: /\b(?:\d[ -]*?){13,19}\b/gu,
26567
+ validate: (match) => luhn(match[0].replace(/\D/gu, ""))
26568
+ },
26569
+ { label: "pii:phone", family: "phone", kind: "pii", score: 0.45, regex: /\b(?:\+?1[\s.-]?)?(?:\(\d{3}\)|\d{3})[\s.-]\d{3}[\s.-]\d{4}\b/gu }
26570
+ ];
26571
+ var SensitiveDataDetector = class {
26572
+ name = "sensitive";
26573
+ options;
26574
+ constructor(options = {}) {
26575
+ this.options = {
26576
+ secrets: { enabled: false, ...options.secrets },
26577
+ pii: { enabled: false, ...options.pii }
26578
+ };
26579
+ }
26580
+ detect(text, _ctx = {}) {
26581
+ if (!text.trim()) {
26582
+ return { score: 0, labels: [], spans: [], detector: this.name };
26583
+ }
26584
+ const spans = [];
26585
+ const labels = /* @__PURE__ */ new Set();
26586
+ let score = 0;
26587
+ if (this.options.secrets.enabled) {
26588
+ score = Math.max(score, this.applyRules(text, SECRET_RULES, this.options.secrets, spans, labels));
26589
+ }
26590
+ if (this.options.pii.enabled) {
26591
+ score = Math.max(score, this.applyRules(text, PII_RULES, this.options.pii, spans, labels));
26592
+ }
26593
+ return {
26594
+ score: clampScore(score),
26595
+ labels: [...labels],
26596
+ spans: mergeSensitiveSpans(spans),
26597
+ detector: this.name
26598
+ };
26599
+ }
26600
+ applyRules(text, rules, options, spans, labels) {
26601
+ let maxScore = 0;
26602
+ for (const rule of rules) {
26603
+ if (options[rule.family] === false) {
26604
+ continue;
26605
+ }
26606
+ for (const match of text.matchAll(rule.regex)) {
26607
+ if (rule.validate && !rule.validate(match)) {
26608
+ continue;
26609
+ }
26610
+ const start = match.index ?? 0;
26611
+ const end = start + match[0].length;
26612
+ labels.add(rule.label);
26613
+ spans.push({ start, end, label: rule.label });
26614
+ maxScore = Math.max(maxScore, rule.score);
26615
+ }
26616
+ }
26617
+ return maxScore;
26618
+ }
26619
+ };
26620
+ function isSecretLabel(label) {
26621
+ return label.startsWith("secret:");
26622
+ }
26623
+ function isPiiLabel(label) {
26624
+ return label.startsWith("pii:");
26625
+ }
26626
+ function hasSecretLabel(labels) {
26627
+ return labels.some(isSecretLabel);
26628
+ }
26629
+ function hasPiiLabel(labels) {
26630
+ return labels.some(isPiiLabel);
26631
+ }
26632
+ function maskSensitiveText(text, spans = []) {
26633
+ if (spans.length === 0) {
26634
+ return text;
26635
+ }
26636
+ let output2 = text;
26637
+ const sorted = [...spans].sort((left, right) => right.start - left.start || right.end - left.end);
26638
+ for (const span of sorted) {
26639
+ output2 = `${output2.slice(0, span.start)}[REDACTED:${span.label ?? "sensitive"}]${output2.slice(span.end)}`;
26640
+ }
26641
+ return output2;
26642
+ }
26643
+ function maskKnownSensitiveText(text) {
26644
+ const detector = new SensitiveDataDetector({
26645
+ secrets: { enabled: true },
26646
+ pii: { enabled: true }
26647
+ });
26648
+ const result = detector.detect(text);
26649
+ return maskSensitiveText(text, result.spans);
26650
+ }
26651
+ function mergeSensitiveSpans(spans) {
26652
+ const sorted = [...spans].sort((a, b) => a.start - b.start || a.end - b.end);
26653
+ const merged = [];
26654
+ for (const span of sorted) {
26655
+ const last = merged[merged.length - 1];
26656
+ if (!last || span.start > last.end) {
26657
+ merged.push({ ...span });
26658
+ continue;
26659
+ }
26660
+ last.end = Math.max(last.end, span.end);
26661
+ if (last.label !== span.label) {
26662
+ last.label = `${last.label ?? "sensitive"},${span.label ?? "sensitive"}`;
26663
+ }
26664
+ }
26665
+ return merged;
26666
+ }
26667
+ function shannonEntropy(input2) {
26668
+ if (!input2) {
26669
+ return 0;
26670
+ }
26671
+ const counts = /* @__PURE__ */ new Map();
26672
+ for (const char of input2) {
26673
+ counts.set(char, (counts.get(char) ?? 0) + 1);
26674
+ }
26675
+ let entropy = 0;
26676
+ for (const count of counts.values()) {
26677
+ const p = count / input2.length;
26678
+ entropy -= p * Math.log2(p);
26679
+ }
26680
+ return entropy;
26681
+ }
26682
+ function luhn(input2) {
26683
+ if (input2.length < 13 || input2.length > 19) {
26684
+ return false;
26685
+ }
26686
+ let sum = 0;
26687
+ let doubleDigit = false;
26688
+ for (let index = input2.length - 1; index >= 0; index -= 1) {
26689
+ let digit = Number(input2[index]);
26690
+ if (!Number.isInteger(digit)) {
26691
+ return false;
26692
+ }
26693
+ if (doubleDigit) {
26694
+ digit *= 2;
26695
+ if (digit > 9) {
26696
+ digit -= 9;
26697
+ }
26698
+ }
26699
+ sum += digit;
26700
+ doubleDigit = !doubleDigit;
26701
+ }
26702
+ return sum % 10 === 0;
26703
+ }
26704
+
26483
26705
  // ../policy/dist/schema.js
26484
- var actionSchema = external_exports.enum(["allow", "block", "sanitize", "redact_spans", "require_approval", "log_only"]);
26706
+ var actionSchema = external_exports.enum(["allow", "block", "sanitize", "redact_spans", "redact_secrets", "require_approval", "log_only"]);
26485
26707
  var directionSchema = external_exports.enum(["request", "response"]);
26486
26708
  var trustSchema2 = external_exports.enum(["trusted", "semi", "untrusted"]);
26487
26709
  var toolClassSchema2 = external_exports.enum(["source", "sink", "pure", "unknown"]);
@@ -26490,6 +26712,7 @@ var capabilitySchema2 = external_exports.enum([
26490
26712
  "reads_untrusted_content",
26491
26713
  "reads_sensitive_data",
26492
26714
  "network_egress",
26715
+ "file_write",
26493
26716
  "writes_local",
26494
26717
  "writes_remote",
26495
26718
  "deletes_data",
@@ -26512,7 +26735,12 @@ var policyConditionSchema = external_exports.object({
26512
26735
  capabilities_all: external_exports.array(capabilitySchema2).optional(),
26513
26736
  trust: singleOrArray(trustSchema2).optional(),
26514
26737
  taint: external_exports.boolean().optional(),
26738
+ sensitive_taint: external_exports.boolean().optional(),
26515
26739
  temporal_taint: external_exports.boolean().optional(),
26740
+ secret_detected: external_exports.boolean().optional(),
26741
+ pii_detected: external_exports.boolean().optional(),
26742
+ destination_allowed: external_exports.boolean().optional(),
26743
+ destination_allowlist_configured: external_exports.boolean().optional(),
26516
26744
  detector_score_gte: external_exports.number().min(0).max(1).optional(),
26517
26745
  detector_score_lt: external_exports.number().min(0).max(1).optional(),
26518
26746
  labels_any: external_exports.array(external_exports.string()).optional(),
@@ -26602,8 +26830,18 @@ function matchesCondition(condition, ctx) {
26602
26830
  return false;
26603
26831
  if (condition.taint !== void 0 && Boolean(ctx.taint) !== condition.taint)
26604
26832
  return false;
26833
+ if (condition.sensitive_taint !== void 0 && Boolean(ctx.sensitive_taint) !== condition.sensitive_taint)
26834
+ return false;
26605
26835
  if (condition.temporal_taint !== void 0 && Boolean(ctx.temporal_taint) !== condition.temporal_taint)
26606
26836
  return false;
26837
+ if (condition.secret_detected !== void 0 && Boolean(ctx.secret_detected) !== condition.secret_detected)
26838
+ return false;
26839
+ if (condition.pii_detected !== void 0 && Boolean(ctx.pii_detected) !== condition.pii_detected)
26840
+ return false;
26841
+ if (condition.destination_allowed !== void 0 && Boolean(ctx.destination_allowed) !== condition.destination_allowed)
26842
+ return false;
26843
+ if (condition.destination_allowlist_configured !== void 0 && Boolean(ctx.destination_allowlist_configured) !== condition.destination_allowlist_configured)
26844
+ return false;
26607
26845
  if (condition.session_quarantined !== void 0 && Boolean(ctx.session_quarantined) !== condition.session_quarantined)
26608
26846
  return false;
26609
26847
  if (condition.detector_score_gte !== void 0 && (ctx.detector_score ?? 0) < condition.detector_score_gte)
@@ -26703,14 +26941,44 @@ var InterceptionEngine = class {
26703
26941
  const toolClass = classification.toolClass;
26704
26942
  const argumentText = flattenArguments(params.arguments);
26705
26943
  const argumentFields = extractArgumentFields(params.arguments);
26944
+ const argumentBlocks = extractTextBlocks(params.arguments);
26945
+ const argumentDetections = await Promise.all(argumentBlocks.map(async (block) => ({
26946
+ block,
26947
+ detection: await this.options.detector.detect(block.text, {
26948
+ server: this.options.serverName,
26949
+ tool,
26950
+ trust: this.options.server.trust,
26951
+ surface: "argument"
26952
+ })
26953
+ })));
26954
+ const argumentDetection = fuseDetections(argumentDetections.map((entry) => entry.detection));
26955
+ const argumentDetectionsByPath = new Map(argumentDetections.map((entry) => [pathKey(entry.block), entry.detection]));
26706
26956
  const matches = this.options.taintStore.match(this.options.sessionId, argumentText, {
26707
26957
  fuzzyHammingMax: this.options.config.taint.fuzzyHammingMax
26708
26958
  });
26709
26959
  const fieldMatches = argumentFields.flatMap((field) => this.options.taintStore.match(this.options.sessionId, field.text, {
26710
26960
  fuzzyHammingMax: this.options.config.taint.fuzzyHammingMax
26711
26961
  }).map((match) => ({ field, match })));
26962
+ const sensitiveMatches = this.options.taintStore.match(this.options.sessionId, argumentText, {
26963
+ fuzzyHammingMax: this.options.config.taint.fuzzyHammingMax,
26964
+ classes: ["sensitive"]
26965
+ });
26966
+ const sensitiveFieldMatches = argumentFields.flatMap((field) => this.options.taintStore.match(this.options.sessionId, field.text, {
26967
+ fuzzyHammingMax: this.options.config.taint.fuzzyHammingMax,
26968
+ classes: ["sensitive"]
26969
+ }).map((match) => ({ field, match })));
26712
26970
  const taintedArgumentRoles = [...new Set(fieldMatches.map(({ field }) => field.role))];
26713
26971
  const temporal = matches.some((match) => match.reason === "temporal") || this.options.taintStore.hasTemporal(this.options.sessionId);
26972
+ const secretDetected = hasSecretLabel(argumentDetection.labels);
26973
+ const piiDetected = hasPiiLabel(argumentDetection.labels);
26974
+ const sensitiveTaint = sensitiveMatches.length > 0 || sensitiveFieldMatches.length > 0;
26975
+ const destination = summarizeDestinations(argumentFields, this.options.config.egress.allowlist);
26976
+ const allTaintMatches = [
26977
+ ...matches,
26978
+ ...fieldMatches.map(({ match }) => match),
26979
+ ...sensitiveMatches,
26980
+ ...sensitiveFieldMatches.map(({ match }) => match)
26981
+ ];
26714
26982
  const decision = evaluatePolicy(this.options.policy, {
26715
26983
  direction: "request",
26716
26984
  method: message.method,
@@ -26720,7 +26988,14 @@ var InterceptionEngine = class {
26720
26988
  capabilities: classification.capabilities,
26721
26989
  trust: this.options.server.trust,
26722
26990
  taint: matches.length > 0 || fieldMatches.length > 0,
26991
+ sensitive_taint: sensitiveTaint,
26723
26992
  temporal_taint: temporal,
26993
+ secret_detected: secretDetected,
26994
+ pii_detected: piiDetected,
26995
+ destination_allowed: destination.allowed,
26996
+ destination_allowlist_configured: destination.allowlistConfigured,
26997
+ detector_score: argumentDetection.score,
26998
+ labels: argumentDetection.labels,
26724
26999
  argument_text: argumentText,
26725
27000
  argument_roles: argumentRolesSummary(argumentFields),
26726
27001
  tainted_argument_roles: taintedArgumentRoles
@@ -26730,29 +27005,49 @@ var InterceptionEngine = class {
26730
27005
  tool,
26731
27006
  toolClass,
26732
27007
  classification,
26733
- taintMatches: [...matches, ...fieldMatches.map(({ match }) => match)],
27008
+ taintMatches: allTaintMatches,
26734
27009
  summary: `${tool} (${toolClass}; ${classification.capabilities.join(",") || "no capabilities"}) wants to run with ${matches.length + fieldMatches.length} taint match(es).`
26735
27010
  });
26736
27011
  await this.auditDecision(message, "request", approval.decision, startedAt, {
26737
27012
  tool,
26738
27013
  toolClass,
26739
27014
  classification,
26740
- taintMatches: [...matches, ...fieldMatches.map(({ match }) => match)],
26741
- approved: approval.approved
27015
+ detector: argumentDetection,
27016
+ taintMatches: allTaintMatches,
27017
+ approved: approval.approved,
27018
+ argumentRoles: argumentRolesSummary(argumentFields),
27019
+ taintedArgumentRoles,
27020
+ destination,
27021
+ sensitiveTaint,
27022
+ secretDetected,
27023
+ piiDetected,
27024
+ taintClasses: classesFromMatches(allTaintMatches),
27025
+ redacted: decision.action === "redact_secrets"
26742
27026
  });
26743
27027
  this.options.taintStore.consumeTurn(this.options.sessionId);
26744
27028
  if (!approval.approved || decision.action === "block") {
27029
+ const taintMatchCount = matches.length + fieldMatches.length;
26745
27030
  return {
26746
27031
  toClient: message.id === void 0 ? [] : [
26747
- makeErrorResponse(message.id, -32020, "Palizade blocked MCP tool call", {
26748
- decision,
26749
- taint: matches,
26750
- taintedArgumentRoles
26751
- })
27032
+ makeErrorResponse(message.id, -32020, formatBlockedToolCallMessage(decision), makeBlockedToolCallData(decision, taintedArgumentRoles, taintMatchCount))
26752
27033
  ],
26753
27034
  toServer: []
26754
27035
  };
26755
27036
  }
27037
+ if (decision.action === "redact_secrets") {
27038
+ const redactedArguments = applyTextTransforms(params.arguments, (text, block) => maskSensitiveText(text, argumentDetectionsByPath.get(pathKey(block))?.spans));
27039
+ this.recordPending(message, tool);
27040
+ return {
27041
+ toClient: [],
27042
+ toServer: [{
27043
+ ...message,
27044
+ params: {
27045
+ ...params,
27046
+ arguments: redactedArguments
27047
+ }
27048
+ }]
27049
+ };
27050
+ }
26756
27051
  this.recordPending(message, tool);
26757
27052
  return { toClient: [], toServer: [message] };
26758
27053
  }
@@ -26855,6 +27150,9 @@ var InterceptionEngine = class {
26855
27150
  capabilities: classification.capabilities,
26856
27151
  trust: this.options.server.trust,
26857
27152
  taint: taintRecords.length > 0,
27153
+ sensitive_taint: recordsHaveClass(taintRecords, "sensitive"),
27154
+ secret_detected: hasSecretLabel(fused.labels),
27155
+ pii_detected: hasPiiLabel(fused.labels),
26858
27156
  detector_score: fused.score,
26859
27157
  labels: fused.labels
26860
27158
  });
@@ -26873,6 +27171,10 @@ var InterceptionEngine = class {
26873
27171
  classification,
26874
27172
  detector: fused,
26875
27173
  taintIds: taintRecords.map((record2) => record2.id),
27174
+ taintClasses: classesFromRecords(taintRecords),
27175
+ sensitiveTaint: recordsHaveClass(taintRecords, "sensitive"),
27176
+ secretDetected: hasSecretLabel(fused.labels),
27177
+ piiDetected: hasPiiLabel(fused.labels),
26876
27178
  approved: approval.approved,
26877
27179
  payload: result
26878
27180
  });
@@ -26901,7 +27203,7 @@ var InterceptionEngine = class {
26901
27203
  toServer: []
26902
27204
  };
26903
27205
  }
26904
- if (decision.action === "redact_spans") {
27206
+ if (decision.action === "redact_spans" || decision.action === "redact_secrets") {
26905
27207
  return {
26906
27208
  toClient: [{
26907
27209
  ...message,
@@ -26978,8 +27280,10 @@ var InterceptionEngine = class {
26978
27280
  return { toClient: [message], toServer: [] };
26979
27281
  }
26980
27282
  registerTaint(tool, toolClass, blocks, detection) {
26981
- const shouldTaint = this.options.server.trust !== "trusted" || toolClass === "source" || detection.score >= this.options.config.taint.suspiciousScore;
26982
- if (!shouldTaint) {
27283
+ const untrusted = this.options.server.trust !== "trusted" || toolClass === "source" || detection.score >= this.options.config.taint.suspiciousScore;
27284
+ const sensitive = this.isSensitiveOrigin(tool) || hasSecretLabel(detection.labels) || hasPiiLabel(detection.labels);
27285
+ const classes = taintClasses({ untrusted, sensitive });
27286
+ if (classes.length === 0) {
26983
27287
  return [];
26984
27288
  }
26985
27289
  return blocks.filter((block) => block.text.trim().length >= 8).map((block) => this.options.taintStore.add({
@@ -26989,24 +27293,30 @@ var InterceptionEngine = class {
26989
27293
  trust: this.options.server.trust,
26990
27294
  text: block.text,
26991
27295
  detectorScore: detection.score,
26992
- labels: detection.labels
27296
+ labels: detection.labels,
27297
+ classes
26993
27298
  }));
26994
27299
  }
26995
27300
  registerTaintFromContents(sourceName, classification, contents, detection) {
26996
27301
  const highRiskSource = this.options.server.trust === "untrusted" || classification.toolClass === "source" || classification.capabilities.includes("reads_untrusted_content");
26997
- const shouldTaint = highRiskSource || detection.score >= this.options.config.taint.suspiciousScore;
26998
- if (!shouldTaint) {
26999
- return [];
27000
- }
27001
- return contents.filter((content) => content.text && content.text.trim().length >= 8 && content.kind !== "binary").map((content) => this.options.taintStore.add({
27002
- sessionId: this.options.sessionId,
27003
- sourceServer: this.options.serverName,
27004
- sourceTool: content.sourceToolOrResource ?? sourceName,
27005
- trust: this.options.server.trust,
27006
- text: content.text ?? "",
27007
- detectorScore: detection.score,
27008
- labels: detection.labels
27009
- }));
27302
+ return contents.filter((content) => content.text && content.text.trim().length >= 8 && content.kind !== "binary").flatMap((content) => {
27303
+ const untrusted = highRiskSource || detection.score >= this.options.config.taint.suspiciousScore;
27304
+ const sensitive = this.isSensitiveOrigin(sourceName, content) || hasSecretLabel(detection.labels) || hasPiiLabel(detection.labels);
27305
+ const classes = taintClasses({ untrusted, sensitive });
27306
+ if (classes.length === 0) {
27307
+ return [];
27308
+ }
27309
+ return [this.options.taintStore.add({
27310
+ sessionId: this.options.sessionId,
27311
+ sourceServer: this.options.serverName,
27312
+ sourceTool: content.sourceToolOrResource ?? sourceName,
27313
+ trust: this.options.server.trust,
27314
+ text: content.text ?? "",
27315
+ detectorScore: detection.score,
27316
+ labels: detection.labels,
27317
+ classes
27318
+ })];
27319
+ });
27010
27320
  }
27011
27321
  async handleDescriptorListResponse(message, bucket, origin, resultKey, nameOf) {
27012
27322
  const startedAt = Date.now();
@@ -27077,6 +27387,9 @@ var InterceptionEngine = class {
27077
27387
  capabilities: classification.capabilities,
27078
27388
  trust: this.options.server.trust,
27079
27389
  taint: taintRecords.length > 0,
27390
+ sensitive_taint: recordsHaveClass(taintRecords, "sensitive"),
27391
+ secret_detected: hasSecretLabel(fused.labels),
27392
+ pii_detected: hasPiiLabel(fused.labels),
27080
27393
  detector_score: fused.score,
27081
27394
  labels: fused.labels
27082
27395
  });
@@ -27093,6 +27406,10 @@ var InterceptionEngine = class {
27093
27406
  classification,
27094
27407
  detector: fused,
27095
27408
  taintIds: taintRecords.map((record2) => record2.id),
27409
+ taintClasses: classesFromRecords(taintRecords),
27410
+ sensitiveTaint: recordsHaveClass(taintRecords, "sensitive"),
27411
+ secretDetected: hasSecretLabel(fused.labels),
27412
+ piiDetected: hasPiiLabel(fused.labels),
27096
27413
  approved: approval.approved,
27097
27414
  payload: result
27098
27415
  });
@@ -27115,7 +27432,7 @@ var InterceptionEngine = class {
27115
27432
  toServer: []
27116
27433
  };
27117
27434
  }
27118
- if (decision.action === "redact_spans") {
27435
+ if (decision.action === "redact_spans" || decision.action === "redact_secrets") {
27119
27436
  return {
27120
27437
  toClient: [{
27121
27438
  ...message,
@@ -27226,7 +27543,7 @@ var InterceptionEngine = class {
27226
27543
  tool: extra.tool,
27227
27544
  direction,
27228
27545
  method: extra.method ?? (isRequest(message) ? message.method : void 0),
27229
- taint_ids: extra.taintIds ?? extra.taintMatches?.map((match) => match.taintId) ?? [],
27546
+ taint_ids: dedupePreservingOrder(extra.taintIds ?? extra.taintMatches?.map((match) => match.taintId) ?? []),
27230
27547
  detector: {
27231
27548
  score: extra.detector?.score ?? 0,
27232
27549
  labels: extra.detector?.labels ?? []
@@ -27238,13 +27555,46 @@ var InterceptionEngine = class {
27238
27555
  action: decision.action,
27239
27556
  reason: decision.reason,
27240
27557
  latency_ms: Date.now() - startedAt,
27241
- payload: extra.payload,
27558
+ payload: scrubAuditPayload(extra.payload),
27242
27559
  metadata: {
27243
27560
  toolClass: extra.toolClass,
27244
27561
  capabilities: extra.classification?.capabilities,
27245
27562
  lockChecks: extra.lockChecks,
27246
27563
  lockStatus: extra.lockStatus,
27247
- approved: extra.approved
27564
+ approved: extra.approved,
27565
+ argumentRoles: extra.argumentRoles,
27566
+ taintedArgumentRoles: extra.taintedArgumentRoles,
27567
+ taintClasses: extra.taintClasses,
27568
+ destination: extra.destination,
27569
+ sensitiveTaint: extra.sensitiveTaint,
27570
+ secretDetected: extra.secretDetected,
27571
+ piiDetected: extra.piiDetected,
27572
+ redacted: extra.redacted
27573
+ }
27574
+ });
27575
+ }
27576
+ isSensitiveOrigin(sourceName, content) {
27577
+ if (this.options.server.sensitive) {
27578
+ return true;
27579
+ }
27580
+ if (this.options.server.sensitiveTools[sourceName] === true) {
27581
+ return true;
27582
+ }
27583
+ if (content?.sourceToolOrResource && this.options.server.sensitiveTools[content.sourceToolOrResource] === true) {
27584
+ return true;
27585
+ }
27586
+ const searchable = [
27587
+ sourceName,
27588
+ content?.sourceToolOrResource,
27589
+ content?.path,
27590
+ typeof content?.rawValue === "string" ? content.rawValue : void 0
27591
+ ].filter((value) => Boolean(value));
27592
+ return this.options.server.sensitivePathPatterns.some((pattern) => {
27593
+ try {
27594
+ const regex = new RegExp(pattern, "iu");
27595
+ return searchable.some((value) => regex.test(value));
27596
+ } catch {
27597
+ return false;
27248
27598
  }
27249
27599
  });
27250
27600
  }
@@ -27252,6 +27602,111 @@ var InterceptionEngine = class {
27252
27602
  function pathKey(block) {
27253
27603
  return block.path.join(".");
27254
27604
  }
27605
+ function taintClasses(input2) {
27606
+ const classes = [];
27607
+ if (input2.untrusted) {
27608
+ classes.push("untrusted");
27609
+ }
27610
+ if (input2.sensitive) {
27611
+ classes.push("sensitive");
27612
+ }
27613
+ return classes;
27614
+ }
27615
+ function recordsHaveClass(records, taintClass) {
27616
+ return records.some((record2) => record2.classes.includes(taintClass));
27617
+ }
27618
+ function classesFromRecords(records) {
27619
+ return dedupePreservingOrder(records.flatMap((record2) => record2.classes));
27620
+ }
27621
+ function classesFromMatches(matches) {
27622
+ return dedupePreservingOrder(matches.flatMap((match) => match.classes ?? []));
27623
+ }
27624
+ function summarizeDestinations(argumentFields, allowlist) {
27625
+ const hosts = dedupePreservingOrder(argumentFields.flatMap((field) => hostsFromField(field)));
27626
+ const emails = dedupePreservingOrder(argumentFields.filter((field) => field.role === "email_recipient").map((field) => field.text.toLowerCase()));
27627
+ const allowlistConfigured = allowlist.hosts.length > 0 || allowlist.emails.length > 0;
27628
+ const hostsAllowed = hosts.every((host) => allowlist.hosts.some((entry) => matchesHost(entry, host)));
27629
+ const emailsAllowed = emails.every((email3) => allowlist.emails.some((entry) => matchesEmail(entry, email3)));
27630
+ const hasDestinations = hosts.length > 0 || emails.length > 0;
27631
+ return {
27632
+ allowed: !allowlistConfigured || !hasDestinations || hostsAllowed && emailsAllowed,
27633
+ allowlistConfigured,
27634
+ hosts,
27635
+ emailRecipients: emails.map(maskSensitiveValueForMetadata),
27636
+ destinationCount: hosts.length + emails.length
27637
+ };
27638
+ }
27639
+ function hostsFromField(field) {
27640
+ if (field.role === "hostname") {
27641
+ return [normalizeHost(field.text)].filter(Boolean);
27642
+ }
27643
+ if (field.role !== "url") {
27644
+ return [];
27645
+ }
27646
+ try {
27647
+ return [normalizeHost(new URL(field.text).hostname)].filter(Boolean);
27648
+ } catch {
27649
+ return [];
27650
+ }
27651
+ }
27652
+ function matchesHost(pattern, host) {
27653
+ const normalizedPattern = normalizeHost(pattern);
27654
+ const normalizedHost = normalizeHost(host);
27655
+ if (!normalizedPattern || !normalizedHost) {
27656
+ return false;
27657
+ }
27658
+ if (normalizedPattern === "*") {
27659
+ return true;
27660
+ }
27661
+ if (normalizedPattern.startsWith("*.")) {
27662
+ const suffix = normalizedPattern.slice(1);
27663
+ return normalizedHost.endsWith(suffix);
27664
+ }
27665
+ return normalizedPattern === normalizedHost;
27666
+ }
27667
+ function matchesEmail(pattern, email3) {
27668
+ const normalizedPattern = pattern.trim().toLowerCase();
27669
+ const normalizedEmail = email3.trim().toLowerCase();
27670
+ if (!normalizedPattern || !normalizedEmail) {
27671
+ return false;
27672
+ }
27673
+ if (normalizedPattern === "*") {
27674
+ return true;
27675
+ }
27676
+ if (normalizedPattern.startsWith("*@")) {
27677
+ return normalizedEmail.endsWith(normalizedPattern.slice(1));
27678
+ }
27679
+ if (normalizedPattern.startsWith("@")) {
27680
+ return normalizedEmail.endsWith(normalizedPattern);
27681
+ }
27682
+ return normalizedPattern === normalizedEmail;
27683
+ }
27684
+ function normalizeHost(value) {
27685
+ return value.trim().toLowerCase().replace(/\.$/u, "");
27686
+ }
27687
+ function maskSensitiveValueForMetadata(value) {
27688
+ const [local, domain2] = value.split("@");
27689
+ if (!local || !domain2) {
27690
+ return "[REDACTED:destination]";
27691
+ }
27692
+ return `${local.slice(0, 1) || "*"}***@${domain2}`;
27693
+ }
27694
+ function scrubAuditPayload(value) {
27695
+ if (value === void 0) {
27696
+ return void 0;
27697
+ }
27698
+ if (typeof value === "string") {
27699
+ return maskKnownSensitiveText(value);
27700
+ }
27701
+ if (value === null || typeof value !== "object") {
27702
+ return value;
27703
+ }
27704
+ try {
27705
+ return applyTextTransforms(value, (text) => maskKnownSensitiveText(text));
27706
+ } catch {
27707
+ return "[payload omitted: audit masking failed]";
27708
+ }
27709
+ }
27255
27710
  function worstLockStatus(checks) {
27256
27711
  if (checks.length === 0)
27257
27712
  return "unknown";
@@ -27274,6 +27729,29 @@ function descriptorName(item, fallback) {
27274
27729
  }
27275
27730
  return `${fallback}:${JSON.stringify(item).slice(0, 80)}`;
27276
27731
  }
27732
+ function formatBlockedToolCallMessage(decision) {
27733
+ const reason = trimTrailingPeriod(decision.reason);
27734
+ const rule = decision.matchedRuleId ? ` (rule: ${decision.matchedRuleId})` : "";
27735
+ return `Palizade blocked this call: ${reason}${rule}.`;
27736
+ }
27737
+ function makeBlockedToolCallData(decision, taintedArgumentRoles, taintMatchCount) {
27738
+ return {
27739
+ action: decision.action,
27740
+ rule: {
27741
+ id: decision.matchedRuleId,
27742
+ name: decision.matchedRuleName
27743
+ },
27744
+ reason: decision.reason,
27745
+ taintedArgumentRoles,
27746
+ taintMatchCount
27747
+ };
27748
+ }
27749
+ function trimTrailingPeriod(text) {
27750
+ return text.endsWith(".") ? text.slice(0, -1) : text;
27751
+ }
27752
+ function dedupePreservingOrder(values) {
27753
+ return [...new Set(values)];
27754
+ }
27277
27755
  function auditScopeId(scope, profileId, runId, sessionId) {
27278
27756
  if (scope === "process") {
27279
27757
  return `process:${sessionId}`;
@@ -27923,6 +28401,7 @@ var SqliteTaintStore = class {
27923
28401
  payload_hash text not null,
27924
28402
  detector_score real not null,
27925
28403
  labels_json text not null,
28404
+ classes_json text,
27926
28405
  fingerprint_json text not null
27927
28406
  );
27928
28407
  create index if not exists idx_taint_session on taint_records(session_id);
@@ -27938,6 +28417,7 @@ var SqliteTaintStore = class {
27938
28417
  this.ensureColumn("taint_records", "scope_id", "text");
27939
28418
  this.ensureColumn("taint_records", "run_id", "text");
27940
28419
  this.ensureColumn("taint_records", "expires_at", "text");
28420
+ this.ensureColumn("taint_records", "classes_json", "text");
27941
28421
  this.db.exec(`
27942
28422
  create index if not exists idx_taint_scope on taint_records(scope_id);
27943
28423
  create index if not exists idx_taint_expires on taint_records(expires_at);
@@ -27962,11 +28442,12 @@ var SqliteTaintStore = class {
27962
28442
  payloadHash: sha2563(input2.text),
27963
28443
  detectorScore: input2.detectorScore,
27964
28444
  labels: [...input2.labels],
28445
+ classes: normalizeClasses(input2.classes),
27965
28446
  fingerprint: makeProtectedFingerprint(input2.text, this.hmacKey)
27966
28447
  };
27967
28448
  this.db.prepare(`insert into taint_records
27968
- (id, profile_id, scope_id, run_id, session_id, source_server, source_tool, trust, created_at, expires_at, payload_hash, detector_score, labels_json, fingerprint_json)
27969
- values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record2.id, record2.profileId, record2.scopeId, record2.runId ?? null, record2.sessionId, record2.sourceServer, record2.sourceTool, record2.trust, record2.createdAt, record2.expiresAt, record2.payloadHash, record2.detectorScore, JSON.stringify(record2.labels), JSON.stringify(record2.fingerprint));
28449
+ (id, profile_id, scope_id, run_id, session_id, source_server, source_tool, trust, created_at, expires_at, payload_hash, detector_score, labels_json, classes_json, fingerprint_json)
28450
+ values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(record2.id, record2.profileId, record2.scopeId, record2.runId ?? null, record2.sessionId, record2.sourceServer, record2.sourceTool, record2.trust, record2.createdAt, record2.expiresAt, record2.payloadHash, record2.detectorScore, JSON.stringify(record2.labels), JSON.stringify(record2.classes), JSON.stringify(record2.fingerprint));
27970
28451
  return record2;
27971
28452
  }
27972
28453
  get(id) {
@@ -27984,27 +28465,34 @@ var SqliteTaintStore = class {
27984
28465
  const records = this.recordsForScope(sessionId);
27985
28466
  const matches = [];
27986
28467
  for (const record2 of records) {
28468
+ if (!matchesClassFilter(record2.classes, options.classes)) {
28469
+ continue;
28470
+ }
27987
28471
  const incomingFragments = new Set(incoming.substrings);
27988
28472
  const substring = record2.fingerprint.substrings.find((candidate) => candidate.length >= minNormalizedLength && incomingFragments.has(candidate));
27989
28473
  if (substring) {
27990
- matches.push({ taintId: record2.id, reason: "substring", token: substring });
28474
+ matches.push({ taintId: record2.id, reason: "substring", token: substring, classes: record2.classes });
27991
28475
  continue;
27992
28476
  }
27993
28477
  const token = record2.fingerprint.tokens.find((candidate) => candidate.length >= 8 && incoming.tokens.includes(candidate));
27994
28478
  if (token) {
27995
- matches.push({ taintId: record2.id, reason: "token", token });
28479
+ matches.push({ taintId: record2.id, reason: "token", token, classes: record2.classes });
27996
28480
  continue;
27997
28481
  }
27998
28482
  if (record2.fingerprint.normalized.length >= 32 && incoming.normalized.length >= 32) {
27999
28483
  const distance = hammingDistanceHex(record2.fingerprint.simhash, incoming.simhash);
28000
28484
  if (distance <= fuzzyHammingMax) {
28001
- matches.push({ taintId: record2.id, reason: "fuzzy", score: 1 - distance / 64 });
28485
+ matches.push({ taintId: record2.id, reason: "fuzzy", classes: record2.classes, score: 1 - distance / 64 });
28002
28486
  }
28003
28487
  }
28004
28488
  }
28005
28489
  for (const temporal of this.activeTemporalRows(sessionId)) {
28006
28490
  for (const taintId of JSON.parse(temporal.source_taint_ids_json)) {
28007
- matches.push({ taintId, reason: "temporal" });
28491
+ const record2 = this.get(taintId);
28492
+ const classes = record2?.classes ?? ["untrusted"];
28493
+ if (matchesClassFilter(classes, options.classes)) {
28494
+ matches.push({ taintId, reason: "temporal", classes });
28495
+ }
28008
28496
  }
28009
28497
  }
28010
28498
  return dedupeMatches(matches);
@@ -28101,9 +28589,17 @@ function rowToRecord(row) {
28101
28589
  payloadHash: row.payload_hash,
28102
28590
  detectorScore: row.detector_score,
28103
28591
  labels: JSON.parse(row.labels_json),
28592
+ classes: row.classes_json ? JSON.parse(row.classes_json) : ["untrusted"],
28104
28593
  fingerprint: JSON.parse(row.fingerprint_json)
28105
28594
  };
28106
28595
  }
28596
+ function normalizeClasses(classes) {
28597
+ const normalized = classes && classes.length > 0 ? classes : ["untrusted"];
28598
+ return [...new Set(normalized)];
28599
+ }
28600
+ function matchesClassFilter(classes, filter) {
28601
+ return !filter || filter.some((taintClass) => classes.includes(taintClass));
28602
+ }
28107
28603
  function makeProtectedFingerprint(input2, key) {
28108
28604
  const normalized = normalizeText(input2);
28109
28605
  const fragments = makeSubstrings(normalized).map((fragment) => hmacSha256Hex(key, fragment));
@@ -28194,6 +28690,12 @@ function createDetector(config2) {
28194
28690
  if (config2.detectors.heuristic) {
28195
28691
  detectors2.push(new HeuristicDetector());
28196
28692
  }
28693
+ if (config2.detectors.secrets.enabled || config2.detectors.pii.enabled) {
28694
+ detectors2.push(new SensitiveDataDetector({
28695
+ secrets: config2.detectors.secrets,
28696
+ pii: config2.detectors.pii
28697
+ }));
28698
+ }
28197
28699
  if (config2.detectors.onnxModelPath) {
28198
28700
  detectors2.push(new OptionalOnnxDetector({ modelPath: config2.detectors.onnxModelPath }));
28199
28701
  }
@@ -28401,6 +28903,15 @@ detectors:
28401
28903
  model: sinatras/Llama-Prompt-Guard-2-86M-ONNX
28402
28904
  cacheDir: .palizade/models
28403
28905
  device: cpu
28906
+ secrets:
28907
+ enabled: false
28908
+ pii:
28909
+ enabled: false
28910
+
28911
+ egress:
28912
+ allowlist:
28913
+ hosts: []
28914
+ emails: []
28404
28915
 
28405
28916
  transport:
28406
28917
  maxMessageBytes: 67108864
@@ -28432,6 +28943,9 @@ servers:
28432
28943
  read_web: source
28433
28944
  send_email: sink
28434
28945
  echo: pure
28946
+ sensitive: false
28947
+ sensitiveTools: {}
28948
+ sensitivePathPatterns: []
28435
28949
  filesystem:
28436
28950
  command: node
28437
28951
  args:
@@ -28453,6 +28967,9 @@ servers:
28453
28967
  edit_file: sink
28454
28968
  create_directory: sink
28455
28969
  move_file: sink
28970
+ sensitive: false
28971
+ sensitiveTools: {}
28972
+ sensitivePathPatterns: []
28456
28973
  `;
28457
28974
  var DEFAULT_POLICY = `version: 1
28458
28975
  defaults:
@@ -28524,7 +29041,6 @@ rules:
28524
29041
  when:
28525
29042
  direction: response
28526
29043
  method: tools/call
28527
- trust: untrusted
28528
29044
  detector_score_gte: 0.35
28529
29045
  action: sanitize
28530
29046
  reason: Untrusted tool output contains injection-like signals.
@@ -28540,6 +29056,15 @@ rules:
28540
29056
  action: sanitize
28541
29057
  reason: Resource or prompt content contains injection-like signals.
28542
29058
 
29059
+ - id: sanitize-strong-injection-any-trust
29060
+ name: Spotlight strong injection signals regardless of trust
29061
+ when:
29062
+ direction: response
29063
+ method: tools/call
29064
+ detector_score_gte: 0.75
29065
+ action: sanitize
29066
+ reason: Strong injection signal in tool output.
29067
+
28543
29068
  - id: block-tainted-sink
28544
29069
  name: Block tainted content entering sinks
28545
29070
  when:
@@ -1,3 +1,3 @@
1
- export declare const DEFAULT_CONFIG = "stateDir: .palizade\npolicy: policies/default.yaml\nlockfile: palizade.lock\n\naudit:\n jsonl: .palizade/audit.jsonl\n sqlite: .palizade/audit.sqlite\n captureRawPayloads: false\n\napprovals:\n mode: localhost\n timeoutMs: 30000\n default: deny\n\ndetectors:\n heuristic: true\n promptGuard2:\n enabled: false\n model: sinatras/Llama-Prompt-Guard-2-86M-ONNX\n cacheDir: .palizade/models\n device: cpu\n\ntransport:\n maxMessageBytes: 67108864\n maxBufferedBytes: 67108864\n allowBatches: false\n allowContentLength: false\n\ntaint:\n sqlite: .palizade/taint.sqlite\n keyPath: .palizade/taint.key\n scope: profile\n profileId: default\n ttlMs: 86400000\n suspiciousScore: 0.35\n fuzzyHammingMax: 7\n temporal:\n enabled: true\n turns: 3\n ttlMs: 300000\n detectorScoreGte: 0.55\n\nservers:\n toy:\n command: node\n args:\n - examples/toy-mcp-server/server.mjs\n trust: untrusted\n toolClasses:\n read_web: source\n send_email: sink\n echo: pure\n filesystem:\n command: node\n args:\n - node_modules/@modelcontextprotocol/server-filesystem/dist/index.js\n - .\n trust: semi\n toolClasses:\n read_file: source\n read_text_file: source\n read_media_file: source\n read_multiple_files: source\n list_directory: source\n list_directory_with_sizes: source\n directory_tree: source\n search_files: source\n get_file_info: source\n list_allowed_directories: source\n write_file: sink\n edit_file: sink\n create_directory: sink\n move_file: sink\n";
2
- export declare const DEFAULT_POLICY = "version: 1\ndefaults:\n action: allow\n on_error: block\n\nrules:\n - id: deny-server-sampling\n name: Deny server-initiated model access\n when:\n direction: request\n method: sampling/createMessage\n action: block\n reason: MCP server attempted to access the model through sampling.\n\n - id: block-poisoned-tool-metadata\n name: Block poisoned tool metadata\n when:\n direction: response\n method: tools/list\n detector_score_gte: 0.75\n action: block\n reason: Tool metadata looks like prompt injection or tool poisoning.\n\n - id: block-untrusted-unknown-tool\n name: Block unknown tools on untrusted servers\n when:\n direction: request\n method: tools/call\n trust: untrusted\n tool_class: unknown\n action: block\n reason: Unknown tools on untrusted servers must be classified explicitly.\n\n - id: approve-semi-unknown-tool\n name: Require approval for unknown tools on semi-trusted servers\n when:\n direction: request\n method: tools/call\n trust: semi\n tool_class: unknown\n action: require_approval\n reason: Unknown tools on semi-trusted servers require approval.\n\n - id: log-trusted-unknown-tool\n name: Audit unknown tools on trusted servers\n when:\n direction: request\n method: tools/call\n trust: trusted\n tool_class: unknown\n action: log_only\n reason: Unknown tool on trusted server allowed with audit logging.\n\n - id: log-unapproved-tool-metadata\n name: Surface tool lock drift\n when:\n direction: response\n method: tools/list\n lock_status:\n - missing\n - new\n - changed\n action: log_only\n reason: Tool metadata is not approved in palizade.lock.\n\n - id: sanitize-suspicious-untrusted-output\n name: Spotlight suspicious untrusted output\n when:\n direction: response\n method: tools/call\n trust: untrusted\n detector_score_gte: 0.35\n action: sanitize\n reason: Untrusted tool output contains injection-like signals.\n\n - id: sanitize-suspicious-resource-content\n name: Spotlight suspicious resource content\n when:\n direction: response\n method:\n - resources/read\n - prompts/get\n detector_score_gte: 0.35\n action: sanitize\n reason: Resource or prompt content contains injection-like signals.\n\n - id: block-tainted-sink\n name: Block tainted content entering sinks\n when:\n direction: request\n method: tools/call\n tool_class: sink\n taint: true\n action: block\n reason: Tainted content is flowing into a sink tool.\n\n - id: block-tainted-egress-destination\n name: Block tainted outbound destinations\n when:\n direction: request\n method: tools/call\n capabilities_any:\n - network_egress\n - sends_message\n tainted_argument_role_any:\n - url\n - hostname\n - email_recipient\n - http_query\n action: block\n reason: Tainted content is being used as an outbound destination or query parameter.\n\n - id: require-approval-temporal-taint-sink\n name: Require approval during temporal taint\n when:\n direction: request\n method: tools/call\n tool_class: sink\n temporal_taint: true\n action: require_approval\n reason: Recent suspicious untrusted content makes sink calls risky.\n";
1
+ export declare const DEFAULT_CONFIG = "stateDir: .palizade\npolicy: policies/default.yaml\nlockfile: palizade.lock\n\naudit:\n jsonl: .palizade/audit.jsonl\n sqlite: .palizade/audit.sqlite\n captureRawPayloads: false\n\napprovals:\n mode: localhost\n timeoutMs: 30000\n default: deny\n\ndetectors:\n heuristic: true\n promptGuard2:\n enabled: false\n model: sinatras/Llama-Prompt-Guard-2-86M-ONNX\n cacheDir: .palizade/models\n device: cpu\n secrets:\n enabled: false\n pii:\n enabled: false\n\negress:\n allowlist:\n hosts: []\n emails: []\n\ntransport:\n maxMessageBytes: 67108864\n maxBufferedBytes: 67108864\n allowBatches: false\n allowContentLength: false\n\ntaint:\n sqlite: .palizade/taint.sqlite\n keyPath: .palizade/taint.key\n scope: profile\n profileId: default\n ttlMs: 86400000\n suspiciousScore: 0.35\n fuzzyHammingMax: 7\n temporal:\n enabled: true\n turns: 3\n ttlMs: 300000\n detectorScoreGte: 0.55\n\nservers:\n toy:\n command: node\n args:\n - examples/toy-mcp-server/server.mjs\n trust: untrusted\n toolClasses:\n read_web: source\n send_email: sink\n echo: pure\n sensitive: false\n sensitiveTools: {}\n sensitivePathPatterns: []\n filesystem:\n command: node\n args:\n - node_modules/@modelcontextprotocol/server-filesystem/dist/index.js\n - .\n trust: semi\n toolClasses:\n read_file: source\n read_text_file: source\n read_media_file: source\n read_multiple_files: source\n list_directory: source\n list_directory_with_sizes: source\n directory_tree: source\n search_files: source\n get_file_info: source\n list_allowed_directories: source\n write_file: sink\n edit_file: sink\n create_directory: sink\n move_file: sink\n sensitive: false\n sensitiveTools: {}\n sensitivePathPatterns: []\n";
2
+ export declare const DEFAULT_POLICY = "version: 1\ndefaults:\n action: allow\n on_error: block\n\nrules:\n - id: deny-server-sampling\n name: Deny server-initiated model access\n when:\n direction: request\n method: sampling/createMessage\n action: block\n reason: MCP server attempted to access the model through sampling.\n\n - id: block-poisoned-tool-metadata\n name: Block poisoned tool metadata\n when:\n direction: response\n method: tools/list\n detector_score_gte: 0.75\n action: block\n reason: Tool metadata looks like prompt injection or tool poisoning.\n\n - id: block-untrusted-unknown-tool\n name: Block unknown tools on untrusted servers\n when:\n direction: request\n method: tools/call\n trust: untrusted\n tool_class: unknown\n action: block\n reason: Unknown tools on untrusted servers must be classified explicitly.\n\n - id: approve-semi-unknown-tool\n name: Require approval for unknown tools on semi-trusted servers\n when:\n direction: request\n method: tools/call\n trust: semi\n tool_class: unknown\n action: require_approval\n reason: Unknown tools on semi-trusted servers require approval.\n\n - id: log-trusted-unknown-tool\n name: Audit unknown tools on trusted servers\n when:\n direction: request\n method: tools/call\n trust: trusted\n tool_class: unknown\n action: log_only\n reason: Unknown tool on trusted server allowed with audit logging.\n\n - id: log-unapproved-tool-metadata\n name: Surface tool lock drift\n when:\n direction: response\n method: tools/list\n lock_status:\n - missing\n - new\n - changed\n action: log_only\n reason: Tool metadata is not approved in palizade.lock.\n\n - id: sanitize-suspicious-untrusted-output\n name: Spotlight suspicious untrusted output\n when:\n direction: response\n method: tools/call\n detector_score_gte: 0.35\n action: sanitize\n reason: Untrusted tool output contains injection-like signals.\n\n - id: sanitize-suspicious-resource-content\n name: Spotlight suspicious resource content\n when:\n direction: response\n method:\n - resources/read\n - prompts/get\n detector_score_gte: 0.35\n action: sanitize\n reason: Resource or prompt content contains injection-like signals.\n\n - id: sanitize-strong-injection-any-trust\n name: Spotlight strong injection signals regardless of trust\n when:\n direction: response\n method: tools/call\n detector_score_gte: 0.75\n action: sanitize\n reason: Strong injection signal in tool output.\n\n - id: block-tainted-sink\n name: Block tainted content entering sinks\n when:\n direction: request\n method: tools/call\n tool_class: sink\n taint: true\n action: block\n reason: Tainted content is flowing into a sink tool.\n\n - id: block-tainted-egress-destination\n name: Block tainted outbound destinations\n when:\n direction: request\n method: tools/call\n capabilities_any:\n - network_egress\n - sends_message\n tainted_argument_role_any:\n - url\n - hostname\n - email_recipient\n - http_query\n action: block\n reason: Tainted content is being used as an outbound destination or query parameter.\n\n - id: require-approval-temporal-taint-sink\n name: Require approval during temporal taint\n when:\n direction: request\n method: tools/call\n tool_class: sink\n temporal_taint: true\n action: require_approval\n reason: Recent suspicious untrusted content makes sink calls risky.\n";
3
3
  //# sourceMappingURL=templates.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,6kDAyE1B,CAAC;AAEF,eAAO,MAAM,cAAc,k4GAyH1B,CAAC"}
1
+ {"version":3,"file":"templates.d.ts","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,cAAc,21DAwF1B,CAAC;AAEF,eAAO,MAAM,cAAc,soHAiI1B,CAAC"}
package/dist/templates.js CHANGED
@@ -19,6 +19,15 @@ detectors:
19
19
  model: sinatras/Llama-Prompt-Guard-2-86M-ONNX
20
20
  cacheDir: .palizade/models
21
21
  device: cpu
22
+ secrets:
23
+ enabled: false
24
+ pii:
25
+ enabled: false
26
+
27
+ egress:
28
+ allowlist:
29
+ hosts: []
30
+ emails: []
22
31
 
23
32
  transport:
24
33
  maxMessageBytes: 67108864
@@ -50,6 +59,9 @@ servers:
50
59
  read_web: source
51
60
  send_email: sink
52
61
  echo: pure
62
+ sensitive: false
63
+ sensitiveTools: {}
64
+ sensitivePathPatterns: []
53
65
  filesystem:
54
66
  command: node
55
67
  args:
@@ -71,6 +83,9 @@ servers:
71
83
  edit_file: sink
72
84
  create_directory: sink
73
85
  move_file: sink
86
+ sensitive: false
87
+ sensitiveTools: {}
88
+ sensitivePathPatterns: []
74
89
  `;
75
90
  export const DEFAULT_POLICY = `version: 1
76
91
  defaults:
@@ -142,7 +157,6 @@ rules:
142
157
  when:
143
158
  direction: response
144
159
  method: tools/call
145
- trust: untrusted
146
160
  detector_score_gte: 0.35
147
161
  action: sanitize
148
162
  reason: Untrusted tool output contains injection-like signals.
@@ -158,6 +172,15 @@ rules:
158
172
  action: sanitize
159
173
  reason: Resource or prompt content contains injection-like signals.
160
174
 
175
+ - id: sanitize-strong-injection-any-trust
176
+ name: Spotlight strong injection signals regardless of trust
177
+ when:
178
+ direction: response
179
+ method: tools/call
180
+ detector_score_gte: 0.75
181
+ action: sanitize
182
+ reason: Strong injection signal in tool output.
183
+
161
184
  - id: block-tainted-sink
162
185
  name: Block tainted content entering sinks
163
186
  when:
@@ -1 +1 @@
1
- {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyE7B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyH7B,CAAC"}
1
+ {"version":3,"file":"templates.js","sourceRoot":"","sources":["../src/templates.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAwF7B,CAAC;AAEF,MAAM,CAAC,MAAM,cAAc,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiI7B,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "palizade",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "MCP-native prompt-injection firewall and security proxy.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",