@akasecurity/ai-tc-claude-code 0.9.0 → 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.
- package/.claude-plugin/plugin.json +1 -1
- package/commands/setup.md +11 -10
- package/package.json +4 -4
- package/scripts/apply-suppressions.js +1235 -181
- package/scripts/backfill.js +1385 -174
- package/scripts/filescan.js +2641 -333
- package/scripts/firstrun.js +1241 -174
- package/scripts/intro.js +820 -147
- package/scripts/onboard.js +1224 -170
- package/scripts/post-tool-use.js +1405 -179
- package/scripts/pre-tool-use.js +1405 -179
- package/scripts/query.js +1242 -175
- package/scripts/reconcile.js +1238 -171
- package/scripts/remediate.js +1390 -179
- package/scripts/session-start.js +1263 -186
- package/scripts/start-light.js +823 -150
- package/scripts/statusline.js +1255 -178
- package/scripts/stop.js +837 -154
- package/scripts/user-prompt-submit.js +1405 -179
package/scripts/session-start.js
CHANGED
|
@@ -545,6 +545,10 @@ var SQLITE_MIGRATIONS = [
|
|
|
545
545
|
{
|
|
546
546
|
tag: "0010_events_session_expression_index",
|
|
547
547
|
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"
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
tag: "0011_egress_writer",
|
|
551
|
+
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'
|
|
548
552
|
}
|
|
549
553
|
];
|
|
550
554
|
|
|
@@ -16203,6 +16207,7 @@ var ExceptionBundleEntry = DetectionException.pick({
|
|
|
16203
16207
|
|
|
16204
16208
|
// ../../packages/schema/src/zod/rule.ts
|
|
16205
16209
|
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
16210
|
+
var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
|
|
16206
16211
|
var KeywordMatcher = external_exports.object({
|
|
16207
16212
|
type: external_exports.literal("keyword"),
|
|
16208
16213
|
// An empty keyword matches at every position, yielding one zero-length span
|
|
@@ -16227,9 +16232,10 @@ function matchesEmptyString(pattern, flags) {
|
|
|
16227
16232
|
return false;
|
|
16228
16233
|
}
|
|
16229
16234
|
}
|
|
16235
|
+
var MAX_PATTERN_LENGTH = 2e3;
|
|
16230
16236
|
var RegexMatcher = external_exports.object({
|
|
16231
16237
|
type: external_exports.literal("regex"),
|
|
16232
|
-
pattern: external_exports.string(),
|
|
16238
|
+
pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
16233
16239
|
flags: external_exports.string().default("gi"),
|
|
16234
16240
|
captureGroup: external_exports.number().int().nonnegative().optional()
|
|
16235
16241
|
}).refine((v) => isValidRegex(v.pattern, v.flags), {
|
|
@@ -16910,6 +16916,212 @@ function buildDetectionsList(summaries, query) {
|
|
|
16910
16916
|
return { counts, items: filtered.map(summaryToDetectionListItem) };
|
|
16911
16917
|
}
|
|
16912
16918
|
|
|
16919
|
+
// ../../packages/schema/src/zod/shares.ts
|
|
16920
|
+
var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
|
|
16921
|
+
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
|
|
16922
|
+
var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
|
|
16923
|
+
var DATA_CLASS_ORDER = DataClass.options;
|
|
16924
|
+
var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
|
|
16925
|
+
var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
|
|
16926
|
+
var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
|
|
16927
|
+
var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
|
|
16928
|
+
var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE", "SDK", "REF"]).meta({ id: "HttpMethod" });
|
|
16929
|
+
var ReviewInfo = external_exports.object({
|
|
16930
|
+
needsReview: external_exports.boolean(),
|
|
16931
|
+
reasons: external_exports.array(ReviewReason)
|
|
16932
|
+
}).meta({ id: "ReviewInfo" });
|
|
16933
|
+
var DestinationNetwork = external_exports.object({
|
|
16934
|
+
port: external_exports.number().int().nullable(),
|
|
16935
|
+
geo: external_exports.string().nullable(),
|
|
16936
|
+
ptr: external_exports.string().nullable()
|
|
16937
|
+
}).meta({ id: "DestinationNetwork" });
|
|
16938
|
+
var EndpointSummary = external_exports.object({
|
|
16939
|
+
id: external_exports.string(),
|
|
16940
|
+
method: HttpMethod,
|
|
16941
|
+
transport: Transport,
|
|
16942
|
+
url: external_exports.string(),
|
|
16943
|
+
template: external_exports.boolean(),
|
|
16944
|
+
dataClass: DataClass,
|
|
16945
|
+
lastSeen: external_exports.iso.datetime(),
|
|
16946
|
+
callSiteCount: external_exports.number().int().nonnegative()
|
|
16947
|
+
}).meta({ id: "EndpointSummary" });
|
|
16948
|
+
var CallSite = external_exports.object({
|
|
16949
|
+
id: external_exports.string(),
|
|
16950
|
+
project: external_exports.string(),
|
|
16951
|
+
file: external_exports.string(),
|
|
16952
|
+
line: external_exports.number().int().nonnegative(),
|
|
16953
|
+
snippet: external_exports.string(),
|
|
16954
|
+
dynamic: external_exports.boolean(),
|
|
16955
|
+
vendored: external_exports.boolean(),
|
|
16956
|
+
/** Deep-link to the Inventory project, when the repo is governed there. */
|
|
16957
|
+
projectId: external_exports.string().nullable()
|
|
16958
|
+
}).meta({ id: "CallSite" });
|
|
16959
|
+
var EndpointWithSites = EndpointSummary.extend({
|
|
16960
|
+
sites: external_exports.array(CallSite)
|
|
16961
|
+
}).meta({ id: "EndpointWithSites" });
|
|
16962
|
+
var ShareDestinationSummary = external_exports.object({
|
|
16963
|
+
id: external_exports.string(),
|
|
16964
|
+
kind: DestinationKind,
|
|
16965
|
+
name: external_exports.string(),
|
|
16966
|
+
host: external_exports.string(),
|
|
16967
|
+
category: external_exports.string(),
|
|
16968
|
+
trust: ShareTrustLevel,
|
|
16969
|
+
/** Effective state (decision applied over the trust default). */
|
|
16970
|
+
status: EgressStatus,
|
|
16971
|
+
/** True when an egress decision override differs from the trust default. */
|
|
16972
|
+
isCustom: external_exports.boolean(),
|
|
16973
|
+
lastSeen: external_exports.iso.datetime(),
|
|
16974
|
+
endpointCount: external_exports.number().int().nonnegative(),
|
|
16975
|
+
callSiteCount: external_exports.number().int().nonnegative(),
|
|
16976
|
+
transports: external_exports.array(Transport),
|
|
16977
|
+
/** Most-sensitive first. */
|
|
16978
|
+
dataClasses: external_exports.array(DataClass),
|
|
16979
|
+
review: ReviewInfo,
|
|
16980
|
+
/** Non-provider hosts only; null for providers. */
|
|
16981
|
+
network: DestinationNetwork.nullable(),
|
|
16982
|
+
/** Embedded for inline expansion — no call sites here. */
|
|
16983
|
+
endpoints: external_exports.array(EndpointSummary)
|
|
16984
|
+
}).meta({ id: "ShareDestinationSummary" });
|
|
16985
|
+
var ShareDestinationDetail = ShareDestinationSummary.omit({
|
|
16986
|
+
endpointCount: true,
|
|
16987
|
+
callSiteCount: true,
|
|
16988
|
+
endpoints: true
|
|
16989
|
+
}).extend({
|
|
16990
|
+
/** Ownership/geo rationale; null for providers. */
|
|
16991
|
+
note: external_exports.string().nullable(),
|
|
16992
|
+
endpoints: external_exports.array(EndpointWithSites)
|
|
16993
|
+
}).meta({ id: "ShareDestinationDetail" });
|
|
16994
|
+
var ReviewDestination = external_exports.object({
|
|
16995
|
+
id: external_exports.string(),
|
|
16996
|
+
kind: DestinationKind,
|
|
16997
|
+
name: external_exports.string(),
|
|
16998
|
+
/** Registrable host — lets the strip derive the provider lettermark, as the register does. */
|
|
16999
|
+
host: external_exports.string(),
|
|
17000
|
+
trust: ShareTrustLevel,
|
|
17001
|
+
status: EgressStatus,
|
|
17002
|
+
review: ReviewInfo,
|
|
17003
|
+
topDataClass: DataClass,
|
|
17004
|
+
callSiteCount: external_exports.number().int().nonnegative(),
|
|
17005
|
+
lastSeen: external_exports.iso.datetime()
|
|
17006
|
+
}).meta({ id: "ReviewDestination" });
|
|
17007
|
+
var ShareDestinationGroup = external_exports.object({
|
|
17008
|
+
kind: DestinationKind,
|
|
17009
|
+
total: external_exports.number().int().nonnegative(),
|
|
17010
|
+
items: external_exports.array(ShareDestinationSummary)
|
|
17011
|
+
}).meta({ id: "ShareDestinationGroup" });
|
|
17012
|
+
var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
|
|
17013
|
+
var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
|
|
17014
|
+
var SharesStats = external_exports.object({
|
|
17015
|
+
destinations: external_exports.number().int().nonnegative(),
|
|
17016
|
+
endpoints: external_exports.number().int().nonnegative(),
|
|
17017
|
+
callSites: external_exports.number().int().nonnegative(),
|
|
17018
|
+
needsReview: external_exports.number().int().nonnegative(),
|
|
17019
|
+
insecure: external_exports.number().int().nonnegative(),
|
|
17020
|
+
byKind: external_exports.object({
|
|
17021
|
+
provider: external_exports.number().int().nonnegative(),
|
|
17022
|
+
internal: external_exports.number().int().nonnegative(),
|
|
17023
|
+
external: external_exports.number().int().nonnegative(),
|
|
17024
|
+
ip: external_exports.number().int().nonnegative()
|
|
17025
|
+
}),
|
|
17026
|
+
byTrust: external_exports.object({
|
|
17027
|
+
recognized: external_exports.number().int().nonnegative(),
|
|
17028
|
+
internal: external_exports.number().int().nonnegative(),
|
|
17029
|
+
unverified: external_exports.number().int().nonnegative(),
|
|
17030
|
+
ip: external_exports.number().int().nonnegative()
|
|
17031
|
+
})
|
|
17032
|
+
}).meta({ id: "SharesStats" });
|
|
17033
|
+
var SetEgressDecisionBody = external_exports.object({
|
|
17034
|
+
/** `null` clears the override — reverts to the trust default, isCustom false. */
|
|
17035
|
+
decision: EgressDecision.nullable()
|
|
17036
|
+
}).meta({ id: "SetEgressDecisionBody" });
|
|
17037
|
+
var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
|
|
17038
|
+
var ListShareDestinationsQuery = external_exports.object({
|
|
17039
|
+
/** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
|
|
17040
|
+
q: external_exports.string().optional(),
|
|
17041
|
+
/** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
|
|
17042
|
+
kind: external_exports.array(DestinationKind).optional(),
|
|
17043
|
+
/** Reserved for future grouping modes; only 'destination' is supported today. */
|
|
17044
|
+
groupBy: external_exports.enum(["destination"]).default("destination"),
|
|
17045
|
+
/**
|
|
17046
|
+
* When true, return a flat severity-ordered `items[]` instead of `groups`.
|
|
17047
|
+
* Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
|
|
17048
|
+
* any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
|
|
17049
|
+
* to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
|
|
17050
|
+
*/
|
|
17051
|
+
review: external_exports.stringbool().default(false)
|
|
17052
|
+
});
|
|
17053
|
+
var ExportSharesQuery = external_exports.object({
|
|
17054
|
+
format: external_exports.enum(["csv", "json"]).default("csv"),
|
|
17055
|
+
q: external_exports.string().optional(),
|
|
17056
|
+
kind: external_exports.array(DestinationKind).optional()
|
|
17057
|
+
});
|
|
17058
|
+
|
|
17059
|
+
// ../../packages/schema/src/zod/egress-extraction.ts
|
|
17060
|
+
var EgressEcosystem = external_exports.enum(["npm", "pypi", "go", "maven", "rubygems", "cargo", "composer", "nuget"]).meta({ id: "EgressEcosystem" });
|
|
17061
|
+
var ProviderRegistryEntry = external_exports.object({
|
|
17062
|
+
id: external_exports.string(),
|
|
17063
|
+
name: external_exports.string(),
|
|
17064
|
+
category: external_exports.string(),
|
|
17065
|
+
/** Suffix-matched: 'stripe.com' matches api.stripe.com, never evilstripe.com. */
|
|
17066
|
+
hostSuffixes: external_exports.array(external_exports.string()).min(1),
|
|
17067
|
+
/** Canonical API base URL recorded for manifest-derived (method 'SDK') endpoints. */
|
|
17068
|
+
apiBase: external_exports.string(),
|
|
17069
|
+
/** Most-sensitive first; index 0 becomes the endpoint dataClass. */
|
|
17070
|
+
defaultDataClasses: external_exports.array(DataClass).min(1),
|
|
17071
|
+
/** SDK identifiers per ecosystem ('go' prefix-matched by path, 'maven' by group-id prefix). */
|
|
17072
|
+
sdks: external_exports.partialRecord(EgressEcosystem, external_exports.array(external_exports.string()))
|
|
17073
|
+
}).meta({ id: "ProviderRegistryEntry" });
|
|
17074
|
+
var EgressCallSiteHit = external_exports.object({
|
|
17075
|
+
file: external_exports.string(),
|
|
17076
|
+
line: external_exports.number().int().positive(),
|
|
17077
|
+
snippet: external_exports.string(),
|
|
17078
|
+
dynamic: external_exports.boolean(),
|
|
17079
|
+
vendored: external_exports.boolean()
|
|
17080
|
+
}).meta({ id: "EgressCallSiteHit" });
|
|
17081
|
+
var ResolvedEgressHit = external_exports.object({
|
|
17082
|
+
host: external_exports.string(),
|
|
17083
|
+
kind: DestinationKind,
|
|
17084
|
+
name: external_exports.string(),
|
|
17085
|
+
category: external_exports.string(),
|
|
17086
|
+
trust: ShareTrustLevel,
|
|
17087
|
+
network: DestinationNetwork.nullable(),
|
|
17088
|
+
method: HttpMethod,
|
|
17089
|
+
transport: Transport,
|
|
17090
|
+
url: external_exports.string(),
|
|
17091
|
+
template: external_exports.boolean(),
|
|
17092
|
+
dataClass: DataClass,
|
|
17093
|
+
site: EgressCallSiteHit
|
|
17094
|
+
}).meta({ id: "ResolvedEgressHit" });
|
|
17095
|
+
var EgressReconcile = external_exports.discriminatedUnion("mode", [
|
|
17096
|
+
external_exports.object({ mode: external_exports.literal("walk"), walkedPrefix: external_exports.string() }),
|
|
17097
|
+
external_exports.object({
|
|
17098
|
+
mode: external_exports.literal("ledger"),
|
|
17099
|
+
scannedFiles: external_exports.array(external_exports.string()),
|
|
17100
|
+
deletedFiles: external_exports.array(external_exports.string())
|
|
17101
|
+
})
|
|
17102
|
+
]).meta({ id: "EgressReconcile" });
|
|
17103
|
+
var RecordProjectEgressInput = external_exports.object({
|
|
17104
|
+
/** Stable reconcile key: 'git:<repo identity>' or 'path:<abs root>' (non-git). */
|
|
17105
|
+
projectKey: external_exports.string().min(1),
|
|
17106
|
+
/** Display name only — never keys reconciliation. */
|
|
17107
|
+
project: external_exports.string(),
|
|
17108
|
+
projectId: external_exports.string().nullable(),
|
|
17109
|
+
reconcile: EgressReconcile,
|
|
17110
|
+
hits: external_exports.array(ResolvedEgressHit)
|
|
17111
|
+
}).meta({ id: "RecordProjectEgressInput" });
|
|
17112
|
+
var EgressWriteSummary = external_exports.object({
|
|
17113
|
+
destinations: external_exports.number().int().nonnegative(),
|
|
17114
|
+
endpoints: external_exports.number().int().nonnegative(),
|
|
17115
|
+
callSites: external_exports.number().int().nonnegative(),
|
|
17116
|
+
truncated: external_exports.boolean(),
|
|
17117
|
+
/**
|
|
17118
|
+
* Files the cap dropped whole. Their stored rows were left untouched, so a
|
|
17119
|
+
* ledger-keeping caller must withhold their ledger entries and read them
|
|
17120
|
+
* again next scan.
|
|
17121
|
+
*/
|
|
17122
|
+
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17123
|
+
}).meta({ id: "EgressWriteSummary" });
|
|
17124
|
+
|
|
16913
17125
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
16914
17126
|
function toApiAction(dbVal) {
|
|
16915
17127
|
const map2 = {
|
|
@@ -17171,7 +17383,7 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17171
17383
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17172
17384
|
|
|
17173
17385
|
// ../../packages/schema/src/zod/local.ts
|
|
17174
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17386
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 3;
|
|
17175
17387
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17176
17388
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17177
17389
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17186,6 +17398,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17186
17398
|
policy: SimpleDetectionPolicy.default("redact"),
|
|
17187
17399
|
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
17188
17400
|
historicalAccess: HistoricalAccess.default("session-only"),
|
|
17401
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17402
|
+
// Shares writes.
|
|
17403
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17189
17404
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17190
17405
|
onboardedAt: external_exports.iso.datetime().optional()
|
|
17191
17406
|
});
|
|
@@ -17669,145 +17884,6 @@ var SetupHandoffOffer = external_exports.object({
|
|
|
17669
17884
|
path: ["liveKeys"]
|
|
17670
17885
|
});
|
|
17671
17886
|
|
|
17672
|
-
// ../../packages/schema/src/zod/shares.ts
|
|
17673
|
-
var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
|
|
17674
|
-
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
|
|
17675
|
-
var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
|
|
17676
|
-
var DATA_CLASS_ORDER = DataClass.options;
|
|
17677
|
-
var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
|
|
17678
|
-
var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
|
|
17679
|
-
var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
|
|
17680
|
-
var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
|
|
17681
|
-
var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE"]).meta({ id: "HttpMethod" });
|
|
17682
|
-
var ReviewInfo = external_exports.object({
|
|
17683
|
-
needsReview: external_exports.boolean(),
|
|
17684
|
-
reasons: external_exports.array(ReviewReason)
|
|
17685
|
-
}).meta({ id: "ReviewInfo" });
|
|
17686
|
-
var DestinationNetwork = external_exports.object({
|
|
17687
|
-
port: external_exports.number().int().nullable(),
|
|
17688
|
-
geo: external_exports.string().nullable(),
|
|
17689
|
-
ptr: external_exports.string().nullable()
|
|
17690
|
-
}).meta({ id: "DestinationNetwork" });
|
|
17691
|
-
var EndpointSummary = external_exports.object({
|
|
17692
|
-
id: external_exports.string(),
|
|
17693
|
-
method: HttpMethod,
|
|
17694
|
-
transport: Transport,
|
|
17695
|
-
url: external_exports.string(),
|
|
17696
|
-
template: external_exports.boolean(),
|
|
17697
|
-
dataClass: DataClass,
|
|
17698
|
-
lastSeen: external_exports.iso.datetime(),
|
|
17699
|
-
callSiteCount: external_exports.number().int().nonnegative()
|
|
17700
|
-
}).meta({ id: "EndpointSummary" });
|
|
17701
|
-
var CallSite = external_exports.object({
|
|
17702
|
-
id: external_exports.string(),
|
|
17703
|
-
project: external_exports.string(),
|
|
17704
|
-
file: external_exports.string(),
|
|
17705
|
-
line: external_exports.number().int().nonnegative(),
|
|
17706
|
-
snippet: external_exports.string(),
|
|
17707
|
-
dynamic: external_exports.boolean(),
|
|
17708
|
-
vendored: external_exports.boolean(),
|
|
17709
|
-
/** Deep-link to the Inventory project, when the repo is governed there. */
|
|
17710
|
-
projectId: external_exports.string().nullable()
|
|
17711
|
-
}).meta({ id: "CallSite" });
|
|
17712
|
-
var EndpointWithSites = EndpointSummary.extend({
|
|
17713
|
-
sites: external_exports.array(CallSite)
|
|
17714
|
-
}).meta({ id: "EndpointWithSites" });
|
|
17715
|
-
var ShareDestinationSummary = external_exports.object({
|
|
17716
|
-
id: external_exports.string(),
|
|
17717
|
-
kind: DestinationKind,
|
|
17718
|
-
name: external_exports.string(),
|
|
17719
|
-
host: external_exports.string(),
|
|
17720
|
-
category: external_exports.string(),
|
|
17721
|
-
trust: ShareTrustLevel,
|
|
17722
|
-
/** Effective state (decision applied over the trust default). */
|
|
17723
|
-
status: EgressStatus,
|
|
17724
|
-
/** True when an egress decision override differs from the trust default. */
|
|
17725
|
-
isCustom: external_exports.boolean(),
|
|
17726
|
-
lastSeen: external_exports.iso.datetime(),
|
|
17727
|
-
endpointCount: external_exports.number().int().nonnegative(),
|
|
17728
|
-
callSiteCount: external_exports.number().int().nonnegative(),
|
|
17729
|
-
transports: external_exports.array(Transport),
|
|
17730
|
-
/** Most-sensitive first. */
|
|
17731
|
-
dataClasses: external_exports.array(DataClass),
|
|
17732
|
-
review: ReviewInfo,
|
|
17733
|
-
/** Non-provider hosts only; null for providers. */
|
|
17734
|
-
network: DestinationNetwork.nullable(),
|
|
17735
|
-
/** Embedded for inline expansion — no call sites here. */
|
|
17736
|
-
endpoints: external_exports.array(EndpointSummary)
|
|
17737
|
-
}).meta({ id: "ShareDestinationSummary" });
|
|
17738
|
-
var ShareDestinationDetail = ShareDestinationSummary.omit({
|
|
17739
|
-
endpointCount: true,
|
|
17740
|
-
callSiteCount: true,
|
|
17741
|
-
endpoints: true
|
|
17742
|
-
}).extend({
|
|
17743
|
-
/** Ownership/geo rationale; null for providers. */
|
|
17744
|
-
note: external_exports.string().nullable(),
|
|
17745
|
-
endpoints: external_exports.array(EndpointWithSites)
|
|
17746
|
-
}).meta({ id: "ShareDestinationDetail" });
|
|
17747
|
-
var ReviewDestination = external_exports.object({
|
|
17748
|
-
id: external_exports.string(),
|
|
17749
|
-
kind: DestinationKind,
|
|
17750
|
-
name: external_exports.string(),
|
|
17751
|
-
/** Registrable host — lets the strip derive the provider lettermark, as the register does. */
|
|
17752
|
-
host: external_exports.string(),
|
|
17753
|
-
trust: ShareTrustLevel,
|
|
17754
|
-
status: EgressStatus,
|
|
17755
|
-
review: ReviewInfo,
|
|
17756
|
-
topDataClass: DataClass,
|
|
17757
|
-
callSiteCount: external_exports.number().int().nonnegative(),
|
|
17758
|
-
lastSeen: external_exports.iso.datetime()
|
|
17759
|
-
}).meta({ id: "ReviewDestination" });
|
|
17760
|
-
var ShareDestinationGroup = external_exports.object({
|
|
17761
|
-
kind: DestinationKind,
|
|
17762
|
-
total: external_exports.number().int().nonnegative(),
|
|
17763
|
-
items: external_exports.array(ShareDestinationSummary)
|
|
17764
|
-
}).meta({ id: "ShareDestinationGroup" });
|
|
17765
|
-
var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
|
|
17766
|
-
var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
|
|
17767
|
-
var SharesStats = external_exports.object({
|
|
17768
|
-
destinations: external_exports.number().int().nonnegative(),
|
|
17769
|
-
endpoints: external_exports.number().int().nonnegative(),
|
|
17770
|
-
callSites: external_exports.number().int().nonnegative(),
|
|
17771
|
-
needsReview: external_exports.number().int().nonnegative(),
|
|
17772
|
-
insecure: external_exports.number().int().nonnegative(),
|
|
17773
|
-
byKind: external_exports.object({
|
|
17774
|
-
provider: external_exports.number().int().nonnegative(),
|
|
17775
|
-
internal: external_exports.number().int().nonnegative(),
|
|
17776
|
-
ip: external_exports.number().int().nonnegative()
|
|
17777
|
-
}),
|
|
17778
|
-
byTrust: external_exports.object({
|
|
17779
|
-
recognized: external_exports.number().int().nonnegative(),
|
|
17780
|
-
internal: external_exports.number().int().nonnegative(),
|
|
17781
|
-
unverified: external_exports.number().int().nonnegative(),
|
|
17782
|
-
ip: external_exports.number().int().nonnegative()
|
|
17783
|
-
})
|
|
17784
|
-
}).meta({ id: "SharesStats" });
|
|
17785
|
-
var SetEgressDecisionBody = external_exports.object({
|
|
17786
|
-
/** `null` clears the override — reverts to the trust default, isCustom false. */
|
|
17787
|
-
decision: EgressDecision.nullable()
|
|
17788
|
-
}).meta({ id: "SetEgressDecisionBody" });
|
|
17789
|
-
var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
|
|
17790
|
-
var ListShareDestinationsQuery = external_exports.object({
|
|
17791
|
-
/** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
|
|
17792
|
-
q: external_exports.string().optional(),
|
|
17793
|
-
/** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
|
|
17794
|
-
kind: external_exports.array(DestinationKind).optional(),
|
|
17795
|
-
/** Reserved for future grouping modes; only 'destination' is supported today. */
|
|
17796
|
-
groupBy: external_exports.enum(["destination"]).default("destination"),
|
|
17797
|
-
/**
|
|
17798
|
-
* When true, return a flat severity-ordered `items[]` instead of `groups`.
|
|
17799
|
-
* Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
|
|
17800
|
-
* any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
|
|
17801
|
-
* to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
|
|
17802
|
-
*/
|
|
17803
|
-
review: external_exports.stringbool().default(false)
|
|
17804
|
-
});
|
|
17805
|
-
var ExportSharesQuery = external_exports.object({
|
|
17806
|
-
format: external_exports.enum(["csv", "json"]).default("csv"),
|
|
17807
|
-
q: external_exports.string().optional(),
|
|
17808
|
-
kind: external_exports.array(DestinationKind).optional()
|
|
17809
|
-
});
|
|
17810
|
-
|
|
17811
17887
|
// ../../packages/schema/src/zod/shares-access.ts
|
|
17812
17888
|
var ALLOWED_BY_DEFAULT_TRUST = /* @__PURE__ */ new Set(["recognized", "internal"]);
|
|
17813
17889
|
function trustDefaultStatus(trust) {
|
|
@@ -17827,7 +17903,7 @@ function deriveReviewReasons(trust, transports) {
|
|
|
17827
17903
|
const reasons = [];
|
|
17828
17904
|
if (trust === "ip") reasons.push("raw_ip");
|
|
17829
17905
|
if (trust === "unverified") reasons.push("unverified_domain");
|
|
17830
|
-
if (transports.includes("http")) reasons.push("plaintext_transport");
|
|
17906
|
+
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
17831
17907
|
return reasons;
|
|
17832
17908
|
}
|
|
17833
17909
|
function buildReviewInfo(trust, transports) {
|
|
@@ -18058,6 +18134,7 @@ function applyMigrations(db) {
|
|
|
18058
18134
|
ensureSyncedAtColumn(db, "audit_events");
|
|
18059
18135
|
ensureScanLedgerTable(db);
|
|
18060
18136
|
ensureBlockedDetectionsTable(db);
|
|
18137
|
+
ensureRuleProbeCacheTable(db);
|
|
18061
18138
|
ensureWriteGateTrigger(db);
|
|
18062
18139
|
ensureTokenUsageColumns(db);
|
|
18063
18140
|
reconcileSourceProjectIds(db);
|
|
@@ -18197,6 +18274,14 @@ function ensureBlockedDetectionsTable(db) {
|
|
|
18197
18274
|
blocked_at INTEGER NOT NULL
|
|
18198
18275
|
)`);
|
|
18199
18276
|
}
|
|
18277
|
+
function ensureRuleProbeCacheTable(db) {
|
|
18278
|
+
db.exec(`CREATE TABLE IF NOT EXISTS rule_probe_cache (
|
|
18279
|
+
rule_key TEXT PRIMARY KEY,
|
|
18280
|
+
verdict TEXT NOT NULL,
|
|
18281
|
+
worst_probe_ms REAL NOT NULL,
|
|
18282
|
+
checked_at INTEGER NOT NULL
|
|
18283
|
+
)`);
|
|
18284
|
+
}
|
|
18200
18285
|
|
|
18201
18286
|
// ../../packages/persistence/src/paths.ts
|
|
18202
18287
|
import { chmodSync, mkdirSync } from "fs";
|
|
@@ -21750,6 +21835,35 @@ var SqliteResolutionsRepository = class {
|
|
|
21750
21835
|
}
|
|
21751
21836
|
};
|
|
21752
21837
|
|
|
21838
|
+
// ../../packages/persistence/src/repositories/rule-probe-cache.ts
|
|
21839
|
+
var SqliteRuleProbeCacheRepository = class {
|
|
21840
|
+
constructor(db) {
|
|
21841
|
+
this.db = db;
|
|
21842
|
+
this.upsertStmt = db.prepare(
|
|
21843
|
+
`INSERT INTO rule_probe_cache (rule_key, verdict, worst_probe_ms, checked_at)
|
|
21844
|
+
VALUES (:ruleKey, :verdict, :worstProbeMs, :checkedAt)
|
|
21845
|
+
ON CONFLICT (rule_key) DO UPDATE SET
|
|
21846
|
+
verdict = excluded.verdict,
|
|
21847
|
+
worst_probe_ms = excluded.worst_probe_ms,
|
|
21848
|
+
checked_at = excluded.checked_at`
|
|
21849
|
+
);
|
|
21850
|
+
this.readStmt = db.prepare(
|
|
21851
|
+
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
21852
|
+
);
|
|
21853
|
+
}
|
|
21854
|
+
db;
|
|
21855
|
+
upsertStmt;
|
|
21856
|
+
readStmt;
|
|
21857
|
+
getVerdict(ruleKey) {
|
|
21858
|
+
return getRow(this.readStmt, { ruleKey });
|
|
21859
|
+
}
|
|
21860
|
+
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
21861
|
+
failOpenTransaction(this.db, () => {
|
|
21862
|
+
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
21863
|
+
});
|
|
21864
|
+
}
|
|
21865
|
+
};
|
|
21866
|
+
|
|
21753
21867
|
// ../../packages/persistence/src/repositories/scan-ledger.ts
|
|
21754
21868
|
var SqliteScanLedgerRepository = class {
|
|
21755
21869
|
constructor(db) {
|
|
@@ -22161,11 +22275,50 @@ var SqliteSecurityRepository = class {
|
|
|
22161
22275
|
|
|
22162
22276
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22163
22277
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
22164
|
-
var
|
|
22278
|
+
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22279
|
+
var IN_CHUNK = 500;
|
|
22280
|
+
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
22281
|
+
var PLAINTEXT_TRANSPORT_SQL = "('http', 'ws')";
|
|
22282
|
+
var OVERRIDE_JOIN = `LEFT JOIN egress_decision_override oh ON oh.host = d.host
|
|
22283
|
+
LEFT JOIN egress_decision_override ol ON ol.destination_id = d.id AND ol.host IS NULL`;
|
|
22165
22284
|
var CALL_SITE_EMBED_CAP = 200;
|
|
22166
22285
|
function parseNetwork(networkJson) {
|
|
22167
22286
|
return safeJson(networkJson, null);
|
|
22168
22287
|
}
|
|
22288
|
+
function capHits(all, mode) {
|
|
22289
|
+
if (all.length <= MAX_EGRESS_CALL_SITES_PER_PROJECT) {
|
|
22290
|
+
return { hits: [...all], droppedFiles: [], truncated: false };
|
|
22291
|
+
}
|
|
22292
|
+
if (mode === "walk") {
|
|
22293
|
+
return {
|
|
22294
|
+
hits: all.slice(0, MAX_EGRESS_CALL_SITES_PER_PROJECT),
|
|
22295
|
+
droppedFiles: [],
|
|
22296
|
+
truncated: true
|
|
22297
|
+
};
|
|
22298
|
+
}
|
|
22299
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
22300
|
+
for (const hit of all) {
|
|
22301
|
+
const bucket = byFile.get(hit.site.file);
|
|
22302
|
+
if (bucket === void 0) byFile.set(hit.site.file, [hit]);
|
|
22303
|
+
else bucket.push(hit);
|
|
22304
|
+
}
|
|
22305
|
+
const hits = [];
|
|
22306
|
+
const droppedFiles = [];
|
|
22307
|
+
for (const [file2, bucket] of byFile) {
|
|
22308
|
+
if (hits.length + bucket.length > MAX_EGRESS_CALL_SITES_PER_PROJECT) droppedFiles.push(file2);
|
|
22309
|
+
else hits.push(...bucket);
|
|
22310
|
+
}
|
|
22311
|
+
return { hits, droppedFiles, truncated: true };
|
|
22312
|
+
}
|
|
22313
|
+
function withoutDroppedFiles(reconcile, droppedFiles) {
|
|
22314
|
+
if (reconcile.mode === "walk" || droppedFiles.length === 0) return reconcile;
|
|
22315
|
+
const dropped = new Set(droppedFiles);
|
|
22316
|
+
return {
|
|
22317
|
+
mode: "ledger",
|
|
22318
|
+
scannedFiles: reconcile.scannedFiles.filter((file2) => !dropped.has(file2)),
|
|
22319
|
+
deletedFiles: reconcile.deletedFiles.filter((file2) => !dropped.has(file2))
|
|
22320
|
+
};
|
|
22321
|
+
}
|
|
22169
22322
|
function toEndpointSummary(row) {
|
|
22170
22323
|
return {
|
|
22171
22324
|
id: row.id,
|
|
@@ -22256,13 +22409,15 @@ var SqliteSharesRepository = class {
|
|
|
22256
22409
|
const callSites = countScalar(this.db, "SELECT count(*) AS n FROM share_call_site");
|
|
22257
22410
|
const insecure = countScalar(
|
|
22258
22411
|
this.db,
|
|
22259
|
-
|
|
22412
|
+
`SELECT count(DISTINCT destination_id) AS n FROM share_endpoint
|
|
22413
|
+
WHERE transport IN ${PLAINTEXT_TRANSPORT_SQL}`
|
|
22260
22414
|
);
|
|
22261
22415
|
const needsReview = countScalar(
|
|
22262
22416
|
this.db,
|
|
22263
22417
|
`SELECT count(DISTINCT d.id) AS n
|
|
22264
22418
|
FROM share_destination d
|
|
22265
|
-
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22419
|
+
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22420
|
+
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
22266
22421
|
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
|
|
22267
22422
|
);
|
|
22268
22423
|
const kindCounts = countBy(
|
|
@@ -22272,6 +22427,7 @@ var SqliteSharesRepository = class {
|
|
|
22272
22427
|
const byKind = {
|
|
22273
22428
|
provider: kindCounts.get("provider") ?? 0,
|
|
22274
22429
|
internal: kindCounts.get("internal") ?? 0,
|
|
22430
|
+
external: kindCounts.get("external") ?? 0,
|
|
22275
22431
|
ip: kindCounts.get("ip") ?? 0
|
|
22276
22432
|
};
|
|
22277
22433
|
const trustCounts = countBy(
|
|
@@ -22347,23 +22503,316 @@ var SqliteSharesRepository = class {
|
|
|
22347
22503
|
// real edit from a no-such-destination.
|
|
22348
22504
|
/**
|
|
22349
22505
|
* Set (decision) or clear (null) the egress decision override for a destination.
|
|
22350
|
-
* `null` deletes the override
|
|
22506
|
+
* `null` deletes the override rows → reverts to the trust default.
|
|
22507
|
+
*
|
|
22508
|
+
* The written row carries both the destination id and its host, so the
|
|
22509
|
+
* decision re-attaches by host after the destination is pruned and
|
|
22510
|
+
* re-detected under a fresh id. Rows written before the host column existed
|
|
22511
|
+
* (host NULL, matched by destination id) are replaced rather than left to
|
|
22512
|
+
* shadow the new one. Runs IMMEDIATE: the host lookup is read-then-write and
|
|
22513
|
+
* would otherwise race a concurrent prune.
|
|
22351
22514
|
*/
|
|
22352
22515
|
setEgressDecision(destinationId, decision) {
|
|
22353
|
-
|
|
22354
|
-
|
|
22355
|
-
|
|
22356
|
-
|
|
22357
|
-
|
|
22516
|
+
let existed = false;
|
|
22517
|
+
withTransaction(
|
|
22518
|
+
this.db,
|
|
22519
|
+
() => {
|
|
22520
|
+
const dest = this.db.prepare("SELECT host FROM share_destination WHERE id = ?").get(destinationId);
|
|
22521
|
+
if (dest === void 0) return;
|
|
22522
|
+
existed = true;
|
|
22523
|
+
this.db.prepare(
|
|
22524
|
+
`DELETE FROM egress_decision_override
|
|
22525
|
+
WHERE host = :host OR (destination_id = :destinationId AND host IS NULL)`
|
|
22526
|
+
).run({ host: dest.host, destinationId });
|
|
22527
|
+
if (decision === null) return;
|
|
22528
|
+
this.db.prepare(
|
|
22529
|
+
`INSERT INTO egress_decision_override
|
|
22530
|
+
(id, destination_id, host, decision, created_at, updated_at)
|
|
22531
|
+
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22532
|
+
).run({
|
|
22533
|
+
id: randomUUID7(),
|
|
22534
|
+
destinationId,
|
|
22535
|
+
host: dest.host,
|
|
22536
|
+
decision,
|
|
22537
|
+
now: Date.now()
|
|
22538
|
+
});
|
|
22539
|
+
},
|
|
22540
|
+
"IMMEDIATE"
|
|
22541
|
+
);
|
|
22542
|
+
return existed;
|
|
22543
|
+
}
|
|
22544
|
+
/**
|
|
22545
|
+
* Record one project's statically-extracted egress: reconcile the previously
|
|
22546
|
+
* stored call sites against this scan, upsert destination → endpoint → call
|
|
22547
|
+
* site for every hit, confirm `last_seen` on everything the project still
|
|
22548
|
+
* references, and drop what no longer has evidence.
|
|
22549
|
+
*
|
|
22550
|
+
* Reconciliation keys on `projectKey` alone; `project` and `projectId` are
|
|
22551
|
+
* display payload and never scope a delete. The whole write is one
|
|
22552
|
+
* transaction: a failure leaves the project's previous inventory exactly as
|
|
22553
|
+
* it was, and THROWS rather than reporting a partial write — callers decide
|
|
22554
|
+
* their own fail-open behavior, and the scanner additionally withholds its
|
|
22555
|
+
* ledger commit so the next scan retries.
|
|
22556
|
+
*
|
|
22557
|
+
* Over-cap input is truncated at a FILE boundary, and the files that lost
|
|
22558
|
+
* their hits are both excluded from the reconcile delete and named in
|
|
22559
|
+
* `droppedFiles`. That pairing is what keeps truncation non-destructive on
|
|
22560
|
+
* the ledger path: a dropped file keeps whatever rows it already had, and its
|
|
22561
|
+
* caller withholds the ledger entry so the next scan reads it again.
|
|
22562
|
+
*/
|
|
22563
|
+
recordProjectEgress(input) {
|
|
22564
|
+
const { hits, droppedFiles, truncated } = capHits(input.hits, input.reconcile.mode);
|
|
22565
|
+
const reconcile = withoutDroppedFiles(input.reconcile, droppedFiles);
|
|
22566
|
+
const now = Date.now();
|
|
22567
|
+
let summary = {
|
|
22568
|
+
destinations: 0,
|
|
22569
|
+
endpoints: 0,
|
|
22570
|
+
callSites: 0,
|
|
22571
|
+
truncated,
|
|
22572
|
+
droppedFiles
|
|
22573
|
+
};
|
|
22574
|
+
withTransaction(
|
|
22575
|
+
this.db,
|
|
22576
|
+
() => {
|
|
22577
|
+
const projectId = input.projectId ?? this.knownProjectId(input.projectKey);
|
|
22578
|
+
this.reconcileCallSites(input.projectKey, reconcile);
|
|
22579
|
+
this.upsertHits(input, hits, projectId, now);
|
|
22580
|
+
this.confirmLastSeen(input.projectKey, now);
|
|
22581
|
+
this.pruneOrphans();
|
|
22582
|
+
summary = { ...this.projectTotals(input.projectKey), truncated, droppedFiles };
|
|
22583
|
+
},
|
|
22584
|
+
"IMMEDIATE"
|
|
22585
|
+
);
|
|
22586
|
+
return summary;
|
|
22587
|
+
}
|
|
22588
|
+
// ─── Egress write internals ──────────────────────────────────────────────────
|
|
22589
|
+
/**
|
|
22590
|
+
* Clear the stored call sites this scan is responsible for re-creating.
|
|
22591
|
+
*
|
|
22592
|
+
* Each pipeline may only delete rows its own walker could have produced. The
|
|
22593
|
+
* fs walk behind 'walk' mode never descends into dot-directories, so its
|
|
22594
|
+
* delete excludes dot-path files — those rows are the plugin scanner's to
|
|
22595
|
+
* reconcile, and deleting them here would make the two pipelines erase each
|
|
22596
|
+
* other's rows on every alternating scan. 'ledger' mode names its files
|
|
22597
|
+
* outright and never mass-deletes, so rows the fs walk contributed for files
|
|
22598
|
+
* the scanner skips (vendored, oversize) survive it.
|
|
22599
|
+
*/
|
|
22600
|
+
reconcileCallSites(projectKey, reconcile) {
|
|
22601
|
+
if (reconcile.mode === "walk") {
|
|
22602
|
+
const prefix = reconcile.walkedPrefix.replace(/\/+$/, "");
|
|
22603
|
+
this.db.prepare(
|
|
22604
|
+
`DELETE FROM share_call_site
|
|
22605
|
+
WHERE project_key = :key
|
|
22606
|
+
AND (:prefix = '' OR file = :prefix OR file LIKE :subtree ESCAPE '\\')
|
|
22607
|
+
AND file NOT LIKE '.%'
|
|
22608
|
+
AND file NOT LIKE '%/.%'`
|
|
22609
|
+
).run({ key: projectKey, prefix, subtree: `${escapeLikePattern(prefix)}/%` });
|
|
22610
|
+
return;
|
|
22611
|
+
}
|
|
22612
|
+
const files = [.../* @__PURE__ */ new Set([...reconcile.scannedFiles, ...reconcile.deletedFiles])];
|
|
22613
|
+
for (let i = 0; i < files.length; i += IN_CHUNK) {
|
|
22614
|
+
const chunk = files.slice(i, i + IN_CHUNK);
|
|
22615
|
+
this.db.prepare(
|
|
22616
|
+
`DELETE FROM share_call_site
|
|
22617
|
+
WHERE project_key = ? AND file IN (${placeholders(chunk.length)})`
|
|
22618
|
+
).run(projectKey, ...chunk);
|
|
22358
22619
|
}
|
|
22620
|
+
}
|
|
22621
|
+
/**
|
|
22622
|
+
* Upsert every hit as destination → endpoint → call site. Destinations key on
|
|
22623
|
+
* `host` and endpoints on `(destination_id, method, url)`, both shared across
|
|
22624
|
+
* projects; only the call site carries `project_key`. A destination's `note`
|
|
22625
|
+
* is user-owned and never overwritten. The id caches keep one upsert per
|
|
22626
|
+
* distinct host and endpoint, so the first hit for a host supplies its
|
|
22627
|
+
* classification for this batch.
|
|
22628
|
+
*/
|
|
22629
|
+
upsertHits(input, hits, projectId, now) {
|
|
22630
|
+
if (hits.length === 0) return;
|
|
22631
|
+
const destStmt = this.db.prepare(
|
|
22632
|
+
`INSERT INTO share_destination
|
|
22633
|
+
(id, kind, name, host, category, trust, network_json, last_seen, provenance,
|
|
22634
|
+
created_at, updated_at)
|
|
22635
|
+
VALUES (:id, :kind, :name, :host, :category, :trust, :networkJson, :now, 'scan', :now, :now)
|
|
22636
|
+
ON CONFLICT (host) DO UPDATE SET
|
|
22637
|
+
kind = excluded.kind,
|
|
22638
|
+
name = excluded.name,
|
|
22639
|
+
category = excluded.category,
|
|
22640
|
+
trust = excluded.trust,
|
|
22641
|
+
network_json = excluded.network_json,
|
|
22642
|
+
last_seen = excluded.last_seen,
|
|
22643
|
+
updated_at = excluded.updated_at`
|
|
22644
|
+
);
|
|
22645
|
+
const destIdStmt = this.db.prepare("SELECT id FROM share_destination WHERE host = ?");
|
|
22646
|
+
const endpointStmt = this.db.prepare(
|
|
22647
|
+
`INSERT INTO share_endpoint
|
|
22648
|
+
(id, destination_id, method, transport, url, template, data_class, last_seen,
|
|
22649
|
+
created_at, updated_at)
|
|
22650
|
+
VALUES (:id, :destinationId, :method, :transport, :url, :template, :dataClass, :now,
|
|
22651
|
+
:now, :now)
|
|
22652
|
+
ON CONFLICT (destination_id, method, url) DO UPDATE SET
|
|
22653
|
+
transport = excluded.transport,
|
|
22654
|
+
template = excluded.template,
|
|
22655
|
+
data_class = excluded.data_class,
|
|
22656
|
+
last_seen = excluded.last_seen,
|
|
22657
|
+
updated_at = excluded.updated_at`
|
|
22658
|
+
);
|
|
22659
|
+
const endpointIdStmt = this.db.prepare(
|
|
22660
|
+
"SELECT id FROM share_endpoint WHERE destination_id = ? AND method = ? AND url = ?"
|
|
22661
|
+
);
|
|
22662
|
+
const siteStmt = this.db.prepare(
|
|
22663
|
+
`INSERT INTO share_call_site
|
|
22664
|
+
(id, endpoint_id, project, project_key, file, line, snippet, dynamic, vendored,
|
|
22665
|
+
project_id, created_at, updated_at)
|
|
22666
|
+
VALUES (:id, :endpointId, :project, :projectKey, :file, :line, :snippet, :dynamic,
|
|
22667
|
+
:vendored, :projectId, :now, :now)
|
|
22668
|
+
ON CONFLICT (endpoint_id, project_key, file, line) DO UPDATE SET
|
|
22669
|
+
snippet = excluded.snippet,
|
|
22670
|
+
dynamic = excluded.dynamic,
|
|
22671
|
+
vendored = excluded.vendored,
|
|
22672
|
+
project = excluded.project,
|
|
22673
|
+
project_id = COALESCE(excluded.project_id, share_call_site.project_id),
|
|
22674
|
+
updated_at = excluded.updated_at`
|
|
22675
|
+
);
|
|
22676
|
+
const destIds = /* @__PURE__ */ new Map();
|
|
22677
|
+
const endpointIds = /* @__PURE__ */ new Map();
|
|
22678
|
+
for (const hit of hits) {
|
|
22679
|
+
let destinationId = destIds.get(hit.host);
|
|
22680
|
+
if (destinationId === void 0) {
|
|
22681
|
+
destStmt.run({
|
|
22682
|
+
id: randomUUID7(),
|
|
22683
|
+
kind: hit.kind,
|
|
22684
|
+
name: hit.name,
|
|
22685
|
+
host: hit.host,
|
|
22686
|
+
category: hit.category,
|
|
22687
|
+
trust: hit.trust,
|
|
22688
|
+
networkJson: hit.network === null ? null : JSON.stringify(hit.network),
|
|
22689
|
+
now
|
|
22690
|
+
});
|
|
22691
|
+
destinationId = getRow(destIdStmt, [hit.host])?.id ?? "";
|
|
22692
|
+
destIds.set(hit.host, destinationId);
|
|
22693
|
+
}
|
|
22694
|
+
const endpointKey = `${destinationId}\0${hit.method}\0${hit.url}`;
|
|
22695
|
+
let endpointId = endpointIds.get(endpointKey);
|
|
22696
|
+
if (endpointId === void 0) {
|
|
22697
|
+
endpointStmt.run({
|
|
22698
|
+
id: randomUUID7(),
|
|
22699
|
+
destinationId,
|
|
22700
|
+
method: hit.method,
|
|
22701
|
+
transport: hit.transport,
|
|
22702
|
+
url: hit.url,
|
|
22703
|
+
template: boolToInt(hit.template),
|
|
22704
|
+
dataClass: hit.dataClass,
|
|
22705
|
+
now
|
|
22706
|
+
});
|
|
22707
|
+
endpointId = getRow(endpointIdStmt, [destinationId, hit.method, hit.url])?.id ?? "";
|
|
22708
|
+
endpointIds.set(endpointKey, endpointId);
|
|
22709
|
+
}
|
|
22710
|
+
siteStmt.run({
|
|
22711
|
+
id: randomUUID7(),
|
|
22712
|
+
endpointId,
|
|
22713
|
+
project: input.project,
|
|
22714
|
+
projectKey: input.projectKey,
|
|
22715
|
+
file: hit.site.file,
|
|
22716
|
+
line: hit.site.line,
|
|
22717
|
+
snippet: hit.site.snippet,
|
|
22718
|
+
dynamic: boolToInt(hit.site.dynamic),
|
|
22719
|
+
vendored: boolToInt(hit.site.vendored),
|
|
22720
|
+
projectId,
|
|
22721
|
+
now
|
|
22722
|
+
});
|
|
22723
|
+
}
|
|
22724
|
+
}
|
|
22725
|
+
/**
|
|
22726
|
+
* The source-project id this project's stored call sites already carry, if
|
|
22727
|
+
* any. Only the pipeline that resolves a source project supplies one; the
|
|
22728
|
+
* other passes null and inherits this, so the link stops flapping between a
|
|
22729
|
+
* real id and NULL depending on which pipeline ran last. The value is a
|
|
22730
|
+
* per-project attribute stored redundantly on each row, so any row's is
|
|
22731
|
+
* representative.
|
|
22732
|
+
*/
|
|
22733
|
+
knownProjectId(projectKey) {
|
|
22734
|
+
return getRow(
|
|
22735
|
+
this.db.prepare(
|
|
22736
|
+
`SELECT project_id AS projectId FROM share_call_site
|
|
22737
|
+
WHERE project_key = ? AND project_id IS NOT NULL LIMIT 1`
|
|
22738
|
+
),
|
|
22739
|
+
[projectKey]
|
|
22740
|
+
)?.projectId ?? null;
|
|
22741
|
+
}
|
|
22742
|
+
/**
|
|
22743
|
+
* Stamp `last_seen` on every endpoint and destination this project still
|
|
22744
|
+
* references — including rows the scan preserved rather than re-wrote, so a
|
|
22745
|
+
* ledger-skipped file's references don't decay into "stale" on the page.
|
|
22746
|
+
*/
|
|
22747
|
+
confirmLastSeen(projectKey, now) {
|
|
22359
22748
|
this.db.prepare(
|
|
22360
|
-
`
|
|
22361
|
-
|
|
22362
|
-
|
|
22363
|
-
|
|
22364
|
-
|
|
22365
|
-
|
|
22366
|
-
|
|
22749
|
+
`UPDATE share_endpoint SET last_seen = :now, updated_at = :now
|
|
22750
|
+
WHERE id IN (SELECT DISTINCT endpoint_id FROM share_call_site WHERE project_key = :key)`
|
|
22751
|
+
).run({ now, key: projectKey });
|
|
22752
|
+
this.db.prepare(
|
|
22753
|
+
`UPDATE share_destination SET last_seen = :now, updated_at = :now
|
|
22754
|
+
WHERE id IN (SELECT DISTINCT e.destination_id
|
|
22755
|
+
FROM share_endpoint e
|
|
22756
|
+
JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22757
|
+
WHERE c.project_key = :key)`
|
|
22758
|
+
).run({ now, key: projectKey });
|
|
22759
|
+
}
|
|
22760
|
+
/**
|
|
22761
|
+
* Drop rows left without evidence: endpoints with no call site, then
|
|
22762
|
+
* destinations with no endpoint. Call sites are the only evidence either one
|
|
22763
|
+
* has, so a row that lost its last one belongs to no project any more.
|
|
22764
|
+
*
|
|
22765
|
+
* Overrides are deleted between the two steps, and only the ones written
|
|
22766
|
+
* before the host column existed. Those match a destination by id alone;
|
|
22767
|
+
* because the id link is released on delete rather than cascading, leaving
|
|
22768
|
+
* them would accumulate rows that match neither join arm and that nothing can
|
|
22769
|
+
* reach again. Host-bearing rows deliberately survive — the host is what
|
|
22770
|
+
* re-attaches a user's decision when the destination comes back.
|
|
22771
|
+
*/
|
|
22772
|
+
pruneOrphans() {
|
|
22773
|
+
this.db.exec(
|
|
22774
|
+
`DELETE FROM share_endpoint
|
|
22775
|
+
WHERE NOT EXISTS (SELECT 1 FROM share_call_site c WHERE c.endpoint_id = share_endpoint.id)`
|
|
22776
|
+
);
|
|
22777
|
+
this.db.exec(
|
|
22778
|
+
`DELETE FROM egress_decision_override
|
|
22779
|
+
WHERE host IS NULL
|
|
22780
|
+
AND destination_id IN (
|
|
22781
|
+
SELECT d.id FROM share_destination d
|
|
22782
|
+
WHERE NOT EXISTS (SELECT 1 FROM share_endpoint e WHERE e.destination_id = d.id))`
|
|
22783
|
+
);
|
|
22784
|
+
this.db.exec(
|
|
22785
|
+
`DELETE FROM share_destination
|
|
22786
|
+
WHERE NOT EXISTS (
|
|
22787
|
+
SELECT 1 FROM share_endpoint e WHERE e.destination_id = share_destination.id)`
|
|
22788
|
+
);
|
|
22789
|
+
}
|
|
22790
|
+
/**
|
|
22791
|
+
* Live totals for one project. Destinations and endpoints are shared across
|
|
22792
|
+
* projects and carry no project column, so both are counted through the call
|
|
22793
|
+
* sites that reference them.
|
|
22794
|
+
*/
|
|
22795
|
+
projectTotals(projectKey) {
|
|
22796
|
+
return {
|
|
22797
|
+
destinations: countScalar(
|
|
22798
|
+
this.db,
|
|
22799
|
+
`SELECT count(DISTINCT e.destination_id) AS n
|
|
22800
|
+
FROM share_endpoint e
|
|
22801
|
+
JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22802
|
+
WHERE c.project_key = ?`,
|
|
22803
|
+
[projectKey]
|
|
22804
|
+
),
|
|
22805
|
+
endpoints: countScalar(
|
|
22806
|
+
this.db,
|
|
22807
|
+
"SELECT count(DISTINCT endpoint_id) AS n FROM share_call_site WHERE project_key = ?",
|
|
22808
|
+
[projectKey]
|
|
22809
|
+
),
|
|
22810
|
+
callSites: countScalar(
|
|
22811
|
+
this.db,
|
|
22812
|
+
"SELECT count(*) AS n FROM share_call_site WHERE project_key = ?",
|
|
22813
|
+
[projectKey]
|
|
22814
|
+
)
|
|
22815
|
+
};
|
|
22367
22816
|
}
|
|
22368
22817
|
// ─── Raw fetchers ────────────────────────────────────────────────────────────
|
|
22369
22818
|
mapDestRow(r) {
|
|
@@ -22383,7 +22832,8 @@ var SqliteSharesRepository = class {
|
|
|
22383
22832
|
fetchDestinations(q, kinds, reviewOnly = false) {
|
|
22384
22833
|
const cols = `d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
|
|
22385
22834
|
d.network_json AS networkJson, d.last_seen AS lastSeenMs,
|
|
22386
|
-
d.created_at AS createdAt,
|
|
22835
|
+
d.created_at AS createdAt,
|
|
22836
|
+
COALESCE(oh.decision, ol.decision) AS overrideDecision`;
|
|
22387
22837
|
const conditions = [];
|
|
22388
22838
|
const params = [];
|
|
22389
22839
|
if (kinds && kinds.length > 0) {
|
|
@@ -22394,7 +22844,8 @@ var SqliteSharesRepository = class {
|
|
|
22394
22844
|
conditions.push(
|
|
22395
22845
|
`(d.trust IN ('unverified', 'ip')
|
|
22396
22846
|
OR EXISTS (SELECT 1 FROM share_endpoint re
|
|
22397
|
-
WHERE re.destination_id = d.id
|
|
22847
|
+
WHERE re.destination_id = d.id
|
|
22848
|
+
AND re.transport IN ${PLAINTEXT_TRANSPORT_SQL}))`
|
|
22398
22849
|
);
|
|
22399
22850
|
}
|
|
22400
22851
|
let sql;
|
|
@@ -22407,7 +22858,7 @@ var SqliteSharesRepository = class {
|
|
|
22407
22858
|
params.push(pattern, pattern, pattern, pattern, pattern);
|
|
22408
22859
|
sql = `SELECT DISTINCT ${cols}
|
|
22409
22860
|
FROM share_destination d
|
|
22410
|
-
|
|
22861
|
+
${OVERRIDE_JOIN}
|
|
22411
22862
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22412
22863
|
LEFT JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22413
22864
|
${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
@@ -22415,7 +22866,7 @@ var SqliteSharesRepository = class {
|
|
|
22415
22866
|
} else {
|
|
22416
22867
|
sql = `SELECT ${cols}
|
|
22417
22868
|
FROM share_destination d
|
|
22418
|
-
|
|
22869
|
+
${OVERRIDE_JOIN}
|
|
22419
22870
|
${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
22420
22871
|
ORDER BY d.created_at ASC, d.id ASC`;
|
|
22421
22872
|
}
|
|
@@ -22430,9 +22881,9 @@ var SqliteSharesRepository = class {
|
|
|
22430
22881
|
this.db.prepare(
|
|
22431
22882
|
`SELECT d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
|
|
22432
22883
|
d.network_json AS networkJson, d.last_seen AS lastSeenMs,
|
|
22433
|
-
|
|
22884
|
+
COALESCE(oh.decision, ol.decision) AS overrideDecision
|
|
22434
22885
|
FROM share_destination d
|
|
22435
|
-
|
|
22886
|
+
${OVERRIDE_JOIN}
|
|
22436
22887
|
WHERE d.id = ?`
|
|
22437
22888
|
),
|
|
22438
22889
|
[destinationId]
|
|
@@ -22661,6 +23112,7 @@ function openLocalDatabase(dir) {
|
|
|
22661
23112
|
const scanLedger = new SqliteScanLedgerRepository(db);
|
|
22662
23113
|
const exceptions = new SqliteExceptionsRepository(db);
|
|
22663
23114
|
const resolutions = new SqliteResolutionsRepository(db);
|
|
23115
|
+
const ruleProbeCache = new SqliteRuleProbeCacheRepository(db);
|
|
22664
23116
|
const security = new SqliteSecurityRepository(db);
|
|
22665
23117
|
const detections = new SqliteDetectionsRepository(db);
|
|
22666
23118
|
const shares = new SqliteSharesRepository(db);
|
|
@@ -22798,6 +23250,7 @@ function openLocalDatabase(dir) {
|
|
|
22798
23250
|
scanLedger,
|
|
22799
23251
|
exceptions,
|
|
22800
23252
|
resolutions,
|
|
23253
|
+
ruleProbeCache,
|
|
22801
23254
|
security,
|
|
22802
23255
|
detections,
|
|
22803
23256
|
shares,
|
|
@@ -23010,13 +23463,581 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
|
|
|
23010
23463
|
import { homedir as homedir2 } from "os";
|
|
23011
23464
|
import { basename as basename3, join as join7 } from "path";
|
|
23012
23465
|
|
|
23466
|
+
// ../../packages/detections/src/egress/registry.ts
|
|
23467
|
+
var EXTRACTOR_VERSION = "1";
|
|
23468
|
+
var PROVIDER_REGISTRY = [
|
|
23469
|
+
{
|
|
23470
|
+
id: "stripe",
|
|
23471
|
+
name: "Stripe",
|
|
23472
|
+
category: "Payments",
|
|
23473
|
+
hostSuffixes: ["stripe.com"],
|
|
23474
|
+
apiBase: "https://api.stripe.com",
|
|
23475
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23476
|
+
sdks: {
|
|
23477
|
+
npm: ["stripe"],
|
|
23478
|
+
pypi: ["stripe"],
|
|
23479
|
+
go: ["github.com/stripe/stripe-go"],
|
|
23480
|
+
maven: ["com.stripe"],
|
|
23481
|
+
rubygems: ["stripe"],
|
|
23482
|
+
composer: ["stripe/stripe-php"],
|
|
23483
|
+
nuget: ["Stripe.net"]
|
|
23484
|
+
}
|
|
23485
|
+
},
|
|
23486
|
+
{
|
|
23487
|
+
id: "datadog",
|
|
23488
|
+
name: "Datadog",
|
|
23489
|
+
category: "Observability",
|
|
23490
|
+
hostSuffixes: ["datadoghq.com", "datadoghq.eu"],
|
|
23491
|
+
apiBase: "https://api.datadoghq.com",
|
|
23492
|
+
defaultDataClasses: ["telemetry", "logs", "metrics"],
|
|
23493
|
+
sdks: {
|
|
23494
|
+
npm: ["dd-trace", "@datadog/browser-logs"],
|
|
23495
|
+
pypi: ["datadog", "ddtrace"],
|
|
23496
|
+
go: ["github.com/DataDog/dd-trace-go"],
|
|
23497
|
+
maven: ["com.datadoghq"],
|
|
23498
|
+
rubygems: ["ddtrace", "dogapi"],
|
|
23499
|
+
nuget: ["Datadog.Trace"]
|
|
23500
|
+
}
|
|
23501
|
+
},
|
|
23502
|
+
{
|
|
23503
|
+
id: "newrelic",
|
|
23504
|
+
name: "New Relic",
|
|
23505
|
+
category: "Observability",
|
|
23506
|
+
hostSuffixes: ["newrelic.com", "nr-data.net"],
|
|
23507
|
+
apiBase: "https://api.newrelic.com",
|
|
23508
|
+
defaultDataClasses: ["telemetry", "logs", "metrics"],
|
|
23509
|
+
sdks: {
|
|
23510
|
+
npm: ["newrelic"],
|
|
23511
|
+
pypi: ["newrelic"],
|
|
23512
|
+
go: ["github.com/newrelic/go-agent"],
|
|
23513
|
+
maven: ["com.newrelic.agent.java"],
|
|
23514
|
+
rubygems: ["newrelic_rpm"],
|
|
23515
|
+
nuget: ["NewRelic.Agent"]
|
|
23516
|
+
}
|
|
23517
|
+
},
|
|
23518
|
+
{
|
|
23519
|
+
id: "sentry",
|
|
23520
|
+
name: "Sentry",
|
|
23521
|
+
category: "Error tracking",
|
|
23522
|
+
hostSuffixes: ["sentry.io"],
|
|
23523
|
+
apiBase: "https://sentry.io",
|
|
23524
|
+
defaultDataClasses: ["source", "telemetry"],
|
|
23525
|
+
sdks: {
|
|
23526
|
+
npm: ["@sentry/node", "@sentry/react", "@sentry/nextjs"],
|
|
23527
|
+
pypi: ["sentry-sdk"],
|
|
23528
|
+
go: ["github.com/getsentry/sentry-go"],
|
|
23529
|
+
maven: ["io.sentry"],
|
|
23530
|
+
rubygems: ["sentry-ruby"],
|
|
23531
|
+
cargo: ["sentry"],
|
|
23532
|
+
composer: ["sentry/sentry"],
|
|
23533
|
+
nuget: ["Sentry"]
|
|
23534
|
+
}
|
|
23535
|
+
},
|
|
23536
|
+
{
|
|
23537
|
+
id: "openai",
|
|
23538
|
+
name: "OpenAI",
|
|
23539
|
+
category: "LLM provider",
|
|
23540
|
+
hostSuffixes: ["openai.com"],
|
|
23541
|
+
apiBase: "https://api.openai.com",
|
|
23542
|
+
defaultDataClasses: ["pii", "source"],
|
|
23543
|
+
sdks: {
|
|
23544
|
+
npm: ["openai"],
|
|
23545
|
+
pypi: ["openai"],
|
|
23546
|
+
go: ["github.com/sashabaranov/go-openai"],
|
|
23547
|
+
maven: ["com.openai"],
|
|
23548
|
+
rubygems: ["ruby-openai"],
|
|
23549
|
+
cargo: ["async-openai"],
|
|
23550
|
+
composer: ["openai-php/client"],
|
|
23551
|
+
nuget: ["OpenAI"]
|
|
23552
|
+
}
|
|
23553
|
+
},
|
|
23554
|
+
{
|
|
23555
|
+
id: "anthropic",
|
|
23556
|
+
name: "Anthropic",
|
|
23557
|
+
category: "LLM provider",
|
|
23558
|
+
hostSuffixes: ["anthropic.com"],
|
|
23559
|
+
apiBase: "https://api.anthropic.com",
|
|
23560
|
+
defaultDataClasses: ["pii", "source"],
|
|
23561
|
+
sdks: {
|
|
23562
|
+
npm: ["@anthropic-ai/sdk"],
|
|
23563
|
+
pypi: ["anthropic"],
|
|
23564
|
+
go: ["github.com/anthropics/anthropic-sdk-go"],
|
|
23565
|
+
nuget: ["Anthropic.SDK"]
|
|
23566
|
+
}
|
|
23567
|
+
},
|
|
23568
|
+
{
|
|
23569
|
+
id: "aws",
|
|
23570
|
+
name: "Amazon Web Services",
|
|
23571
|
+
category: "Cloud platform",
|
|
23572
|
+
hostSuffixes: ["amazonaws.com"],
|
|
23573
|
+
apiBase: "https://s3.amazonaws.com",
|
|
23574
|
+
defaultDataClasses: ["secrets", "customer"],
|
|
23575
|
+
sdks: {
|
|
23576
|
+
npm: ["@aws-sdk/client-s3", "aws-sdk"],
|
|
23577
|
+
pypi: ["boto3"],
|
|
23578
|
+
go: ["github.com/aws/aws-sdk-go", "github.com/aws/aws-sdk-go-v2"],
|
|
23579
|
+
maven: ["com.amazonaws", "software.amazon.awssdk"],
|
|
23580
|
+
rubygems: ["aws-sdk-s3"],
|
|
23581
|
+
cargo: ["aws-sdk-s3"],
|
|
23582
|
+
nuget: ["AWSSDK.S3"]
|
|
23583
|
+
}
|
|
23584
|
+
},
|
|
23585
|
+
{
|
|
23586
|
+
id: "gcp",
|
|
23587
|
+
name: "Google Cloud",
|
|
23588
|
+
category: "Cloud platform",
|
|
23589
|
+
hostSuffixes: ["googleapis.com"],
|
|
23590
|
+
apiBase: "https://storage.googleapis.com",
|
|
23591
|
+
defaultDataClasses: ["customer", "logs"],
|
|
23592
|
+
sdks: {
|
|
23593
|
+
npm: ["@google-cloud/storage"],
|
|
23594
|
+
pypi: ["google-cloud-storage"],
|
|
23595
|
+
go: ["cloud.google.com/go"],
|
|
23596
|
+
maven: ["com.google.cloud"],
|
|
23597
|
+
rubygems: ["google-cloud-storage"],
|
|
23598
|
+
nuget: ["Google.Cloud.Storage.V1"]
|
|
23599
|
+
}
|
|
23600
|
+
},
|
|
23601
|
+
{
|
|
23602
|
+
id: "azure",
|
|
23603
|
+
name: "Microsoft Azure",
|
|
23604
|
+
category: "Cloud platform",
|
|
23605
|
+
hostSuffixes: ["azure.com", "windows.net"],
|
|
23606
|
+
apiBase: "https://management.azure.com",
|
|
23607
|
+
defaultDataClasses: ["customer", "logs"],
|
|
23608
|
+
sdks: {
|
|
23609
|
+
npm: ["@azure/storage-blob"],
|
|
23610
|
+
pypi: ["azure-storage-blob"],
|
|
23611
|
+
go: ["github.com/Azure/azure-sdk-for-go"],
|
|
23612
|
+
maven: ["com.azure"],
|
|
23613
|
+
rubygems: ["azure-storage-blob"],
|
|
23614
|
+
nuget: ["Azure.Storage.Blobs"]
|
|
23615
|
+
}
|
|
23616
|
+
},
|
|
23617
|
+
{
|
|
23618
|
+
id: "slack",
|
|
23619
|
+
name: "Slack",
|
|
23620
|
+
category: "Notifications",
|
|
23621
|
+
hostSuffixes: ["slack.com"],
|
|
23622
|
+
apiBase: "https://slack.com/api",
|
|
23623
|
+
defaultDataClasses: ["logs"],
|
|
23624
|
+
sdks: {
|
|
23625
|
+
npm: ["@slack/web-api"],
|
|
23626
|
+
pypi: ["slack-sdk"],
|
|
23627
|
+
go: ["github.com/slack-go/slack"],
|
|
23628
|
+
maven: ["com.slack.api"],
|
|
23629
|
+
rubygems: ["slack-ruby-client"],
|
|
23630
|
+
composer: ["slack-php/slack-api"],
|
|
23631
|
+
nuget: ["SlackNet"]
|
|
23632
|
+
}
|
|
23633
|
+
},
|
|
23634
|
+
{
|
|
23635
|
+
id: "segment",
|
|
23636
|
+
name: "Segment",
|
|
23637
|
+
category: "Analytics",
|
|
23638
|
+
hostSuffixes: ["segment.io", "segment.com"],
|
|
23639
|
+
apiBase: "https://api.segment.io",
|
|
23640
|
+
defaultDataClasses: ["customer"],
|
|
23641
|
+
sdks: {
|
|
23642
|
+
npm: ["@segment/analytics-node", "analytics-node"],
|
|
23643
|
+
pypi: ["segment-analytics-python"],
|
|
23644
|
+
go: ["github.com/segmentio/analytics-go"],
|
|
23645
|
+
maven: ["com.segment.analytics.java"],
|
|
23646
|
+
rubygems: ["analytics-ruby"],
|
|
23647
|
+
nuget: ["Analytics"]
|
|
23648
|
+
}
|
|
23649
|
+
},
|
|
23650
|
+
{
|
|
23651
|
+
id: "twilio",
|
|
23652
|
+
name: "Twilio",
|
|
23653
|
+
category: "Communications",
|
|
23654
|
+
hostSuffixes: ["twilio.com"],
|
|
23655
|
+
apiBase: "https://api.twilio.com",
|
|
23656
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23657
|
+
sdks: {
|
|
23658
|
+
npm: ["twilio"],
|
|
23659
|
+
pypi: ["twilio"],
|
|
23660
|
+
go: ["github.com/twilio/twilio-go"],
|
|
23661
|
+
maven: ["com.twilio.sdk"],
|
|
23662
|
+
rubygems: ["twilio-ruby"],
|
|
23663
|
+
composer: ["twilio/sdk"],
|
|
23664
|
+
nuget: ["Twilio"]
|
|
23665
|
+
}
|
|
23666
|
+
},
|
|
23667
|
+
{
|
|
23668
|
+
id: "sendgrid",
|
|
23669
|
+
name: "SendGrid",
|
|
23670
|
+
category: "Email",
|
|
23671
|
+
hostSuffixes: ["sendgrid.com"],
|
|
23672
|
+
apiBase: "https://api.sendgrid.com",
|
|
23673
|
+
defaultDataClasses: ["pii"],
|
|
23674
|
+
sdks: {
|
|
23675
|
+
npm: ["@sendgrid/mail"],
|
|
23676
|
+
pypi: ["sendgrid"],
|
|
23677
|
+
go: ["github.com/sendgrid/sendgrid-go"],
|
|
23678
|
+
maven: ["com.sendgrid"],
|
|
23679
|
+
rubygems: ["sendgrid-ruby"],
|
|
23680
|
+
composer: ["sendgrid/sendgrid"],
|
|
23681
|
+
nuget: ["SendGrid"]
|
|
23682
|
+
}
|
|
23683
|
+
},
|
|
23684
|
+
{
|
|
23685
|
+
id: "mailgun",
|
|
23686
|
+
name: "Mailgun",
|
|
23687
|
+
category: "Email",
|
|
23688
|
+
hostSuffixes: ["mailgun.net"],
|
|
23689
|
+
apiBase: "https://api.mailgun.net",
|
|
23690
|
+
defaultDataClasses: ["pii"],
|
|
23691
|
+
sdks: {
|
|
23692
|
+
npm: ["mailgun.js"],
|
|
23693
|
+
pypi: ["mailgun"],
|
|
23694
|
+
rubygems: ["mailgun-ruby"],
|
|
23695
|
+
composer: ["mailgun/mailgun-php"],
|
|
23696
|
+
nuget: ["Mailgun"]
|
|
23697
|
+
}
|
|
23698
|
+
},
|
|
23699
|
+
{
|
|
23700
|
+
id: "mixpanel",
|
|
23701
|
+
name: "Mixpanel",
|
|
23702
|
+
category: "Analytics",
|
|
23703
|
+
hostSuffixes: ["mixpanel.com"],
|
|
23704
|
+
apiBase: "https://api.mixpanel.com",
|
|
23705
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23706
|
+
sdks: {
|
|
23707
|
+
npm: ["mixpanel"],
|
|
23708
|
+
pypi: ["mixpanel"],
|
|
23709
|
+
rubygems: ["mixpanel-ruby"],
|
|
23710
|
+
nuget: ["Mixpanel"]
|
|
23711
|
+
}
|
|
23712
|
+
},
|
|
23713
|
+
{
|
|
23714
|
+
id: "amplitude",
|
|
23715
|
+
name: "Amplitude",
|
|
23716
|
+
category: "Analytics",
|
|
23717
|
+
hostSuffixes: ["amplitude.com"],
|
|
23718
|
+
apiBase: "https://api2.amplitude.com",
|
|
23719
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23720
|
+
sdks: {
|
|
23721
|
+
npm: ["@amplitude/analytics-node"],
|
|
23722
|
+
pypi: ["amplitude-analytics"],
|
|
23723
|
+
nuget: ["Amplitude"]
|
|
23724
|
+
}
|
|
23725
|
+
},
|
|
23726
|
+
{
|
|
23727
|
+
id: "posthog",
|
|
23728
|
+
name: "PostHog",
|
|
23729
|
+
category: "Analytics",
|
|
23730
|
+
hostSuffixes: ["posthog.com"],
|
|
23731
|
+
apiBase: "https://us.i.posthog.com",
|
|
23732
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23733
|
+
sdks: {
|
|
23734
|
+
npm: ["posthog-node", "posthog-js"],
|
|
23735
|
+
pypi: ["posthog"],
|
|
23736
|
+
go: ["github.com/posthog/posthog-go"],
|
|
23737
|
+
rubygems: ["posthog-ruby"],
|
|
23738
|
+
composer: ["posthog/posthog-php"],
|
|
23739
|
+
nuget: ["PostHog"]
|
|
23740
|
+
}
|
|
23741
|
+
},
|
|
23742
|
+
{
|
|
23743
|
+
id: "honeycomb",
|
|
23744
|
+
name: "Honeycomb",
|
|
23745
|
+
category: "Observability",
|
|
23746
|
+
hostSuffixes: ["honeycomb.io"],
|
|
23747
|
+
apiBase: "https://api.honeycomb.io",
|
|
23748
|
+
defaultDataClasses: ["telemetry", "metrics"],
|
|
23749
|
+
sdks: {
|
|
23750
|
+
npm: ["libhoney"],
|
|
23751
|
+
pypi: ["libhoney"],
|
|
23752
|
+
go: ["github.com/honeycombio/libhoney-go"],
|
|
23753
|
+
rubygems: ["libhoney"]
|
|
23754
|
+
}
|
|
23755
|
+
},
|
|
23756
|
+
{
|
|
23757
|
+
id: "grafana",
|
|
23758
|
+
name: "Grafana Cloud",
|
|
23759
|
+
category: "Observability",
|
|
23760
|
+
hostSuffixes: ["grafana.net"],
|
|
23761
|
+
apiBase: "https://grafana.net",
|
|
23762
|
+
defaultDataClasses: ["logs", "metrics"],
|
|
23763
|
+
sdks: {
|
|
23764
|
+
npm: ["@grafana/faro-web-sdk"]
|
|
23765
|
+
}
|
|
23766
|
+
},
|
|
23767
|
+
{
|
|
23768
|
+
id: "splunk",
|
|
23769
|
+
name: "Splunk",
|
|
23770
|
+
category: "Observability",
|
|
23771
|
+
hostSuffixes: ["splunkcloud.com", "splunk.com"],
|
|
23772
|
+
apiBase: "https://http-inputs.splunkcloud.com",
|
|
23773
|
+
defaultDataClasses: ["logs"],
|
|
23774
|
+
sdks: {
|
|
23775
|
+
npm: ["splunk-logging"],
|
|
23776
|
+
pypi: ["splunk-sdk"],
|
|
23777
|
+
maven: ["com.splunk"],
|
|
23778
|
+
nuget: ["Splunk.Logging.Common"]
|
|
23779
|
+
}
|
|
23780
|
+
},
|
|
23781
|
+
{
|
|
23782
|
+
id: "pagerduty",
|
|
23783
|
+
name: "PagerDuty",
|
|
23784
|
+
category: "Incident response",
|
|
23785
|
+
hostSuffixes: ["pagerduty.com"],
|
|
23786
|
+
apiBase: "https://api.pagerduty.com",
|
|
23787
|
+
defaultDataClasses: ["logs"],
|
|
23788
|
+
sdks: {
|
|
23789
|
+
npm: ["@pagerduty/pdjs"],
|
|
23790
|
+
pypi: ["pdpyras"],
|
|
23791
|
+
go: ["github.com/PagerDuty/go-pagerduty"],
|
|
23792
|
+
rubygems: ["pagerduty"]
|
|
23793
|
+
}
|
|
23794
|
+
},
|
|
23795
|
+
{
|
|
23796
|
+
id: "github",
|
|
23797
|
+
name: "GitHub",
|
|
23798
|
+
category: "Developer platform",
|
|
23799
|
+
hostSuffixes: ["github.com", "githubusercontent.com"],
|
|
23800
|
+
apiBase: "https://api.github.com",
|
|
23801
|
+
defaultDataClasses: ["source"],
|
|
23802
|
+
sdks: {
|
|
23803
|
+
npm: ["@octokit/rest", "octokit"],
|
|
23804
|
+
pypi: ["pygithub"],
|
|
23805
|
+
go: ["github.com/google/go-github"],
|
|
23806
|
+
maven: ["org.kohsuke.github-api"],
|
|
23807
|
+
rubygems: ["octokit"],
|
|
23808
|
+
cargo: ["octocrab"],
|
|
23809
|
+
composer: ["knplabs/github-api"],
|
|
23810
|
+
nuget: ["Octokit"]
|
|
23811
|
+
}
|
|
23812
|
+
},
|
|
23813
|
+
{
|
|
23814
|
+
id: "gitlab",
|
|
23815
|
+
name: "GitLab",
|
|
23816
|
+
category: "Developer platform",
|
|
23817
|
+
hostSuffixes: ["gitlab.com"],
|
|
23818
|
+
apiBase: "https://gitlab.com/api",
|
|
23819
|
+
defaultDataClasses: ["source"],
|
|
23820
|
+
sdks: {
|
|
23821
|
+
npm: ["@gitbeaker/rest"],
|
|
23822
|
+
pypi: ["python-gitlab"],
|
|
23823
|
+
go: ["gitlab.com/gitlab-org/api/client-go"],
|
|
23824
|
+
rubygems: ["gitlab"],
|
|
23825
|
+
nuget: ["GitLabApiClient"]
|
|
23826
|
+
}
|
|
23827
|
+
},
|
|
23828
|
+
{
|
|
23829
|
+
id: "auth0",
|
|
23830
|
+
name: "Auth0",
|
|
23831
|
+
category: "Identity",
|
|
23832
|
+
hostSuffixes: ["auth0.com"],
|
|
23833
|
+
apiBase: "https://login.auth0.com",
|
|
23834
|
+
defaultDataClasses: ["pii"],
|
|
23835
|
+
sdks: {
|
|
23836
|
+
npm: ["auth0"],
|
|
23837
|
+
pypi: ["auth0-python"],
|
|
23838
|
+
go: ["github.com/auth0/go-auth0"],
|
|
23839
|
+
maven: ["com.auth0"],
|
|
23840
|
+
rubygems: ["auth0"],
|
|
23841
|
+
composer: ["auth0/auth0-php"],
|
|
23842
|
+
nuget: ["Auth0.ManagementApi"]
|
|
23843
|
+
}
|
|
23844
|
+
},
|
|
23845
|
+
{
|
|
23846
|
+
id: "okta",
|
|
23847
|
+
name: "Okta",
|
|
23848
|
+
category: "Identity",
|
|
23849
|
+
hostSuffixes: ["okta.com", "oktapreview.com"],
|
|
23850
|
+
apiBase: "https://login.okta.com",
|
|
23851
|
+
defaultDataClasses: ["pii"],
|
|
23852
|
+
sdks: {
|
|
23853
|
+
npm: ["@okta/okta-sdk-nodejs"],
|
|
23854
|
+
pypi: ["okta"],
|
|
23855
|
+
go: ["github.com/okta/okta-sdk-golang"],
|
|
23856
|
+
maven: ["com.okta.sdk"],
|
|
23857
|
+
nuget: ["Okta.Sdk"]
|
|
23858
|
+
}
|
|
23859
|
+
},
|
|
23860
|
+
{
|
|
23861
|
+
id: "clerk",
|
|
23862
|
+
name: "Clerk",
|
|
23863
|
+
category: "Identity",
|
|
23864
|
+
hostSuffixes: ["clerk.com", "clerk.dev"],
|
|
23865
|
+
apiBase: "https://api.clerk.com",
|
|
23866
|
+
defaultDataClasses: ["pii"],
|
|
23867
|
+
sdks: {
|
|
23868
|
+
npm: ["@clerk/backend", "@clerk/nextjs"],
|
|
23869
|
+
pypi: ["clerk-backend-api"],
|
|
23870
|
+
go: ["github.com/clerk/clerk-sdk-go"]
|
|
23871
|
+
}
|
|
23872
|
+
},
|
|
23873
|
+
{
|
|
23874
|
+
id: "supabase",
|
|
23875
|
+
name: "Supabase",
|
|
23876
|
+
category: "Backend platform",
|
|
23877
|
+
hostSuffixes: ["supabase.co", "supabase.com"],
|
|
23878
|
+
apiBase: "https://api.supabase.com",
|
|
23879
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23880
|
+
sdks: {
|
|
23881
|
+
npm: ["@supabase/supabase-js"],
|
|
23882
|
+
pypi: ["supabase"],
|
|
23883
|
+
cargo: ["postgrest"]
|
|
23884
|
+
}
|
|
23885
|
+
},
|
|
23886
|
+
{
|
|
23887
|
+
id: "firebase",
|
|
23888
|
+
name: "Firebase",
|
|
23889
|
+
category: "Backend platform",
|
|
23890
|
+
hostSuffixes: ["firebaseio.com", "firebase.google.com"],
|
|
23891
|
+
apiBase: "https://firebaseio.com",
|
|
23892
|
+
defaultDataClasses: ["customer"],
|
|
23893
|
+
sdks: {
|
|
23894
|
+
npm: ["firebase", "firebase-admin"],
|
|
23895
|
+
pypi: ["firebase-admin"],
|
|
23896
|
+
go: ["firebase.google.com/go"],
|
|
23897
|
+
maven: ["com.google.firebase"]
|
|
23898
|
+
}
|
|
23899
|
+
},
|
|
23900
|
+
{
|
|
23901
|
+
id: "mongodb-atlas",
|
|
23902
|
+
name: "MongoDB Atlas",
|
|
23903
|
+
category: "Database SaaS",
|
|
23904
|
+
hostSuffixes: ["mongodb.net", "mongodb.com"],
|
|
23905
|
+
apiBase: "https://cloud.mongodb.com",
|
|
23906
|
+
defaultDataClasses: ["customer"],
|
|
23907
|
+
sdks: {
|
|
23908
|
+
npm: ["mongodb"],
|
|
23909
|
+
pypi: ["pymongo"],
|
|
23910
|
+
go: ["go.mongodb.org/mongo-driver"],
|
|
23911
|
+
maven: ["org.mongodb"],
|
|
23912
|
+
rubygems: ["mongo"],
|
|
23913
|
+
cargo: ["mongodb"],
|
|
23914
|
+
nuget: ["MongoDB.Driver"]
|
|
23915
|
+
}
|
|
23916
|
+
},
|
|
23917
|
+
{
|
|
23918
|
+
id: "planetscale",
|
|
23919
|
+
name: "PlanetScale",
|
|
23920
|
+
category: "Database SaaS",
|
|
23921
|
+
hostSuffixes: ["psdb.cloud", "planetscale.com"],
|
|
23922
|
+
apiBase: "https://api.planetscale.com",
|
|
23923
|
+
defaultDataClasses: ["customer"],
|
|
23924
|
+
sdks: {
|
|
23925
|
+
npm: ["@planetscale/database"],
|
|
23926
|
+
go: ["github.com/planetscale/planetscale-go"]
|
|
23927
|
+
}
|
|
23928
|
+
},
|
|
23929
|
+
{
|
|
23930
|
+
id: "algolia",
|
|
23931
|
+
name: "Algolia",
|
|
23932
|
+
category: "Search SaaS",
|
|
23933
|
+
hostSuffixes: ["algolia.net", "algolianet.com"],
|
|
23934
|
+
apiBase: "https://algolia.net",
|
|
23935
|
+
defaultDataClasses: ["customer"],
|
|
23936
|
+
sdks: {
|
|
23937
|
+
npm: ["algoliasearch"],
|
|
23938
|
+
pypi: ["algoliasearch"],
|
|
23939
|
+
go: ["github.com/algolia/algoliasearch-client-go"],
|
|
23940
|
+
maven: ["com.algolia"],
|
|
23941
|
+
rubygems: ["algolia"],
|
|
23942
|
+
composer: ["algolia/algoliasearch-client-php"],
|
|
23943
|
+
nuget: ["Algolia.Search"]
|
|
23944
|
+
}
|
|
23945
|
+
},
|
|
23946
|
+
{
|
|
23947
|
+
id: "cloudflare",
|
|
23948
|
+
name: "Cloudflare",
|
|
23949
|
+
category: "CDN / edge",
|
|
23950
|
+
hostSuffixes: ["cloudflare.com", "workers.dev"],
|
|
23951
|
+
apiBase: "https://api.cloudflare.com",
|
|
23952
|
+
defaultDataClasses: ["logs"],
|
|
23953
|
+
sdks: {
|
|
23954
|
+
npm: ["cloudflare"],
|
|
23955
|
+
pypi: ["cloudflare"],
|
|
23956
|
+
go: ["github.com/cloudflare/cloudflare-go"],
|
|
23957
|
+
nuget: ["CloudFlare.Client"]
|
|
23958
|
+
}
|
|
23959
|
+
},
|
|
23960
|
+
{
|
|
23961
|
+
id: "huggingface",
|
|
23962
|
+
name: "Hugging Face",
|
|
23963
|
+
category: "LLM provider",
|
|
23964
|
+
hostSuffixes: ["huggingface.co"],
|
|
23965
|
+
apiBase: "https://api-inference.huggingface.co",
|
|
23966
|
+
defaultDataClasses: ["source"],
|
|
23967
|
+
sdks: {
|
|
23968
|
+
npm: ["@huggingface/inference"],
|
|
23969
|
+
pypi: ["huggingface-hub", "transformers"],
|
|
23970
|
+
rubygems: ["hugging-face"]
|
|
23971
|
+
}
|
|
23972
|
+
},
|
|
23973
|
+
{
|
|
23974
|
+
id: "cohere",
|
|
23975
|
+
name: "Cohere",
|
|
23976
|
+
category: "LLM provider",
|
|
23977
|
+
hostSuffixes: ["cohere.com", "cohere.ai"],
|
|
23978
|
+
apiBase: "https://api.cohere.com",
|
|
23979
|
+
defaultDataClasses: ["pii", "source"],
|
|
23980
|
+
sdks: {
|
|
23981
|
+
npm: ["cohere-ai"],
|
|
23982
|
+
pypi: ["cohere"],
|
|
23983
|
+
go: ["github.com/cohere-ai/cohere-go"]
|
|
23984
|
+
}
|
|
23985
|
+
},
|
|
23986
|
+
{
|
|
23987
|
+
id: "mistral",
|
|
23988
|
+
name: "Mistral AI",
|
|
23989
|
+
category: "LLM provider",
|
|
23990
|
+
hostSuffixes: ["mistral.ai"],
|
|
23991
|
+
apiBase: "https://api.mistral.ai",
|
|
23992
|
+
defaultDataClasses: ["pii", "source"],
|
|
23993
|
+
sdks: {
|
|
23994
|
+
npm: ["@mistralai/mistralai"],
|
|
23995
|
+
pypi: ["mistralai"],
|
|
23996
|
+
go: ["github.com/gage-technologies/mistral-go"]
|
|
23997
|
+
}
|
|
23998
|
+
}
|
|
23999
|
+
];
|
|
24000
|
+
var EGRESS_VERSION_MATERIAL = `${EXTRACTOR_VERSION}
|
|
24001
|
+
${JSON.stringify(PROVIDER_REGISTRY)}`;
|
|
24002
|
+
|
|
24003
|
+
// ../../packages/detections/src/egress/extract.ts
|
|
24004
|
+
var SECRET_KEY_NAMES = "api[_-]?key|apikey|private[_-]?key|access[_-]?key|access[_-]?token|token|secret|credentials?|password|passwd|pwd|authorization|sig|signature|sas|assertion";
|
|
24005
|
+
var AUTH_SCHEMES = "Bearer|Basic|Token|Digest|ApiKey|SSWS|AWS4-HMAC-SHA256";
|
|
24006
|
+
var SECRET_VALUE = new RegExp(
|
|
24007
|
+
`((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(?!(?:${AUTH_SCHEMES})[\\s'"\`])[^\\s'"\`&]+`,
|
|
24008
|
+
"gi"
|
|
24009
|
+
);
|
|
24010
|
+
var AUTH_SCHEME_VALUE = new RegExp(
|
|
24011
|
+
`((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(${AUTH_SCHEMES})\\s+[^\\s'"\`]+`,
|
|
24012
|
+
"gi"
|
|
24013
|
+
);
|
|
24014
|
+
var WEBHOOK_SECRET_PATHS = [
|
|
24015
|
+
{ hosts: ["hooks.slack.com"], prefix: "/services/" },
|
|
24016
|
+
{
|
|
24017
|
+
hosts: ["discord.com", "discordapp.com", "ptb.discord.com", "canary.discord.com"],
|
|
24018
|
+
prefix: "/api/webhooks/"
|
|
24019
|
+
},
|
|
24020
|
+
{ hosts: ["hooks.zapier.com"], prefix: "/hooks/" },
|
|
24021
|
+
{ hosts: ["outlook.office.com", "outlook.office365.com"], prefix: "/webhook/" }
|
|
24022
|
+
];
|
|
24023
|
+
function escapeRegExp(literal2) {
|
|
24024
|
+
return literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
24025
|
+
}
|
|
24026
|
+
var WEBHOOK_URL = new RegExp(
|
|
24027
|
+
`(https?://(?:${WEBHOOK_SECRET_PATHS.flatMap(
|
|
24028
|
+
(entry) => entry.hosts.map((host) => `${escapeRegExp(host)}${escapeRegExp(entry.prefix)}`)
|
|
24029
|
+
).join("|")}))[^\\s'"\`<>()[\\]{},;]+`,
|
|
24030
|
+
"gi"
|
|
24031
|
+
);
|
|
24032
|
+
|
|
23013
24033
|
// ../../packages/detections/src/escape-regexp.ts
|
|
23014
|
-
function
|
|
24034
|
+
function escapeRegExp2(value) {
|
|
23015
24035
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23016
24036
|
}
|
|
23017
24037
|
|
|
23018
24038
|
// ../../packages/detections/src/matchers/limits.ts
|
|
23019
24039
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
24040
|
+
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
23020
24041
|
|
|
23021
24042
|
// ../../packages/detections/src/matchers/keyword.ts
|
|
23022
24043
|
var KeywordMatcher2 = class {
|
|
@@ -23027,7 +24048,7 @@ var KeywordMatcher2 = class {
|
|
|
23027
24048
|
for (const kw of keywords) {
|
|
23028
24049
|
if (kw.length === 0) continue;
|
|
23029
24050
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
23030
|
-
const re = new RegExp(
|
|
24051
|
+
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
23031
24052
|
let m;
|
|
23032
24053
|
while ((m = re.exec(text)) !== null) {
|
|
23033
24054
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -23044,9 +24065,13 @@ var RegexMatcher2 = class {
|
|
|
23044
24065
|
if (rule.matcher.type !== "regex") return [];
|
|
23045
24066
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
23046
24067
|
const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
|
|
24068
|
+
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
23047
24069
|
const spans = [];
|
|
23048
24070
|
let m;
|
|
23049
|
-
|
|
24071
|
+
const maxIterations = scanText2.length + 1;
|
|
24072
|
+
let iterations = 0;
|
|
24073
|
+
while ((m = re.exec(scanText2)) !== null) {
|
|
24074
|
+
if (++iterations > maxIterations) break;
|
|
23050
24075
|
const group = captureGroup != null ? m[captureGroup] : m[0];
|
|
23051
24076
|
if (m[0].length === 0) re.lastIndex++;
|
|
23052
24077
|
if (group && spans.length < MAX_MATCHES_PER_RULE) {
|
|
@@ -23150,7 +24175,7 @@ function isCorroborated(candidate, candidates, text) {
|
|
|
23150
24175
|
for (const label of labels) {
|
|
23151
24176
|
const trimmed = label.trim();
|
|
23152
24177
|
if (trimmed.length === 0) continue;
|
|
23153
|
-
const re = new RegExp(`(?<![A-Za-z0-9])${
|
|
24178
|
+
const re = new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
|
|
23154
24179
|
if (re.test(haystack)) return true;
|
|
23155
24180
|
}
|
|
23156
24181
|
}
|
|
@@ -23397,6 +24422,31 @@ function whole(command) {
|
|
|
23397
24422
|
return { start: 0, end: command.length };
|
|
23398
24423
|
}
|
|
23399
24424
|
|
|
24425
|
+
// ../../packages/detections/src/security/redos-probe.ts
|
|
24426
|
+
var EXPONENTIAL_UNITS = [
|
|
24427
|
+
"a",
|
|
24428
|
+
"0",
|
|
24429
|
+
" ",
|
|
24430
|
+
"x",
|
|
24431
|
+
"ab",
|
|
24432
|
+
"a.",
|
|
24433
|
+
"a-",
|
|
24434
|
+
"a_",
|
|
24435
|
+
"a@",
|
|
24436
|
+
"a/",
|
|
24437
|
+
"a:",
|
|
24438
|
+
"a=",
|
|
24439
|
+
"a;",
|
|
24440
|
+
"aA0",
|
|
24441
|
+
" "
|
|
24442
|
+
];
|
|
24443
|
+
var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
|
|
24444
|
+
(unit) => [23, 25].map((len) => unit.repeat(Math.ceil(len / unit.length)).slice(0, len) + "!")
|
|
24445
|
+
);
|
|
24446
|
+
var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
|
|
24447
|
+
(unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
|
|
24448
|
+
);
|
|
24449
|
+
|
|
23400
24450
|
// ../../rules/code-flaws/auth-jwt-no-verify.json
|
|
23401
24451
|
var auth_jwt_no_verify_default = {
|
|
23402
24452
|
specVersion: 1,
|
|
@@ -25940,10 +26990,10 @@ function parseFrontmatter(raw) {
|
|
|
25940
26990
|
if (lines[0]?.trim() !== "---") return out;
|
|
25941
26991
|
for (const line of lines.slice(1)) {
|
|
25942
26992
|
if (line.trim() === "---") break;
|
|
25943
|
-
const
|
|
25944
|
-
if (
|
|
25945
|
-
const key = line.slice(0,
|
|
25946
|
-
const value = line.slice(
|
|
26993
|
+
const sep5 = line.indexOf(":");
|
|
26994
|
+
if (sep5 === -1) continue;
|
|
26995
|
+
const key = line.slice(0, sep5).trim();
|
|
26996
|
+
const value = line.slice(sep5 + 1).trim().replace(/^['"]|['"]$/g, "");
|
|
25947
26997
|
if (value === "") continue;
|
|
25948
26998
|
if (key === "name") out.name = value;
|
|
25949
26999
|
else if (key === "description") out.description = value;
|
|
@@ -26153,10 +27203,14 @@ function claimOncePerSession(dataDir2, marker, sessionId) {
|
|
|
26153
27203
|
return true;
|
|
26154
27204
|
}
|
|
26155
27205
|
|
|
27206
|
+
// ../../packages/plugin-sdk/src/paths.ts
|
|
27207
|
+
import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
27208
|
+
import { basename as basename4, dirname as dirname2, sep as sep3 } from "path";
|
|
27209
|
+
|
|
26156
27210
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
26157
27211
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
26158
|
-
import { existsSync as existsSync4, readdirSync as
|
|
26159
|
-
import { basename as
|
|
27212
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
27213
|
+
import { basename as basename5, join as join9, relative, sep as sep4 } from "path";
|
|
26160
27214
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
26161
27215
|
".git",
|
|
26162
27216
|
"node_modules",
|
|
@@ -26185,7 +27239,7 @@ function readIgnoreLayer(dir) {
|
|
|
26185
27239
|
function isIgnored(layers, absPath, isDir) {
|
|
26186
27240
|
let ignored = false;
|
|
26187
27241
|
for (const layer of layers) {
|
|
26188
|
-
const rel = relative(layer.base, absPath).split(
|
|
27242
|
+
const rel = relative(layer.base, absPath).split(sep4).join("/") + (isDir ? "/" : "");
|
|
26189
27243
|
const verdict = layer.matcher.test(rel);
|
|
26190
27244
|
if (verdict.ignored) ignored = true;
|
|
26191
27245
|
else if (verdict.unignored) ignored = false;
|
|
@@ -26246,7 +27300,7 @@ function resolveProjectFiles(cwd) {
|
|
|
26246
27300
|
let visit2 = function(dir, layers) {
|
|
26247
27301
|
let dirents;
|
|
26248
27302
|
try {
|
|
26249
|
-
dirents =
|
|
27303
|
+
dirents = readdirSync3(dir, { withFileTypes: true, encoding: "utf8" });
|
|
26250
27304
|
} catch {
|
|
26251
27305
|
walk.lostSubtree = true;
|
|
26252
27306
|
return false;
|
|
@@ -26265,10 +27319,10 @@ function resolveProjectFiles(cwd) {
|
|
|
26265
27319
|
if (entry.name === ".git") continue;
|
|
26266
27320
|
if (isIgnored(dirLayers, fullPath, false)) continue;
|
|
26267
27321
|
if (files.length >= MAX_FILES) return true;
|
|
26268
|
-
const relPath = relative(root, fullPath).split(
|
|
27322
|
+
const relPath = relative(root, fullPath).split(sep4).join("/");
|
|
26269
27323
|
files.push({
|
|
26270
27324
|
path: relPath,
|
|
26271
|
-
name:
|
|
27325
|
+
name: basename5(entry.name),
|
|
26272
27326
|
origin: classifyOrigin(relPath, entry.name),
|
|
26273
27327
|
defaultAccess: "approved"
|
|
26274
27328
|
});
|
|
@@ -26586,6 +27640,13 @@ var StandaloneDataGateway = class {
|
|
|
26586
27640
|
this.db.scanLedger.upsertEntries(entries);
|
|
26587
27641
|
return Promise.resolve();
|
|
26588
27642
|
}
|
|
27643
|
+
getRuleProbeVerdict(ruleKey) {
|
|
27644
|
+
return Promise.resolve(this.db.ruleProbeCache.getVerdict(ruleKey));
|
|
27645
|
+
}
|
|
27646
|
+
setRuleProbeVerdict(ruleKey, verdict, worstProbeMs) {
|
|
27647
|
+
this.db.ruleProbeCache.setVerdict(ruleKey, verdict, worstProbeMs);
|
|
27648
|
+
return Promise.resolve();
|
|
27649
|
+
}
|
|
26589
27650
|
openAtRestKeysForPath(path) {
|
|
26590
27651
|
return Promise.resolve(this.db.resolutions.openAtRestKeysForPath(path));
|
|
26591
27652
|
}
|
|
@@ -26596,6 +27657,12 @@ var StandaloneDataGateway = class {
|
|
|
26596
27657
|
this.db.resolutions.insertResolution(input);
|
|
26597
27658
|
return Promise.resolve();
|
|
26598
27659
|
}
|
|
27660
|
+
// Bare forward — no toggle read here. The plugin-path kill-switch is
|
|
27661
|
+
// enforced by the caller, which already holds the parsed workspace
|
|
27662
|
+
// settings; this class only ever sees `dataDir`, not the settings base.
|
|
27663
|
+
recordProjectEgress(input) {
|
|
27664
|
+
return Promise.resolve(this.db.shares.recordProjectEgress(input));
|
|
27665
|
+
}
|
|
26599
27666
|
close() {
|
|
26600
27667
|
this.db.close();
|
|
26601
27668
|
return Promise.resolve();
|
|
@@ -26740,7 +27807,7 @@ function buildSessionRoot(sessionId, input, ctx, resolved, provider, branch) {
|
|
|
26740
27807
|
|
|
26741
27808
|
// src/history/reconcile-trigger.ts
|
|
26742
27809
|
import { spawn } from "child_process";
|
|
26743
|
-
import { dirname as
|
|
27810
|
+
import { dirname as dirname3, join as join12 } from "path";
|
|
26744
27811
|
import { fileURLToPath } from "url";
|
|
26745
27812
|
|
|
26746
27813
|
// src/history/tail.ts
|
|
@@ -26770,7 +27837,7 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
|
|
|
26770
27837
|
try {
|
|
26771
27838
|
const marker = `${RECONCILE_MARKER_PREFIX}-${safeSessionId(sessionId)}`;
|
|
26772
27839
|
if (throttled(dataDir2, marker, RECONCILE_THROTTLE_MS)) return;
|
|
26773
|
-
const here =
|
|
27840
|
+
const here = dirname3(fileURLToPath(import.meta.url));
|
|
26774
27841
|
const child = spawn(process.execPath, [join12(here, "reconcile.js"), sessionId, transcriptPath], {
|
|
26775
27842
|
detached: true,
|
|
26776
27843
|
stdio: "ignore"
|
|
@@ -26784,13 +27851,23 @@ function triggerReconcile(dataDir2, sessionId, transcriptPath) {
|
|
|
26784
27851
|
async function readStdin() {
|
|
26785
27852
|
return new Promise((resolve) => {
|
|
26786
27853
|
let data = "";
|
|
26787
|
-
|
|
26788
|
-
|
|
26789
|
-
|
|
26790
|
-
|
|
26791
|
-
|
|
27854
|
+
let settled = false;
|
|
27855
|
+
const finish = () => {
|
|
27856
|
+
if (settled) return;
|
|
27857
|
+
settled = true;
|
|
27858
|
+
clearTimeout(timer);
|
|
27859
|
+
process.stdin.removeListener("data", onData);
|
|
27860
|
+
process.stdin.removeListener("end", finish);
|
|
26792
27861
|
resolve(data);
|
|
26793
|
-
}
|
|
27862
|
+
};
|
|
27863
|
+
const onData = (chunk) => {
|
|
27864
|
+
data += chunk;
|
|
27865
|
+
};
|
|
27866
|
+
const timer = setTimeout(finish, 5e3);
|
|
27867
|
+
process.stdin.setEncoding("utf8");
|
|
27868
|
+
process.stdin.on("data", onData);
|
|
27869
|
+
process.stdin.on("end", finish);
|
|
27870
|
+
process.stdin.on("error", finish);
|
|
26794
27871
|
});
|
|
26795
27872
|
}
|
|
26796
27873
|
function parseJson(raw) {
|