@grwnd/pi-governance 1.4.1 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -3759,6 +3759,10 @@ __export(index_exports, {
3759
3759
  ConfigValidationError: () => ConfigValidationError,
3760
3760
  ConfigWatcher: () => ConfigWatcher,
3761
3761
  DANGEROUS_PATTERNS: () => DANGEROUS_PATTERNS,
3762
+ DLP_PII_PATTERNS: () => PII_PATTERNS,
3763
+ DLP_SECRET_PATTERNS: () => SECRET_PATTERNS,
3764
+ DlpMasker: () => DlpMasker,
3765
+ DlpScanner: () => DlpScanner,
3762
3766
  EnvIdentityProvider: () => EnvIdentityProvider,
3763
3767
  IdentityChain: () => IdentityChain,
3764
3768
  JsonlAuditSink: () => JsonlAuditSink,
@@ -3770,6 +3774,7 @@ __export(index_exports, {
3770
3774
  WebhookAuditSink: () => WebhookAuditSink,
3771
3775
  YamlFactStore: () => YamlFactStore,
3772
3776
  YamlPolicyEngine: () => YamlPolicyEngine,
3777
+ compareSeverity: () => compareSeverity,
3773
3778
  createApprovalFlow: () => createApprovalFlow,
3774
3779
  createIdentityChain: () => createIdentityChain,
3775
3780
  createPolicyEngine: () => createPolicyEngine,
@@ -3856,12 +3861,78 @@ var OrgUnitOverride = import_typebox.Type.Object({
3856
3861
  })
3857
3862
  )
3858
3863
  });
3864
+ var DlpMaskingConfig = import_typebox.Type.Object({
3865
+ strategy: import_typebox.Type.Union([import_typebox.Type.Literal("partial"), import_typebox.Type.Literal("full"), import_typebox.Type.Literal("hash")], {
3866
+ default: "partial"
3867
+ }),
3868
+ show_chars: import_typebox.Type.Optional(import_typebox.Type.Number({ default: 4, minimum: 0 })),
3869
+ placeholder: import_typebox.Type.Optional(import_typebox.Type.String({ default: "***" }))
3870
+ });
3871
+ var DlpCustomPatternConfig = import_typebox.Type.Object({
3872
+ name: import_typebox.Type.String(),
3873
+ pattern: import_typebox.Type.String(),
3874
+ severity: import_typebox.Type.Union([
3875
+ import_typebox.Type.Literal("low"),
3876
+ import_typebox.Type.Literal("medium"),
3877
+ import_typebox.Type.Literal("high"),
3878
+ import_typebox.Type.Literal("critical")
3879
+ ]),
3880
+ action: import_typebox.Type.Optional(
3881
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3882
+ )
3883
+ });
3884
+ var DlpAllowlistEntryConfig = import_typebox.Type.Object({
3885
+ pattern: import_typebox.Type.String()
3886
+ });
3887
+ var DlpRoleOverrideConfig = import_typebox.Type.Object({
3888
+ enabled: import_typebox.Type.Optional(import_typebox.Type.Boolean()),
3889
+ mode: import_typebox.Type.Optional(
3890
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3891
+ ),
3892
+ on_input: import_typebox.Type.Optional(
3893
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3894
+ ),
3895
+ on_output: import_typebox.Type.Optional(
3896
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3897
+ )
3898
+ });
3899
+ var DlpConfig = import_typebox.Type.Object({
3900
+ enabled: import_typebox.Type.Boolean({ default: false }),
3901
+ mode: import_typebox.Type.Optional(
3902
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")], {
3903
+ default: "audit"
3904
+ })
3905
+ ),
3906
+ on_input: import_typebox.Type.Optional(
3907
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3908
+ ),
3909
+ on_output: import_typebox.Type.Optional(
3910
+ import_typebox.Type.Union([import_typebox.Type.Literal("audit"), import_typebox.Type.Literal("mask"), import_typebox.Type.Literal("block")])
3911
+ ),
3912
+ masking: import_typebox.Type.Optional(DlpMaskingConfig),
3913
+ severity_threshold: import_typebox.Type.Optional(
3914
+ import_typebox.Type.Union(
3915
+ [import_typebox.Type.Literal("low"), import_typebox.Type.Literal("medium"), import_typebox.Type.Literal("high"), import_typebox.Type.Literal("critical")],
3916
+ { default: "low" }
3917
+ )
3918
+ ),
3919
+ built_in: import_typebox.Type.Optional(
3920
+ import_typebox.Type.Object({
3921
+ secrets: import_typebox.Type.Boolean({ default: true }),
3922
+ pii: import_typebox.Type.Boolean({ default: true })
3923
+ })
3924
+ ),
3925
+ custom_patterns: import_typebox.Type.Optional(import_typebox.Type.Array(DlpCustomPatternConfig)),
3926
+ allowlist: import_typebox.Type.Optional(import_typebox.Type.Array(DlpAllowlistEntryConfig)),
3927
+ role_overrides: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), DlpRoleOverrideConfig))
3928
+ });
3859
3929
  var GovernanceConfigSchema = import_typebox.Type.Object({
3860
3930
  auth: import_typebox.Type.Optional(AuthConfig),
3861
3931
  policy: import_typebox.Type.Optional(PolicyConfig),
3862
3932
  templates: import_typebox.Type.Optional(TemplatesConfig),
3863
3933
  hitl: import_typebox.Type.Optional(HitlConfig),
3864
3934
  audit: import_typebox.Type.Optional(AuditConfig),
3935
+ dlp: import_typebox.Type.Optional(DlpConfig),
3865
3936
  org_units: import_typebox.Type.Optional(import_typebox.Type.Record(import_typebox.Type.String(), OrgUnitOverride))
3866
3937
  });
