@akasecurity/ai-tc-claude-code 0.9.10 → 0.9.11
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/package.json +5 -5
- package/scripts/apply-suppressions.js +622 -430
- package/scripts/backfill.js +732 -504
- package/scripts/filescan.js +726 -496
- package/scripts/firstrun.js +772 -516
- package/scripts/history-sync.js +611 -418
- package/scripts/intro.js +240 -90
- package/scripts/message-display.js +604 -412
- package/scripts/onboard.js +601 -408
- package/scripts/post-model-switch.js +258 -110
- package/scripts/post-tool-use.js +880 -493
- package/scripts/pre-model-switch.js +691 -469
- package/scripts/pre-tool-use.js +728 -498
- package/scripts/query.js +845 -526
- package/scripts/reconcile.js +716 -494
- package/scripts/remediate.js +728 -498
- package/scripts/scan-worker.js +153 -58
- package/scripts/session-start.js +783 -558
- package/scripts/start-light.js +238 -88
- package/scripts/statusline.js +697 -475
- package/scripts/stop.js +490 -121
- package/scripts/sync.js +722 -494
- package/scripts/user-prompt-submit.js +725 -495
package/scripts/statusline.js
CHANGED
|
@@ -491,9 +491,6 @@ var require_ignore = __commonJS({
|
|
|
491
491
|
}
|
|
492
492
|
});
|
|
493
493
|
|
|
494
|
-
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
495
|
-
import { createHash as createHash4 } from "crypto";
|
|
496
|
-
|
|
497
494
|
// ../../packages/persistence/src/attached-derived.ts
|
|
498
495
|
import { rmSync } from "fs";
|
|
499
496
|
import { join } from "path";
|
|
@@ -20423,6 +20420,20 @@ function epochMillisToIso(ms) {
|
|
|
20423
20420
|
return new Date(ms).toISOString();
|
|
20424
20421
|
}
|
|
20425
20422
|
|
|
20423
|
+
// ../../packages/schema/src/security/recommendations.ts
|
|
20424
|
+
function healthScore(summary) {
|
|
20425
|
+
const handled = summary.byAction.block + summary.byAction.redact + summary.byAction.warn;
|
|
20426
|
+
const handledRatio = summary.findings === 0 ? 1 : handled / summary.findings;
|
|
20427
|
+
return Math.round(100 * (0.6 * summary.coverage + 0.4 * handledRatio));
|
|
20428
|
+
}
|
|
20429
|
+
function findingStatus(summary) {
|
|
20430
|
+
return {
|
|
20431
|
+
score: healthScore(summary),
|
|
20432
|
+
unreviewed: { ...summary.bySeverity },
|
|
20433
|
+
openFindings: summary.findings
|
|
20434
|
+
};
|
|
20435
|
+
}
|
|
20436
|
+
|
|
20426
20437
|
// ../../packages/schema/src/token/cost-model.ts
|
|
20427
20438
|
var PROVIDER_PLATFORM = /* @__PURE__ */ new Map([
|
|
20428
20439
|
["anthropic", "anthropic"],
|
|
@@ -20742,13 +20753,11 @@ var FindingGroup = external_exports.object({
|
|
|
20742
20753
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20743
20754
|
instances: external_exports.array(FindingInstance),
|
|
20744
20755
|
// Derived from instances' statuses with open-dominates precedence (see
|
|
20745
|
-
//
|
|
20756
|
+
// foldGroupStatus). Undefined only when no instance carries a status.
|
|
20746
20757
|
status: FindingStatus.optional(),
|
|
20747
|
-
// The distinct people across the WHOLE group, not just the
|
|
20748
|
-
//
|
|
20749
|
-
//
|
|
20750
|
-
// instance carries a user, or when the store supplied whole-group folds
|
|
20751
|
-
// without one.
|
|
20758
|
+
// The distinct people across the WHOLE group, not just the instances
|
|
20759
|
+
// carried here. Undefined when no instance carries a user, or when the
|
|
20760
|
+
// store supplied whole-group folds without one.
|
|
20752
20761
|
users: external_exports.array(FindingUser).optional()
|
|
20753
20762
|
}).meta({ id: "FindingGroup" });
|
|
20754
20763
|
var FindingStats = external_exports.object({
|
|
@@ -20777,21 +20786,31 @@ var FindingFacets = external_exports.object({
|
|
|
20777
20786
|
// counted under no value.
|
|
20778
20787
|
status: external_exports.array(FindingFacetItem),
|
|
20779
20788
|
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
20780
|
-
// reads, which can filter by it; the
|
|
20789
|
+
// reads, which can filter by it; the type-level read omits the dimension
|
|
20781
20790
|
// because a group spans tools.
|
|
20782
20791
|
tool: external_exports.array(FindingFacetItem).optional()
|
|
20783
20792
|
}).meta({ id: "FindingFacets" });
|
|
20784
|
-
var
|
|
20785
|
-
|
|
20793
|
+
var FindingTypeSummary = FindingGroup.omit({ instances: true, match: true }).meta({
|
|
20794
|
+
id: "FindingTypeSummary"
|
|
20795
|
+
});
|
|
20796
|
+
var DEFAULT_FINDING_TYPES_LIMIT = 50;
|
|
20797
|
+
var MAX_FINDING_TYPES_LIMIT = 100;
|
|
20798
|
+
var ListFindingTypesQuery = external_exports.object({
|
|
20786
20799
|
// NOTE: severity filters by Severity (critical/high/medium/low), not by
|
|
20787
|
-
// FindingAction.
|
|
20800
|
+
// FindingAction. It narrows TYPES: a type's severity is the one its newest
|
|
20801
|
+
// firing version carries, and this list pages types.
|
|
20802
|
+
//
|
|
20803
|
+
// That is NOT a claim the findings of a type share it. A rule can hold several
|
|
20804
|
+
// definition versions at different severities, so a type kept by this filter
|
|
20805
|
+
// can hold findings that individually do not match — see totals.findings on
|
|
20806
|
+
// ListFindingTypesResponse, which counts them all.
|
|
20788
20807
|
severity: external_exports.array(Severity).optional(),
|
|
20789
20808
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20790
20809
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20791
20810
|
action: external_exports.array(FindingAction).optional(),
|
|
20792
|
-
// Matches a
|
|
20793
|
-
// individual
|
|
20794
|
-
//
|
|
20811
|
+
// Matches a type's DERIVED status (see FindingGroup.status), not its
|
|
20812
|
+
// individual findings' — so a filtered row's status always reads one of the
|
|
20813
|
+
// requested values.
|
|
20795
20814
|
status: external_exports.array(FindingStatus).optional(),
|
|
20796
20815
|
q: external_exports.string().optional(),
|
|
20797
20816
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
@@ -20801,23 +20820,37 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
20801
20820
|
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
20802
20821
|
// means all time — this list has no default window.
|
|
20803
20822
|
from: external_exports.iso.datetime().optional(),
|
|
20804
|
-
// A
|
|
20805
|
-
//
|
|
20806
|
-
//
|
|
20807
|
-
//
|
|
20808
|
-
//
|
|
20823
|
+
// A RULE id that must appear in the page even when the cursor has already
|
|
20824
|
+
// advanced past its sort position. This is what keeps the selected type
|
|
20825
|
+
// visible in the list once it paginates: the target is appended out of sort
|
|
20826
|
+
// order rather than scanned forward for. Never affects totals, facets or the
|
|
20827
|
+
// cursor. Unlike the grouped read this replaces, it names a rule only — an
|
|
20828
|
+
// instance id is resolved by `findingInstance`, which is a primary-key seek
|
|
20829
|
+
// and so is not bounded by what any page happens to hold.
|
|
20809
20830
|
includeId: external_exports.string().optional(),
|
|
20810
|
-
|
|
20811
|
-
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
20831
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_TYPES_LIMIT).optional(),
|
|
20812
20832
|
cursor: external_exports.string().optional()
|
|
20813
20833
|
});
|
|
20814
|
-
var
|
|
20834
|
+
var ListFindingTypesResponse = external_exports.object({
|
|
20815
20835
|
totals: external_exports.object({
|
|
20836
|
+
// Findings belonging to the matching TYPES — not findings that each match
|
|
20837
|
+
// the filters. The filters here select types, so a type that survives
|
|
20838
|
+
// contributes its whole instanceCount.
|
|
20839
|
+
//
|
|
20840
|
+
// `status` is the one exception, narrowed per finding via
|
|
20841
|
+
// countInstancesByStatus. `severity`, `provider` and `action` are not, so
|
|
20842
|
+
// this can exceed what the instance read reports for the same filters: a
|
|
20843
|
+
// rule whose severity moved between versions is kept on its newest and
|
|
20844
|
+
// still counts its older findings. Narrowing the other three needs
|
|
20845
|
+
// per-dimension counts the aggregate does not carry today.
|
|
20816
20846
|
findings: external_exports.number().int().nonnegative(),
|
|
20817
|
-
|
|
20847
|
+
// Counts TYPES, which is the unit this read pages. The instance read's
|
|
20848
|
+
// own totals count findings; the two deliberately answer different
|
|
20849
|
+
// questions and are never summed.
|
|
20850
|
+
types: external_exports.number().int().nonnegative()
|
|
20818
20851
|
}),
|
|
20819
20852
|
facets: FindingFacets,
|
|
20820
|
-
items: external_exports.array(
|
|
20853
|
+
items: external_exports.array(FindingTypeSummary),
|
|
20821
20854
|
nextCursor: external_exports.string().nullable(),
|
|
20822
20855
|
// Present only on session-scoped queries (`sessionId` set): per ruleId, how
|
|
20823
20856
|
// many times that rule fired in the session's persisted transcript. Findings
|
|
@@ -20825,7 +20858,7 @@ var ListGroupedFindingsResponse = external_exports.object({
|
|
|
20825
20858
|
// every firing, so the two numbers legitimately differ — this map lets a
|
|
20826
20859
|
// session-scoped view show both.
|
|
20827
20860
|
sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
|
|
20828
|
-
}).meta({ id: "
|
|
20861
|
+
}).meta({ id: "ListFindingTypesResponse" });
|
|
20829
20862
|
var ApplyFindingActionRequest = external_exports.object({
|
|
20830
20863
|
// 'quarantined' is system-assigned (see FindingAction) — clients may not set
|
|
20831
20864
|
// it, so it is excluded from the request contract. The mapping helper
|
|
@@ -20855,12 +20888,13 @@ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
|
20855
20888
|
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
20856
20889
|
var ListFindingInstancesQuery = external_exports.object({
|
|
20857
20890
|
severity: external_exports.array(Severity).optional(),
|
|
20858
|
-
// Rule ids, the same vocabulary the
|
|
20891
|
+
// Rule ids, the same vocabulary the types list's `subtype` carries. Pinning
|
|
20892
|
+
// ONE of them is how the master/detail view scopes its right-hand panel.
|
|
20859
20893
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20860
20894
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20861
20895
|
action: external_exports.array(FindingAction).optional(),
|
|
20862
20896
|
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
20863
|
-
// the
|
|
20897
|
+
// the types query's type-level fold.
|
|
20864
20898
|
status: external_exports.array(FindingStatus).optional(),
|
|
20865
20899
|
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
20866
20900
|
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
@@ -20877,37 +20911,47 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
20877
20911
|
});
|
|
20878
20912
|
var ListFindingInstancesResponse = external_exports.object({
|
|
20879
20913
|
// Instances matching the filters across the whole scope, not just this
|
|
20880
|
-
// page — cursor-independent, like the
|
|
20914
|
+
// page — cursor-independent, like the types list's totals.
|
|
20881
20915
|
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
20882
|
-
// Counts in INSTANCES here, where the
|
|
20916
|
+
// Counts in INSTANCES here, where the types response counts types. Each
|
|
20883
20917
|
// dimension still excludes its own filter.
|
|
20884
20918
|
facets: FindingFacets,
|
|
20885
20919
|
items: external_exports.array(FindingInstanceDetail),
|
|
20886
20920
|
nextCursor: external_exports.string().nullable()
|
|
20887
20921
|
}).meta({ id: "ListFindingInstancesResponse" });
|
|
20888
|
-
var
|
|
20889
|
-
//
|
|
20890
|
-
//
|
|
20891
|
-
|
|
20892
|
-
|
|
20893
|
-
|
|
20894
|
-
|
|
20895
|
-
// Folded from the instances' derived statuses with the same
|
|
20896
|
-
// open-dominates precedence a group uses.
|
|
20897
|
-
status: FindingStatus.optional(),
|
|
20898
|
-
// Distinct rules seen at this location, capped — the row shows them as
|
|
20899
|
-
// chips, and the count is what conveys scale.
|
|
20900
|
-
ruleIds: external_exports.array(external_exports.string())
|
|
20901
|
-
}).meta({ id: "FindingLocationFile" });
|
|
20902
|
-
var FindingLocationRepo = external_exports.object({
|
|
20922
|
+
var FindingLocationSummary = external_exports.object({
|
|
20923
|
+
// Opaque, stable, minted from the pair by encodeLocationId. It exists
|
|
20924
|
+
// because a location's identity is two values and a URL param carries one:
|
|
20925
|
+
// `?loc=` names a location the way `?rule=` names a type. Only ever compared
|
|
20926
|
+
// for EQUALITY — the page's selection check, this read's `includeId`, the
|
|
20927
|
+
// client's page dedupe — never decoded, and never a sort key.
|
|
20928
|
+
id: external_exports.string(),
|
|
20903
20929
|
/** Empty when the instances carried no repo attribute. */
|
|
20904
20930
|
repo: external_exports.string(),
|
|
20931
|
+
// Empty when the instances carried no file path (a prompt, or a tool call
|
|
20932
|
+
// with no file attribution). Both halves empty is a real location — usually
|
|
20933
|
+
// the largest one in a store — and is selectable like any other.
|
|
20934
|
+
file: external_exports.string(),
|
|
20905
20935
|
instanceCount: external_exports.number().int().nonnegative(),
|
|
20936
|
+
// The WORST severity present, not the first row's. It is this list's primary
|
|
20937
|
+
// sort key, so it is also what explains why a row is where it is, and it is
|
|
20938
|
+
// how a reader decides what to open without opening everything.
|
|
20906
20939
|
maxSeverity: Severity,
|
|
20907
20940
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20941
|
+
// Folded from the instances' derived statuses with the same open-dominates
|
|
20942
|
+
// precedence a group uses, so it answers "is anything left to do here" and
|
|
20943
|
+
// not much more: a location holding 1 open among 40 resolved reads like one
|
|
20944
|
+
// holding 40 open. That loss is accepted — the panel beside this list
|
|
20945
|
+
// carries each finding's own status, and instanceCount sits next to the
|
|
20946
|
+
// badge.
|
|
20908
20947
|
status: FindingStatus.optional(),
|
|
20909
|
-
|
|
20910
|
-
|
|
20948
|
+
// Every distinct rule seen at this location, UNCAPPED — so the length is a
|
|
20949
|
+
// tally rather than a sample and a row can say how many there are. Bounded
|
|
20950
|
+
// by the ruleset, not by the store. The view bounds what it DISPLAYS.
|
|
20951
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
20952
|
+
}).meta({ id: "FindingLocationSummary" });
|
|
20953
|
+
var DEFAULT_FINDING_LOCATIONS_LIMIT = 50;
|
|
20954
|
+
var MAX_FINDING_LOCATIONS_LIMIT = 100;
|
|
20911
20955
|
var ListFindingLocationsQuery = external_exports.object({
|
|
20912
20956
|
severity: external_exports.array(Severity).optional(),
|
|
20913
20957
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
@@ -20920,18 +20964,42 @@ var ListFindingLocationsQuery = external_exports.object({
|
|
|
20920
20964
|
q: external_exports.string().optional(),
|
|
20921
20965
|
sessionId: external_exports.string().optional(),
|
|
20922
20966
|
from: external_exports.iso.datetime().optional(),
|
|
20923
|
-
|
|
20967
|
+
// A LOCATION id (see FindingLocationSummary.id) that must appear in the page
|
|
20968
|
+
// even when the cursor has already advanced past its sort position — the
|
|
20969
|
+
// counterpart of ListFindingTypesQuery.includeId, and needed far more often
|
|
20970
|
+
// here. Selecting a row pushes the URL, which re-renders the server and resets
|
|
20971
|
+
// the client's page cache to page 0; with distinct (repo, file) pairs running
|
|
20972
|
+
// into the thousands, a selection sitting off page 0 is the ordinary case
|
|
20973
|
+
// rather than a deep-link corner. Never affects totals, facets or the cursor.
|
|
20974
|
+
includeId: external_exports.string().optional(),
|
|
20975
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_LOCATIONS_LIMIT).optional(),
|
|
20976
|
+
cursor: external_exports.string().optional()
|
|
20924
20977
|
});
|
|
20925
20978
|
var ListFindingLocationsResponse = external_exports.object({
|
|
20926
20979
|
totals: external_exports.object({
|
|
20980
|
+
// Findings matching the filters across the whole scope. Unlike the types
|
|
20981
|
+
// read's same-named field this needs no caveat: the filters here narrow
|
|
20982
|
+
// per finding, so this is the sum of every row's instanceCount.
|
|
20927
20983
|
findings: external_exports.number().int().nonnegative(),
|
|
20928
|
-
|
|
20929
|
-
|
|
20984
|
+
// Counts LOCATIONS, the unit this read pages — the number the paginator
|
|
20985
|
+
// states. The facets beside it count FINDINGS (see below); a surface
|
|
20986
|
+
// showing both says which is which.
|
|
20987
|
+
locations: external_exports.number().int().nonnegative()
|
|
20930
20988
|
}),
|
|
20931
|
-
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
20989
|
+
// Counts in FINDINGS, where the types response counts types, each dimension
|
|
20990
|
+
// still excluding its own filter. Deliberately not locations: counting those
|
|
20991
|
+
// needs a set of location keys per dimension per value — memory tracking the
|
|
20992
|
+
// store times the vocabulary, in a read whose scan promises flat memory —
|
|
20993
|
+
// and the cheap per-location version is not an approximation but WRONG. A
|
|
20994
|
+
// location holding {claudecode, block} and {codex, warn} would survive
|
|
20995
|
+
// provider=claudecode AND action=warn, under which no single finding
|
|
20996
|
+
// matches, so the facet would contradict the instanceCount this whole view
|
|
20997
|
+
// rests on. Findings also keep the toolbar in the same unit as the page
|
|
20998
|
+
// tally and the panel it sits above.
|
|
20999
|
+
facets: FindingFacets,
|
|
21000
|
+
/** Sorted by max severity, then most recent, then (repo, file). */
|
|
21001
|
+
items: external_exports.array(FindingLocationSummary),
|
|
21002
|
+
nextCursor: external_exports.string().nullable()
|
|
20935
21003
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
20936
21004
|
|
|
20937
21005
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -22100,6 +22168,14 @@ var ControlPlaneErrorBody = external_exports.object({
|
|
|
22100
22168
|
message: external_exports.string().optional()
|
|
22101
22169
|
}).optional()
|
|
22102
22170
|
});
|
|
22171
|
+
var RemoteFailureKind = external_exports.enum([
|
|
22172
|
+
"unauthorized",
|
|
22173
|
+
"forbidden",
|
|
22174
|
+
"route-absent",
|
|
22175
|
+
"invalid-request",
|
|
22176
|
+
"rejected",
|
|
22177
|
+
"unreachable"
|
|
22178
|
+
]);
|
|
22103
22179
|
var AttachDeviceRequest = external_exports.object({
|
|
22104
22180
|
// This machine's own continuity id, so re-attaching ROTATES the credential
|
|
22105
22181
|
// on one machine record instead of producing a second one. Client-minted
|
|
@@ -22793,139 +22869,62 @@ function deriveFindingStatus(row) {
|
|
|
22793
22869
|
if (row.latestResolutionStatus === "dismissed") return "dismissed";
|
|
22794
22870
|
return "open";
|
|
22795
22871
|
}
|
|
22796
|
-
function distinctUsers(instances) {
|
|
22797
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22798
|
-
const users = [];
|
|
22799
|
-
for (const i of instances) {
|
|
22800
|
-
if (i.user === void 0 || seen.has(i.user.id)) continue;
|
|
22801
|
-
seen.add(i.user.id);
|
|
22802
|
-
users.push(i.user);
|
|
22803
|
-
}
|
|
22804
|
-
return users;
|
|
22805
|
-
}
|
|
22806
22872
|
function sortUsers(users) {
|
|
22807
22873
|
return [...users].sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
22808
22874
|
}
|
|
22809
|
-
function
|
|
22810
|
-
const overrides = opts.overrides;
|
|
22875
|
+
function buildFindingTypes(aggregates, opts = {}) {
|
|
22811
22876
|
const packNames = opts.packNames;
|
|
22812
|
-
const
|
|
22813
|
-
const
|
|
22814
|
-
|
|
22815
|
-
const
|
|
22816
|
-
|
|
22817
|
-
else byRuleId.set(row.ruleId, [row]);
|
|
22818
|
-
}
|
|
22819
|
-
const groups = [];
|
|
22820
|
-
for (const [ruleId, ruleRows] of byRuleId) {
|
|
22821
|
-
const instances = ruleRows.map((r) => {
|
|
22822
|
-
const effectiveDbAction = overrides?.get(r.id) ?? r.actionTaken;
|
|
22823
|
-
return {
|
|
22824
|
-
id: r.id,
|
|
22825
|
-
provider: toApiProvider(r.sourceTool),
|
|
22826
|
-
repo: r.repo,
|
|
22827
|
-
file: r.file,
|
|
22828
|
-
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
22829
|
-
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
22830
|
-
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
22831
|
-
...r.user === void 0 ? {} : { user: r.user },
|
|
22832
|
-
action: toApiAction(effectiveDbAction),
|
|
22833
|
-
detectedAt: r.occurredAt,
|
|
22834
|
-
confidence: r.confidence,
|
|
22835
|
-
status: r.status
|
|
22836
|
-
};
|
|
22837
|
-
});
|
|
22838
|
-
const agg = aggregates?.get(ruleId);
|
|
22839
|
-
const users = agg ? sortUsers(agg.users ?? []) : distinctUsers(instances);
|
|
22840
|
-
const latestDetectedAt = agg?.latestDetectedAt ?? ruleRows.reduce(
|
|
22841
|
-
(max, r) => r.occurredAt > max ? r.occurredAt : max,
|
|
22842
|
-
ruleRows[0]?.occurredAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
22843
|
-
);
|
|
22844
|
-
const seenProviders = /* @__PURE__ */ new Set();
|
|
22845
|
-
const providers = (agg ? [...new Set(agg.sourceTools.map(toApiProvider))].sort() : instances.map((i) => i.provider)).filter((p) => {
|
|
22846
|
-
if (seenProviders.has(p)) return false;
|
|
22847
|
-
seenProviders.add(p);
|
|
22848
|
-
return true;
|
|
22849
|
-
});
|
|
22850
|
-
const actionSet = new Set(
|
|
22851
|
-
agg ? agg.actionsTaken.map(toApiAction) : instances.map((i) => i.action)
|
|
22852
|
-
);
|
|
22877
|
+
const types = [];
|
|
22878
|
+
for (const [ruleId, agg] of aggregates) {
|
|
22879
|
+
const users = sortUsers(agg.users ?? []);
|
|
22880
|
+
const providers = [...new Set(agg.sourceTools.map(toApiProvider))].sort();
|
|
22881
|
+
const actionSet = new Set(agg.actionsTaken.map(toApiAction));
|
|
22853
22882
|
const aggregateAction = actionSet.size === 1 ? [...actionSet][0] ?? null : null;
|
|
22854
|
-
const
|
|
22855
|
-
const
|
|
22856
|
-
id: ruleId,
|
|
22857
|
-
name: packNames?.get(ruleId) ?? null
|
|
22858
|
-
};
|
|
22859
|
-
const apiCategory = toApiCategory(ruleRows[0]?.category ?? "custom");
|
|
22860
|
-
const policy = { id: `category:${apiCategory}`, name: apiCategory };
|
|
22861
|
-
const match = {
|
|
22862
|
-
maskedValue: ruleRows[0]?.maskedMatch ?? "",
|
|
22863
|
-
contextPrefix: ""
|
|
22864
|
-
// empty (pending privacy review)
|
|
22865
|
-
};
|
|
22866
|
-
const status = foldGroupStatus(
|
|
22867
|
-
agg ? agg.statusInputs.map(deriveFindingStatus) : instances.map((i) => i.status)
|
|
22868
|
-
);
|
|
22869
|
-
const group = {
|
|
22883
|
+
const apiCategory = toApiCategory(agg.category ?? "custom");
|
|
22884
|
+
const type = {
|
|
22870
22885
|
id: ruleId,
|
|
22871
22886
|
category: apiCategory,
|
|
22872
22887
|
subtype: ruleId,
|
|
22873
22888
|
// human label comes with pack metadata later
|
|
22874
|
-
severity,
|
|
22875
|
-
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
instanceCount: agg?.instanceCount ?? instances.length,
|
|
22889
|
+
severity: agg.severity ?? "low",
|
|
22890
|
+
detection: { id: ruleId, name: packNames?.get(ruleId) ?? null },
|
|
22891
|
+
policy: { id: `category:${apiCategory}`, name: apiCategory },
|
|
22892
|
+
instanceCount: agg.instanceCount,
|
|
22879
22893
|
providers,
|
|
22880
22894
|
aggregateAction,
|
|
22881
|
-
latestDetectedAt,
|
|
22882
|
-
|
|
22883
|
-
status,
|
|
22895
|
+
latestDetectedAt: agg.latestDetectedAt,
|
|
22896
|
+
status: foldGroupStatus(agg.statusInputs.map(deriveFindingStatus)),
|
|
22884
22897
|
...users.length > 0 ? { users } : {}
|
|
22885
22898
|
};
|
|
22886
|
-
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
haystackCache.set(group, buildHaystack(group, agg.searchText));
|
|
22890
|
-
}
|
|
22899
|
+
actionsCache.set(type, [...actionSet]);
|
|
22900
|
+
if (agg.searchText !== void 0) {
|
|
22901
|
+
haystackCache.set(type, buildHaystack(type, agg.searchText));
|
|
22891
22902
|
}
|
|
22892
|
-
|
|
22903
|
+
types.push(type);
|
|
22893
22904
|
}
|
|
22894
|
-
return
|
|
22905
|
+
return types;
|
|
22895
22906
|
}
|
|
22896
22907
|
var haystackCache = /* @__PURE__ */ new WeakMap();
|
|
22897
|
-
function buildHaystack(
|
|
22908
|
+
function buildHaystack(t, extra) {
|
|
22898
22909
|
return [
|
|
22899
|
-
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
...g.instances.map((i) => i.repo),
|
|
22905
|
-
...g.instances.map((i) => i.file),
|
|
22906
|
-
...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
|
|
22907
|
-
...g.instances.map((i) => i.id),
|
|
22908
|
-
// The people: the whole group's list when the store folded one, plus the
|
|
22909
|
-
// preview's own — the two overlap, and a haystack does not mind.
|
|
22910
|
-
...(g.users ?? []).map((u) => u.name),
|
|
22911
|
-
...g.instances.map((i) => i.user?.name ?? ""),
|
|
22910
|
+
t.subtype,
|
|
22911
|
+
t.category,
|
|
22912
|
+
t.policy.name,
|
|
22913
|
+
t.id,
|
|
22914
|
+
...(t.users ?? []).map((u) => u.name),
|
|
22912
22915
|
...extra === void 0 ? [] : [extra]
|
|
22913
22916
|
].join(" ").toLowerCase();
|
|
22914
22917
|
}
|
|
22915
|
-
function
|
|
22916
|
-
const cached2 = haystackCache.get(
|
|
22918
|
+
function typeHaystack(t) {
|
|
22919
|
+
const cached2 = haystackCache.get(t);
|
|
22917
22920
|
if (cached2 !== void 0) return cached2;
|
|
22918
|
-
const haystack = buildHaystack(
|
|
22919
|
-
haystackCache.set(
|
|
22921
|
+
const haystack = buildHaystack(t);
|
|
22922
|
+
haystackCache.set(t, haystack);
|
|
22920
22923
|
return haystack;
|
|
22921
22924
|
}
|
|
22922
22925
|
var actionsCache = /* @__PURE__ */ new WeakMap();
|
|
22923
|
-
function
|
|
22924
|
-
|
|
22925
|
-
if (cached2 !== void 0) return cached2;
|
|
22926
|
-
const actions = [...new Set(g.instances.map((i) => i.action))];
|
|
22927
|
-
actionsCache.set(g, actions);
|
|
22928
|
-
return actions;
|
|
22926
|
+
function typeActions(t) {
|
|
22927
|
+
return actionsCache.get(t) ?? [];
|
|
22929
22928
|
}
|
|
22930
22929
|
function countInstancesByStatus(statusInputs, statuses) {
|
|
22931
22930
|
const statusSet = new Set(statuses);
|
|
@@ -22936,8 +22935,8 @@ function countInstancesByStatus(statusInputs, statuses) {
|
|
|
22936
22935
|
}
|
|
22937
22936
|
return sum;
|
|
22938
22937
|
}
|
|
22939
|
-
function applyFindingFilters(
|
|
22940
|
-
let filtered =
|
|
22938
|
+
function applyFindingFilters(types, opts) {
|
|
22939
|
+
let filtered = types;
|
|
22941
22940
|
if (opts.severity && opts.severity.length > 0) {
|
|
22942
22941
|
const sevSet = new Set(opts.severity);
|
|
22943
22942
|
filtered = filtered.filter((g) => sevSet.has(g.severity));
|
|
@@ -22948,7 +22947,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22948
22947
|
}
|
|
22949
22948
|
if (opts.actions && opts.actions.length > 0) {
|
|
22950
22949
|
const actionSet = new Set(opts.actions);
|
|
22951
|
-
filtered = filtered.filter((
|
|
22950
|
+
filtered = filtered.filter((t) => typeActions(t).some((a) => actionSet.has(a)));
|
|
22952
22951
|
}
|
|
22953
22952
|
if (opts.subtype && opts.subtype.length > 0) {
|
|
22954
22953
|
const subtypeSet = new Set(opts.subtype);
|
|
@@ -22960,7 +22959,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22960
22959
|
}
|
|
22961
22960
|
if (opts.q) {
|
|
22962
22961
|
const q = opts.q.toLowerCase();
|
|
22963
|
-
filtered = filtered.filter((
|
|
22962
|
+
filtered = filtered.filter((t) => typeHaystack(t).includes(q));
|
|
22964
22963
|
}
|
|
22965
22964
|
return filtered;
|
|
22966
22965
|
}
|
|
@@ -22975,11 +22974,11 @@ function compareFindingGroupOrder(a, b) {
|
|
|
22975
22974
|
if (recencyDiff !== 0) return recencyDiff;
|
|
22976
22975
|
return a.id.localeCompare(b.id);
|
|
22977
22976
|
}
|
|
22978
|
-
function
|
|
22979
|
-
return [...
|
|
22977
|
+
function sortFindingTypes(types) {
|
|
22978
|
+
return [...types].sort(compareFindingGroupOrder);
|
|
22980
22979
|
}
|
|
22981
|
-
function computeFindingFacets(
|
|
22982
|
-
const forSeverity = applyFindingFilters(
|
|
22980
|
+
function computeFindingFacets(allTypes, opts) {
|
|
22981
|
+
const forSeverity = applyFindingFilters(allTypes, {
|
|
22983
22982
|
providers: opts.providers,
|
|
22984
22983
|
actions: opts.actions,
|
|
22985
22984
|
statuses: opts.statuses,
|
|
@@ -22990,7 +22989,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
22990
22989
|
for (const g of forSeverity) {
|
|
22991
22990
|
severityMap.set(g.severity, (severityMap.get(g.severity) ?? 0) + 1);
|
|
22992
22991
|
}
|
|
22993
|
-
const forProvider = applyFindingFilters(
|
|
22992
|
+
const forProvider = applyFindingFilters(allTypes, {
|
|
22994
22993
|
actions: opts.actions,
|
|
22995
22994
|
statuses: opts.statuses,
|
|
22996
22995
|
q: opts.q,
|
|
@@ -23001,7 +23000,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23001
23000
|
for (const g of forProvider) {
|
|
23002
23001
|
for (const p of g.providers) providerMap.set(p, (providerMap.get(p) ?? 0) + 1);
|
|
23003
23002
|
}
|
|
23004
|
-
const forAction = applyFindingFilters(
|
|
23003
|
+
const forAction = applyFindingFilters(allTypes, {
|
|
23005
23004
|
providers: opts.providers,
|
|
23006
23005
|
statuses: opts.statuses,
|
|
23007
23006
|
q: opts.q,
|
|
@@ -23010,9 +23009,9 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23010
23009
|
});
|
|
23011
23010
|
const actionMap = /* @__PURE__ */ new Map();
|
|
23012
23011
|
for (const g of forAction) {
|
|
23013
|
-
for (const a of
|
|
23012
|
+
for (const a of typeActions(g)) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
23014
23013
|
}
|
|
23015
|
-
const forSubtype = applyFindingFilters(
|
|
23014
|
+
const forSubtype = applyFindingFilters(allTypes, {
|
|
23016
23015
|
providers: opts.providers,
|
|
23017
23016
|
actions: opts.actions,
|
|
23018
23017
|
statuses: opts.statuses,
|
|
@@ -23021,7 +23020,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23021
23020
|
});
|
|
23022
23021
|
const subtypeMap = /* @__PURE__ */ new Map();
|
|
23023
23022
|
for (const g of forSubtype) subtypeMap.set(g.subtype, (subtypeMap.get(g.subtype) ?? 0) + 1);
|
|
23024
|
-
const forStatus = applyFindingFilters(
|
|
23023
|
+
const forStatus = applyFindingFilters(allTypes, {
|
|
23025
23024
|
providers: opts.providers,
|
|
23026
23025
|
actions: opts.actions,
|
|
23027
23026
|
q: opts.q,
|
|
@@ -23069,10 +23068,20 @@ function matchesDimension(row, opts, dimension) {
|
|
|
23069
23068
|
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
23070
23069
|
case "tools":
|
|
23071
23070
|
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
23071
|
+
// An EMPTY value is a real filter here, not an absent one. The location
|
|
23072
|
+
// list buckets a finding whose event recorded no repo — or no file — under
|
|
23073
|
+
// the empty string, and selecting that bucket has to narrow the panel to
|
|
23074
|
+
// exactly it. Only `undefined` means "no filter"; a caller that wants every
|
|
23075
|
+
// row omits the key, which every call site already does.
|
|
23076
|
+
//
|
|
23077
|
+
// Reading '' as unset is what this replaced, and it failed in the one place
|
|
23078
|
+
// it mattered: the no-repo/no-file bucket is often the largest in a real
|
|
23079
|
+
// store, and its panel dropped both predicates and returned the WHOLE scope
|
|
23080
|
+
// — a row reading 3 findings beside a panel listing every finding there is.
|
|
23072
23081
|
case "repo":
|
|
23073
|
-
return opts.repo === void 0 ||
|
|
23082
|
+
return opts.repo === void 0 || row.repo === opts.repo;
|
|
23074
23083
|
case "file":
|
|
23075
|
-
return opts.file === void 0 ||
|
|
23084
|
+
return opts.file === void 0 || row.file === opts.file;
|
|
23076
23085
|
case "q":
|
|
23077
23086
|
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
23078
23087
|
}
|
|
@@ -23186,6 +23195,23 @@ function addToLocation(acc, row) {
|
|
|
23186
23195
|
acc.statuses.push(row.status);
|
|
23187
23196
|
acc.ruleIds.add(row.ruleId);
|
|
23188
23197
|
}
|
|
23198
|
+
function compareLocationOrder(a, b) {
|
|
23199
|
+
const rankA = SEVERITY_ORDER2[a.maxSeverity] ?? -1;
|
|
23200
|
+
const rankB = SEVERITY_ORDER2[b.maxSeverity] ?? -1;
|
|
23201
|
+
if (rankA !== rankB) return rankA - rankB;
|
|
23202
|
+
if (a.latestDetectedAt !== b.latestDetectedAt) {
|
|
23203
|
+
return a.latestDetectedAt < b.latestDetectedAt ? 1 : -1;
|
|
23204
|
+
}
|
|
23205
|
+
if (a.repo !== b.repo) return a.repo < b.repo ? -1 : 1;
|
|
23206
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
23207
|
+
return 0;
|
|
23208
|
+
}
|
|
23209
|
+
function encodeLocationId(repo, file2) {
|
|
23210
|
+
return `${encodePart(repo)}/${encodePart(file2)}`;
|
|
23211
|
+
}
|
|
23212
|
+
function encodePart(value) {
|
|
23213
|
+
return encodeURIComponent(value.replace(/[\uD800-\uDFFF]/gu, "\uFFFD"));
|
|
23214
|
+
}
|
|
23189
23215
|
|
|
23190
23216
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
23191
23217
|
var InstalledPack = external_exports.object({
|
|
@@ -23689,11 +23715,11 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23689
23715
|
// covers the current payload and must be re-granted.
|
|
23690
23716
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23691
23717
|
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23692
|
-
// with the payload shape and the endpoint they agreed to. Since payload
|
|
23693
|
-
// that covers
|
|
23694
|
-
// carry prompt/reply text in `content
|
|
23695
|
-
// Absent until granted, and a grant for a different endpoint
|
|
23696
|
-
// payload no longer counts.
|
|
23718
|
+
// with the payload shape and the endpoint they agreed to. Since payload v3
|
|
23719
|
+
// that covers the pre-attach backlog AND undelivered captures alike, and both
|
|
23720
|
+
// carry prompt/reply/tool-output text in `content`; the key name predates
|
|
23721
|
+
// both widenings. Absent until granted, and a grant for a different endpoint
|
|
23722
|
+
// or an older payload no longer counts.
|
|
23697
23723
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23698
23724
|
});
|
|
23699
23725
|
function defaultWorkspaceSettings() {
|
|
@@ -23824,6 +23850,9 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23824
23850
|
"dataSharesInPlace",
|
|
23825
23851
|
"redactFallback"
|
|
23826
23852
|
]).meta({ id: "ManagedSettingKey" });
|
|
23853
|
+
function isManagedSettingKey(value) {
|
|
23854
|
+
return ManagedSettingKey.safeParse(value).success;
|
|
23855
|
+
}
|
|
23827
23856
|
var ManagedSettingsValues = external_exports.object({
|
|
23828
23857
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23829
23858
|
controlPlane: external_exports.object({
|
|
@@ -23848,7 +23877,27 @@ var ManagedSettings = external_exports.object({
|
|
|
23848
23877
|
// Which of those the user may not change. A key here with no matching value
|
|
23849
23878
|
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23850
23879
|
// the user may still override. The two are separable on purpose.
|
|
23851
|
-
|
|
23880
|
+
//
|
|
23881
|
+
// Parsed as NAMES rather than as the enum, and split below: a name this
|
|
23882
|
+
// build does not know is dropped from the locked set and reported, never a
|
|
23883
|
+
// reason to refuse the file. The same shape reaches an older build whenever
|
|
23884
|
+
// an administrator locks a key a newer build added, and refusing it there
|
|
23885
|
+
// ran that build entirely unmanaged — every pin and lock gone — on exactly
|
|
23886
|
+
// the fleets most likely to carry a version skew. A name outside the enum
|
|
23887
|
+
// is still never HONOURED: the lockable set stays explicit above.
|
|
23888
|
+
lockedFields: external_exports.array(external_exports.string()).default([])
|
|
23889
|
+
}).transform(({ lockedFields, ...rest }) => {
|
|
23890
|
+
const known = [];
|
|
23891
|
+
const unknown2 = [];
|
|
23892
|
+
for (const name of lockedFields) {
|
|
23893
|
+
if (isManagedSettingKey(name)) known.push(name);
|
|
23894
|
+
else unknown2.push(name);
|
|
23895
|
+
}
|
|
23896
|
+
return {
|
|
23897
|
+
...rest,
|
|
23898
|
+
lockedFields: known,
|
|
23899
|
+
...unknown2.length > 0 ? { unknownLockedFields: unknown2 } : {}
|
|
23900
|
+
};
|
|
23852
23901
|
}).meta({ id: "ManagedSettings" });
|
|
23853
23902
|
|
|
23854
23903
|
// ../../packages/schema/src/zod/project-files.ts
|
|
@@ -23972,7 +24021,11 @@ var FindingsTimeseriesPoint = external_exports.object({
|
|
|
23972
24021
|
timestamp: external_exports.iso.date(),
|
|
23973
24022
|
critical: external_exports.number().int().nonnegative(),
|
|
23974
24023
|
high: external_exports.number().int().nonnegative(),
|
|
23975
|
-
medium: external_exports.number().int().nonnegative()
|
|
24024
|
+
medium: external_exports.number().int().nonnegative(),
|
|
24025
|
+
// Optional and additive, so a producer written against the earlier
|
|
24026
|
+
// three-series contract keeps validating. A consumer plotting it resolves the
|
|
24027
|
+
// absent case itself — the chart point requires a number.
|
|
24028
|
+
low: external_exports.number().int().nonnegative().optional()
|
|
23976
24029
|
}).meta({ id: "FindingsTimeseriesPoint" });
|
|
23977
24030
|
var FindingsTimeseriesResponse = external_exports.object({
|
|
23978
24031
|
range: TimeRange,
|
|
@@ -23998,6 +24051,10 @@ var ResolvedFeedItem = external_exports.object({
|
|
|
23998
24051
|
findingKey: external_exports.string(),
|
|
23999
24052
|
ruleId: external_exports.string(),
|
|
24000
24053
|
severity: Severity,
|
|
24054
|
+
// Repository slug, and the file path RELATIVE to it. The pair is what
|
|
24055
|
+
// identifies the file: a bare path matches the same name in every repo.
|
|
24056
|
+
// Optional and additive; empty when the event carried no repo.
|
|
24057
|
+
repo: external_exports.string().optional(),
|
|
24001
24058
|
path: external_exports.string(),
|
|
24002
24059
|
// ISO-8601 datetime (matches FindingInstance.detectedAt / the rest of the
|
|
24003
24060
|
// findings domain). The reader `.toISOString()`s the DB epoch-ms values.
|
|
@@ -25541,7 +25598,8 @@ var SqliteActivityRepository = class {
|
|
|
25541
25598
|
SELECT 1 FROM audit_events d
|
|
25542
25599
|
WHERE d.root_session_id = audit_events.id
|
|
25543
25600
|
AND (d.content LIKE ? ESCAPE '\\'
|
|
25544
|
-
OR json_extract(d.attributes, '$.detail')
|
|
25601
|
+
OR coalesce(json_extract(d.attributes, '$.detail'),
|
|
25602
|
+
json_extract(d.attributes, '$.target')) LIKE ? ESCAPE '\\')))`
|
|
25545
25603
|
);
|
|
25546
25604
|
params.push(pattern, pattern, pattern, pattern, pattern, pattern);
|
|
25547
25605
|
}
|
|
@@ -26879,23 +26937,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26879
26937
|
)`;
|
|
26880
26938
|
|
|
26881
26939
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26882
|
-
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26883
|
-
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26884
|
-
var LOCATION_RULE_IDS_CAP = 20;
|
|
26885
|
-
function compareLocationOrder(a, b) {
|
|
26886
|
-
return compareFindingGroupOrder(
|
|
26887
|
-
{
|
|
26888
|
-
severity: a.maxSeverity,
|
|
26889
|
-
latestDetectedAt: a.latestDetectedAt,
|
|
26890
|
-
id: ""
|
|
26891
|
-
},
|
|
26892
|
-
{
|
|
26893
|
-
severity: b.maxSeverity,
|
|
26894
|
-
latestDetectedAt: b.latestDetectedAt,
|
|
26895
|
-
id: ""
|
|
26896
|
-
}
|
|
26897
|
-
);
|
|
26898
|
-
}
|
|
26899
26940
|
var CONCAT_SEP = ",";
|
|
26900
26941
|
var TUPLE_SEP = "|";
|
|
26901
26942
|
function splitConcat(value) {
|
|
@@ -26947,13 +26988,48 @@ function decodeGroupCursor(cursor) {
|
|
|
26947
26988
|
return null;
|
|
26948
26989
|
}
|
|
26949
26990
|
function firstAfter(sorted, cursor) {
|
|
26950
|
-
const index = sorted.findIndex((
|
|
26991
|
+
const index = sorted.findIndex((t) => compareFindingGroupOrder(t, cursor) > 0);
|
|
26951
26992
|
return index === -1 ? sorted.length : index;
|
|
26952
26993
|
}
|
|
26953
26994
|
function findDeepLinked(sorted, page, id) {
|
|
26954
|
-
if (page.some((
|
|
26955
|
-
return sorted.find((
|
|
26995
|
+
if (page.some((t) => t.id === id)) return void 0;
|
|
26996
|
+
return sorted.find((t) => t.id === id);
|
|
26997
|
+
}
|
|
26998
|
+
function encodeLocationCursor(location) {
|
|
26999
|
+
const payload = {
|
|
27000
|
+
sev: location.maxSeverity,
|
|
27001
|
+
t: location.latestDetectedAt,
|
|
27002
|
+
r: location.repo,
|
|
27003
|
+
f: location.file
|
|
27004
|
+
};
|
|
27005
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
27006
|
+
}
|
|
27007
|
+
function decodeLocationCursor(cursor) {
|
|
27008
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
27009
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.r === "string" && typeof parsed2.f === "string") {
|
|
27010
|
+
return { maxSeverity: parsed2.sev, latestDetectedAt: parsed2.t, repo: parsed2.r, file: parsed2.f };
|
|
27011
|
+
}
|
|
27012
|
+
return null;
|
|
27013
|
+
}
|
|
27014
|
+
function firstLocationAfter(sorted, cursor) {
|
|
27015
|
+
const index = sorted.findIndex((l) => compareLocationOrder(l, cursor) > 0);
|
|
27016
|
+
return index === -1 ? sorted.length : index;
|
|
26956
27017
|
}
|
|
27018
|
+
function findDeepLinkedLocation(sorted, page, id) {
|
|
27019
|
+
if (page.some((l) => l.id === id)) return void 0;
|
|
27020
|
+
return sorted.find((l) => l.id === id);
|
|
27021
|
+
}
|
|
27022
|
+
var FINDING_ROW_COLUMNS_SQL = `f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
27023
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
27024
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27025
|
+
e.started_at AS occurred_at,
|
|
27026
|
+
e.source_tool AS source_tool,
|
|
27027
|
+
e.repo AS repo,
|
|
27028
|
+
e.file_path AS file,
|
|
27029
|
+
e.tool_name AS tool_name,
|
|
27030
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27031
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27032
|
+
${latestResolutionStatusSql("f")} AS latest_status`;
|
|
26957
27033
|
var DAY_MS3 = 864e5;
|
|
26958
27034
|
var SqliteFindingsRepository = class {
|
|
26959
27035
|
constructor(db) {
|
|
@@ -27074,30 +27150,26 @@ var SqliteFindingsRepository = class {
|
|
|
27074
27150
|
);
|
|
27075
27151
|
}
|
|
27076
27152
|
/**
|
|
27077
|
-
*
|
|
27078
|
-
*
|
|
27079
|
-
*
|
|
27080
|
-
*
|
|
27081
|
-
*
|
|
27082
|
-
*
|
|
27083
|
-
*
|
|
27084
|
-
*
|
|
27085
|
-
*
|
|
27086
|
-
*
|
|
27087
|
-
*
|
|
27088
|
-
*
|
|
27153
|
+
* Finding TYPES for the dashboard — one row per rule, scoped to the four
|
|
27154
|
+
* capture kinds (audit_events also holds structural/reconciler/scan rows this
|
|
27155
|
+
* list must never surface), with per-filter-excluded facets, the requested
|
|
27156
|
+
* filters applied, and sorted by severity then recency. Filtering and faceting
|
|
27157
|
+
* run in JS via the shared @akasecurity/schema helpers. `totals` reflect the
|
|
27158
|
+
* full filtered set; `items` is the requested page (default 50), keyset-paged.
|
|
27159
|
+
* Under a `status` filter, `totals.findings` counts only findings whose
|
|
27160
|
+
* derived status was requested.
|
|
27161
|
+
*
|
|
27162
|
+
* ONE read, which materializes no findings: a single aggregate per rule_id,
|
|
27163
|
+
* folding EVERY finding into the numbers a type row and the filters need
|
|
27164
|
+
* (count, severity, category, providers, actions, statuses, latest, search
|
|
27165
|
+
* text). The findings OF a type come from listFindingInstances scoped to
|
|
27166
|
+
* `subtype`, so neither list bounds the other and no per-type cap exists.
|
|
27089
27167
|
*
|
|
27090
|
-
* Two reads, neither of which materializes a row per finding:
|
|
27091
|
-
* 1. one aggregate row per rule_id, folding EVERY instance into the numbers
|
|
27092
|
-
* the group and the filters need (count, providers, actions, statuses,
|
|
27093
|
-
* latest, search text);
|
|
27094
|
-
* 2. each group's newest PREVIEW_INSTANCES_PER_GROUP instances, which
|
|
27095
|
-
* populate `instances` for the table's expanded rows.
|
|
27096
27168
|
* The aggregates carry raw DB values and are translated by the same
|
|
27097
|
-
* @akasecurity/schema mappers
|
|
27098
|
-
* rule is ever restated in SQL.
|
|
27169
|
+
* @akasecurity/schema mappers every other path uses, so no enum mapping or
|
|
27170
|
+
* status rule is ever restated in SQL.
|
|
27099
27171
|
*/
|
|
27100
|
-
|
|
27172
|
+
listFindingTypes(query) {
|
|
27101
27173
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
27102
27174
|
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
27103
27175
|
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
@@ -27110,12 +27182,7 @@ var SqliteFindingsRepository = class {
|
|
|
27110
27182
|
predicate,
|
|
27111
27183
|
params: sessionParams
|
|
27112
27184
|
});
|
|
27113
|
-
const
|
|
27114
|
-
sessionId: query.sessionId,
|
|
27115
|
-
from: query.from
|
|
27116
|
-
});
|
|
27117
|
-
const groupable = rows.map(toFlatFindingRow);
|
|
27118
|
-
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27185
|
+
const allTypes = buildFindingTypes(aggregates);
|
|
27119
27186
|
const filterOpts = {
|
|
27120
27187
|
severity: query.severity,
|
|
27121
27188
|
providers: query.provider,
|
|
@@ -27124,30 +27191,25 @@ var SqliteFindingsRepository = class {
|
|
|
27124
27191
|
subtype: query.subtype,
|
|
27125
27192
|
q: query.q
|
|
27126
27193
|
};
|
|
27127
|
-
const facets = computeFindingFacets(
|
|
27128
|
-
const sorted =
|
|
27194
|
+
const facets = computeFindingFacets(allTypes, filterOpts);
|
|
27195
|
+
const sorted = sortFindingTypes(applyFindingFilters(allTypes, filterOpts));
|
|
27129
27196
|
const statusFilter = query.status ?? [];
|
|
27130
27197
|
const totals = {
|
|
27131
|
-
findings: sorted.reduce((acc,
|
|
27132
|
-
if (statusFilter.length === 0) return acc +
|
|
27133
|
-
const agg = aggregates.get(
|
|
27134
|
-
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ??
|
|
27198
|
+
findings: sorted.reduce((acc, t) => {
|
|
27199
|
+
if (statusFilter.length === 0) return acc + t.instanceCount;
|
|
27200
|
+
const agg = aggregates.get(t.id);
|
|
27201
|
+
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ?? t.instanceCount : t.instanceCount);
|
|
27135
27202
|
}, 0),
|
|
27136
|
-
|
|
27203
|
+
types: sorted.length
|
|
27137
27204
|
};
|
|
27138
|
-
const limit = query.limit ??
|
|
27205
|
+
const limit = query.limit ?? DEFAULT_FINDING_TYPES_LIMIT;
|
|
27139
27206
|
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
27140
27207
|
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
27141
27208
|
const page = sorted.slice(start, start + limit);
|
|
27142
27209
|
const lastOnPage = page.at(-1);
|
|
27143
27210
|
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
27144
27211
|
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
27145
|
-
const
|
|
27146
|
-
const narrow = (g) => statusSet ? {
|
|
27147
|
-
...g,
|
|
27148
|
-
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
27149
|
-
} : g;
|
|
27150
|
-
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
27212
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []];
|
|
27151
27213
|
return Promise.resolve({
|
|
27152
27214
|
totals,
|
|
27153
27215
|
facets,
|
|
@@ -27158,7 +27220,7 @@ var SqliteFindingsRepository = class {
|
|
|
27158
27220
|
}
|
|
27159
27221
|
/**
|
|
27160
27222
|
* One row per rule_id, folding EVERY instance of the group into the values
|
|
27161
|
-
*
|
|
27223
|
+
* buildFindingTypes cannot recover from an aggregate. Bounded by the number of
|
|
27162
27224
|
* distinct rule_ids (the installed packs' rules), not by the store's size.
|
|
27163
27225
|
*
|
|
27164
27226
|
* A single scan, folded in two levels: the inner SELECT groups by
|
|
@@ -27252,13 +27314,25 @@ var SqliteFindingsRepository = class {
|
|
|
27252
27314
|
});
|
|
27253
27315
|
}
|
|
27254
27316
|
/**
|
|
27255
|
-
* The same findings folded by
|
|
27317
|
+
* The same findings folded by WHERE they live — one row per (repo, file) pair.
|
|
27256
27318
|
*
|
|
27257
27319
|
* The grouping keys come from the capturing event's attributes, which is what
|
|
27258
|
-
* the local store relates a finding to
|
|
27259
|
-
*
|
|
27260
|
-
* empty-string bucket, which
|
|
27261
|
-
*
|
|
27320
|
+
* the local store relates a finding to; there is no finding↔asset row to group
|
|
27321
|
+
* by instead. A repo or file the event did not record folds into the
|
|
27322
|
+
* empty-string bucket, which is a real location like any other: it is listed,
|
|
27323
|
+
* it is selectable, and its `?loc=` token is as good as any other row's.
|
|
27324
|
+
*
|
|
27325
|
+
* ONE flat list rather than repos nesting files. A rollup can only be paged by
|
|
27326
|
+
* repo, which leaves the file list inside it unbounded — the shape the by-type
|
|
27327
|
+
* list was rebuilt to remove — and two-level pagination inside an
|
|
27328
|
+
* expand/collapse table is what pushed that view to master/detail in the first
|
|
27329
|
+
* place.
|
|
27330
|
+
*
|
|
27331
|
+
* Every filter narrows the FINDINGS and the locations fall out of what
|
|
27332
|
+
* survives, so each row's `instanceCount` is exactly what listFindingInstances
|
|
27333
|
+
* reports for the same filters scoped to that pair. The view depends on it:
|
|
27334
|
+
* one toolbar sits over both panels precisely because a location owns none of
|
|
27335
|
+
* its fields.
|
|
27262
27336
|
*/
|
|
27263
27337
|
listFindingLocations(query) {
|
|
27264
27338
|
const opts = {
|
|
@@ -27270,13 +27344,16 @@ var SqliteFindingsRepository = class {
|
|
|
27270
27344
|
tools: query.tool,
|
|
27271
27345
|
q: query.q
|
|
27272
27346
|
};
|
|
27273
|
-
const limit = query.limit ??
|
|
27347
|
+
const limit = query.limit ?? DEFAULT_FINDING_LOCATIONS_LIMIT;
|
|
27348
|
+
const cursor = query.cursor === void 0 ? null : decodeLocationCursor(query.cursor);
|
|
27274
27349
|
const byRepo = /* @__PURE__ */ new Map();
|
|
27350
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27275
27351
|
let total = 0;
|
|
27276
27352
|
for (const row of this.scanFindingRows({
|
|
27277
27353
|
sessionId: query.sessionId,
|
|
27278
27354
|
from: query.from
|
|
27279
27355
|
})) {
|
|
27356
|
+
accumulator.add(row);
|
|
27280
27357
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27281
27358
|
total += 1;
|
|
27282
27359
|
let files = byRepo.get(row.repo);
|
|
@@ -27291,103 +27368,35 @@ var SqliteFindingsRepository = class {
|
|
|
27291
27368
|
}
|
|
27292
27369
|
addToLocation(acc, row);
|
|
27293
27370
|
}
|
|
27294
|
-
|
|
27295
|
-
const
|
|
27296
|
-
|
|
27297
|
-
|
|
27298
|
-
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
|
|
27314
|
-
|
|
27315
|
-
|
|
27316
|
-
);
|
|
27317
|
-
const statuses = fileRows.map((f) => f.status);
|
|
27318
|
-
const folded = foldGroupStatus(statuses);
|
|
27319
|
-
return {
|
|
27320
|
-
repo,
|
|
27321
|
-
instanceCount: rollup.instanceCount,
|
|
27322
|
-
maxSeverity: rollup.maxSeverity,
|
|
27323
|
-
latestDetectedAt: rollup.latestDetectedAt,
|
|
27324
|
-
...folded === void 0 ? {} : { status: folded },
|
|
27325
|
-
files: fileRows
|
|
27326
|
-
};
|
|
27327
|
-
});
|
|
27328
|
-
repos.sort(compareLocationOrder);
|
|
27371
|
+
const sorted = [];
|
|
27372
|
+
for (const [repo, files] of byRepo) {
|
|
27373
|
+
for (const [file2, acc] of files) {
|
|
27374
|
+
const status = foldGroupStatus(acc.statuses);
|
|
27375
|
+
sorted.push({
|
|
27376
|
+
id: encodeLocationId(repo, file2),
|
|
27377
|
+
repo,
|
|
27378
|
+
file: file2,
|
|
27379
|
+
instanceCount: acc.instanceCount,
|
|
27380
|
+
maxSeverity: acc.maxSeverity,
|
|
27381
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
27382
|
+
...status === void 0 ? {} : { status },
|
|
27383
|
+
ruleIds: [...acc.ruleIds]
|
|
27384
|
+
});
|
|
27385
|
+
}
|
|
27386
|
+
}
|
|
27387
|
+
sorted.sort(compareLocationOrder);
|
|
27388
|
+
const start = cursor === null ? 0 : firstLocationAfter(sorted, cursor);
|
|
27389
|
+
const page = sorted.slice(start, start + limit);
|
|
27390
|
+
const lastOnPage = page.at(-1);
|
|
27391
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeLocationCursor(lastOnPage) : null;
|
|
27392
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinkedLocation(sorted, page, query.includeId);
|
|
27329
27393
|
return Promise.resolve({
|
|
27330
|
-
totals: { findings: total,
|
|
27331
|
-
|
|
27332
|
-
|
|
27394
|
+
totals: { findings: total, locations: sorted.length },
|
|
27395
|
+
facets: accumulator.facets(),
|
|
27396
|
+
items: [...page, ...deepLinked ? [deepLinked] : []],
|
|
27397
|
+
nextCursor
|
|
27333
27398
|
});
|
|
27334
27399
|
}
|
|
27335
|
-
/**
|
|
27336
|
-
* Each group's newest instances, for the table's expanded rows.
|
|
27337
|
-
*
|
|
27338
|
-
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27339
|
-
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27340
|
-
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27341
|
-
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27342
|
-
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27343
|
-
* store while the answer does not.
|
|
27344
|
-
*
|
|
27345
|
-
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27346
|
-
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27347
|
-
* which is already the order the page wants, and keeps rows per rule until
|
|
27348
|
-
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27349
|
-
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27350
|
-
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27351
|
-
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27352
|
-
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27353
|
-
* store with many firing rules widens it. The bound that DOES hold
|
|
27354
|
-
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27355
|
-
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27356
|
-
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27357
|
-
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27358
|
-
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27359
|
-
* everything in scope with a block sort of the id tie-break only, never a
|
|
27360
|
-
* sort of the scope, which is still that floor.
|
|
27361
|
-
*
|
|
27362
|
-
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27363
|
-
* run without a shared snapshot, so a capture landing between them can add a
|
|
27364
|
-
* rule here that has no counts there, and the counts are what the group is
|
|
27365
|
-
* built from.
|
|
27366
|
-
*/
|
|
27367
|
-
previewRows(aggregates, scope) {
|
|
27368
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
27369
|
-
let remaining = 0;
|
|
27370
|
-
for (const [ruleId, agg] of aggregates) {
|
|
27371
|
-
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27372
|
-
wanted.set(ruleId, n);
|
|
27373
|
-
remaining += n;
|
|
27374
|
-
}
|
|
27375
|
-
const rows = [];
|
|
27376
|
-
if (remaining === 0) return rows;
|
|
27377
|
-
const { sql, params } = this.findingScanSql(scope);
|
|
27378
|
-
const taken = /* @__PURE__ */ new Map();
|
|
27379
|
-
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27380
|
-
const want = wanted.get(r.rule_id);
|
|
27381
|
-
if (want === void 0) continue;
|
|
27382
|
-
const have = taken.get(r.rule_id) ?? 0;
|
|
27383
|
-
if (have >= want) continue;
|
|
27384
|
-
taken.set(r.rule_id, have + 1);
|
|
27385
|
-
rows.push(r);
|
|
27386
|
-
remaining -= 1;
|
|
27387
|
-
if (remaining === 0) break;
|
|
27388
|
-
}
|
|
27389
|
-
return rows;
|
|
27390
|
-
}
|
|
27391
27400
|
/**
|
|
27392
27401
|
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27393
27402
|
*
|
|
@@ -27414,6 +27423,33 @@ var SqliteFindingsRepository = class {
|
|
|
27414
27423
|
yield toFlatFindingRow(r);
|
|
27415
27424
|
}
|
|
27416
27425
|
}
|
|
27426
|
+
/**
|
|
27427
|
+
* One finding by its own id, or null when no such row exists.
|
|
27428
|
+
*
|
|
27429
|
+
* A primary-key seek on `inspection_findings`, so its cost does not grow with
|
|
27430
|
+
* the store — and, unlike anything derived from a list page, it resolves a
|
|
27431
|
+
* finding of ANY age. That is what the Findings page's one-shot `?finding=`
|
|
27432
|
+
* deep link needs: the id it carries may name a finding thousands of rows
|
|
27433
|
+
* older than anything a first page holds.
|
|
27434
|
+
*
|
|
27435
|
+
* Deliberately UNFILTERED — no capture-kind, session or time predicate. It
|
|
27436
|
+
* RESOLVES an id; whether that row would survive the list's current filters is
|
|
27437
|
+
* a different question, and hiding the target because a filter excludes it is
|
|
27438
|
+
* worse than showing it.
|
|
27439
|
+
*
|
|
27440
|
+
* `groupId` on the result IS the rule id, so this one read answers both "which
|
|
27441
|
+
* type should the list select?" and "what does the drawer show?".
|
|
27442
|
+
*/
|
|
27443
|
+
findingInstance(id) {
|
|
27444
|
+
const row = this.db.prepare(
|
|
27445
|
+
`SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27446
|
+
FROM inspection_findings f
|
|
27447
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27448
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27449
|
+
WHERE f.id = ?`
|
|
27450
|
+
).get(id);
|
|
27451
|
+
return Promise.resolve(row === void 0 ? null : toInstanceDetail(toFlatFindingRow(row)));
|
|
27452
|
+
}
|
|
27417
27453
|
/**
|
|
27418
27454
|
* The one statement both instance-level scans run: every finding in scope,
|
|
27419
27455
|
* joined to its event and definition, newest first.
|
|
@@ -27447,17 +27483,7 @@ var SqliteFindingsRepository = class {
|
|
|
27447
27483
|
conditions.push("e.started_at >= ?");
|
|
27448
27484
|
params.push(isoToEpochMillis(scope.from));
|
|
27449
27485
|
}
|
|
27450
|
-
const sql = `SELECT
|
|
27451
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
27452
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27453
|
-
e.started_at AS occurred_at,
|
|
27454
|
-
e.source_tool AS source_tool,
|
|
27455
|
-
e.repo AS repo,
|
|
27456
|
-
e.file_path AS file,
|
|
27457
|
-
e.tool_name AS tool_name,
|
|
27458
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27459
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27460
|
-
${latestResolutionStatusSql("f")} AS latest_status
|
|
27486
|
+
const sql = `SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27461
27487
|
FROM audit_events e
|
|
27462
27488
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27463
27489
|
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -27471,6 +27497,26 @@ var SqliteFindingsRepository = class {
|
|
|
27471
27497
|
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27472
27498
|
const rows = this.db.prepare(
|
|
27473
27499
|
`SELECT rule_id,
|
|
27500
|
+
-- BARE columns beside max(latest_at), which is deliberate and
|
|
27501
|
+
-- is SQLite's documented behaviour: with a single min()/max()
|
|
27502
|
+
-- in an aggregate query, every bare column takes its value from
|
|
27503
|
+
-- the row that produced the extremum. So these are the severity
|
|
27504
|
+
-- and category of the definition whose finding is NEWEST, which
|
|
27505
|
+
-- is what the row-based build they replaced read off its first
|
|
27506
|
+
-- (newest-first) row.
|
|
27507
|
+
--
|
|
27508
|
+
-- min() is WRONG here and was the defect: inspection_definitions
|
|
27509
|
+
-- holds one row per rule VERSION (see its writer \u2014 a version bump
|
|
27510
|
+
-- mints a new row), so a rule whose severity moved between
|
|
27511
|
+
-- versions has several, and min() picks the ALPHABETICALLY
|
|
27512
|
+
-- smallest \u2014 'low' over 'medium', but 'critical' over 'high'.
|
|
27513
|
+
-- That is arbitrary in direction, and it feeds the badge, the
|
|
27514
|
+
-- filter, the facet counts and the primary sort key.
|
|
27515
|
+
--
|
|
27516
|
+
-- Adding a second min()/max() aggregate here would make these
|
|
27517
|
+
-- bare columns ambiguous again; keep max(latest_at) the only one.
|
|
27518
|
+
severity,
|
|
27519
|
+
category,
|
|
27474
27520
|
sum(tuple_count) AS instance_count,
|
|
27475
27521
|
max(latest_at) AS latest_at,
|
|
27476
27522
|
group_concat(source_tools) AS source_tools,
|
|
@@ -27481,6 +27527,14 @@ var SqliteFindingsRepository = class {
|
|
|
27481
27527
|
group_concat(tool_names) AS tool_names
|
|
27482
27528
|
FROM (
|
|
27483
27529
|
SELECT d.rule_id AS rule_id,
|
|
27530
|
+
-- Severity and category are columns of the DEFINITION, and
|
|
27531
|
+
-- a rule can have SEVERAL definitions (one per version), so
|
|
27532
|
+
-- these are grouped on below and resolved to the newest
|
|
27533
|
+
-- firing version by the outer query's bare-column select.
|
|
27534
|
+
-- They ride the aggregate because the type build has no rows
|
|
27535
|
+
-- to read them off \u2014 see buildFindingTypes.
|
|
27536
|
+
d.severity AS severity,
|
|
27537
|
+
d.category AS category,
|
|
27484
27538
|
e.event_type || '${TUPLE_SEP}' ||
|
|
27485
27539
|
(CASE WHEN f.finding_key IS NULL THEN '' ELSE 'k' END) || '${TUPLE_SEP}' ||
|
|
27486
27540
|
coalesce(latest.status, '') AS status_tuple,
|
|
@@ -27495,7 +27549,7 @@ var SqliteFindingsRepository = class {
|
|
|
27495
27549
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
27496
27550
|
ON latest.finding_key = f.finding_key
|
|
27497
27551
|
${scope.predicate}
|
|
27498
|
-
GROUP BY d.rule_id, status_tuple
|
|
27552
|
+
GROUP BY d.rule_id, d.severity, d.category, status_tuple
|
|
27499
27553
|
)
|
|
27500
27554
|
GROUP BY rule_id`
|
|
27501
27555
|
).all(scope.params);
|
|
@@ -27504,6 +27558,8 @@ var SqliteFindingsRepository = class {
|
|
|
27504
27558
|
r.rule_id,
|
|
27505
27559
|
{
|
|
27506
27560
|
instanceCount: r.instance_count,
|
|
27561
|
+
severity: r.severity,
|
|
27562
|
+
category: r.category,
|
|
27507
27563
|
sourceTools: splitConcat(r.source_tools),
|
|
27508
27564
|
actionsTaken: splitConcat(r.actions_taken),
|
|
27509
27565
|
statusInputs: splitConcat(r.status_inputs).map((tuple2) => {
|
|
@@ -27520,7 +27576,7 @@ var SqliteFindingsRepository = class {
|
|
|
27520
27576
|
latestDetectedAt: epochMillisToIso(r.latest_at),
|
|
27521
27577
|
// Free text only — joined and substring-matched, so group_concat's
|
|
27522
27578
|
// commas need no unpicking (a repo/path containing one still matches).
|
|
27523
|
-
// Left undefined (not '') when unfetched, so
|
|
27579
|
+
// Left undefined (not '') when unfetched, so buildFindingTypes can
|
|
27524
27580
|
// tell "no q this request" from "a group with no repo/file at all"
|
|
27525
27581
|
// and skip priming a haystack nothing will read.
|
|
27526
27582
|
...withSearchText ? {
|
|
@@ -27672,6 +27728,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27672
27728
|
this.markOwedStmt = db.prepare(
|
|
27673
27729
|
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27674
27730
|
);
|
|
27731
|
+
this.markCaptureBacklogOwedStmt = db.prepare(
|
|
27732
|
+
`UPDATE audit_events SET outbox_owed = 1
|
|
27733
|
+
WHERE synced_at IS NULL
|
|
27734
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27735
|
+
AND started_at < :before`
|
|
27736
|
+
);
|
|
27675
27737
|
this.stampStmt = db.prepare(
|
|
27676
27738
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27677
27739
|
);
|
|
@@ -27720,7 +27782,9 @@ var SqliteHistorySyncRepository = class {
|
|
|
27720
27782
|
);
|
|
27721
27783
|
this.disownCapturesStmt = db.prepare(
|
|
27722
27784
|
`UPDATE audit_events SET outbox_owed = NULL
|
|
27723
|
-
WHERE outbox_owed IS NOT NULL
|
|
27785
|
+
WHERE outbox_owed IS NOT NULL
|
|
27786
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27787
|
+
AND started_at < :attachedAt`
|
|
27724
27788
|
);
|
|
27725
27789
|
this.rearmStmt = db.prepare(
|
|
27726
27790
|
`UPDATE audit_events SET synced_at = NULL
|
|
@@ -27796,6 +27860,7 @@ var SqliteHistorySyncRepository = class {
|
|
|
27796
27860
|
freezeBoundaryStmt;
|
|
27797
27861
|
captureRowsStmt;
|
|
27798
27862
|
markOwedStmt;
|
|
27863
|
+
markCaptureBacklogOwedStmt;
|
|
27799
27864
|
captureSkipCountStmt;
|
|
27800
27865
|
disownCapturesStmt;
|
|
27801
27866
|
partitionStmt;
|
|
@@ -27859,6 +27924,23 @@ var SqliteHistorySyncRepository = class {
|
|
|
27859
27924
|
markCaptureOwed(id) {
|
|
27860
27925
|
this.markOwedStmt.run({ id });
|
|
27861
27926
|
}
|
|
27927
|
+
/**
|
|
27928
|
+
* Mark every capture already on disk as owed, as of `before`.
|
|
27929
|
+
*
|
|
27930
|
+
* The consent-time backfill, called once from `aka attach` when a human
|
|
27931
|
+
* grants existing-history consent — never from an ongoing drain pass, and
|
|
27932
|
+
* never inferred from a boundary that could later move. `before` is the
|
|
27933
|
+
* caller's own "now" at the moment consent was granted, so what this marks
|
|
27934
|
+
* is exactly the backlog the consent prompt already counted, not whatever a
|
|
27935
|
+
* later re-attach or key rotation might widen it to.
|
|
27936
|
+
*
|
|
27937
|
+
* Returns how many rows matched, for the caller to log or test against. Not a
|
|
27938
|
+
* count of NEWLY marked rows — a row still unsynced from an earlier call
|
|
27939
|
+
* matches again and is counted again, the same as `UPDATE`'s own `changes`.
|
|
27940
|
+
*/
|
|
27941
|
+
markCaptureBacklogOwed(before) {
|
|
27942
|
+
return Number(this.markCaptureBacklogOwedStmt.run({ before }).changes);
|
|
27943
|
+
}
|
|
27862
27944
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27863
27945
|
markSynced(ids, atMs) {
|
|
27864
27946
|
this.stampAll(ids, atMs);
|
|
@@ -27975,15 +28057,42 @@ var SqliteHistorySyncRepository = class {
|
|
|
27975
28057
|
*
|
|
27976
28058
|
* Delivery is a fact about ONE recipient: rows sent to the deployment a
|
|
27977
28059
|
* machine has just left are undelivered as far as the new one is concerned.
|
|
27978
|
-
* All
|
|
27979
|
-
* attributed to the wrong deployment,
|
|
28060
|
+
* All four in one transaction, so a crash between them cannot leave stamps
|
|
28061
|
+
* attributed to the wrong deployment, a boundary that belongs to another, or
|
|
28062
|
+
* a disown with no re-mark to follow it.
|
|
27980
28063
|
*
|
|
27981
28064
|
* The boundary is written HERE and only here, which is what freezes it: a
|
|
27982
28065
|
* re-attach to the SAME deployment (a key rotation) leaves the fingerprint
|
|
27983
28066
|
* unchanged, so this never runs and the backlog does not widen back over rows
|
|
27984
28067
|
* the live path has since delivered.
|
|
28068
|
+
*
|
|
28069
|
+
* `backfillCapturesBefore` is the caller's OWN "now" at the instant a human
|
|
28070
|
+
* granted existing-history consent for the deployment this call is arming —
|
|
28071
|
+
* a DIFFERENT instant from `backlogBefore`: the re-mark boundary is the GRANT
|
|
28072
|
+
* instant, `backlogBefore` is the ATTACH instant, and the two can be far
|
|
28073
|
+
* apart. Passed only when that grant is valid, since this method has no way
|
|
28074
|
+
* to check consent itself and must not mark a row owed for a machine that
|
|
28075
|
+
* never agreed to it. Applied AFTER the disown above, in the SAME
|
|
28076
|
+
* transaction: what the disown clears is every marker below `backlogBefore`,
|
|
28077
|
+
* which includes this deployment's OWN pre-attach rows — `aka attach` calls
|
|
28078
|
+
* `seedCaptureBacklogOwed` at the attach instant, so every row it marks sits
|
|
28079
|
+
* on the cleared side of that bound — and the re-mark in the same
|
|
28080
|
+
* transaction is what puts those rows back. A crash between the two cannot
|
|
28081
|
+
* strand the ledger disowned with nothing re-marked — the transaction either
|
|
28082
|
+
* lands whole or not at all, and a fingerprint mismatch that has not yet
|
|
28083
|
+
* committed re-enters this method on the very next pass. Omit it (the
|
|
28084
|
+
* structural-only tests do) to exercise the disown in isolation.
|
|
28085
|
+
*
|
|
28086
|
+
* The disown is bounded by `backlogBefore`, which is what keeps it from
|
|
28087
|
+
* touching a marker the NEW deployment's OWN live path has already set: B's
|
|
28088
|
+
* live path can mark a capture owed from the moment `aka attach` writes the
|
|
28089
|
+
* descriptor, before the drain's first pass ever reaches this method, and
|
|
28090
|
+
* such a row sits at or after the bound rather than below it. What keeps the
|
|
28091
|
+
* disown from eating THIS SAME CALL's own re-mark is the order, not the
|
|
28092
|
+
* bound — disown runs first, re-mark second, both inside the one
|
|
28093
|
+
* transaction above.
|
|
27985
28094
|
*/
|
|
27986
|
-
rearmFor(fingerprint, backlogBefore) {
|
|
28095
|
+
rearmFor(fingerprint, backlogBefore, backfillCapturesBefore) {
|
|
27987
28096
|
this.ensureRowStmt.run();
|
|
27988
28097
|
withTransaction(
|
|
27989
28098
|
this.db,
|
|
@@ -27991,7 +28100,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27991
28100
|
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27992
28101
|
this.rearmStmt.run();
|
|
27993
28102
|
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
27994
|
-
this.disownCapturesStmt.run();
|
|
28103
|
+
this.disownCapturesStmt.run({ attachedAt: backlogBefore });
|
|
28104
|
+
}
|
|
28105
|
+
if (backfillCapturesBefore !== void 0) {
|
|
28106
|
+
this.markCaptureBacklogOwedStmt.run({ before: backfillCapturesBefore });
|
|
27995
28107
|
}
|
|
27996
28108
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27997
28109
|
},
|
|
@@ -28268,7 +28380,8 @@ function managedSettingsPaths(platform2 = process.platform) {
|
|
|
28268
28380
|
}
|
|
28269
28381
|
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28270
28382
|
}
|
|
28271
|
-
|
|
28383
|
+
var testOnlyManagedPaths = null;
|
|
28384
|
+
function readManagedSettings(paths = testOnlyManagedPaths ?? managedSettingsPaths()) {
|
|
28272
28385
|
for (const path of paths) {
|
|
28273
28386
|
let text;
|
|
28274
28387
|
try {
|
|
@@ -30748,7 +30861,7 @@ function toUtcDateString(ms) {
|
|
|
30748
30861
|
return new Date(ms).toISOString().slice(0, 10);
|
|
30749
30862
|
}
|
|
30750
30863
|
function isTimeseriesSeverity(s) {
|
|
30751
|
-
return s === "critical" || s === "high" || s === "medium";
|
|
30864
|
+
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
30752
30865
|
}
|
|
30753
30866
|
var SqliteSecurityRepository = class {
|
|
30754
30867
|
constructor(db, now = () => Date.now()) {
|
|
@@ -30877,12 +30990,16 @@ var SqliteSecurityRepository = class {
|
|
|
30877
30990
|
const now = this.now();
|
|
30878
30991
|
const windowStart = startOfUtcDay2(now) - (lenDays - 1) * DAY_MS4;
|
|
30879
30992
|
const rows = this.findingsInRange(windowStart, now);
|
|
30880
|
-
const points = Array.from(
|
|
30881
|
-
|
|
30882
|
-
|
|
30883
|
-
|
|
30884
|
-
|
|
30885
|
-
|
|
30993
|
+
const points = Array.from(
|
|
30994
|
+
{ length: numBuckets },
|
|
30995
|
+
(_, i) => ({
|
|
30996
|
+
timestamp: toUtcDateString(windowStart + i * bucketMs),
|
|
30997
|
+
critical: 0,
|
|
30998
|
+
high: 0,
|
|
30999
|
+
medium: 0,
|
|
31000
|
+
low: 0
|
|
31001
|
+
})
|
|
31002
|
+
);
|
|
30886
31003
|
for (const r of rows) {
|
|
30887
31004
|
const idx = Math.floor((r.occurredAt - windowStart) / bucketMs);
|
|
30888
31005
|
const bucket = points[idx];
|
|
@@ -31100,6 +31217,7 @@ var SqliteSecurityRepository = class {
|
|
|
31100
31217
|
`SELECT f.finding_key AS finding_key,
|
|
31101
31218
|
d.rule_id AS rule_id,
|
|
31102
31219
|
d.severity AS severity,
|
|
31220
|
+
e.repo AS repo,
|
|
31103
31221
|
e.file_path AS path,
|
|
31104
31222
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
31105
31223
|
latest.resolved_at AS latest_resolved_at
|
|
@@ -31119,6 +31237,7 @@ var SqliteSecurityRepository = class {
|
|
|
31119
31237
|
const items = rows.map((r) => ({
|
|
31120
31238
|
findingKey: r.finding_key,
|
|
31121
31239
|
ruleId: r.rule_id,
|
|
31240
|
+
repo: r.repo ?? "",
|
|
31122
31241
|
severity: r.severity,
|
|
31123
31242
|
path: r.path ?? "",
|
|
31124
31243
|
resolvedAt: new Date(r.latest_resolved_at).toISOString(),
|
|
@@ -31128,13 +31247,66 @@ var SqliteSecurityRepository = class {
|
|
|
31128
31247
|
}));
|
|
31129
31248
|
return Promise.resolve({ items });
|
|
31130
31249
|
}
|
|
31250
|
+
/**
|
|
31251
|
+
* Per-rule tallies of the findings that are still OPEN, whole-store.
|
|
31252
|
+
*
|
|
31253
|
+
* Scoped by status rather than by time, because the card this feeds is a to-do
|
|
31254
|
+
* list: a secret committed three weeks ago and never rotated is still the most
|
|
31255
|
+
* important thing to fix, and any window hides it. It carried a "newest N
|
|
31256
|
+
* findings" cap and then a range; the first meant a different span on every
|
|
31257
|
+
* machine, and the second reported "no recommendations" over live exposure.
|
|
31258
|
+
*
|
|
31259
|
+
* `open` mirrors `deriveFindingStatus` — at-rest, minus resolved and dismissed —
|
|
31260
|
+
* so a row's count is exactly what `?status=open&type=<rule>` returns. Note that
|
|
31261
|
+
* is NOT `severitySummary`'s `openAtRest`, which keeps dismissed findings (a
|
|
31262
|
+
* dismissal is a judgement, not a remediation) and drops untracked legacy rows.
|
|
31263
|
+
* The two answer different questions and only this one has to match a link.
|
|
31264
|
+
*
|
|
31265
|
+
* Aggregated in SQL: the result is O(distinct rule × category × severity), so a
|
|
31266
|
+
* whole-store scope costs a grouped scan rather than a row per finding.
|
|
31267
|
+
*/
|
|
31268
|
+
recommendationInputs() {
|
|
31269
|
+
const rows = allRows(
|
|
31270
|
+
this.db.prepare(
|
|
31271
|
+
`SELECT d.rule_id AS rule_id,
|
|
31272
|
+
d.category AS category,
|
|
31273
|
+
d.severity AS severity,
|
|
31274
|
+
COUNT(*) AS count
|
|
31275
|
+
FROM inspection_findings f
|
|
31276
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31277
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
31278
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
31279
|
+
ON latest.finding_key = f.finding_key
|
|
31280
|
+
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
31281
|
+
AND e.event_type = 'code_change'
|
|
31282
|
+
AND (
|
|
31283
|
+
f.finding_key IS NULL
|
|
31284
|
+
OR latest.status IS NULL
|
|
31285
|
+
OR latest.status NOT IN ('resolved', 'dismissed')
|
|
31286
|
+
)
|
|
31287
|
+
GROUP BY d.rule_id, d.category, d.severity`
|
|
31288
|
+
)
|
|
31289
|
+
);
|
|
31290
|
+
return Promise.resolve(
|
|
31291
|
+
rows.map((r) => ({
|
|
31292
|
+
ruleId: r.rule_id,
|
|
31293
|
+
category: r.category,
|
|
31294
|
+
severity: r.severity,
|
|
31295
|
+
count: r.count
|
|
31296
|
+
}))
|
|
31297
|
+
);
|
|
31298
|
+
}
|
|
31131
31299
|
// Findings whose parent event occurred in [fromMs, toMs), with the parent's
|
|
31132
31300
|
// epoch-millis timestamp. started_at is an INTEGER column, so the bounds stay
|
|
31133
31301
|
// numeric and the JS aggregations bucket/split on ms directly.
|
|
31134
31302
|
findingsInRange(fromMs, toMs) {
|
|
31135
31303
|
const rows = allRows(
|
|
31136
31304
|
this.db.prepare(
|
|
31137
|
-
`
|
|
31305
|
+
// `rule_id`/`category` cost nothing extra: inspection_definitions is already
|
|
31306
|
+
// joined for `severity`, so they are two more columns off a row this read
|
|
31307
|
+
// already fetches. They feed the recommended-actions rollup.
|
|
31308
|
+
`SELECT e.started_at AS occurred_at, d.severity AS severity, f.action_taken AS action_taken,
|
|
31309
|
+
d.rule_id AS rule_id, d.category AS category
|
|
31138
31310
|
FROM inspection_findings f
|
|
31139
31311
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31140
31312
|
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -31147,7 +31319,9 @@ var SqliteSecurityRepository = class {
|
|
|
31147
31319
|
return rows.map((r) => ({
|
|
31148
31320
|
occurredAt: r.occurred_at,
|
|
31149
31321
|
severity: r.severity,
|
|
31150
|
-
actionTaken: r.action_taken
|
|
31322
|
+
actionTaken: r.action_taken,
|
|
31323
|
+
ruleId: r.rule_id,
|
|
31324
|
+
category: r.category
|
|
31151
31325
|
}));
|
|
31152
31326
|
}
|
|
31153
31327
|
};
|
|
@@ -32317,8 +32491,45 @@ function openLocalDatabase(dir) {
|
|
|
32317
32491
|
};
|
|
32318
32492
|
}
|
|
32319
32493
|
|
|
32320
|
-
// ../../packages/persistence/src/
|
|
32494
|
+
// ../../packages/persistence/src/egress-wire.ts
|
|
32321
32495
|
import { createHash as createHash3 } from "crypto";
|
|
32496
|
+
function hashProjectKey(projectKey) {
|
|
32497
|
+
return createHash3("sha256").update(projectKey, "utf8").digest("hex");
|
|
32498
|
+
}
|
|
32499
|
+
function toIngestHit(hit) {
|
|
32500
|
+
return {
|
|
32501
|
+
host: hit.host,
|
|
32502
|
+
kind: hit.kind,
|
|
32503
|
+
name: hit.name,
|
|
32504
|
+
category: hit.category,
|
|
32505
|
+
trust: hit.trust,
|
|
32506
|
+
network: hit.network,
|
|
32507
|
+
method: hit.method,
|
|
32508
|
+
transport: hit.transport,
|
|
32509
|
+
url: hit.url,
|
|
32510
|
+
template: hit.template,
|
|
32511
|
+
dataClass: hit.dataClass,
|
|
32512
|
+
site: {
|
|
32513
|
+
file: hit.site.file,
|
|
32514
|
+
line: hit.site.line,
|
|
32515
|
+
dynamic: hit.site.dynamic,
|
|
32516
|
+
vendored: hit.site.vendored
|
|
32517
|
+
}
|
|
32518
|
+
};
|
|
32519
|
+
}
|
|
32520
|
+
function toEgressIngestRequest(input2) {
|
|
32521
|
+
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
32522
|
+
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
32523
|
+
return {
|
|
32524
|
+
projectKey: hashProjectKey(input2.projectKey),
|
|
32525
|
+
project: input2.project,
|
|
32526
|
+
reconcile,
|
|
32527
|
+
hits: hits.map(toIngestHit)
|
|
32528
|
+
};
|
|
32529
|
+
}
|
|
32530
|
+
|
|
32531
|
+
// ../../packages/persistence/src/finding-key.ts
|
|
32532
|
+
import { createHash as createHash4 } from "crypto";
|
|
32322
32533
|
|
|
32323
32534
|
// ../../packages/persistence/src/fingerprint.ts
|
|
32324
32535
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -32359,14 +32570,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
32359
32570
|
return parseKeyFile(raw);
|
|
32360
32571
|
}
|
|
32361
32572
|
|
|
32362
|
-
// ../../packages/persistence/src/history-
|
|
32573
|
+
// ../../packages/persistence/src/history-backfill.ts
|
|
32363
32574
|
import { existsSync as existsSync4 } from "fs";
|
|
32364
32575
|
import { join as join9 } from "path";
|
|
32576
|
+
|
|
32577
|
+
// ../../packages/persistence/src/history-preview.ts
|
|
32578
|
+
import { existsSync as existsSync5 } from "fs";
|
|
32579
|
+
import { join as join10 } from "path";
|
|
32365
32580
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32366
32581
|
|
|
32367
32582
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
32368
|
-
import { existsSync as
|
|
32369
|
-
import { dirname as dirname3, join as
|
|
32583
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
32584
|
+
import { dirname as dirname3, join as join11, resolve } from "path";
|
|
32370
32585
|
|
|
32371
32586
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
32372
32587
|
import {
|
|
@@ -32381,61 +32596,25 @@ import {
|
|
|
32381
32596
|
import { execFileSync } from "child_process";
|
|
32382
32597
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32383
32598
|
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32384
|
-
import { join as
|
|
32599
|
+
import { join as join12 } from "path";
|
|
32385
32600
|
|
|
32386
32601
|
// ../../packages/persistence/src/vault/vault.ts
|
|
32387
32602
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32388
32603
|
|
|
32389
32604
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32390
|
-
import { existsSync as
|
|
32391
|
-
import { join as
|
|
32605
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
32606
|
+
import { join as join13 } from "path";
|
|
32392
32607
|
var MARKER = "warn-era-capped";
|
|
32393
32608
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32394
32609
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32395
|
-
const marker =
|
|
32396
|
-
if (
|
|
32610
|
+
const marker = join13(dataDir2, MARKER);
|
|
32611
|
+
if (existsSync7(marker)) return { capped: 0, skipped: "already-run" };
|
|
32397
32612
|
const capped = db.policies.capCategoryActions();
|
|
32398
32613
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
32399
32614
|
`, { mode: DATA_FILE_MODE });
|
|
32400
32615
|
return { capped };
|
|
32401
32616
|
}
|
|
32402
32617
|
|
|
32403
|
-
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
32404
|
-
function hashProjectKey(projectKey) {
|
|
32405
|
-
return createHash4("sha256").update(projectKey, "utf8").digest("hex");
|
|
32406
|
-
}
|
|
32407
|
-
function toIngestHit(hit) {
|
|
32408
|
-
return {
|
|
32409
|
-
host: hit.host,
|
|
32410
|
-
kind: hit.kind,
|
|
32411
|
-
name: hit.name,
|
|
32412
|
-
category: hit.category,
|
|
32413
|
-
trust: hit.trust,
|
|
32414
|
-
network: hit.network,
|
|
32415
|
-
method: hit.method,
|
|
32416
|
-
transport: hit.transport,
|
|
32417
|
-
url: hit.url,
|
|
32418
|
-
template: hit.template,
|
|
32419
|
-
dataClass: hit.dataClass,
|
|
32420
|
-
site: {
|
|
32421
|
-
file: hit.site.file,
|
|
32422
|
-
line: hit.site.line,
|
|
32423
|
-
dynamic: hit.site.dynamic,
|
|
32424
|
-
vendored: hit.site.vendored
|
|
32425
|
-
}
|
|
32426
|
-
};
|
|
32427
|
-
}
|
|
32428
|
-
function toEgressIngestRequest(input2) {
|
|
32429
|
-
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
32430
|
-
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
32431
|
-
return {
|
|
32432
|
-
projectKey: hashProjectKey(input2.projectKey),
|
|
32433
|
-
project: input2.project,
|
|
32434
|
-
reconcile,
|
|
32435
|
-
hits: hits.map(toIngestHit)
|
|
32436
|
-
};
|
|
32437
|
-
}
|
|
32438
|
-
|
|
32439
32618
|
// ../../packages/remote/src/http.ts
|
|
32440
32619
|
import { request as httpRequest } from "http";
|
|
32441
32620
|
import { request as httpsRequest } from "https";
|
|
@@ -32715,6 +32894,7 @@ function createRemoteClient(options) {
|
|
|
32715
32894
|
url: url2(ROUTES.shares),
|
|
32716
32895
|
body: JSON.stringify(validated.data)
|
|
32717
32896
|
});
|
|
32897
|
+
if (response.status === 404) throw new RemoteRouteAbsent(ROUTES.shares);
|
|
32718
32898
|
okBody(response);
|
|
32719
32899
|
},
|
|
32720
32900
|
async pollCommand() {
|
|
@@ -32737,19 +32917,51 @@ function createRemoteClient(options) {
|
|
|
32737
32917
|
};
|
|
32738
32918
|
}
|
|
32739
32919
|
|
|
32740
|
-
// ../../packages/
|
|
32920
|
+
// ../../packages/remote/src/failure-kind.ts
|
|
32741
32921
|
function statusOf(err) {
|
|
32742
32922
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
32743
32923
|
const { status } = err;
|
|
32744
32924
|
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
32745
32925
|
return status >= 100 && status <= 599 ? status : null;
|
|
32746
32926
|
}
|
|
32747
|
-
function
|
|
32748
|
-
|
|
32927
|
+
function nameOf(err) {
|
|
32928
|
+
if (typeof err !== "object" || err === null || !("name" in err)) return null;
|
|
32929
|
+
return typeof err.name === "string" ? err.name : null;
|
|
32930
|
+
}
|
|
32931
|
+
function classifyRemoteFailure(err) {
|
|
32932
|
+
switch (nameOf(err)) {
|
|
32933
|
+
case "RemoteRouteAbsent":
|
|
32934
|
+
return "route-absent";
|
|
32935
|
+
case "RemoteRequestInvalid":
|
|
32936
|
+
return "invalid-request";
|
|
32937
|
+
case "RemoteResponseInvalid":
|
|
32938
|
+
return "rejected";
|
|
32939
|
+
default:
|
|
32940
|
+
break;
|
|
32941
|
+
}
|
|
32942
|
+
const status = statusOf(err);
|
|
32943
|
+
if (status === null) return "unreachable";
|
|
32944
|
+
switch (status) {
|
|
32749
32945
|
case 401:
|
|
32750
32946
|
return "unauthorized";
|
|
32751
32947
|
case 403:
|
|
32752
32948
|
return "forbidden";
|
|
32949
|
+
case 429:
|
|
32950
|
+
return "unreachable";
|
|
32951
|
+
case 404:
|
|
32952
|
+
return "unreachable";
|
|
32953
|
+
default:
|
|
32954
|
+
return status >= 400 && status <= 499 ? "rejected" : "unreachable";
|
|
32955
|
+
}
|
|
32956
|
+
}
|
|
32957
|
+
|
|
32958
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
32959
|
+
function classifyFailure(err) {
|
|
32960
|
+
switch (classifyRemoteFailure(err)) {
|
|
32961
|
+
case "unauthorized":
|
|
32962
|
+
return "unauthorized";
|
|
32963
|
+
case "forbidden":
|
|
32964
|
+
return "forbidden";
|
|
32753
32965
|
default:
|
|
32754
32966
|
return "unreachable";
|
|
32755
32967
|
}
|
|
@@ -32772,10 +32984,10 @@ function withTimeout(promise2, ms) {
|
|
|
32772
32984
|
|
|
32773
32985
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
32774
32986
|
import { readFileSync as readFileSync8 } from "fs";
|
|
32775
|
-
import { join as
|
|
32987
|
+
import { join as join14 } from "path";
|
|
32776
32988
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
32777
32989
|
function forwardDropsPath(dataDir2) {
|
|
32778
|
-
return
|
|
32990
|
+
return join14(dataDir2, FORWARD_DROPS_FILENAME);
|
|
32779
32991
|
}
|
|
32780
32992
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
32781
32993
|
if (count <= 0) return;
|
|
@@ -32811,13 +33023,13 @@ function readForwardDrops(dataDir2) {
|
|
|
32811
33023
|
|
|
32812
33024
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
32813
33025
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
32814
|
-
import { readFileSync as
|
|
33026
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
32815
33027
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
32816
|
-
import { join as
|
|
33028
|
+
import { join as join24 } from "path";
|
|
32817
33029
|
|
|
32818
33030
|
// ../../packages/plugin-sdk/src/config.ts
|
|
32819
|
-
import { existsSync as
|
|
32820
|
-
import { join as
|
|
33031
|
+
import { existsSync as existsSync8 } from "fs";
|
|
33032
|
+
import { join as join15 } from "path";
|
|
32821
33033
|
|
|
32822
33034
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
32823
33035
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -32871,8 +33083,8 @@ function resolveProvider() {
|
|
|
32871
33083
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32872
33084
|
try {
|
|
32873
33085
|
ensureLayoutDirSync(base);
|
|
32874
|
-
const settingsFile =
|
|
32875
|
-
if (
|
|
33086
|
+
const settingsFile = join15(settingsDir(base), "settings.json");
|
|
33087
|
+
if (existsSync8(settingsFile)) tightenFile(settingsFile);
|
|
32876
33088
|
} catch {
|
|
32877
33089
|
}
|
|
32878
33090
|
migrateLegacyLayout(base);
|
|
@@ -32897,7 +33109,7 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32897
33109
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32898
33110
|
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32899
33111
|
import { homedir as homedir2 } from "os";
|
|
32900
|
-
import { basename as basename3, join as
|
|
33112
|
+
import { basename as basename3, join as join17 } from "path";
|
|
32901
33113
|
|
|
32902
33114
|
// ../../packages/detections/src/egress/registry.ts
|
|
32903
33115
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -35680,24 +35892,20 @@ function bundledDetections() {
|
|
|
35680
35892
|
}
|
|
35681
35893
|
|
|
35682
35894
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
35683
|
-
import { existsSync as
|
|
35684
|
-
import { basename as basename2, dirname as dirname4, isAbsolute, join as
|
|
35895
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
35896
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join16, sep as sep2 } from "path";
|
|
35685
35897
|
|
|
35686
35898
|
// ../../packages/plugin-sdk/src/events.ts
|
|
35687
35899
|
import { createHash as createHash5, randomUUID as randomUUID13 } from "crypto";
|
|
35688
35900
|
|
|
35689
35901
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
35690
|
-
import { existsSync as
|
|
35902
|
+
import { existsSync as existsSync10 } from "fs";
|
|
35691
35903
|
import { fileURLToPath } from "url";
|
|
35692
35904
|
import { Worker } from "worker_threads";
|
|
35693
35905
|
|
|
35694
|
-
// ../../packages/plugin-sdk/src/
|
|
35695
|
-
|
|
35696
|
-
import {
|
|
35697
|
-
import { join as join17 } from "path";
|
|
35698
|
-
|
|
35699
|
-
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
35700
|
-
import { arch, hostname as hostname4, platform, release } from "os";
|
|
35906
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
35907
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
35908
|
+
import { join as join19 } from "path";
|
|
35701
35909
|
|
|
35702
35910
|
// ../../packages/plugin-sdk/src/model-governance.ts
|
|
35703
35911
|
import {
|
|
@@ -35705,24 +35913,50 @@ import {
|
|
|
35705
35913
|
fstatSync,
|
|
35706
35914
|
mkdirSync as mkdirSync2,
|
|
35707
35915
|
openSync as openSync2,
|
|
35708
|
-
readFileSync as
|
|
35916
|
+
readFileSync as readFileSync11,
|
|
35709
35917
|
readSync,
|
|
35710
35918
|
writeFileSync as writeFileSync5
|
|
35711
35919
|
} from "fs";
|
|
35712
35920
|
import { join as join18 } from "path";
|
|
35713
35921
|
var TAIL_BYTES = 256 * 1024;
|
|
35714
35922
|
|
|
35923
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
35924
|
+
var HOST_FEATURE = {
|
|
35925
|
+
ModelSwitch: "model-switch",
|
|
35926
|
+
VaultPointerDisplay: "vault-pointer-display"
|
|
35927
|
+
};
|
|
35928
|
+
var HOST_FLOORS = {
|
|
35929
|
+
[HOST_FEATURE.ModelSwitch]: {
|
|
35930
|
+
label: "model-switch protection",
|
|
35931
|
+
hookEvents: ["PreModelSwitch", "PostModelSwitch"],
|
|
35932
|
+
since: "2.1.251"
|
|
35933
|
+
},
|
|
35934
|
+
[HOST_FEATURE.VaultPointerDisplay]: {
|
|
35935
|
+
label: "vault pointer display",
|
|
35936
|
+
hookEvents: ["MessageDisplay"],
|
|
35937
|
+
since: "2.1.152"
|
|
35938
|
+
}
|
|
35939
|
+
};
|
|
35940
|
+
|
|
35941
|
+
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
35942
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
35943
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
35944
|
+
import { join as join20 } from "path";
|
|
35945
|
+
|
|
35946
|
+
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
35947
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
35948
|
+
|
|
35715
35949
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
35716
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
35717
|
-
import { join as
|
|
35950
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
35951
|
+
import { join as join21 } from "path";
|
|
35718
35952
|
|
|
35719
35953
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
35720
35954
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
35721
35955
|
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
35722
35956
|
|
|
35723
35957
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
35724
|
-
import { existsSync as
|
|
35725
|
-
import { basename as basename5, join as
|
|
35958
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
|
|
35959
|
+
import { basename as basename5, join as join22 } from "path";
|
|
35726
35960
|
|
|
35727
35961
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
35728
35962
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -35758,7 +35992,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
35758
35992
|
|
|
35759
35993
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
35760
35994
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
35761
|
-
import { join as
|
|
35995
|
+
import { join as join23 } from "path";
|
|
35762
35996
|
|
|
35763
35997
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
35764
35998
|
function isInvalidRequest(err) {
|
|
@@ -35798,7 +36032,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
35798
36032
|
}
|
|
35799
36033
|
function createForwardPolicy(deps) {
|
|
35800
36034
|
const now = deps.now ?? (() => Date.now());
|
|
35801
|
-
const file2 =
|
|
36035
|
+
const file2 = join24(deps.dir, STATE_FILENAME);
|
|
35802
36036
|
let state = null;
|
|
35803
36037
|
let loading = null;
|
|
35804
36038
|
async function readState() {
|
|
@@ -36520,8 +36754,8 @@ function toolAuditEvent(input2) {
|
|
|
36520
36754
|
}
|
|
36521
36755
|
|
|
36522
36756
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
36523
|
-
import { readFileSync as
|
|
36524
|
-
import { join as
|
|
36757
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
36758
|
+
import { join as join25 } from "path";
|
|
36525
36759
|
|
|
36526
36760
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
36527
36761
|
import { createHash as createHash6 } from "crypto";
|
|
@@ -36538,7 +36772,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
36538
36772
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
36539
36773
|
|
|
36540
36774
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
36541
|
-
import { readFileSync as
|
|
36775
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
36542
36776
|
function createPluginBlock(build, policyStore) {
|
|
36543
36777
|
return async () => {
|
|
36544
36778
|
const cached2 = await policyStore.read();
|
|
@@ -36557,7 +36791,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
36557
36791
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
36558
36792
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
36559
36793
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
36560
|
-
import { join as
|
|
36794
|
+
import { join as join26 } from "path";
|
|
36561
36795
|
|
|
36562
36796
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
36563
36797
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -36581,7 +36815,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
36581
36815
|
|
|
36582
36816
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
36583
36817
|
function createPolicyStore(dir = dataDir()) {
|
|
36584
|
-
const file2 =
|
|
36818
|
+
const file2 = join26(dir, "policy-cache.json");
|
|
36585
36819
|
async function read() {
|
|
36586
36820
|
try {
|
|
36587
36821
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -36812,11 +37046,11 @@ function readStorePosture(dbPath2) {
|
|
|
36812
37046
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
36813
37047
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
36814
37048
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
36815
|
-
import { join as
|
|
37049
|
+
import { join as join27 } from "path";
|
|
36816
37050
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
36817
37051
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
36818
|
-
const file2 =
|
|
36819
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
37052
|
+
const file2 = join27(dir, "posture-state.json");
|
|
37053
|
+
const legacyFile = legacyDir === void 0 ? null : join27(legacyDir, "posture-state.json");
|
|
36820
37054
|
async function persist(state) {
|
|
36821
37055
|
await ensureDataDir(dir);
|
|
36822
37056
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -36884,8 +37118,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
36884
37118
|
}
|
|
36885
37119
|
|
|
36886
37120
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
36887
|
-
import { readFileSync as
|
|
36888
|
-
import { join as
|
|
37121
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
37122
|
+
import { join as join28 } from "path";
|
|
36889
37123
|
|
|
36890
37124
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
36891
37125
|
var REFUSAL_LINES = {
|
|
@@ -37381,7 +37615,7 @@ async function readStdin() {
|
|
|
37381
37615
|
|
|
37382
37616
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
37383
37617
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
37384
|
-
import { join as
|
|
37618
|
+
import { join as join29 } from "path";
|
|
37385
37619
|
|
|
37386
37620
|
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
37387
37621
|
var RANK = Object.fromEntries(
|
|
@@ -37389,9 +37623,9 @@ var RANK = Object.fromEntries(
|
|
|
37389
37623
|
);
|
|
37390
37624
|
|
|
37391
37625
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
37392
|
-
import { mkdtempSync, readFileSync as
|
|
37626
|
+
import { mkdtempSync, readFileSync as readFileSync19, rmdirSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
37393
37627
|
import { tmpdir } from "os";
|
|
37394
|
-
import { basename as basename6, dirname as dirname6, join as
|
|
37628
|
+
import { basename as basename6, dirname as dirname6, join as join30 } from "path";
|
|
37395
37629
|
var SuppressionEntrySchema = external_exports.object({
|
|
37396
37630
|
ruleId: external_exports.string(),
|
|
37397
37631
|
category: DetectionCategory,
|
|
@@ -37477,11 +37711,6 @@ var SEVERITY_GLYPH = {
|
|
|
37477
37711
|
low: SHADE.light
|
|
37478
37712
|
};
|
|
37479
37713
|
var CATEGORY_ORDER2 = DetectionCategory.options;
|
|
37480
|
-
function healthScore(summary) {
|
|
37481
|
-
const handled = summary.byAction.block + summary.byAction.redact + summary.byAction.warn;
|
|
37482
|
-
const handledRatio = summary.findings === 0 ? 1 : handled / summary.findings;
|
|
37483
|
-
return Math.round(100 * (0.6 * summary.coverage + 0.4 * handledRatio));
|
|
37484
|
-
}
|
|
37485
37714
|
function renderStatusBar(s, opts = {}) {
|
|
37486
37715
|
const u = s.unreviewed;
|
|
37487
37716
|
if (opts.color !== true) {
|
|
@@ -37500,13 +37729,6 @@ function renderStatusBar(s, opts = {}) {
|
|
|
37500
37729
|
function renderStatusLine(summary) {
|
|
37501
37730
|
return renderStatusBar(findingStatus(summary), { color: true });
|
|
37502
37731
|
}
|
|
37503
|
-
function findingStatus(summary) {
|
|
37504
|
-
return {
|
|
37505
|
-
score: healthScore(summary),
|
|
37506
|
-
unreviewed: { ...summary.bySeverity },
|
|
37507
|
-
openFindings: summary.findings
|
|
37508
|
-
};
|
|
37509
|
-
}
|
|
37510
37732
|
|
|
37511
37733
|
// src/statusline.ts
|
|
37512
37734
|
try {
|