@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/backfill.js
CHANGED
|
@@ -494,9 +494,6 @@ var require_ignore = __commonJS({
|
|
|
494
494
|
// src/backfill.ts
|
|
495
495
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
496
496
|
|
|
497
|
-
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
498
|
-
import { createHash as createHash4 } from "crypto";
|
|
499
|
-
|
|
500
497
|
// ../../packages/persistence/src/attached-derived.ts
|
|
501
498
|
import { rmSync } from "fs";
|
|
502
499
|
import { join } from "path";
|
|
@@ -20748,13 +20745,11 @@ var FindingGroup = external_exports.object({
|
|
|
20748
20745
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20749
20746
|
instances: external_exports.array(FindingInstance),
|
|
20750
20747
|
// Derived from instances' statuses with open-dominates precedence (see
|
|
20751
|
-
//
|
|
20748
|
+
// foldGroupStatus). Undefined only when no instance carries a status.
|
|
20752
20749
|
status: FindingStatus.optional(),
|
|
20753
|
-
// The distinct people across the WHOLE group, not just the
|
|
20754
|
-
//
|
|
20755
|
-
//
|
|
20756
|
-
// instance carries a user, or when the store supplied whole-group folds
|
|
20757
|
-
// without one.
|
|
20750
|
+
// The distinct people across the WHOLE group, not just the instances
|
|
20751
|
+
// carried here. Undefined when no instance carries a user, or when the
|
|
20752
|
+
// store supplied whole-group folds without one.
|
|
20758
20753
|
users: external_exports.array(FindingUser).optional()
|
|
20759
20754
|
}).meta({ id: "FindingGroup" });
|
|
20760
20755
|
var FindingStats = external_exports.object({
|
|
@@ -20783,21 +20778,31 @@ var FindingFacets = external_exports.object({
|
|
|
20783
20778
|
// counted under no value.
|
|
20784
20779
|
status: external_exports.array(FindingFacetItem),
|
|
20785
20780
|
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
20786
|
-
// reads, which can filter by it; the
|
|
20781
|
+
// reads, which can filter by it; the type-level read omits the dimension
|
|
20787
20782
|
// because a group spans tools.
|
|
20788
20783
|
tool: external_exports.array(FindingFacetItem).optional()
|
|
20789
20784
|
}).meta({ id: "FindingFacets" });
|
|
20790
|
-
var
|
|
20791
|
-
|
|
20785
|
+
var FindingTypeSummary = FindingGroup.omit({ instances: true, match: true }).meta({
|
|
20786
|
+
id: "FindingTypeSummary"
|
|
20787
|
+
});
|
|
20788
|
+
var DEFAULT_FINDING_TYPES_LIMIT = 50;
|
|
20789
|
+
var MAX_FINDING_TYPES_LIMIT = 100;
|
|
20790
|
+
var ListFindingTypesQuery = external_exports.object({
|
|
20792
20791
|
// NOTE: severity filters by Severity (critical/high/medium/low), not by
|
|
20793
|
-
// FindingAction.
|
|
20792
|
+
// FindingAction. It narrows TYPES: a type's severity is the one its newest
|
|
20793
|
+
// firing version carries, and this list pages types.
|
|
20794
|
+
//
|
|
20795
|
+
// That is NOT a claim the findings of a type share it. A rule can hold several
|
|
20796
|
+
// definition versions at different severities, so a type kept by this filter
|
|
20797
|
+
// can hold findings that individually do not match — see totals.findings on
|
|
20798
|
+
// ListFindingTypesResponse, which counts them all.
|
|
20794
20799
|
severity: external_exports.array(Severity).optional(),
|
|
20795
20800
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20796
20801
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20797
20802
|
action: external_exports.array(FindingAction).optional(),
|
|
20798
|
-
// Matches a
|
|
20799
|
-
// individual
|
|
20800
|
-
//
|
|
20803
|
+
// Matches a type's DERIVED status (see FindingGroup.status), not its
|
|
20804
|
+
// individual findings' — so a filtered row's status always reads one of the
|
|
20805
|
+
// requested values.
|
|
20801
20806
|
status: external_exports.array(FindingStatus).optional(),
|
|
20802
20807
|
q: external_exports.string().optional(),
|
|
20803
20808
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
@@ -20807,23 +20812,37 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
20807
20812
|
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
20808
20813
|
// means all time — this list has no default window.
|
|
20809
20814
|
from: external_exports.iso.datetime().optional(),
|
|
20810
|
-
// A
|
|
20811
|
-
//
|
|
20812
|
-
//
|
|
20813
|
-
//
|
|
20814
|
-
//
|
|
20815
|
+
// A RULE id that must appear in the page even when the cursor has already
|
|
20816
|
+
// advanced past its sort position. This is what keeps the selected type
|
|
20817
|
+
// visible in the list once it paginates: the target is appended out of sort
|
|
20818
|
+
// order rather than scanned forward for. Never affects totals, facets or the
|
|
20819
|
+
// cursor. Unlike the grouped read this replaces, it names a rule only — an
|
|
20820
|
+
// instance id is resolved by `findingInstance`, which is a primary-key seek
|
|
20821
|
+
// and so is not bounded by what any page happens to hold.
|
|
20815
20822
|
includeId: external_exports.string().optional(),
|
|
20816
|
-
|
|
20817
|
-
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
20823
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_TYPES_LIMIT).optional(),
|
|
20818
20824
|
cursor: external_exports.string().optional()
|
|
20819
20825
|
});
|
|
20820
|
-
var
|
|
20826
|
+
var ListFindingTypesResponse = external_exports.object({
|
|
20821
20827
|
totals: external_exports.object({
|
|
20828
|
+
// Findings belonging to the matching TYPES — not findings that each match
|
|
20829
|
+
// the filters. The filters here select types, so a type that survives
|
|
20830
|
+
// contributes its whole instanceCount.
|
|
20831
|
+
//
|
|
20832
|
+
// `status` is the one exception, narrowed per finding via
|
|
20833
|
+
// countInstancesByStatus. `severity`, `provider` and `action` are not, so
|
|
20834
|
+
// this can exceed what the instance read reports for the same filters: a
|
|
20835
|
+
// rule whose severity moved between versions is kept on its newest and
|
|
20836
|
+
// still counts its older findings. Narrowing the other three needs
|
|
20837
|
+
// per-dimension counts the aggregate does not carry today.
|
|
20822
20838
|
findings: external_exports.number().int().nonnegative(),
|
|
20823
|
-
|
|
20839
|
+
// Counts TYPES, which is the unit this read pages. The instance read's
|
|
20840
|
+
// own totals count findings; the two deliberately answer different
|
|
20841
|
+
// questions and are never summed.
|
|
20842
|
+
types: external_exports.number().int().nonnegative()
|
|
20824
20843
|
}),
|
|
20825
20844
|
facets: FindingFacets,
|
|
20826
|
-
items: external_exports.array(
|
|
20845
|
+
items: external_exports.array(FindingTypeSummary),
|
|
20827
20846
|
nextCursor: external_exports.string().nullable(),
|
|
20828
20847
|
// Present only on session-scoped queries (`sessionId` set): per ruleId, how
|
|
20829
20848
|
// many times that rule fired in the session's persisted transcript. Findings
|
|
@@ -20831,7 +20850,7 @@ var ListGroupedFindingsResponse = external_exports.object({
|
|
|
20831
20850
|
// every firing, so the two numbers legitimately differ — this map lets a
|
|
20832
20851
|
// session-scoped view show both.
|
|
20833
20852
|
sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
|
|
20834
|
-
}).meta({ id: "
|
|
20853
|
+
}).meta({ id: "ListFindingTypesResponse" });
|
|
20835
20854
|
var ApplyFindingActionRequest = external_exports.object({
|
|
20836
20855
|
// 'quarantined' is system-assigned (see FindingAction) — clients may not set
|
|
20837
20856
|
// it, so it is excluded from the request contract. The mapping helper
|
|
@@ -20861,12 +20880,13 @@ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
|
20861
20880
|
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
20862
20881
|
var ListFindingInstancesQuery = external_exports.object({
|
|
20863
20882
|
severity: external_exports.array(Severity).optional(),
|
|
20864
|
-
// Rule ids, the same vocabulary the
|
|
20883
|
+
// Rule ids, the same vocabulary the types list's `subtype` carries. Pinning
|
|
20884
|
+
// ONE of them is how the master/detail view scopes its right-hand panel.
|
|
20865
20885
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20866
20886
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20867
20887
|
action: external_exports.array(FindingAction).optional(),
|
|
20868
20888
|
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
20869
|
-
// the
|
|
20889
|
+
// the types query's type-level fold.
|
|
20870
20890
|
status: external_exports.array(FindingStatus).optional(),
|
|
20871
20891
|
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
20872
20892
|
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
@@ -20883,37 +20903,47 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
20883
20903
|
});
|
|
20884
20904
|
var ListFindingInstancesResponse = external_exports.object({
|
|
20885
20905
|
// Instances matching the filters across the whole scope, not just this
|
|
20886
|
-
// page — cursor-independent, like the
|
|
20906
|
+
// page — cursor-independent, like the types list's totals.
|
|
20887
20907
|
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
20888
|
-
// Counts in INSTANCES here, where the
|
|
20908
|
+
// Counts in INSTANCES here, where the types response counts types. Each
|
|
20889
20909
|
// dimension still excludes its own filter.
|
|
20890
20910
|
facets: FindingFacets,
|
|
20891
20911
|
items: external_exports.array(FindingInstanceDetail),
|
|
20892
20912
|
nextCursor: external_exports.string().nullable()
|
|
20893
20913
|
}).meta({ id: "ListFindingInstancesResponse" });
|
|
20894
|
-
var
|
|
20895
|
-
//
|
|
20896
|
-
//
|
|
20897
|
-
|
|
20898
|
-
|
|
20899
|
-
|
|
20900
|
-
|
|
20901
|
-
// Folded from the instances' derived statuses with the same
|
|
20902
|
-
// open-dominates precedence a group uses.
|
|
20903
|
-
status: FindingStatus.optional(),
|
|
20904
|
-
// Distinct rules seen at this location, capped — the row shows them as
|
|
20905
|
-
// chips, and the count is what conveys scale.
|
|
20906
|
-
ruleIds: external_exports.array(external_exports.string())
|
|
20907
|
-
}).meta({ id: "FindingLocationFile" });
|
|
20908
|
-
var FindingLocationRepo = external_exports.object({
|
|
20914
|
+
var FindingLocationSummary = external_exports.object({
|
|
20915
|
+
// Opaque, stable, minted from the pair by encodeLocationId. It exists
|
|
20916
|
+
// because a location's identity is two values and a URL param carries one:
|
|
20917
|
+
// `?loc=` names a location the way `?rule=` names a type. Only ever compared
|
|
20918
|
+
// for EQUALITY — the page's selection check, this read's `includeId`, the
|
|
20919
|
+
// client's page dedupe — never decoded, and never a sort key.
|
|
20920
|
+
id: external_exports.string(),
|
|
20909
20921
|
/** Empty when the instances carried no repo attribute. */
|
|
20910
20922
|
repo: external_exports.string(),
|
|
20923
|
+
// Empty when the instances carried no file path (a prompt, or a tool call
|
|
20924
|
+
// with no file attribution). Both halves empty is a real location — usually
|
|
20925
|
+
// the largest one in a store — and is selectable like any other.
|
|
20926
|
+
file: external_exports.string(),
|
|
20911
20927
|
instanceCount: external_exports.number().int().nonnegative(),
|
|
20928
|
+
// The WORST severity present, not the first row's. It is this list's primary
|
|
20929
|
+
// sort key, so it is also what explains why a row is where it is, and it is
|
|
20930
|
+
// how a reader decides what to open without opening everything.
|
|
20912
20931
|
maxSeverity: Severity,
|
|
20913
20932
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20933
|
+
// Folded from the instances' derived statuses with the same open-dominates
|
|
20934
|
+
// precedence a group uses, so it answers "is anything left to do here" and
|
|
20935
|
+
// not much more: a location holding 1 open among 40 resolved reads like one
|
|
20936
|
+
// holding 40 open. That loss is accepted — the panel beside this list
|
|
20937
|
+
// carries each finding's own status, and instanceCount sits next to the
|
|
20938
|
+
// badge.
|
|
20914
20939
|
status: FindingStatus.optional(),
|
|
20915
|
-
|
|
20916
|
-
|
|
20940
|
+
// Every distinct rule seen at this location, UNCAPPED — so the length is a
|
|
20941
|
+
// tally rather than a sample and a row can say how many there are. Bounded
|
|
20942
|
+
// by the ruleset, not by the store. The view bounds what it DISPLAYS.
|
|
20943
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
20944
|
+
}).meta({ id: "FindingLocationSummary" });
|
|
20945
|
+
var DEFAULT_FINDING_LOCATIONS_LIMIT = 50;
|
|
20946
|
+
var MAX_FINDING_LOCATIONS_LIMIT = 100;
|
|
20917
20947
|
var ListFindingLocationsQuery = external_exports.object({
|
|
20918
20948
|
severity: external_exports.array(Severity).optional(),
|
|
20919
20949
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
@@ -20926,18 +20956,42 @@ var ListFindingLocationsQuery = external_exports.object({
|
|
|
20926
20956
|
q: external_exports.string().optional(),
|
|
20927
20957
|
sessionId: external_exports.string().optional(),
|
|
20928
20958
|
from: external_exports.iso.datetime().optional(),
|
|
20929
|
-
|
|
20959
|
+
// A LOCATION id (see FindingLocationSummary.id) that must appear in the page
|
|
20960
|
+
// even when the cursor has already advanced past its sort position — the
|
|
20961
|
+
// counterpart of ListFindingTypesQuery.includeId, and needed far more often
|
|
20962
|
+
// here. Selecting a row pushes the URL, which re-renders the server and resets
|
|
20963
|
+
// the client's page cache to page 0; with distinct (repo, file) pairs running
|
|
20964
|
+
// into the thousands, a selection sitting off page 0 is the ordinary case
|
|
20965
|
+
// rather than a deep-link corner. Never affects totals, facets or the cursor.
|
|
20966
|
+
includeId: external_exports.string().optional(),
|
|
20967
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_LOCATIONS_LIMIT).optional(),
|
|
20968
|
+
cursor: external_exports.string().optional()
|
|
20930
20969
|
});
|
|
20931
20970
|
var ListFindingLocationsResponse = external_exports.object({
|
|
20932
20971
|
totals: external_exports.object({
|
|
20972
|
+
// Findings matching the filters across the whole scope. Unlike the types
|
|
20973
|
+
// read's same-named field this needs no caveat: the filters here narrow
|
|
20974
|
+
// per finding, so this is the sum of every row's instanceCount.
|
|
20933
20975
|
findings: external_exports.number().int().nonnegative(),
|
|
20934
|
-
|
|
20935
|
-
|
|
20976
|
+
// Counts LOCATIONS, the unit this read pages — the number the paginator
|
|
20977
|
+
// states. The facets beside it count FINDINGS (see below); a surface
|
|
20978
|
+
// showing both says which is which.
|
|
20979
|
+
locations: external_exports.number().int().nonnegative()
|
|
20936
20980
|
}),
|
|
20937
|
-
|
|
20938
|
-
|
|
20939
|
-
|
|
20940
|
-
|
|
20981
|
+
// Counts in FINDINGS, where the types response counts types, each dimension
|
|
20982
|
+
// still excluding its own filter. Deliberately not locations: counting those
|
|
20983
|
+
// needs a set of location keys per dimension per value — memory tracking the
|
|
20984
|
+
// store times the vocabulary, in a read whose scan promises flat memory —
|
|
20985
|
+
// and the cheap per-location version is not an approximation but WRONG. A
|
|
20986
|
+
// location holding {claudecode, block} and {codex, warn} would survive
|
|
20987
|
+
// provider=claudecode AND action=warn, under which no single finding
|
|
20988
|
+
// matches, so the facet would contradict the instanceCount this whole view
|
|
20989
|
+
// rests on. Findings also keep the toolbar in the same unit as the page
|
|
20990
|
+
// tally and the panel it sits above.
|
|
20991
|
+
facets: FindingFacets,
|
|
20992
|
+
/** Sorted by max severity, then most recent, then (repo, file). */
|
|
20993
|
+
items: external_exports.array(FindingLocationSummary),
|
|
20994
|
+
nextCursor: external_exports.string().nullable()
|
|
20941
20995
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
20942
20996
|
|
|
20943
20997
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -22106,6 +22160,14 @@ var ControlPlaneErrorBody = external_exports.object({
|
|
|
22106
22160
|
message: external_exports.string().optional()
|
|
22107
22161
|
}).optional()
|
|
22108
22162
|
});
|
|
22163
|
+
var RemoteFailureKind = external_exports.enum([
|
|
22164
|
+
"unauthorized",
|
|
22165
|
+
"forbidden",
|
|
22166
|
+
"route-absent",
|
|
22167
|
+
"invalid-request",
|
|
22168
|
+
"rejected",
|
|
22169
|
+
"unreachable"
|
|
22170
|
+
]);
|
|
22109
22171
|
var AttachDeviceRequest = external_exports.object({
|
|
22110
22172
|
// This machine's own continuity id, so re-attaching ROTATES the credential
|
|
22111
22173
|
// on one machine record instead of producing a second one. Client-minted
|
|
@@ -22799,139 +22861,62 @@ function deriveFindingStatus(row) {
|
|
|
22799
22861
|
if (row.latestResolutionStatus === "dismissed") return "dismissed";
|
|
22800
22862
|
return "open";
|
|
22801
22863
|
}
|
|
22802
|
-
function distinctUsers(instances) {
|
|
22803
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22804
|
-
const users = [];
|
|
22805
|
-
for (const i of instances) {
|
|
22806
|
-
if (i.user === void 0 || seen.has(i.user.id)) continue;
|
|
22807
|
-
seen.add(i.user.id);
|
|
22808
|
-
users.push(i.user);
|
|
22809
|
-
}
|
|
22810
|
-
return users;
|
|
22811
|
-
}
|
|
22812
22864
|
function sortUsers(users) {
|
|
22813
22865
|
return [...users].sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
22814
22866
|
}
|
|
22815
|
-
function
|
|
22816
|
-
const overrides = opts.overrides;
|
|
22867
|
+
function buildFindingTypes(aggregates, opts = {}) {
|
|
22817
22868
|
const packNames = opts.packNames;
|
|
22818
|
-
const
|
|
22819
|
-
const
|
|
22820
|
-
|
|
22821
|
-
const
|
|
22822
|
-
|
|
22823
|
-
else byRuleId.set(row.ruleId, [row]);
|
|
22824
|
-
}
|
|
22825
|
-
const groups = [];
|
|
22826
|
-
for (const [ruleId, ruleRows] of byRuleId) {
|
|
22827
|
-
const instances = ruleRows.map((r) => {
|
|
22828
|
-
const effectiveDbAction = overrides?.get(r.id) ?? r.actionTaken;
|
|
22829
|
-
return {
|
|
22830
|
-
id: r.id,
|
|
22831
|
-
provider: toApiProvider(r.sourceTool),
|
|
22832
|
-
repo: r.repo,
|
|
22833
|
-
file: r.file,
|
|
22834
|
-
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
22835
|
-
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
22836
|
-
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
22837
|
-
...r.user === void 0 ? {} : { user: r.user },
|
|
22838
|
-
action: toApiAction(effectiveDbAction),
|
|
22839
|
-
detectedAt: r.occurredAt,
|
|
22840
|
-
confidence: r.confidence,
|
|
22841
|
-
status: r.status
|
|
22842
|
-
};
|
|
22843
|
-
});
|
|
22844
|
-
const agg = aggregates?.get(ruleId);
|
|
22845
|
-
const users = agg ? sortUsers(agg.users ?? []) : distinctUsers(instances);
|
|
22846
|
-
const latestDetectedAt = agg?.latestDetectedAt ?? ruleRows.reduce(
|
|
22847
|
-
(max, r) => r.occurredAt > max ? r.occurredAt : max,
|
|
22848
|
-
ruleRows[0]?.occurredAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
22849
|
-
);
|
|
22850
|
-
const seenProviders = /* @__PURE__ */ new Set();
|
|
22851
|
-
const providers = (agg ? [...new Set(agg.sourceTools.map(toApiProvider))].sort() : instances.map((i) => i.provider)).filter((p) => {
|
|
22852
|
-
if (seenProviders.has(p)) return false;
|
|
22853
|
-
seenProviders.add(p);
|
|
22854
|
-
return true;
|
|
22855
|
-
});
|
|
22856
|
-
const actionSet = new Set(
|
|
22857
|
-
agg ? agg.actionsTaken.map(toApiAction) : instances.map((i) => i.action)
|
|
22858
|
-
);
|
|
22869
|
+
const types = [];
|
|
22870
|
+
for (const [ruleId, agg] of aggregates) {
|
|
22871
|
+
const users = sortUsers(agg.users ?? []);
|
|
22872
|
+
const providers = [...new Set(agg.sourceTools.map(toApiProvider))].sort();
|
|
22873
|
+
const actionSet = new Set(agg.actionsTaken.map(toApiAction));
|
|
22859
22874
|
const aggregateAction = actionSet.size === 1 ? [...actionSet][0] ?? null : null;
|
|
22860
|
-
const
|
|
22861
|
-
const
|
|
22862
|
-
id: ruleId,
|
|
22863
|
-
name: packNames?.get(ruleId) ?? null
|
|
22864
|
-
};
|
|
22865
|
-
const apiCategory = toApiCategory(ruleRows[0]?.category ?? "custom");
|
|
22866
|
-
const policy = { id: `category:${apiCategory}`, name: apiCategory };
|
|
22867
|
-
const match = {
|
|
22868
|
-
maskedValue: ruleRows[0]?.maskedMatch ?? "",
|
|
22869
|
-
contextPrefix: ""
|
|
22870
|
-
// empty (pending privacy review)
|
|
22871
|
-
};
|
|
22872
|
-
const status = foldGroupStatus(
|
|
22873
|
-
agg ? agg.statusInputs.map(deriveFindingStatus) : instances.map((i) => i.status)
|
|
22874
|
-
);
|
|
22875
|
-
const group = {
|
|
22875
|
+
const apiCategory = toApiCategory(agg.category ?? "custom");
|
|
22876
|
+
const type = {
|
|
22876
22877
|
id: ruleId,
|
|
22877
22878
|
category: apiCategory,
|
|
22878
22879
|
subtype: ruleId,
|
|
22879
22880
|
// human label comes with pack metadata later
|
|
22880
|
-
severity,
|
|
22881
|
-
|
|
22882
|
-
|
|
22883
|
-
|
|
22884
|
-
instanceCount: agg?.instanceCount ?? instances.length,
|
|
22881
|
+
severity: agg.severity ?? "low",
|
|
22882
|
+
detection: { id: ruleId, name: packNames?.get(ruleId) ?? null },
|
|
22883
|
+
policy: { id: `category:${apiCategory}`, name: apiCategory },
|
|
22884
|
+
instanceCount: agg.instanceCount,
|
|
22885
22885
|
providers,
|
|
22886
22886
|
aggregateAction,
|
|
22887
|
-
latestDetectedAt,
|
|
22888
|
-
|
|
22889
|
-
status,
|
|
22887
|
+
latestDetectedAt: agg.latestDetectedAt,
|
|
22888
|
+
status: foldGroupStatus(agg.statusInputs.map(deriveFindingStatus)),
|
|
22890
22889
|
...users.length > 0 ? { users } : {}
|
|
22891
22890
|
};
|
|
22892
|
-
|
|
22893
|
-
|
|
22894
|
-
|
|
22895
|
-
haystackCache.set(group, buildHaystack(group, agg.searchText));
|
|
22896
|
-
}
|
|
22891
|
+
actionsCache.set(type, [...actionSet]);
|
|
22892
|
+
if (agg.searchText !== void 0) {
|
|
22893
|
+
haystackCache.set(type, buildHaystack(type, agg.searchText));
|
|
22897
22894
|
}
|
|
22898
|
-
|
|
22895
|
+
types.push(type);
|
|
22899
22896
|
}
|
|
22900
|
-
return
|
|
22897
|
+
return types;
|
|
22901
22898
|
}
|
|
22902
22899
|
var haystackCache = /* @__PURE__ */ new WeakMap();
|
|
22903
|
-
function buildHaystack(
|
|
22900
|
+
function buildHaystack(t, extra) {
|
|
22904
22901
|
return [
|
|
22905
|
-
|
|
22906
|
-
|
|
22907
|
-
|
|
22908
|
-
|
|
22909
|
-
|
|
22910
|
-
...g.instances.map((i) => i.repo),
|
|
22911
|
-
...g.instances.map((i) => i.file),
|
|
22912
|
-
...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
|
|
22913
|
-
...g.instances.map((i) => i.id),
|
|
22914
|
-
// The people: the whole group's list when the store folded one, plus the
|
|
22915
|
-
// preview's own — the two overlap, and a haystack does not mind.
|
|
22916
|
-
...(g.users ?? []).map((u) => u.name),
|
|
22917
|
-
...g.instances.map((i) => i.user?.name ?? ""),
|
|
22902
|
+
t.subtype,
|
|
22903
|
+
t.category,
|
|
22904
|
+
t.policy.name,
|
|
22905
|
+
t.id,
|
|
22906
|
+
...(t.users ?? []).map((u) => u.name),
|
|
22918
22907
|
...extra === void 0 ? [] : [extra]
|
|
22919
22908
|
].join(" ").toLowerCase();
|
|
22920
22909
|
}
|
|
22921
|
-
function
|
|
22922
|
-
const cached2 = haystackCache.get(
|
|
22910
|
+
function typeHaystack(t) {
|
|
22911
|
+
const cached2 = haystackCache.get(t);
|
|
22923
22912
|
if (cached2 !== void 0) return cached2;
|
|
22924
|
-
const haystack = buildHaystack(
|
|
22925
|
-
haystackCache.set(
|
|
22913
|
+
const haystack = buildHaystack(t);
|
|
22914
|
+
haystackCache.set(t, haystack);
|
|
22926
22915
|
return haystack;
|
|
22927
22916
|
}
|
|
22928
22917
|
var actionsCache = /* @__PURE__ */ new WeakMap();
|
|
22929
|
-
function
|
|
22930
|
-
|
|
22931
|
-
if (cached2 !== void 0) return cached2;
|
|
22932
|
-
const actions = [...new Set(g.instances.map((i) => i.action))];
|
|
22933
|
-
actionsCache.set(g, actions);
|
|
22934
|
-
return actions;
|
|
22918
|
+
function typeActions(t) {
|
|
22919
|
+
return actionsCache.get(t) ?? [];
|
|
22935
22920
|
}
|
|
22936
22921
|
function countInstancesByStatus(statusInputs, statuses) {
|
|
22937
22922
|
const statusSet = new Set(statuses);
|
|
@@ -22942,8 +22927,8 @@ function countInstancesByStatus(statusInputs, statuses) {
|
|
|
22942
22927
|
}
|
|
22943
22928
|
return sum;
|
|
22944
22929
|
}
|
|
22945
|
-
function applyFindingFilters(
|
|
22946
|
-
let filtered =
|
|
22930
|
+
function applyFindingFilters(types, opts) {
|
|
22931
|
+
let filtered = types;
|
|
22947
22932
|
if (opts.severity && opts.severity.length > 0) {
|
|
22948
22933
|
const sevSet = new Set(opts.severity);
|
|
22949
22934
|
filtered = filtered.filter((g) => sevSet.has(g.severity));
|
|
@@ -22954,7 +22939,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22954
22939
|
}
|
|
22955
22940
|
if (opts.actions && opts.actions.length > 0) {
|
|
22956
22941
|
const actionSet = new Set(opts.actions);
|
|
22957
|
-
filtered = filtered.filter((
|
|
22942
|
+
filtered = filtered.filter((t) => typeActions(t).some((a) => actionSet.has(a)));
|
|
22958
22943
|
}
|
|
22959
22944
|
if (opts.subtype && opts.subtype.length > 0) {
|
|
22960
22945
|
const subtypeSet = new Set(opts.subtype);
|
|
@@ -22966,7 +22951,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22966
22951
|
}
|
|
22967
22952
|
if (opts.q) {
|
|
22968
22953
|
const q = opts.q.toLowerCase();
|
|
22969
|
-
filtered = filtered.filter((
|
|
22954
|
+
filtered = filtered.filter((t) => typeHaystack(t).includes(q));
|
|
22970
22955
|
}
|
|
22971
22956
|
return filtered;
|
|
22972
22957
|
}
|
|
@@ -22981,11 +22966,11 @@ function compareFindingGroupOrder(a, b) {
|
|
|
22981
22966
|
if (recencyDiff !== 0) return recencyDiff;
|
|
22982
22967
|
return a.id.localeCompare(b.id);
|
|
22983
22968
|
}
|
|
22984
|
-
function
|
|
22985
|
-
return [...
|
|
22969
|
+
function sortFindingTypes(types) {
|
|
22970
|
+
return [...types].sort(compareFindingGroupOrder);
|
|
22986
22971
|
}
|
|
22987
|
-
function computeFindingFacets(
|
|
22988
|
-
const forSeverity = applyFindingFilters(
|
|
22972
|
+
function computeFindingFacets(allTypes, opts) {
|
|
22973
|
+
const forSeverity = applyFindingFilters(allTypes, {
|
|
22989
22974
|
providers: opts.providers,
|
|
22990
22975
|
actions: opts.actions,
|
|
22991
22976
|
statuses: opts.statuses,
|
|
@@ -22996,7 +22981,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
22996
22981
|
for (const g of forSeverity) {
|
|
22997
22982
|
severityMap.set(g.severity, (severityMap.get(g.severity) ?? 0) + 1);
|
|
22998
22983
|
}
|
|
22999
|
-
const forProvider = applyFindingFilters(
|
|
22984
|
+
const forProvider = applyFindingFilters(allTypes, {
|
|
23000
22985
|
actions: opts.actions,
|
|
23001
22986
|
statuses: opts.statuses,
|
|
23002
22987
|
q: opts.q,
|
|
@@ -23007,7 +22992,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23007
22992
|
for (const g of forProvider) {
|
|
23008
22993
|
for (const p of g.providers) providerMap.set(p, (providerMap.get(p) ?? 0) + 1);
|
|
23009
22994
|
}
|
|
23010
|
-
const forAction = applyFindingFilters(
|
|
22995
|
+
const forAction = applyFindingFilters(allTypes, {
|
|
23011
22996
|
providers: opts.providers,
|
|
23012
22997
|
statuses: opts.statuses,
|
|
23013
22998
|
q: opts.q,
|
|
@@ -23016,9 +23001,9 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23016
23001
|
});
|
|
23017
23002
|
const actionMap = /* @__PURE__ */ new Map();
|
|
23018
23003
|
for (const g of forAction) {
|
|
23019
|
-
for (const a of
|
|
23004
|
+
for (const a of typeActions(g)) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
23020
23005
|
}
|
|
23021
|
-
const forSubtype = applyFindingFilters(
|
|
23006
|
+
const forSubtype = applyFindingFilters(allTypes, {
|
|
23022
23007
|
providers: opts.providers,
|
|
23023
23008
|
actions: opts.actions,
|
|
23024
23009
|
statuses: opts.statuses,
|
|
@@ -23027,7 +23012,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23027
23012
|
});
|
|
23028
23013
|
const subtypeMap = /* @__PURE__ */ new Map();
|
|
23029
23014
|
for (const g of forSubtype) subtypeMap.set(g.subtype, (subtypeMap.get(g.subtype) ?? 0) + 1);
|
|
23030
|
-
const forStatus = applyFindingFilters(
|
|
23015
|
+
const forStatus = applyFindingFilters(allTypes, {
|
|
23031
23016
|
providers: opts.providers,
|
|
23032
23017
|
actions: opts.actions,
|
|
23033
23018
|
q: opts.q,
|
|
@@ -23075,10 +23060,20 @@ function matchesDimension(row, opts, dimension) {
|
|
|
23075
23060
|
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
23076
23061
|
case "tools":
|
|
23077
23062
|
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
23063
|
+
// An EMPTY value is a real filter here, not an absent one. The location
|
|
23064
|
+
// list buckets a finding whose event recorded no repo — or no file — under
|
|
23065
|
+
// the empty string, and selecting that bucket has to narrow the panel to
|
|
23066
|
+
// exactly it. Only `undefined` means "no filter"; a caller that wants every
|
|
23067
|
+
// row omits the key, which every call site already does.
|
|
23068
|
+
//
|
|
23069
|
+
// Reading '' as unset is what this replaced, and it failed in the one place
|
|
23070
|
+
// it mattered: the no-repo/no-file bucket is often the largest in a real
|
|
23071
|
+
// store, and its panel dropped both predicates and returned the WHOLE scope
|
|
23072
|
+
// — a row reading 3 findings beside a panel listing every finding there is.
|
|
23078
23073
|
case "repo":
|
|
23079
|
-
return opts.repo === void 0 ||
|
|
23074
|
+
return opts.repo === void 0 || row.repo === opts.repo;
|
|
23080
23075
|
case "file":
|
|
23081
|
-
return opts.file === void 0 ||
|
|
23076
|
+
return opts.file === void 0 || row.file === opts.file;
|
|
23082
23077
|
case "q":
|
|
23083
23078
|
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
23084
23079
|
}
|
|
@@ -23192,6 +23187,23 @@ function addToLocation(acc, row) {
|
|
|
23192
23187
|
acc.statuses.push(row.status);
|
|
23193
23188
|
acc.ruleIds.add(row.ruleId);
|
|
23194
23189
|
}
|
|
23190
|
+
function compareLocationOrder(a, b) {
|
|
23191
|
+
const rankA = SEVERITY_ORDER2[a.maxSeverity] ?? -1;
|
|
23192
|
+
const rankB = SEVERITY_ORDER2[b.maxSeverity] ?? -1;
|
|
23193
|
+
if (rankA !== rankB) return rankA - rankB;
|
|
23194
|
+
if (a.latestDetectedAt !== b.latestDetectedAt) {
|
|
23195
|
+
return a.latestDetectedAt < b.latestDetectedAt ? 1 : -1;
|
|
23196
|
+
}
|
|
23197
|
+
if (a.repo !== b.repo) return a.repo < b.repo ? -1 : 1;
|
|
23198
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
23199
|
+
return 0;
|
|
23200
|
+
}
|
|
23201
|
+
function encodeLocationId(repo, file2) {
|
|
23202
|
+
return `${encodePart(repo)}/${encodePart(file2)}`;
|
|
23203
|
+
}
|
|
23204
|
+
function encodePart(value) {
|
|
23205
|
+
return encodeURIComponent(value.replace(/[\uD800-\uDFFF]/gu, "\uFFFD"));
|
|
23206
|
+
}
|
|
23195
23207
|
|
|
23196
23208
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
23197
23209
|
var InstalledPack = external_exports.object({
|
|
@@ -23705,11 +23717,11 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23705
23717
|
// covers the current payload and must be re-granted.
|
|
23706
23718
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23707
23719
|
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23708
|
-
// with the payload shape and the endpoint they agreed to. Since payload
|
|
23709
|
-
// that covers
|
|
23710
|
-
// carry prompt/reply text in `content
|
|
23711
|
-
// Absent until granted, and a grant for a different endpoint
|
|
23712
|
-
// payload no longer counts.
|
|
23720
|
+
// with the payload shape and the endpoint they agreed to. Since payload v3
|
|
23721
|
+
// that covers the pre-attach backlog AND undelivered captures alike, and both
|
|
23722
|
+
// carry prompt/reply/tool-output text in `content`; the key name predates
|
|
23723
|
+
// both widenings. Absent until granted, and a grant for a different endpoint
|
|
23724
|
+
// or an older payload no longer counts.
|
|
23713
23725
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23714
23726
|
});
|
|
23715
23727
|
function defaultWorkspaceSettings() {
|
|
@@ -23840,6 +23852,9 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23840
23852
|
"dataSharesInPlace",
|
|
23841
23853
|
"redactFallback"
|
|
23842
23854
|
]).meta({ id: "ManagedSettingKey" });
|
|
23855
|
+
function isManagedSettingKey(value) {
|
|
23856
|
+
return ManagedSettingKey.safeParse(value).success;
|
|
23857
|
+
}
|
|
23843
23858
|
var ManagedSettingsValues = external_exports.object({
|
|
23844
23859
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23845
23860
|
controlPlane: external_exports.object({
|
|
@@ -23864,7 +23879,27 @@ var ManagedSettings = external_exports.object({
|
|
|
23864
23879
|
// Which of those the user may not change. A key here with no matching value
|
|
23865
23880
|
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23866
23881
|
// the user may still override. The two are separable on purpose.
|
|
23867
|
-
|
|
23882
|
+
//
|
|
23883
|
+
// Parsed as NAMES rather than as the enum, and split below: a name this
|
|
23884
|
+
// build does not know is dropped from the locked set and reported, never a
|
|
23885
|
+
// reason to refuse the file. The same shape reaches an older build whenever
|
|
23886
|
+
// an administrator locks a key a newer build added, and refusing it there
|
|
23887
|
+
// ran that build entirely unmanaged — every pin and lock gone — on exactly
|
|
23888
|
+
// the fleets most likely to carry a version skew. A name outside the enum
|
|
23889
|
+
// is still never HONOURED: the lockable set stays explicit above.
|
|
23890
|
+
lockedFields: external_exports.array(external_exports.string()).default([])
|
|
23891
|
+
}).transform(({ lockedFields, ...rest }) => {
|
|
23892
|
+
const known = [];
|
|
23893
|
+
const unknown2 = [];
|
|
23894
|
+
for (const name of lockedFields) {
|
|
23895
|
+
if (isManagedSettingKey(name)) known.push(name);
|
|
23896
|
+
else unknown2.push(name);
|
|
23897
|
+
}
|
|
23898
|
+
return {
|
|
23899
|
+
...rest,
|
|
23900
|
+
lockedFields: known,
|
|
23901
|
+
...unknown2.length > 0 ? { unknownLockedFields: unknown2 } : {}
|
|
23902
|
+
};
|
|
23868
23903
|
}).meta({ id: "ManagedSettings" });
|
|
23869
23904
|
|
|
23870
23905
|
// ../../packages/schema/src/zod/project-files.ts
|
|
@@ -23988,7 +24023,11 @@ var FindingsTimeseriesPoint = external_exports.object({
|
|
|
23988
24023
|
timestamp: external_exports.iso.date(),
|
|
23989
24024
|
critical: external_exports.number().int().nonnegative(),
|
|
23990
24025
|
high: external_exports.number().int().nonnegative(),
|
|
23991
|
-
medium: external_exports.number().int().nonnegative()
|
|
24026
|
+
medium: external_exports.number().int().nonnegative(),
|
|
24027
|
+
// Optional and additive, so a producer written against the earlier
|
|
24028
|
+
// three-series contract keeps validating. A consumer plotting it resolves the
|
|
24029
|
+
// absent case itself — the chart point requires a number.
|
|
24030
|
+
low: external_exports.number().int().nonnegative().optional()
|
|
23992
24031
|
}).meta({ id: "FindingsTimeseriesPoint" });
|
|
23993
24032
|
var FindingsTimeseriesResponse = external_exports.object({
|
|
23994
24033
|
range: TimeRange,
|
|
@@ -24014,6 +24053,10 @@ var ResolvedFeedItem = external_exports.object({
|
|
|
24014
24053
|
findingKey: external_exports.string(),
|
|
24015
24054
|
ruleId: external_exports.string(),
|
|
24016
24055
|
severity: Severity,
|
|
24056
|
+
// Repository slug, and the file path RELATIVE to it. The pair is what
|
|
24057
|
+
// identifies the file: a bare path matches the same name in every repo.
|
|
24058
|
+
// Optional and additive; empty when the event carried no repo.
|
|
24059
|
+
repo: external_exports.string().optional(),
|
|
24017
24060
|
path: external_exports.string(),
|
|
24018
24061
|
// ISO-8601 datetime (matches FindingInstance.detectedAt / the rest of the
|
|
24019
24062
|
// findings domain). The reader `.toISOString()`s the DB epoch-ms values.
|
|
@@ -25587,7 +25630,8 @@ var SqliteActivityRepository = class {
|
|
|
25587
25630
|
SELECT 1 FROM audit_events d
|
|
25588
25631
|
WHERE d.root_session_id = audit_events.id
|
|
25589
25632
|
AND (d.content LIKE ? ESCAPE '\\'
|
|
25590
|
-
OR json_extract(d.attributes, '$.detail')
|
|
25633
|
+
OR coalesce(json_extract(d.attributes, '$.detail'),
|
|
25634
|
+
json_extract(d.attributes, '$.target')) LIKE ? ESCAPE '\\')))`
|
|
25591
25635
|
);
|
|
25592
25636
|
params.push(pattern, pattern, pattern, pattern, pattern, pattern);
|
|
25593
25637
|
}
|
|
@@ -26925,23 +26969,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26925
26969
|
)`;
|
|
26926
26970
|
|
|
26927
26971
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26928
|
-
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26929
|
-
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26930
|
-
var LOCATION_RULE_IDS_CAP = 20;
|
|
26931
|
-
function compareLocationOrder(a, b) {
|
|
26932
|
-
return compareFindingGroupOrder(
|
|
26933
|
-
{
|
|
26934
|
-
severity: a.maxSeverity,
|
|
26935
|
-
latestDetectedAt: a.latestDetectedAt,
|
|
26936
|
-
id: ""
|
|
26937
|
-
},
|
|
26938
|
-
{
|
|
26939
|
-
severity: b.maxSeverity,
|
|
26940
|
-
latestDetectedAt: b.latestDetectedAt,
|
|
26941
|
-
id: ""
|
|
26942
|
-
}
|
|
26943
|
-
);
|
|
26944
|
-
}
|
|
26945
26972
|
var CONCAT_SEP = ",";
|
|
26946
26973
|
var TUPLE_SEP = "|";
|
|
26947
26974
|
function splitConcat(value) {
|
|
@@ -26993,13 +27020,48 @@ function decodeGroupCursor(cursor) {
|
|
|
26993
27020
|
return null;
|
|
26994
27021
|
}
|
|
26995
27022
|
function firstAfter(sorted, cursor) {
|
|
26996
|
-
const index = sorted.findIndex((
|
|
27023
|
+
const index = sorted.findIndex((t) => compareFindingGroupOrder(t, cursor) > 0);
|
|
26997
27024
|
return index === -1 ? sorted.length : index;
|
|
26998
27025
|
}
|
|
26999
27026
|
function findDeepLinked(sorted, page, id) {
|
|
27000
|
-
if (page.some((
|
|
27001
|
-
return sorted.find((
|
|
27027
|
+
if (page.some((t) => t.id === id)) return void 0;
|
|
27028
|
+
return sorted.find((t) => t.id === id);
|
|
27002
27029
|
}
|
|
27030
|
+
function encodeLocationCursor(location) {
|
|
27031
|
+
const payload = {
|
|
27032
|
+
sev: location.maxSeverity,
|
|
27033
|
+
t: location.latestDetectedAt,
|
|
27034
|
+
r: location.repo,
|
|
27035
|
+
f: location.file
|
|
27036
|
+
};
|
|
27037
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
27038
|
+
}
|
|
27039
|
+
function decodeLocationCursor(cursor) {
|
|
27040
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
27041
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.r === "string" && typeof parsed2.f === "string") {
|
|
27042
|
+
return { maxSeverity: parsed2.sev, latestDetectedAt: parsed2.t, repo: parsed2.r, file: parsed2.f };
|
|
27043
|
+
}
|
|
27044
|
+
return null;
|
|
27045
|
+
}
|
|
27046
|
+
function firstLocationAfter(sorted, cursor) {
|
|
27047
|
+
const index = sorted.findIndex((l) => compareLocationOrder(l, cursor) > 0);
|
|
27048
|
+
return index === -1 ? sorted.length : index;
|
|
27049
|
+
}
|
|
27050
|
+
function findDeepLinkedLocation(sorted, page, id) {
|
|
27051
|
+
if (page.some((l) => l.id === id)) return void 0;
|
|
27052
|
+
return sorted.find((l) => l.id === id);
|
|
27053
|
+
}
|
|
27054
|
+
var FINDING_ROW_COLUMNS_SQL = `f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
27055
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
27056
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27057
|
+
e.started_at AS occurred_at,
|
|
27058
|
+
e.source_tool AS source_tool,
|
|
27059
|
+
e.repo AS repo,
|
|
27060
|
+
e.file_path AS file,
|
|
27061
|
+
e.tool_name AS tool_name,
|
|
27062
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27063
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27064
|
+
${latestResolutionStatusSql("f")} AS latest_status`;
|
|
27003
27065
|
var DAY_MS3 = 864e5;
|
|
27004
27066
|
var SqliteFindingsRepository = class {
|
|
27005
27067
|
constructor(db) {
|
|
@@ -27120,30 +27182,26 @@ var SqliteFindingsRepository = class {
|
|
|
27120
27182
|
);
|
|
27121
27183
|
}
|
|
27122
27184
|
/**
|
|
27123
|
-
*
|
|
27124
|
-
*
|
|
27125
|
-
*
|
|
27126
|
-
*
|
|
27127
|
-
*
|
|
27128
|
-
*
|
|
27129
|
-
*
|
|
27130
|
-
*
|
|
27131
|
-
*
|
|
27132
|
-
*
|
|
27133
|
-
*
|
|
27134
|
-
*
|
|
27185
|
+
* Finding TYPES for the dashboard — one row per rule, scoped to the four
|
|
27186
|
+
* capture kinds (audit_events also holds structural/reconciler/scan rows this
|
|
27187
|
+
* list must never surface), with per-filter-excluded facets, the requested
|
|
27188
|
+
* filters applied, and sorted by severity then recency. Filtering and faceting
|
|
27189
|
+
* run in JS via the shared @akasecurity/schema helpers. `totals` reflect the
|
|
27190
|
+
* full filtered set; `items` is the requested page (default 50), keyset-paged.
|
|
27191
|
+
* Under a `status` filter, `totals.findings` counts only findings whose
|
|
27192
|
+
* derived status was requested.
|
|
27193
|
+
*
|
|
27194
|
+
* ONE read, which materializes no findings: a single aggregate per rule_id,
|
|
27195
|
+
* folding EVERY finding into the numbers a type row and the filters need
|
|
27196
|
+
* (count, severity, category, providers, actions, statuses, latest, search
|
|
27197
|
+
* text). The findings OF a type come from listFindingInstances scoped to
|
|
27198
|
+
* `subtype`, so neither list bounds the other and no per-type cap exists.
|
|
27135
27199
|
*
|
|
27136
|
-
* Two reads, neither of which materializes a row per finding:
|
|
27137
|
-
* 1. one aggregate row per rule_id, folding EVERY instance into the numbers
|
|
27138
|
-
* the group and the filters need (count, providers, actions, statuses,
|
|
27139
|
-
* latest, search text);
|
|
27140
|
-
* 2. each group's newest PREVIEW_INSTANCES_PER_GROUP instances, which
|
|
27141
|
-
* populate `instances` for the table's expanded rows.
|
|
27142
27200
|
* The aggregates carry raw DB values and are translated by the same
|
|
27143
|
-
* @akasecurity/schema mappers
|
|
27144
|
-
* rule is ever restated in SQL.
|
|
27201
|
+
* @akasecurity/schema mappers every other path uses, so no enum mapping or
|
|
27202
|
+
* status rule is ever restated in SQL.
|
|
27145
27203
|
*/
|
|
27146
|
-
|
|
27204
|
+
listFindingTypes(query) {
|
|
27147
27205
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
27148
27206
|
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
27149
27207
|
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
@@ -27156,12 +27214,7 @@ var SqliteFindingsRepository = class {
|
|
|
27156
27214
|
predicate,
|
|
27157
27215
|
params: sessionParams
|
|
27158
27216
|
});
|
|
27159
|
-
const
|
|
27160
|
-
sessionId: query.sessionId,
|
|
27161
|
-
from: query.from
|
|
27162
|
-
});
|
|
27163
|
-
const groupable = rows.map(toFlatFindingRow);
|
|
27164
|
-
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27217
|
+
const allTypes = buildFindingTypes(aggregates);
|
|
27165
27218
|
const filterOpts = {
|
|
27166
27219
|
severity: query.severity,
|
|
27167
27220
|
providers: query.provider,
|
|
@@ -27170,30 +27223,25 @@ var SqliteFindingsRepository = class {
|
|
|
27170
27223
|
subtype: query.subtype,
|
|
27171
27224
|
q: query.q
|
|
27172
27225
|
};
|
|
27173
|
-
const facets = computeFindingFacets(
|
|
27174
|
-
const sorted =
|
|
27226
|
+
const facets = computeFindingFacets(allTypes, filterOpts);
|
|
27227
|
+
const sorted = sortFindingTypes(applyFindingFilters(allTypes, filterOpts));
|
|
27175
27228
|
const statusFilter = query.status ?? [];
|
|
27176
27229
|
const totals = {
|
|
27177
|
-
findings: sorted.reduce((acc,
|
|
27178
|
-
if (statusFilter.length === 0) return acc +
|
|
27179
|
-
const agg = aggregates.get(
|
|
27180
|
-
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ??
|
|
27230
|
+
findings: sorted.reduce((acc, t) => {
|
|
27231
|
+
if (statusFilter.length === 0) return acc + t.instanceCount;
|
|
27232
|
+
const agg = aggregates.get(t.id);
|
|
27233
|
+
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ?? t.instanceCount : t.instanceCount);
|
|
27181
27234
|
}, 0),
|
|
27182
|
-
|
|
27235
|
+
types: sorted.length
|
|
27183
27236
|
};
|
|
27184
|
-
const limit = query.limit ??
|
|
27237
|
+
const limit = query.limit ?? DEFAULT_FINDING_TYPES_LIMIT;
|
|
27185
27238
|
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
27186
27239
|
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
27187
27240
|
const page = sorted.slice(start, start + limit);
|
|
27188
27241
|
const lastOnPage = page.at(-1);
|
|
27189
27242
|
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
27190
27243
|
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
27191
|
-
const
|
|
27192
|
-
const narrow = (g) => statusSet ? {
|
|
27193
|
-
...g,
|
|
27194
|
-
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
27195
|
-
} : g;
|
|
27196
|
-
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
27244
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []];
|
|
27197
27245
|
return Promise.resolve({
|
|
27198
27246
|
totals,
|
|
27199
27247
|
facets,
|
|
@@ -27204,7 +27252,7 @@ var SqliteFindingsRepository = class {
|
|
|
27204
27252
|
}
|
|
27205
27253
|
/**
|
|
27206
27254
|
* One row per rule_id, folding EVERY instance of the group into the values
|
|
27207
|
-
*
|
|
27255
|
+
* buildFindingTypes cannot recover from an aggregate. Bounded by the number of
|
|
27208
27256
|
* distinct rule_ids (the installed packs' rules), not by the store's size.
|
|
27209
27257
|
*
|
|
27210
27258
|
* A single scan, folded in two levels: the inner SELECT groups by
|
|
@@ -27298,13 +27346,25 @@ var SqliteFindingsRepository = class {
|
|
|
27298
27346
|
});
|
|
27299
27347
|
}
|
|
27300
27348
|
/**
|
|
27301
|
-
* The same findings folded by
|
|
27349
|
+
* The same findings folded by WHERE they live — one row per (repo, file) pair.
|
|
27302
27350
|
*
|
|
27303
27351
|
* The grouping keys come from the capturing event's attributes, which is what
|
|
27304
|
-
* the local store relates a finding to
|
|
27305
|
-
*
|
|
27306
|
-
* empty-string bucket, which
|
|
27307
|
-
*
|
|
27352
|
+
* the local store relates a finding to; there is no finding↔asset row to group
|
|
27353
|
+
* by instead. A repo or file the event did not record folds into the
|
|
27354
|
+
* empty-string bucket, which is a real location like any other: it is listed,
|
|
27355
|
+
* it is selectable, and its `?loc=` token is as good as any other row's.
|
|
27356
|
+
*
|
|
27357
|
+
* ONE flat list rather than repos nesting files. A rollup can only be paged by
|
|
27358
|
+
* repo, which leaves the file list inside it unbounded — the shape the by-type
|
|
27359
|
+
* list was rebuilt to remove — and two-level pagination inside an
|
|
27360
|
+
* expand/collapse table is what pushed that view to master/detail in the first
|
|
27361
|
+
* place.
|
|
27362
|
+
*
|
|
27363
|
+
* Every filter narrows the FINDINGS and the locations fall out of what
|
|
27364
|
+
* survives, so each row's `instanceCount` is exactly what listFindingInstances
|
|
27365
|
+
* reports for the same filters scoped to that pair. The view depends on it:
|
|
27366
|
+
* one toolbar sits over both panels precisely because a location owns none of
|
|
27367
|
+
* its fields.
|
|
27308
27368
|
*/
|
|
27309
27369
|
listFindingLocations(query) {
|
|
27310
27370
|
const opts = {
|
|
@@ -27316,13 +27376,16 @@ var SqliteFindingsRepository = class {
|
|
|
27316
27376
|
tools: query.tool,
|
|
27317
27377
|
q: query.q
|
|
27318
27378
|
};
|
|
27319
|
-
const limit = query.limit ??
|
|
27379
|
+
const limit = query.limit ?? DEFAULT_FINDING_LOCATIONS_LIMIT;
|
|
27380
|
+
const cursor = query.cursor === void 0 ? null : decodeLocationCursor(query.cursor);
|
|
27320
27381
|
const byRepo = /* @__PURE__ */ new Map();
|
|
27382
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27321
27383
|
let total = 0;
|
|
27322
27384
|
for (const row of this.scanFindingRows({
|
|
27323
27385
|
sessionId: query.sessionId,
|
|
27324
27386
|
from: query.from
|
|
27325
27387
|
})) {
|
|
27388
|
+
accumulator.add(row);
|
|
27326
27389
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27327
27390
|
total += 1;
|
|
27328
27391
|
let files = byRepo.get(row.repo);
|
|
@@ -27337,103 +27400,35 @@ var SqliteFindingsRepository = class {
|
|
|
27337
27400
|
}
|
|
27338
27401
|
addToLocation(acc, row);
|
|
27339
27402
|
}
|
|
27340
|
-
|
|
27341
|
-
const
|
|
27342
|
-
|
|
27343
|
-
|
|
27344
|
-
|
|
27345
|
-
|
|
27346
|
-
|
|
27347
|
-
|
|
27348
|
-
|
|
27349
|
-
|
|
27350
|
-
|
|
27351
|
-
|
|
27352
|
-
|
|
27353
|
-
|
|
27354
|
-
|
|
27355
|
-
|
|
27356
|
-
|
|
27357
|
-
|
|
27358
|
-
|
|
27359
|
-
|
|
27360
|
-
|
|
27361
|
-
|
|
27362
|
-
);
|
|
27363
|
-
const statuses = fileRows.map((f) => f.status);
|
|
27364
|
-
const folded = foldGroupStatus(statuses);
|
|
27365
|
-
return {
|
|
27366
|
-
repo,
|
|
27367
|
-
instanceCount: rollup.instanceCount,
|
|
27368
|
-
maxSeverity: rollup.maxSeverity,
|
|
27369
|
-
latestDetectedAt: rollup.latestDetectedAt,
|
|
27370
|
-
...folded === void 0 ? {} : { status: folded },
|
|
27371
|
-
files: fileRows
|
|
27372
|
-
};
|
|
27373
|
-
});
|
|
27374
|
-
repos.sort(compareLocationOrder);
|
|
27403
|
+
const sorted = [];
|
|
27404
|
+
for (const [repo, files] of byRepo) {
|
|
27405
|
+
for (const [file2, acc] of files) {
|
|
27406
|
+
const status = foldGroupStatus(acc.statuses);
|
|
27407
|
+
sorted.push({
|
|
27408
|
+
id: encodeLocationId(repo, file2),
|
|
27409
|
+
repo,
|
|
27410
|
+
file: file2,
|
|
27411
|
+
instanceCount: acc.instanceCount,
|
|
27412
|
+
maxSeverity: acc.maxSeverity,
|
|
27413
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
27414
|
+
...status === void 0 ? {} : { status },
|
|
27415
|
+
ruleIds: [...acc.ruleIds]
|
|
27416
|
+
});
|
|
27417
|
+
}
|
|
27418
|
+
}
|
|
27419
|
+
sorted.sort(compareLocationOrder);
|
|
27420
|
+
const start = cursor === null ? 0 : firstLocationAfter(sorted, cursor);
|
|
27421
|
+
const page = sorted.slice(start, start + limit);
|
|
27422
|
+
const lastOnPage = page.at(-1);
|
|
27423
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeLocationCursor(lastOnPage) : null;
|
|
27424
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinkedLocation(sorted, page, query.includeId);
|
|
27375
27425
|
return Promise.resolve({
|
|
27376
|
-
totals: { findings: total,
|
|
27377
|
-
|
|
27378
|
-
|
|
27426
|
+
totals: { findings: total, locations: sorted.length },
|
|
27427
|
+
facets: accumulator.facets(),
|
|
27428
|
+
items: [...page, ...deepLinked ? [deepLinked] : []],
|
|
27429
|
+
nextCursor
|
|
27379
27430
|
});
|
|
27380
27431
|
}
|
|
27381
|
-
/**
|
|
27382
|
-
* Each group's newest instances, for the table's expanded rows.
|
|
27383
|
-
*
|
|
27384
|
-
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27385
|
-
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27386
|
-
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27387
|
-
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27388
|
-
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27389
|
-
* store while the answer does not.
|
|
27390
|
-
*
|
|
27391
|
-
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27392
|
-
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27393
|
-
* which is already the order the page wants, and keeps rows per rule until
|
|
27394
|
-
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27395
|
-
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27396
|
-
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27397
|
-
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27398
|
-
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27399
|
-
* store with many firing rules widens it. The bound that DOES hold
|
|
27400
|
-
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27401
|
-
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27402
|
-
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27403
|
-
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27404
|
-
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27405
|
-
* everything in scope with a block sort of the id tie-break only, never a
|
|
27406
|
-
* sort of the scope, which is still that floor.
|
|
27407
|
-
*
|
|
27408
|
-
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27409
|
-
* run without a shared snapshot, so a capture landing between them can add a
|
|
27410
|
-
* rule here that has no counts there, and the counts are what the group is
|
|
27411
|
-
* built from.
|
|
27412
|
-
*/
|
|
27413
|
-
previewRows(aggregates, scope) {
|
|
27414
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
27415
|
-
let remaining = 0;
|
|
27416
|
-
for (const [ruleId, agg] of aggregates) {
|
|
27417
|
-
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27418
|
-
wanted.set(ruleId, n);
|
|
27419
|
-
remaining += n;
|
|
27420
|
-
}
|
|
27421
|
-
const rows = [];
|
|
27422
|
-
if (remaining === 0) return rows;
|
|
27423
|
-
const { sql, params } = this.findingScanSql(scope);
|
|
27424
|
-
const taken = /* @__PURE__ */ new Map();
|
|
27425
|
-
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27426
|
-
const want = wanted.get(r.rule_id);
|
|
27427
|
-
if (want === void 0) continue;
|
|
27428
|
-
const have = taken.get(r.rule_id) ?? 0;
|
|
27429
|
-
if (have >= want) continue;
|
|
27430
|
-
taken.set(r.rule_id, have + 1);
|
|
27431
|
-
rows.push(r);
|
|
27432
|
-
remaining -= 1;
|
|
27433
|
-
if (remaining === 0) break;
|
|
27434
|
-
}
|
|
27435
|
-
return rows;
|
|
27436
|
-
}
|
|
27437
27432
|
/**
|
|
27438
27433
|
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27439
27434
|
*
|
|
@@ -27460,6 +27455,33 @@ var SqliteFindingsRepository = class {
|
|
|
27460
27455
|
yield toFlatFindingRow(r);
|
|
27461
27456
|
}
|
|
27462
27457
|
}
|
|
27458
|
+
/**
|
|
27459
|
+
* One finding by its own id, or null when no such row exists.
|
|
27460
|
+
*
|
|
27461
|
+
* A primary-key seek on `inspection_findings`, so its cost does not grow with
|
|
27462
|
+
* the store — and, unlike anything derived from a list page, it resolves a
|
|
27463
|
+
* finding of ANY age. That is what the Findings page's one-shot `?finding=`
|
|
27464
|
+
* deep link needs: the id it carries may name a finding thousands of rows
|
|
27465
|
+
* older than anything a first page holds.
|
|
27466
|
+
*
|
|
27467
|
+
* Deliberately UNFILTERED — no capture-kind, session or time predicate. It
|
|
27468
|
+
* RESOLVES an id; whether that row would survive the list's current filters is
|
|
27469
|
+
* a different question, and hiding the target because a filter excludes it is
|
|
27470
|
+
* worse than showing it.
|
|
27471
|
+
*
|
|
27472
|
+
* `groupId` on the result IS the rule id, so this one read answers both "which
|
|
27473
|
+
* type should the list select?" and "what does the drawer show?".
|
|
27474
|
+
*/
|
|
27475
|
+
findingInstance(id) {
|
|
27476
|
+
const row = this.db.prepare(
|
|
27477
|
+
`SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27478
|
+
FROM inspection_findings f
|
|
27479
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27480
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27481
|
+
WHERE f.id = ?`
|
|
27482
|
+
).get(id);
|
|
27483
|
+
return Promise.resolve(row === void 0 ? null : toInstanceDetail(toFlatFindingRow(row)));
|
|
27484
|
+
}
|
|
27463
27485
|
/**
|
|
27464
27486
|
* The one statement both instance-level scans run: every finding in scope,
|
|
27465
27487
|
* joined to its event and definition, newest first.
|
|
@@ -27493,17 +27515,7 @@ var SqliteFindingsRepository = class {
|
|
|
27493
27515
|
conditions.push("e.started_at >= ?");
|
|
27494
27516
|
params.push(isoToEpochMillis(scope.from));
|
|
27495
27517
|
}
|
|
27496
|
-
const sql = `SELECT
|
|
27497
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
27498
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27499
|
-
e.started_at AS occurred_at,
|
|
27500
|
-
e.source_tool AS source_tool,
|
|
27501
|
-
e.repo AS repo,
|
|
27502
|
-
e.file_path AS file,
|
|
27503
|
-
e.tool_name AS tool_name,
|
|
27504
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27505
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27506
|
-
${latestResolutionStatusSql("f")} AS latest_status
|
|
27518
|
+
const sql = `SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27507
27519
|
FROM audit_events e
|
|
27508
27520
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27509
27521
|
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -27517,6 +27529,26 @@ var SqliteFindingsRepository = class {
|
|
|
27517
27529
|
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27518
27530
|
const rows = this.db.prepare(
|
|
27519
27531
|
`SELECT rule_id,
|
|
27532
|
+
-- BARE columns beside max(latest_at), which is deliberate and
|
|
27533
|
+
-- is SQLite's documented behaviour: with a single min()/max()
|
|
27534
|
+
-- in an aggregate query, every bare column takes its value from
|
|
27535
|
+
-- the row that produced the extremum. So these are the severity
|
|
27536
|
+
-- and category of the definition whose finding is NEWEST, which
|
|
27537
|
+
-- is what the row-based build they replaced read off its first
|
|
27538
|
+
-- (newest-first) row.
|
|
27539
|
+
--
|
|
27540
|
+
-- min() is WRONG here and was the defect: inspection_definitions
|
|
27541
|
+
-- holds one row per rule VERSION (see its writer \u2014 a version bump
|
|
27542
|
+
-- mints a new row), so a rule whose severity moved between
|
|
27543
|
+
-- versions has several, and min() picks the ALPHABETICALLY
|
|
27544
|
+
-- smallest \u2014 'low' over 'medium', but 'critical' over 'high'.
|
|
27545
|
+
-- That is arbitrary in direction, and it feeds the badge, the
|
|
27546
|
+
-- filter, the facet counts and the primary sort key.
|
|
27547
|
+
--
|
|
27548
|
+
-- Adding a second min()/max() aggregate here would make these
|
|
27549
|
+
-- bare columns ambiguous again; keep max(latest_at) the only one.
|
|
27550
|
+
severity,
|
|
27551
|
+
category,
|
|
27520
27552
|
sum(tuple_count) AS instance_count,
|
|
27521
27553
|
max(latest_at) AS latest_at,
|
|
27522
27554
|
group_concat(source_tools) AS source_tools,
|
|
@@ -27527,6 +27559,14 @@ var SqliteFindingsRepository = class {
|
|
|
27527
27559
|
group_concat(tool_names) AS tool_names
|
|
27528
27560
|
FROM (
|
|
27529
27561
|
SELECT d.rule_id AS rule_id,
|
|
27562
|
+
-- Severity and category are columns of the DEFINITION, and
|
|
27563
|
+
-- a rule can have SEVERAL definitions (one per version), so
|
|
27564
|
+
-- these are grouped on below and resolved to the newest
|
|
27565
|
+
-- firing version by the outer query's bare-column select.
|
|
27566
|
+
-- They ride the aggregate because the type build has no rows
|
|
27567
|
+
-- to read them off \u2014 see buildFindingTypes.
|
|
27568
|
+
d.severity AS severity,
|
|
27569
|
+
d.category AS category,
|
|
27530
27570
|
e.event_type || '${TUPLE_SEP}' ||
|
|
27531
27571
|
(CASE WHEN f.finding_key IS NULL THEN '' ELSE 'k' END) || '${TUPLE_SEP}' ||
|
|
27532
27572
|
coalesce(latest.status, '') AS status_tuple,
|
|
@@ -27541,7 +27581,7 @@ var SqliteFindingsRepository = class {
|
|
|
27541
27581
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
27542
27582
|
ON latest.finding_key = f.finding_key
|
|
27543
27583
|
${scope.predicate}
|
|
27544
|
-
GROUP BY d.rule_id, status_tuple
|
|
27584
|
+
GROUP BY d.rule_id, d.severity, d.category, status_tuple
|
|
27545
27585
|
)
|
|
27546
27586
|
GROUP BY rule_id`
|
|
27547
27587
|
).all(scope.params);
|
|
@@ -27550,6 +27590,8 @@ var SqliteFindingsRepository = class {
|
|
|
27550
27590
|
r.rule_id,
|
|
27551
27591
|
{
|
|
27552
27592
|
instanceCount: r.instance_count,
|
|
27593
|
+
severity: r.severity,
|
|
27594
|
+
category: r.category,
|
|
27553
27595
|
sourceTools: splitConcat(r.source_tools),
|
|
27554
27596
|
actionsTaken: splitConcat(r.actions_taken),
|
|
27555
27597
|
statusInputs: splitConcat(r.status_inputs).map((tuple2) => {
|
|
@@ -27566,7 +27608,7 @@ var SqliteFindingsRepository = class {
|
|
|
27566
27608
|
latestDetectedAt: epochMillisToIso(r.latest_at),
|
|
27567
27609
|
// Free text only — joined and substring-matched, so group_concat's
|
|
27568
27610
|
// commas need no unpicking (a repo/path containing one still matches).
|
|
27569
|
-
// Left undefined (not '') when unfetched, so
|
|
27611
|
+
// Left undefined (not '') when unfetched, so buildFindingTypes can
|
|
27570
27612
|
// tell "no q this request" from "a group with no repo/file at all"
|
|
27571
27613
|
// and skip priming a haystack nothing will read.
|
|
27572
27614
|
...withSearchText ? {
|
|
@@ -27718,6 +27760,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27718
27760
|
this.markOwedStmt = db.prepare(
|
|
27719
27761
|
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27720
27762
|
);
|
|
27763
|
+
this.markCaptureBacklogOwedStmt = db.prepare(
|
|
27764
|
+
`UPDATE audit_events SET outbox_owed = 1
|
|
27765
|
+
WHERE synced_at IS NULL
|
|
27766
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27767
|
+
AND started_at < :before`
|
|
27768
|
+
);
|
|
27721
27769
|
this.stampStmt = db.prepare(
|
|
27722
27770
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27723
27771
|
);
|
|
@@ -27766,7 +27814,9 @@ var SqliteHistorySyncRepository = class {
|
|
|
27766
27814
|
);
|
|
27767
27815
|
this.disownCapturesStmt = db.prepare(
|
|
27768
27816
|
`UPDATE audit_events SET outbox_owed = NULL
|
|
27769
|
-
WHERE outbox_owed IS NOT NULL
|
|
27817
|
+
WHERE outbox_owed IS NOT NULL
|
|
27818
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27819
|
+
AND started_at < :attachedAt`
|
|
27770
27820
|
);
|
|
27771
27821
|
this.rearmStmt = db.prepare(
|
|
27772
27822
|
`UPDATE audit_events SET synced_at = NULL
|
|
@@ -27842,6 +27892,7 @@ var SqliteHistorySyncRepository = class {
|
|
|
27842
27892
|
freezeBoundaryStmt;
|
|
27843
27893
|
captureRowsStmt;
|
|
27844
27894
|
markOwedStmt;
|
|
27895
|
+
markCaptureBacklogOwedStmt;
|
|
27845
27896
|
captureSkipCountStmt;
|
|
27846
27897
|
disownCapturesStmt;
|
|
27847
27898
|
partitionStmt;
|
|
@@ -27905,6 +27956,23 @@ var SqliteHistorySyncRepository = class {
|
|
|
27905
27956
|
markCaptureOwed(id) {
|
|
27906
27957
|
this.markOwedStmt.run({ id });
|
|
27907
27958
|
}
|
|
27959
|
+
/**
|
|
27960
|
+
* Mark every capture already on disk as owed, as of `before`.
|
|
27961
|
+
*
|
|
27962
|
+
* The consent-time backfill, called once from `aka attach` when a human
|
|
27963
|
+
* grants existing-history consent — never from an ongoing drain pass, and
|
|
27964
|
+
* never inferred from a boundary that could later move. `before` is the
|
|
27965
|
+
* caller's own "now" at the moment consent was granted, so what this marks
|
|
27966
|
+
* is exactly the backlog the consent prompt already counted, not whatever a
|
|
27967
|
+
* later re-attach or key rotation might widen it to.
|
|
27968
|
+
*
|
|
27969
|
+
* Returns how many rows matched, for the caller to log or test against. Not a
|
|
27970
|
+
* count of NEWLY marked rows — a row still unsynced from an earlier call
|
|
27971
|
+
* matches again and is counted again, the same as `UPDATE`'s own `changes`.
|
|
27972
|
+
*/
|
|
27973
|
+
markCaptureBacklogOwed(before) {
|
|
27974
|
+
return Number(this.markCaptureBacklogOwedStmt.run({ before }).changes);
|
|
27975
|
+
}
|
|
27908
27976
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27909
27977
|
markSynced(ids, atMs) {
|
|
27910
27978
|
this.stampAll(ids, atMs);
|
|
@@ -28021,15 +28089,42 @@ var SqliteHistorySyncRepository = class {
|
|
|
28021
28089
|
*
|
|
28022
28090
|
* Delivery is a fact about ONE recipient: rows sent to the deployment a
|
|
28023
28091
|
* machine has just left are undelivered as far as the new one is concerned.
|
|
28024
|
-
* All
|
|
28025
|
-
* attributed to the wrong deployment,
|
|
28092
|
+
* All four in one transaction, so a crash between them cannot leave stamps
|
|
28093
|
+
* attributed to the wrong deployment, a boundary that belongs to another, or
|
|
28094
|
+
* a disown with no re-mark to follow it.
|
|
28026
28095
|
*
|
|
28027
28096
|
* The boundary is written HERE and only here, which is what freezes it: a
|
|
28028
28097
|
* re-attach to the SAME deployment (a key rotation) leaves the fingerprint
|
|
28029
28098
|
* unchanged, so this never runs and the backlog does not widen back over rows
|
|
28030
28099
|
* the live path has since delivered.
|
|
28100
|
+
*
|
|
28101
|
+
* `backfillCapturesBefore` is the caller's OWN "now" at the instant a human
|
|
28102
|
+
* granted existing-history consent for the deployment this call is arming —
|
|
28103
|
+
* a DIFFERENT instant from `backlogBefore`: the re-mark boundary is the GRANT
|
|
28104
|
+
* instant, `backlogBefore` is the ATTACH instant, and the two can be far
|
|
28105
|
+
* apart. Passed only when that grant is valid, since this method has no way
|
|
28106
|
+
* to check consent itself and must not mark a row owed for a machine that
|
|
28107
|
+
* never agreed to it. Applied AFTER the disown above, in the SAME
|
|
28108
|
+
* transaction: what the disown clears is every marker below `backlogBefore`,
|
|
28109
|
+
* which includes this deployment's OWN pre-attach rows — `aka attach` calls
|
|
28110
|
+
* `seedCaptureBacklogOwed` at the attach instant, so every row it marks sits
|
|
28111
|
+
* on the cleared side of that bound — and the re-mark in the same
|
|
28112
|
+
* transaction is what puts those rows back. A crash between the two cannot
|
|
28113
|
+
* strand the ledger disowned with nothing re-marked — the transaction either
|
|
28114
|
+
* lands whole or not at all, and a fingerprint mismatch that has not yet
|
|
28115
|
+
* committed re-enters this method on the very next pass. Omit it (the
|
|
28116
|
+
* structural-only tests do) to exercise the disown in isolation.
|
|
28117
|
+
*
|
|
28118
|
+
* The disown is bounded by `backlogBefore`, which is what keeps it from
|
|
28119
|
+
* touching a marker the NEW deployment's OWN live path has already set: B's
|
|
28120
|
+
* live path can mark a capture owed from the moment `aka attach` writes the
|
|
28121
|
+
* descriptor, before the drain's first pass ever reaches this method, and
|
|
28122
|
+
* such a row sits at or after the bound rather than below it. What keeps the
|
|
28123
|
+
* disown from eating THIS SAME CALL's own re-mark is the order, not the
|
|
28124
|
+
* bound — disown runs first, re-mark second, both inside the one
|
|
28125
|
+
* transaction above.
|
|
28031
28126
|
*/
|
|
28032
|
-
rearmFor(fingerprint, backlogBefore) {
|
|
28127
|
+
rearmFor(fingerprint, backlogBefore, backfillCapturesBefore) {
|
|
28033
28128
|
this.ensureRowStmt.run();
|
|
28034
28129
|
withTransaction(
|
|
28035
28130
|
this.db,
|
|
@@ -28037,7 +28132,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
28037
28132
|
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
28038
28133
|
this.rearmStmt.run();
|
|
28039
28134
|
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
28040
|
-
this.disownCapturesStmt.run();
|
|
28135
|
+
this.disownCapturesStmt.run({ attachedAt: backlogBefore });
|
|
28136
|
+
}
|
|
28137
|
+
if (backfillCapturesBefore !== void 0) {
|
|
28138
|
+
this.markCaptureBacklogOwedStmt.run({ before: backfillCapturesBefore });
|
|
28041
28139
|
}
|
|
28042
28140
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
28043
28141
|
},
|
|
@@ -28317,7 +28415,8 @@ function managedSettingsPaths(platform2 = process.platform) {
|
|
|
28317
28415
|
}
|
|
28318
28416
|
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28319
28417
|
}
|
|
28320
|
-
|
|
28418
|
+
var testOnlyManagedPaths = null;
|
|
28419
|
+
function readManagedSettings(paths = testOnlyManagedPaths ?? managedSettingsPaths()) {
|
|
28321
28420
|
for (const path of paths) {
|
|
28322
28421
|
let text;
|
|
28323
28422
|
try {
|
|
@@ -30797,7 +30896,7 @@ function toUtcDateString(ms) {
|
|
|
30797
30896
|
return new Date(ms).toISOString().slice(0, 10);
|
|
30798
30897
|
}
|
|
30799
30898
|
function isTimeseriesSeverity(s) {
|
|
30800
|
-
return s === "critical" || s === "high" || s === "medium";
|
|
30899
|
+
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
30801
30900
|
}
|
|
30802
30901
|
var SqliteSecurityRepository = class {
|
|
30803
30902
|
constructor(db, now = () => Date.now()) {
|
|
@@ -30926,12 +31025,16 @@ var SqliteSecurityRepository = class {
|
|
|
30926
31025
|
const now = this.now();
|
|
30927
31026
|
const windowStart = startOfUtcDay2(now) - (lenDays - 1) * DAY_MS4;
|
|
30928
31027
|
const rows = this.findingsInRange(windowStart, now);
|
|
30929
|
-
const points = Array.from(
|
|
30930
|
-
|
|
30931
|
-
|
|
30932
|
-
|
|
30933
|
-
|
|
30934
|
-
|
|
31028
|
+
const points = Array.from(
|
|
31029
|
+
{ length: numBuckets },
|
|
31030
|
+
(_, i) => ({
|
|
31031
|
+
timestamp: toUtcDateString(windowStart + i * bucketMs),
|
|
31032
|
+
critical: 0,
|
|
31033
|
+
high: 0,
|
|
31034
|
+
medium: 0,
|
|
31035
|
+
low: 0
|
|
31036
|
+
})
|
|
31037
|
+
);
|
|
30935
31038
|
for (const r of rows) {
|
|
30936
31039
|
const idx = Math.floor((r.occurredAt - windowStart) / bucketMs);
|
|
30937
31040
|
const bucket = points[idx];
|
|
@@ -31149,6 +31252,7 @@ var SqliteSecurityRepository = class {
|
|
|
31149
31252
|
`SELECT f.finding_key AS finding_key,
|
|
31150
31253
|
d.rule_id AS rule_id,
|
|
31151
31254
|
d.severity AS severity,
|
|
31255
|
+
e.repo AS repo,
|
|
31152
31256
|
e.file_path AS path,
|
|
31153
31257
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
31154
31258
|
latest.resolved_at AS latest_resolved_at
|
|
@@ -31168,6 +31272,7 @@ var SqliteSecurityRepository = class {
|
|
|
31168
31272
|
const items = rows.map((r) => ({
|
|
31169
31273
|
findingKey: r.finding_key,
|
|
31170
31274
|
ruleId: r.rule_id,
|
|
31275
|
+
repo: r.repo ?? "",
|
|
31171
31276
|
severity: r.severity,
|
|
31172
31277
|
path: r.path ?? "",
|
|
31173
31278
|
resolvedAt: new Date(r.latest_resolved_at).toISOString(),
|
|
@@ -31177,13 +31282,66 @@ var SqliteSecurityRepository = class {
|
|
|
31177
31282
|
}));
|
|
31178
31283
|
return Promise.resolve({ items });
|
|
31179
31284
|
}
|
|
31285
|
+
/**
|
|
31286
|
+
* Per-rule tallies of the findings that are still OPEN, whole-store.
|
|
31287
|
+
*
|
|
31288
|
+
* Scoped by status rather than by time, because the card this feeds is a to-do
|
|
31289
|
+
* list: a secret committed three weeks ago and never rotated is still the most
|
|
31290
|
+
* important thing to fix, and any window hides it. It carried a "newest N
|
|
31291
|
+
* findings" cap and then a range; the first meant a different span on every
|
|
31292
|
+
* machine, and the second reported "no recommendations" over live exposure.
|
|
31293
|
+
*
|
|
31294
|
+
* `open` mirrors `deriveFindingStatus` — at-rest, minus resolved and dismissed —
|
|
31295
|
+
* so a row's count is exactly what `?status=open&type=<rule>` returns. Note that
|
|
31296
|
+
* is NOT `severitySummary`'s `openAtRest`, which keeps dismissed findings (a
|
|
31297
|
+
* dismissal is a judgement, not a remediation) and drops untracked legacy rows.
|
|
31298
|
+
* The two answer different questions and only this one has to match a link.
|
|
31299
|
+
*
|
|
31300
|
+
* Aggregated in SQL: the result is O(distinct rule × category × severity), so a
|
|
31301
|
+
* whole-store scope costs a grouped scan rather than a row per finding.
|
|
31302
|
+
*/
|
|
31303
|
+
recommendationInputs() {
|
|
31304
|
+
const rows = allRows(
|
|
31305
|
+
this.db.prepare(
|
|
31306
|
+
`SELECT d.rule_id AS rule_id,
|
|
31307
|
+
d.category AS category,
|
|
31308
|
+
d.severity AS severity,
|
|
31309
|
+
COUNT(*) AS count
|
|
31310
|
+
FROM inspection_findings f
|
|
31311
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31312
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
31313
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
31314
|
+
ON latest.finding_key = f.finding_key
|
|
31315
|
+
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
31316
|
+
AND e.event_type = 'code_change'
|
|
31317
|
+
AND (
|
|
31318
|
+
f.finding_key IS NULL
|
|
31319
|
+
OR latest.status IS NULL
|
|
31320
|
+
OR latest.status NOT IN ('resolved', 'dismissed')
|
|
31321
|
+
)
|
|
31322
|
+
GROUP BY d.rule_id, d.category, d.severity`
|
|
31323
|
+
)
|
|
31324
|
+
);
|
|
31325
|
+
return Promise.resolve(
|
|
31326
|
+
rows.map((r) => ({
|
|
31327
|
+
ruleId: r.rule_id,
|
|
31328
|
+
category: r.category,
|
|
31329
|
+
severity: r.severity,
|
|
31330
|
+
count: r.count
|
|
31331
|
+
}))
|
|
31332
|
+
);
|
|
31333
|
+
}
|
|
31180
31334
|
// Findings whose parent event occurred in [fromMs, toMs), with the parent's
|
|
31181
31335
|
// epoch-millis timestamp. started_at is an INTEGER column, so the bounds stay
|
|
31182
31336
|
// numeric and the JS aggregations bucket/split on ms directly.
|
|
31183
31337
|
findingsInRange(fromMs, toMs) {
|
|
31184
31338
|
const rows = allRows(
|
|
31185
31339
|
this.db.prepare(
|
|
31186
|
-
`
|
|
31340
|
+
// `rule_id`/`category` cost nothing extra: inspection_definitions is already
|
|
31341
|
+
// joined for `severity`, so they are two more columns off a row this read
|
|
31342
|
+
// already fetches. They feed the recommended-actions rollup.
|
|
31343
|
+
`SELECT e.started_at AS occurred_at, d.severity AS severity, f.action_taken AS action_taken,
|
|
31344
|
+
d.rule_id AS rule_id, d.category AS category
|
|
31187
31345
|
FROM inspection_findings f
|
|
31188
31346
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31189
31347
|
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -31196,7 +31354,9 @@ var SqliteSecurityRepository = class {
|
|
|
31196
31354
|
return rows.map((r) => ({
|
|
31197
31355
|
occurredAt: r.occurred_at,
|
|
31198
31356
|
severity: r.severity,
|
|
31199
|
-
actionTaken: r.action_taken
|
|
31357
|
+
actionTaken: r.action_taken,
|
|
31358
|
+
ruleId: r.rule_id,
|
|
31359
|
+
category: r.category
|
|
31200
31360
|
}));
|
|
31201
31361
|
}
|
|
31202
31362
|
};
|
|
@@ -32366,6 +32526,43 @@ function openLocalDatabase(dir) {
|
|
|
32366
32526
|
};
|
|
32367
32527
|
}
|
|
32368
32528
|
|
|
32529
|
+
// ../../packages/persistence/src/egress-wire.ts
|
|
32530
|
+
import { createHash as createHash3 } from "crypto";
|
|
32531
|
+
function hashProjectKey(projectKey) {
|
|
32532
|
+
return createHash3("sha256").update(projectKey, "utf8").digest("hex");
|
|
32533
|
+
}
|
|
32534
|
+
function toIngestHit(hit) {
|
|
32535
|
+
return {
|
|
32536
|
+
host: hit.host,
|
|
32537
|
+
kind: hit.kind,
|
|
32538
|
+
name: hit.name,
|
|
32539
|
+
category: hit.category,
|
|
32540
|
+
trust: hit.trust,
|
|
32541
|
+
network: hit.network,
|
|
32542
|
+
method: hit.method,
|
|
32543
|
+
transport: hit.transport,
|
|
32544
|
+
url: hit.url,
|
|
32545
|
+
template: hit.template,
|
|
32546
|
+
dataClass: hit.dataClass,
|
|
32547
|
+
site: {
|
|
32548
|
+
file: hit.site.file,
|
|
32549
|
+
line: hit.site.line,
|
|
32550
|
+
dynamic: hit.site.dynamic,
|
|
32551
|
+
vendored: hit.site.vendored
|
|
32552
|
+
}
|
|
32553
|
+
};
|
|
32554
|
+
}
|
|
32555
|
+
function toEgressIngestRequest(input2) {
|
|
32556
|
+
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
32557
|
+
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
32558
|
+
return {
|
|
32559
|
+
projectKey: hashProjectKey(input2.projectKey),
|
|
32560
|
+
project: input2.project,
|
|
32561
|
+
reconcile,
|
|
32562
|
+
hits: hits.map(toIngestHit)
|
|
32563
|
+
};
|
|
32564
|
+
}
|
|
32565
|
+
|
|
32369
32566
|
// ../../packages/persistence/src/exception-policy.ts
|
|
32370
32567
|
var UserGrantPolicyProvider = class {
|
|
32371
32568
|
#exceptions;
|
|
@@ -32387,13 +32584,13 @@ var UserGrantPolicyProvider = class {
|
|
|
32387
32584
|
};
|
|
32388
32585
|
|
|
32389
32586
|
// ../../packages/persistence/src/finding-key.ts
|
|
32390
|
-
import { createHash as
|
|
32587
|
+
import { createHash as createHash4 } from "crypto";
|
|
32391
32588
|
function normalizeFilePath(filePath) {
|
|
32392
32589
|
return filePath.replaceAll("\\", "/");
|
|
32393
32590
|
}
|
|
32394
32591
|
function computeFindingKey(input2) {
|
|
32395
32592
|
const normalizedPath = normalizeFilePath(input2.filePath);
|
|
32396
|
-
return
|
|
32593
|
+
return createHash4("sha256").update(`${input2.ruleId}\0${normalizedPath}\0${input2.valueFingerprint}`).digest("hex");
|
|
32397
32594
|
}
|
|
32398
32595
|
|
|
32399
32596
|
// ../../packages/persistence/src/fingerprint.ts
|
|
@@ -32519,14 +32716,18 @@ function fingerprintValue(key, raw) {
|
|
|
32519
32716
|
return createHmac("sha256", key.material).update(raw, "utf8").digest("hex");
|
|
32520
32717
|
}
|
|
32521
32718
|
|
|
32522
|
-
// ../../packages/persistence/src/history-
|
|
32719
|
+
// ../../packages/persistence/src/history-backfill.ts
|
|
32523
32720
|
import { existsSync as existsSync4 } from "fs";
|
|
32524
32721
|
import { join as join9 } from "path";
|
|
32722
|
+
|
|
32723
|
+
// ../../packages/persistence/src/history-preview.ts
|
|
32724
|
+
import { existsSync as existsSync5 } from "fs";
|
|
32725
|
+
import { join as join10 } from "path";
|
|
32525
32726
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32526
32727
|
|
|
32527
32728
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
32528
|
-
import { existsSync as
|
|
32529
|
-
import { dirname as dirname3, join as
|
|
32729
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
32730
|
+
import { dirname as dirname3, join as join11, resolve } from "path";
|
|
32530
32731
|
|
|
32531
32732
|
// ../../packages/persistence/src/vault/crypto.ts
|
|
32532
32733
|
import {
|
|
@@ -32640,7 +32841,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
32640
32841
|
import { execFileSync } from "child_process";
|
|
32641
32842
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32642
32843
|
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32643
|
-
import { join as
|
|
32844
|
+
import { join as join12 } from "path";
|
|
32644
32845
|
var VAULT_OCCUPANT_REASON = {
|
|
32645
32846
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
32646
32847
|
gone: "the path was occupied but holds no keyring (removed while it was being created)",
|
|
@@ -32739,7 +32940,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32739
32940
|
throw asError(err);
|
|
32740
32941
|
}
|
|
32741
32942
|
try {
|
|
32742
|
-
writeFileSync3(
|
|
32943
|
+
writeFileSync3(join12(lock, LOCK_OWNER_FILE), `${owner}
|
|
32743
32944
|
`, { mode: DATA_FILE_MODE });
|
|
32744
32945
|
return true;
|
|
32745
32946
|
} catch (err) {
|
|
@@ -32748,7 +32949,7 @@ function claimRotationLock(lock, owner) {
|
|
|
32748
32949
|
}
|
|
32749
32950
|
}
|
|
32750
32951
|
function acquireRotationLock(keysDir2) {
|
|
32751
|
-
const lock =
|
|
32952
|
+
const lock = join12(keysDir2, `${VAULT_KEY_FILENAME}.lock`);
|
|
32752
32953
|
const owner = randomBytes2(16).toString("hex");
|
|
32753
32954
|
if (claimRotationLock(lock, owner)) return { lock, owner };
|
|
32754
32955
|
let held;
|
|
@@ -32775,7 +32976,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
32775
32976
|
}
|
|
32776
32977
|
function releaseRotationLock(lease) {
|
|
32777
32978
|
try {
|
|
32778
|
-
if (readFileSync7(
|
|
32979
|
+
if (readFileSync7(join12(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
32779
32980
|
} catch {
|
|
32780
32981
|
return;
|
|
32781
32982
|
}
|
|
@@ -32796,7 +32997,7 @@ var FileKeyProvider = class {
|
|
|
32796
32997
|
this.#keysDir = keysDir2;
|
|
32797
32998
|
}
|
|
32798
32999
|
get filePath() {
|
|
32799
|
-
return
|
|
33000
|
+
return join12(this.#keysDir, VAULT_KEY_FILENAME);
|
|
32800
33001
|
}
|
|
32801
33002
|
loadOrCreate() {
|
|
32802
33003
|
return asAsync(() => {
|
|
@@ -33461,55 +33662,19 @@ var SecretVault = class {
|
|
|
33461
33662
|
};
|
|
33462
33663
|
|
|
33463
33664
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
33464
|
-
import { existsSync as
|
|
33465
|
-
import { join as
|
|
33665
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
33666
|
+
import { join as join13 } from "path";
|
|
33466
33667
|
var MARKER = "warn-era-capped";
|
|
33467
33668
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
33468
33669
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
33469
|
-
const marker =
|
|
33470
|
-
if (
|
|
33670
|
+
const marker = join13(dataDir2, MARKER);
|
|
33671
|
+
if (existsSync7(marker)) return { capped: 0, skipped: "already-run" };
|
|
33471
33672
|
const capped = db.policies.capCategoryActions();
|
|
33472
33673
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
33473
33674
|
`, { mode: DATA_FILE_MODE });
|
|
33474
33675
|
return { capped };
|
|
33475
33676
|
}
|
|
33476
33677
|
|
|
33477
|
-
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
33478
|
-
function hashProjectKey(projectKey) {
|
|
33479
|
-
return createHash4("sha256").update(projectKey, "utf8").digest("hex");
|
|
33480
|
-
}
|
|
33481
|
-
function toIngestHit(hit) {
|
|
33482
|
-
return {
|
|
33483
|
-
host: hit.host,
|
|
33484
|
-
kind: hit.kind,
|
|
33485
|
-
name: hit.name,
|
|
33486
|
-
category: hit.category,
|
|
33487
|
-
trust: hit.trust,
|
|
33488
|
-
network: hit.network,
|
|
33489
|
-
method: hit.method,
|
|
33490
|
-
transport: hit.transport,
|
|
33491
|
-
url: hit.url,
|
|
33492
|
-
template: hit.template,
|
|
33493
|
-
dataClass: hit.dataClass,
|
|
33494
|
-
site: {
|
|
33495
|
-
file: hit.site.file,
|
|
33496
|
-
line: hit.site.line,
|
|
33497
|
-
dynamic: hit.site.dynamic,
|
|
33498
|
-
vendored: hit.site.vendored
|
|
33499
|
-
}
|
|
33500
|
-
};
|
|
33501
|
-
}
|
|
33502
|
-
function toEgressIngestRequest(input2) {
|
|
33503
|
-
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
33504
|
-
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
33505
|
-
return {
|
|
33506
|
-
projectKey: hashProjectKey(input2.projectKey),
|
|
33507
|
-
project: input2.project,
|
|
33508
|
-
reconcile,
|
|
33509
|
-
hits: hits.map(toIngestHit)
|
|
33510
|
-
};
|
|
33511
|
-
}
|
|
33512
|
-
|
|
33513
33678
|
// ../../packages/remote/src/http.ts
|
|
33514
33679
|
import { request as httpRequest } from "http";
|
|
33515
33680
|
import { request as httpsRequest } from "https";
|
|
@@ -33789,6 +33954,7 @@ function createRemoteClient(options) {
|
|
|
33789
33954
|
url: url2(ROUTES.shares),
|
|
33790
33955
|
body: JSON.stringify(validated.data)
|
|
33791
33956
|
});
|
|
33957
|
+
if (response.status === 404) throw new RemoteRouteAbsent(ROUTES.shares);
|
|
33792
33958
|
okBody(response);
|
|
33793
33959
|
},
|
|
33794
33960
|
async pollCommand() {
|
|
@@ -33811,19 +33977,51 @@ function createRemoteClient(options) {
|
|
|
33811
33977
|
};
|
|
33812
33978
|
}
|
|
33813
33979
|
|
|
33814
|
-
// ../../packages/
|
|
33980
|
+
// ../../packages/remote/src/failure-kind.ts
|
|
33815
33981
|
function statusOf(err) {
|
|
33816
33982
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
33817
33983
|
const { status } = err;
|
|
33818
33984
|
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
33819
33985
|
return status >= 100 && status <= 599 ? status : null;
|
|
33820
33986
|
}
|
|
33821
|
-
function
|
|
33822
|
-
|
|
33987
|
+
function nameOf(err) {
|
|
33988
|
+
if (typeof err !== "object" || err === null || !("name" in err)) return null;
|
|
33989
|
+
return typeof err.name === "string" ? err.name : null;
|
|
33990
|
+
}
|
|
33991
|
+
function classifyRemoteFailure(err) {
|
|
33992
|
+
switch (nameOf(err)) {
|
|
33993
|
+
case "RemoteRouteAbsent":
|
|
33994
|
+
return "route-absent";
|
|
33995
|
+
case "RemoteRequestInvalid":
|
|
33996
|
+
return "invalid-request";
|
|
33997
|
+
case "RemoteResponseInvalid":
|
|
33998
|
+
return "rejected";
|
|
33999
|
+
default:
|
|
34000
|
+
break;
|
|
34001
|
+
}
|
|
34002
|
+
const status = statusOf(err);
|
|
34003
|
+
if (status === null) return "unreachable";
|
|
34004
|
+
switch (status) {
|
|
33823
34005
|
case 401:
|
|
33824
34006
|
return "unauthorized";
|
|
33825
34007
|
case 403:
|
|
33826
34008
|
return "forbidden";
|
|
34009
|
+
case 429:
|
|
34010
|
+
return "unreachable";
|
|
34011
|
+
case 404:
|
|
34012
|
+
return "unreachable";
|
|
34013
|
+
default:
|
|
34014
|
+
return status >= 400 && status <= 499 ? "rejected" : "unreachable";
|
|
34015
|
+
}
|
|
34016
|
+
}
|
|
34017
|
+
|
|
34018
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
34019
|
+
function classifyFailure(err) {
|
|
34020
|
+
switch (classifyRemoteFailure(err)) {
|
|
34021
|
+
case "unauthorized":
|
|
34022
|
+
return "unauthorized";
|
|
34023
|
+
case "forbidden":
|
|
34024
|
+
return "forbidden";
|
|
33827
34025
|
default:
|
|
33828
34026
|
return "unreachable";
|
|
33829
34027
|
}
|
|
@@ -33846,10 +34044,10 @@ function withTimeout(promise2, ms) {
|
|
|
33846
34044
|
|
|
33847
34045
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
33848
34046
|
import { readFileSync as readFileSync8 } from "fs";
|
|
33849
|
-
import { join as
|
|
34047
|
+
import { join as join14 } from "path";
|
|
33850
34048
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
33851
34049
|
function forwardDropsPath(dataDir2) {
|
|
33852
|
-
return
|
|
34050
|
+
return join14(dataDir2, FORWARD_DROPS_FILENAME);
|
|
33853
34051
|
}
|
|
33854
34052
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
33855
34053
|
if (count <= 0) return;
|
|
@@ -33885,13 +34083,13 @@ function readForwardDrops(dataDir2) {
|
|
|
33885
34083
|
|
|
33886
34084
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
33887
34085
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
33888
|
-
import { readFileSync as
|
|
34086
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
33889
34087
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
33890
|
-
import { join as
|
|
34088
|
+
import { join as join24 } from "path";
|
|
33891
34089
|
|
|
33892
34090
|
// ../../packages/plugin-sdk/src/config.ts
|
|
33893
|
-
import { existsSync as
|
|
33894
|
-
import { join as
|
|
34091
|
+
import { existsSync as existsSync8 } from "fs";
|
|
34092
|
+
import { join as join15 } from "path";
|
|
33895
34093
|
|
|
33896
34094
|
// ../../packages/plugin-sdk/src/provider-env.ts
|
|
33897
34095
|
var DEFAULT_ANTHROPIC_HOST = "api.anthropic.com";
|
|
@@ -33955,8 +34153,8 @@ function providerFromModelId(modelId) {
|
|
|
33955
34153
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
33956
34154
|
try {
|
|
33957
34155
|
ensureLayoutDirSync(base);
|
|
33958
|
-
const settingsFile =
|
|
33959
|
-
if (
|
|
34156
|
+
const settingsFile = join15(settingsDir(base), "settings.json");
|
|
34157
|
+
if (existsSync8(settingsFile)) tightenFile(settingsFile);
|
|
33960
34158
|
} catch {
|
|
33961
34159
|
}
|
|
33962
34160
|
migrateLegacyLayout(base);
|
|
@@ -33981,7 +34179,7 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
33981
34179
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
33982
34180
|
import { readdirSync as readdirSync2, readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
33983
34181
|
import { homedir as homedir2 } from "os";
|
|
33984
|
-
import { basename as basename3, join as
|
|
34182
|
+
import { basename as basename3, join as join17 } from "path";
|
|
33985
34183
|
|
|
33986
34184
|
// ../../packages/detections/src/egress/registry.ts
|
|
33987
34185
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -37112,8 +37310,8 @@ function scanText(text, ruleVersions) {
|
|
|
37112
37310
|
}
|
|
37113
37311
|
|
|
37114
37312
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
37115
|
-
import { existsSync as
|
|
37116
|
-
import { basename as basename2, dirname as dirname4, isAbsolute, join as
|
|
37313
|
+
import { existsSync as existsSync9, readFileSync as readFileSync9, statSync as statSync6 } from "fs";
|
|
37314
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join16, sep as sep2 } from "path";
|
|
37117
37315
|
function resolveRepoIdentity(cwd) {
|
|
37118
37316
|
try {
|
|
37119
37317
|
const root = findGitRoot(cwd);
|
|
@@ -37146,32 +37344,32 @@ function resolveRepoNwo(cwd) {
|
|
|
37146
37344
|
function findGitRoot(start) {
|
|
37147
37345
|
let dir = start;
|
|
37148
37346
|
for (; ; ) {
|
|
37149
|
-
if (
|
|
37347
|
+
if (existsSync9(join16(dir, ".git"))) return dir;
|
|
37150
37348
|
const parent = dirname4(dir);
|
|
37151
37349
|
if (parent === dir) return void 0;
|
|
37152
37350
|
dir = parent;
|
|
37153
37351
|
}
|
|
37154
37352
|
}
|
|
37155
37353
|
function resolveGitContext(root) {
|
|
37156
|
-
const dotGit =
|
|
37354
|
+
const dotGit = join16(root, ".git");
|
|
37157
37355
|
try {
|
|
37158
37356
|
if (statSync6(dotGit).isDirectory()) {
|
|
37159
|
-
return { configPath:
|
|
37357
|
+
return { configPath: join16(dotGit, "config"), headRoot: root };
|
|
37160
37358
|
}
|
|
37161
37359
|
} catch {
|
|
37162
37360
|
return void 0;
|
|
37163
37361
|
}
|
|
37164
37362
|
const target = /^gitdir:\s*(.+?)\s*$/m.exec(safeRead(dotGit) ?? "")?.[1];
|
|
37165
37363
|
if (!target) return void 0;
|
|
37166
|
-
const gitdir = isAbsolute(target) ? target :
|
|
37167
|
-
if (
|
|
37168
|
-
return { configPath:
|
|
37364
|
+
const gitdir = isAbsolute(target) ? target : join16(root, target);
|
|
37365
|
+
if (existsSync9(join16(gitdir, "config"))) {
|
|
37366
|
+
return { configPath: join16(gitdir, "config"), headRoot: root };
|
|
37169
37367
|
}
|
|
37170
|
-
const commonRaw = safeRead(
|
|
37368
|
+
const commonRaw = safeRead(join16(gitdir, "commondir"))?.trim();
|
|
37171
37369
|
if (!commonRaw) return void 0;
|
|
37172
|
-
const commonGitDir = isAbsolute(commonRaw) ? commonRaw :
|
|
37370
|
+
const commonGitDir = isAbsolute(commonRaw) ? commonRaw : join16(gitdir, commonRaw);
|
|
37173
37371
|
const headRoot = basename2(commonGitDir) === ".git" ? dirname4(commonGitDir) : root;
|
|
37174
|
-
return { configPath:
|
|
37372
|
+
return { configPath: join16(commonGitDir, "config"), headRoot };
|
|
37175
37373
|
}
|
|
37176
37374
|
function safeRead(path) {
|
|
37177
37375
|
try {
|
|
@@ -37254,7 +37452,7 @@ function buildIngestEvent(input2) {
|
|
|
37254
37452
|
}
|
|
37255
37453
|
|
|
37256
37454
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
37257
|
-
import { existsSync as
|
|
37455
|
+
import { existsSync as existsSync10 } from "fs";
|
|
37258
37456
|
import { fileURLToPath } from "url";
|
|
37259
37457
|
import { Worker } from "worker_threads";
|
|
37260
37458
|
var ISOLATED_SCAN_BUDGET_MS = 2e3;
|
|
@@ -37268,7 +37466,7 @@ function resolveWorkerUrl() {
|
|
|
37268
37466
|
for (const name of ["scan-worker.js", "scan-worker.ts"]) {
|
|
37269
37467
|
const candidate = new URL(name, import.meta.url);
|
|
37270
37468
|
try {
|
|
37271
|
-
if (
|
|
37469
|
+
if (existsSync10(fileURLToPath(candidate))) {
|
|
37272
37470
|
resolvedWorkerUrl = candidate;
|
|
37273
37471
|
return candidate;
|
|
37274
37472
|
}
|
|
@@ -37731,10 +37929,45 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
37731
37929
|
};
|
|
37732
37930
|
}
|
|
37733
37931
|
|
|
37932
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
37933
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
37934
|
+
import { join as join19 } from "path";
|
|
37935
|
+
|
|
37936
|
+
// ../../packages/plugin-sdk/src/model-governance.ts
|
|
37937
|
+
import {
|
|
37938
|
+
closeSync as closeSync2,
|
|
37939
|
+
fstatSync,
|
|
37940
|
+
mkdirSync as mkdirSync2,
|
|
37941
|
+
openSync as openSync2,
|
|
37942
|
+
readFileSync as readFileSync11,
|
|
37943
|
+
readSync,
|
|
37944
|
+
writeFileSync as writeFileSync5
|
|
37945
|
+
} from "fs";
|
|
37946
|
+
import { join as join18 } from "path";
|
|
37947
|
+
var TAIL_BYTES = 256 * 1024;
|
|
37948
|
+
|
|
37949
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
37950
|
+
var HOST_FEATURE = {
|
|
37951
|
+
ModelSwitch: "model-switch",
|
|
37952
|
+
VaultPointerDisplay: "vault-pointer-display"
|
|
37953
|
+
};
|
|
37954
|
+
var HOST_FLOORS = {
|
|
37955
|
+
[HOST_FEATURE.ModelSwitch]: {
|
|
37956
|
+
label: "model-switch protection",
|
|
37957
|
+
hookEvents: ["PreModelSwitch", "PostModelSwitch"],
|
|
37958
|
+
since: "2.1.251"
|
|
37959
|
+
},
|
|
37960
|
+
[HOST_FEATURE.VaultPointerDisplay]: {
|
|
37961
|
+
label: "vault pointer display",
|
|
37962
|
+
hookEvents: ["MessageDisplay"],
|
|
37963
|
+
since: "2.1.152"
|
|
37964
|
+
}
|
|
37965
|
+
};
|
|
37966
|
+
|
|
37734
37967
|
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
37735
37968
|
var import_ignore = __toESM(require_ignore(), 1);
|
|
37736
|
-
import { readFileSync as
|
|
37737
|
-
import { join as
|
|
37969
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
37970
|
+
import { join as join20 } from "path";
|
|
37738
37971
|
|
|
37739
37972
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
37740
37973
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
@@ -37765,22 +37998,9 @@ function resolveInventoryContext(input2) {
|
|
|
37765
37998
|
return ctx;
|
|
37766
37999
|
}
|
|
37767
38000
|
|
|
37768
|
-
// ../../packages/plugin-sdk/src/model-governance.ts
|
|
37769
|
-
import {
|
|
37770
|
-
closeSync as closeSync2,
|
|
37771
|
-
fstatSync,
|
|
37772
|
-
mkdirSync as mkdirSync2,
|
|
37773
|
-
openSync as openSync2,
|
|
37774
|
-
readFileSync as readFileSync12,
|
|
37775
|
-
readSync,
|
|
37776
|
-
writeFileSync as writeFileSync5
|
|
37777
|
-
} from "fs";
|
|
37778
|
-
import { join as join18 } from "path";
|
|
37779
|
-
var TAIL_BYTES = 256 * 1024;
|
|
37780
|
-
|
|
37781
38001
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
37782
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
37783
|
-
import { join as
|
|
38002
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
38003
|
+
import { join as join21 } from "path";
|
|
37784
38004
|
|
|
37785
38005
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
37786
38006
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
@@ -37822,8 +38042,8 @@ function createPolicyResolver(bundle) {
|
|
|
37822
38042
|
}
|
|
37823
38043
|
|
|
37824
38044
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
37825
|
-
import { existsSync as
|
|
37826
|
-
import { basename as basename5, join as
|
|
38045
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
|
|
38046
|
+
import { basename as basename5, join as join22 } from "path";
|
|
37827
38047
|
|
|
37828
38048
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
37829
38049
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -37894,6 +38114,14 @@ function safeMaskedMatch(rawMatch) {
|
|
|
37894
38114
|
// ../../packages/plugin-sdk/src/runtime.ts
|
|
37895
38115
|
import { randomUUID as randomUUID14 } from "crypto";
|
|
37896
38116
|
var ENFORCEMENT_CEILING_ENABLED = false;
|
|
38117
|
+
function applyEnforcementCeiling(action, policyMode, enabled) {
|
|
38118
|
+
if (!enabled || policyMode !== "warn") return action;
|
|
38119
|
+
return action === "block" || action === "redact" ? "warn" : action;
|
|
38120
|
+
}
|
|
38121
|
+
function resolveEnforcedAction(action, opts) {
|
|
38122
|
+
const degraded = !opts.rewritable && action === "redact" ? builtinPolicyToAction(opts.redactFallback) : action;
|
|
38123
|
+
return applyEnforcementCeiling(degraded, opts.policyMode, opts.ceilingEnabled);
|
|
38124
|
+
}
|
|
37897
38125
|
function startTiming() {
|
|
37898
38126
|
try {
|
|
37899
38127
|
return performance.now();
|
|
@@ -38002,12 +38230,12 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
38002
38230
|
}
|
|
38003
38231
|
function actionForFinding(finding, excepted, rewritable = true) {
|
|
38004
38232
|
if (excepted?.has(finding)) return "allow";
|
|
38005
|
-
|
|
38006
|
-
|
|
38007
|
-
|
|
38008
|
-
|
|
38009
|
-
|
|
38010
|
-
|
|
38233
|
+
return resolveEnforcedAction(resolveAction(finding.ruleId, finding.category), {
|
|
38234
|
+
policyMode,
|
|
38235
|
+
redactFallback,
|
|
38236
|
+
rewritable,
|
|
38237
|
+
ceilingEnabled: ENFORCEMENT_CEILING_ENABLED
|
|
38238
|
+
});
|
|
38011
38239
|
}
|
|
38012
38240
|
function decide(findings, text, excepted, rewritable = true) {
|
|
38013
38241
|
if (findings.length === 0) return { action: "log", text, findings: [] };
|
|
@@ -38256,7 +38484,7 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
38256
38484
|
|
|
38257
38485
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
38258
38486
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
38259
|
-
import { join as
|
|
38487
|
+
import { join as join23 } from "path";
|
|
38260
38488
|
|
|
38261
38489
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
38262
38490
|
function redactedPlaceholder(category) {
|
|
@@ -38609,7 +38837,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
38609
38837
|
}
|
|
38610
38838
|
function createForwardPolicy(deps) {
|
|
38611
38839
|
const now = deps.now ?? (() => Date.now());
|
|
38612
|
-
const file2 =
|
|
38840
|
+
const file2 = join24(deps.dir, STATE_FILENAME);
|
|
38613
38841
|
let state = null;
|
|
38614
38842
|
let loading = null;
|
|
38615
38843
|
async function readState() {
|
|
@@ -39331,8 +39559,8 @@ function toolAuditEvent(input2) {
|
|
|
39331
39559
|
}
|
|
39332
39560
|
|
|
39333
39561
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
39334
|
-
import { readFileSync as
|
|
39335
|
-
import { join as
|
|
39562
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
39563
|
+
import { join as join25 } from "path";
|
|
39336
39564
|
|
|
39337
39565
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
39338
39566
|
import { createHash as createHash6 } from "crypto";
|
|
@@ -39349,14 +39577,14 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
39349
39577
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
39350
39578
|
|
|
39351
39579
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
39352
|
-
import { readFileSync as
|
|
39580
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
39353
39581
|
var manifestBuildCache = /* @__PURE__ */ new Map();
|
|
39354
39582
|
function readManifestBuild(manifestUrl, packageName) {
|
|
39355
39583
|
const key = manifestUrl.href;
|
|
39356
39584
|
if (!manifestBuildCache.has(key)) {
|
|
39357
39585
|
let build;
|
|
39358
39586
|
try {
|
|
39359
|
-
const manifest = JSON.parse(
|
|
39587
|
+
const manifest = JSON.parse(readFileSync17(manifestUrl, "utf8"));
|
|
39360
39588
|
build = typeof manifest.version === "string" && manifest.version.length > 0 ? { package: packageName, version: manifest.version } : void 0;
|
|
39361
39589
|
} catch {
|
|
39362
39590
|
build = void 0;
|
|
@@ -39383,7 +39611,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
39383
39611
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
39384
39612
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
39385
39613
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
39386
|
-
import { join as
|
|
39614
|
+
import { join as join26 } from "path";
|
|
39387
39615
|
|
|
39388
39616
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
39389
39617
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -39407,7 +39635,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
39407
39635
|
|
|
39408
39636
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
39409
39637
|
function createPolicyStore(dir = dataDir()) {
|
|
39410
|
-
const file2 =
|
|
39638
|
+
const file2 = join26(dir, "policy-cache.json");
|
|
39411
39639
|
async function read() {
|
|
39412
39640
|
try {
|
|
39413
39641
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -39638,11 +39866,11 @@ function readStorePosture(dbPath2) {
|
|
|
39638
39866
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
39639
39867
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
39640
39868
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
39641
|
-
import { join as
|
|
39869
|
+
import { join as join27 } from "path";
|
|
39642
39870
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
39643
39871
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
39644
|
-
const file2 =
|
|
39645
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
39872
|
+
const file2 = join27(dir, "posture-state.json");
|
|
39873
|
+
const legacyFile = legacyDir === void 0 ? null : join27(legacyDir, "posture-state.json");
|
|
39646
39874
|
async function persist(state) {
|
|
39647
39875
|
await ensureDataDir(dir);
|
|
39648
39876
|
const tmp = `${file2}.${randomUUID17()}.tmp`;
|
|
@@ -39710,8 +39938,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
39710
39938
|
}
|
|
39711
39939
|
|
|
39712
39940
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
39713
|
-
import { readFileSync as
|
|
39714
|
-
import { join as
|
|
39941
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
39942
|
+
import { join as join28 } from "path";
|
|
39715
39943
|
|
|
39716
39944
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
39717
39945
|
var REFUSAL_LINES = {
|
|
@@ -40183,7 +40411,7 @@ var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
|
40183
40411
|
|
|
40184
40412
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
40185
40413
|
import { writeFileSync as writeFileSync8 } from "fs";
|
|
40186
|
-
import { join as
|
|
40414
|
+
import { join as join29 } from "path";
|
|
40187
40415
|
|
|
40188
40416
|
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
40189
40417
|
var RANK = Object.fromEntries(
|
|
@@ -40191,9 +40419,9 @@ var RANK = Object.fromEntries(
|
|
|
40191
40419
|
);
|
|
40192
40420
|
|
|
40193
40421
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
40194
|
-
import { mkdtempSync, readFileSync as
|
|
40422
|
+
import { mkdtempSync, readFileSync as readFileSync19, rmdirSync, rmSync as rmSync7, writeFileSync as writeFileSync9 } from "fs";
|
|
40195
40423
|
import { tmpdir } from "os";
|
|
40196
|
-
import { basename as basename6, dirname as dirname6, join as
|
|
40424
|
+
import { basename as basename6, dirname as dirname6, join as join30 } from "path";
|
|
40197
40425
|
var SuppressionEntrySchema = external_exports.object({
|
|
40198
40426
|
ruleId: external_exports.string(),
|
|
40199
40427
|
category: DetectionCategory,
|
|
@@ -40238,11 +40466,11 @@ var PersistedPlanSchema = external_exports.object({
|
|
|
40238
40466
|
var TRIAGE_STATUSES = ["complete", "complete:no-history", "skipped:no-consent"];
|
|
40239
40467
|
|
|
40240
40468
|
// src/history/transcripts.ts
|
|
40241
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
40469
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync20 } from "fs";
|
|
40242
40470
|
import { homedir as homedir3 } from "os";
|
|
40243
|
-
import { join as
|
|
40471
|
+
import { join as join31 } from "path";
|
|
40244
40472
|
function transcriptsDir(home) {
|
|
40245
|
-
return
|
|
40473
|
+
return join31(home ?? homedir3(), ".claude", "projects");
|
|
40246
40474
|
}
|
|
40247
40475
|
function isRecord(value) {
|
|
40248
40476
|
return typeof value === "object" && value !== null;
|
|
@@ -40490,7 +40718,7 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
40490
40718
|
return;
|
|
40491
40719
|
}
|
|
40492
40720
|
for (const project of projects) {
|
|
40493
|
-
const projectDir =
|
|
40721
|
+
const projectDir = join31(dir, project);
|
|
40494
40722
|
let files;
|
|
40495
40723
|
try {
|
|
40496
40724
|
files = readdirSync5(projectDir).filter((name) => name.endsWith(".jsonl"));
|
|
@@ -40500,10 +40728,10 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
40500
40728
|
for (const file2 of files) {
|
|
40501
40729
|
if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
|
|
40502
40730
|
continue;
|
|
40503
|
-
const filePath =
|
|
40731
|
+
const filePath = join31(projectDir, file2);
|
|
40504
40732
|
let content;
|
|
40505
40733
|
try {
|
|
40506
|
-
content =
|
|
40734
|
+
content = readFileSync20(filePath, "utf8");
|
|
40507
40735
|
} catch {
|
|
40508
40736
|
continue;
|
|
40509
40737
|
}
|
|
@@ -40635,20 +40863,20 @@ async function scanHistory(config2, opts = {}, onHit) {
|
|
|
40635
40863
|
}
|
|
40636
40864
|
|
|
40637
40865
|
// src/history/tail-scrub.ts
|
|
40638
|
-
import { readFileSync as
|
|
40866
|
+
import { readFileSync as readFileSync22, renameSync as renameSync6, rmSync as rmSync9, statSync as statSync11, writeFileSync as writeFileSync11 } from "fs";
|
|
40639
40867
|
|
|
40640
40868
|
// src/remediation/redact.ts
|
|
40641
40869
|
import {
|
|
40642
40870
|
lstatSync as lstatSync4,
|
|
40643
40871
|
readdirSync as readdirSync6,
|
|
40644
|
-
readFileSync as
|
|
40872
|
+
readFileSync as readFileSync21,
|
|
40645
40873
|
realpathSync as realpathSync4,
|
|
40646
40874
|
renameSync as renameSync5,
|
|
40647
40875
|
rmSync as rmSync8,
|
|
40648
40876
|
statSync as statSync10,
|
|
40649
40877
|
writeFileSync as writeFileSync10
|
|
40650
40878
|
} from "fs";
|
|
40651
|
-
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute2, join as
|
|
40879
|
+
import { basename as basename7, dirname as dirname7, isAbsolute as isAbsolute2, join as join32, relative, resolve as resolve2 } from "path";
|
|
40652
40880
|
function platformRedactionScope(home) {
|
|
40653
40881
|
return { artifactRoots: [transcriptsDir(home)] };
|
|
40654
40882
|
}
|
|
@@ -40679,7 +40907,7 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
40679
40907
|
if (realPath === null) return null;
|
|
40680
40908
|
const statBefore = statSync11(realPath);
|
|
40681
40909
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
40682
|
-
const content =
|
|
40910
|
+
const content = readFileSync22(realPath, "utf8");
|
|
40683
40911
|
const lines = content.split("\n");
|
|
40684
40912
|
let rewritten = 0;
|
|
40685
40913
|
for (const [i, line] of lines.entries()) {
|
|
@@ -40729,11 +40957,11 @@ import {
|
|
|
40729
40957
|
fstatSync as fstatSync2,
|
|
40730
40958
|
mkdirSync as mkdirSync5,
|
|
40731
40959
|
openSync as openSync3,
|
|
40732
|
-
readFileSync as
|
|
40960
|
+
readFileSync as readFileSync23,
|
|
40733
40961
|
readSync as readSync2,
|
|
40734
40962
|
writeFileSync as writeFileSync12
|
|
40735
40963
|
} from "fs";
|
|
40736
|
-
import { join as
|
|
40964
|
+
import { join as join33 } from "path";
|
|
40737
40965
|
|
|
40738
40966
|
// src/history/usage.ts
|
|
40739
40967
|
var NO_PROJECT_CWD = "/nonexistent/aka-reconciler/no-project";
|