@akasecurity/cli 0.9.0-rc1 → 0.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. package/README.md +3 -3
  2. package/dist/cli.js +2559 -546
  3. package/package.json +4 -4
  4. package/web-ui/web-ui/.next/BUILD_ID +1 -1
  5. package/web-ui/web-ui/.next/app-build-manifest.json +58 -58
  6. package/web-ui/web-ui/.next/app-path-routes-manifest.json +7 -7
  7. package/web-ui/web-ui/.next/build-manifest.json +2 -2
  8. package/web-ui/web-ui/.next/prerender-manifest.json +3 -3
  9. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js +2 -2
  10. package/web-ui/web-ui/.next/server/app/(app)/activity/page.js.nft.json +1 -1
  11. package/web-ui/web-ui/.next/server/app/(app)/activity/page_client-reference-manifest.js +1 -1
  12. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js +2 -2
  13. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page.js.nft.json +1 -1
  14. package/web-ui/web-ui/.next/server/app/(app)/data-shares/page_client-reference-manifest.js +1 -1
  15. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js +2 -2
  16. package/web-ui/web-ui/.next/server/app/(app)/detections/page.js.nft.json +1 -1
  17. package/web-ui/web-ui/.next/server/app/(app)/detections/page_client-reference-manifest.js +1 -1
  18. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js +2 -2
  19. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page.js.nft.json +1 -1
  20. package/web-ui/web-ui/.next/server/app/(app)/exceptions/[id]/page_client-reference-manifest.js +1 -1
  21. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js +2 -2
  22. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page.js.nft.json +1 -1
  23. package/web-ui/web-ui/.next/server/app/(app)/exceptions/new/page_client-reference-manifest.js +1 -1
  24. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js +1 -1
  25. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page.js.nft.json +1 -1
  26. package/web-ui/web-ui/.next/server/app/(app)/exceptions/page_client-reference-manifest.js +1 -1
  27. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js +2 -2
  28. package/web-ui/web-ui/.next/server/app/(app)/findings/page.js.nft.json +1 -1
  29. package/web-ui/web-ui/.next/server/app/(app)/findings/page_client-reference-manifest.js +1 -1
  30. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js +1 -1
  31. package/web-ui/web-ui/.next/server/app/(app)/inventory/page.js.nft.json +1 -1
  32. package/web-ui/web-ui/.next/server/app/(app)/inventory/page_client-reference-manifest.js +1 -1
  33. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js +2 -2
  34. package/web-ui/web-ui/.next/server/app/(app)/policies/page.js.nft.json +1 -1
  35. package/web-ui/web-ui/.next/server/app/(app)/policies/page_client-reference-manifest.js +1 -1
  36. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js +1 -1
  37. package/web-ui/web-ui/.next/server/app/(app)/scan/page.js.nft.json +1 -1
  38. package/web-ui/web-ui/.next/server/app/(app)/scan/page_client-reference-manifest.js +1 -1
  39. package/web-ui/web-ui/.next/server/app/(app)/security/page.js +1 -1
  40. package/web-ui/web-ui/.next/server/app/(app)/security/page.js.nft.json +1 -1
  41. package/web-ui/web-ui/.next/server/app/(app)/security/page_client-reference-manifest.js +1 -1
  42. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js +2 -2
  43. package/web-ui/web-ui/.next/server/app/(app)/settings/page.js.nft.json +1 -1
  44. package/web-ui/web-ui/.next/server/app/(app)/settings/page_client-reference-manifest.js +1 -1
  45. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js +2 -2
  46. package/web-ui/web-ui/.next/server/app/(app)/updates/page.js.nft.json +1 -1
  47. package/web-ui/web-ui/.next/server/app/(app)/updates/page_client-reference-manifest.js +1 -1
  48. package/web-ui/web-ui/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  49. package/web-ui/web-ui/.next/server/app/_not-found.html +1 -1
  50. package/web-ui/web-ui/.next/server/app/_not-found.rsc +2 -2
  51. package/web-ui/web-ui/.next/server/app/index.html +1 -1
  52. package/web-ui/web-ui/.next/server/app/index.rsc +2 -2
  53. package/web-ui/web-ui/.next/server/app/page_client-reference-manifest.js +1 -1
  54. package/web-ui/web-ui/.next/server/app-paths-manifest.json +7 -7
  55. package/web-ui/web-ui/.next/server/chunks/{366.js → 244.js} +92 -25
  56. package/web-ui/web-ui/.next/server/chunks/264.js +1 -0
  57. package/web-ui/web-ui/.next/server/chunks/291.js +647 -0
  58. package/web-ui/web-ui/.next/server/chunks/45.js +1 -1
  59. package/web-ui/web-ui/.next/server/chunks/461.js +5 -5
  60. package/web-ui/web-ui/.next/server/chunks/773.js +4 -4
  61. package/web-ui/web-ui/.next/server/chunks/983.js +2 -0
  62. package/web-ui/web-ui/.next/server/functions-config-manifest.json +5 -5
  63. package/web-ui/web-ui/.next/server/middleware-manifest.json +5 -5
  64. package/web-ui/web-ui/.next/server/pages/404.html +1 -1
  65. package/web-ui/web-ui/.next/server/pages/500.html +1 -1
  66. package/web-ui/web-ui/.next/server/server-reference-manifest.js +1 -1
  67. package/web-ui/web-ui/.next/server/server-reference-manifest.json +1 -1
  68. package/web-ui/web-ui/.next/static/chunks/{24-55b96836aea5ce23.js → 24-d36fb6bd72e29537.js} +2 -2
  69. package/web-ui/web-ui/.next/static/chunks/561-52ad6ae6bf64b471.js +1 -0
  70. package/web-ui/web-ui/.next/static/chunks/app/(app)/activity/{page-0c208c8b275a69ac.js → page-797e2ce43b8db7e1.js} +1 -1
  71. package/web-ui/web-ui/.next/static/chunks/app/(app)/data-shares/{page-041187c76bd42461.js → page-d9e9023871035a2d.js} +1 -1
  72. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-5bd8e04955058e46.js +1 -0
  73. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-a254c9fc7cc27b97.js +1 -0
  74. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/new/{page-2cfe42c3dd4833c3.js → page-622151ff8e6960c8.js} +1 -1
  75. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-c1221106bd6ba356.js +1 -0
  76. package/web-ui/web-ui/.next/static/chunks/app/(app)/findings/{page-d33f3999e89c7e77.js → page-fc68b0fb9b669a03.js} +1 -1
  77. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-68418378cfb10a0f.js +1 -0
  78. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-2b9ea3e95dbe82fb.js +1 -0
  79. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-315b8b57329d5eb6.js +1 -0
  80. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-77adb595bb447040.js +1 -0
  81. package/web-ui/web-ui/.next/static/chunks/app/{layout-40fea3e270a8e120.js → layout-8b19b03146218dc7.js} +1 -1
  82. package/web-ui/web-ui/.next/static/css/{43f95c94ecc1bd17.css → 500a1c2bf0fc64fb.css} +1 -1
  83. package/web-ui/web-ui/.next/server/chunks/484.js +0 -580
  84. package/web-ui/web-ui/.next/server/chunks/900.js +0 -1
  85. package/web-ui/web-ui/.next/server/chunks/918.js +0 -2
  86. package/web-ui/web-ui/.next/static/chunks/561-c3388f066f813e17.js +0 -1
  87. package/web-ui/web-ui/.next/static/chunks/app/(app)/detections/page-481fedcbfb474cef.js +0 -1
  88. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/[id]/page-a20e7d7a2f0ad507.js +0 -1
  89. package/web-ui/web-ui/.next/static/chunks/app/(app)/exceptions/page-849d66cc2cd77cb6.js +0 -1
  90. package/web-ui/web-ui/.next/static/chunks/app/(app)/inventory/page-76edd10592ba3cb1.js +0 -1
  91. package/web-ui/web-ui/.next/static/chunks/app/(app)/scan/page-50b604f229747240.js +0 -1
  92. package/web-ui/web-ui/.next/static/chunks/app/(app)/settings/page-92d1086c8e8ed3bf.js +0 -1
  93. package/web-ui/web-ui/.next/static/chunks/app/(app)/updates/page-c3c1671d32547619.js +0 -1
  94. /package/web-ui/web-ui/.next/static/{unUYwnPVSRwxtrUFgSAxr → ez8SLWYcUCScGuFekZ-G_}/_buildManifest.js +0 -0
  95. /package/web-ui/web-ui/.next/static/{unUYwnPVSRwxtrUFgSAxr → ez8SLWYcUCScGuFekZ-G_}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -750,18 +750,1420 @@ function installAgentPlugin(agentId, mode = "capture") {
750
750
  return run("claude", ["plugin", "install", resolved.ref], mode);
751
751
  }
752
752
 
