@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
|
@@ -494,7 +494,7 @@ var require_ignore = __commonJS({
|
|
|
494
494
|
// src/apply-suppressions.ts
|
|
495
495
|
import { existsSync as existsSync5, readFileSync as readFileSync9 } from "fs";
|
|
496
496
|
import { userInfo } from "os";
|
|
497
|
-
import { dirname as
|
|
497
|
+
import { dirname as dirname5, join as join13 } from "path";
|
|
498
498
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
499
499
|
|
|
500
500
|
// ../../packages/persistence/src/database.ts
|
|
@@ -548,6 +548,10 @@ var SQLITE_MIGRATIONS = [
|
|
|
548
548
|
{
|
|
549
549
|
tag: "0010_events_session_expression_index",
|
|
550
550
|
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"
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
tag: "0011_egress_writer",
|
|
554
|
+
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'
|
|
551
555
|
}
|
|
552
556
|
];
|
|
553
557
|
|
|
@@ -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), {
|
|
@@ -16801,6 +16807,212 @@ function buildDetectionsList(summaries, query) {
|
|
|
16801
16807
|
return { counts, items: filtered.map(summaryToDetectionListItem) };
|
|
16802
16808
|
}
|
|
16803
16809
|
|
|
16810
|
+
// ../../packages/schema/src/zod/shares.ts
|
|
16811
|
+
var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
|
|
16812
|
+
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
|
|
16813
|
+
var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
|
|
16814
|
+
var DATA_CLASS_ORDER = DataClass.options;
|
|
16815
|
+
var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
|
|
16816
|
+
var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
|
|
16817
|
+
var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
|
|
16818
|
+
var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
|
|
16819
|
+
var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE", "SDK", "REF"]).meta({ id: "HttpMethod" });
|
|
16820
|
+
var ReviewInfo = external_exports.object({
|
|
16821
|
+
needsReview: external_exports.boolean(),
|
|
16822
|
+
reasons: external_exports.array(ReviewReason)
|
|
16823
|
+
}).meta({ id: "ReviewInfo" });
|
|
16824
|
+
var DestinationNetwork = external_exports.object({
|
|
16825
|
+
port: external_exports.number().int().nullable(),
|
|
16826
|
+
geo: external_exports.string().nullable(),
|
|
16827
|
+
ptr: external_exports.string().nullable()
|
|
16828
|
+
}).meta({ id: "DestinationNetwork" });
|
|
16829
|
+
var EndpointSummary = external_exports.object({
|
|
16830
|
+
id: external_exports.string(),
|
|
16831
|
+
method: HttpMethod,
|
|
16832
|
+
transport: Transport,
|
|
16833
|
+
url: external_exports.string(),
|
|
16834
|
+
template: external_exports.boolean(),
|
|
16835
|
+
dataClass: DataClass,
|
|
16836
|
+
lastSeen: external_exports.iso.datetime(),
|
|
16837
|
+
callSiteCount: external_exports.number().int().nonnegative()
|
|
16838
|
+
}).meta({ id: "EndpointSummary" });
|
|
16839
|
+
var CallSite = external_exports.object({
|
|
16840
|
+
id: external_exports.string(),
|
|
16841
|
+
project: external_exports.string(),
|
|
16842
|
+
file: external_exports.string(),
|
|
16843
|
+
line: external_exports.number().int().nonnegative(),
|
|
16844
|
+
snippet: external_exports.string(),
|
|
16845
|
+
dynamic: external_exports.boolean(),
|
|
16846
|
+
vendored: external_exports.boolean(),
|
|
16847
|
+
/** Deep-link to the Inventory project, when the repo is governed there. */
|
|
16848
|
+
projectId: external_exports.string().nullable()
|
|
16849
|
+
}).meta({ id: "CallSite" });
|
|
16850
|
+
var EndpointWithSites = EndpointSummary.extend({
|
|
16851
|
+
sites: external_exports.array(CallSite)
|
|
16852
|
+
}).meta({ id: "EndpointWithSites" });
|
|
16853
|
+
var ShareDestinationSummary = external_exports.object({
|
|
16854
|
+
id: external_exports.string(),
|
|
16855
|
+
kind: DestinationKind,
|
|
16856
|
+
name: external_exports.string(),
|
|
16857
|
+
host: external_exports.string(),
|
|
16858
|
+
category: external_exports.string(),
|
|
16859
|
+
trust: ShareTrustLevel,
|
|
16860
|
+
/** Effective state (decision applied over the trust default). */
|
|
16861
|
+
status: EgressStatus,
|
|
16862
|
+
/** True when an egress decision override differs from the trust default. */
|
|
16863
|
+
isCustom: external_exports.boolean(),
|
|
16864
|
+
lastSeen: external_exports.iso.datetime(),
|
|
16865
|
+
endpointCount: external_exports.number().int().nonnegative(),
|
|
16866
|
+
callSiteCount: external_exports.number().int().nonnegative(),
|
|
16867
|
+
transports: external_exports.array(Transport),
|
|
16868
|
+
/** Most-sensitive first. */
|
|
16869
|
+
dataClasses: external_exports.array(DataClass),
|
|
16870
|
+
review: ReviewInfo,
|
|
16871
|
+
/** Non-provider hosts only; null for providers. */
|
|
16872
|
+
network: DestinationNetwork.nullable(),
|
|
16873
|
+
/** Embedded for inline expansion — no call sites here. */
|
|
16874
|
+
endpoints: external_exports.array(EndpointSummary)
|
|
16875
|
+
}).meta({ id: "ShareDestinationSummary" });
|
|
16876
|
+
var ShareDestinationDetail = ShareDestinationSummary.omit({
|
|
16877
|
+
endpointCount: true,
|
|
16878
|
+
callSiteCount: true,
|
|
16879
|
+
endpoints: true
|
|
16880
|
+
}).extend({
|
|
16881
|
+
/** Ownership/geo rationale; null for providers. */
|
|
16882
|
+
note: external_exports.string().nullable(),
|
|
16883
|
+
endpoints: external_exports.array(EndpointWithSites)
|
|
16884
|
+
}).meta({ id: "ShareDestinationDetail" });
|
|
16885
|
+
var ReviewDestination = external_exports.object({
|
|
16886
|
+
id: external_exports.string(),
|
|
16887
|
+
kind: DestinationKind,
|
|
16888
|
+
name: external_exports.string(),
|
|
16889
|
+
/** Registrable host — lets the strip derive the provider lettermark, as the register does. */
|
|
16890
|
+
host: external_exports.string(),
|
|
16891
|
+
trust: ShareTrustLevel,
|
|
16892
|
+
status: EgressStatus,
|
|
16893
|
+
review: ReviewInfo,
|
|
16894
|
+
topDataClass: DataClass,
|
|
16895
|
+
callSiteCount: external_exports.number().int().nonnegative(),
|
|
16896
|
+
lastSeen: external_exports.iso.datetime()
|
|
16897
|
+
}).meta({ id: "ReviewDestination" });
|
|
16898
|
+
var ShareDestinationGroup = external_exports.object({
|
|
16899
|
+
kind: DestinationKind,
|
|
16900
|
+
total: external_exports.number().int().nonnegative(),
|
|
16901
|
+
items: external_exports.array(ShareDestinationSummary)
|
|
16902
|
+
}).meta({ id: "ShareDestinationGroup" });
|
|
16903
|
+
var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
|
|
16904
|
+
var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
|
|
16905
|
+
var SharesStats = external_exports.object({
|
|
16906
|
+
destinations: external_exports.number().int().nonnegative(),
|
|
16907
|
+
endpoints: external_exports.number().int().nonnegative(),
|
|
16908
|
+
callSites: external_exports.number().int().nonnegative(),
|
|
16909
|
+
needsReview: external_exports.number().int().nonnegative(),
|
|
16910
|
+
insecure: external_exports.number().int().nonnegative(),
|
|
16911
|
+
byKind: external_exports.object({
|
|
16912
|
+
provider: external_exports.number().int().nonnegative(),
|
|
16913
|
+
internal: external_exports.number().int().nonnegative(),
|
|
16914
|
+
external: external_exports.number().int().nonnegative(),
|
|
16915
|
+
ip: external_exports.number().int().nonnegative()
|
|
16916
|
+
}),
|
|
16917
|
+
byTrust: external_exports.object({
|
|
16918
|
+
recognized: external_exports.number().int().nonnegative(),
|
|
16919
|
+
internal: external_exports.number().int().nonnegative(),
|
|
16920
|
+
unverified: external_exports.number().int().nonnegative(),
|
|
16921
|
+
ip: external_exports.number().int().nonnegative()
|
|
16922
|
+
})
|
|
16923
|
+
}).meta({ id: "SharesStats" });
|
|
16924
|
+
var SetEgressDecisionBody = external_exports.object({
|
|
16925
|
+
/** `null` clears the override — reverts to the trust default, isCustom false. */
|
|
16926
|
+
decision: EgressDecision.nullable()
|
|
16927
|
+
}).meta({ id: "SetEgressDecisionBody" });
|
|
16928
|
+
var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
|
|
16929
|
+
var ListShareDestinationsQuery = external_exports.object({
|
|
16930
|
+
/** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
|
|
16931
|
+
q: external_exports.string().optional(),
|
|
16932
|
+
/** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
|
|
16933
|
+
kind: external_exports.array(DestinationKind).optional(),
|
|
16934
|
+
/** Reserved for future grouping modes; only 'destination' is supported today. */
|
|
16935
|
+
groupBy: external_exports.enum(["destination"]).default("destination"),
|
|
16936
|
+
/**
|
|
16937
|
+
* When true, return a flat severity-ordered `items[]` instead of `groups`.
|
|
16938
|
+
* Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
|
|
16939
|
+
* any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
|
|
16940
|
+
* to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
|
|
16941
|
+
*/
|
|
16942
|
+
review: external_exports.stringbool().default(false)
|
|
16943
|
+
});
|
|
16944
|
+
var ExportSharesQuery = external_exports.object({
|
|
16945
|
+
format: external_exports.enum(["csv", "json"]).default("csv"),
|
|
16946
|
+
q: external_exports.string().optional(),
|
|
16947
|
+
kind: external_exports.array(DestinationKind).optional()
|
|
16948
|
+
});
|
|
16949
|
+
|
|
16950
|
+
// ../../packages/schema/src/zod/egress-extraction.ts
|
|
16951
|
+
var EgressEcosystem = external_exports.enum(["npm", "pypi", "go", "maven", "rubygems", "cargo", "composer", "nuget"]).meta({ id: "EgressEcosystem" });
|
|
16952
|
+
var ProviderRegistryEntry = external_exports.object({
|
|
16953
|
+
id: external_exports.string(),
|
|
16954
|
+
name: external_exports.string(),
|
|
16955
|
+
category: external_exports.string(),
|
|
16956
|
+
/** Suffix-matched: 'stripe.com' matches api.stripe.com, never evilstripe.com. */
|
|
16957
|
+
hostSuffixes: external_exports.array(external_exports.string()).min(1),
|
|
16958
|
+
/** Canonical API base URL recorded for manifest-derived (method 'SDK') endpoints. */
|
|
16959
|
+
apiBase: external_exports.string(),
|
|
16960
|
+
/** Most-sensitive first; index 0 becomes the endpoint dataClass. */
|
|
16961
|
+
defaultDataClasses: external_exports.array(DataClass).min(1),
|
|
16962
|
+
/** SDK identifiers per ecosystem ('go' prefix-matched by path, 'maven' by group-id prefix). */
|
|
16963
|
+
sdks: external_exports.partialRecord(EgressEcosystem, external_exports.array(external_exports.string()))
|
|
16964
|
+
}).meta({ id: "ProviderRegistryEntry" });
|
|
16965
|
+
var EgressCallSiteHit = external_exports.object({
|
|
16966
|
+
file: external_exports.string(),
|
|
16967
|
+
line: external_exports.number().int().positive(),
|
|
16968
|
+
snippet: external_exports.string(),
|
|
16969
|
+
dynamic: external_exports.boolean(),
|
|
16970
|
+
vendored: external_exports.boolean()
|
|
16971
|
+
}).meta({ id: "EgressCallSiteHit" });
|
|
16972
|
+
var ResolvedEgressHit = external_exports.object({
|
|
16973
|
+
host: external_exports.string(),
|
|
16974
|
+
kind: DestinationKind,
|
|
16975
|
+
name: external_exports.string(),
|
|
16976
|
+
category: external_exports.string(),
|
|
16977
|
+
trust: ShareTrustLevel,
|
|
16978
|
+
network: DestinationNetwork.nullable(),
|
|
16979
|
+
method: HttpMethod,
|
|
16980
|
+
transport: Transport,
|
|
16981
|
+
url: external_exports.string(),
|
|
16982
|
+
template: external_exports.boolean(),
|
|
16983
|
+
dataClass: DataClass,
|
|
16984
|
+
site: EgressCallSiteHit
|
|
16985
|
+
}).meta({ id: "ResolvedEgressHit" });
|
|
16986
|
+
var EgressReconcile = external_exports.discriminatedUnion("mode", [
|
|
16987
|
+
external_exports.object({ mode: external_exports.literal("walk"), walkedPrefix: external_exports.string() }),
|
|
16988
|
+
external_exports.object({
|
|
16989
|
+
mode: external_exports.literal("ledger"),
|
|
16990
|
+
scannedFiles: external_exports.array(external_exports.string()),
|
|
16991
|
+
deletedFiles: external_exports.array(external_exports.string())
|
|
16992
|
+
})
|
|
16993
|
+
]).meta({ id: "EgressReconcile" });
|
|
16994
|
+
var RecordProjectEgressInput = external_exports.object({
|
|
16995
|
+
/** Stable reconcile key: 'git:<repo identity>' or 'path:<abs root>' (non-git). */
|
|
16996
|
+
projectKey: external_exports.string().min(1),
|
|
16997
|
+
/** Display name only — never keys reconciliation. */
|
|
16998
|
+
project: external_exports.string(),
|
|
16999
|
+
projectId: external_exports.string().nullable(),
|
|
17000
|
+
reconcile: EgressReconcile,
|
|
17001
|
+
hits: external_exports.array(ResolvedEgressHit)
|
|
17002
|
+
}).meta({ id: "RecordProjectEgressInput" });
|
|
17003
|
+
var EgressWriteSummary = external_exports.object({
|
|
17004
|
+
destinations: external_exports.number().int().nonnegative(),
|
|
17005
|
+
endpoints: external_exports.number().int().nonnegative(),
|
|
17006
|
+
callSites: external_exports.number().int().nonnegative(),
|
|
17007
|
+
truncated: external_exports.boolean(),
|
|
17008
|
+
/**
|
|
17009
|
+
* Files the cap dropped whole. Their stored rows were left untouched, so a
|
|
17010
|
+
* ledger-keeping caller must withhold their ledger entries and read them
|
|
17011
|
+
* again next scan.
|
|
17012
|
+
*/
|
|
17013
|
+
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17014
|
+
}).meta({ id: "EgressWriteSummary" });
|
|
17015
|
+
|
|
16804
17016
|
// ../../packages/schema/src/zod/findings-group-build.ts
|
|
16805
17017
|
function toApiAction(dbVal) {
|
|
16806
17018
|
const map2 = {
|
|
@@ -17062,7 +17274,7 @@ var PatchInstalledPackRequest = external_exports.object({
|
|
|
17062
17274
|
}).meta({ id: "PatchInstalledPackRequest" });
|
|
17063
17275
|
|
|
17064
17276
|
// ../../packages/schema/src/zod/local.ts
|
|
17065
|
-
var WORKSPACE_SETTINGS_SPEC_VERSION =
|
|
17277
|
+
var WORKSPACE_SETTINGS_SPEC_VERSION = 3;
|
|
17066
17278
|
var RunMode = external_exports.enum(["standalone"]);
|
|
17067
17279
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17068
17280
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
@@ -17077,6 +17289,9 @@ var WorkspaceSettings = external_exports.object({
|
|
|
17077
17289
|
policy: SimpleDetectionPolicy.default("redact"),
|
|
17078
17290
|
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
17079
17291
|
historicalAccess: HistoricalAccess.default("session-only"),
|
|
17292
|
+
// In-place egress extraction on the scan paths; disable to stop all Data
|
|
17293
|
+
// Shares writes.
|
|
17294
|
+
dataSharesInPlace: external_exports.boolean().default(true),
|
|
17080
17295
|
// Absent until /aka:setup completes; its presence is what "onboarded" means.
|
|
17081
17296
|
onboardedAt: external_exports.iso.datetime().optional()
|
|
17082
17297
|
});
|
|
@@ -17560,145 +17775,6 @@ var SetupHandoffOffer = external_exports.object({
|
|
|
17560
17775
|
path: ["liveKeys"]
|
|
17561
17776
|
});
|
|
17562
17777
|
|
|
17563
|
-
// ../../packages/schema/src/zod/shares.ts
|
|
17564
|
-
var DestinationKind = external_exports.enum(["provider", "internal", "ip"]).meta({ id: "DestinationKind" });
|
|
17565
|
-
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp"]).meta({ id: "Transport" });
|
|
17566
|
-
var DataClass = external_exports.enum(["secrets", "pii", "customer", "source", "telemetry", "logs", "metrics", "none"]).meta({ id: "DataClass" });
|
|
17567
|
-
var DATA_CLASS_ORDER = DataClass.options;
|
|
17568
|
-
var ShareTrustLevel = external_exports.enum(["recognized", "internal", "unverified", "ip"]).meta({ id: "ShareTrustLevel" });
|
|
17569
|
-
var EgressDecision = external_exports.enum(["allow", "block"]).meta({ id: "EgressDecision" });
|
|
17570
|
-
var EgressStatus = external_exports.enum(["allowed", "blocked", "review"]).meta({ id: "EgressStatus" });
|
|
17571
|
-
var ReviewReason = external_exports.enum(["raw_ip", "unverified_domain", "plaintext_transport"]).meta({ id: "ReviewReason" });
|
|
17572
|
-
var HttpMethod = external_exports.enum(["GET", "POST", "PUT", "DELETE"]).meta({ id: "HttpMethod" });
|
|
17573
|
-
var ReviewInfo = external_exports.object({
|
|
17574
|
-
needsReview: external_exports.boolean(),
|
|
17575
|
-
reasons: external_exports.array(ReviewReason)
|
|
17576
|
-
}).meta({ id: "ReviewInfo" });
|
|
17577
|
-
var DestinationNetwork = external_exports.object({
|
|
17578
|
-
port: external_exports.number().int().nullable(),
|
|
17579
|
-
geo: external_exports.string().nullable(),
|
|
17580
|
-
ptr: external_exports.string().nullable()
|
|
17581
|
-
}).meta({ id: "DestinationNetwork" });
|
|
17582
|
-
var EndpointSummary = external_exports.object({
|
|
17583
|
-
id: external_exports.string(),
|
|
17584
|
-
method: HttpMethod,
|
|
17585
|
-
transport: Transport,
|
|
17586
|
-
url: external_exports.string(),
|
|
17587
|
-
template: external_exports.boolean(),
|
|
17588
|
-
dataClass: DataClass,
|
|
17589
|
-
lastSeen: external_exports.iso.datetime(),
|
|
17590
|
-
callSiteCount: external_exports.number().int().nonnegative()
|
|
17591
|
-
}).meta({ id: "EndpointSummary" });
|
|
17592
|
-
var CallSite = external_exports.object({
|
|
17593
|
-
id: external_exports.string(),
|
|
17594
|
-
project: external_exports.string(),
|
|
17595
|
-
file: external_exports.string(),
|
|
17596
|
-
line: external_exports.number().int().nonnegative(),
|
|
17597
|
-
snippet: external_exports.string(),
|
|
17598
|
-
dynamic: external_exports.boolean(),
|
|
17599
|
-
vendored: external_exports.boolean(),
|
|
17600
|
-
/** Deep-link to the Inventory project, when the repo is governed there. */
|
|
17601
|
-
projectId: external_exports.string().nullable()
|
|
17602
|
-
}).meta({ id: "CallSite" });
|
|
17603
|
-
var EndpointWithSites = EndpointSummary.extend({
|
|
17604
|
-
sites: external_exports.array(CallSite)
|
|
17605
|
-
}).meta({ id: "EndpointWithSites" });
|
|
17606
|
-
var ShareDestinationSummary = external_exports.object({
|
|
17607
|
-
id: external_exports.string(),
|
|
17608
|
-
kind: DestinationKind,
|
|
17609
|
-
name: external_exports.string(),
|
|
17610
|
-
host: external_exports.string(),
|
|
17611
|
-
category: external_exports.string(),
|
|
17612
|
-
trust: ShareTrustLevel,
|
|
17613
|
-
/** Effective state (decision applied over the trust default). */
|
|
17614
|
-
status: EgressStatus,
|
|
17615
|
-
/** True when an egress decision override differs from the trust default. */
|
|
17616
|
-
isCustom: external_exports.boolean(),
|
|
17617
|
-
lastSeen: external_exports.iso.datetime(),
|
|
17618
|
-
endpointCount: external_exports.number().int().nonnegative(),
|
|
17619
|
-
callSiteCount: external_exports.number().int().nonnegative(),
|
|
17620
|
-
transports: external_exports.array(Transport),
|
|
17621
|
-
/** Most-sensitive first. */
|
|
17622
|
-
dataClasses: external_exports.array(DataClass),
|
|
17623
|
-
review: ReviewInfo,
|
|
17624
|
-
/** Non-provider hosts only; null for providers. */
|
|
17625
|
-
network: DestinationNetwork.nullable(),
|
|
17626
|
-
/** Embedded for inline expansion — no call sites here. */
|
|
17627
|
-
endpoints: external_exports.array(EndpointSummary)
|
|
17628
|
-
}).meta({ id: "ShareDestinationSummary" });
|
|
17629
|
-
var ShareDestinationDetail = ShareDestinationSummary.omit({
|
|
17630
|
-
endpointCount: true,
|
|
17631
|
-
callSiteCount: true,
|
|
17632
|
-
endpoints: true
|
|
17633
|
-
}).extend({
|
|
17634
|
-
/** Ownership/geo rationale; null for providers. */
|
|
17635
|
-
note: external_exports.string().nullable(),
|
|
17636
|
-
endpoints: external_exports.array(EndpointWithSites)
|
|
17637
|
-
}).meta({ id: "ShareDestinationDetail" });
|
|
17638
|
-
var ReviewDestination = external_exports.object({
|
|
17639
|
-
id: external_exports.string(),
|
|
17640
|
-
kind: DestinationKind,
|
|
17641
|
-
name: external_exports.string(),
|
|
17642
|
-
/** Registrable host — lets the strip derive the provider lettermark, as the register does. */
|
|
17643
|
-
host: external_exports.string(),
|
|
17644
|
-
trust: ShareTrustLevel,
|
|
17645
|
-
status: EgressStatus,
|
|
17646
|
-
review: ReviewInfo,
|
|
17647
|
-
topDataClass: DataClass,
|
|
17648
|
-
callSiteCount: external_exports.number().int().nonnegative(),
|
|
17649
|
-
lastSeen: external_exports.iso.datetime()
|
|
17650
|
-
}).meta({ id: "ReviewDestination" });
|
|
17651
|
-
var ShareDestinationGroup = external_exports.object({
|
|
17652
|
-
kind: DestinationKind,
|
|
17653
|
-
total: external_exports.number().int().nonnegative(),
|
|
17654
|
-
items: external_exports.array(ShareDestinationSummary)
|
|
17655
|
-
}).meta({ id: "ShareDestinationGroup" });
|
|
17656
|
-
var ListShareDestinationsResponse = external_exports.object({ groups: external_exports.array(ShareDestinationGroup) }).meta({ id: "ListShareDestinationsResponse" });
|
|
17657
|
-
var NeedsReviewResponse = external_exports.object({ items: external_exports.array(ReviewDestination) }).meta({ id: "NeedsReviewResponse" });
|
|
17658
|
-
var SharesStats = external_exports.object({
|
|
17659
|
-
destinations: external_exports.number().int().nonnegative(),
|
|
17660
|
-
endpoints: external_exports.number().int().nonnegative(),
|
|
17661
|
-
callSites: external_exports.number().int().nonnegative(),
|
|
17662
|
-
needsReview: external_exports.number().int().nonnegative(),
|
|
17663
|
-
insecure: external_exports.number().int().nonnegative(),
|
|
17664
|
-
byKind: external_exports.object({
|
|
17665
|
-
provider: external_exports.number().int().nonnegative(),
|
|
17666
|
-
internal: external_exports.number().int().nonnegative(),
|
|
17667
|
-
ip: external_exports.number().int().nonnegative()
|
|
17668
|
-
}),
|
|
17669
|
-
byTrust: external_exports.object({
|
|
17670
|
-
recognized: external_exports.number().int().nonnegative(),
|
|
17671
|
-
internal: external_exports.number().int().nonnegative(),
|
|
17672
|
-
unverified: external_exports.number().int().nonnegative(),
|
|
17673
|
-
ip: external_exports.number().int().nonnegative()
|
|
17674
|
-
})
|
|
17675
|
-
}).meta({ id: "SharesStats" });
|
|
17676
|
-
var SetEgressDecisionBody = external_exports.object({
|
|
17677
|
-
/** `null` clears the override — reverts to the trust default, isCustom false. */
|
|
17678
|
-
decision: EgressDecision.nullable()
|
|
17679
|
-
}).meta({ id: "SetEgressDecisionBody" });
|
|
17680
|
-
var SetEgressDecisionResponse = external_exports.object({ destination: ShareDestinationSummary }).meta({ id: "SetEgressDecisionResponse" });
|
|
17681
|
-
var ListShareDestinationsQuery = external_exports.object({
|
|
17682
|
-
/** Case-insensitive match over destination name/category, endpoint url, call-site project/file. */
|
|
17683
|
-
q: external_exports.string().optional(),
|
|
17684
|
-
/** Repeatable. Restrict to these DestinationKind values; absent means all kinds. */
|
|
17685
|
-
kind: external_exports.array(DestinationKind).optional(),
|
|
17686
|
-
/** Reserved for future grouping modes; only 'destination' is supported today. */
|
|
17687
|
-
groupBy: external_exports.enum(["destination"]).default("destination"),
|
|
17688
|
-
/**
|
|
17689
|
-
* When true, return a flat severity-ordered `items[]` instead of `groups`.
|
|
17690
|
-
* Uses `z.stringbool()` (NOT `z.coerce.boolean()` — `Boolean(str)` is true for
|
|
17691
|
-
* any non-empty string, so `?review=false`/`?review=0` would wrongly coerce
|
|
17692
|
-
* to `true`). `z.stringbool()` parses true/1/yes vs false/0/no correctly.
|
|
17693
|
-
*/
|
|
17694
|
-
review: external_exports.stringbool().default(false)
|
|
17695
|
-
});
|
|
17696
|
-
var ExportSharesQuery = external_exports.object({
|
|
17697
|
-
format: external_exports.enum(["csv", "json"]).default("csv"),
|
|
17698
|
-
q: external_exports.string().optional(),
|
|
17699
|
-
kind: external_exports.array(DestinationKind).optional()
|
|
17700
|
-
});
|
|
17701
|
-
|
|
17702
17778
|
// ../../packages/schema/src/zod/shares-access.ts
|
|
17703
17779
|
var ALLOWED_BY_DEFAULT_TRUST = /* @__PURE__ */ new Set(["recognized", "internal"]);
|
|
17704
17780
|
function trustDefaultStatus(trust) {
|
|
@@ -17718,7 +17794,7 @@ function deriveReviewReasons(trust, transports) {
|
|
|
17718
17794
|
const reasons = [];
|
|
17719
17795
|
if (trust === "ip") reasons.push("raw_ip");
|
|
17720
17796
|
if (trust === "unverified") reasons.push("unverified_domain");
|
|
17721
|
-
if (transports.includes("http")) reasons.push("plaintext_transport");
|
|
17797
|
+
if (transports.includes("http") || transports.includes("ws")) reasons.push("plaintext_transport");
|
|
17722
17798
|
return reasons;
|
|
17723
17799
|
}
|
|
17724
17800
|
function buildReviewInfo(trust, transports) {
|
|
@@ -17938,6 +18014,7 @@ function applyMigrations(db) {
|
|
|
17938
18014
|
ensureSyncedAtColumn(db, "audit_events");
|
|
17939
18015
|
ensureScanLedgerTable(db);
|
|
17940
18016
|
ensureBlockedDetectionsTable(db);
|
|
18017
|
+
ensureRuleProbeCacheTable(db);
|
|
17941
18018
|
ensureWriteGateTrigger(db);
|
|
17942
18019
|
ensureTokenUsageColumns(db);
|
|
17943
18020
|
reconcileSourceProjectIds(db);
|
|
@@ -18077,6 +18154,14 @@ function ensureBlockedDetectionsTable(db) {
|
|
|
18077
18154
|
blocked_at INTEGER NOT NULL
|
|
18078
18155
|
)`);
|
|
18079
18156
|
}
|
|
18157
|
+
function ensureRuleProbeCacheTable(db) {
|
|
18158
|
+
db.exec(`CREATE TABLE IF NOT EXISTS rule_probe_cache (
|
|
18159
|
+
rule_key TEXT PRIMARY KEY,
|
|
18160
|
+
verdict TEXT NOT NULL,
|
|
18161
|
+
worst_probe_ms REAL NOT NULL,
|
|
18162
|
+
checked_at INTEGER NOT NULL
|
|
18163
|
+
)`);
|
|
18164
|
+
}
|
|
18080
18165
|
|
|
18081
18166
|
// ../../packages/persistence/src/paths.ts
|
|
18082
18167
|
import { chmodSync, mkdirSync } from "fs";
|
|
@@ -21630,6 +21715,35 @@ var SqliteResolutionsRepository = class {
|
|
|
21630
21715
|
}
|
|
21631
21716
|
};
|
|
21632
21717
|
|
|
21718
|
+
// ../../packages/persistence/src/repositories/rule-probe-cache.ts
|
|
21719
|
+
var SqliteRuleProbeCacheRepository = class {
|
|
21720
|
+
constructor(db) {
|
|
21721
|
+
this.db = db;
|
|
21722
|
+
this.upsertStmt = db.prepare(
|
|
21723
|
+
`INSERT INTO rule_probe_cache (rule_key, verdict, worst_probe_ms, checked_at)
|
|
21724
|
+
VALUES (:ruleKey, :verdict, :worstProbeMs, :checkedAt)
|
|
21725
|
+
ON CONFLICT (rule_key) DO UPDATE SET
|
|
21726
|
+
verdict = excluded.verdict,
|
|
21727
|
+
worst_probe_ms = excluded.worst_probe_ms,
|
|
21728
|
+
checked_at = excluded.checked_at`
|
|
21729
|
+
);
|
|
21730
|
+
this.readStmt = db.prepare(
|
|
21731
|
+
`SELECT verdict, worst_probe_ms AS worstProbeMs FROM rule_probe_cache WHERE rule_key = :ruleKey`
|
|
21732
|
+
);
|
|
21733
|
+
}
|
|
21734
|
+
db;
|
|
21735
|
+
upsertStmt;
|
|
21736
|
+
readStmt;
|
|
21737
|
+
getVerdict(ruleKey) {
|
|
21738
|
+
return getRow(this.readStmt, { ruleKey });
|
|
21739
|
+
}
|
|
21740
|
+
setVerdict(ruleKey, verdict, worstProbeMs) {
|
|
21741
|
+
failOpenTransaction(this.db, () => {
|
|
21742
|
+
this.upsertStmt.run({ ruleKey, verdict, worstProbeMs, checkedAt: Date.now() });
|
|
21743
|
+
});
|
|
21744
|
+
}
|
|
21745
|
+
};
|
|
21746
|
+
|
|
21633
21747
|
// ../../packages/persistence/src/repositories/scan-ledger.ts
|
|
21634
21748
|
var SqliteScanLedgerRepository = class {
|
|
21635
21749
|
constructor(db) {
|
|
@@ -22041,11 +22155,50 @@ var SqliteSecurityRepository = class {
|
|
|
22041
22155
|
|
|
22042
22156
|
// ../../packages/persistence/src/repositories/shares.ts
|
|
22043
22157
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
22044
|
-
var
|
|
22158
|
+
var MAX_EGRESS_CALL_SITES_PER_PROJECT = 5e3;
|
|
22159
|
+
var IN_CHUNK = 500;
|
|
22160
|
+
var KIND_ORDER = ["provider", "internal", "external", "ip"];
|
|
22161
|
+
var PLAINTEXT_TRANSPORT_SQL = "('http', 'ws')";
|
|
22162
|
+
var OVERRIDE_JOIN = `LEFT JOIN egress_decision_override oh ON oh.host = d.host
|
|
22163
|
+
LEFT JOIN egress_decision_override ol ON ol.destination_id = d.id AND ol.host IS NULL`;
|
|
22045
22164
|
var CALL_SITE_EMBED_CAP = 200;
|
|
22046
22165
|
function parseNetwork(networkJson) {
|
|
22047
22166
|
return safeJson(networkJson, null);
|
|
22048
22167
|
}
|
|
22168
|
+
function capHits(all, mode) {
|
|
22169
|
+
if (all.length <= MAX_EGRESS_CALL_SITES_PER_PROJECT) {
|
|
22170
|
+
return { hits: [...all], droppedFiles: [], truncated: false };
|
|
22171
|
+
}
|
|
22172
|
+
if (mode === "walk") {
|
|
22173
|
+
return {
|
|
22174
|
+
hits: all.slice(0, MAX_EGRESS_CALL_SITES_PER_PROJECT),
|
|
22175
|
+
droppedFiles: [],
|
|
22176
|
+
truncated: true
|
|
22177
|
+
};
|
|
22178
|
+
}
|
|
22179
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
22180
|
+
for (const hit of all) {
|
|
22181
|
+
const bucket = byFile.get(hit.site.file);
|
|
22182
|
+
if (bucket === void 0) byFile.set(hit.site.file, [hit]);
|
|
22183
|
+
else bucket.push(hit);
|
|
22184
|
+
}
|
|
22185
|
+
const hits = [];
|
|
22186
|
+
const droppedFiles = [];
|
|
22187
|
+
for (const [file2, bucket] of byFile) {
|
|
22188
|
+
if (hits.length + bucket.length > MAX_EGRESS_CALL_SITES_PER_PROJECT) droppedFiles.push(file2);
|
|
22189
|
+
else hits.push(...bucket);
|
|
22190
|
+
}
|
|
22191
|
+
return { hits, droppedFiles, truncated: true };
|
|
22192
|
+
}
|
|
22193
|
+
function withoutDroppedFiles(reconcile, droppedFiles) {
|
|
22194
|
+
if (reconcile.mode === "walk" || droppedFiles.length === 0) return reconcile;
|
|
22195
|
+
const dropped = new Set(droppedFiles);
|
|
22196
|
+
return {
|
|
22197
|
+
mode: "ledger",
|
|
22198
|
+
scannedFiles: reconcile.scannedFiles.filter((file2) => !dropped.has(file2)),
|
|
22199
|
+
deletedFiles: reconcile.deletedFiles.filter((file2) => !dropped.has(file2))
|
|
22200
|
+
};
|
|
22201
|
+
}
|
|
22049
22202
|
function toEndpointSummary(row) {
|
|
22050
22203
|
return {
|
|
22051
22204
|
id: row.id,
|
|
@@ -22136,13 +22289,15 @@ var SqliteSharesRepository = class {
|
|
|
22136
22289
|
const callSites = countScalar(this.db, "SELECT count(*) AS n FROM share_call_site");
|
|
22137
22290
|
const insecure = countScalar(
|
|
22138
22291
|
this.db,
|
|
22139
|
-
|
|
22292
|
+
`SELECT count(DISTINCT destination_id) AS n FROM share_endpoint
|
|
22293
|
+
WHERE transport IN ${PLAINTEXT_TRANSPORT_SQL}`
|
|
22140
22294
|
);
|
|
22141
22295
|
const needsReview = countScalar(
|
|
22142
22296
|
this.db,
|
|
22143
22297
|
`SELECT count(DISTINCT d.id) AS n
|
|
22144
22298
|
FROM share_destination d
|
|
22145
|
-
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22299
|
+
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22300
|
+
AND e.transport IN ${PLAINTEXT_TRANSPORT_SQL}
|
|
22146
22301
|
WHERE d.trust IN ('unverified', 'ip') OR e.id IS NOT NULL`
|
|
22147
22302
|
);
|
|
22148
22303
|
const kindCounts = countBy(
|
|
@@ -22152,6 +22307,7 @@ var SqliteSharesRepository = class {
|
|
|
22152
22307
|
const byKind = {
|
|
22153
22308
|
provider: kindCounts.get("provider") ?? 0,
|
|
22154
22309
|
internal: kindCounts.get("internal") ?? 0,
|
|
22310
|
+
external: kindCounts.get("external") ?? 0,
|
|
22155
22311
|
ip: kindCounts.get("ip") ?? 0
|
|
22156
22312
|
};
|
|
22157
22313
|
const trustCounts = countBy(
|
|
@@ -22227,23 +22383,316 @@ var SqliteSharesRepository = class {
|
|
|
22227
22383
|
// real edit from a no-such-destination.
|
|
22228
22384
|
/**
|
|
22229
22385
|
* Set (decision) or clear (null) the egress decision override for a destination.
|
|
22230
|
-
* `null` deletes the override
|
|
22386
|
+
* `null` deletes the override rows → reverts to the trust default.
|
|
22387
|
+
*
|
|
22388
|
+
* The written row carries both the destination id and its host, so the
|
|
22389
|
+
* decision re-attaches by host after the destination is pruned and
|
|
22390
|
+
* re-detected under a fresh id. Rows written before the host column existed
|
|
22391
|
+
* (host NULL, matched by destination id) are replaced rather than left to
|
|
22392
|
+
* shadow the new one. Runs IMMEDIATE: the host lookup is read-then-write and
|
|
22393
|
+
* would otherwise race a concurrent prune.
|
|
22231
22394
|
*/
|
|
22232
22395
|
setEgressDecision(destinationId, decision) {
|
|
22233
|
-
|
|
22234
|
-
|
|
22235
|
-
|
|
22236
|
-
|
|
22237
|
-
|
|
22396
|
+
let existed = false;
|
|
22397
|
+
withTransaction(
|
|
22398
|
+
this.db,
|
|
22399
|
+
() => {
|
|
22400
|
+
const dest = this.db.prepare("SELECT host FROM share_destination WHERE id = ?").get(destinationId);
|
|
22401
|
+
if (dest === void 0) return;
|
|
22402
|
+
existed = true;
|
|
22403
|
+
this.db.prepare(
|
|
22404
|
+
`DELETE FROM egress_decision_override
|
|
22405
|
+
WHERE host = :host OR (destination_id = :destinationId AND host IS NULL)`
|
|
22406
|
+
).run({ host: dest.host, destinationId });
|
|
22407
|
+
if (decision === null) return;
|
|
22408
|
+
this.db.prepare(
|
|
22409
|
+
`INSERT INTO egress_decision_override
|
|
22410
|
+
(id, destination_id, host, decision, created_at, updated_at)
|
|
22411
|
+
VALUES (:id, :destinationId, :host, :decision, :now, :now)`
|
|
22412
|
+
).run({
|
|
22413
|
+
id: randomUUID7(),
|
|
22414
|
+
destinationId,
|
|
22415
|
+
host: dest.host,
|
|
22416
|
+
decision,
|
|
22417
|
+
now: Date.now()
|
|
22418
|
+
});
|
|
22419
|
+
},
|
|
22420
|
+
"IMMEDIATE"
|
|
22421
|
+
);
|
|
22422
|
+
return existed;
|
|
22423
|
+
}
|
|
22424
|
+
/**
|
|
22425
|
+
* Record one project's statically-extracted egress: reconcile the previously
|
|
22426
|
+
* stored call sites against this scan, upsert destination → endpoint → call
|
|
22427
|
+
* site for every hit, confirm `last_seen` on everything the project still
|
|
22428
|
+
* references, and drop what no longer has evidence.
|
|
22429
|
+
*
|
|
22430
|
+
* Reconciliation keys on `projectKey` alone; `project` and `projectId` are
|
|
22431
|
+
* display payload and never scope a delete. The whole write is one
|
|
22432
|
+
* transaction: a failure leaves the project's previous inventory exactly as
|
|
22433
|
+
* it was, and THROWS rather than reporting a partial write — callers decide
|
|
22434
|
+
* their own fail-open behavior, and the scanner additionally withholds its
|
|
22435
|
+
* ledger commit so the next scan retries.
|
|
22436
|
+
*
|
|
22437
|
+
* Over-cap input is truncated at a FILE boundary, and the files that lost
|
|
22438
|
+
* their hits are both excluded from the reconcile delete and named in
|
|
22439
|
+
* `droppedFiles`. That pairing is what keeps truncation non-destructive on
|
|
22440
|
+
* the ledger path: a dropped file keeps whatever rows it already had, and its
|
|
22441
|
+
* caller withholds the ledger entry so the next scan reads it again.
|
|
22442
|
+
*/
|
|
22443
|
+
recordProjectEgress(input) {
|
|
22444
|
+
const { hits, droppedFiles, truncated } = capHits(input.hits, input.reconcile.mode);
|
|
22445
|
+
const reconcile = withoutDroppedFiles(input.reconcile, droppedFiles);
|
|
22446
|
+
const now = Date.now();
|
|
22447
|
+
let summary = {
|
|
22448
|
+
destinations: 0,
|
|
22449
|
+
endpoints: 0,
|
|
22450
|
+
callSites: 0,
|
|
22451
|
+
truncated,
|
|
22452
|
+
droppedFiles
|
|
22453
|
+
};
|
|
22454
|
+
withTransaction(
|
|
22455
|
+
this.db,
|
|
22456
|
+
() => {
|
|
22457
|
+
const projectId = input.projectId ?? this.knownProjectId(input.projectKey);
|
|
22458
|
+
this.reconcileCallSites(input.projectKey, reconcile);
|
|
22459
|
+
this.upsertHits(input, hits, projectId, now);
|
|
22460
|
+
this.confirmLastSeen(input.projectKey, now);
|
|
22461
|
+
this.pruneOrphans();
|
|
22462
|
+
summary = { ...this.projectTotals(input.projectKey), truncated, droppedFiles };
|
|
22463
|
+
},
|
|
22464
|
+
"IMMEDIATE"
|
|
22465
|
+
);
|
|
22466
|
+
return summary;
|
|
22467
|
+
}
|
|
22468
|
+
// ─── Egress write internals ──────────────────────────────────────────────────
|
|
22469
|
+
/**
|
|
22470
|
+
* Clear the stored call sites this scan is responsible for re-creating.
|
|
22471
|
+
*
|
|
22472
|
+
* Each pipeline may only delete rows its own walker could have produced. The
|
|
22473
|
+
* fs walk behind 'walk' mode never descends into dot-directories, so its
|
|
22474
|
+
* delete excludes dot-path files — those rows are the plugin scanner's to
|
|
22475
|
+
* reconcile, and deleting them here would make the two pipelines erase each
|
|
22476
|
+
* other's rows on every alternating scan. 'ledger' mode names its files
|
|
22477
|
+
* outright and never mass-deletes, so rows the fs walk contributed for files
|
|
22478
|
+
* the scanner skips (vendored, oversize) survive it.
|
|
22479
|
+
*/
|
|
22480
|
+
reconcileCallSites(projectKey, reconcile) {
|
|
22481
|
+
if (reconcile.mode === "walk") {
|
|
22482
|
+
const prefix = reconcile.walkedPrefix.replace(/\/+$/, "");
|
|
22483
|
+
this.db.prepare(
|
|
22484
|
+
`DELETE FROM share_call_site
|
|
22485
|
+
WHERE project_key = :key
|
|
22486
|
+
AND (:prefix = '' OR file = :prefix OR file LIKE :subtree ESCAPE '\\')
|
|
22487
|
+
AND file NOT LIKE '.%'
|
|
22488
|
+
AND file NOT LIKE '%/.%'`
|
|
22489
|
+
).run({ key: projectKey, prefix, subtree: `${escapeLikePattern(prefix)}/%` });
|
|
22490
|
+
return;
|
|
22491
|
+
}
|
|
22492
|
+
const files = [.../* @__PURE__ */ new Set([...reconcile.scannedFiles, ...reconcile.deletedFiles])];
|
|
22493
|
+
for (let i = 0; i < files.length; i += IN_CHUNK) {
|
|
22494
|
+
const chunk = files.slice(i, i + IN_CHUNK);
|
|
22495
|
+
this.db.prepare(
|
|
22496
|
+
`DELETE FROM share_call_site
|
|
22497
|
+
WHERE project_key = ? AND file IN (${placeholders(chunk.length)})`
|
|
22498
|
+
).run(projectKey, ...chunk);
|
|
22499
|
+
}
|
|
22500
|
+
}
|
|
22501
|
+
/**
|
|
22502
|
+
* Upsert every hit as destination → endpoint → call site. Destinations key on
|
|
22503
|
+
* `host` and endpoints on `(destination_id, method, url)`, both shared across
|
|
22504
|
+
* projects; only the call site carries `project_key`. A destination's `note`
|
|
22505
|
+
* is user-owned and never overwritten. The id caches keep one upsert per
|
|
22506
|
+
* distinct host and endpoint, so the first hit for a host supplies its
|
|
22507
|
+
* classification for this batch.
|
|
22508
|
+
*/
|
|
22509
|
+
upsertHits(input, hits, projectId, now) {
|
|
22510
|
+
if (hits.length === 0) return;
|
|
22511
|
+
const destStmt = this.db.prepare(
|
|
22512
|
+
`INSERT INTO share_destination
|
|
22513
|
+
(id, kind, name, host, category, trust, network_json, last_seen, provenance,
|
|
22514
|
+
created_at, updated_at)
|
|
22515
|
+
VALUES (:id, :kind, :name, :host, :category, :trust, :networkJson, :now, 'scan', :now, :now)
|
|
22516
|
+
ON CONFLICT (host) DO UPDATE SET
|
|
22517
|
+
kind = excluded.kind,
|
|
22518
|
+
name = excluded.name,
|
|
22519
|
+
category = excluded.category,
|
|
22520
|
+
trust = excluded.trust,
|
|
22521
|
+
network_json = excluded.network_json,
|
|
22522
|
+
last_seen = excluded.last_seen,
|
|
22523
|
+
updated_at = excluded.updated_at`
|
|
22524
|
+
);
|
|
22525
|
+
const destIdStmt = this.db.prepare("SELECT id FROM share_destination WHERE host = ?");
|
|
22526
|
+
const endpointStmt = this.db.prepare(
|
|
22527
|
+
`INSERT INTO share_endpoint
|
|
22528
|
+
(id, destination_id, method, transport, url, template, data_class, last_seen,
|
|
22529
|
+
created_at, updated_at)
|
|
22530
|
+
VALUES (:id, :destinationId, :method, :transport, :url, :template, :dataClass, :now,
|
|
22531
|
+
:now, :now)
|
|
22532
|
+
ON CONFLICT (destination_id, method, url) DO UPDATE SET
|
|
22533
|
+
transport = excluded.transport,
|
|
22534
|
+
template = excluded.template,
|
|
22535
|
+
data_class = excluded.data_class,
|
|
22536
|
+
last_seen = excluded.last_seen,
|
|
22537
|
+
updated_at = excluded.updated_at`
|
|
22538
|
+
);
|
|
22539
|
+
const endpointIdStmt = this.db.prepare(
|
|
22540
|
+
"SELECT id FROM share_endpoint WHERE destination_id = ? AND method = ? AND url = ?"
|
|
22541
|
+
);
|
|
22542
|
+
const siteStmt = this.db.prepare(
|
|
22543
|
+
`INSERT INTO share_call_site
|
|
22544
|
+
(id, endpoint_id, project, project_key, file, line, snippet, dynamic, vendored,
|
|
22545
|
+
project_id, created_at, updated_at)
|
|
22546
|
+
VALUES (:id, :endpointId, :project, :projectKey, :file, :line, :snippet, :dynamic,
|
|
22547
|
+
:vendored, :projectId, :now, :now)
|
|
22548
|
+
ON CONFLICT (endpoint_id, project_key, file, line) DO UPDATE SET
|
|
22549
|
+
snippet = excluded.snippet,
|
|
22550
|
+
dynamic = excluded.dynamic,
|
|
22551
|
+
vendored = excluded.vendored,
|
|
22552
|
+
project = excluded.project,
|
|
22553
|
+
project_id = COALESCE(excluded.project_id, share_call_site.project_id),
|
|
22554
|
+
updated_at = excluded.updated_at`
|
|
22555
|
+
);
|
|
22556
|
+
const destIds = /* @__PURE__ */ new Map();
|
|
22557
|
+
const endpointIds = /* @__PURE__ */ new Map();
|
|
22558
|
+
for (const hit of hits) {
|
|
22559
|
+
let destinationId = destIds.get(hit.host);
|
|
22560
|
+
if (destinationId === void 0) {
|
|
22561
|
+
destStmt.run({
|
|
22562
|
+
id: randomUUID7(),
|
|
22563
|
+
kind: hit.kind,
|
|
22564
|
+
name: hit.name,
|
|
22565
|
+
host: hit.host,
|
|
22566
|
+
category: hit.category,
|
|
22567
|
+
trust: hit.trust,
|
|
22568
|
+
networkJson: hit.network === null ? null : JSON.stringify(hit.network),
|
|
22569
|
+
now
|
|
22570
|
+
});
|
|
22571
|
+
destinationId = getRow(destIdStmt, [hit.host])?.id ?? "";
|
|
22572
|
+
destIds.set(hit.host, destinationId);
|
|
22573
|
+
}
|
|
22574
|
+
const endpointKey = `${destinationId}\0${hit.method}\0${hit.url}`;
|
|
22575
|
+
let endpointId = endpointIds.get(endpointKey);
|
|
22576
|
+
if (endpointId === void 0) {
|
|
22577
|
+
endpointStmt.run({
|
|
22578
|
+
id: randomUUID7(),
|
|
22579
|
+
destinationId,
|
|
22580
|
+
method: hit.method,
|
|
22581
|
+
transport: hit.transport,
|
|
22582
|
+
url: hit.url,
|
|
22583
|
+
template: boolToInt(hit.template),
|
|
22584
|
+
dataClass: hit.dataClass,
|
|
22585
|
+
now
|
|
22586
|
+
});
|
|
22587
|
+
endpointId = getRow(endpointIdStmt, [destinationId, hit.method, hit.url])?.id ?? "";
|
|
22588
|
+
endpointIds.set(endpointKey, endpointId);
|
|
22589
|
+
}
|
|
22590
|
+
siteStmt.run({
|
|
22591
|
+
id: randomUUID7(),
|
|
22592
|
+
endpointId,
|
|
22593
|
+
project: input.project,
|
|
22594
|
+
projectKey: input.projectKey,
|
|
22595
|
+
file: hit.site.file,
|
|
22596
|
+
line: hit.site.line,
|
|
22597
|
+
snippet: hit.site.snippet,
|
|
22598
|
+
dynamic: boolToInt(hit.site.dynamic),
|
|
22599
|
+
vendored: boolToInt(hit.site.vendored),
|
|
22600
|
+
projectId,
|
|
22601
|
+
now
|
|
22602
|
+
});
|
|
22238
22603
|
}
|
|
22604
|
+
}
|
|
22605
|
+
/**
|
|
22606
|
+
* The source-project id this project's stored call sites already carry, if
|
|
22607
|
+
* any. Only the pipeline that resolves a source project supplies one; the
|
|
22608
|
+
* other passes null and inherits this, so the link stops flapping between a
|
|
22609
|
+
* real id and NULL depending on which pipeline ran last. The value is a
|
|
22610
|
+
* per-project attribute stored redundantly on each row, so any row's is
|
|
22611
|
+
* representative.
|
|
22612
|
+
*/
|
|
22613
|
+
knownProjectId(projectKey) {
|
|
22614
|
+
return getRow(
|
|
22615
|
+
this.db.prepare(
|
|
22616
|
+
`SELECT project_id AS projectId FROM share_call_site
|
|
22617
|
+
WHERE project_key = ? AND project_id IS NOT NULL LIMIT 1`
|
|
22618
|
+
),
|
|
22619
|
+
[projectKey]
|
|
22620
|
+
)?.projectId ?? null;
|
|
22621
|
+
}
|
|
22622
|
+
/**
|
|
22623
|
+
* Stamp `last_seen` on every endpoint and destination this project still
|
|
22624
|
+
* references — including rows the scan preserved rather than re-wrote, so a
|
|
22625
|
+
* ledger-skipped file's references don't decay into "stale" on the page.
|
|
22626
|
+
*/
|
|
22627
|
+
confirmLastSeen(projectKey, now) {
|
|
22239
22628
|
this.db.prepare(
|
|
22240
|
-
`
|
|
22241
|
-
|
|
22242
|
-
|
|
22243
|
-
|
|
22244
|
-
|
|
22245
|
-
|
|
22246
|
-
|
|
22629
|
+
`UPDATE share_endpoint SET last_seen = :now, updated_at = :now
|
|
22630
|
+
WHERE id IN (SELECT DISTINCT endpoint_id FROM share_call_site WHERE project_key = :key)`
|
|
22631
|
+
).run({ now, key: projectKey });
|
|
22632
|
+
this.db.prepare(
|
|
22633
|
+
`UPDATE share_destination SET last_seen = :now, updated_at = :now
|
|
22634
|
+
WHERE id IN (SELECT DISTINCT e.destination_id
|
|
22635
|
+
FROM share_endpoint e
|
|
22636
|
+
JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22637
|
+
WHERE c.project_key = :key)`
|
|
22638
|
+
).run({ now, key: projectKey });
|
|
22639
|
+
}
|
|
22640
|
+
/**
|
|
22641
|
+
* Drop rows left without evidence: endpoints with no call site, then
|
|
22642
|
+
* destinations with no endpoint. Call sites are the only evidence either one
|
|
22643
|
+
* has, so a row that lost its last one belongs to no project any more.
|
|
22644
|
+
*
|
|
22645
|
+
* Overrides are deleted between the two steps, and only the ones written
|
|
22646
|
+
* before the host column existed. Those match a destination by id alone;
|
|
22647
|
+
* because the id link is released on delete rather than cascading, leaving
|
|
22648
|
+
* them would accumulate rows that match neither join arm and that nothing can
|
|
22649
|
+
* reach again. Host-bearing rows deliberately survive — the host is what
|
|
22650
|
+
* re-attaches a user's decision when the destination comes back.
|
|
22651
|
+
*/
|
|
22652
|
+
pruneOrphans() {
|
|
22653
|
+
this.db.exec(
|
|
22654
|
+
`DELETE FROM share_endpoint
|
|
22655
|
+
WHERE NOT EXISTS (SELECT 1 FROM share_call_site c WHERE c.endpoint_id = share_endpoint.id)`
|
|
22656
|
+
);
|
|
22657
|
+
this.db.exec(
|
|
22658
|
+
`DELETE FROM egress_decision_override
|
|
22659
|
+
WHERE host IS NULL
|
|
22660
|
+
AND destination_id IN (
|
|
22661
|
+
SELECT d.id FROM share_destination d
|
|
22662
|
+
WHERE NOT EXISTS (SELECT 1 FROM share_endpoint e WHERE e.destination_id = d.id))`
|
|
22663
|
+
);
|
|
22664
|
+
this.db.exec(
|
|
22665
|
+
`DELETE FROM share_destination
|
|
22666
|
+
WHERE NOT EXISTS (
|
|
22667
|
+
SELECT 1 FROM share_endpoint e WHERE e.destination_id = share_destination.id)`
|
|
22668
|
+
);
|
|
22669
|
+
}
|
|
22670
|
+
/**
|
|
22671
|
+
* Live totals for one project. Destinations and endpoints are shared across
|
|
22672
|
+
* projects and carry no project column, so both are counted through the call
|
|
22673
|
+
* sites that reference them.
|
|
22674
|
+
*/
|
|
22675
|
+
projectTotals(projectKey) {
|
|
22676
|
+
return {
|
|
22677
|
+
destinations: countScalar(
|
|
22678
|
+
this.db,
|
|
22679
|
+
`SELECT count(DISTINCT e.destination_id) AS n
|
|
22680
|
+
FROM share_endpoint e
|
|
22681
|
+
JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22682
|
+
WHERE c.project_key = ?`,
|
|
22683
|
+
[projectKey]
|
|
22684
|
+
),
|
|
22685
|
+
endpoints: countScalar(
|
|
22686
|
+
this.db,
|
|
22687
|
+
"SELECT count(DISTINCT endpoint_id) AS n FROM share_call_site WHERE project_key = ?",
|
|
22688
|
+
[projectKey]
|
|
22689
|
+
),
|
|
22690
|
+
callSites: countScalar(
|
|
22691
|
+
this.db,
|
|
22692
|
+
"SELECT count(*) AS n FROM share_call_site WHERE project_key = ?",
|
|
22693
|
+
[projectKey]
|
|
22694
|
+
)
|
|
22695
|
+
};
|
|
22247
22696
|
}
|
|
22248
22697
|
// ─── Raw fetchers ────────────────────────────────────────────────────────────
|
|
22249
22698
|
mapDestRow(r) {
|
|
@@ -22263,7 +22712,8 @@ var SqliteSharesRepository = class {
|
|
|
22263
22712
|
fetchDestinations(q, kinds, reviewOnly = false) {
|
|
22264
22713
|
const cols = `d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
|
|
22265
22714
|
d.network_json AS networkJson, d.last_seen AS lastSeenMs,
|
|
22266
|
-
d.created_at AS createdAt,
|
|
22715
|
+
d.created_at AS createdAt,
|
|
22716
|
+
COALESCE(oh.decision, ol.decision) AS overrideDecision`;
|
|
22267
22717
|
const conditions = [];
|
|
22268
22718
|
const params = [];
|
|
22269
22719
|
if (kinds && kinds.length > 0) {
|
|
@@ -22274,7 +22724,8 @@ var SqliteSharesRepository = class {
|
|
|
22274
22724
|
conditions.push(
|
|
22275
22725
|
`(d.trust IN ('unverified', 'ip')
|
|
22276
22726
|
OR EXISTS (SELECT 1 FROM share_endpoint re
|
|
22277
|
-
WHERE re.destination_id = d.id
|
|
22727
|
+
WHERE re.destination_id = d.id
|
|
22728
|
+
AND re.transport IN ${PLAINTEXT_TRANSPORT_SQL}))`
|
|
22278
22729
|
);
|
|
22279
22730
|
}
|
|
22280
22731
|
let sql;
|
|
@@ -22287,7 +22738,7 @@ var SqliteSharesRepository = class {
|
|
|
22287
22738
|
params.push(pattern, pattern, pattern, pattern, pattern);
|
|
22288
22739
|
sql = `SELECT DISTINCT ${cols}
|
|
22289
22740
|
FROM share_destination d
|
|
22290
|
-
|
|
22741
|
+
${OVERRIDE_JOIN}
|
|
22291
22742
|
LEFT JOIN share_endpoint e ON e.destination_id = d.id
|
|
22292
22743
|
LEFT JOIN share_call_site c ON c.endpoint_id = e.id
|
|
22293
22744
|
${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
@@ -22295,7 +22746,7 @@ var SqliteSharesRepository = class {
|
|
|
22295
22746
|
} else {
|
|
22296
22747
|
sql = `SELECT ${cols}
|
|
22297
22748
|
FROM share_destination d
|
|
22298
|
-
|
|
22749
|
+
${OVERRIDE_JOIN}
|
|
22299
22750
|
${conditions.length ? `WHERE ${conditions.join(" AND ")}` : ""}
|
|
22300
22751
|
ORDER BY d.created_at ASC, d.id ASC`;
|
|
22301
22752
|
}
|
|
@@ -22310,9 +22761,9 @@ var SqliteSharesRepository = class {
|
|
|
22310
22761
|
this.db.prepare(
|
|
22311
22762
|
`SELECT d.id, d.kind, d.name, d.host, d.category, d.trust, d.note,
|
|
22312
22763
|
d.network_json AS networkJson, d.last_seen AS lastSeenMs,
|
|
22313
|
-
|
|
22764
|
+
COALESCE(oh.decision, ol.decision) AS overrideDecision
|
|
22314
22765
|
FROM share_destination d
|
|
22315
|
-
|
|
22766
|
+
${OVERRIDE_JOIN}
|
|
22316
22767
|
WHERE d.id = ?`
|
|
22317
22768
|
),
|
|
22318
22769
|
[destinationId]
|
|
@@ -22541,6 +22992,7 @@ function openLocalDatabase(dir) {
|
|
|
22541
22992
|
const scanLedger = new SqliteScanLedgerRepository(db);
|
|
22542
22993
|
const exceptions = new SqliteExceptionsRepository(db);
|
|
22543
22994
|
const resolutions = new SqliteResolutionsRepository(db);
|
|
22995
|
+
const ruleProbeCache = new SqliteRuleProbeCacheRepository(db);
|
|
22544
22996
|
const security = new SqliteSecurityRepository(db);
|
|
22545
22997
|
const detections = new SqliteDetectionsRepository(db);
|
|
22546
22998
|
const shares = new SqliteSharesRepository(db);
|
|
@@ -22678,6 +23130,7 @@ function openLocalDatabase(dir) {
|
|
|
22678
23130
|
scanLedger,
|
|
22679
23131
|
exceptions,
|
|
22680
23132
|
resolutions,
|
|
23133
|
+
ruleProbeCache,
|
|
22681
23134
|
security,
|
|
22682
23135
|
detections,
|
|
22683
23136
|
shares,
|
|
@@ -22847,13 +23300,581 @@ import { readdirSync, readFileSync as readFileSync4, realpathSync, statSync as s
|
|
|
22847
23300
|
import { homedir as homedir2 } from "os";
|
|
22848
23301
|
import { basename as basename2, join as join7 } from "path";
|
|
22849
23302
|
|
|
23303
|
+
// ../../packages/detections/src/egress/registry.ts
|
|
23304
|
+
var EXTRACTOR_VERSION = "1";
|
|
23305
|
+
var PROVIDER_REGISTRY = [
|
|
23306
|
+
{
|
|
23307
|
+
id: "stripe",
|
|
23308
|
+
name: "Stripe",
|
|
23309
|
+
category: "Payments",
|
|
23310
|
+
hostSuffixes: ["stripe.com"],
|
|
23311
|
+
apiBase: "https://api.stripe.com",
|
|
23312
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23313
|
+
sdks: {
|
|
23314
|
+
npm: ["stripe"],
|
|
23315
|
+
pypi: ["stripe"],
|
|
23316
|
+
go: ["github.com/stripe/stripe-go"],
|
|
23317
|
+
maven: ["com.stripe"],
|
|
23318
|
+
rubygems: ["stripe"],
|
|
23319
|
+
composer: ["stripe/stripe-php"],
|
|
23320
|
+
nuget: ["Stripe.net"]
|
|
23321
|
+
}
|
|
23322
|
+
},
|
|
23323
|
+
{
|
|
23324
|
+
id: "datadog",
|
|
23325
|
+
name: "Datadog",
|
|
23326
|
+
category: "Observability",
|
|
23327
|
+
hostSuffixes: ["datadoghq.com", "datadoghq.eu"],
|
|
23328
|
+
apiBase: "https://api.datadoghq.com",
|
|
23329
|
+
defaultDataClasses: ["telemetry", "logs", "metrics"],
|
|
23330
|
+
sdks: {
|
|
23331
|
+
npm: ["dd-trace", "@datadog/browser-logs"],
|
|
23332
|
+
pypi: ["datadog", "ddtrace"],
|
|
23333
|
+
go: ["github.com/DataDog/dd-trace-go"],
|
|
23334
|
+
maven: ["com.datadoghq"],
|
|
23335
|
+
rubygems: ["ddtrace", "dogapi"],
|
|
23336
|
+
nuget: ["Datadog.Trace"]
|
|
23337
|
+
}
|
|
23338
|
+
},
|
|
23339
|
+
{
|
|
23340
|
+
id: "newrelic",
|
|
23341
|
+
name: "New Relic",
|
|
23342
|
+
category: "Observability",
|
|
23343
|
+
hostSuffixes: ["newrelic.com", "nr-data.net"],
|
|
23344
|
+
apiBase: "https://api.newrelic.com",
|
|
23345
|
+
defaultDataClasses: ["telemetry", "logs", "metrics"],
|
|
23346
|
+
sdks: {
|
|
23347
|
+
npm: ["newrelic"],
|
|
23348
|
+
pypi: ["newrelic"],
|
|
23349
|
+
go: ["github.com/newrelic/go-agent"],
|
|
23350
|
+
maven: ["com.newrelic.agent.java"],
|
|
23351
|
+
rubygems: ["newrelic_rpm"],
|
|
23352
|
+
nuget: ["NewRelic.Agent"]
|
|
23353
|
+
}
|
|
23354
|
+
},
|
|
23355
|
+
{
|
|
23356
|
+
id: "sentry",
|
|
23357
|
+
name: "Sentry",
|
|
23358
|
+
category: "Error tracking",
|
|
23359
|
+
hostSuffixes: ["sentry.io"],
|
|
23360
|
+
apiBase: "https://sentry.io",
|
|
23361
|
+
defaultDataClasses: ["source", "telemetry"],
|
|
23362
|
+
sdks: {
|
|
23363
|
+
npm: ["@sentry/node", "@sentry/react", "@sentry/nextjs"],
|
|
23364
|
+
pypi: ["sentry-sdk"],
|
|
23365
|
+
go: ["github.com/getsentry/sentry-go"],
|
|
23366
|
+
maven: ["io.sentry"],
|
|
23367
|
+
rubygems: ["sentry-ruby"],
|
|
23368
|
+
cargo: ["sentry"],
|
|
23369
|
+
composer: ["sentry/sentry"],
|
|
23370
|
+
nuget: ["Sentry"]
|
|
23371
|
+
}
|
|
23372
|
+
},
|
|
23373
|
+
{
|
|
23374
|
+
id: "openai",
|
|
23375
|
+
name: "OpenAI",
|
|
23376
|
+
category: "LLM provider",
|
|
23377
|
+
hostSuffixes: ["openai.com"],
|
|
23378
|
+
apiBase: "https://api.openai.com",
|
|
23379
|
+
defaultDataClasses: ["pii", "source"],
|
|
23380
|
+
sdks: {
|
|
23381
|
+
npm: ["openai"],
|
|
23382
|
+
pypi: ["openai"],
|
|
23383
|
+
go: ["github.com/sashabaranov/go-openai"],
|
|
23384
|
+
maven: ["com.openai"],
|
|
23385
|
+
rubygems: ["ruby-openai"],
|
|
23386
|
+
cargo: ["async-openai"],
|
|
23387
|
+
composer: ["openai-php/client"],
|
|
23388
|
+
nuget: ["OpenAI"]
|
|
23389
|
+
}
|
|
23390
|
+
},
|
|
23391
|
+
{
|
|
23392
|
+
id: "anthropic",
|
|
23393
|
+
name: "Anthropic",
|
|
23394
|
+
category: "LLM provider",
|
|
23395
|
+
hostSuffixes: ["anthropic.com"],
|
|
23396
|
+
apiBase: "https://api.anthropic.com",
|
|
23397
|
+
defaultDataClasses: ["pii", "source"],
|
|
23398
|
+
sdks: {
|
|
23399
|
+
npm: ["@anthropic-ai/sdk"],
|
|
23400
|
+
pypi: ["anthropic"],
|
|
23401
|
+
go: ["github.com/anthropics/anthropic-sdk-go"],
|
|
23402
|
+
nuget: ["Anthropic.SDK"]
|
|
23403
|
+
}
|
|
23404
|
+
},
|
|
23405
|
+
{
|
|
23406
|
+
id: "aws",
|
|
23407
|
+
name: "Amazon Web Services",
|
|
23408
|
+
category: "Cloud platform",
|
|
23409
|
+
hostSuffixes: ["amazonaws.com"],
|
|
23410
|
+
apiBase: "https://s3.amazonaws.com",
|
|
23411
|
+
defaultDataClasses: ["secrets", "customer"],
|
|
23412
|
+
sdks: {
|
|
23413
|
+
npm: ["@aws-sdk/client-s3", "aws-sdk"],
|
|
23414
|
+
pypi: ["boto3"],
|
|
23415
|
+
go: ["github.com/aws/aws-sdk-go", "github.com/aws/aws-sdk-go-v2"],
|
|
23416
|
+
maven: ["com.amazonaws", "software.amazon.awssdk"],
|
|
23417
|
+
rubygems: ["aws-sdk-s3"],
|
|
23418
|
+
cargo: ["aws-sdk-s3"],
|
|
23419
|
+
nuget: ["AWSSDK.S3"]
|
|
23420
|
+
}
|
|
23421
|
+
},
|
|
23422
|
+
{
|
|
23423
|
+
id: "gcp",
|
|
23424
|
+
name: "Google Cloud",
|
|
23425
|
+
category: "Cloud platform",
|
|
23426
|
+
hostSuffixes: ["googleapis.com"],
|
|
23427
|
+
apiBase: "https://storage.googleapis.com",
|
|
23428
|
+
defaultDataClasses: ["customer", "logs"],
|
|
23429
|
+
sdks: {
|
|
23430
|
+
npm: ["@google-cloud/storage"],
|
|
23431
|
+
pypi: ["google-cloud-storage"],
|
|
23432
|
+
go: ["cloud.google.com/go"],
|
|
23433
|
+
maven: ["com.google.cloud"],
|
|
23434
|
+
rubygems: ["google-cloud-storage"],
|
|
23435
|
+
nuget: ["Google.Cloud.Storage.V1"]
|
|
23436
|
+
}
|
|
23437
|
+
},
|
|
23438
|
+
{
|
|
23439
|
+
id: "azure",
|
|
23440
|
+
name: "Microsoft Azure",
|
|
23441
|
+
category: "Cloud platform",
|
|
23442
|
+
hostSuffixes: ["azure.com", "windows.net"],
|
|
23443
|
+
apiBase: "https://management.azure.com",
|
|
23444
|
+
defaultDataClasses: ["customer", "logs"],
|
|
23445
|
+
sdks: {
|
|
23446
|
+
npm: ["@azure/storage-blob"],
|
|
23447
|
+
pypi: ["azure-storage-blob"],
|
|
23448
|
+
go: ["github.com/Azure/azure-sdk-for-go"],
|
|
23449
|
+
maven: ["com.azure"],
|
|
23450
|
+
rubygems: ["azure-storage-blob"],
|
|
23451
|
+
nuget: ["Azure.Storage.Blobs"]
|
|
23452
|
+
}
|
|
23453
|
+
},
|
|
23454
|
+
{
|
|
23455
|
+
id: "slack",
|
|
23456
|
+
name: "Slack",
|
|
23457
|
+
category: "Notifications",
|
|
23458
|
+
hostSuffixes: ["slack.com"],
|
|
23459
|
+
apiBase: "https://slack.com/api",
|
|
23460
|
+
defaultDataClasses: ["logs"],
|
|
23461
|
+
sdks: {
|
|
23462
|
+
npm: ["@slack/web-api"],
|
|
23463
|
+
pypi: ["slack-sdk"],
|
|
23464
|
+
go: ["github.com/slack-go/slack"],
|
|
23465
|
+
maven: ["com.slack.api"],
|
|
23466
|
+
rubygems: ["slack-ruby-client"],
|
|
23467
|
+
composer: ["slack-php/slack-api"],
|
|
23468
|
+
nuget: ["SlackNet"]
|
|
23469
|
+
}
|
|
23470
|
+
},
|
|
23471
|
+
{
|
|
23472
|
+
id: "segment",
|
|
23473
|
+
name: "Segment",
|
|
23474
|
+
category: "Analytics",
|
|
23475
|
+
hostSuffixes: ["segment.io", "segment.com"],
|
|
23476
|
+
apiBase: "https://api.segment.io",
|
|
23477
|
+
defaultDataClasses: ["customer"],
|
|
23478
|
+
sdks: {
|
|
23479
|
+
npm: ["@segment/analytics-node", "analytics-node"],
|
|
23480
|
+
pypi: ["segment-analytics-python"],
|
|
23481
|
+
go: ["github.com/segmentio/analytics-go"],
|
|
23482
|
+
maven: ["com.segment.analytics.java"],
|
|
23483
|
+
rubygems: ["analytics-ruby"],
|
|
23484
|
+
nuget: ["Analytics"]
|
|
23485
|
+
}
|
|
23486
|
+
},
|
|
23487
|
+
{
|
|
23488
|
+
id: "twilio",
|
|
23489
|
+
name: "Twilio",
|
|
23490
|
+
category: "Communications",
|
|
23491
|
+
hostSuffixes: ["twilio.com"],
|
|
23492
|
+
apiBase: "https://api.twilio.com",
|
|
23493
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23494
|
+
sdks: {
|
|
23495
|
+
npm: ["twilio"],
|
|
23496
|
+
pypi: ["twilio"],
|
|
23497
|
+
go: ["github.com/twilio/twilio-go"],
|
|
23498
|
+
maven: ["com.twilio.sdk"],
|
|
23499
|
+
rubygems: ["twilio-ruby"],
|
|
23500
|
+
composer: ["twilio/sdk"],
|
|
23501
|
+
nuget: ["Twilio"]
|
|
23502
|
+
}
|
|
23503
|
+
},
|
|
23504
|
+
{
|
|
23505
|
+
id: "sendgrid",
|
|
23506
|
+
name: "SendGrid",
|
|
23507
|
+
category: "Email",
|
|
23508
|
+
hostSuffixes: ["sendgrid.com"],
|
|
23509
|
+
apiBase: "https://api.sendgrid.com",
|
|
23510
|
+
defaultDataClasses: ["pii"],
|
|
23511
|
+
sdks: {
|
|
23512
|
+
npm: ["@sendgrid/mail"],
|
|
23513
|
+
pypi: ["sendgrid"],
|
|
23514
|
+
go: ["github.com/sendgrid/sendgrid-go"],
|
|
23515
|
+
maven: ["com.sendgrid"],
|
|
23516
|
+
rubygems: ["sendgrid-ruby"],
|
|
23517
|
+
composer: ["sendgrid/sendgrid"],
|
|
23518
|
+
nuget: ["SendGrid"]
|
|
23519
|
+
}
|
|
23520
|
+
},
|
|
23521
|
+
{
|
|
23522
|
+
id: "mailgun",
|
|
23523
|
+
name: "Mailgun",
|
|
23524
|
+
category: "Email",
|
|
23525
|
+
hostSuffixes: ["mailgun.net"],
|
|
23526
|
+
apiBase: "https://api.mailgun.net",
|
|
23527
|
+
defaultDataClasses: ["pii"],
|
|
23528
|
+
sdks: {
|
|
23529
|
+
npm: ["mailgun.js"],
|
|
23530
|
+
pypi: ["mailgun"],
|
|
23531
|
+
rubygems: ["mailgun-ruby"],
|
|
23532
|
+
composer: ["mailgun/mailgun-php"],
|
|
23533
|
+
nuget: ["Mailgun"]
|
|
23534
|
+
}
|
|
23535
|
+
},
|
|
23536
|
+
{
|
|
23537
|
+
id: "mixpanel",
|
|
23538
|
+
name: "Mixpanel",
|
|
23539
|
+
category: "Analytics",
|
|
23540
|
+
hostSuffixes: ["mixpanel.com"],
|
|
23541
|
+
apiBase: "https://api.mixpanel.com",
|
|
23542
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23543
|
+
sdks: {
|
|
23544
|
+
npm: ["mixpanel"],
|
|
23545
|
+
pypi: ["mixpanel"],
|
|
23546
|
+
rubygems: ["mixpanel-ruby"],
|
|
23547
|
+
nuget: ["Mixpanel"]
|
|
23548
|
+
}
|
|
23549
|
+
},
|
|
23550
|
+
{
|
|
23551
|
+
id: "amplitude",
|
|
23552
|
+
name: "Amplitude",
|
|
23553
|
+
category: "Analytics",
|
|
23554
|
+
hostSuffixes: ["amplitude.com"],
|
|
23555
|
+
apiBase: "https://api2.amplitude.com",
|
|
23556
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23557
|
+
sdks: {
|
|
23558
|
+
npm: ["@amplitude/analytics-node"],
|
|
23559
|
+
pypi: ["amplitude-analytics"],
|
|
23560
|
+
nuget: ["Amplitude"]
|
|
23561
|
+
}
|
|
23562
|
+
},
|
|
23563
|
+
{
|
|
23564
|
+
id: "posthog",
|
|
23565
|
+
name: "PostHog",
|
|
23566
|
+
category: "Analytics",
|
|
23567
|
+
hostSuffixes: ["posthog.com"],
|
|
23568
|
+
apiBase: "https://us.i.posthog.com",
|
|
23569
|
+
defaultDataClasses: ["customer", "telemetry"],
|
|
23570
|
+
sdks: {
|
|
23571
|
+
npm: ["posthog-node", "posthog-js"],
|
|
23572
|
+
pypi: ["posthog"],
|
|
23573
|
+
go: ["github.com/posthog/posthog-go"],
|
|
23574
|
+
rubygems: ["posthog-ruby"],
|
|
23575
|
+
composer: ["posthog/posthog-php"],
|
|
23576
|
+
nuget: ["PostHog"]
|
|
23577
|
+
}
|
|
23578
|
+
},
|
|
23579
|
+
{
|
|
23580
|
+
id: "honeycomb",
|
|
23581
|
+
name: "Honeycomb",
|
|
23582
|
+
category: "Observability",
|
|
23583
|
+
hostSuffixes: ["honeycomb.io"],
|
|
23584
|
+
apiBase: "https://api.honeycomb.io",
|
|
23585
|
+
defaultDataClasses: ["telemetry", "metrics"],
|
|
23586
|
+
sdks: {
|
|
23587
|
+
npm: ["libhoney"],
|
|
23588
|
+
pypi: ["libhoney"],
|
|
23589
|
+
go: ["github.com/honeycombio/libhoney-go"],
|
|
23590
|
+
rubygems: ["libhoney"]
|
|
23591
|
+
}
|
|
23592
|
+
},
|
|
23593
|
+
{
|
|
23594
|
+
id: "grafana",
|
|
23595
|
+
name: "Grafana Cloud",
|
|
23596
|
+
category: "Observability",
|
|
23597
|
+
hostSuffixes: ["grafana.net"],
|
|
23598
|
+
apiBase: "https://grafana.net",
|
|
23599
|
+
defaultDataClasses: ["logs", "metrics"],
|
|
23600
|
+
sdks: {
|
|
23601
|
+
npm: ["@grafana/faro-web-sdk"]
|
|
23602
|
+
}
|
|
23603
|
+
},
|
|
23604
|
+
{
|
|
23605
|
+
id: "splunk",
|
|
23606
|
+
name: "Splunk",
|
|
23607
|
+
category: "Observability",
|
|
23608
|
+
hostSuffixes: ["splunkcloud.com", "splunk.com"],
|
|
23609
|
+
apiBase: "https://http-inputs.splunkcloud.com",
|
|
23610
|
+
defaultDataClasses: ["logs"],
|
|
23611
|
+
sdks: {
|
|
23612
|
+
npm: ["splunk-logging"],
|
|
23613
|
+
pypi: ["splunk-sdk"],
|
|
23614
|
+
maven: ["com.splunk"],
|
|
23615
|
+
nuget: ["Splunk.Logging.Common"]
|
|
23616
|
+
}
|
|
23617
|
+
},
|
|
23618
|
+
{
|
|
23619
|
+
id: "pagerduty",
|
|
23620
|
+
name: "PagerDuty",
|
|
23621
|
+
category: "Incident response",
|
|
23622
|
+
hostSuffixes: ["pagerduty.com"],
|
|
23623
|
+
apiBase: "https://api.pagerduty.com",
|
|
23624
|
+
defaultDataClasses: ["logs"],
|
|
23625
|
+
sdks: {
|
|
23626
|
+
npm: ["@pagerduty/pdjs"],
|
|
23627
|
+
pypi: ["pdpyras"],
|
|
23628
|
+
go: ["github.com/PagerDuty/go-pagerduty"],
|
|
23629
|
+
rubygems: ["pagerduty"]
|
|
23630
|
+
}
|
|
23631
|
+
},
|
|
23632
|
+
{
|
|
23633
|
+
id: "github",
|
|
23634
|
+
name: "GitHub",
|
|
23635
|
+
category: "Developer platform",
|
|
23636
|
+
hostSuffixes: ["github.com", "githubusercontent.com"],
|
|
23637
|
+
apiBase: "https://api.github.com",
|
|
23638
|
+
defaultDataClasses: ["source"],
|
|
23639
|
+
sdks: {
|
|
23640
|
+
npm: ["@octokit/rest", "octokit"],
|
|
23641
|
+
pypi: ["pygithub"],
|
|
23642
|
+
go: ["github.com/google/go-github"],
|
|
23643
|
+
maven: ["org.kohsuke.github-api"],
|
|
23644
|
+
rubygems: ["octokit"],
|
|
23645
|
+
cargo: ["octocrab"],
|
|
23646
|
+
composer: ["knplabs/github-api"],
|
|
23647
|
+
nuget: ["Octokit"]
|
|
23648
|
+
}
|
|
23649
|
+
},
|
|
23650
|
+
{
|
|
23651
|
+
id: "gitlab",
|
|
23652
|
+
name: "GitLab",
|
|
23653
|
+
category: "Developer platform",
|
|
23654
|
+
hostSuffixes: ["gitlab.com"],
|
|
23655
|
+
apiBase: "https://gitlab.com/api",
|
|
23656
|
+
defaultDataClasses: ["source"],
|
|
23657
|
+
sdks: {
|
|
23658
|
+
npm: ["@gitbeaker/rest"],
|
|
23659
|
+
pypi: ["python-gitlab"],
|
|
23660
|
+
go: ["gitlab.com/gitlab-org/api/client-go"],
|
|
23661
|
+
rubygems: ["gitlab"],
|
|
23662
|
+
nuget: ["GitLabApiClient"]
|
|
23663
|
+
}
|
|
23664
|
+
},
|
|
23665
|
+
{
|
|
23666
|
+
id: "auth0",
|
|
23667
|
+
name: "Auth0",
|
|
23668
|
+
category: "Identity",
|
|
23669
|
+
hostSuffixes: ["auth0.com"],
|
|
23670
|
+
apiBase: "https://login.auth0.com",
|
|
23671
|
+
defaultDataClasses: ["pii"],
|
|
23672
|
+
sdks: {
|
|
23673
|
+
npm: ["auth0"],
|
|
23674
|
+
pypi: ["auth0-python"],
|
|
23675
|
+
go: ["github.com/auth0/go-auth0"],
|
|
23676
|
+
maven: ["com.auth0"],
|
|
23677
|
+
rubygems: ["auth0"],
|
|
23678
|
+
composer: ["auth0/auth0-php"],
|
|
23679
|
+
nuget: ["Auth0.ManagementApi"]
|
|
23680
|
+
}
|
|
23681
|
+
},
|
|
23682
|
+
{
|
|
23683
|
+
id: "okta",
|
|
23684
|
+
name: "Okta",
|
|
23685
|
+
category: "Identity",
|
|
23686
|
+
hostSuffixes: ["okta.com", "oktapreview.com"],
|
|
23687
|
+
apiBase: "https://login.okta.com",
|
|
23688
|
+
defaultDataClasses: ["pii"],
|
|
23689
|
+
sdks: {
|
|
23690
|
+
npm: ["@okta/okta-sdk-nodejs"],
|
|
23691
|
+
pypi: ["okta"],
|
|
23692
|
+
go: ["github.com/okta/okta-sdk-golang"],
|
|
23693
|
+
maven: ["com.okta.sdk"],
|
|
23694
|
+
nuget: ["Okta.Sdk"]
|
|
23695
|
+
}
|
|
23696
|
+
},
|
|
23697
|
+
{
|
|
23698
|
+
id: "clerk",
|
|
23699
|
+
name: "Clerk",
|
|
23700
|
+
category: "Identity",
|
|
23701
|
+
hostSuffixes: ["clerk.com", "clerk.dev"],
|
|
23702
|
+
apiBase: "https://api.clerk.com",
|
|
23703
|
+
defaultDataClasses: ["pii"],
|
|
23704
|
+
sdks: {
|
|
23705
|
+
npm: ["@clerk/backend", "@clerk/nextjs"],
|
|
23706
|
+
pypi: ["clerk-backend-api"],
|
|
23707
|
+
go: ["github.com/clerk/clerk-sdk-go"]
|
|
23708
|
+
}
|
|
23709
|
+
},
|
|
23710
|
+
{
|
|
23711
|
+
id: "supabase",
|
|
23712
|
+
name: "Supabase",
|
|
23713
|
+
category: "Backend platform",
|
|
23714
|
+
hostSuffixes: ["supabase.co", "supabase.com"],
|
|
23715
|
+
apiBase: "https://api.supabase.com",
|
|
23716
|
+
defaultDataClasses: ["pii", "customer"],
|
|
23717
|
+
sdks: {
|
|
23718
|
+
npm: ["@supabase/supabase-js"],
|
|
23719
|
+
pypi: ["supabase"],
|
|
23720
|
+
cargo: ["postgrest"]
|
|
23721
|
+
}
|
|
23722
|
+
},
|
|
23723
|
+
{
|
|
23724
|
+
id: "firebase",
|
|
23725
|
+
name: "Firebase",
|
|
23726
|
+
category: "Backend platform",
|
|
23727
|
+
hostSuffixes: ["firebaseio.com", "firebase.google.com"],
|
|
23728
|
+
apiBase: "https://firebaseio.com",
|
|
23729
|
+
defaultDataClasses: ["customer"],
|
|
23730
|
+
sdks: {
|
|
23731
|
+
npm: ["firebase", "firebase-admin"],
|
|
23732
|
+
pypi: ["firebase-admin"],
|
|
23733
|
+
go: ["firebase.google.com/go"],
|
|
23734
|
+
maven: ["com.google.firebase"]
|
|
23735
|
+
}
|
|
23736
|
+
},
|
|
23737
|
+
{
|
|
23738
|
+
id: "mongodb-atlas",
|
|
23739
|
+
name: "MongoDB Atlas",
|
|
23740
|
+
category: "Database SaaS",
|
|
23741
|
+
hostSuffixes: ["mongodb.net", "mongodb.com"],
|
|
23742
|
+
apiBase: "https://cloud.mongodb.com",
|
|
23743
|
+
defaultDataClasses: ["customer"],
|
|
23744
|
+
sdks: {
|
|
23745
|
+
npm: ["mongodb"],
|
|
23746
|
+
pypi: ["pymongo"],
|
|
23747
|
+
go: ["go.mongodb.org/mongo-driver"],
|
|
23748
|
+
maven: ["org.mongodb"],
|
|
23749
|
+
rubygems: ["mongo"],
|
|
23750
|
+
cargo: ["mongodb"],
|
|
23751
|
+
nuget: ["MongoDB.Driver"]
|
|
23752
|
+
}
|
|
23753
|
+
},
|
|
23754
|
+
{
|
|
23755
|
+
id: "planetscale",
|
|
23756
|
+
name: "PlanetScale",
|
|
23757
|
+
category: "Database SaaS",
|
|
23758
|
+
hostSuffixes: ["psdb.cloud", "planetscale.com"],
|
|
23759
|
+
apiBase: "https://api.planetscale.com",
|
|
23760
|
+
defaultDataClasses: ["customer"],
|
|
23761
|
+
sdks: {
|
|
23762
|
+
npm: ["@planetscale/database"],
|
|
23763
|
+
go: ["github.com/planetscale/planetscale-go"]
|
|
23764
|
+
}
|
|
23765
|
+
},
|
|
23766
|
+
{
|
|
23767
|
+
id: "algolia",
|
|
23768
|
+
name: "Algolia",
|
|
23769
|
+
category: "Search SaaS",
|
|
23770
|
+
hostSuffixes: ["algolia.net", "algolianet.com"],
|
|
23771
|
+
apiBase: "https://algolia.net",
|
|
23772
|
+
defaultDataClasses: ["customer"],
|
|
23773
|
+
sdks: {
|
|
23774
|
+
npm: ["algoliasearch"],
|
|
23775
|
+
pypi: ["algoliasearch"],
|
|
23776
|
+
go: ["github.com/algolia/algoliasearch-client-go"],
|
|
23777
|
+
maven: ["com.algolia"],
|
|
23778
|
+
rubygems: ["algolia"],
|
|
23779
|
+
composer: ["algolia/algoliasearch-client-php"],
|
|
23780
|
+
nuget: ["Algolia.Search"]
|
|
23781
|
+
}
|
|
23782
|
+
},
|
|
23783
|
+
{
|
|
23784
|
+
id: "cloudflare",
|
|
23785
|
+
name: "Cloudflare",
|
|
23786
|
+
category: "CDN / edge",
|
|
23787
|
+
hostSuffixes: ["cloudflare.com", "workers.dev"],
|
|
23788
|
+
apiBase: "https://api.cloudflare.com",
|
|
23789
|
+
defaultDataClasses: ["logs"],
|
|
23790
|
+
sdks: {
|
|
23791
|
+
npm: ["cloudflare"],
|
|
23792
|
+
pypi: ["cloudflare"],
|
|
23793
|
+
go: ["github.com/cloudflare/cloudflare-go"],
|
|
23794
|
+
nuget: ["CloudFlare.Client"]
|
|
23795
|
+
}
|
|
23796
|
+
},
|
|
23797
|
+
{
|
|
23798
|
+
id: "huggingface",
|
|
23799
|
+
name: "Hugging Face",
|
|
23800
|
+
category: "LLM provider",
|
|
23801
|
+
hostSuffixes: ["huggingface.co"],
|
|
23802
|
+
apiBase: "https://api-inference.huggingface.co",
|
|
23803
|
+
defaultDataClasses: ["source"],
|
|
23804
|
+
sdks: {
|
|
23805
|
+
npm: ["@huggingface/inference"],
|
|
23806
|
+
pypi: ["huggingface-hub", "transformers"],
|
|
23807
|
+
rubygems: ["hugging-face"]
|
|
23808
|
+
}
|
|
23809
|
+
},
|
|
23810
|
+
{
|
|
23811
|
+
id: "cohere",
|
|
23812
|
+
name: "Cohere",
|
|
23813
|
+
category: "LLM provider",
|
|
23814
|
+
hostSuffixes: ["cohere.com", "cohere.ai"],
|
|
23815
|
+
apiBase: "https://api.cohere.com",
|
|
23816
|
+
defaultDataClasses: ["pii", "source"],
|
|
23817
|
+
sdks: {
|
|
23818
|
+
npm: ["cohere-ai"],
|
|
23819
|
+
pypi: ["cohere"],
|
|
23820
|
+
go: ["github.com/cohere-ai/cohere-go"]
|
|
23821
|
+
}
|
|
23822
|
+
},
|
|
23823
|
+
{
|
|
23824
|
+
id: "mistral",
|
|
23825
|
+
name: "Mistral AI",
|
|
23826
|
+
category: "LLM provider",
|
|
23827
|
+
hostSuffixes: ["mistral.ai"],
|
|
23828
|
+
apiBase: "https://api.mistral.ai",
|
|
23829
|
+
defaultDataClasses: ["pii", "source"],
|
|
23830
|
+
sdks: {
|
|
23831
|
+
npm: ["@mistralai/mistralai"],
|
|
23832
|
+
pypi: ["mistralai"],
|
|
23833
|
+
go: ["github.com/gage-technologies/mistral-go"]
|
|
23834
|
+
}
|
|
23835
|
+
}
|
|
23836
|
+
];
|
|
23837
|
+
var EGRESS_VERSION_MATERIAL = `${EXTRACTOR_VERSION}
|
|
23838
|
+
${JSON.stringify(PROVIDER_REGISTRY)}`;
|
|
23839
|
+
|
|
23840
|
+
// ../../packages/detections/src/egress/extract.ts
|
|
23841
|
+
var SECRET_KEY_NAMES = "api[_-]?key|apikey|private[_-]?key|access[_-]?key|access[_-]?token|token|secret|credentials?|password|passwd|pwd|authorization|sig|signature|sas|assertion";
|
|
23842
|
+
var AUTH_SCHEMES = "Bearer|Basic|Token|Digest|ApiKey|SSWS|AWS4-HMAC-SHA256";
|
|
23843
|
+
var SECRET_VALUE = new RegExp(
|
|
23844
|
+
`((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(?!(?:${AUTH_SCHEMES})[\\s'"\`])[^\\s'"\`&]+`,
|
|
23845
|
+
"gi"
|
|
23846
|
+
);
|
|
23847
|
+
var AUTH_SCHEME_VALUE = new RegExp(
|
|
23848
|
+
`((?:${SECRET_KEY_NAMES})['"\`]?\\s*[:=]\\s*['"\`]?)(${AUTH_SCHEMES})\\s+[^\\s'"\`]+`,
|
|
23849
|
+
"gi"
|
|
23850
|
+
);
|
|
23851
|
+
var WEBHOOK_SECRET_PATHS = [
|
|
23852
|
+
{ hosts: ["hooks.slack.com"], prefix: "/services/" },
|
|
23853
|
+
{
|
|
23854
|
+
hosts: ["discord.com", "discordapp.com", "ptb.discord.com", "canary.discord.com"],
|
|
23855
|
+
prefix: "/api/webhooks/"
|
|
23856
|
+
},
|
|
23857
|
+
{ hosts: ["hooks.zapier.com"], prefix: "/hooks/" },
|
|
23858
|
+
{ hosts: ["outlook.office.com", "outlook.office365.com"], prefix: "/webhook/" }
|
|
23859
|
+
];
|
|
23860
|
+
function escapeRegExp(literal2) {
|
|
23861
|
+
return literal2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
23862
|
+
}
|
|
23863
|
+
var WEBHOOK_URL = new RegExp(
|
|
23864
|
+
`(https?://(?:${WEBHOOK_SECRET_PATHS.flatMap(
|
|
23865
|
+
(entry) => entry.hosts.map((host) => `${escapeRegExp(host)}${escapeRegExp(entry.prefix)}`)
|
|
23866
|
+
).join("|")}))[^\\s'"\`<>()[\\]{},;]+`,
|
|
23867
|
+
"gi"
|
|
23868
|
+
);
|
|
23869
|
+
|
|
22850
23870
|
// ../../packages/detections/src/escape-regexp.ts
|
|
22851
|
-
function
|
|
23871
|
+
function escapeRegExp2(value) {
|
|
22852
23872
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
22853
23873
|
}
|
|
22854
23874
|
|
|
22855
23875
|
// ../../packages/detections/src/matchers/limits.ts
|
|
22856
23876
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
23877
|
+
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
22857
23878
|
|
|
22858
23879
|
// ../../packages/detections/src/matchers/keyword.ts
|
|
22859
23880
|
var KeywordMatcher2 = class {
|
|
@@ -22864,7 +23885,7 @@ var KeywordMatcher2 = class {
|
|
|
22864
23885
|
for (const kw of keywords) {
|
|
22865
23886
|
if (kw.length === 0) continue;
|
|
22866
23887
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
22867
|
-
const re = new RegExp(
|
|
23888
|
+
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
22868
23889
|
let m;
|
|
22869
23890
|
while ((m = re.exec(text)) !== null) {
|
|
22870
23891
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -22881,9 +23902,13 @@ var RegexMatcher2 = class {
|
|
|
22881
23902
|
if (rule.matcher.type !== "regex") return [];
|
|
22882
23903
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
22883
23904
|
const re = new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`);
|
|
23905
|
+
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
22884
23906
|
const spans = [];
|
|
22885
23907
|
let m;
|
|
22886
|
-
|
|
23908
|
+
const maxIterations = scanText2.length + 1;
|
|
23909
|
+
let iterations = 0;
|
|
23910
|
+
while ((m = re.exec(scanText2)) !== null) {
|
|
23911
|
+
if (++iterations > maxIterations) break;
|
|
22887
23912
|
const group = captureGroup != null ? m[captureGroup] : m[0];
|
|
22888
23913
|
if (m[0].length === 0) re.lastIndex++;
|
|
22889
23914
|
if (group && spans.length < MAX_MATCHES_PER_RULE) {
|
|
@@ -23005,6 +24030,31 @@ var CONFIG_POSTURE_RULES = [
|
|
|
23005
24030
|
}
|
|
23006
24031
|
];
|
|
23007
24032
|
|
|
24033
|
+
// ../../packages/detections/src/security/redos-probe.ts
|
|
24034
|
+
var EXPONENTIAL_UNITS = [
|
|
24035
|
+
"a",
|
|
24036
|
+
"0",
|
|
24037
|
+
" ",
|
|
24038
|
+
"x",
|
|
24039
|
+
"ab",
|
|
24040
|
+
"a.",
|
|
24041
|
+
"a-",
|
|
24042
|
+
"a_",
|
|
24043
|
+
"a@",
|
|
24044
|
+
"a/",
|
|
24045
|
+
"a:",
|
|
24046
|
+
"a=",
|
|
24047
|
+
"a;",
|
|
24048
|
+
"aA0",
|
|
24049
|
+
" "
|
|
24050
|
+
];
|
|
24051
|
+
var EXPONENTIAL_PROBES = EXPONENTIAL_UNITS.flatMap(
|
|
24052
|
+
(unit) => [23, 25].map((len) => unit.repeat(Math.ceil(len / unit.length)).slice(0, len) + "!")
|
|
24053
|
+
);
|
|
24054
|
+
var POLYNOMIAL_PROBES = ["abc-", "a.", "a ", "a=", "x", "0", "a@", "a/", "ab"].map(
|
|
24055
|
+
(unit) => unit.repeat(1e4).slice(0, 4e4) + "!"
|
|
24056
|
+
);
|
|
24057
|
+
|
|
23008
24058
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
23009
24059
|
import { existsSync as existsSync3, readFileSync as readFileSync3, statSync } from "fs";
|
|
23010
24060
|
import { basename, dirname, isAbsolute, join as join6, sep as sep2 } from "path";
|
|
@@ -23022,6 +24072,10 @@ import { arch, hostname as hostname3, platform, release } from "os";
|
|
|
23022
24072
|
import { mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
23023
24073
|
import { join as join8 } from "path";
|
|
23024
24074
|
|
|
24075
|
+
// ../../packages/plugin-sdk/src/paths.ts
|
|
24076
|
+
import { readdirSync as readdirSync2, realpathSync as realpathSync2 } from "fs";
|
|
24077
|
+
import { basename as basename3, dirname as dirname2, sep as sep3 } from "path";
|
|
24078
|
+
|
|
23025
24079
|
// ../../packages/plugin-sdk/src/posture.ts
|
|
23026
24080
|
function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
23027
24081
|
for (const category of Object.keys(posture)) {
|
|
@@ -23034,8 +24088,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
23034
24088
|
|
|
23035
24089
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
23036
24090
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
23037
|
-
import { existsSync as existsSync4, readdirSync as
|
|
23038
|
-
import { basename as
|
|
24091
|
+
import { existsSync as existsSync4, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
|
|
24092
|
+
import { basename as basename4, join as join9, relative, sep as sep4 } from "path";
|
|
23039
24093
|
|
|
23040
24094
|
// ../../packages/plugin-sdk/src/raw-egress.ts
|
|
23041
24095
|
var RawEgressError = class extends Error {
|
|
@@ -23130,12 +24184,12 @@ import { mkdirSync as mkdirSync4, statSync as statSync3, writeFileSync as writeF
|
|
|
23130
24184
|
import { join as join10 } from "path";
|
|
23131
24185
|
|
|
23132
24186
|
// src/command-registry.ts
|
|
23133
|
-
import { readdirSync as
|
|
24187
|
+
import { readdirSync as readdirSync4 } from "fs";
|
|
23134
24188
|
import { fileURLToPath } from "url";
|
|
23135
24189
|
var COMMAND_NAMESPACE = "aka";
|
|
23136
24190
|
var COMMANDS_DIR = fileURLToPath(new URL("../commands", import.meta.url));
|
|
23137
24191
|
function readRegisteredCommands() {
|
|
23138
|
-
return
|
|
24192
|
+
return readdirSync4(COMMANDS_DIR).filter((f) => f.endsWith(".md")).map((f) => `/${COMMAND_NAMESPACE}:${f.replace(/\.md$/, "")}`);
|
|
23139
24193
|
}
|
|
23140
24194
|
function selectRegisteredCommands(curated, registry2) {
|
|
23141
24195
|
const registered = new Set(registry2);
|
|
@@ -23216,8 +24270,8 @@ function table(headers, rows, opts = {}) {
|
|
|
23216
24270
|
const widths = headers.map(
|
|
23217
24271
|
(h, i) => Math.max(visibleLength(h), ...rows.map((r) => visibleLength(r[i] ?? "")))
|
|
23218
24272
|
);
|
|
23219
|
-
const
|
|
23220
|
-
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(
|
|
24273
|
+
const sep5 = " ".repeat(gap);
|
|
24274
|
+
const fmt = (cells) => cells.map((cell, i) => padEnd(cell, widths[i] ?? 0)).join(sep5);
|
|
23221
24275
|
const headerLine = fmt(headers.map((h) => h.toUpperCase()));
|
|
23222
24276
|
if (opts.rowSep === true) {
|
|
23223
24277
|
const fullWidth = widths.reduce((n, w) => n + w, 0) + gap * Math.max(0, widths.length - 1);
|
|
@@ -23229,7 +24283,7 @@ function table(headers, rows, opts = {}) {
|
|
|
23229
24283
|
});
|
|
23230
24284
|
return [headerLine, rule, ...body].join("\n");
|
|
23231
24285
|
}
|
|
23232
|
-
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(
|
|
24286
|
+
const ruleLine = widths.map((w) => "\u2500".repeat(w)).join(sep5);
|
|
23233
24287
|
return [headerLine, ruleLine, ...rows.map(fmt)].join("\n");
|
|
23234
24288
|
}
|
|
23235
24289
|
function fenced(body) {
|
|
@@ -23553,7 +24607,7 @@ function mergeRecommendations(verdicts) {
|
|
|
23553
24607
|
// src/triage/plan-file.ts
|
|
23554
24608
|
import { mkdtempSync, readFileSync as readFileSync7, rmdirSync, rmSync as rmSync2, writeFileSync as writeFileSync6 } from "fs";
|
|
23555
24609
|
import { tmpdir } from "os";
|
|
23556
|
-
import { basename as
|
|
24610
|
+
import { basename as basename5, dirname as dirname3, join as join11 } from "path";
|
|
23557
24611
|
var SuppressionEntrySchema = external_exports.object({
|
|
23558
24612
|
ruleId: external_exports.string(),
|
|
23559
24613
|
category: DetectionCategory,
|
|
@@ -23620,8 +24674,8 @@ function readPlanFile(path) {
|
|
|
23620
24674
|
}
|
|
23621
24675
|
function deletePlanFile(path) {
|
|
23622
24676
|
rmSync2(path, { force: true });
|
|
23623
|
-
const dir =
|
|
23624
|
-
if (!
|
|
24677
|
+
const dir = dirname3(path);
|
|
24678
|
+
if (!basename5(dir).startsWith("aka-plan-")) return;
|
|
23625
24679
|
try {
|
|
23626
24680
|
rmdirSync(dir);
|
|
23627
24681
|
} catch {
|
|
@@ -24095,7 +25149,7 @@ async function runConfirm(deps, planIO) {
|
|
|
24095
25149
|
import { execFileSync } from "child_process";
|
|
24096
25150
|
import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, rmSync as rmSync3 } from "fs";
|
|
24097
25151
|
import { tmpdir as tmpdir2 } from "os";
|
|
24098
|
-
import { dirname as
|
|
25152
|
+
import { dirname as dirname4, join as join12 } from "path";
|
|
24099
25153
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
24100
25154
|
|
|
24101
25155
|
// src/triage/parse-verdict.ts
|
|
@@ -24108,7 +25162,7 @@ function parseRecommendation(text) {
|
|
|
24108
25162
|
}
|
|
24109
25163
|
|
|
24110
25164
|
// src/triage/judge.ts
|
|
24111
|
-
var TRIAGE_DIR =
|
|
25165
|
+
var TRIAGE_DIR = dirname4(fileURLToPath2(import.meta.url));
|
|
24112
25166
|
var DEFAULT_RUBRIC_PATH = join12(TRIAGE_DIR, "..", "..", "eval", "prompt.md");
|
|
24113
25167
|
function parseVerdict(stdout) {
|
|
24114
25168
|
let envelope;
|
|
@@ -24199,7 +25253,7 @@ function resolveCreatedBy() {
|
|
|
24199
25253
|
}
|
|
24200
25254
|
}
|
|
24201
25255
|
function loadRubric() {
|
|
24202
|
-
const here =
|
|
25256
|
+
const here = dirname5(fileURLToPath3(import.meta.url));
|
|
24203
25257
|
const shipped = join13(here, "triage-rubric.md");
|
|
24204
25258
|
if (existsSync5(shipped)) return readFileSync9(shipped, "utf8");
|
|
24205
25259
|
return readFileSync9(join13(here, "..", "eval", "prompt.md"), "utf8");
|