3867
3938
 
@@ -3892,6 +3963,9 @@ var DEFAULTS = {
3892
3963
  },
3893
3964
  audit: {
3894
3965
  sinks: [{ type: "jsonl", path: "~/.pi/agent/audit.jsonl" }]
3966
+ },
3967
+ dlp: {
3968
+ enabled: false
3895
3969
  }
3896
3970
  };
3897
3971
 
@@ -4363,6 +4437,300 @@ var BashClassifier = class {
4363
4437
  }
4364
4438
  };
4365
4439
 
4440
+ // src/lib/dlp/patterns.ts
4441
+ var SECRET_PATTERNS = [
4442
+ // AWS
4443
+ {
4444
+ name: "aws_access_key",
4445
+ pattern: /\b(AKIA[0-9A-Z]{16})\b/g,
4446
+ severity: "critical",
4447
+ category: "secret"
4448
+ },
4449
+ {
4450
+ name: "aws_secret_key",
4451
+ pattern: /\b([A-Za-z0-9/+=]{40})(?=\s|$|"|')/g,
4452
+ severity: "critical",
4453
+ category: "secret"
4454
+ },
4455
+ // GitHub
4456
+ {
4457
+ name: "github_pat",
4458
+ pattern: /\b(ghp_[A-Za-z0-9]{36,})\b/g,
4459
+ severity: "critical",
4460
+ category: "secret"
4461
+ },
4462
+ {
4463
+ name: "github_oauth",
4464
+ pattern: /\b(gho_[A-Za-z0-9]{36,})\b/g,
4465
+ severity: "high",
4466
+ category: "secret"
4467
+ },
4468
+ {
4469
+ name: "github_app_token",
4470
+ pattern: /\b(ghu_[A-Za-z0-9]{36,})\b/g,
4471
+ severity: "high",
4472
+ category: "secret"
4473
+ },
4474
+ // Anthropic
4475
+ {
4476
+ name: "anthropic_api_key",
4477
+ pattern: /\b(sk-ant-api03-[A-Za-z0-9_-]{90,})\b/g,
4478
+ severity: "critical",
4479
+ category: "secret"
4480
+ },
4481
+ // OpenAI
4482
+ {
4483
+ name: "openai_api_key",
4484
+ pattern: /\b(sk-[A-Za-z0-9]{20,}T3BlbkFJ[A-Za-z0-9]{20,})\b/g,
4485
+ severity: "critical",
4486
+ category: "secret"
4487
+ },
4488
+ // JWT
4489
+ {
4490
+ name: "jwt_token",
4491
+ pattern: /\b(eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,})\b/g,
4492
+ severity: "high",
4493
+ category: "secret"
4494
+ },
4495
+ // Private key headers
4496
+ {
4497
+ name: "private_key",
4498
+ pattern: /-----BEGIN\s+(RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
4499
+ severity: "critical",
4500
+ category: "secret"
4501
+ },
4502
+ // Database connection strings
4503
+ {
4504
+ name: "database_url",
4505
+ pattern: /\b((?:postgres|mysql|mongodb|redis):\/\/[^\s'"]{10,})\b/g,
4506
+ severity: "high",
4507
+ category: "secret"
4508
+ },
4509
+ // Slack
4510
+ {
4511
+ name: "slack_token",
4512
+ pattern: /\b(xox[bpras]-[A-Za-z0-9-]{10,})\b/g,
4513
+ severity: "high",
4514
+ category: "secret"
4515
+ },
4516
+ // Stripe
4517
+ {
4518
+ name: "stripe_key",
4519
+ pattern: /\b([rs]k_(?:live|test)_[A-Za-z0-9]{20,})\b/g,
4520
+ severity: "critical",
4521
+ category: "secret"
4522
+ },
4523
+ // npm
4524
+ {
4525
+ name: "npm_token",
4526
+ pattern: /\b(npm_[A-Za-z0-9]{36,})\b/g,
4527
+ severity: "high",
4528
+ category: "secret"
4529
+ },
4530
+ // SendGrid
4531
+ {
4532
+ name: "sendgrid_key",
4533
+ pattern: /\b(SG\.[A-Za-z0-9_-]{22,}\.[A-Za-z0-9_-]{22,})\b/g,
4534
+ severity: "high",
4535
+ category: "secret"
4536
+ },
4537
+ // Generic API key patterns (env-var style assignments)
4538
+ {
4539
+ name: "generic_api_key",
4540
+ pattern: /\b(?:API_KEY|API_SECRET|ACCESS_TOKEN|AUTH_TOKEN|SECRET_KEY)\s*[=:]\s*['"]?([A-Za-z0-9_-]{16,})['"]?/gi,
4541
+ severity: "medium",
4542
+ category: "secret"
4543
+ },
4544
+ // High-entropy string near keyword context
4545
+ {
4546
+ name: "generic_secret_assignment",
4547
+ pattern: /\b(?:password|passwd|secret|token|credential)\s*[=:]\s*['"]([^'"]{8,})['"]?/gi,
4548
+ severity: "medium",
4549
+ category: "secret"
4550
+ }
4551
+ ];
4552
+ var PII_PATTERNS = [
4553
+ // SSN (US)
4554
+ {
4555
+ name: "ssn",
4556
+ pattern: /\b(\d{3}-\d{2}-\d{4})\b/g,
4557
+ severity: "critical",
4558
+ category: "pii"
4559
+ },
4560
+ // Credit card numbers
4561
+ {
4562
+ name: "credit_card",
4563
+ 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,
4564
+ severity: "critical",
4565
+ category: "pii"
4566
+ },
4567
+ // Email address
4568
+ {
4569
+ name: "email",
4570
+ pattern: /\b([A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,})\b/g,
4571
+ severity: "low",
4572
+ category: "pii"
4573
+ },
4574
+ // US phone number
4575
+ {
4576
+ name: "phone_us",
4577
+ pattern: /\b(\+?1?[-.\s]?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4})\b/g,
4578
+ severity: "medium",
4579
+ category: "pii"
4580
+ },
4581
+ // IPv4 address
4582
+ {
4583
+ name: "ipv4",
4584
+ 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,
4585
+ severity: "low",
4586
+ category: "pii"
4587
+ }
4588
+ ];
4589
+
4590
+ // src/lib/dlp/scanner.ts
4591
+ var SEVERITY_ORDER = {
4592
+ low: 0,
4593
+ medium: 1,
4594
+ high: 2,
4595
+ critical: 3
4596
+ };
4597
+ var DlpScanner = class {
4598
+ patterns;
4599
+ allowlistRegexps;
4600
+ severityThreshold;
4601
+ config;
4602
+ constructor(config) {
4603
+ this.config = config;
4604
+ this.severityThreshold = SEVERITY_ORDER[config.severity_threshold];
4605
+ this.patterns = [];
4606
+ this.allowlistRegexps = [];
4607
+ if (config.built_in.secrets) {
4608
+ for (const def of SECRET_PATTERNS) {
4609
+ this.patterns.push({ def });
4610
+ }
4611
+ }
4612
+ if (config.built_in.pii) {
4613
+ for (const def of PII_PATTERNS) {
4614
+ this.patterns.push({ def });
4615
+ }
4616
+ }
4617
+ for (const cp of config.custom_patterns) {
4618
+ const def = {
4619
+ name: cp.name,
4620
+ pattern: new RegExp(cp.pattern, "g"),
4621
+ severity: cp.severity,
4622
+ category: "custom"
4623
+ };
4624
+ this.patterns.push({ def, action: cp.action });
4625
+ }
4626
+ for (const compiled of this.patterns) {
4627
+ const override = config.pattern_overrides.get(compiled.def.name);
4628
+ if (override) {
4629
+ compiled.action = override;
4630
+ }
4631
+ }
4632
+ for (const entry of config.allowlist) {
4633
+ this.allowlistRegexps.push(new RegExp(entry.pattern));
4634
+ }
4635
+ }
4636
+ scan(text) {
4637
+ if (!this.config.enabled || text.length === 0) {
4638
+ return { hasMatches: false, matches: [] };
4639
+ }
4640
+ const matches = [];
4641
+ for (const compiled of this.patterns) {
4642
+ if (SEVERITY_ORDER[compiled.def.severity] < this.severityThreshold) {
4643
+ continue;
4644
+ }
4645
+ const regex = new RegExp(compiled.def.pattern.source, compiled.def.pattern.flags);
4646
+ let match;
4647
+ while ((match = regex.exec(text)) !== null) {
4648
+ const matched = match[1] ?? match[0];
4649
+ const start = match[1] ? match.index + match[0].indexOf(match[1]) : match.index;
4650
+ const end = start + matched.length;
4651
+ if (this.isAllowlisted(matched)) {
4652
+ continue;
4653
+ }
4654
+ matches.push({
4655
+ patternName: compiled.def.name,
4656
+ category: compiled.def.category,
4657
+ severity: compiled.def.severity,
4658
+ start,
4659
+ end,
4660
+ matched
4661
+ });
4662
+ }
4663
+ }
4664
+ return { hasMatches: matches.length > 0, matches };
4665
+ }
4666
+ getAction(direction) {
4667
+ if (direction === "input" && this.config.on_input) {
4668
+ return this.config.on_input;
4669
+ }
4670
+ if (direction === "output" && this.config.on_output) {
4671
+ return this.config.on_output;
4672
+ }
4673
+ return this.config.mode;
4674
+ }
4675
+ getPatternAction(match, direction) {
4676
+ const compiled = this.patterns.find((p) => p.def.name === match.patternName);
4677
+ if (compiled?.action) {
4678
+ return compiled.action;
4679
+ }
4680
+ return this.getAction(direction);
4681
+ }
4682
+ isAllowlisted(value) {
4683
+ for (const re of this.allowlistRegexps) {
4684
+ if (re.test(value)) return true;
4685
+ }
4686
+ return false;
4687
+ }
4688
+ };
4689
+ function compareSeverity(a, b) {
4690
+ return SEVERITY_ORDER[a] - SEVERITY_ORDER[b];
4691
+ }
4692
+
4693
+ // src/lib/dlp/masker.ts
4694
+ var import_node_crypto = require("crypto");
4695
+ var DEFAULT_CONFIG = {
4696
+ strategy: "partial",
4697
+ show_chars: 4,
4698
+ placeholder: "***"
4699
+ };
4700
+ var DlpMasker = class {
4701
+ config;
4702
+ constructor(config) {
4703
+ this.config = { ...DEFAULT_CONFIG, ...config };
4704
+ }
4705
+ maskValue(value) {
4706
+ switch (this.config.strategy) {
4707
+ case "full":
4708
+ return this.config.placeholder;
4709
+ case "hash": {
4710
+ const hash = (0, import_node_crypto.createHash)("sha256").update(value).digest("hex").slice(0, 8);
4711
+ return `[REDACTED:${hash}]`;
4712
+ }
4713
+ case "partial":
4714
+ default: {
4715
+ if (value.length <= this.config.show_chars) {
4716
+ return this.config.placeholder;
4717
+ }
4718
+ return this.config.placeholder + value.slice(-this.config.show_chars);
4719
+ }
4720
+ }
4721
+ }
4722
+ maskText(text, matches) {
4723
+ if (matches.length === 0) return text;
4724
+ const sorted = [...matches].sort((a, b) => b.start - a.start);
4725
+ let result = text;
4726
+ for (const match of sorted) {
4727
+ const masked = this.maskValue(match.matched);
4728
+ result = result.slice(0, match.start) + masked + result.slice(match.end);
4729
+ }
4730
+ return result;
4731
+ }
4732
+ };
4733
+
4366
4734
  // src/lib/budget/tracker.ts
4367
4735
  var BudgetTracker = class {
4368
4736
  _used = 0;
@@ -4488,7 +4856,7 @@ function render(templateContent, variables) {
4488
4856
  }
4489
4857
 
4490
4858
  // src/lib/audit/logger.ts
4491
- var import_node_crypto = require("crypto");
4859
+ var import_node_crypto2 = require("crypto");
4492
4860
 
4493
4861
  // src/lib/audit/sinks/jsonl.ts
4494
4862
  var import_promises = require("fs/promises");
@@ -4585,7 +4953,7 @@ var AuditLogger = class {
4585
4953
  async log(record) {
4586
4954
  const full = {
4587
4955
  ...record,
4588
- id: (0, import_node_crypto.randomUUID)(),
4956
+ id: (0, import_node_crypto2.randomUUID)(),
4589
4957
  timestamp: (/* @__PURE__ */ new Date()).toISOString()
4590
4958
  };
4591
4959
  this.counts.set(full.event, (this.counts.get(full.event) ?? 0) + 1);
@@ -4709,6 +5077,10 @@ function createApprovalFlow(config, ui) {
4709
5077
  ConfigValidationError,
4710
5078
  ConfigWatcher,
4711
5079
  DANGEROUS_PATTERNS,
5080
+ DLP_PII_PATTERNS,
5081
+ DLP_SECRET_PATTERNS,
5082
+ DlpMasker,
5083
+ DlpScanner,
4712
5084
  EnvIdentityProvider,
4713
5085
  IdentityChain,
4714
5086
  JsonlAuditSink,
@@ -4720,6 +5092,7 @@ function createApprovalFlow(config, ui) {
4720
5092
  WebhookAuditSink,
4721
5093
  YamlFactStore,
4722
5094
  YamlPolicyEngine,
5095
+ compareSeverity,
4723
5096
  createApprovalFlow,
4724
5097
  createIdentityChain,
4725
5098
  createPolicyEngine,