753
- // ../packages/local-ops/src/fs-scan.ts
754
- import { createHash, randomUUID } from "crypto";
755
- import { readdirSync, readFileSync as readFileSync2, statSync } from "fs";
756
- import { join as join2, relative, sep } from "path";
753
+ // ../packages/local-ops/src/egress-record.ts
754
+ import { statSync as statSync4 } from "fs";
755
+ import { dirname as dirname4, relative as relative2, resolve } from "path";
756
+
757
+ // ../packages/detections/src/egress/registry.ts
758
+ var EXTRACTOR_VERSION = "1";
759
+ var PROVIDER_REGISTRY = [
760
+ {
761
+ id: "stripe",
762
+ name: "Stripe",
763
+ category: "Payments",
764
+ hostSuffixes: ["stripe.com"],
765
+ apiBase: "https://api.stripe.com",
766
+ defaultDataClasses: ["pii", "customer"],
767
+ sdks: {
768
+ npm: ["stripe"],
769
+ pypi: ["stripe"],
770
+ go: ["github.com/stripe/stripe-go"],
771
+ maven: ["com.stripe"],
772
+ rubygems: ["stripe"],
773
+ composer: ["stripe/stripe-php"],
774
+ nuget: ["Stripe.net"]
775
+ }
776
+ },
777
+ {
778
+ id: "datadog",
779
+ name: "Datadog",
780
+ category: "Observability",
781
+ hostSuffixes: ["datadoghq.com", "datadoghq.eu"],
782
+ apiBase: "https://api.datadoghq.com",
783
+ defaultDataClasses: ["telemetry", "logs", "metrics"],
784
+ sdks: {
785
+ npm: ["dd-trace", "@datadog/browser-logs"],
786
+ pypi: ["datadog", "ddtrace"],
787
+ go: ["github.com/DataDog/dd-trace-go"],
788
+ maven: ["com.datadoghq"],
789
+ rubygems: ["ddtrace", "dogapi"],
790
+ nuget: ["Datadog.Trace"]
791
+ }
792
+ },
793
+ {
794
+ id: "newrelic",
795
+ name: "New Relic",
796
+ category: "Observability",
797
+ hostSuffixes: ["newrelic.com", "nr-data.net"],
798
+ apiBase: "https://api.newrelic.com",
799
+ defaultDataClasses: ["telemetry", "logs", "metrics"],
800
+ sdks: {
801
+ npm: ["newrelic"],
802
+ pypi: ["newrelic"],
803
+ go: ["github.com/newrelic/go-agent"],
804
+ maven: ["com.newrelic.agent.java"],
805
+ rubygems: ["newrelic_rpm"],
806
+ nuget: ["NewRelic.Agent"]
807
+ }
808
+ },
809
+ {
810
+ id: "sentry",
811
+ name: "Sentry",
812
+ category: "Error tracking",
813
+ hostSuffixes: ["sentry.io"],
814
+ apiBase: "https://sentry.io",
815
+ defaultDataClasses: ["source", "telemetry"],
816
+ sdks: {
817
+ npm: ["@sentry/node", "@sentry/react", "@sentry/nextjs"],
818
+ pypi: ["sentry-sdk"],
819
+ go: ["github.com/getsentry/sentry-go"],
820
+ maven: ["io.sentry"],
821
+ rubygems: ["sentry-ruby"],
822
+ cargo: ["sentry"],
823
+ composer: ["sentry/sentry"],
824
+ nuget: ["Sentry"]
825
+ }
826
+ },
827
+ {
828
+ id: "openai",
829
+ name: "OpenAI",
830
+ category: "LLM provider",
831
+ hostSuffixes: ["openai.com"],
832
+ apiBase: "https://api.openai.com",
833
+ defaultDataClasses: ["pii", "source"],
834
+ sdks: {
835
+ npm: ["openai"],
836
+ pypi: ["openai"],
837
+ go: ["github.com/sashabaranov/go-openai"],
838
+ maven: ["com.openai"],
839
+ rubygems: ["ruby-openai"],
840
+ cargo: ["async-openai"],
841
+ composer: ["openai-php/client"],
842
+ nuget: ["OpenAI"]
843
+ }
844
+ },
845
+ {
846
+ id: "anthropic",
847
+ name: "Anthropic",
848
+ category: "LLM provider",
849
+ hostSuffixes: ["anthropic.com"],
850
+ apiBase: "https://api.anthropic.com",
851
+ defaultDataClasses: ["pii", "source"],
852
+ sdks: {
853
+ npm: ["@anthropic-ai/sdk"],
854
+ pypi: ["anthropic"],
855
+ go: ["github.com/anthropics/anthropic-sdk-go"],
856
+ nuget: ["Anthropic.SDK"]
857
+ }
858
+ },
859
+ {
860
+ id: "aws",
861
+ name: "Amazon Web Services",
862
+ category: "Cloud platform",
863
+ hostSuffixes: ["amazonaws.com"],
864
+ apiBase: "https://s3.amazonaws.com",
865
+ defaultDataClasses: ["secrets", "customer"],
866
+ sdks: {
867
+ npm: ["@aws-sdk/client-s3", "aws-sdk"],
868
+ pypi: ["boto3"],
869
+ go: ["github.com/aws/aws-sdk-go", "github.com/aws/aws-sdk-go-v2"],
870
+ maven: ["com.amazonaws", "software.amazon.awssdk"],
871
+ rubygems: ["aws-sdk-s3"],
872
+ cargo: ["aws-sdk-s3"],
873
+ nuget: ["AWSSDK.S3"]
874
+ }
875
+ },
876
+ {
877
+ id: "gcp",
878
+ name: "Google Cloud",
879
+ category: "Cloud platform",
880
+ hostSuffixes: ["googleapis.com"],
881
+ apiBase: "https://storage.googleapis.com",
882
+ defaultDataClasses: ["customer", "logs"],
883
+ sdks: {
884
+ npm: ["@google-cloud/storage"],
885
+ pypi: ["google-cloud-storage"],
886
+ go: ["cloud.google.com/go"],
887
+ maven: ["com.google.cloud"],
888
+ rubygems: ["google-cloud-storage"],
889
+ nuget: ["Google.Cloud.Storage.V1"]
890
+ }
891
+ },
892
+ {
893
+ id: "azure",
894
+ name: "Microsoft Azure",
895
+ category: "Cloud platform",
896
+ hostSuffixes: ["azure.com", "windows.net"],
897
+ apiBase: "https://management.azure.com",
898
+ defaultDataClasses: ["customer", "logs"],
899
+ sdks: {
900
+ npm: ["@azure/storage-blob"],
901
+ pypi: ["azure-storage-blob"],
902
+ go: ["github.com/Azure/azure-sdk-for-go"],
903
+ maven: ["com.azure"],
904
+ rubygems: ["azure-storage-blob"],
905
+ nuget: ["Azure.Storage.Blobs"]
906
+ }
907
+ },
908
+ {
909
+ id: "slack",
910
+ name: "Slack",
911
+ category: "Notifications",
912
+ hostSuffixes: ["slack.com"],
913
+ apiBase: "https://slack.com/api",
914
+ defaultDataClasses: ["logs"],
915
+ sdks: {
916
+ npm: ["@slack/web-api"],
917
+ pypi: ["slack-sdk"],
918
+ go: ["github.com/slack-go/slack"],
919
+ maven: ["com.slack.api"],
920
+ rubygems: ["slack-ruby-client"],
921
+ composer: ["slack-php/slack-api"],
922
+ nuget: ["SlackNet"]
923
+ }
924
+ },
925
+ {
926
+ id: "segment",
927
+ name: "Segment",
928
+ category: "Analytics",
929
+ hostSuffixes: ["segment.io", "segment.com"],
930
+ apiBase: "https://api.segment.io",
931
+ defaultDataClasses: ["customer"],
932
+ sdks: {
933
+ npm: ["@segment/analytics-node", "analytics-node"],
934
+ pypi: ["segment-analytics-python"],
935
+ go: ["github.com/segmentio/analytics-go"],
936
+ maven: ["com.segment.analytics.java"],
937
+ rubygems: ["analytics-ruby"],
938
+ nuget: ["Analytics"]
939
+ }
940
+ },
941
+ {
942
+ id: "twilio",
943
+ name: "Twilio",
944
+ category: "Communications",
945
+ hostSuffixes: ["twilio.com"],
946
+ apiBase: "https://api.twilio.com",
947
+ defaultDataClasses: ["pii", "customer"],
948
+ sdks: {
949
+ npm: ["twilio"],
950
+ pypi: ["twilio"],
951
+ go: ["github.com/twilio/twilio-go"],
952
+ maven: ["com.twilio.sdk"],
953
+ rubygems: ["twilio-ruby"],
954
+ composer: ["twilio/sdk"],
955
+ nuget: ["Twilio"]
956
+ }
957
+ },
958
+ {
959
+ id: "sendgrid",
960
+ name: "SendGrid",
961
+ category: "Email",
962
+ hostSuffixes: ["sendgrid.com"],
963
+ apiBase: "https://api.sendgrid.com",
964
+ defaultDataClasses: ["pii"],
965
+ sdks: {
966
+ npm: ["@sendgrid/mail"],
967
+ pypi: ["sendgrid"],
968
+ go: ["github.com/sendgrid/sendgrid-go"],
969
+ maven: ["com.sendgrid"],
970
+ rubygems: ["sendgrid-ruby"],
971
+ composer: ["sendgrid/sendgrid"],
972
+ nuget: ["SendGrid"]
973
+ }
974
+ },
975
+ {
976
+ id: "mailgun",
977
+ name: "Mailgun",
978
+ category: "Email",
979
+ hostSuffixes: ["mailgun.net"],
980
+ apiBase: "https://api.mailgun.net",
981
+ defaultDataClasses: ["pii"],
982
+ sdks: {
983
+ npm: ["mailgun.js"],
984
+ pypi: ["mailgun"],
985
+ rubygems: ["mailgun-ruby"],
986
+ composer: ["mailgun/mailgun-php"],
987
+ nuget: ["Mailgun"]
988
+ }
989
+ },
990
+ {
991
+ id: "mixpanel",
992
+ name: "Mixpanel",
993
+ category: "Analytics",
994
+ hostSuffixes: ["mixpanel.com"],
995
+ apiBase: "https://api.mixpanel.com",
996
+ defaultDataClasses: ["customer", "telemetry"],
997
+ sdks: {
998
+ npm: ["mixpanel"],
999
+ pypi: ["mixpanel"],
1000
+ rubygems: ["mixpanel-ruby"],
1001
+ nuget: ["Mixpanel"]
1002
+ }
1003
+ },
1004
+ {
1005
+ id: "amplitude",
1006
+ name: "Amplitude",
1007
+ category: "Analytics",
1008
+ hostSuffixes: ["amplitude.com"],
1009
+ apiBase: "https://api2.amplitude.com",
1010
+ defaultDataClasses: ["customer", "telemetry"],
1011
+ sdks: {
1012
+ npm: ["@amplitude/analytics-node"],
1013
+ pypi: ["amplitude-analytics"],
1014
+ nuget: ["Amplitude"]
1015
+ }
1016
+ },
1017
+ {
1018
+ id: "posthog",
1019
+ name: "PostHog",
1020
+ category: "Analytics",
1021
+ hostSuffixes: ["posthog.com"],
1022
+ apiBase: "https://us.i.posthog.com",
1023
+ defaultDataClasses: ["customer", "telemetry"],
1024
+ sdks: {
1025
+ npm: ["posthog-node", "posthog-js"],
1026
+ pypi: ["posthog"],
1027
+ go: ["github.com/posthog/posthog-go"],
1028
+ rubygems: ["posthog-ruby"],
1029
+ composer: ["posthog/posthog-php"],
1030
+ nuget: ["PostHog"]
1031
+ }
1032
+ },
1033
+ {
1034
+ id: "honeycomb",
1035
+ name: "Honeycomb",
1036
+ category: "Observability",
1037
+ hostSuffixes: ["honeycomb.io"],
1038
+ apiBase: "https://api.honeycomb.io",
1039
+ defaultDataClasses: ["telemetry", "metrics"],
1040
+ sdks: {
1041
+ npm: ["libhoney"],
1042
+ pypi: ["libhoney"],
1043
+ go: ["github.com/honeycombio/libhoney-go"],
1044
+ rubygems: ["libhoney"]
1045
+ }
1046
+ },
1047
+ {
1048
+ id: "grafana",
1049
+ name: "Grafana Cloud",
1050
+ category: "Observability",
1051
+ hostSuffixes: ["grafana.net"],
1052
+ apiBase: "https://grafana.net",
1053
+ defaultDataClasses: ["logs", "metrics"],
1054
+ sdks: {
1055
+ npm: ["@grafana/faro-web-sdk"]
1056
+ }
1057
+ },
1058
+ {
1059
+ id: "splunk",
1060
+ name: "Splunk",
1061
+ category: "Observability",
1062
+ hostSuffixes: ["splunkcloud.com", "splunk.com"],
1063
+ apiBase: "https://http-inputs.splunkcloud.com",
1064
+ defaultDataClasses: ["logs"],
1065
+ sdks: {
1066
+ npm: ["splunk-logging"],
1067
+ pypi: ["splunk-sdk"],
1068
+ maven: ["com.splunk"],
1069
+ nuget: ["Splunk.Logging.Common"]
1070
+ }
1071
+ },
1072
+ {
1073
+ id: "pagerduty",
1074
+ name: "PagerDuty",
1075
+ category: "Incident response",
1076
+ hostSuffixes: ["pagerduty.com"],
1077
+ apiBase: "https://api.pagerduty.com",
1078
+ defaultDataClasses: ["logs"],
1079
+ sdks: {
1080
+ npm: ["@pagerduty/pdjs"],
1081
+ pypi: ["pdpyras"],
1082
+ go: ["github.com/PagerDuty/go-pagerduty"],
1083
+ rubygems: ["pagerduty"]
1084
+ }
1085
+ },
1086
+ {
1087
+ id: "github",
1088
+ name: "GitHub",
1089
+ category: "Developer platform",
1090
+ hostSuffixes: ["github.com", "githubusercontent.com"],
1091
+ apiBase: "https://api.github.com",
1092
+ defaultDataClasses: ["source"],
1093
+ sdks: {
1094
+ npm: ["@octokit/rest", "octokit"],
1095
+ pypi: ["pygithub"],
1096
+ go: ["github.com/google/go-github"],
1097
+ maven: ["org.kohsuke.github-api"],
1098
+ rubygems: ["octokit"],
1099
+ cargo: ["octocrab"],
1100
+ composer: ["knplabs/github-api"],
1101
+ nuget: ["Octokit"]
1102
+ }
1103
+ },
1104
+ {
1105
+ id: "gitlab",
1106
+ name: "GitLab",
1107
+ category: "Developer platform",
1108
+ hostSuffixes: ["gitlab.com"],
1109
+ apiBase: "https://gitlab.com/api",
1110
+ defaultDataClasses: ["source"],
1111
+ sdks: {
1112
+ npm: ["@gitbeaker/rest"],
1113
+ pypi: ["python-gitlab"],
1114
+ go: ["gitlab.com/gitlab-org/api/client-go"],
1115
+ rubygems: ["gitlab"],
1116
+ nuget: ["GitLabApiClient"]
1117
+ }
1118
+ },
1119
+ {
1120
+ id: "auth0",
1121
+ name: "Auth0",
1122
+ category: "Identity",
1123
+ hostSuffixes: ["auth0.com"],
1124
+ apiBase: "https://login.auth0.com",
1125
+ defaultDataClasses: ["pii"],
1126
+ sdks: {
1127
+ npm: ["auth0"],
1128
+ pypi: ["auth0-python"],
1129
+ go: ["github.com/auth0/go-auth0"],
1130
+ maven: ["com.auth0"],
1131
+ rubygems: ["auth0"],
1132
+ composer: ["auth0/auth0-php"],
1133
+ nuget: ["Auth0.ManagementApi"]
1134
+ }
1135
+ },
1136
+ {
1137
+ id: "okta",
1138
+ name: "Okta",
1139
+ category: "Identity",
1140
+ hostSuffixes: ["okta.com", "oktapreview.com"],
1141
+ apiBase: "https://login.okta.com",
1142
+ defaultDataClasses: ["pii"],
1143
+ sdks: {
1144
+ npm: ["@okta/okta-sdk-nodejs"],
1145
+ pypi: ["okta"],
1146
+ go: ["github.com/okta/okta-sdk-golang"],
1147
+ maven: ["com.okta.sdk"],
1148
+ nuget: ["Okta.Sdk"]
1149
+ }
1150
+ },
1151
+ {
1152
+ id: "clerk",
1153
+ name: "Clerk",
1154
+ category: "Identity",
1155
+ hostSuffixes: ["clerk.com", "clerk.dev"],
1156
+ apiBase: "https://api.clerk.com",
1157
+ defaultDataClasses: ["pii"],
1158
+ sdks: {
1159
+ npm: ["@clerk/backend", "@clerk/nextjs"],
1160
+ pypi: ["clerk-backend-api"],
1161
+ go: ["github.com/clerk/clerk-sdk-go"]
1162
+ }
1163
+ },
1164
+ {
1165
+ id: "supabase",
1166
+ name: "Supabase",
1167
+ category: "Backend platform",
1168
+ hostSuffixes: ["supabase.co", "supabase.com"],
1169
+ apiBase: "https://api.supabase.com",
1170
+ defaultDataClasses: ["pii", "customer"],
1171
+ sdks: {
1172
+ npm: ["@supabase/supabase-js"],
1173
+ pypi: ["supabase"],
1174
+ cargo: ["postgrest"]
1175
+ }
1176
+ },
1177
+ {
1178
+ id: "firebase",
1179
+ name: "Firebase",
1180
+ category: "Backend platform",
1181
+ hostSuffixes: ["firebaseio.com", "firebase.google.com"],
1182
+ apiBase: "https://firebaseio.com",
1183
+ defaultDataClasses: ["customer"],
1184
+ sdks: {
1185
+ npm: ["firebase", "firebase-admin"],
1186
+ pypi: ["firebase-admin"],
1187
+ go: ["firebase.google.com/go"],
1188
+ maven: ["com.google.firebase"]
1189
+ }
1190
+ },
1191
+ {
1192
+ id: "mongodb-atlas",
1193
+ name: "MongoDB Atlas",
1194
+ category: "Database SaaS",
1195
+ hostSuffixes: ["mongodb.net", "mongodb.com"],
1196
+ apiBase: "https://cloud.mongodb.com",
1197
+ defaultDataClasses: ["customer"],
1198
+ sdks: {
1199
+ npm: ["mongodb"],
1200
+ pypi: ["pymongo"],
1201
+ go: ["go.mongodb.org/mongo-driver"],
1202
+ maven: ["org.mongodb"],
1203
+ rubygems: ["mongo"],
1204
+ cargo: ["mongodb"],
1205
+ nuget: ["MongoDB.Driver"]
1206
+ }
1207
+ },
1208
+ {
1209
+ id: "planetscale",
1210
+ name: "PlanetScale",
1211
+ category: "Database SaaS",
1212
+ hostSuffixes: ["psdb.cloud", "planetscale.com"],
1213
+ apiBase: "https://api.planetscale.com",
1214
+ defaultDataClasses: ["customer"],
1215
+ sdks: {
1216
+ npm: ["@planetscale/database"],
1217
+ go: ["github.com/planetscale/planetscale-go"]
1218
+ }
1219
+ },
1220
+ {
1221
+ id: "algolia",
1222
+ name: "Algolia",
1223
+ category: "Search SaaS",
1224
+ hostSuffixes: ["algolia.net", "algolianet.com"],
1225
+ apiBase: "https://algolia.net",
1226
+ defaultDataClasses: ["customer"],
1227
+ sdks: {
1228
+ npm: ["algoliasearch"],
1229
+ pypi: ["algoliasearch"],
1230
+ go: ["github.com/algolia/algoliasearch-client-go"],
1231
+ maven: ["com.algolia"],
1232
+ rubygems: ["algolia"],
1233
+ composer: ["algolia/algoliasearch-client-php"],
1234
+ nuget: ["Algolia.Search"]
1235
+ }
1236
+ },
1237
+ {
1238
+ id: "cloudflare",
1239
+ name: "Cloudflare",
1240
+ category: "CDN / edge",
1241
+ hostSuffixes: ["cloudflare.com", "workers.dev"],
1242
+ apiBase: "https://api.cloudflare.com",
1243
+ defaultDataClasses: ["logs"],
1244
+ sdks: {
1245
+ npm: ["cloudflare"],
1246
+ pypi: ["cloudflare"],
1247
+ go: ["github.com/cloudflare/cloudflare-go"],
1248
+ nuget: ["CloudFlare.Client"]
1249
+ }
1250
+ },
1251
+ {
1252
+ id: "huggingface",
1253
+ name: "Hugging Face",
1254
+ category: "LLM provider",
1255
+ hostSuffixes: ["huggingface.co"],
1256
+ apiBase: "https://api-inference.huggingface.co",
1257
+ defaultDataClasses: ["source"],
1258
+ sdks: {
1259
+ npm: ["@huggingface/inference"],
1260
+ pypi: ["huggingface-hub", "transformers"],
1261
+ rubygems: ["hugging-face"]
1262
+ }
1263
+ },
1264
+ {
1265
+ id: "cohere",
1266
+ name: "Cohere",
1267
+ category: "LLM provider",
1268
+ hostSuffixes: ["cohere.com", "cohere.ai"],
1269
+ apiBase: "https://api.cohere.com",
1270
+ defaultDataClasses: ["pii", "source"],
1271
+ sdks: {
1272
+ npm: ["cohere-ai"],
1273
+ pypi: ["cohere"],
1274
+ go: ["github.com/cohere-ai/cohere-go"]
1275
+ }
1276
+ },
1277
+ {
1278
+ id: "mistral",
1279
+ name: "Mistral AI",
1280
+ category: "LLM provider",
1281
+ hostSuffixes: ["mistral.ai"],
1282
+ apiBase: "https://api.mistral.ai",
1283
+ defaultDataClasses: ["pii", "source"],
1284
+ sdks: {
1285
+ npm: ["@mistralai/mistralai"],
1286
+ pypi: ["mistralai"],
1287
+ go: ["github.com/gage-technologies/mistral-go"]
1288
+ }
1289
+ }
1290
+ ];
1291
+ var EGRESS_VERSION_MATERIAL = `${EXTRACTOR_VERSION}
1292
+ ${JSON.stringify(PROVIDER_REGISTRY)}`;
1293
+ var INTERNAL_TLDS = ["internal", "local", "corp", "lan", "intranet", "home.arpa"];
1294
+ var EXCLUDED_HOST_SUFFIXES = [
1295
+ "localhost",
1296
+ "test",
1297
+ "example",
1298
+ "invalid",
1299
+ "example.com",
1300
+ "example.org",
1301
+ "example.net",
1302
+ "w3.org",
1303
+ "schemas.openxmlformats.org",
1304
+ "schemas.microsoft.com",
1305
+ "schemas.android.com",
1306
+ "maven.apache.org"
1307
+ ];
1308
+ function hostMatchesSuffix(host, suffix) {
1309
+ return host === suffix || host.endsWith(`.${suffix}`);
1310
+ }
1311
+ function isValidIPv4(host) {
1312
+ const parts = host.split(".");
1313
+ if (parts.length !== 4) return false;
1314
+ return parts.every((part) => /^\d{1,3}$/.test(part) && Number(part) <= 255);
1315
+ }
1316
+ function isPrivateOrReservedIPv4(host) {
1317
+ const octets = host.split(".").map(Number);
1318
+ const a = octets[0];
1319
+ const b = octets[1];
1320
+ if (a === void 0 || b === void 0) return false;
1321
+ if (a === 0 || a === 10 || a === 127 || a >= 224) return true;
1322
+ if (a === 169 && b === 254) return true;
1323
+ if (a === 172 && b >= 16 && b <= 31) return true;
1324
+ if (a === 192 && b === 168) return true;
1325
+ return false;
1326
+ }
1327
+ function normalizeIPv6Literal(host) {
1328
+ const unbracketed = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
1329
+ const zoneIndex = unbracketed.indexOf("%");
1330
+ const unzoned = zoneIndex === -1 ? unbracketed : unbracketed.slice(0, zoneIndex);
1331
+ return unzoned.toLowerCase();
1332
+ }
1333
+ var IPV6_HEX_GROUP = /^[0-9a-f]{1,4}$/;
1334
+ function isValidIPv6(host) {
1335
+ const h = normalizeIPv6Literal(host);
1336
+ if (!h.includes(":")) return false;
1337
+ const collapsedHalves = h.split("::");
1338
+ if (collapsedHalves.length > 2) return false;
1339
+ const sides = collapsedHalves.length === 2 ? collapsedHalves : [h];
1340
+ const groups = sides.flatMap((side) => side === "" ? [] : side.split(":"));
1341
+ if (!groups.every((g) => IPV6_HEX_GROUP.test(g))) return false;
1342
+ return collapsedHalves.length === 2 ? groups.length <= 7 : groups.length === 8;
1343
+ }
1344
+ function isPrivateOrReservedIPv6(host) {
1345
+ const h = normalizeIPv6Literal(host);
1346
+ if (h === "::" || h === "::1") return true;
1347
+ const firstGroup = (h.split(":")[0] ?? "").padStart(4, "0");
1348
+ if (firstGroup.startsWith("fc") || firstGroup.startsWith("fd")) return true;
1349
+ if (firstGroup.startsWith("fe") && "89ab".includes(firstGroup[2] ?? "")) return true;
1350
+ if (firstGroup.startsWith("ff")) return true;
1351
+ return false;
1352
+ }
1353
+ var IPV4_MAPPED_IPV6 = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/;
1354
+ function ipv4MappedAddress(host) {
1355
+ const match = IPV4_MAPPED_IPV6.exec(normalizeIPv6Literal(host));
1356
+ return match?.[1] ?? null;
1357
+ }
1358
+ function resolveHost(host, opts) {
1359
+ const h = host.toLowerCase();
1360
+ if (isValidIPv4(h)) {
1361
+ if (isPrivateOrReservedIPv4(h)) return null;
1362
+ return { kind: "ip", trust: "ip", name: h, category: "Unresolved host", entry: null };
1363
+ }
1364
+ if (isValidIPv6(h)) {
1365
+ if (isPrivateOrReservedIPv6(h)) return null;
1366
+ return { kind: "ip", trust: "ip", name: h, category: "Unresolved host", entry: null };
1367
+ }
1368
+ const mappedIPv4 = ipv4MappedAddress(h);
1369
+ if (mappedIPv4 !== null && isValidIPv4(mappedIPv4)) {
1370
+ if (isPrivateOrReservedIPv4(mappedIPv4)) return null;
1371
+ return { kind: "ip", trust: "ip", name: h, category: "Unresolved host", entry: null };
1372
+ }
1373
+ if (EXCLUDED_HOST_SUFFIXES.some((suffix) => hostMatchesSuffix(h, suffix))) return null;
1374
+ const entry = PROVIDER_REGISTRY.find(
1375
+ (p) => p.hostSuffixes.some((suffix) => hostMatchesSuffix(h, suffix))
1376
+ );
1377
+ if (entry) {
1378
+ return {
1379
+ kind: "provider",
1380
+ trust: "recognized",
1381
+ name: entry.name,
1382
+ category: entry.category,
1383
+ entry
1384
+ };
1385
+ }
1386
+ const internalDomains = opts?.internalDomains ?? [];
1387
+ const isInternal = !h.includes(".") && !h.includes(":") || INTERNAL_TLDS.some((tld) => hostMatchesSuffix(h, tld)) || internalDomains.some((domain2) => hostMatchesSuffix(h, domain2.toLowerCase()));
1388
+ if (isInternal) {
1389
+ return {
1390
+ kind: "internal",
1391
+ trust: "internal",
1392
+ name: h,
1393
+ category: "Internal services",
1394
+ entry: null
1395
+ };
1396
+ }
1397
+ return {
1398
+ kind: "external",
1399
+ trust: "unverified",
1400
+ name: h,
1401
+ category: "External domain",
1402
+ entry: null
1403
+ };
1404
+ }
1405
+ function resolveSdk(ecosystem, pkg) {
1406
+ for (const entry of PROVIDER_REGISTRY) {
1407
+ const idents = entry.sdks[ecosystem];
1408
+ if (idents === void 0) continue;
1409
+ if (idents.some((ident) => sdkMatches(ecosystem, pkg, ident))) return entry;
1410
+ }
1411
+ return null;
1412
+ }
1413
+ function sdkMatches(ecosystem, pkg, ident) {
1414
+ switch (ecosystem) {
1415
+ case "nuget":
1416
+ return pkg.toLowerCase() === ident.toLowerCase();
1417
+ case "pypi":
1418
+ return normalizePypi(pkg) === normalizePypi(ident);
1419
+ case "go":
1420
+ return pkg === ident || pkg.startsWith(`${ident}/`);
1421
+ case "maven":
1422
+ return pkg === ident || pkg.startsWith(`${ident}.`);
1423
+ default:
1424
+ return pkg === ident;
1425
+ }
1426
+ }
1427
+ function normalizePypi(name) {
1428
+ return name.toLowerCase().replace(/[-_.]+/g, "-");
1429
+ }
1430
+
1431
+ // ../packages/detections/src/egress/extract.ts
1432
+ var EGRESS_CODE_EXTENSIONS = /* @__PURE__ */ new Set([
1433
+ ".ts",
1434
+ ".tsx",
1435
+ ".js",
1436
+ ".jsx",
1437
+ ".mjs",
1438
+ ".cjs",
1439
+ ".py",
1440
+ ".java",
1441
+ ".rb",
1442
+ ".cs",
1443
+ ".php",
1444
+ ".go",
1445
+ ".rs"
1446
+ ]);
1447
+ var SNIPPET_MAX = 200;
1448
+ var MASK = "\u2022\u2022\u2022\u2022";
1449
+ var URL_CANDIDATE = /(https?|wss?|sftp|grpcs?|smtp):\/\/(?:[^\s'"`<>()[\]{},;]|\$\{[^}\s]{1,64}\}|\{[A-Za-z_]\w{0,63}\}|%[sd])+/gi;
1450
+ var PLACEHOLDER = /\$\{[^}\s]{1,64}\}|\{[A-Za-z_]\w{0,63}\}|%[sd]/g;
1451
+ var VAR_TOKEN = "${var}";
1452
+ var VAR_SENTINEL = "akaegressvar0";
1453
+ var TRAILING_PUNCTUATION = /[.,;:'"]+$/;
1454
+ var TRANSPORT_BY_SCHEME = {
1455
+ http: "http",
1456
+ https: "https",
1457
+ ws: "ws",
1458
+ wss: "wss",
1459
+ sftp: "sftp",
1460
+ grpc: "grpc",
1461
+ grpcs: "grpc",
1462
+ smtp: "smtp"
1463
+ };
1464
+ var BEFORE_WINDOW = 200;
1465
+ var AFTER_WINDOW = 300;
1466
+ var VERB_METHOD_OPENER = /\.\s*(get|post|put|delete)\s*\(\s*['"`]*$/i;
1467
+ var VERB_FIRST_ARGUMENT = /["'](GET|POST|PUT|DELETE)["']\s*,([^)\]};]{0,150})$/i;
1468
+ var OPTIONS_METHOD = /method\s*[:=]\s*['"](GET|POST|PUT|DELETE)/i;
1469
+ var CLIENT_OPENER = /\b(fetch|urlopen|got|ky)\s*\(\s*['"`]*$/i;
1470
+ var ANY_METHOD_KEY = /\bmethod\s*[:=]/i;
1471
+ var STATEMENT_BREAK = /;|\n\s*(?:(?:const|let|var|val|function|func|fn|def|class|return|export|import|if|for|while|switch|try|public|private)\b|\w+\s*=[^=])/;
1472
+ var IPV4_CANDIDATE = /\b(?:\d{1,3}\.){3}\d{1,3}(?::\d{1,5})?\b/g;
1473
+ var IP_HOST_CONTEXT = /\b(host|hostname|server|address|endpoint|ip|url|uri)\b/i;
1474
+ var IP_SFTP_CONTEXT = /\bs(ftp|cp|sh)\b/i;
1475
+ var IP_SMTP_CONTEXT = /\b(smtp|mail)\b/i;
1476
+ var SECRET_KEY_NAMES = "api[_-]?key|apikey|private[_-]?key|access[_-]?key|access[_-]?token|token|secret|credentials?|password|passwd|pwd|authorization|sig|signature|sas|assertion";
1477
+ var AUTH_SCHEMES = "Bearer|Basic|Token|Digest|ApiKey|SSWS|AWS4-HMAC-SHA256";
1478
+ var USERINFO = /:\/\/[^@/\s]+@/g;
1479
+ var SECRET_VALUE = new RegExp(
1480
+ `((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(?!(?:${AUTH_SCHEMES})[\\s'"\`])[^\\s'"\`&]+`,
1481
+ "gi"
1482
+ );
1483
+ var AUTH_SCHEME_VALUE = new RegExp(
1484
+ `((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(${AUTH_SCHEMES})\\s+[^\\s'"\`]+`,
1485
+ "gi"
1486
+ );
1487
+ var BEARER_TOKEN = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
1488
+ var WEBHOOK_SECRET_PATHS = [
1489
+ { hosts: ["hooks.slack.com"], prefix: "/services/" },
1490
+ {
1491
+ hosts: ["discord.com", "discordapp.com", "ptb.discord.com", "canary.discord.com"],
1492
+ prefix: "/api/webhooks/"
1493
+ },
1494
+ { hosts: ["hooks.zapier.com"], prefix: "/hooks/" },
1495
+ { hosts: ["outlook.office.com", "outlook.office365.com"], prefix: "/webhook/" }
1496
+ ];
1497
+ function escapeRegExp(literal2) {
1498
+ return literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1499
+ }
1500
+ var WEBHOOK_URL = new RegExp(
1501
+ `(https?://(?:${WEBHOOK_SECRET_PATHS.flatMap(
1502
+ (entry) => entry.hosts.map((host) => `${escapeRegExp(host)}${escapeRegExp(entry.prefix)}`)
1503
+ ).join("|")}))[^\\s'"\`<>()[\\]{},;]+`,
1504
+ "gi"
1505
+ );
1506
+ function maskWebhookPath(host, pathname) {
1507
+ for (const entry of WEBHOOK_SECRET_PATHS) {
1508
+ if (entry.hosts.includes(host) && pathname.startsWith(entry.prefix)) {
1509
+ return `${entry.prefix}${MASK}`;
1510
+ }
1511
+ }
1512
+ return pathname;
1513
+ }
1514
+ var VENDORED_PATH = /(^|\/)(vendor|third_party|external)\//;
1515
+ function isVendoredPath(file2) {
1516
+ return VENDORED_PATH.test(file2);
1517
+ }
1518
+ function redactLine(line) {
1519
+ return line.trim().replace(USERINFO, "://").replace(WEBHOOK_URL, `$1${MASK}`).replace(SECRET_VALUE, `$1${MASK}`).replace(AUTH_SCHEME_VALUE, `$1$2 ${MASK}`).replace(BEARER_TOKEN, `Bearer ${MASK}`);
1520
+ }
1521
+ function redactSnippet(line, anchor = 0) {
1522
+ const redacted = redactLine(line);
1523
+ if (redacted.length <= SNIPPET_MAX) return redacted;
1524
+ const lead = line.length - line.trimStart().length;
1525
+ const trimmed = line.trim();
1526
+ const mapped = redacted.length === trimmed.length ? anchor - lead : redactLine(trimmed.slice(0, Math.max(0, anchor - lead))).length;
1527
+ const start = Math.min(
1528
+ Math.max(0, mapped - Math.floor(SNIPPET_MAX / 2)),
1529
+ redacted.length - SNIPPET_MAX
1530
+ );
1531
+ return redacted.slice(start, start + SNIPPET_MAX);
1532
+ }
1533
+ function extractEgress(text) {
1534
+ const lineStarts = lineStartOffsets(text);
1535
+ const urlSpans = [];
1536
+ const hits = [];
1537
+ const lineTextOf = memoizeByLine((index) => lineTextAt(text, lineStarts, index));
1538
+ const ipContextOf = memoizeByLine((index) => ipLineContext(lineTextOf(index)));
1539
+ const snippetAt = (index, offset) => redactSnippet(lineTextOf(index), offset - (lineStarts[index] ?? 0));
1540
+ for (const match of text.matchAll(URL_CANDIDATE)) {
1541
+ const start = match.index;
1542
+ const matched = match[0];
1543
+ urlSpans.push([start, start + matched.length]);
1544
+ const scheme = match[1];
1545
+ if (scheme === void 0) continue;
1546
+ const candidate = matched.replace(TRAILING_PUNCTUATION, "");
1547
+ if (candidate === "") continue;
1548
+ const parsed = parseCandidate(candidate, scheme);
1549
+ if (parsed === null) continue;
1550
+ const index = lineIndexAt(lineStarts, start);
1551
+ hits.push({
1552
+ ...parsed,
1553
+ method: inferMethod(text, start, start + matched.length),
1554
+ line: index + 1,
1555
+ snippet: snippetAt(index, start)
1556
+ });
1557
+ }
1558
+ for (const match of text.matchAll(IPV4_CANDIDATE)) {
1559
+ const start = match.index;
1560
+ if (isInsideSpan(urlSpans, start)) continue;
1561
+ const index = lineIndexAt(lineStarts, start);
1562
+ const parsed = parseBareIp(match[0], ipContextOf(index));
1563
+ if (parsed === null) continue;
1564
+ hits.push({ ...parsed, method: "REF", line: index + 1, snippet: snippetAt(index, start) });
1565
+ }
1566
+ return hits.sort(compareHits);
1567
+ }
1568
+ function parseCandidate(candidate, scheme) {
1569
+ const transport = TRANSPORT_BY_SCHEME[scheme.toLowerCase()];
1570
+ if (transport === void 0) return null;
1571
+ let placeholders2 = 0;
1572
+ const normalized = candidate.replace(PLACEHOLDER, () => {
1573
+ placeholders2 += 1;
1574
+ return VAR_TOKEN;
1575
+ });
1576
+ let parsed;
1577
+ try {
1578
+ parsed = new URL(normalized.split(VAR_TOKEN).join(VAR_SENTINEL));
1579
+ } catch {
1580
+ return null;
1581
+ }
1582
+ const host = parsed.hostname.toLowerCase();
1583
+ if (host === "" || host.includes(VAR_SENTINEL)) return null;
1584
+ const authority = parsed.host.toLowerCase();
1585
+ const path = maskWebhookPath(host, parsed.pathname);
1586
+ const url2 = `${transport}://${authority}${path}`.split(VAR_SENTINEL).join(VAR_TOKEN);
1587
+ return {
1588
+ url: url2,
1589
+ host,
1590
+ port: parsed.port === "" ? null : Number(parsed.port),
1591
+ transport,
1592
+ template: placeholders2 > 0
1593
+ };
1594
+ }
1595
+ function parseBareIp(candidate, context) {
1596
+ const [address, port] = splitPort(candidate);
1597
+ if (!isValidIPv4(address) || isPrivateOrReservedIPv4(address)) return null;
1598
+ if (!context.named) return null;
1599
+ const { transport } = context;
1600
+ return {
1601
+ url: `${transport}://${address}${port === null ? "" : `:${String(port)}`}`,
1602
+ host: address,
1603
+ port,
1604
+ transport,
1605
+ template: false
1606
+ };
1607
+ }
1608
+ function ipLineContext(line) {
1609
+ const context = identifierWords(line);
1610
+ let transport = "http";
1611
+ if (IP_SFTP_CONTEXT.test(context)) transport = "sftp";
1612
+ else if (IP_SMTP_CONTEXT.test(context)) transport = "smtp";
1613
+ return { named: IP_HOST_CONTEXT.test(context), transport };
1614
+ }
1615
+ function isInsideSpan(spans, offset) {
1616
+ let low = 0;
1617
+ let high = spans.length - 1;
1618
+ let found = -1;
1619
+ while (low <= high) {
1620
+ const mid = Math.floor((low + high) / 2);
1621
+ const span2 = spans[mid];
1622
+ if (span2 === void 0) break;
1623
+ if (span2[0] <= offset) {
1624
+ found = mid;
1625
+ low = mid + 1;
1626
+ } else {
1627
+ high = mid - 1;
1628
+ }
1629
+ }
1630
+ const span = found === -1 ? void 0 : spans[found];
1631
+ return span !== void 0 && offset < span[1];
1632
+ }
1633
+ function splitPort(candidate) {
1634
+ const colon = candidate.indexOf(":");
1635
+ if (colon === -1) return [candidate, null];
1636
+ return [candidate.slice(0, colon), Number(candidate.slice(colon + 1))];
1637
+ }
1638
+ function identifierWords(line) {
1639
+ return line.replace(/[_-]+/g, " ").replace(/([a-z0-9])([A-Z])/g, "$1 $2");
1640
+ }
1641
+ function inferMethod(text, start, end) {
1642
+ const before = text.slice(Math.max(0, start - BEFORE_WINDOW), start);
1643
+ const after = text.slice(end, end + AFTER_WINDOW);
1644
+ const opener = VERB_METHOD_OPENER.exec(before);
1645
+ if (opener?.[1] !== void 0) return verbOf(opener[1]);
1646
+ const firstArgument = VERB_FIRST_ARGUMENT.exec(before);
1647
+ if (firstArgument?.[1] !== void 0 && !STATEMENT_BREAK.test(firstArgument[2] ?? "")) {
1648
+ return verbOf(firstArgument[1]);
1649
+ }
1650
+ const options = OPTIONS_METHOD.exec(after);
1651
+ if (options?.[1] !== void 0 && !STATEMENT_BREAK.test(after.slice(0, options.index))) {
1652
+ return verbOf(options[1]);
1653
+ }
1654
+ if (CLIENT_OPENER.test(before)) {
1655
+ const close = callCloseIndex(after);
1656
+ if (close !== -1 && isSoleArgument(after, close) && !ANY_METHOD_KEY.test(after.slice(0, close))) {
1657
+ return "GET";
1658
+ }
1659
+ }
1660
+ return "REF";
1661
+ }
1662
+ function verbOf(raw) {
1663
+ return raw.toUpperCase();
1664
+ }
1665
+ function callCloseIndex(span) {
1666
+ let depth = 1;
1667
+ for (let i = 0; i < span.length; i += 1) {
1668
+ const char = span[i];
1669
+ if (char === "(") depth += 1;
1670
+ else if (char === ")") {
1671
+ depth -= 1;
1672
+ if (depth === 0) return i + 1;
1673
+ }
1674
+ }
1675
+ return -1;
1676
+ }
1677
+ function isSoleArgument(span, close) {
1678
+ const between = span.slice(0, close - 1).trimEnd();
1679
+ const withoutTrailingComma = between.endsWith(",") ? between.slice(0, -1) : between;
1680
+ return !withoutTrailingComma.includes(",");
1681
+ }
1682
+ function memoizeByLine(compute) {
1683
+ const cache = /* @__PURE__ */ new Map();
1684
+ return (index) => {
1685
+ const cached2 = cache.get(index);
1686
+ if (cached2 !== void 0) return cached2;
1687
+ const value = compute(index);
1688
+ cache.set(index, value);
1689
+ return value;
1690
+ };
1691
+ }
1692
+ function lineStartOffsets(text) {
1693
+ const starts = [0];
1694
+ for (let i = 0; i < text.length; i += 1) {
1695
+ if (text[i] === "\n") starts.push(i + 1);
1696
+ }
1697
+ return starts;
1698
+ }
1699
+ function lineIndexAt(starts, offset) {
1700
+ let low = 0;
1701
+ let high = starts.length - 1;
1702
+ while (low < high) {
1703
+ const mid = Math.ceil((low + high) / 2);
1704
+ if ((starts[mid] ?? 0) <= offset) low = mid;
1705
+ else high = mid - 1;
1706
+ }
1707
+ return low;
1708
+ }
1709
+ function lineTextAt(text, starts, index) {
1710
+ const start = starts[index] ?? 0;
1711
+ const next = starts[index + 1];
1712
+ return next === void 0 ? text.slice(start) : text.slice(start, next - 1);
1713
+ }
1714
+ function compareHits(a, b) {
1715
+ if (a.line !== b.line) return a.line - b.line;
1716
+ if (a.url !== b.url) return a.url < b.url ? -1 : 1;
1717
+ return 0;
1718
+ }
1719
+
1720
+ // ../packages/detections/src/egress/manifests.ts
1721
+ var LOCKFILE_BASENAMES = /* @__PURE__ */ new Set([
1722
+ "package-lock.json",
1723
+ "yarn.lock",
1724
+ "pnpm-lock.yaml",
1725
+ "Cargo.lock",
1726
+ "composer.lock",
1727
+ "Gemfile.lock",
1728
+ "poetry.lock",
1729
+ "go.sum",
1730
+ "packages.lock.json"
1731
+ ]);
1732
+ var EXACT_KIND_BY_BASENAME = {
1733
+ "package.json": "package.json",
1734
+ "requirements.txt": "requirements.txt",
1735
+ "pyproject.toml": "pyproject.toml",
1736
+ "go.mod": "go.mod",
1737
+ "pom.xml": "pom.xml",
1738
+ "build.gradle": "build.gradle",
1739
+ "build.gradle.kts": "build.gradle",
1740
+ Gemfile: "Gemfile",
1741
+ "Cargo.toml": "Cargo.toml",
1742
+ "composer.json": "composer.json",
1743
+ "packages.config": "packages.config"
1744
+ };
1745
+ function manifestKindOf(basename6) {
1746
+ if (LOCKFILE_BASENAMES.has(basename6)) return null;
1747
+ const exact = Object.hasOwn(EXACT_KIND_BY_BASENAME, basename6) ? EXACT_KIND_BY_BASENAME[basename6] : void 0;
1748
+ if (exact !== void 0) return exact;
1749
+ if (basename6.endsWith(".csproj")) return "csproj";
1750
+ return null;
1751
+ }
1752
+ function extractManifestSdks(text, kind) {
1753
+ switch (kind) {
1754
+ case "package.json":
1755
+ return extractPackageJson(text);
1756
+ case "requirements.txt":
1757
+ return extractRequirementsTxt(text);
1758
+ case "pyproject.toml":
1759
+ return extractPyprojectToml(text);
1760
+ case "go.mod":
1761
+ return extractGoMod(text);
1762
+ case "pom.xml":
1763
+ return extractPomXml(text);
1764
+ case "build.gradle":
1765
+ return extractBuildGradle(text);
1766
+ case "Gemfile":
1767
+ return extractGemfile(text);
1768
+ case "Cargo.toml":
1769
+ return extractCargoToml(text);
1770
+ case "composer.json":
1771
+ return extractComposerJson(text);
1772
+ case "csproj":
1773
+ return extractCsproj(text);
1774
+ case "packages.config":
1775
+ return extractPackagesConfig(text);
1776
+ default:
1777
+ return [];
1778
+ }
1779
+ }
1780
+ function makeHit(ecosystem, pkg, line, rawLine) {
1781
+ return { ecosystem, pkg, line, snippet: redactSnippet(rawLine) };
1782
+ }
1783
+ function extractPackageJson(text) {
1784
+ const parsed = parseJson(text);
1785
+ if (parsed === null) return [];
1786
+ const seen = /* @__PURE__ */ new Set();
1787
+ const hits = [];
1788
+ for (const pkg of objectKeys(parsed.dependencies)) {
1789
+ seen.add(pkg);
1790
+ hits.push(hitAtQuotedKey("npm", pkg, text, "dependencies"));
1791
+ }
1792
+ for (const pkg of objectKeys(parsed.optionalDependencies)) {
1793
+ if (seen.has(pkg)) continue;
1794
+ seen.add(pkg);
1795
+ hits.push(hitAtQuotedKey("npm", pkg, text, "optionalDependencies"));
1796
+ }
1797
+ return hits;
1798
+ }
1799
+ var REQUIREMENTS_NAME = /^\s*([A-Za-z0-9][\w.-]*)/;
1800
+ function extractRequirementsTxt(text) {
1801
+ const hits = [];
1802
+ eachLine(text, (rawLine, lineNumber) => {
1803
+ const match = REQUIREMENTS_NAME.exec(rawLine);
1804
+ const name = match?.[1];
1805
+ if (name === void 0) return;
1806
+ hits.push(makeHit("pypi", normalizePypi(name), lineNumber, rawLine));
1807
+ });
1808
+ return hits;
1809
+ }
1810
+ var TOML_SECTION = /^\s*\[([^\]]+)\]\s*$/;
1811
+ var TOML_QUOTED = /"([^"]*)"|'([^']*)'/g;
1812
+ var PEP508_NAME = /^([A-Za-z0-9][\w.-]*)/;
1813
+ var POETRY_KEY = /^\s*([A-Za-z0-9][\w.-]*)\s*=/;
1814
+ function extractPyprojectToml(text) {
1815
+ const hits = [];
1816
+ let section = "";
1817
+ let inDependenciesArray = false;
1818
+ eachLine(text, (rawLine, lineNumber) => {
1819
+ const sectionMatch = TOML_SECTION.exec(rawLine);
1820
+ if (sectionMatch) {
1821
+ section = sectionMatch[1]?.trim() ?? "";
1822
+ inDependenciesArray = false;
1823
+ return;
1824
+ }
1825
+ if (!inDependenciesArray && /^\s*dependencies\s*=\s*\[/.test(rawLine)) {
1826
+ inDependenciesArray = true;
1827
+ }
1828
+ if (inDependenciesArray) {
1829
+ for (const spec of quotedStrings(rawLine)) {
1830
+ const name = PEP508_NAME.exec(spec)?.[1];
1831
+ if (name !== void 0)
1832
+ hits.push(makeHit("pypi", normalizePypi(name), lineNumber, rawLine));
1833
+ }
1834
+ if (rawLine.includes("]")) inDependenciesArray = false;
1835
+ return;
1836
+ }
1837
+ if (section === "tool.poetry.dependencies") {
1838
+ const key = POETRY_KEY.exec(rawLine)?.[1];
1839
+ if (key !== void 0 && key !== "python") {
1840
+ hits.push(makeHit("pypi", normalizePypi(key), lineNumber, rawLine));
1841
+ }
1842
+ }
1843
+ });
1844
+ return hits;
1845
+ }
1846
+ function quotedStrings(line) {
1847
+ return [...line.matchAll(TOML_QUOTED)].map((m) => m[1] ?? m[2] ?? "");
1848
+ }
1849
+ var GO_BLOCK_OPEN = /^\s*(require|exclude|replace|retract)\s*\(/;
1850
+ var GO_BLOCK_CLOSE = /^\s*\)/;
1851
+ var GO_REQUIRE_SINGLE_LINE = /^\s*require\s+([\w./-]+)\s+v\d/;
1852
+ var GO_MODULE_VERSION_LINE = /^\s*([\w./-]+)\s+v\d/;
1853
+ function extractGoMod(text) {
1854
+ const hits = [];
1855
+ let blockKeyword = null;
1856
+ eachLine(text, (rawLine, lineNumber) => {
1857
+ if (blockKeyword === null) {
1858
+ const open = GO_BLOCK_OPEN.exec(rawLine)?.[1];
1859
+ if (open !== void 0) {
1860
+ blockKeyword = open;
1861
+ return;
1862
+ }
1863
+ const path = GO_REQUIRE_SINGLE_LINE.exec(rawLine)?.[1];
1864
+ if (path !== void 0) hits.push(makeHit("go", path, lineNumber, rawLine));
1865
+ return;
1866
+ }
1867
+ if (GO_BLOCK_CLOSE.test(rawLine)) {
1868
+ blockKeyword = null;
1869
+ return;
1870
+ }
1871
+ if (blockKeyword === "require") {
1872
+ const path = GO_MODULE_VERSION_LINE.exec(rawLine)?.[1];
1873
+ if (path !== void 0) hits.push(makeHit("go", path, lineNumber, rawLine));
1874
+ }
1875
+ });
1876
+ return hits;
1877
+ }
1878
+ var POM_CONTEXT_TAGS = /* @__PURE__ */ new Set([
1879
+ "project",
1880
+ "parent",
1881
+ "dependencyManagement",
1882
+ "dependencies",
1883
+ "dependency",
1884
+ "build",
1885
+ "plugins",
1886
+ "plugin",
1887
+ "exclusions",
1888
+ "exclusion"
1889
+ ]);
1890
+ var XML_TAG = /<(\/?)([A-Za-z][\w.-]*)([^<>]*)>/g;
1891
+ var LEADING_TEXT = /^([^<]*)/;
1892
+ function extractPomXml(text) {
1893
+ const hits = [];
1894
+ const stack = [];
1895
+ let inComment = false;
1896
+ eachLine(text, (rawLine, lineNumber) => {
1897
+ const stripped = stripXmlComments(rawLine, inComment);
1898
+ inComment = stripped.inComment;
1899
+ const visible = stripped.visible;
1900
+ for (const match of visible.matchAll(XML_TAG)) {
1901
+ const name = match[2];
1902
+ if (name === void 0) continue;
1903
+ const closing = match[1] === "/";
1904
+ const rest = match[3] ?? "";
1905
+ const selfClosing = rest.trimEnd().endsWith("/");
1906
+ if (closing) {
1907
+ if (POM_CONTEXT_TAGS.has(name) && stack[stack.length - 1] === name) stack.pop();
1908
+ continue;
1909
+ }
1910
+ if (selfClosing) continue;
1911
+ if (name === "groupId") {
1912
+ const after = visible.slice(match.index + match[0].length);
1913
+ const value = LEADING_TEXT.exec(after)?.[1]?.trim() ?? "";
1914
+ if (value !== "" && isProjectDependencyGroupId(stack)) {
1915
+ hits.push(makeHit("maven", value, lineNumber, rawLine));
1916
+ }
1917
+ continue;
1918
+ }
1919
+ if (POM_CONTEXT_TAGS.has(name)) stack.push(name);
1920
+ }
1921
+ });
1922
+ return hits;
1923
+ }
1924
+ function isProjectDependencyGroupId(stack) {
1925
+ return stack[stack.length - 1] === "dependency" && stack[stack.length - 2] === "dependencies" && !stack.includes("dependencyManagement") && !stack.includes("parent") && !stack.includes("build");
1926
+ }
1927
+ var GRADLE_DEPENDENCY = /\b(?:implementation|api|compile)\b\s*[('"]*(?:platform\s*\(\s*['"])?([\w.-]+):[\w.-]+/g;
1928
+ var LINE_COMMENT = /^\s*\/\//;
1929
+ function extractBuildGradle(text) {
1930
+ const hits = [];
1931
+ eachLine(text, (rawLine, lineNumber) => {
1932
+ if (LINE_COMMENT.test(rawLine)) return;
1933
+ for (const match of rawLine.matchAll(GRADLE_DEPENDENCY)) {
1934
+ const groupId = match[1];
1935
+ if (groupId !== void 0) hits.push(makeHit("maven", groupId, lineNumber, rawLine));
1936
+ }
1937
+ });
1938
+ return hits;
1939
+ }
1940
+ var GEMFILE_DEPENDENCY = /^\s*gem\s+['"]([\w-]+)['"]/;
1941
+ function extractGemfile(text) {
1942
+ const hits = [];
1943
+ eachLine(text, (rawLine, lineNumber) => {
1944
+ const name = GEMFILE_DEPENDENCY.exec(rawLine)?.[1];
1945
+ if (name !== void 0) hits.push(makeHit("rubygems", name, lineNumber, rawLine));
1946
+ });
1947
+ return hits;
1948
+ }
1949
+ var CARGO_KEY = /^([A-Za-z0-9_-]+)\s*=/;
1950
+ function extractCargoToml(text) {
1951
+ const hits = [];
1952
+ let mode = "none";
1953
+ eachLine(text, (rawLine, lineNumber) => {
1954
+ const sectionMatch = TOML_SECTION.exec(rawLine);
1955
+ if (sectionMatch) {
1956
+ const name = sectionMatch[1]?.trim() ?? "";
1957
+ if (name === "dependencies") {
1958
+ mode = "plain";
1959
+ } else if (name.startsWith("dependencies.")) {
1960
+ mode = "dotted";
1961
+ const crate = name.slice("dependencies.".length);
1962
+ if (crate !== "") hits.push(makeHit("cargo", crate, lineNumber, rawLine));
1963
+ } else {
1964
+ mode = "none";
1965
+ }
1966
+ return;
1967
+ }
1968
+ if (mode === "plain") {
1969
+ const crate = CARGO_KEY.exec(rawLine)?.[1];
1970
+ if (crate !== void 0) hits.push(makeHit("cargo", crate, lineNumber, rawLine));
1971
+ }
1972
+ });
1973
+ return hits;
1974
+ }
1975
+ function extractComposerJson(text) {
1976
+ const parsed = parseJson(text);
1977
+ if (parsed === null) return [];
1978
+ const pkgs = objectKeys(parsed.require).filter((pkg) => pkg !== "php" && !pkg.startsWith("ext-"));
1979
+ return pkgs.map((pkg) => hitAtQuotedKey("composer", pkg, text, "require"));
1980
+ }
1981
+ var CSPROJ_PACKAGE_REFERENCE = /<PackageReference\s+Include="([^"]+)"/;
1982
+ function extractCsproj(text) {
1983
+ const hits = [];
1984
+ let inComment = false;
1985
+ eachLine(text, (rawLine, lineNumber) => {
1986
+ const stripped = stripXmlComments(rawLine, inComment);
1987
+ inComment = stripped.inComment;
1988
+ const name = CSPROJ_PACKAGE_REFERENCE.exec(stripped.visible)?.[1];
1989
+ if (name !== void 0) hits.push(makeHit("nuget", name, lineNumber, rawLine));
1990
+ });
1991
+ return hits;
1992
+ }
1993
+ var PACKAGES_CONFIG_PACKAGE = /<package\s+id="([^"]+)"/;
1994
+ function extractPackagesConfig(text) {
1995
+ const hits = [];
1996
+ let inComment = false;
1997
+ eachLine(text, (rawLine, lineNumber) => {
1998
+ const stripped = stripXmlComments(rawLine, inComment);
1999
+ inComment = stripped.inComment;
2000
+ const name = PACKAGES_CONFIG_PACKAGE.exec(stripped.visible)?.[1];
2001
+ if (name !== void 0) hits.push(makeHit("nuget", name, lineNumber, rawLine));
2002
+ });
2003
+ return hits;
2004
+ }
2005
+ function stripXmlComments(line, inComment) {
2006
+ let visible = "";
2007
+ let rest = line;
2008
+ let comment = inComment;
2009
+ for (; ; ) {
2010
+ if (comment) {
2011
+ const end = rest.indexOf("-->");
2012
+ if (end === -1) return { visible, inComment: true };
2013
+ rest = rest.slice(end + 3);
2014
+ comment = false;
2015
+ continue;
2016
+ }
2017
+ const start = rest.indexOf("<!--");
2018
+ if (start === -1) return { visible: visible + rest, inComment: false };
2019
+ visible += rest.slice(0, start);
2020
+ rest = rest.slice(start + 4);
2021
+ comment = true;
2022
+ }
2023
+ }
2024
+ function eachLine(text, fn) {
2025
+ const lines = text.split("\n");
2026
+ for (let i = 0; i < lines.length; i += 1) {
2027
+ fn(lines[i] ?? "", i + 1);
2028
+ }
2029
+ }
2030
+ function parseJson(text) {
2031
+ let parsed;
2032
+ try {
2033
+ parsed = JSON.parse(text);
2034
+ } catch {
2035
+ return null;
2036
+ }
2037
+ if (typeof parsed !== "object" || parsed === null) return null;
2038
+ const record2 = parsed;
2039
+ return {
2040
+ dependencies: record2.dependencies,
2041
+ optionalDependencies: record2.optionalDependencies,
2042
+ require: record2.require
2043
+ };
2044
+ }
2045
+ function objectKeys(value) {
2046
+ if (typeof value !== "object" || value === null) return [];
2047
+ return Object.keys(value);
2048
+ }
2049
+ function hitAtQuotedKey(ecosystem, pkg, text, sectionKey) {
2050
+ const sectionStart = text.indexOf(`"${sectionKey}"`);
2051
+ const searchFrom = sectionStart === -1 ? 0 : sectionStart;
2052
+ const index = text.indexOf(`"${pkg}"`, searchFrom);
2053
+ if (index === -1) return makeHit(ecosystem, pkg, 1, pkg);
2054
+ return makeHit(ecosystem, pkg, lineNumberAt(text, index), lineContaining(text, index));
2055
+ }
2056
+ function lineNumberAt(text, index) {
2057
+ let line = 1;
2058
+ for (let i = 0; i < index; i += 1) {
2059
+ if (text[i] === "\n") line += 1;
2060
+ }
2061
+ return line;
2062
+ }
2063
+ function lineContaining(text, index) {
2064
+ const start = text.lastIndexOf("\n", index) + 1;
2065
+ const end = text.indexOf("\n", index);
2066
+ return text.slice(start, end === -1 ? text.length : end);
2067
+ }
2068
+
2069
+ // ../packages/detections/src/egress/resolve.ts
2070
+ var VERB_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "DELETE"]);
2071
+ function resolveEgress(files, opts) {
2072
+ const resolved = [];
2073
+ for (const file2 of files) {
2074
+ for (const endpoint of file2.endpoints) {
2075
+ const hit = resolveEndpointHit(file2, endpoint, opts);
2076
+ if (hit !== null) resolved.push(hit);
2077
+ }
2078
+ for (const sdkHit of file2.sdkHits) {
2079
+ const hit = resolveSdkHit(file2, sdkHit);
2080
+ if (hit !== null) resolved.push(hit);
2081
+ }
2082
+ }
2083
+ return dropRefsWithVerbSibling(dedupeExact(resolved));
2084
+ }
2085
+ function resolveEndpointHit(file2, endpoint, opts) {
2086
+ const resolution = resolveHost(endpoint.host, opts);
2087
+ if (resolution === null) return null;
2088
+ const isProvider = resolution.kind === "provider";
2089
+ return {
2090
+ host: endpoint.host,
2091
+ kind: resolution.kind,
2092
+ name: resolution.name,
2093
+ category: resolution.category,
2094
+ trust: resolution.trust,
2095
+ network: isProvider ? null : { port: endpoint.port, geo: null, ptr: null },
2096
+ method: endpoint.method,
2097
+ transport: endpoint.transport,
2098
+ url: endpoint.url,
2099
+ template: endpoint.template,
2100
+ dataClass: isProvider ? resolution.entry?.defaultDataClasses[0] ?? "none" : "none",
2101
+ site: {
2102
+ file: file2.file,
2103
+ line: endpoint.line,
2104
+ snippet: endpoint.snippet,
2105
+ dynamic: endpoint.template,
2106
+ vendored: file2.vendored
2107
+ }
2108
+ };
2109
+ }
2110
+ function resolveSdkHit(file2, sdkHit) {
2111
+ const entry = resolveSdk(sdkHit.ecosystem, sdkHit.pkg);
2112
+ if (entry === null) return null;
2113
+ return {
2114
+ host: new URL(entry.apiBase).hostname,
2115
+ kind: "provider",
2116
+ name: entry.name,
2117
+ category: entry.category,
2118
+ trust: "recognized",
2119
+ network: null,
2120
+ method: "SDK",
2121
+ transport: "https",
2122
+ url: entry.apiBase,
2123
+ template: false,
2124
+ dataClass: entry.defaultDataClasses[0] ?? "none",
2125
+ site: {
2126
+ file: file2.file,
2127
+ line: sdkHit.line,
2128
+ snippet: sdkHit.snippet,
2129
+ dynamic: false,
2130
+ vendored: file2.vendored
2131
+ }
2132
+ };
2133
+ }
2134
+ function dedupeExact(hits) {
2135
+ const seen = /* @__PURE__ */ new Set();
2136
+ const deduped = [];
2137
+ for (const hit of hits) {
2138
+ const key = exactKey(hit);
2139
+ if (seen.has(key)) continue;
2140
+ seen.add(key);
2141
+ deduped.push(hit);
2142
+ }
2143
+ return deduped;
2144
+ }
2145
+ function exactKey(hit) {
2146
+ return JSON.stringify([hit.host, hit.method, hit.url, hit.site.file, hit.site.line]);
2147
+ }
2148
+ function dropRefsWithVerbSibling(hits) {
2149
+ const verbPairs = /* @__PURE__ */ new Set();
2150
+ for (const hit of hits) {
2151
+ if (VERB_METHODS.has(hit.method)) verbPairs.add(pairKey(hit));
2152
+ }
2153
+ return hits.filter((hit) => hit.method !== "REF" || !verbPairs.has(pairKey(hit)));
2154
+ }
2155
+ function pairKey(hit) {
2156
+ return JSON.stringify([hit.host, hit.url]);
2157
+ }
757
2158
 
758
2159
  // ../packages/detections/src/escape-regexp.ts
759
- function escapeRegExp(value) {
2160
+ function escapeRegExp2(value) {
760
2161
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
761
2162
  }
762
2163
 
763
2164
  // ../packages/detections/src/matchers/limits.ts
764
2165
  var MAX_MATCHES_PER_RULE = 1e4;
2166
+ var MAX_REGEX_INPUT_LENGTH = 2e5;
765
2167
 
766
2168
  // ../packages/detections/src/matchers/keyword.ts
767
2169
  var KeywordMatcher = class {
@@ -772,7 +2174,7 @@ var KeywordMatcher = class {
772
2174
  for (const kw of keywords) {
773
2175
  if (kw.length === 0) continue;
774
2176
  if (spans.length >= MAX_MATCHES_PER_RULE) break;
775
- const re = new RegExp(escapeRegExp(kw), caseSensitive ? "gu" : "giu");
2177
+ const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
776
2178
  let m;
777
2179
  while ((m = re.exec(text)) !== null) {
778
2180
  spans.push({ start: m.index, end: m.index + m[0].length });
@@ -789,9 +2191,13 @@ var RegexMatcher = class {
789
2191
  if (rule.matcher.type !== "regex") return [];
790
2192
  const { pattern, flags, captureGroup } = rule.matcher;
791
2193
  const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
2194
+ const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
792
2195
  const spans = [];
793
2196
  let m;
794
- while ((m = re.exec(text)) !== null) {
2197
+ const maxIterations = scanText2.length + 1;
2198
+ let iterations = 0;
2199
+ while ((m = re.exec(scanText2)) !== null) {
2200
+ if (++iterations > maxIterations) break;
795
2201
  const group = captureGroup != null ? m[captureGroup] : m[0];
796
2202
  if (m[0].length === 0) re.lastIndex++;
797
2203
  if (group && spans.length < MAX_MATCHES_PER_RULE) {
@@ -895,7 +2301,7 @@ function isCorroborated(candidate, candidates, text) {
895
2301
  for (const label of labels) {
896
2302
  const trimmed = label.trim();
897
2303
  if (trimmed.length === 0) continue;
898
- const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp(trimmed)}(?![A-Za-z0-9])`, "i");
2304
+ const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
899
2305
  if (re.test(haystack)) return true;
900
2306
  }
901
2307
  }
@@ -1060,6 +2466,37 @@ var CONFIG_POSTURE_RULES = [
1060
2466
  }
1061
2467
  ];
1062
2468
 
2469
+ // ../packages/detections/src/security/redos-probe.ts
2470
+ var EXPONENTIAL_UNITS = [
2471
+ "a",
2472
+ "0",
2473
+ " ",
2474
+ "x",
2475
+ "ab",
2476
+ "a.",
2477
+ "a-",
2478
+ "a_",
2479
+ "a@",
2480
+ "a/",
2481
+ "a:",
2482
+ "a=",
2483
+ "a;",
2484
+ "aA0",
2485
+ " "
2486
+ ];
2487
+ var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
2488
+ (unit) => [23, 25].map((len) => unit.repeat(Math.ceil(len / unit.length)).slice(0, len) + "!")
2489
+ );
2490
+ var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
2491
+ (unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
2492
+ );
2493
+
2494
+ // ../packages/persistence/src/database.ts
2495
+ import { randomUUID as randomUUID8 } from "crypto";
2496
+ import { existsSync as existsSync2, renameSync, rmSync } from "fs";
2497
+ import { join as join2, sep } from "path";
2498
+ import { DatabaseSync } from "node:sqlite";
2499
+
1063
2500
  // ../packages/schema/src/drizzle/sqlite-ddl.ts
1064
2501
  var SQLITE_MIGRATIONS = [
1065
2502
  {
@@ -1105,6 +2542,10 @@ var SQLITE_MIGRATIONS = [
1105
2542
  {
1106
2543
  tag: "0010_events_session_expression_index",
1107
2544
  sql: "-- Custom migration: partial expression index for session-scoped finding reads.\n--\n-- sessionFindingsCount, the session-scoped listGroupedFindings paths, and the\n-- insert-time session dedup all filter live-capture events by\n-- json_extract(e.metadata, '$.sessionId') = :sessionId\n-- \u2014 previously a full findings-join scan with a JSON parse per row. The\n-- IS NOT NULL predicate keeps the index to session-stamped events only (an\n-- equality probe implies non-null, so SQLite still uses it).\nCREATE INDEX `idx_events_session_id` ON `events` (json_extract(`metadata`, '$.sessionId')) WHERE json_extract(`metadata`, '$.sessionId') IS NOT NULL;\n"
2545
+ },
2546
+ {
2547
+ tag: "0011_egress_writer",
2548
+ sql: '-- Stable per-project reconcile key for egress call sites, plus a host-keyed\n-- egress decision override that survives destination pruning.\n--\n-- DROP INDEX IF EXISTS, not a bare DROP: the applier replays a pending\n-- migration\'s non-index statements verbatim, so a store that lost\n-- uq_share_call_site out of band would throw here \u2014 and on the plugin hook path\n-- that throw is swallowed fail-open, silently stopping capture.\n--\n-- Existing rows carry the old key\'s discriminator into project_key\n-- (\'legacy:\' || project), so the new unique index is a re-encoding of the old\n-- one and cannot collide on rows the old one allowed. Leaving them on the\n-- column default would collapse two projects\' identical file/line hits onto\n-- one key and abort the whole migration. Writer keys are \'git:\'/\'path:\'-\n-- prefixed, so a backfilled row never collides with a captured one either.\n--\n-- egress_decision_override.destination_id becomes nullable with ON DELETE SET\n-- NULL, which SQLite can only do by rebuilding the table. `host` is ADDed\n-- before the rebuild so the copy has a column to read and so the migration\n-- still presents two probeable columns to the applier\'s evidence check.\nALTER TABLE `share_call_site` ADD `project_key` text DEFAULT \'\' NOT NULL;--> statement-breakpoint\nUPDATE `share_call_site` SET `project_key` = \'legacy:\' || `project`;--> statement-breakpoint\nDROP INDEX IF EXISTS `uq_share_call_site`;--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_share_call_site` ON `share_call_site` (`endpoint_id`,`project_key`,`file`,`line`);--> statement-breakpoint\nCREATE INDEX `idx_share_call_site_project` ON `share_call_site` (`project_key`,`endpoint_id`);--> statement-breakpoint\nALTER TABLE `egress_decision_override` ADD `host` text;--> statement-breakpoint\nPRAGMA foreign_keys=OFF;--> statement-breakpoint\nCREATE TABLE `__new_egress_decision_override` (\n `id` text PRIMARY KEY NOT NULL,\n `destination_id` text,\n `host` text,\n `decision` text NOT NULL,\n `created_at` integer NOT NULL,\n `updated_at` integer NOT NULL,\n FOREIGN KEY (`destination_id`) REFERENCES `share_destination`(`id`) ON UPDATE no action ON DELETE set null\n);\n--> statement-breakpoint\nINSERT INTO `__new_egress_decision_override`("id", "destination_id", "host", "decision", "created_at", "updated_at") SELECT "id", "destination_id", "host", "decision", "created_at", "updated_at" FROM `egress_decision_override`;--> statement-breakpoint\nDROP TABLE `egress_decision_override`;--> statement-breakpoint\nALTER TABLE `__new_egress_decision_override` RENAME TO `egress_decision_override`;--> statement-breakpoint\nPRAGMA foreign_keys=ON;--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_egress_decision_override` ON `egress_decision_override` (`destination_id`);--> statement-breakpoint\nCREATE UNIQUE INDEX `uq_egress_decision_override_host` ON `egress_decision_override` (`host`) WHERE `host` IS NOT NULL;\n'
1108
2549
  }
1109
2550
  ];
1110
2551
 
@@ -16883,6 +18324,7 @@ var ExceptionBundleEntry = DetectionException.pick({
16883
18324
 
16884
18325
  // ../packages/schema/src/zod/rule.ts
16885
18326
  var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
18327
+ var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
16886
18328
  var KeywordMatcher2 = external_exports.object({
16887
18329
  type: external_exports.literal("keyword"),
16888
18330
  // An empty keyword matches at every position, yielding one zero-length span
@@ -16907,9 +18349,10 @@ function matchesEmptyString(pattern, flags) {
16907
18349
  return false;
16908
18350
  }
16909
18351
  }
18352
+ var MAX_PATTERN_LENGTH = 2e3;
16910
18353
  var RegexMatcher2 = external_exports.object({
16911
18354
  type: external_exports.literal("regex"),
16912
- pattern: external_exports.string(),
18355
+ pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
16913
18356
  flags: external_exports.string().default("gi"),
16914
18357
  captureGroup: external_exports.number().int().nonnegative().optional()
16915
18358
  }).refine((v) => isValidRegex(v.pattern, v.flags), {
@@ -17396,85 +18839,291 @@ var ImportDetectionRequest = external_exports.object({
17396
18839
  libraryId: external_exports.string().refine((v) => /^[^/]+\/[^/]+$/.test(v), {
17397
18840
  message: "libraryId must be in namespace/packId format"
17398
18841
  })
17399
- }).meta({ id: "ImportDetectionRequest" });
17400
-
17401
- // ../packages/schema/src/zod/detection-build.ts
17402
- function summaryToDetectionListItem(s) {
17403
- return {
17404
- id: `${s.namespace}/${s.packId}`,
17405
- name: s.name,
17406
- version: s.version,
17407
- enabled: s.enabled,
17408
- origin: "library",
17409
- // v1: every installed pack is library origin
17410
- namespace: s.namespace,
17411
- packId: s.packId,
17412
- ruleCount: s.ruleCount,
17413
- ...s.policyId != null ? { policyId: s.policyId } : {},
17414
- ...s.latestVersion != null ? { latestVersion: s.latestVersion } : {}
17415
- };
17416
- }
17417
- function rowToDetectionDetail(row, findingsLast30d, update) {
17418
- const rules = row.rules.flatMap((r) => {
17419
- const parsed = Matcher.safeParse(r.matcher);
17420
- if (!parsed.success) return [];
17421
- return [
17422
- {
17423
- id: r.id,
17424
- name: r.name,
17425
- category: r.category,
17426
- severity: r.severity,
17427
- matcher: parsed.data
17428
- }
17429
- ];
17430
- });
17431
- return {
17432
- id: `${row.namespace}/${row.packId}`,
17433
- name: row.name,
17434
- version: row.version,
17435
- enabled: row.enabled,
17436
- origin: "library",
17437
- namespace: row.namespace,
17438
- packId: row.packId,
17439
- ruleCount: row.rules.length,
17440
- editedAt: row.updatedAt.toISOString(),
17441
- findingsLast30d,
17442
- latestVersion: update ? update.latestVersion : null,
17443
- update,
17444
- rules,
17445
- modified: false,
17446
- ...row.policyId != null ? { policyId: row.policyId } : {}
17447
- };
17448
- }
17449
- function splitDetectionId(id) {
17450
- const idx = id.indexOf("/");
17451
- if (idx < 1 || idx === id.length - 1) return null;
17452
- return { namespace: id.slice(0, idx), packId: id.slice(idx + 1) };
17453
- }
17454
- function buildDetectionsList(summaries, query) {
17455
- const withUpdate = summaries.filter((s) => s.latestVersion != null);
17456
- const counts = {
17457
- all: summaries.length,
17458
- library: summaries.length,
17459
- // all origin=library in v1
17460
- custom: 0,
17461
- customized: 0,
17462
- updates: withUpdate.length
17463
- };
17464
- const filter = query.filter;
17465
- let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
17466
- if (query.q) {
17467
- const q = query.q.toLowerCase();
17468
- filtered = filtered.filter(
17469
- (s) => s.name.toLowerCase().includes(q) || s.packId.toLowerCase().includes(q) || s.namespace.toLowerCase().includes(q)
17470
- );
17471
- }
17472
- filtered.sort((a, b) => {
17473
- if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
17474
- return a.name.localeCompare(b.name);
17475
- });
17476
- return { counts, items: filtered.map(summaryToDetectionListItem) };
17477
- }
18842
+ }).meta({ id: "ImportDetectionRequest" });
18843
+
18844
+ // ../packages/schema/src/zod/detection-build.ts
18845
+ function summaryToDetectionListItem(s) {
18846
+ return {
18847
+ id: `${s.namespace}/${s.packId}`,
18848
+ name: s.name,
18849
+ version: s.version,
18850
+ enabled: s.enabled,
18851
+ origin: "library",
18852
+ // v1: every installed pack is library origin
18853
+ namespace: s.namespace,
18854
+ packId: s.packId,
18855
+ ruleCount: s.ruleCount,
18856
+ ...s.policyId != null ? { policyId: s.policyId } : {},
18857
+ ...s.latestVersion != null ? { latestVersion: s.latestVersion } : {}
18858
+ };
18859
+ }
18860
+ function rowToDetectionDetail(row, findingsLast30d, update) {
18861
+ const rules = row.rules.flatMap((r) => {
18862
+ const parsed = Matcher.safeParse(r.matcher);
18863
+ if (!parsed.success) return [];
18864
+ return [
18865
+ {
18866
+ id: r.id,
18867
+ name: r.name,
18868
+ category: r.category,
18869
+ severity: r.severity,
18870
+ matcher: parsed.data
18871
+ }
18872
+ ];
18873
+ });
18874
+ return {
18875
+ id: `${row.namespace}/${row.packId}`,
18876
+ name: row.name,
18877
+ version: row.version,
18878
+ enabled: row.enabled,
18879
+ origin: "library",
18880
+ namespace: row.namespace,
18881
+ packId: row.packId,
18882
+ ruleCount: row.rules.length,
18883
+ editedAt: row.updatedAt.toISOString(),
18884
+ findingsLast30d,
18885
+ latestVersion: update ? update.latestVersion : null,
18886
+ update,
18887
+ rules,
18888
+ modified: false,
18889
+ ...row.policyId != null ? { policyId: row.policyId } : {}
18890
+ };
18891
+ }
18892
+ function splitDetectionId(id) {
18893
+ const idx = id.indexOf("/");
18894
+ if (idx < 1 || idx === id.length - 1) return null;
18895
+ return { namespace: id.slice(0, idx), packId: id.slice(idx + 1) };
18896
+ }
18897
+ function buildDetectionsList(summaries, query) {
18898
+ const withUpdate = summaries.filter((s) => s.latestVersion != null);
18899
+ const counts = {
18900
+ all: summaries.length,
18901
+ library: summaries.length,
18902
+ // all origin=library in v1
18903
+ custom: 0,
18904
+ customized: 0,
18905
+ updates: withUpdate.length
18906
+ };
18907
+ const filter = query.filter;
18908
+ let filtered = filter === "custom" || filter === "customized" ? [] : filter === "updates" ? [...withUpdate] : [...summaries];
18909
+ if (query.q) {
18910
+ const q = query.q.toLowerCase();
18911
+ filtered = filtered.filter(
18912
+ (s) => s.name.toLowerCase().includes(q) || s.packId.toLowerCase().includes(q) || s.namespace.toLowerCase().includes(q)
18913
+ );
18914
+ }
18915
+ filtered.sort((a, b) => {
18916
+ if (a.enabled !== b.enabled) return a.enabled ? -1 : 1;
18917
+ return a.name.localeCompare(b.name);
18918
+ });
18919
+ return { counts, items: filtered.map(summaryToDetectionListItem) };
18920
+ }
18921
+
18922
+ // ../packages/schema/src/zod/shares.ts
18923
+ var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
18924
+ var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
18925
+ var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
18926
+ var DATA_CLASS_ORDER = DataClass.options;
18927
+ var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
18928
+ var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
18929
+ var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
18930
+ var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
18931
+ var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE", "SDK", "REF"]).meta({ id: "HttpMethod" });
18932
+ var ReviewInfo = external_exports.object({
18933
+ needsReview: external_exports.boolean(),
18934
+ reasons: external_exports.array(ReviewReason)
18935
+ }).meta({ id: "ReviewInfo" });
18936
+ var DestinationNetwork = external_exports.object({
18937
+ port: external_exports.number().int().nullable(),
18938
+ geo: external_exports.string().nullable(),
18939
+ ptr: external_exports.string().nullable()
18940
+ }).meta({ id: "DestinationNetwork" });
18941
+ var EndpointSummary = external_exports.object({
18942
+ id: external_exports.string(),
18943
+ method: HttpMethod,
18944
+ transport: Transport,
18945
+ url: external_exports.string(),
18946
+ template: external_exports.boolean(),
18947
+ dataClass: DataClass,
18948
+ lastSeen: external_exports.iso.datetime(),
18949
+ callSiteCount: external_exports.number().int().nonnegative()
18950
+ }).meta({ id: "EndpointSummary" });
18951
+ var CallSite = external_exports.object({
18952
+ id: external_exports.string(),
18953
+ project: external_exports.string(),
18954
+ file: external_exports.string(),
18955
+ line: external_exports.number().int().nonnegative(),
18956
+ snippet: external_exports.string(),
18957
+ dynamic: external_exports.boolean(),
18958
+ vendored: external_exports.boolean(),
18959
+ /** Deep-link to the Inventory project, when the repo is governed there. */
18960
+ projectId: external_exports.string().nullable()
18961
+ }).meta({ id: "CallSite" });
18962
+ var EndpointWithSites = EndpointSummary.extend({
18963
+ sites: external_exports.array(CallSite)
18964
+ }).meta({ id: "EndpointWithSites" });
18965
+ var ShareDestinationSummary = external_exports.object({
18966
+ id: external_exports.string(),
18967
+ kind: DestinationKind,
18968
+ name: external_exports.string(),
18969
+ host: external_exports.string(),
18970
+ category: external_exports.string(),
18971
+ trust: ShareTrustLevel,
18972
+ /** Effective state (decision applied over the trust default). */
18973
+ status: EgressStatus,
18974
+ /** True when an egress decision override differs from the trust default. */
18975
+ isCustom: external_exports.boolean(),
18976
+ lastSeen: external_exports.iso.datetime(),
18977
+ endpointCount: external_exports.number().int().nonnegative(),
18978
+ callSiteCount: external_exports.number().int().nonnegative(),
18979
+ transports: external_exports.array(Transport),
18980
+ /** Most-sensitive first. */
18981
+ dataClasses: external_exports.array(DataClass),
18982
+ review: ReviewInfo,
18983
+ /** Non-provider hosts only; null for providers. */
18984
+ network: DestinationNetwork.nullable(),
18985
+ /** Embedded for inline expansion — no call sites here. */
18986
+ endpoints: external_exports.array(EndpointSummary)
18987
+ }).meta({ id: "ShareDestinationSummary" });
18988
+ var ShareDestinationDetail = ShareDestinationSummary.omit({
18989
+ endpointCount: true,
18990
+ callSiteCount: true,
18991
+ endpoints: true
18992
+ }).extend({
18993
+ /** Ownership/geo rationale; null for providers. */
18994
+ note: external_exports.string().nullable(),
18995
+ endpoints: external_exports.array(EndpointWithSites)
18996
+ }).meta({ id: "ShareDestinationDetail" });
18997
+ var ReviewDestination = external_exports.object({
18998
+ id: external_exports.string(),
18999
+ kind: DestinationKind,
19000
+ name: external_exports.string(),
19001
+ /** Registrable host — lets the strip derive the provider lettermark, as the register does. */
19002
+ host: external_exports.string(),
19003
+ trust: ShareTrustLevel,
19004
+ status: EgressStatus,
19005
+ review: ReviewInfo,
19006
+ topDataClass: DataClass,
19007
+ callSiteCount: external_exports.number().int().nonnegative(),
19008
+ lastSeen: external_exports.iso.datetime()
19009
+ }).meta({ id: "ReviewDestination" });
19010
+ var ShareDestinationGroup = external_exports.object({
19011
+ kind: DestinationKind,
19012
+ total: external_exports.number().int().nonnegative(),
19013
+ items: external_exports.array(ShareDestinationSummary)
19014
+ }).meta({ id: "ShareDestinationGroup" });
19015
+ var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
19016
+ var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
19017
+ var SharesStats = external_exports.object({
19018
+ destinations: external_exports.number().int().nonnegative(),
19019
+ endpoints: external_exports.number().int().nonnegative(),
19020
+ callSites: external_exports.number().int().nonnegative(),
19021
+ needsReview: external_exports.number().int().nonnegative(),
19022
+ insecure: external_exports.number().int().nonnegative(),
19023
+ byKind: external_exports.object({
19024
+ provider: external_exports.number().int().nonnegative(),
19025
+ internal: external_exports.number().int().nonnegative(),
19026
+ external: external_exports.number().int().nonnegative(),
19027
+ ip: external_exports.number().int().nonnegative()
19028
+ }),
19029
+ byTrust: external_exports.object({
19030
+ recognized: external_exports.number().int().nonnegative(),
19031
+ internal: external_exports.number().int().nonnegative(),
19032
+ unverified: external_exports.number().int().nonnegative(),
19033
+ ip: external_exports.number().int().nonnegative()
19034
+ })
19035
+ }).meta({ id: "SharesStats" });
19036
+ var SetEgressDecisionBody = external_exports.object({
19037
+ /** `null` clears the override — reverts to the trust default, isCustom false. */
19038
+ decision: EgressDecision.nullable()
19039
+ }).meta({ id: "SetEgressDecisionBody" });
19040
+ var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
19041
+ var ListShareDestinationsQuery = external_exports.object({
19042
+ /** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
19043
+ q: external_exports.string().optional(),
19044
+ /** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
19045
+ kind: external_exports.array(DestinationKind).optional(),
19046
+ /** Reserved for future grouping modes; only 'destination' is supported today. */
19047
+ groupBy: external_exports.enum(["destination"]).default("destination"),
19048
+ /**
19049
+ * When true, return a flat severity-ordered `items[]` instead of `groups`.
19050
+ * Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
19051
+ * any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
19052
+ * to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
19053
+ */
19054
+ review: external_exports.stringbool().default(false)
19055
+ });
19056
+ var ExportSharesQuery = external_exports.object({
19057
+ format: external_exports.enum(["csv", "json"]).default("csv"),
19058
+ q: external_exports.string().optional(),
19059
+ kind: external_exports.array(DestinationKind).optional()
19060
+ });
19061
+
19062
+ // ../packages/schema/src/zod/egress-extraction.ts
19063
+ var EgressEcosystem = external_exports.enum(["npm", "pypi", "go", "maven", "rubygems", "cargo", "composer", "nuget"]).meta({ id: "EgressEcosystem" });
19064
+ var ProviderRegistryEntry = external_exports.object({
19065
+ id: external_exports.string(),
19066
+ name: external_exports.string(),
19067
+ category: external_exports.string(),
19068
+ /** Suffix-matched: 'stripe.com' matches api.stripe.com, never evilstripe.com. */
19069
+ hostSuffixes: external_exports.array(external_exports.string()).min(1),
19070
+ /** Canonical API base URL recorded for manifest-derived (method 'SDK') endpoints. */
19071
+ apiBase: external_exports.string(),
19072
+ /** Most-sensitive first; index 0 becomes the endpoint dataClass. */
19073
+ defaultDataClasses: external_exports.array(DataClass).min(1),
19074
+ /** SDK identifiers per ecosystem ('go' prefix-matched by path, 'maven' by group-id prefix). */
19075
+ sdks: external_exports.partialRecord(EgressEcosystem, external_exports.array(external_exports.string()))
19076
+ }).meta({ id: "ProviderRegistryEntry" });
19077
+ var EgressCallSiteHit = external_exports.object({
19078
+ file: external_exports.string(),
19079
+ line: external_exports.number().int().positive(),
19080
+ snippet: external_exports.string(),
19081
+ dynamic: external_exports.boolean(),
19082
+ vendored: external_exports.boolean()
19083
+ }).meta({ id: "EgressCallSiteHit" });
19084
+ var ResolvedEgressHit = external_exports.object({
19085
+ host: external_exports.string(),
19086
+ kind: DestinationKind,
19087
+ name: external_exports.string(),
19088
+ category: external_exports.string(),
19089
+ trust: ShareTrustLevel,
19090
+ network: DestinationNetwork.nullable(),
19091
+ method: HttpMethod,
19092
+ transport: Transport,
19093
+ url: external_exports.string(),
19094
+ template: external_exports.boolean(),
19095
+ dataClass: DataClass,
19096
+ site: EgressCallSiteHit
19097
+ }).meta({ id: "ResolvedEgressHit" });
19098
+ var EgressReconcile = external_exports.discriminatedUnion("mode", [
19099
+ external_exports.object({ mode: external_exports.literal("walk"), walkedPrefix: external_exports.string() }),
19100
+ external_exports.object({
19101
+ mode: external_exports.literal("ledger"),
19102
+ scannedFiles: external_exports.array(external_exports.string()),
19103
+ deletedFiles: external_exports.array(external_exports.string())
19104
+ })
19105
+ ]).meta({ id: "EgressReconcile" });
19106
+ var RecordProjectEgressInput = external_exports.object({
19107
+ /** Stable reconcile key: 'git:<repo identity>' or 'path:<abs root>' (non-git). */
19108
+ projectKey: external_exports.string().min(1),
19109
+ /** Display name only — never keys reconciliation. */
19110
+ project: external_exports.string(),
19111
+ projectId: external_exports.string().nullable(),
19112
+ reconcile: EgressReconcile,
19113
+ hits: external_exports.array(ResolvedEgressHit)
19114
+ }).meta({ id: "RecordProjectEgressInput" });
19115
+ var EgressWriteSummary = external_exports.object({
19116
+ destinations: external_exports.number().int().nonnegative(),
19117
+ endpoints: external_exports.number().int().nonnegative(),
19118
+ callSites: external_exports.number().int().nonnegative(),
19119
+ truncated: external_exports.boolean(),
19120
+ /**
19121
+ * Files the cap dropped whole. Their stored rows were left untouched, so a
19122
+ * ledger-keeping caller must withhold their ledger entries and read them
19123
+ * again next scan.
19124
+ */
19125
+ droppedFiles: external_exports.array(external_exports.string()).default([])
19126
+ }).meta({ id: "EgressWriteSummary" });
17478
19127
 
17479
19128
  // ../packages/schema/src/zod/findings-group-build.ts
17480
19129
  function toApiAction(dbVal) {
@@ -17737,7 +19386,7 @@ var PatchInstalledPackRequest = external_exports.object({
17737
19386
  }).meta({ id: "PatchInstalledPackRequest" });
17738
19387
 
17739
19388
  // ../packages/schema/src/zod/local.ts
17740
- var WORKSPACE_SETTINGS_SPEC_VERSION = 2;
19389
+ var WORKSPACE_SETTINGS_SPEC_VERSION = 3;
17741
19390
  var RunMode = external_exports.enum(["standalone"]);
17742
19391
  var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
17743
19392
  var HistoricalAccess = external_exports.enum(["full", "session-only"]);
@@ -17752,6 +19401,9 @@ var WorkspaceSettings = external_exports.object({
17752
19401
  policy: SimpleDetectionPolicy.default("redact"),
17753
19402
  // Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
17754
19403
  historicalAccess: HistoricalAccess.default("session-only"),
19404
+ // In-place egress extraction on the scan paths; disable to stop all Data
19405
+ // Shares writes.
19406
+ dataSharesInPlace: external_exports.boolean().default(true),
17755
19407
  // Absent until /aka:setup completes; its presence is what "onboarded" means.
17756
19408
  onboardedAt: external_exports.iso.datetime().optional()
17757
19409
  });
@@ -18161,220 +19813,81 @@ var TriageRecommendation = external_exports.object({
18161
19813
  });
18162
19814
 
18163
19815
  // ../packages/schema/src/zod/setup-frame.ts
18164
- var CalibrationCounts = external_exports.object({
18165
- total: external_exports.number().int().nonnegative(),
18166
- important: external_exports.number().int().nonnegative(),
18167
- routine: external_exports.number().int().nonnegative()
18168
- }).refine((c) => c.total === c.important + c.routine, {
18169
- message: "total must equal important + routine",
18170
- path: ["total"]
18171
- });
18172
- var FalsePositivePatternValue = external_exports.object({
18173
- ruleId: external_exports.string(),
18174
- category: DetectionCategory,
18175
- valueFingerprint: external_exports.string(),
18176
- keyVersion: external_exports.number().int().nonnegative()
18177
- });
18178
- var FalsePositivePatternGroup = external_exports.object({
18179
- pattern: external_exports.string(),
18180
- count: external_exports.number().int().nonnegative(),
18181
- values: external_exports.array(FalsePositivePatternValue).min(1)
18182
- });
18183
- var CalibrationFindingKind = external_exports.object({
18184
- category: DetectionCategory,
18185
- count: external_exports.number().int().nonnegative(),
18186
- egress: external_exports.boolean()
18187
- });
18188
- var CalibrationFrame = external_exports.object({
18189
- counts: CalibrationCounts,
18190
- routineCategories: external_exports.array(DetectionCategory),
18191
- surfacedCategories: external_exports.array(DetectionCategory),
18192
- findingKinds: external_exports.array(CalibrationFindingKind),
18193
- posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
18194
- maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
18195
- falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
18196
- });
18197
- var CalibrationPreviewCategory = TriageCategoryRec.pick({
18198
- category: true,
18199
- genuineCount: true,
18200
- fpCount: true
18201
- }).extend({
18202
- egress: external_exports.boolean()
18203
- });
18204
- var CalibrationPreview = external_exports.object({
18205
- categories: external_exports.array(CalibrationPreviewCategory),
18206
- posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
18207
- });
18208
- var CalibrationResult = external_exports.object({
18209
- frame: CalibrationFrame,
18210
- copy: external_exports.string()
18211
- });
18212
- var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
18213
- var SetupHandoffOption = external_exports.object({
18214
- id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
18215
- label: external_exports.string()
18216
- });
18217
- var DashboardHandoffOptions = external_exports.tuple([
18218
- SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
18219
- SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
18220
- ]);
18221
- var ComposedRemediationOptions = external_exports.tuple([
18222
- SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
18223
- SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
18224
- SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
18225
- ]);
18226
- var SetupHandoffOffer = external_exports.object({
18227
- worthALook: external_exports.number().int().nonnegative(),
18228
- liveKeys: external_exports.number().int().nonnegative().optional(),
18229
- options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
18230
- }).refine(
18231
- (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
18232
- {
18233
- message: "the chain-entry option is present exactly when liveKeys > 0",
18234
- path: ["options"]
18235
- }
18236
- ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
18237
- message: "liveKeys is a subset of worthALook and cannot exceed it",
18238
- path: ["liveKeys"]
18239
- });
18240
-
18241
- // ../packages/schema/src/zod/shares.ts
18242
- var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
18243
- var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
18244
- var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
18245
- var DATA_CLASS_ORDER = DataClass.options;
18246
- var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
18247
- var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
18248
- var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
18249
- var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
18250
- var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE"]).meta({ id: "HttpMethod" });
18251
- var ReviewInfo = external_exports.object({
18252
- needsReview: external_exports.boolean(),
18253
- reasons: external_exports.array(ReviewReason)
18254
- }).meta({ id: "ReviewInfo" });
18255
- var DestinationNetwork = external_exports.object({
18256
- port: external_exports.number().int().nullable(),
18257
- geo: external_exports.string().nullable(),
18258
- ptr: external_exports.string().nullable()
18259
- }).meta({ id: "DestinationNetwork" });
18260
- var EndpointSummary = external_exports.object({
18261
- id: external_exports.string(),
18262
- method: HttpMethod,
18263
- transport: Transport,
18264
- url: external_exports.string(),
18265
- template: external_exports.boolean(),
18266
- dataClass: DataClass,
18267
- lastSeen: external_exports.iso.datetime(),
18268
- callSiteCount: external_exports.number().int().nonnegative()
18269
- }).meta({ id: "EndpointSummary" });
18270
- var CallSite = external_exports.object({
18271
- id: external_exports.string(),
18272
- project: external_exports.string(),
18273
- file: external_exports.string(),
18274
- line: external_exports.number().int().nonnegative(),
18275
- snippet: external_exports.string(),
18276
- dynamic: external_exports.boolean(),
18277
- vendored: external_exports.boolean(),
18278
- /** Deep-link to the Inventory project, when the repo is governed there. */
18279
- projectId: external_exports.string().nullable()
18280
- }).meta({ id: "CallSite" });
18281
- var EndpointWithSites = EndpointSummary.extend({
18282
- sites: external_exports.array(CallSite)
18283
- }).meta({ id: "EndpointWithSites" });
18284
- var ShareDestinationSummary = external_exports.object({
18285
- id: external_exports.string(),
18286
- kind: DestinationKind,
18287
- name: external_exports.string(),
18288
- host: external_exports.string(),
18289
- category: external_exports.string(),
18290
- trust: ShareTrustLevel,
18291
- /** Effective state (decision applied over the trust default). */
18292
- status: EgressStatus,
18293
- /** True when an egress decision override differs from the trust default. */
18294
- isCustom: external_exports.boolean(),
18295
- lastSeen: external_exports.iso.datetime(),
18296
- endpointCount: external_exports.number().int().nonnegative(),
18297
- callSiteCount: external_exports.number().int().nonnegative(),
18298
- transports: external_exports.array(Transport),
18299
- /** Most-sensitive first. */
18300
- dataClasses: external_exports.array(DataClass),
18301
- review: ReviewInfo,
18302
- /** Non-provider hosts only; null for providers. */
18303
- network: DestinationNetwork.nullable(),
18304
- /** Embedded for inline expansion — no call sites here. */
18305
- endpoints: external_exports.array(EndpointSummary)
18306
- }).meta({ id: "ShareDestinationSummary" });
18307
- var ShareDestinationDetail = ShareDestinationSummary.omit({
18308
- endpointCount: true,
18309
- callSiteCount: true,
18310
- endpoints: true
18311
- }).extend({
18312
- /** Ownership/geo rationale; null for providers. */
18313
- note: external_exports.string().nullable(),
18314
- endpoints: external_exports.array(EndpointWithSites)
18315
- }).meta({ id: "ShareDestinationDetail" });
18316
- var ReviewDestination = external_exports.object({
18317
- id: external_exports.string(),
18318
- kind: DestinationKind,
18319
- name: external_exports.string(),
18320
- /** Registrable host — lets the strip derive the provider lettermark, as the register does. */
18321
- host: external_exports.string(),
18322
- trust: ShareTrustLevel,
18323
- status: EgressStatus,
18324
- review: ReviewInfo,
18325
- topDataClass: DataClass,
18326
- callSiteCount: external_exports.number().int().nonnegative(),
18327
- lastSeen: external_exports.iso.datetime()
18328
- }).meta({ id: "ReviewDestination" });
18329
- var ShareDestinationGroup = external_exports.object({
18330
- kind: DestinationKind,
19816
+ var CalibrationCounts = external_exports.object({
18331
19817
  total: external_exports.number().int().nonnegative(),
18332
- items: external_exports.array(ShareDestinationSummary)
18333
- }).meta({ id: "ShareDestinationGroup" });
18334
- var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
18335
- var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
18336
- var SharesStats = external_exports.object({
18337
- destinations: external_exports.number().int().nonnegative(),
18338
- endpoints: external_exports.number().int().nonnegative(),
18339
- callSites: external_exports.number().int().nonnegative(),
18340
- needsReview: external_exports.number().int().nonnegative(),
18341
- insecure: external_exports.number().int().nonnegative(),
18342
- byKind: external_exports.object({
18343
- provider: external_exports.number().int().nonnegative(),
18344
- internal: external_exports.number().int().nonnegative(),
18345
- ip: external_exports.number().int().nonnegative()
18346
- }),
18347
- byTrust: external_exports.object({
18348
- recognized: external_exports.number().int().nonnegative(),
18349
- internal: external_exports.number().int().nonnegative(),
18350
- unverified: external_exports.number().int().nonnegative(),
18351
- ip: external_exports.number().int().nonnegative()
18352
- })
18353
- }).meta({ id: "SharesStats" });
18354
- var SetEgressDecisionBody = external_exports.object({
18355
- /** `null` clears the override — reverts to the trust default, isCustom false. */
18356
- decision: EgressDecision.nullable()
18357
- }).meta({ id: "SetEgressDecisionBody" });
18358
- var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
18359
- var ListShareDestinationsQuery = external_exports.object({
18360
- /** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
18361
- q: external_exports.string().optional(),
18362
- /** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
18363
- kind: external_exports.array(DestinationKind).optional(),
18364
- /** Reserved for future grouping modes; only 'destination' is supported today. */
18365
- groupBy: external_exports.enum(["destination"]).default("destination"),
18366
- /**
18367
- * When true, return a flat severity-ordered `items[]` instead of `groups`.
18368
- * Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
18369
- * any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
18370
- * to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
18371
- */
18372
- review: external_exports.stringbool().default(false)
19818
+ important: external_exports.number().int().nonnegative(),
19819
+ routine: external_exports.number().int().nonnegative()
19820
+ }).refine((c) => c.total === c.important + c.routine, {
19821
+ message: "total must equal important + routine",
19822
+ path: ["total"]
18373
19823
  });
18374
- var ExportSharesQuery = external_exports.object({
18375
- format: external_exports.enum(["csv", "json"]).default("csv"),
18376
- q: external_exports.string().optional(),
18377
- kind: external_exports.array(DestinationKind).optional()
19824
+ var FalsePositivePatternValue = external_exports.object({
19825
+ ruleId: external_exports.string(),
19826
+ category: DetectionCategory,
19827
+ valueFingerprint: external_exports.string(),
19828
+ keyVersion: external_exports.number().int().nonnegative()
19829
+ });
19830
+ var FalsePositivePatternGroup = external_exports.object({
19831
+ pattern: external_exports.string(),
19832
+ count: external_exports.number().int().nonnegative(),
19833
+ values: external_exports.array(FalsePositivePatternValue).min(1)
19834
+ });
19835
+ var CalibrationFindingKind = external_exports.object({
19836
+ category: DetectionCategory,
19837
+ count: external_exports.number().int().nonnegative(),
19838
+ egress: external_exports.boolean()
19839
+ });
19840
+ var CalibrationFrame = external_exports.object({
19841
+ counts: CalibrationCounts,
19842
+ routineCategories: external_exports.array(DetectionCategory),
19843
+ surfacedCategories: external_exports.array(DetectionCategory),
19844
+ findingKinds: external_exports.array(CalibrationFindingKind),
19845
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId),
19846
+ maskedFindings: external_exports.array(MaskedSecretFinding).optional(),
19847
+ falsePositivePatterns: external_exports.array(FalsePositivePatternGroup).optional()
19848
+ });
19849
+ var CalibrationPreviewCategory = TriageCategoryRec.pick({
19850
+ category: true,
19851
+ genuineCount: true,
19852
+ fpCount: true
19853
+ }).extend({
19854
+ egress: external_exports.boolean()
19855
+ });
19856
+ var CalibrationPreview = external_exports.object({
19857
+ categories: external_exports.array(CalibrationPreviewCategory),
19858
+ posture: external_exports.record(DetectionCategory, BuiltinPolicyId)
19859
+ });
19860
+ var CalibrationResult = external_exports.object({
19861
+ frame: CalibrationFrame,
19862
+ copy: external_exports.string()
19863
+ });
19864
+ var FirstRunCalibration = external_exports.enum(["scan", "floor"]);
19865
+ var SetupHandoffOption = external_exports.object({
19866
+ id: external_exports.enum(["enter-remediation", "open-dashboard", "not-now"]),
19867
+ label: external_exports.string()
19868
+ });
19869
+ var DashboardHandoffOptions = external_exports.tuple([
19870
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
19871
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
19872
+ ]);
19873
+ var ComposedRemediationOptions = external_exports.tuple([
19874
+ SetupHandoffOption.extend({ id: external_exports.literal("enter-remediation") }),
19875
+ SetupHandoffOption.extend({ id: external_exports.literal("open-dashboard") }),
19876
+ SetupHandoffOption.extend({ id: external_exports.literal("not-now") })
19877
+ ]);
19878
+ var SetupHandoffOffer = external_exports.object({
19879
+ worthALook: external_exports.number().int().nonnegative(),
19880
+ liveKeys: external_exports.number().int().nonnegative().optional(),
19881
+ options: external_exports.union([DashboardHandoffOptions, ComposedRemediationOptions])
19882
+ }).refine(
19883
+ (o) => o.options.some((opt) => opt.id === "enter-remediation") === (o.liveKeys ?? 0) > 0,
19884
+ {
19885
+ message: "the chain-entry option is present exactly when liveKeys > 0",
19886
+ path: ["options"]
19887
+ }
19888
+ ).refine((o) => (o.liveKeys ?? 0) <= o.worthALook, {
19889
+ message: "liveKeys is a subset of worthALook and cannot exceed it",
19890
+ path: ["liveKeys"]
18378
19891
  });
18379
19892
 
18380
19893
  // ../packages/schema/src/zod/shares-access.ts
@@ -18396,7 +19909,7 @@ function deriveReviewReasons(trust, transports) {
18396
19909
  const reasons = [];
18397
19910
  if (trust === "ip") reasons.push("raw_ip");
18398
19911
  if (trust === "unverified") reasons.push("unverified_domain");
18399
- if (transports.includes("http")) reasons.push("plaintext_transport");
19912
+ if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
18400
19913
  return reasons;
18401
19914
  }
18402
19915
  function buildReviewInfo(trust, transports) {
@@ -18423,139 +19936,6 @@ function reviewSeverityRank(reasons) {
18423
19936
  return Math.min(...reasons.map((r) => REVIEW_SEVERITY_RANK[r]));
18424
19937
  }
18425
19938
 
18426
- // ../packages/local-ops/src/fs-scan.ts
18427
- var import_ignore = __toESM(require_ignore(), 1);
18428
- var AKAIGNORE_FILENAME = ".akaignore";
18429
- var SKIP_DIRS = /* @__PURE__ */ new Set([
18430
- "node_modules",
18431
- ".git",
18432
- "dist",
18433
- ".next",
18434
- "coverage",
18435
- ".turbo",
18436
- "build",
18437
- "out"
18438
- ]);
18439
- var MAX_BYTES = 1e6;
18440
- function readIgnoreLayer(dir, filename) {
18441
- try {
18442
- const content = readFileSync2(join2(dir, filename), "utf8");
18443
- return { base: dir, matcher: (0, import_ignore.default)().add(content) };
18444
- } catch {
18445
- return void 0;
18446
- }
18447
- }
18448
- function evaluate(layers, absPath, isDir) {
18449
- let state = "unmatched";
18450
- for (const layer of layers) {
18451
- const rel = relative(layer.base, absPath).split(sep).join("/") + (isDir ? "/" : "");
18452
- const verdict = layer.matcher.test(rel);
18453
- if (verdict.ignored) state = "ignored";
18454
- else if (verdict.unignored) state = "unignored";
18455
- }
18456
- return state;
18457
- }
18458
- function* collectFiles(target) {
18459
- let st;
18460
- try {
18461
- st = statSync(target);
18462
- } catch {
18463
- return;
18464
- }
18465
- if (st.isFile()) {
18466
- if (st.size <= MAX_BYTES) yield { path: target, gitignored: false };
18467
- return;
18468
- }
18469
- if (!st.isDirectory()) return;
18470
- yield* visit(target, [], [], false);
18471
- }
18472
- function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
18473
- const markLayer = inIgnoredDir ? void 0 : readIgnoreLayer(dir, ".gitignore");
18474
- const dirMarkLayers = markLayer ? [...markLayers, markLayer] : markLayers;
18475
- const skipLayer = readIgnoreLayer(dir, AKAIGNORE_FILENAME);
18476
- const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
18477
- for (const entry of readdirSync(dir, { withFileTypes: true })) {
18478
- const path = join2(dir, entry.name);
18479
- if (entry.isDirectory()) {
18480
- const skipState = evaluate(dirSkipLayers, path, true);
18481
- if (skipState !== "unignored" && (SKIP_DIRS.has(entry.name) || entry.name.startsWith(".") || skipState === "ignored")) {
18482
- continue;
18483
- }
18484
- const dirIgnored = inIgnoredDir || evaluate(dirMarkLayers, path, true) === "ignored";
18485
- yield* visit(path, dirMarkLayers, dirSkipLayers, dirIgnored);
18486
- } else if (entry.isFile()) {
18487
- if (evaluate(dirSkipLayers, path, false) === "ignored") continue;
18488
- let size;
18489
- try {
18490
- size = statSync(path).size;
18491
- } catch {
18492
- continue;
18493
- }
18494
- if (size > MAX_BYTES) continue;
18495
- yield {
18496
- path,
18497
- gitignored: inIgnoredDir || evaluate(dirMarkLayers, path, false) === "ignored"
18498
- };
18499
- }
18500
- }
18501
- }
18502
- function scanPathIntoStore(db, target, opts = {}) {
18503
- let scanned = 0;
18504
- let findingCount = 0;
18505
- const files = [];
18506
- for (const { path: file2, gitignored } of collectFiles(target)) {
18507
- let text;
18508
- try {
18509
- text = readFileSync2(file2, "utf8");
18510
- } catch {
18511
- continue;
18512
- }
18513
- scanned++;
18514
- const matches = scan(text, opts.rules);
18515
- if (matches.length === 0) continue;
18516
- const eventId = randomUUID();
18517
- const metadata = { filePath: file2 };
18518
- if (gitignored) metadata.gitignored = true;
18519
- const event = {
18520
- id: eventId,
18521
- sourceTool: opts.sourceTool ?? "cli",
18522
- kind: "code_change",
18523
- occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
18524
- contentHash: createHash("sha256").update(text).digest("hex"),
18525
- content: redact(text, matches),
18526
- // store the REDACTED file, never the raw secret
18527
- metadata
18528
- };
18529
- const findings = matches.map((m) => ({
18530
- id: randomUUID(),
18531
- eventId,
18532
- ruleId: m.ruleId,
18533
- category: m.category,
18534
- severity: m.severity,
18535
- span: m.span,
18536
- maskedMatch: maskMatch(m.rawMatch),
18537
- // Per-pack action (monitor-by-default) when the installed snapshot supplies
18538
- // one, else the per-category fallback — mirrors the live path's resolveAction.
18539
- actionTaken: opts.ruleActions?.get(m.ruleId) ?? DEFAULT_ACTIONS[m.category],
18540
- confidence: m.confidence
18541
- }));
18542
- db.recordCapture(event, findings);
18543
- files.push({ path: file2, gitignored, findings });
18544
- findingCount += findings.length;
18545
- }
18546
- return { scanned, findings: findingCount, files };
18547
- }
18548
-
18549
- // ../packages/local-ops/src/project-inventory.ts
18550
- import { statSync as statSync5 } from "fs";
18551
- import { resolve } from "path";
18552
-
18553
- // ../packages/persistence/src/database.ts
18554
- import { randomUUID as randomUUID9 } from "crypto";
18555
- import { existsSync as existsSync2, renameSync, rmSync } from "fs";
18556
- import { join as join3, sep as sep2 } from "path";
18557
- import { DatabaseSync } from "node:sqlite";
18558
-
18559
19939
  // ../packages/persistence/src/internal/sql-text.ts
18560
19940
  function escapeLikePattern(s) {
18561
19941
  return s.replace(/\\/g, "\\\\").replace(/%/g, "\\%").replace(/_/g, "\\_");
@@ -18653,9 +20033,9 @@ function evidenceExists(db, object2) {
18653
20033
  }
18654
20034
 
18655
20035
  // ../packages/persistence/src/ids.ts
18656
- import { createHash as createHash2 } from "crypto";
20036
+ import { createHash } from "crypto";
18657
20037
  function sha256Hex(input) {
18658
- return createHash2("sha256").update(input).digest("hex");
20038
+ return createHash("sha256").update(input).digest("hex");
18659
20039
  }
18660
20040
  function inventoryId(objectType, identityKey) {
18661
20041
  return sha256Hex(canonicalIdentity(["inventory", objectType, identityKey]));
@@ -18749,6 +20129,7 @@ function applyMigrations(db) {
18749
20129
  ensureSyncedAtColumn(db, "audit_events");
18750
20130
  ensureScanLedgerTable(db);
18751
20131
  ensureBlockedDetectionsTable(db);
20132
+ ensureRuleProbeCacheTable(db);
18752
20133
  ensureWriteGateTrigger(db);
18753
20134
  ensureTokenUsageColumns(db);
18754
20135
  reconcileSourceProjectIds(db);
@@ -18888,6 +20269,14 @@ function ensureBlockedDetectionsTable(db) {
18888
20269
  blocked_at INTEGER NOT NULL
18889
20270
  )`);
18890
20271
  }
20272
+ function ensureRuleProbeCacheTable(db) {
20273
+ db.exec(`CREATE TABLE IF NOT EXISTS rule_probe_cache (
20274
+ rule_key TEXT PRIMARY KEY,
20275
+ verdict TEXT NOT NULL,
20276
+ worst_probe_ms REAL NOT NULL,
20277
+ checked_at INTEGER NOT NULL
20278
+ )`);
20279
+ }
18891
20280
 
18892
20281
  // ../packages/persistence/src/paths.ts
18893
20282
  import { chmodSync, mkdirSync } from "fs";
@@ -20109,7 +21498,7 @@ var SqliteEventsRepository = class {
20109
21498
  };
20110
21499
 
20111
21500
  // ../packages/persistence/src/repositories/exceptions.ts
20112
- import { randomUUID as randomUUID2 } from "crypto";
21501
+ import { randomUUID } from "crypto";
20113
21502
 
20114
21503
  // ../packages/persistence/src/internal/sqlite-errors.ts
20115
21504
  var SQLITE_CONSTRAINT_UNIQUE = 2067;
@@ -20187,7 +21576,7 @@ var SqliteExceptionsRepository = class {
20187
21576
  "provider conditions are not supported yet \u2014 a grant with one would never apply"
20188
21577
  );
20189
21578
  }
20190
- const id = randomUUID2();
21579
+ const id = randomUUID();
20191
21580
  const now = Date.now();
20192
21581
  try {
20193
21582
  this.insertExceptionRow(id, input, now);
@@ -20892,7 +22281,7 @@ var SqliteInspectionFindingsRepository = class {
20892
22281
  };
20893
22282
 
20894
22283
  // ../packages/persistence/src/repositories/installed-packs.ts
20895
- import { createHash as createHash3, randomUUID as randomUUID3 } from "crypto";
22284
+ import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
20896
22285
 
20897
22286
  // ../packages/persistence/src/semver.ts
20898
22287
  function parse4(version2) {
@@ -20942,7 +22331,7 @@ function inventorySignature(packs2) {
20942
22331
  return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
20943
22332
  }
20944
22333
  function hashRules(rulesJson) {
20945
- return createHash3("sha1").update(rulesJson).digest("hex");
22334
+ return createHash2("sha1").update(rulesJson).digest("hex");
20946
22335
  }
20947
22336
  function parseVersion(v) {
20948
22337
  const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
@@ -21043,7 +22432,7 @@ var SqliteInstalledPacksRepository = class {
21043
22432
  let behind = false;
21044
22433
  for (const row of rows) {
21045
22434
  const params = {
21046
- id: randomUUID3(),
22435
+ id: randomUUID2(),
21047
22436
  namespace: row.namespace,
21048
22437
  packId: row.packId,
21049
22438
  version: row.version,
@@ -21055,7 +22444,7 @@ var SqliteInstalledPacksRepository = class {
21055
22444
  if (stored === void 0 || !isMirrorDowngrade(row, stored)) {
21056
22445
  this.upsertAvailableStmt.run({
21057
22446
  ...params,
21058
- id: randomUUID3(),
22447
+ id: randomUUID2(),
21059
22448
  recordedBy: meta3?.recordedBy ?? null
21060
22449
  });
21061
22450
  } else {
@@ -21376,7 +22765,7 @@ var SqliteInventoryRepository = class {
21376
22765
  };
21377
22766
 
21378
22767
  // ../packages/persistence/src/repositories/inventory-assets.ts
21379
- import { randomUUID as randomUUID4 } from "crypto";
22768
+ import { randomUUID as randomUUID3 } from "crypto";
21380
22769
  var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
21381
22770
  var VALID_HARNESS_IDS = new Set(HarnessId.options);
21382
22771
  var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
@@ -21856,7 +23245,7 @@ var SqliteInventoryAssetsRepository = class {
21856
23245
  `INSERT INTO file_access_override (id, project_id, path, access, created_at, updated_at)
21857
23246
  VALUES (:id, :projectId, :path, :access, :now, :now)
21858
23247
  ON CONFLICT (project_id, path) DO UPDATE SET access = excluded.access, updated_at = excluded.updated_at`
21859
- ).run({ id: randomUUID4(), projectId, path, access, now: Date.now() });
23248
+ ).run({ id: randomUUID3(), projectId, path, access, now: Date.now() });
21860
23249
  }
21861
23250
  return true;
21862
23251
  }
@@ -21877,7 +23266,7 @@ var SqliteInventoryAssetsRepository = class {
21877
23266
  `INSERT INTO mcp_trust_override (id, asset_id, trust, created_at, updated_at)
21878
23267
  VALUES (:id, :assetId, :trust, :now, :now)
21879
23268
  ON CONFLICT (asset_id) DO UPDATE SET trust = excluded.trust, updated_at = excluded.updated_at`
21880
- ).run({ id: randomUUID4(), assetId, trust, now: Date.now() });
23269
+ ).run({ id: randomUUID3(), assetId, trust, now: Date.now() });
21881
23270
  }
21882
23271
  this.configRowsCache = void 0;
21883
23272
  return "ok";
@@ -22174,7 +23563,7 @@ var SqliteInventoryAssetsRepository = class {
22174
23563
  };
22175
23564
 
22176
23565
  // ../packages/persistence/src/repositories/policies.ts
22177
- import { randomUUID as randomUUID5 } from "crypto";
23566
+ import { randomUUID as randomUUID4 } from "crypto";
22178
23567
  var SqlitePoliciesRepository = class {
22179
23568
  constructor(db) {
22180
23569
  this.db = db;
@@ -22209,7 +23598,7 @@ var SqlitePoliciesRepository = class {
22209
23598
  failOpenTransaction(this.db, () => {
22210
23599
  for (const [category, action] of Object.entries(DEFAULT_ACTIONS)) {
22211
23600
  stmt.run({
22212
- id: randomUUID5(),
23601
+ id: randomUUID4(),
22213
23602
  target: JSON.stringify({ category }),
22214
23603
  action,
22215
23604
  now: Date.now()
@@ -22229,7 +23618,7 @@ var SqlitePoliciesRepository = class {
22229
23618
  `INSERT INTO policies (id, scope, target, action, enabled, created_at, updated_at)
22230
23619
  VALUES (:id, 'global', :target, :action, 1, :now, :now)
22231
23620
  ON CONFLICT(scope, target) DO UPDATE SET action = excluded.action, enabled = 1, updated_at = excluded.updated_at`
22232
- ).run({ id: randomUUID5(), target: JSON.stringify({ category }), action, now });
23621
+ ).run({ id: randomUUID4(), target: JSON.stringify({ category }), action, now });
22233
23622
  }
22234
23623
  // Caps every global per-category policy currently set to block/redact down
22235
23624
  // to warn (see warn-era-cap.ts). Rule-targeted policies are untouched.
@@ -22297,7 +23686,7 @@ var SqlitePolicyCatalogRepository = class {
22297
23686
  };
22298
23687
 
22299
23688
  // ../packages/persistence/src/repositories/project-files.ts
22300
- import { randomUUID as randomUUID6 } from "crypto";
23689
+ import { randomUUID as randomUUID5 } from "crypto";
22301
23690
  var SqliteProjectFilesRepository = class {
22302
23691
  constructor(db) {
22303
23692
  this.db = db;
@@ -22329,7 +23718,7 @@ var SqliteProjectFilesRepository = class {
22329
23718
  const stamp = Math.max(now, maxStamp + 1);
22330
23719
  for (const file2 of scan2.files) {
22331
23720
  this.upsertStmt.run({
22332
- id: randomUUID6(),
23721
+ id: randomUUID5(),
22333
23722
  projectId,
22334
23723
  path: file2.path,
22335
23724
  name: file2.name,
@@ -22343,7 +23732,7 @@ var SqliteProjectFilesRepository = class {
22343
23732
  };
22344
23733
 
22345
23734
  // ../packages/persistence/src/repositories/resolutions.ts
22346
- import { randomUUID as randomUUID7 } from "crypto";
23735
+ import { randomUUID as randomUUID6 } from "crypto";
22347
23736
  var SqliteResolutionsRepository = class {
22348
23737
  constructor(db, now = () => Date.now()) {
22349
23738
  this.db = db;
@@ -22397,7 +23786,7 @@ var SqliteResolutionsRepository = class {
22397
23786
  */
22398
23787
  insertResolution(r) {
22399
23788
  this.insertStmt.run({
22400
- id: randomUUID7(),
23789
+ id: randomUUID6(),
22401
23790
  findingKey: r.findingKey,
22402
23791
  status: FindingStatus.parse(r.status),
22403
23792
  method: ResolutionMethod.parse(r.method),
@@ -22441,6 +23830,35 @@ var SqliteResolutionsRepository = class {
22441
23830
  }
22442
23831
  };
22443
23832
 
23833
+ // ../packages/persistence/src/repositories/rule-probe-cache.ts
23834
+ var SqliteRuleProbeCacheRepository = class {
23835
+ constructor(db) {
23836
+ this.db = db;
23837
+ this.upsertStmt = db.prepare(
23838
+ `INSERT INTO rule_probe_cache (rule_key, verdict, worst_probe_ms, checked_at)
23839
+ VALUES (:ruleKey, :verdict, :worstProbeMs, :checkedAt)
23840
+ ON CONFLICT (rule_key) DO UPDATE SET
23841
+ verdict = excluded.verdict,
23842
+ worst_probe_ms = excluded.worst_probe_ms,
23843
+ checked_at = excluded.checked_at`
23844
+ );
23845
+ this.readStmt = db.prepare(
23846
+ `SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
23847
+ );
23848
+ }
23849
+ db;
23850
+ upsertStmt;
23851
+ readStmt;
23852
+ getVerdict(ruleKey) {
23853
+ return getRow(this.readStmt, { ruleKey });
23854
+ }
23855
+ setVerdict(ruleKey, verdict, worstProbeMs) {
23856
+ failOpenTransaction(this.db, () => {
23857
+ this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
23858
+ });
23859
+ }
23860
+ };
23861
+
22444
23862
  // ../packages/persistence/src/repositories/scan-ledger.ts
22445
23863
  var SqliteScanLedgerRepository = class {
22446
23864
  constructor(db) {
@@ -22851,12 +24269,51 @@ var SqliteSecurityRepository = class {
22851
24269
  };
22852
24270
 
22853
24271
  // ../packages/persistence/src/repositories/shares.ts
22854
- import { randomUUID as randomUUID8 } from "crypto";
22855
- var KIND_ORDER = ["provider", "internal", "ip"];
24272
+ import { randomUUID as randomUUID7 } from "crypto";
24273
+ var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
24274
+ var IN_CHUNK = 500;
24275
+ var KIND_ORDER = ["provider", "internal", "external", "ip"];
24276
+ var PLAINTEXT_TRANSPORT_SQL = "('http', 'ws')";
24277
+ var OVERRIDE_JOIN = `LEFT JOIN egress_decision_override oh ON oh.host = d.host
24278
+ LEFT JOIN egress_decision_override ol ON ol.destination_id = d.id AND ol.host IS NULL`;
22856
24279
  var CALL_SITE_EMBED_CAP = 200;
22857
24280
  function parseNetwork(networkJson) {
22858
24281
  return safeJson(networkJson, null);
22859
24282
  }
24283
+ function capHits(all, mode) {
24284
+ if (all.length <= MAX_EGRESS_CALL_SITES_PER_PROJECT) {
24285
+ return { hits: [...all], droppedFiles: [], truncated: false };
24286
+ }
24287
+ if (mode === "walk") {
24288
+ return {
24289
+ hits: all.slice(0, MAX_EGRESS_CALL_SITES_PER_PROJECT),
24290
+ droppedFiles: [],
24291
+ truncated: true
24292
+ };
24293
+ }
24294
+ const byFile = /* @__PURE__ */ new Map();
24295
+ for (const hit of all) {
24296
+ const bucket = byFile.get(hit.site.file);
24297
+ if (bucket === void 0) byFile.set(hit.site.file, [hit]);
24298
+ else bucket.push(hit);
24299
+ }
24300
+ const hits = [];
24301
+ const droppedFiles = [];
24302
+ for (const [file2, bucket] of byFile) {
24303
+ if (hits.length + bucket.length > MAX_EGRESS_CALL_SITES_PER_PROJECT) droppedFiles.push(file2);
24304
+ else hits.push(...bucket);
24305
+ }
24306
+ return { hits, droppedFiles, truncated: true };
24307
+ }
24308
+ function withoutDroppedFiles(reconcile, droppedFiles) {
24309
+ if (reconcile.mode === "walk" || droppedFiles.length === 0) return reconcile;
24310
+ const dropped = new Set(droppedFiles);
24311
+ return {
24312
+ mode: "ledger",
24313
+ scannedFiles: reconcile.scannedFiles.filter((file2) => !dropped.has(file2)),
24314
+ deletedFiles: reconcile.deletedFiles.filter((file2) => !dropped.has(file2))
24315
+ };
24316
+ }
22860
24317
  function toEndpointSummary(row) {
22861
24318
  return {
22862
24319
  id: row.id,
@@ -22947,13 +24404,15 @@ var SqliteSharesRepository = class {
22947
24404
  const callSites = countScalar(this.db, "SELECT count(*) AS n FROM share_call_site");
22948
24405
  const insecure = countScalar(
22949
24406
  this.db,
22950
- "SELECT count(DISTINCT destination_id) AS n FROM share_endpoint WHERE transport = 'http'"
24407
+ `SELECT count(DISTINCT destination_id) AS n FROM share_endpoint
24408
+ WHERE transport IN ${PLAINTEXT_TRANSPORT_SQL}`
22951
24409
  );
22952
24410
  const needsReview = countScalar(
22953
24411
  this.db,
22954
24412
  `SELECT count(DISTINCT d.id) AS n
22955
24413
  FROM share_destination d
22956
- LEFT JOIN share_endpoint e ON e.destination_id = d.id AND e.transport = 'http'
24414
+ LEFT JOIN share_endpoint e ON e.destination_id = d.id
24415
+ AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
22957
24416
  WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
22958
24417
  );
22959
24418
  const kindCounts = countBy(
@@ -22963,6 +24422,7 @@ var SqliteSharesRepository = class {
22963
24422
  const byKind = {
22964
24423
  provider: kindCounts.get("provider") ?? 0,
22965
24424
  internal: kindCounts.get("internal") ?? 0,
24425
+ external: kindCounts.get("external") ?? 0,
22966
24426
  ip: kindCounts.get("ip") ?? 0
22967
24427
  };
22968
24428
  const trustCounts = countBy(
@@ -23038,23 +24498,316 @@ var SqliteSharesRepository = class {
23038
24498
  // real edit from a no-such-destination.
23039
24499
  /**
23040
24500
  * Set (decision) or clear (null) the egress decision override for a destination.
23041
- * `null` deletes the override row → reverts to the trust default.
24501
+ * `null` deletes the override rows → reverts to the trust default.
24502
+ *
24503
+ * The written row carries both the destination id and its host, so the
24504
+ * decision re-attaches by host after the destination is pruned and
24505
+ * re-detected under a fresh id. Rows written before the host column existed
24506
+ * (host NULL, matched by destination id) are replaced rather than left to
24507
+ * shadow the new one. Runs IMMEDIATE: the host lookup is read-then-write and
24508
+ * would otherwise race a concurrent prune.
23042
24509
  */
23043
24510
  setEgressDecision(destinationId, decision) {
23044
- const exists = this.db.prepare("SELECT 1 FROM share_destination WHERE id = ?").get(destinationId);
23045
- if (exists === void 0) return false;
23046
- if (decision === null) {
23047
- this.db.prepare("DELETE FROM egress_decision_override WHERE destination_id = ?").run(destinationId);
23048
- return true;
24511
+ let existed = false;
24512
+ withTransaction(
24513
+ this.db,
24514
+ () => {
24515
+ const dest = this.db.prepare("SELECT host FROM share_destination WHERE id = ?").get(destinationId);
24516
+ if (dest === void 0) return;
24517
+ existed = true;
24518
+ this.db.prepare(
24519
+ `DELETE FROM egress_decision_override
24520
+ WHERE host = :host OR (destination_id = :destinationId AND host IS NULL)`
24521
+ ).run({ host: dest.host, destinationId });
24522
+ if (decision === null) return;
24523
+ this.db.prepare(
24524
+ `INSERT INTO egress_decision_override
24525
+ (id, destination_id, host, decision, created_at, updated_at)
24526
+ VALUES (:id, :destinationId, :host, :decision, :now, :now)`
24527
+ ).run({
24528
+ id: randomUUID7(),
24529
+ destinationId,
24530
+ host: dest.host,
24531
+ decision,
24532
+ now: Date.now()
24533
+ });
24534
+ },
24535
+ "IMMEDIATE"
24536
+ );
24537
+ return existed;
24538
+ }
24539
+ /**
24540
+ * Record one project's statically-extracted egress: reconcile the previously
24541
+ * stored call sites against this scan, upsert destination → endpoint → call
24542
+ * site for every hit, confirm `last_seen` on everything the project still
24543
+ * references, and drop what no longer has evidence.
24544
+ *
24545
+ * Reconciliation keys on `projectKey` alone; `project` and `projectId` are
24546
+ * display payload and never scope a delete. The whole write is one
24547
+ * transaction: a failure leaves the project's previous inventory exactly as
24548
+ * it was, and THROWS rather than reporting a partial write — callers decide
24549
+ * their own fail-open behavior, and the scanner additionally withholds its
24550
+ * ledger commit so the next scan retries.
24551
+ *
24552
+ * Over-cap input is truncated at a FILE boundary, and the files that lost
24553
+ * their hits are both excluded from the reconcile delete and named in
24554
+ * `droppedFiles`. That pairing is what keeps truncation non-destructive on
24555
+ * the ledger path: a dropped file keeps whatever rows it already had, and its
24556
+ * caller withholds the ledger entry so the next scan reads it again.
24557
+ */
24558
+ recordProjectEgress(input) {
24559
+ const { hits, droppedFiles, truncated } = capHits(input.hits, input.reconcile.mode);
24560
+ const reconcile = withoutDroppedFiles(input.reconcile, droppedFiles);
24561
+ const now = Date.now();
24562
+ let summary = {
24563
+ destinations: 0,
24564
+ endpoints: 0,
24565
+ callSites: 0,
24566
+ truncated,
24567
+ droppedFiles
24568
+ };
24569
+ withTransaction(
24570
+ this.db,
24571
+ () => {
24572
+ const projectId = input.projectId ?? this.knownProjectId(input.projectKey);
24573
+ this.reconcileCallSites(input.projectKey, reconcile);
24574
+ this.upsertHits(input, hits, projectId, now);
24575
+ this.confirmLastSeen(input.projectKey, now);
24576
+ this.pruneOrphans();
24577
+ summary = { ...this.projectTotals(input.projectKey), truncated, droppedFiles };
24578
+ },
24579
+ "IMMEDIATE"
24580
+ );
24581
+ return summary;
24582
+ }
24583
+ // ─── Egress write internals ──────────────────────────────────────────────────
24584
+ /**
24585
+ * Clear the stored call sites this scan is responsible for re-creating.
24586
+ *
24587
+ * Each pipeline may only delete rows its own walker could have produced. The
24588
+ * fs walk behind 'walk' mode never descends into dot-directories, so its
24589
+ * delete excludes dot-path files — those rows are the plugin scanner's to
24590
+ * reconcile, and deleting them here would make the two pipelines erase each
24591
+ * other's rows on every alternating scan. 'ledger' mode names its files
24592
+ * outright and never mass-deletes, so rows the fs walk contributed for files
24593
+ * the scanner skips (vendored, oversize) survive it.
24594
+ */
24595
+ reconcileCallSites(projectKey, reconcile) {
24596
+ if (reconcile.mode === "walk") {
24597
+ const prefix = reconcile.walkedPrefix.replace(/\/+$/, "");
24598
+ this.db.prepare(
24599
+ `DELETE FROM share_call_site
24600
+ WHERE project_key = :key
24601
+ AND (:prefix = '' OR file = :prefix OR file LIKE :subtree ESCAPE '\\')
24602
+ AND file NOT LIKE '.%'
24603
+ AND file NOT LIKE '%/.%'`
24604
+ ).run({ key: projectKey, prefix, subtree: `${escapeLikePattern(prefix)}/%` });
24605
+ return;
24606
+ }
24607
+ const files = [.../* @__PURE__ */ new Set([...reconcile.scannedFiles, ...reconcile.deletedFiles])];
24608
+ for (let i = 0; i < files.length; i += IN_CHUNK) {
24609
+ const chunk = files.slice(i, i + IN_CHUNK);
24610
+ this.db.prepare(
24611
+ `DELETE FROM share_call_site
24612
+ WHERE project_key = ? AND file IN (${placeholders(chunk.length)})`
24613
+ ).run(projectKey, ...chunk);
24614
+ }
24615
+ }
24616
+ /**
24617
+ * Upsert every hit as destination → endpoint → call site. Destinations key on
24618
+ * `host` and endpoints on `(destination_id, method, url)`, both shared across
24619
+ * projects; only the call site carries `project_key`. A destination's `note`
24620
+ * is user-owned and never overwritten. The id caches keep one upsert per
24621
+ * distinct host and endpoint, so the first hit for a host supplies its
24622
+ * classification for this batch.
24623
+ */
24624
+ upsertHits(input, hits, projectId, now) {
24625
+ if (hits.length === 0) return;
24626
+ const destStmt = this.db.prepare(
24627
+ `INSERT INTO share_destination
24628
+ (id, kind, name, host, category, trust, network_json, last_seen, provenance,
24629
+ created_at, updated_at)
24630
+ VALUES (:id, :kind, :name, :host, :category, :trust, :networkJson, :now, 'scan', :now, :now)
24631
+ ON CONFLICT (host) DO UPDATE SET
24632
+ kind = excluded.kind,
24633
+ name = excluded.name,
24634
+ category = excluded.category,
24635
+ trust = excluded.trust,
24636
+ network_json = excluded.network_json,
24637
+ last_seen = excluded.last_seen,
24638
+ updated_at = excluded.updated_at`
24639
+ );
24640
+ const destIdStmt = this.db.prepare("SELECT id FROM share_destination WHERE host = ?");
24641
+ const endpointStmt = this.db.prepare(
24642
+ `INSERT INTO share_endpoint
24643
+ (id, destination_id, method, transport, url, template, data_class, last_seen,
24644
+ created_at, updated_at)
24645
+ VALUES (:id, :destinationId, :method, :transport, :url, :template, :dataClass, :now,
24646
+ :now, :now)
24647
+ ON CONFLICT (destination_id, method, url) DO UPDATE SET
24648
+ transport = excluded.transport,
24649
+ template = excluded.template,
24650
+ data_class = excluded.data_class,
24651
+ last_seen = excluded.last_seen,
24652
+ updated_at = excluded.updated_at`
24653
+ );
24654
+ const endpointIdStmt = this.db.prepare(
24655
+ "SELECT id FROM share_endpoint WHERE destination_id = ? AND method = ? AND url = ?"
24656
+ );
24657
+ const siteStmt = this.db.prepare(
24658
+ `INSERT INTO share_call_site
24659
+ (id, endpoint_id, project, project_key, file, line, snippet, dynamic, vendored,
24660
+ project_id, created_at, updated_at)
24661
+ VALUES (:id, :endpointId, :project, :projectKey, :file, :line, :snippet, :dynamic,
24662
+ :vendored, :projectId, :now, :now)
24663
+ ON CONFLICT (endpoint_id, project_key, file, line) DO UPDATE SET
24664
+ snippet = excluded.snippet,
24665
+ dynamic = excluded.dynamic,
24666
+ vendored = excluded.vendored,
24667
+ project = excluded.project,
24668
+ project_id = COALESCE(excluded.project_id, share_call_site.project_id),
24669
+ updated_at = excluded.updated_at`
24670
+ );
24671
+ const destIds = /* @__PURE__ */ new Map();
24672
+ const endpointIds = /* @__PURE__ */ new Map();
24673
+ for (const hit of hits) {
24674
+ let destinationId = destIds.get(hit.host);
24675
+ if (destinationId === void 0) {
24676
+ destStmt.run({
24677
+ id: randomUUID7(),
24678
+ kind: hit.kind,
24679
+ name: hit.name,
24680
+ host: hit.host,
24681
+ category: hit.category,
24682
+ trust: hit.trust,
24683
+ networkJson: hit.network === null ? null : JSON.stringify(hit.network),
24684
+ now
24685
+ });
24686
+ destinationId = getRow(destIdStmt, [hit.host])?.id ?? "";
24687
+ destIds.set(hit.host, destinationId);
24688
+ }
24689
+ const endpointKey = `${destinationId}\0${hit.method}\0${hit.url}`;
24690
+ let endpointId = endpointIds.get(endpointKey);
24691
+ if (endpointId === void 0) {
24692
+ endpointStmt.run({
24693
+ id: randomUUID7(),
24694
+ destinationId,
24695
+ method: hit.method,
24696
+ transport: hit.transport,
24697
+ url: hit.url,
24698
+ template: boolToInt(hit.template),
24699
+ dataClass: hit.dataClass,
24700
+ now
24701
+ });
24702
+ endpointId = getRow(endpointIdStmt, [destinationId, hit.method, hit.url])?.id ?? "";
24703
+ endpointIds.set(endpointKey, endpointId);
24704
+ }
24705
+ siteStmt.run({
24706
+ id: randomUUID7(),
24707
+ endpointId,
24708
+ project: input.project,
24709
+ projectKey: input.projectKey,
24710
+ file: hit.site.file,
24711
+ line: hit.site.line,
24712
+ snippet: hit.site.snippet,
24713
+ dynamic: boolToInt(hit.site.dynamic),
24714
+ vendored: boolToInt(hit.site.vendored),
24715
+ projectId,
24716
+ now
24717
+ });
23049
24718
  }
24719
+ }
24720
+ /**
24721
+ * The source-project id this project's stored call sites already carry, if
24722
+ * any. Only the pipeline that resolves a source project supplies one; the
24723
+ * other passes null and inherits this, so the link stops flapping between a
24724
+ * real id and NULL depending on which pipeline ran last. The value is a
24725
+ * per-project attribute stored redundantly on each row, so any row's is
24726
+ * representative.
24727
+ */
24728
+ knownProjectId(projectKey) {
24729
+ return getRow(
24730
+ this.db.prepare(
24731
+ `SELECT project_id AS projectId FROM share_call_site
24732
+ WHERE project_key = ? AND project_id IS NOT NULL LIMIT 1`
24733
+ ),
24734
+ [projectKey]
24735
+ )?.projectId ?? null;
24736
+ }
24737
+ /**
24738
+ * Stamp `last_seen` on every endpoint and destination this project still
24739
+ * references — including rows the scan preserved rather than re-wrote, so a
24740
+ * ledger-skipped file's references don't decay into "stale" on the page.
24741
+ */
24742
+ confirmLastSeen(projectKey, now) {
23050
24743
  this.db.prepare(
23051
- `INSERT INTO egress_decision_override (id, destination_id, decision, created_at, updated_at)
23052
- VALUES (:id, :destinationId, :decision, :now, :now)
23053
- ON CONFLICT (destination_id) DO UPDATE SET
23054
- decision = excluded.decision,
23055
- updated_at = excluded.updated_at`
23056
- ).run({ id: randomUUID8(), destinationId, decision, now: Date.now() });
23057
- return true;
24744
+ `UPDATE share_endpoint SET last_seen = :now, updated_at = :now
24745
+ WHERE id IN (SELECT DISTINCT endpoint_id FROM share_call_site WHERE project_key = :key)`
24746
+ ).run({ now, key: projectKey });
24747
+ this.db.prepare(
24748
+ `UPDATE share_destination SET last_seen = :now, updated_at = :now
24749
+ WHERE id IN (SELECT DISTINCT e.destination_id
24750
+ FROM share_endpoint e
24751
+ JOIN share_call_site c ON c.endpoint_id = e.id
24752
+ WHERE c.project_key = :key)`
24753
+ ).run({ now, key: projectKey });
24754
+ }
24755
+ /**
24756
+ * Drop rows left without evidence: endpoints with no call site, then
24757
+ * destinations with no endpoint. Call sites are the only evidence either one
24758
+ * has, so a row that lost its last one belongs to no project any more.
24759
+ *
24760
+ * Overrides are deleted between the two steps, and only the ones written
24761
+ * before the host column existed. Those match a destination by id alone;
24762
+ * because the id link is released on delete rather than cascading, leaving
24763
+ * them would accumulate rows that match neither join arm and that nothing can
24764
+ * reach again. Host-bearing rows deliberately survive — the host is what
24765
+ * re-attaches a user's decision when the destination comes back.
24766
+ */
24767
+ pruneOrphans() {
24768
+ this.db.exec(
24769
+ `DELETE FROM share_endpoint
24770
+ WHERE NOT EXISTS (SELECT 1 FROM share_call_site c WHERE c.endpoint_id = share_endpoint.id)`
24771
+ );
24772
+ this.db.exec(
24773
+ `DELETE FROM egress_decision_override
24774
+ WHERE host IS NULL
24775
+ AND destination_id IN (
24776
+ SELECT d.id FROM share_destination d
24777
+ WHERE NOT EXISTS (SELECT 1 FROM share_endpoint e WHERE e.destination_id = d.id))`
24778
+ );
24779
+ this.db.exec(
24780
+ `DELETE FROM share_destination
24781
+ WHERE NOT EXISTS (
24782
+ SELECT 1 FROM share_endpoint e WHERE e.destination_id = share_destination.id)`
24783
+ );
24784
+ }
24785
+ /**
24786
+ * Live totals for one project. Destinations and endpoints are shared across
24787
+ * projects and carry no project column, so both are counted through the call
24788
+ * sites that reference them.
24789
+ */
24790
+ projectTotals(projectKey) {
24791
+ return {
24792
+ destinations: countScalar(
24793
+ this.db,
24794
+ `SELECT count(DISTINCT e.destination_id) AS n
24795
+ FROM share_endpoint e
24796
+ JOIN share_call_site c ON c.endpoint_id = e.id
24797
+ WHERE c.project_key = ?`,
24798
+ [projectKey]
24799
+ ),
24800
+ endpoints: countScalar(
24801
+ this.db,
24802
+ "SELECT count(DISTINCT endpoint_id) AS n FROM share_call_site WHERE project_key = ?",
24803
+ [projectKey]
24804
+ ),
24805
+ callSites: countScalar(
24806
+ this.db,
24807
+ "SELECT count(*) AS n FROM share_call_site WHERE project_key = ?",
24808
+ [projectKey]
24809
+ )
24810
+ };
23058
24811
  }
23059
24812
  // ─── Raw fetchers ────────────────────────────────────────────────────────────
23060
24813
  mapDestRow(r) {
@@ -23074,7 +24827,8 @@ var SqliteSharesRepository = class {
23074
24827
  fetchDestinations(q, kinds, reviewOnly = false) {
23075
24828
  const cols = `d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
23076
24829
  d.network_json AS networkJson, d.last_seen AS lastSeenMs,
23077
- d.created_at AS createdAt, o.decision AS overrideDecision`;
24830
+ d.created_at AS createdAt,
24831
+ COALESCE(oh.decision, ol.decision) AS overrideDecision`;
23078
24832
  const conditions = [];
23079
24833
  const params = [];
23080
24834
  if (kinds && kinds.length > 0) {
@@ -23085,7 +24839,8 @@ var SqliteSharesRepository = class {
23085
24839
  conditions.push(
23086
24840
  `(d.trust IN ('unverified', 'ip')
23087
24841
  OR EXISTS (SELECT 1 FROM share_endpoint re
23088
- WHERE re.destination_id = d.id AND re.transport = 'http'))`
24842
+ WHERE re.destination_id = d.id
24843
+ AND re.transport IN ${PLAINTEXT_TRANSPORT_SQL}))`
23089
24844
  );
23090
24845
  }
23091
24846
  let sql;
@@ -23098,7 +24853,7 @@ var SqliteSharesRepository = class {
23098
24853
  params.push(pattern, pattern, pattern, pattern, pattern);
23099
24854
  sql = `SELECT DISTINCT ${cols}
23100
24855
  FROM share_destination d
23101
- LEFT JOIN egress_decision_override o ON o.destination_id = d.id
24856
+ ${OVERRIDE_JOIN}
23102
24857
  LEFT JOIN share_endpoint e ON e.destination_id = d.id
23103
24858
  LEFT JOIN share_call_site c ON c.endpoint_id = e.id
23104
24859
  ${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
@@ -23106,7 +24861,7 @@ var SqliteSharesRepository = class {
23106
24861
  } else {
23107
24862
  sql = `SELECT ${cols}
23108
24863
  FROM share_destination d
23109
- LEFT JOIN egress_decision_override o ON o.destination_id = d.id
24864
+ ${OVERRIDE_JOIN}
23110
24865
  ${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
23111
24866
  ORDER BY d.created_at ASC, d.id ASC`;
23112
24867
  }
@@ -23121,9 +24876,9 @@ var SqliteSharesRepository = class {
23121
24876
  this.db.prepare(
23122
24877
  `SELECT d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
23123
24878
  d.network_json AS networkJson, d.last_seen AS lastSeenMs,
23124
- o.decision AS overrideDecision
24879
+ COALESCE(oh.decision, ol.decision) AS overrideDecision
23125
24880
  FROM share_destination d
23126
- LEFT JOIN egress_decision_override o ON o.destination_id = d.id
24881
+ ${OVERRIDE_JOIN}
23127
24882
  WHERE d.id = ?`
23128
24883
  ),
23129
24884
  [destinationId]
@@ -23333,7 +25088,7 @@ function backupLegacyStore(file2) {
23333
25088
  }
23334
25089
  function openLocalDatabase(dir) {
23335
25090
  ensureDataDirSync(dir);
23336
- const file2 = join3(dir, DB_FILENAME);
25091
+ const file2 = join2(dir, DB_FILENAME);
23337
25092
  let db = openWithPragmas(file2);
23338
25093
  if (isForeignSqliteLineage(db)) {
23339
25094
  db.close();
@@ -23352,6 +25107,7 @@ function openLocalDatabase(dir) {
23352
25107
  const scanLedger = new SqliteScanLedgerRepository(db);
23353
25108
  const exceptions = new SqliteExceptionsRepository(db);
23354
25109
  const resolutions = new SqliteResolutionsRepository(db);
25110
+ const ruleProbeCache = new SqliteRuleProbeCacheRepository(db);
23355
25111
  const security = new SqliteSecurityRepository(db);
23356
25112
  const detections = new SqliteDetectionsRepository(db);
23357
25113
  const shares = new SqliteSharesRepository(db);
@@ -23410,7 +25166,7 @@ function openLocalDatabase(dir) {
23410
25166
  const definitionId = definitionIds.get(`${finding.ruleId}@${finding.version}`);
23411
25167
  if (!definitionId) continue;
23412
25168
  inspectionFindings.insertFinding({
23413
- id: randomUUID9(),
25169
+ id: randomUUID8(),
23414
25170
  auditEventId: record2.scanEvent.id,
23415
25171
  inspectionDefinitionId: definitionId,
23416
25172
  span: finding.span,
@@ -23443,8 +25199,8 @@ function openLocalDatabase(dir) {
23443
25199
  }
23444
25200
  function reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
23445
25201
  try {
23446
- const headPosix = headRoot.split(sep2).join("/");
23447
- const worktreePosix = worktreeRoot.split(sep2).join("/");
25202
+ const headPosix = headRoot.split(sep).join("/");
25203
+ const worktreePosix = worktreeRoot.split(sep).join("/");
23448
25204
  const exactArm = worktreeRoot === headRoot ? "" : "url IN (:worktreePosix, :worktreeWin) OR ";
23449
25205
  const stale = db.prepare(
23450
25206
  `SELECT id FROM source_project
@@ -23489,6 +25245,7 @@ function openLocalDatabase(dir) {
23489
25245
  scanLedger,
23490
25246
  exceptions,
23491
25247
  resolutions,
25248
+ ruleProbeCache,
23492
25249
  security,
23493
25250
  detections,
23494
25251
  shares,
@@ -23520,12 +25277,12 @@ function openLocalDatabase(dir) {
23520
25277
 
23521
25278
  // ../packages/persistence/src/fingerprint.ts
23522
25279
  import { createHmac, randomBytes } from "crypto";
23523
- import { chmodSync as chmodSync2, readFileSync as readFileSync3, renameSync as renameSync2, writeFileSync } from "fs";
23524
- import { join as join4 } from "path";
25280
+ import { chmodSync as chmodSync2, readFileSync as readFileSync2, renameSync as renameSync2, writeFileSync } from "fs";
25281
+ import { join as join3 } from "path";
23525
25282
  var KEY_FILENAME = "exception.key";
23526
25283
  var KEY_MATERIAL_BYTES = 32;
23527
25284
  function keyFilePath(dataDir2) {
23528
- return join4(dataDir2, KEY_FILENAME);
25285
+ return join3(dataDir2, KEY_FILENAME);
23529
25286
  }
23530
25287
  function parseKeyFile(raw) {
23531
25288
  const parsed = JSON.parse(raw);
@@ -23562,7 +25319,7 @@ function writeKeyFile(dataDir2, key) {
23562
25319
  function readFingerprintKey(dataDir2) {
23563
25320
  let raw;
23564
25321
  try {
23565
- raw = readFileSync3(keyFilePath(dataDir2), "utf8");
25322
+ raw = readFileSync2(keyFilePath(dataDir2), "utf8");
23566
25323
  } catch (err) {
23567
25324
  if (err.code === "ENOENT") return null;
23568
25325
  throw err instanceof Error ? err : new Error(String(err));
@@ -23595,30 +25352,48 @@ function fingerprintValue(key, raw) {
23595
25352
  import { chmodSync as chmodSync3, mkdirSync as mkdirSync2, renameSync as renameSync3 } from "fs";
23596
25353
  import { chmod, mkdir } from "fs/promises";
23597
25354
  import { homedir as homedir3 } from "os";
23598
- import { join as join5 } from "path";
25355
+ import { join as join4 } from "path";
23599
25356
  function defaultDataDir() {
23600
- return join5(homedir3(), ".aka");
25357
+ return join4(homedir3(), ".aka");
23601
25358
  }
23602
25359
  function settingsDir(base = defaultDataDir()) {
23603
- return join5(base, "settings");
25360
+ return join4(base, "settings");
23604
25361
  }
23605
25362
  function dataDir(base = defaultDataDir()) {
23606
- return join5(base, "data");
25363
+ return join4(base, "data");
23607
25364
  }
23608
25365
  function dbPath(base = defaultDataDir()) {
23609
- return join5(dataDir(base), "aka.db");
25366
+ return join4(dataDir(base), "aka.db");
23610
25367
  }
23611
25368
  function ensureLayoutDirSync(dir = defaultDataDir()) {
23612
25369
  ensureDataDirSync(dir);
23613
25370
  }
23614
25371
 
23615
25372
  // ../packages/persistence/src/settings.ts
23616
- import { readFileSync as readFileSync4, renameSync as renameSync4, writeFileSync as writeFileSync2 } from "fs";
23617
- import { join as join6 } from "path";
25373
+ import { readFileSync as readFileSync3, renameSync as renameSync4, writeFileSync as writeFileSync2 } from "fs";
25374
+ import { join as join5 } from "path";
25375
+ function readWorkspaceSettings(base = defaultDataDir()) {
25376
+ const record2 = readJson(join5(settingsDir(base), "settings.json"));
25377
+ if (!record2) return defaultWorkspaceSettings();
25378
+ try {
25379
+ return WorkspaceSettings.parse(record2);
25380
+ } catch {
25381
+ return defaultWorkspaceSettings();
25382
+ }
25383
+ }
25384
+ function readJson(file2) {
25385
+ let text;
25386
+ try {
25387
+ text = readFileSync3(file2, "utf8");
25388
+ } catch {
25389
+ return null;
25390
+ }
25391
+ return parseJsonObject(text) ?? null;
25392
+ }
23618
25393
 
23619
25394
  // ../packages/persistence/src/warn-era-cap.ts
23620
25395
  import { existsSync as existsSync3, writeFileSync as writeFileSync3 } from "fs";
23621
- import { join as join7 } from "path";
25396
+ import { join as join6 } from "path";
23622
25397
 
23623
25398
  // ../packages/plugin-sdk/src/provider-env.ts
23624
25399
  var booleanish = external_exports.string().optional().transform((v) => {
@@ -23639,9 +25414,9 @@ var providerEnvShape = {
23639
25414
  var ProviderEnvSchema = external_exports.object(providerEnvShape);
23640
25415
 
23641
25416
  // ../packages/plugin-sdk/src/config-inventory.ts
23642
- import { readdirSync as readdirSync2, readFileSync as readFileSync6, realpathSync, statSync as statSync3 } from "fs";
25417
+ import { readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync2 } from "fs";
23643
25418
  import { homedir as homedir4 } from "os";
23644
- import { basename as basename2, join as join9 } from "path";
25419
+ import { basename as basename2, join as join8 } from "path";
23645
25420
 
23646
25421
  // ../rules/code-flaws/auth-jwt-no-verify.json
23647
25422
  var auth_jwt_no_verify_default = {
@@ -25676,8 +27451,8 @@ function bundledDetections() {
25676
27451
  }
25677
27452
 
25678
27453
  // ../packages/plugin-sdk/src/repo.ts
25679
- import { existsSync as existsSync4, readFileSync as readFileSync5, statSync as statSync2 } from "fs";
25680
- import { basename, dirname as dirname2, isAbsolute, join as join8, sep as sep3 } from "path";
27454
+ import { existsSync as existsSync4, readFileSync as readFileSync4, statSync } from "fs";
27455
+ import { basename, dirname as dirname2, isAbsolute, join as join7, sep as sep2 } from "path";
25681
27456
  function resolveRepoIdentity(cwd) {
25682
27457
  try {
25683
27458
  const root = findGitRoot(cwd);
@@ -25689,7 +27464,7 @@ function resolveRepoIdentity(cwd) {
25689
27464
  // The path fallback is normalized to posix separators (a no-op outside
25690
27465
  // win32) so the persistence layer's `/`-separated checkout-path patterns
25691
27466
  // (the ghost sweep + the read-side worktree filter) match it as written.
25692
- url: url2 ?? headRoot.split(sep3).join("/"),
27467
+ url: url2 ?? headRoot.split(sep2).join("/"),
25693
27468
  name: (url2 ? slugFromUrl(url2) : void 0) ?? basename(headRoot)
25694
27469
  };
25695
27470
  } catch {
@@ -25715,36 +27490,36 @@ function resolveHeadRoot(cwd) {
25715
27490
  function findGitRoot(start) {
25716
27491
  let dir = start;
25717
27492
  for (; ; ) {
25718
- if (existsSync4(join8(dir, ".git"))) return dir;
27493
+ if (existsSync4(join7(dir, ".git"))) return dir;
25719
27494
  const parent = dirname2(dir);
25720
27495
  if (parent === dir) return void 0;
25721
27496
  dir = parent;
25722
27497
  }
25723
27498
  }
25724
27499
  function resolveGitContext(root) {
25725
- const dotGit = join8(root, ".git");
27500
+ const dotGit = join7(root, ".git");
25726
27501
  try {
25727
- if (statSync2(dotGit).isDirectory()) {
25728
- return { configPath: join8(dotGit, "config"), headRoot: root };
27502
+ if (statSync(dotGit).isDirectory()) {
27503
+ return { configPath: join7(dotGit, "config"), headRoot: root };
25729
27504
  }
25730
27505
  } catch {
25731
27506
  return void 0;
25732
27507
  }
25733
27508
  const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
25734
27509
  if (!target) return void 0;
25735
- const gitdir = isAbsolute(target) ? target : join8(root, target);
25736
- if (existsSync4(join8(gitdir, "config"))) {
25737
- return { configPath: join8(gitdir, "config"), headRoot: root };
27510
+ const gitdir = isAbsolute(target) ? target : join7(root, target);
27511
+ if (existsSync4(join7(gitdir, "config"))) {
27512
+ return { configPath: join7(gitdir, "config"), headRoot: root };
25738
27513
  }
25739
- const commonRaw = safeRead(join8(gitdir, "commondir"))?.trim();
27514
+ const commonRaw = safeRead(join7(gitdir, "commondir"))?.trim();
25740
27515
  if (!commonRaw) return void 0;
25741
- const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join8(gitdir, commonRaw);
27516
+ const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join7(gitdir, commonRaw);
25742
27517
  const headRoot = basename(commonGitDir) === ".git" ? dirname2(commonGitDir) : root;
25743
- return { configPath: join8(commonGitDir, "config"), headRoot };
27518
+ return { configPath: join7(commonGitDir, "config"), headRoot };
25744
27519
  }
25745
27520
  function safeRead(path) {
25746
27521
  try {
25747
- return readFileSync5(path, "utf8");
27522
+ return readFileSync4(path, "utf8");
25748
27523
  } catch {
25749
27524
  return void 0;
25750
27525
  }
@@ -25782,23 +27557,57 @@ function slugFromUrl(url2) {
25782
27557
  }
25783
27558
 
25784
27559
  // ../packages/plugin-sdk/src/events.ts
25785
- import { createHash as createHash4, randomUUID as randomUUID10 } from "crypto";
27560
+ import { createHash as createHash3, randomUUID as randomUUID9 } from "crypto";
25786
27561
 
25787
27562
  // ../packages/plugin-sdk/src/finding-key.ts
25788
- import { createHash as createHash5 } from "crypto";
27563
+ import { createHash as createHash4 } from "crypto";
25789
27564
 
25790
27565
  // ../packages/plugin-sdk/src/inventory-resolver.ts
25791
27566
  import { arch, hostname as hostname3, platform, release } from "os";
25792
27567
 
25793
27568
  // ../packages/plugin-sdk/src/nudge.ts
25794
- import { mkdirSync as mkdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync4 } from "fs";
25795
- import { join as join10 } from "path";
27569
+ import { mkdirSync as mkdirSync3, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
27570
+ import { join as join9 } from "path";
27571
+
27572
+ // ../packages/plugin-sdk/src/paths.ts
27573
+ import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
27574
+ import { basename as basename3, dirname as dirname3, sep as sep3 } from "path";
27575
+ function toPosix(path) {
27576
+ return path.split(sep3).join("/");
27577
+ }
27578
+ var MAX_PROJECT_ROOT_LEVELS = 40;
27579
+ function findProjectRoot(startDir, recognizeMarker) {
27580
+ let dir = startDir;
27581
+ let root = null;
27582
+ for (let level = 0; level < MAX_PROJECT_ROOT_LEVELS; level += 1) {
27583
+ if (directoryHasMarker(dir, recognizeMarker)) root = dir;
27584
+ const parent = dirname3(dir);
27585
+ if (parent === dir) break;
27586
+ dir = parent;
27587
+ }
27588
+ return root ?? startDir;
27589
+ }
27590
+ function directoryHasMarker(dir, recognizeMarker) {
27591
+ try {
27592
+ for (const entry of readdirSync2(dir, { withFileTypes: true })) {
27593
+ if (entry.isFile() && recognizeMarker(entry.name) != null) return true;
27594
+ }
27595
+ } catch {
27596
+ return false;
27597
+ }
27598
+ return false;
27599
+ }
27600
+ function resolveNonGitProject(startDir, recognizeMarker) {
27601
+ const projectRoot = findProjectRoot(startDir, recognizeMarker);
27602
+ const realRoot = realpathSync2(projectRoot);
27603
+ return { root: projectRoot, projectKey: `path:${realRoot}`, project: basename3(realRoot) };
27604
+ }
25796
27605
 
25797
27606
  // ../packages/plugin-sdk/src/project-files.ts
25798
- var import_ignore2 = __toESM(require_ignore(), 1);
25799
- import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync8 } from "fs";
25800
- import { basename as basename3, join as join11, relative as relative2, sep as sep4 } from "path";
25801
- var SKIP_DIRS2 = /* @__PURE__ */ new Set([
27607
+ var import_ignore = __toESM(require_ignore(), 1);
27608
+ import { existsSync as existsSync5, readdirSync as readdirSync3, readFileSync as readFileSync7 } from "fs";
27609
+ import { basename as basename4, join as join10, relative, sep as sep4 } from "path";
27610
+ var SKIP_DIRS = /* @__PURE__ */ new Set([
25802
27611
  ".git",
25803
27612
  "node_modules",
25804
27613
  ".pnpm-store",
@@ -25815,10 +27624,10 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
25815
27624
  ".cache"
25816
27625
  ]);
25817
27626
  var MAX_FILES = 2e4;
25818
- function readIgnoreLayer2(dir) {
27627
+ function readIgnoreLayer(dir) {
25819
27628
  try {
25820
- const content = readFileSync8(join11(dir, ".gitignore"), "utf8");
25821
- return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
27629
+ const content = readFileSync7(join10(dir, ".gitignore"), "utf8");
27630
+ return { base: dir, matcher: (0, import_ignore.default)().add(content) };
25822
27631
  } catch {
25823
27632
  return void 0;
25824
27633
  }
@@ -25826,7 +27635,7 @@ function readIgnoreLayer2(dir) {
25826
27635
  function isIgnored(layers, absPath, isDir) {
25827
27636
  let ignored = false;
25828
27637
  for (const layer of layers) {
25829
- const rel = relative2(layer.base, absPath).split(sep4).join("/") + (isDir ? "/" : "");
27638
+ const rel = relative(layer.base, absPath).split(sep4).join("/") + (isDir ? "/" : "");
25830
27639
  const verdict = layer.matcher.test(rel);
25831
27640
  if (verdict.ignored) ignored = true;
25832
27641
  else if (verdict.unignored) ignored = false;
@@ -25892,13 +27701,13 @@ function resolveProjectFiles(cwd) {
25892
27701
  walk.lostSubtree = true;
25893
27702
  return false;
25894
27703
  }
25895
- const layer = readIgnoreLayer2(dir);
27704
+ const layer = readIgnoreLayer(dir);
25896
27705
  const dirLayers = layer ? [...layers, layer] : layers;
25897
27706
  for (const entry of dirents) {
25898
- const fullPath = join11(dir, entry.name);
27707
+ const fullPath = join10(dir, entry.name);
25899
27708
  if (entry.isDirectory()) {
25900
- if (SKIP_DIRS2.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
25901
- if (existsSync5(join11(fullPath, ".git"))) continue;
27709
+ if (SKIP_DIRS.has(entry.name) || isIgnored(dirLayers, fullPath, true)) continue;
27710
+ if (existsSync5(join10(fullPath, ".git"))) continue;
25902
27711
  if (visit3(fullPath, dirLayers)) return true;
25903
27712
  continue;
25904
27713
  }
@@ -25906,10 +27715,10 @@ function resolveProjectFiles(cwd) {
25906
27715
  if (entry.name === ".git") continue;
25907
27716
  if (isIgnored(dirLayers, fullPath, false)) continue;
25908
27717
  if (files.length >= MAX_FILES) return true;
25909
- const relPath = relative2(root, fullPath).split(sep4).join("/");
27718
+ const relPath = relative(root, fullPath).split(sep4).join("/");
25910
27719
  files.push({
25911
27720
  path: relPath,
25912
- name: basename3(entry.name),
27721
+ name: basename4(entry.name),
25913
27722
  origin: classifyOrigin(relPath, entry.name),
25914
27723
  defaultAccess: "approved"
25915
27724
  });
@@ -25933,20 +27742,206 @@ function resolveProjectFiles(cwd) {
25933
27742
  }
25934
27743
 
25935
27744
  // ../packages/plugin-sdk/src/runtime.ts
25936
- import { randomUUID as randomUUID11 } from "crypto";
27745
+ import { randomUUID as randomUUID10 } from "crypto";
25937
27746
 
25938
27747
  // ../packages/plugin-sdk/src/suppressions.ts
25939
27748
  var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
25940
27749
 
25941
27750
  // ../packages/plugin-sdk/src/throttle.ts
25942
- import { mkdirSync as mkdirSync4, statSync as statSync4, writeFileSync as writeFileSync5 } from "fs";
25943
- import { join as join12 } from "path";
27751
+ import { mkdirSync as mkdirSync4, statSync as statSync3, writeFileSync as writeFileSync5 } from "fs";
27752
+ import { join as join11 } from "path";
27753
+
27754
+ // ../packages/local-ops/src/egress-record.ts
27755
+ function recordProjectEgress(db, target, egress, base = defaultDataDir()) {
27756
+ try {
27757
+ if (!readWorkspaceSettings(base).dataSharesInPlace) return null;
27758
+ const abs = resolve(target);
27759
+ const targetDir = statSync4(abs).isDirectory() ? abs : dirname4(abs);
27760
+ const identity = resolveRepoIdentity(abs);
27761
+ const worktreeRoot = resolveWorktreeRoot(abs);
27762
+ let root;
27763
+ let projectKey;
27764
+ let project;
27765
+ let projectId;
27766
+ if (identity && worktreeRoot) {
27767
+ root = worktreeRoot;
27768
+ projectKey = `git:${identity.url}`;
27769
+ project = identity.name;
27770
+ projectId = db.sourceProject.upsert({
27771
+ url: identity.url,
27772
+ name: identity.name,
27773
+ attributes: {}
27774
+ });
27775
+ } else {
27776
+ ({ root, projectKey, project } = resolveNonGitProject(targetDir, manifestKindOf));
27777
+ projectId = null;
27778
+ }
27779
+ const files = [];
27780
+ for (const hit of egress.files) {
27781
+ const file2 = toPosix(relative2(root, hit.file));
27782
+ if (file2 === "" || file2.startsWith("../")) continue;
27783
+ if (file2.startsWith(".") || file2.includes("/.")) continue;
27784
+ files.push({ ...hit, file: file2, vendored: isVendoredPath(file2) });
27785
+ }
27786
+ const summary = db.shares.recordProjectEgress({
27787
+ projectKey,
27788
+ project,
27789
+ projectId,
27790
+ reconcile: { mode: "walk", walkedPrefix: toPosix(relative2(root, abs)) },
27791
+ hits: resolveEgress(files)
27792
+ });
27793
+ return { ...summary, project };
27794
+ } catch {
27795
+ return null;
27796
+ }
27797
+ }
27798
+
27799
+ // ../packages/local-ops/src/fs-scan.ts
27800
+ import { createHash as createHash5, randomUUID as randomUUID11 } from "crypto";
27801
+ import { readdirSync as readdirSync4, readFileSync as readFileSync8, statSync as statSync5 } from "fs";
27802
+ import { basename as basename5, extname, join as join12, relative as relative3 } from "path";
27803
+ var import_ignore2 = __toESM(require_ignore(), 1);
27804
+ var AKAIGNORE_FILENAME = ".akaignore";
27805
+ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
27806
+ "node_modules",
27807
+ ".git",
27808
+ "dist",
27809
+ ".next",
27810
+ "coverage",
27811
+ ".turbo",
27812
+ "build",
27813
+ "out"
27814
+ ]);
27815
+ var MAX_BYTES = 1e6;
27816
+ function readIgnoreLayer2(dir, filename) {
27817
+ try {
27818
+ const content = readFileSync8(join12(dir, filename), "utf8");
27819
+ return { base: dir, matcher: (0, import_ignore2.default)().add(content) };
27820
+ } catch {
27821
+ return void 0;
27822
+ }
27823
+ }
27824
+ function evaluate(layers, absPath, isDir) {
27825
+ let state = "unmatched";
27826
+ for (const layer of layers) {
27827
+ const rel = toPosix(relative3(layer.base, absPath)) + (isDir ? "/" : "");
27828
+ const verdict = layer.matcher.test(rel);
27829
+ if (verdict.ignored) state = "ignored";
27830
+ else if (verdict.unignored) state = "unignored";
27831
+ }
27832
+ return state;
27833
+ }
27834
+ function* collectFiles(target) {
27835
+ let st;
27836
+ try {
27837
+ st = statSync5(target);
27838
+ } catch {
27839
+ return;
27840
+ }
27841
+ if (st.isFile()) {
27842
+ if (st.size <= MAX_BYTES) yield { path: target, gitignored: false };
27843
+ return;
27844
+ }
27845
+ if (!st.isDirectory()) return;
27846
+ yield* visit(target, [], [], false);
27847
+ }
27848
+ function* visit(dir, markLayers, skipLayers, inIgnoredDir) {
27849
+ const markLayer = inIgnoredDir ? void 0 : readIgnoreLayer2(dir, ".gitignore");
27850
+ const dirMarkLayers = markLayer ? [...markLayers, markLayer] : markLayers;
27851
+ const skipLayer = readIgnoreLayer2(dir, AKAIGNORE_FILENAME);
27852
+ const dirSkipLayers = skipLayer ? [...skipLayers, skipLayer] : skipLayers;
27853
+ for (const entry of readdirSync4(dir, { withFileTypes: true })) {
27854
+ const path = join12(dir, entry.name);
27855
+ if (entry.isDirectory()) {
27856
+ const skipState = evaluate(dirSkipLayers, path, true);
27857
+ if (skipState !== "unignored" && (SKIP_DIRS2.has(entry.name) || entry.name.startsWith(".") || skipState === "ignored")) {
27858
+ continue;
27859
+ }
27860
+ const dirIgnored = inIgnoredDir || evaluate(dirMarkLayers, path, true) === "ignored";
27861
+ yield* visit(path, dirMarkLayers, dirSkipLayers, dirIgnored);
27862
+ } else if (entry.isFile()) {
27863
+ if (evaluate(dirSkipLayers, path, false) === "ignored") continue;
27864
+ let size;
27865
+ try {
27866
+ size = statSync5(path).size;
27867
+ } catch {
27868
+ continue;
27869
+ }
27870
+ if (size > MAX_BYTES) continue;
27871
+ yield {
27872
+ path,
27873
+ gitignored: inIgnoredDir || evaluate(dirMarkLayers, path, false) === "ignored"
27874
+ };
27875
+ }
27876
+ }
27877
+ }
27878
+ function extractFileEgress(file2, text) {
27879
+ if (text.includes("\0")) return null;
27880
+ const name = basename5(file2);
27881
+ if (LOCKFILE_BASENAMES.has(name)) return null;
27882
+ const kind = manifestKindOf(name);
27883
+ const sdkHits = kind === null ? [] : extractManifestSdks(text, kind);
27884
+ const endpoints = kind === null && EGRESS_CODE_EXTENSIONS.has(extname(file2)) ? extractEgress(text) : [];
27885
+ if (endpoints.length === 0 && sdkHits.length === 0) return null;
27886
+ return { file: file2, vendored: isVendoredPath(toPosix(file2)), endpoints, sdkHits };
27887
+ }
27888
+ function scanPathIntoStore(db, target, opts = {}) {
27889
+ let scanned = 0;
27890
+ let findingCount = 0;
27891
+ const files = [];
27892
+ const egressFiles = [];
27893
+ for (const { path: file2, gitignored } of collectFiles(target)) {
27894
+ let text;
27895
+ try {
27896
+ text = readFileSync8(file2, "utf8");
27897
+ } catch {
27898
+ continue;
27899
+ }
27900
+ scanned++;
27901
+ const fileEgress = extractFileEgress(file2, text);
27902
+ if (fileEgress) egressFiles.push(fileEgress);
27903
+ const matches = scan(text, opts.rules);
27904
+ if (matches.length === 0) continue;
27905
+ const eventId = randomUUID11();
27906
+ const metadata = { filePath: file2 };
27907
+ if (gitignored) metadata.gitignored = true;
27908
+ const event = {
27909
+ id: eventId,
27910
+ sourceTool: opts.sourceTool ?? "cli",
27911
+ kind: "code_change",
27912
+ occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
27913
+ contentHash: createHash5("sha256").update(text).digest("hex"),
27914
+ content: redact(text, matches),
27915
+ // store the REDACTED file, never the raw secret
27916
+ metadata
27917
+ };
27918
+ const findings = matches.map((m) => ({
27919
+ id: randomUUID11(),
27920
+ eventId,
27921
+ ruleId: m.ruleId,
27922
+ category: m.category,
27923
+ severity: m.severity,
27924
+ span: m.span,
27925
+ maskedMatch: maskMatch(m.rawMatch),
27926
+ // Per-pack action (monitor-by-default) when the installed snapshot supplies
27927
+ // one, else the per-category fallback — mirrors the live path's resolveAction.
27928
+ actionTaken: opts.ruleActions?.get(m.ruleId) ?? DEFAULT_ACTIONS[m.category],
27929
+ confidence: m.confidence
27930
+ }));
27931
+ db.recordCapture(event, findings);
27932
+ files.push({ path: file2, gitignored, findings });
27933
+ findingCount += findings.length;
27934
+ }
27935
+ return { scanned, findings: findingCount, files, egress: { files: egressFiles } };
27936
+ }
25944
27937
 
25945
27938
  // ../packages/local-ops/src/project-inventory.ts
27939
+ import { statSync as statSync6 } from "fs";
27940
+ import { resolve as resolve2 } from "path";
25946
27941
  function recordProjectInventory(db, target) {
25947
27942
  try {
25948
- const abs = resolve(target);
25949
- statSync5(abs);
27943
+ const abs = resolve2(target);
27944
+ statSync6(abs);
25950
27945
  const identity = resolveRepoIdentity(abs);
25951
27946
  if (!identity) return null;
25952
27947
  const sourceProjectId2 = db.sourceProject.upsert({
@@ -26366,7 +28361,7 @@ import { spawn as spawn3 } from "child_process";
26366
28361
  import { existsSync as existsSync8 } from "fs";
26367
28362
  import { createRequire as createRequire2 } from "module";
26368
28363
  import { createServer } from "net";
26369
- import { dirname as dirname3, join as join14 } from "path";
28364
+ import { dirname as dirname5, join as join14 } from "path";
26370
28365
  import { fileURLToPath as fileURLToPath2, pathToFileURL } from "url";
26371
28366
  import { parseArgs as parseArgs2 } from "util";
26372
28367
 
@@ -26384,19 +28379,19 @@ function openUrl(url2) {
26384
28379
 
26385
28380
  // src/commands/dashboard.ts
26386
28381
  var require3 = createRequire2(import.meta.url);
26387
- var here = dirname3(fileURLToPath2(import.meta.url));
28382
+ var here = dirname5(fileURLToPath2(import.meta.url));
26388
28383
  function pkgBase() {
26389
- return isSea() ? dirname3(process.execPath) : join14(here, "..");
28384
+ return isSea() ? dirname5(process.execPath) : join14(here, "..");
26390
28385
  }
26391
28386
  function isPortFree(port) {
26392
- return new Promise((resolve2) => {
28387
+ return new Promise((resolve3) => {
26393
28388
  const probe = createServer();
26394
28389
  probe.once("error", () => {
26395
- resolve2(false);
28390
+ resolve3(false);
26396
28391
  });
26397
28392
  probe.once("listening", () => {
26398
28393
  probe.close(() => {
26399
- resolve2(true);
28394
+ resolve3(true);
26400
28395
  });
26401
28396
  });
26402
28397
  probe.listen(port, "127.0.0.1");
@@ -26447,7 +28442,7 @@ async function runDashboard(argv) {
26447
28442
  launchStandalone(bundledServer, port, shouldOpen, url2);
26448
28443
  return;
26449
28444
  }
26450
- const webUiDir = dirname3(require3.resolve("@akasecurity/web-ui/package.json"));
28445
+ const webUiDir = dirname5(require3.resolve("@akasecurity/web-ui/package.json"));
26451
28446
  const nextBin = require3.resolve("next/dist/bin/next", { paths: [webUiDir] });
26452
28447
  const startNext = () => {
26453
28448
  const child = spawn3(
@@ -26484,7 +28479,7 @@ function launchStandalone(serverJs, port, shouldOpen, url2) {
26484
28479
  return;
26485
28480
  }
26486
28481
  const child = spawn3(reinvoke.command, reinvoke.args, {
26487
- cwd: dirname3(serverJs),
28482
+ cwd: dirname5(serverJs),
26488
28483
  stdio: ["inherit", "pipe", "inherit"],
26489
28484
  // Inherit the parent env so the child server sees PATH etc.; PORT/HOSTNAME tell
26490
28485
  // Next's standalone server where to bind (its only port input).
@@ -26703,11 +28698,11 @@ function terminalPrompter() {
26703
28698
  },
26704
28699
  isInteractive: process.stdin.isTTY && process.stdout.isTTY,
26705
28700
  ask(question) {
26706
- return new Promise((resolve2) => {
28701
+ return new Promise((resolve3) => {
26707
28702
  const rl = createInterface({ input: process.stdin, output: process.stdout });
26708
28703
  rl.question(question, (answer) => {
26709
28704
  rl.close();
26710
- resolve2(answer);
28705
+ resolve3(answer);
26711
28706
  });
26712
28707
  });
26713
28708
  },
@@ -26718,7 +28713,7 @@ function terminalPrompter() {
26718
28713
  // fatal signals: a security tool must never brick the terminal it was
26719
28714
  // invoked from.
26720
28715
  askHidden(question) {
26721
- return new Promise((resolve2, reject) => {
28716
+ return new Promise((resolve3, reject) => {
26722
28717
  const stdin = process.stdin;
26723
28718
  process.stdout.write(question);
26724
28719
  stdin.setRawMode(true);
@@ -26741,7 +28736,7 @@ function terminalPrompter() {
26741
28736
  restore();
26742
28737
  process.stdout.write("\n");
26743
28738
  if (err) reject(err);
26744
- else resolve2(state.value);
28739
+ else resolve3(state.value);
26745
28740
  };
26746
28741
  const onError = (err) => {
26747
28742
  finish(err);
@@ -27601,7 +29596,7 @@ async function confirm(question) {
27601
29596
  }
27602
29597
 
27603
29598
  // src/commands/scan.ts
27604
- import { statSync as statSync6 } from "fs";
29599
+ import { statSync as statSync7 } from "fs";
27605
29600
  import { parseArgs as parseArgs7 } from "util";
27606
29601
  function severityRank(s) {
27607
29602
  return Severity.options.indexOf(s);
@@ -27611,6 +29606,10 @@ function renderInventoryLine(inv) {
27611
29606
  const partial2 = inv.truncated ? " (partial walk)" : "";
27612
29607
  return `Inventory: ${inv.name} \xB7 ${String(inv.fileCount)} project file(s) recorded${partial2}`;
27613
29608
  }
29609
+ function renderEgressLine(egress) {
29610
+ const cap = egress.truncated ? ` (capped at ${String(MAX_EGRESS_CALL_SITES_PER_PROJECT)} call sites)` : "";
29611
+ return `Data shares: ${String(egress.destinations)} destination(s) \xB7 ${String(egress.endpoints)} endpoint(s) \xB7 ${String(egress.callSites)} call site(s)${cap}`;
29612
+ }
27614
29613
  function runScan(argv) {
27615
29614
  const { values, positionals } = parseArgs7({
27616
29615
  args: argv,
@@ -27644,7 +29643,7 @@ function runScan(argv) {
27644
29643
  failOn = parsed.data;
27645
29644
  }
27646
29645
  try {
27647
- statSync6(target);
29646
+ statSync7(target);
27648
29647
  } catch {
27649
29648
  process.stderr.write(`aka scan: no such file or directory: ${target}
27650
29649
  `);
@@ -27655,10 +29654,12 @@ function runScan(argv) {
27655
29654
  const db = openLocalDatabase(dataDir(home));
27656
29655
  let result;
27657
29656
  let inventory;
29657
+ let egress;
27658
29658
  try {
27659
29659
  const { ruleActions } = db.installedPacks.installedRuleset();
27660
29660
  result = scanPathIntoStore(db, target, { ruleActions, sourceTool: "cli" });
27661
29661
  inventory = recordProjectInventory(db, target);
29662
+ egress = recordProjectEgress(db, target, result.egress, home);
27662
29663
  } finally {
27663
29664
  db.close();
27664
29665
  }
@@ -27682,8 +29683,18 @@ function runScan(argv) {
27682
29683
  fileCount: inventory.fileCount,
27683
29684
  truncated: inventory.truncated
27684
29685
  } : null;
29686
+ const egressJson = egress ? {
29687
+ destinations: egress.destinations,
29688
+ endpoints: egress.endpoints,
29689
+ callSites: egress.callSites,
29690
+ truncated: egress.truncated
29691
+ } : null;
27685
29692
  process.stdout.write(
27686
- `${JSON.stringify({ target, scanned: result.scanned, findings, inventory: inventoryJson }, null, 2)}
29693
+ `${JSON.stringify(
29694
+ { target, scanned: result.scanned, findings, inventory: inventoryJson, egress: egressJson },
29695
+ null,
29696
+ 2
29697
+ )}
27687
29698
  `
27688
29699
  );
27689
29700
  } else {
@@ -27692,6 +29703,8 @@ function runScan(argv) {
27692
29703
  `
27693
29704
  );
27694
29705
  if (inventory) process.stdout.write(`${renderInventoryLine(inventory)}
29706
+ `);
29707
+ if (egress) process.stdout.write(`${renderEgressLine(egress)}
27695
29708
  `);
27696
29709
  }
27697
29710
  if (failOn !== void 0) {