@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
|
@@ -492,8 +492,8 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// ../../packages/plugin-sdk/src/config.ts
|
|
495
|
-
import { existsSync as
|
|
496
|
-
import { join as
|
|
495
|
+
import { existsSync as existsSync8 } from "fs";
|
|
496
|
+
import { join as join14 } from "path";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/attached-derived.ts
|
|
499
499
|
import { rmSync } from "fs";
|
|
@@ -20743,13 +20743,11 @@ var FindingGroup = external_exports.object({
|
|
|
20743
20743
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20744
20744
|
instances: external_exports.array(FindingInstance),
|
|
20745
20745
|
// Derived from instances' statuses with open-dominates precedence (see
|
|
20746
|
-
//
|
|
20746
|
+
// foldGroupStatus). Undefined only when no instance carries a status.
|
|
20747
20747
|
status: FindingStatus.optional(),
|
|
20748
|
-
// The distinct people across the WHOLE group, not just the
|
|
20749
|
-
//
|
|
20750
|
-
//
|
|
20751
|
-
// instance carries a user, or when the store supplied whole-group folds
|
|
20752
|
-
// without one.
|
|
20748
|
+
// The distinct people across the WHOLE group, not just the instances
|
|
20749
|
+
// carried here. Undefined when no instance carries a user, or when the
|
|
20750
|
+
// store supplied whole-group folds without one.
|
|
20753
20751
|
users: external_exports.array(FindingUser).optional()
|
|
20754
20752
|
}).meta({ id: "FindingGroup" });
|
|
20755
20753
|
var FindingStats = external_exports.object({
|
|
@@ -20778,21 +20776,31 @@ var FindingFacets = external_exports.object({
|
|
|
20778
20776
|
// counted under no value.
|
|
20779
20777
|
status: external_exports.array(FindingFacetItem),
|
|
20780
20778
|
// Host tool (attributes.tool_name). Present only on the instance-level
|
|
20781
|
-
// reads, which can filter by it; the
|
|
20779
|
+
// reads, which can filter by it; the type-level read omits the dimension
|
|
20782
20780
|
// because a group spans tools.
|
|
20783
20781
|
tool: external_exports.array(FindingFacetItem).optional()
|
|
20784
20782
|
}).meta({ id: "FindingFacets" });
|
|
20785
|
-
var
|
|
20786
|
-
|
|
20783
|
+
var FindingTypeSummary = FindingGroup.omit({ instances: true, match: true }).meta({
|
|
20784
|
+
id: "FindingTypeSummary"
|
|
20785
|
+
});
|
|
20786
|
+
var DEFAULT_FINDING_TYPES_LIMIT = 50;
|
|
20787
|
+
var MAX_FINDING_TYPES_LIMIT = 100;
|
|
20788
|
+
var ListFindingTypesQuery = external_exports.object({
|
|
20787
20789
|
// NOTE: severity filters by Severity (critical/high/medium/low), not by
|
|
20788
|
-
// FindingAction.
|
|
20790
|
+
// FindingAction. It narrows TYPES: a type's severity is the one its newest
|
|
20791
|
+
// firing version carries, and this list pages types.
|
|
20792
|
+
//
|
|
20793
|
+
// That is NOT a claim the findings of a type share it. A rule can hold several
|
|
20794
|
+
// definition versions at different severities, so a type kept by this filter
|
|
20795
|
+
// can hold findings that individually do not match — see totals.findings on
|
|
20796
|
+
// ListFindingTypesResponse, which counts them all.
|
|
20789
20797
|
severity: external_exports.array(Severity).optional(),
|
|
20790
20798
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20791
20799
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20792
20800
|
action: external_exports.array(FindingAction).optional(),
|
|
20793
|
-
// Matches a
|
|
20794
|
-
// individual
|
|
20795
|
-
//
|
|
20801
|
+
// Matches a type's DERIVED status (see FindingGroup.status), not its
|
|
20802
|
+
// individual findings' — so a filtered row's status always reads one of the
|
|
20803
|
+
// requested values.
|
|
20796
20804
|
status: external_exports.array(FindingStatus).optional(),
|
|
20797
20805
|
q: external_exports.string().optional(),
|
|
20798
20806
|
// Scope to findings whose event carries this session id (the Activity page's
|
|
@@ -20802,23 +20810,37 @@ var ListGroupedFindingsQuery = external_exports.object({
|
|
|
20802
20810
|
// from a time-scoped page (Activity's range) can carry that scope. Absent
|
|
20803
20811
|
// means all time — this list has no default window.
|
|
20804
20812
|
from: external_exports.iso.datetime().optional(),
|
|
20805
|
-
// A
|
|
20806
|
-
//
|
|
20807
|
-
//
|
|
20808
|
-
//
|
|
20809
|
-
//
|
|
20813
|
+
// A RULE id that must appear in the page even when the cursor has already
|
|
20814
|
+
// advanced past its sort position. This is what keeps the selected type
|
|
20815
|
+
// visible in the list once it paginates: the target is appended out of sort
|
|
20816
|
+
// order rather than scanned forward for. Never affects totals, facets or the
|
|
20817
|
+
// cursor. Unlike the grouped read this replaces, it names a rule only — an
|
|
20818
|
+
// instance id is resolved by `findingInstance`, which is a primary-key seek
|
|
20819
|
+
// and so is not bounded by what any page happens to hold.
|
|
20810
20820
|
includeId: external_exports.string().optional(),
|
|
20811
|
-
|
|
20812
|
-
limit: external_exports.coerce.number().int().min(1).max(100).optional(),
|
|
20821
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_TYPES_LIMIT).optional(),
|
|
20813
20822
|
cursor: external_exports.string().optional()
|
|
20814
20823
|
});
|
|
20815
|
-
var
|
|
20824
|
+
var ListFindingTypesResponse = external_exports.object({
|
|
20816
20825
|
totals: external_exports.object({
|
|
20826
|
+
// Findings belonging to the matching TYPES — not findings that each match
|
|
20827
|
+
// the filters. The filters here select types, so a type that survives
|
|
20828
|
+
// contributes its whole instanceCount.
|
|
20829
|
+
//
|
|
20830
|
+
// `status` is the one exception, narrowed per finding via
|
|
20831
|
+
// countInstancesByStatus. `severity`, `provider` and `action` are not, so
|
|
20832
|
+
// this can exceed what the instance read reports for the same filters: a
|
|
20833
|
+
// rule whose severity moved between versions is kept on its newest and
|
|
20834
|
+
// still counts its older findings. Narrowing the other three needs
|
|
20835
|
+
// per-dimension counts the aggregate does not carry today.
|
|
20817
20836
|
findings: external_exports.number().int().nonnegative(),
|
|
20818
|
-
|
|
20837
|
+
// Counts TYPES, which is the unit this read pages. The instance read's
|
|
20838
|
+
// own totals count findings; the two deliberately answer different
|
|
20839
|
+
// questions and are never summed.
|
|
20840
|
+
types: external_exports.number().int().nonnegative()
|
|
20819
20841
|
}),
|
|
20820
20842
|
facets: FindingFacets,
|
|
20821
|
-
items: external_exports.array(
|
|
20843
|
+
items: external_exports.array(FindingTypeSummary),
|
|
20822
20844
|
nextCursor: external_exports.string().nullable(),
|
|
20823
20845
|
// Present only on session-scoped queries (`sessionId` set): per ruleId, how
|
|
20824
20846
|
// many times that rule fired in the session's persisted transcript. Findings
|
|
@@ -20826,7 +20848,7 @@ var ListGroupedFindingsResponse = external_exports.object({
|
|
|
20826
20848
|
// every firing, so the two numbers legitimately differ — this map lets a
|
|
20827
20849
|
// session-scoped view show both.
|
|
20828
20850
|
sessionFirings: external_exports.record(external_exports.string(), external_exports.number().int().nonnegative()).optional()
|
|
20829
|
-
}).meta({ id: "
|
|
20851
|
+
}).meta({ id: "ListFindingTypesResponse" });
|
|
20830
20852
|
var ApplyFindingActionRequest = external_exports.object({
|
|
20831
20853
|
// 'quarantined' is system-assigned (see FindingAction) — clients may not set
|
|
20832
20854
|
// it, so it is excluded from the request contract. The mapping helper
|
|
@@ -20856,12 +20878,13 @@ var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
|
20856
20878
|
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
20857
20879
|
var ListFindingInstancesQuery = external_exports.object({
|
|
20858
20880
|
severity: external_exports.array(Severity).optional(),
|
|
20859
|
-
// Rule ids, the same vocabulary the
|
|
20881
|
+
// Rule ids, the same vocabulary the types list's `subtype` carries. Pinning
|
|
20882
|
+
// ONE of them is how the master/detail view scopes its right-hand panel.
|
|
20860
20883
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
20861
20884
|
provider: external_exports.array(FindingProvider).optional(),
|
|
20862
20885
|
action: external_exports.array(FindingAction).optional(),
|
|
20863
20886
|
// Matches each instance's OWN derived status (deriveFindingStatus), unlike
|
|
20864
|
-
// the
|
|
20887
|
+
// the types query's type-level fold.
|
|
20865
20888
|
status: external_exports.array(FindingStatus).optional(),
|
|
20866
20889
|
// Exact host-tool names (attributes.tool_name, e.g. 'Bash'). A real filter,
|
|
20867
20890
|
// where the free-text `q` can only match the rendered "via Bash" label.
|
|
@@ -20878,37 +20901,47 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
20878
20901
|
});
|
|
20879
20902
|
var ListFindingInstancesResponse = external_exports.object({
|
|
20880
20903
|
// Instances matching the filters across the whole scope, not just this
|
|
20881
|
-
// page — cursor-independent, like the
|
|
20904
|
+
// page — cursor-independent, like the types list's totals.
|
|
20882
20905
|
totals: external_exports.object({ findings: external_exports.number().int().nonnegative() }),
|
|
20883
|
-
// Counts in INSTANCES here, where the
|
|
20906
|
+
// Counts in INSTANCES here, where the types response counts types. Each
|
|
20884
20907
|
// dimension still excludes its own filter.
|
|
20885
20908
|
facets: FindingFacets,
|
|
20886
20909
|
items: external_exports.array(FindingInstanceDetail),
|
|
20887
20910
|
nextCursor: external_exports.string().nullable()
|
|
20888
20911
|
}).meta({ id: "ListFindingInstancesResponse" });
|
|
20889
|
-
var
|
|
20890
|
-
//
|
|
20891
|
-
//
|
|
20892
|
-
|
|
20893
|
-
|
|
20894
|
-
|
|
20895
|
-
|
|
20896
|
-
// Folded from the instances' derived statuses with the same
|
|
20897
|
-
// open-dominates precedence a group uses.
|
|
20898
|
-
status: FindingStatus.optional(),
|
|
20899
|
-
// Distinct rules seen at this location, capped — the row shows them as
|
|
20900
|
-
// chips, and the count is what conveys scale.
|
|
20901
|
-
ruleIds: external_exports.array(external_exports.string())
|
|
20902
|
-
}).meta({ id: "FindingLocationFile" });
|
|
20903
|
-
var FindingLocationRepo = external_exports.object({
|
|
20912
|
+
var FindingLocationSummary = external_exports.object({
|
|
20913
|
+
// Opaque, stable, minted from the pair by encodeLocationId. It exists
|
|
20914
|
+
// because a location's identity is two values and a URL param carries one:
|
|
20915
|
+
// `?loc=` names a location the way `?rule=` names a type. Only ever compared
|
|
20916
|
+
// for EQUALITY — the page's selection check, this read's `includeId`, the
|
|
20917
|
+
// client's page dedupe — never decoded, and never a sort key.
|
|
20918
|
+
id: external_exports.string(),
|
|
20904
20919
|
/** Empty when the instances carried no repo attribute. */
|
|
20905
20920
|
repo: external_exports.string(),
|
|
20921
|
+
// Empty when the instances carried no file path (a prompt, or a tool call
|
|
20922
|
+
// with no file attribution). Both halves empty is a real location — usually
|
|
20923
|
+
// the largest one in a store — and is selectable like any other.
|
|
20924
|
+
file: external_exports.string(),
|
|
20906
20925
|
instanceCount: external_exports.number().int().nonnegative(),
|
|
20926
|
+
// The WORST severity present, not the first row's. It is this list's primary
|
|
20927
|
+
// sort key, so it is also what explains why a row is where it is, and it is
|
|
20928
|
+
// how a reader decides what to open without opening everything.
|
|
20907
20929
|
maxSeverity: Severity,
|
|
20908
20930
|
latestDetectedAt: external_exports.iso.datetime(),
|
|
20931
|
+
// Folded from the instances' derived statuses with the same open-dominates
|
|
20932
|
+
// precedence a group uses, so it answers "is anything left to do here" and
|
|
20933
|
+
// not much more: a location holding 1 open among 40 resolved reads like one
|
|
20934
|
+
// holding 40 open. That loss is accepted — the panel beside this list
|
|
20935
|
+
// carries each finding's own status, and instanceCount sits next to the
|
|
20936
|
+
// badge.
|
|
20909
20937
|
status: FindingStatus.optional(),
|
|
20910
|
-
|
|
20911
|
-
|
|
20938
|
+
// Every distinct rule seen at this location, UNCAPPED — so the length is a
|
|
20939
|
+
// tally rather than a sample and a row can say how many there are. Bounded
|
|
20940
|
+
// by the ruleset, not by the store. The view bounds what it DISPLAYS.
|
|
20941
|
+
ruleIds: external_exports.array(external_exports.string())
|
|
20942
|
+
}).meta({ id: "FindingLocationSummary" });
|
|
20943
|
+
var DEFAULT_FINDING_LOCATIONS_LIMIT = 50;
|
|
20944
|
+
var MAX_FINDING_LOCATIONS_LIMIT = 100;
|
|
20912
20945
|
var ListFindingLocationsQuery = external_exports.object({
|
|
20913
20946
|
severity: external_exports.array(Severity).optional(),
|
|
20914
20947
|
subtype: external_exports.array(external_exports.string()).optional(),
|
|
@@ -20921,18 +20954,42 @@ var ListFindingLocationsQuery = external_exports.object({
|
|
|
20921
20954
|
q: external_exports.string().optional(),
|
|
20922
20955
|
sessionId: external_exports.string().optional(),
|
|
20923
20956
|
from: external_exports.iso.datetime().optional(),
|
|
20924
|
-
|
|
20957
|
+
// A LOCATION id (see FindingLocationSummary.id) that must appear in the page
|
|
20958
|
+
// even when the cursor has already advanced past its sort position — the
|
|
20959
|
+
// counterpart of ListFindingTypesQuery.includeId, and needed far more often
|
|
20960
|
+
// here. Selecting a row pushes the URL, which re-renders the server and resets
|
|
20961
|
+
// the client's page cache to page 0; with distinct (repo, file) pairs running
|
|
20962
|
+
// into the thousands, a selection sitting off page 0 is the ordinary case
|
|
20963
|
+
// rather than a deep-link corner. Never affects totals, facets or the cursor.
|
|
20964
|
+
includeId: external_exports.string().optional(),
|
|
20965
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FINDING_LOCATIONS_LIMIT).optional(),
|
|
20966
|
+
cursor: external_exports.string().optional()
|
|
20925
20967
|
});
|
|
20926
20968
|
var ListFindingLocationsResponse = external_exports.object({
|
|
20927
20969
|
totals: external_exports.object({
|
|
20970
|
+
// Findings matching the filters across the whole scope. Unlike the types
|
|
20971
|
+
// read's same-named field this needs no caveat: the filters here narrow
|
|
20972
|
+
// per finding, so this is the sum of every row's instanceCount.
|
|
20928
20973
|
findings: external_exports.number().int().nonnegative(),
|
|
20929
|
-
|
|
20930
|
-
|
|
20974
|
+
// Counts LOCATIONS, the unit this read pages — the number the paginator
|
|
20975
|
+
// states. The facets beside it count FINDINGS (see below); a surface
|
|
20976
|
+
// showing both says which is which.
|
|
20977
|
+
locations: external_exports.number().int().nonnegative()
|
|
20931
20978
|
}),
|
|
20932
|
-
|
|
20933
|
-
|
|
20934
|
-
|
|
20935
|
-
|
|
20979
|
+
// Counts in FINDINGS, where the types response counts types, each dimension
|
|
20980
|
+
// still excluding its own filter. Deliberately not locations: counting those
|
|
20981
|
+
// needs a set of location keys per dimension per value — memory tracking the
|
|
20982
|
+
// store times the vocabulary, in a read whose scan promises flat memory —
|
|
20983
|
+
// and the cheap per-location version is not an approximation but WRONG. A
|
|
20984
|
+
// location holding {claudecode, block} and {codex, warn} would survive
|
|
20985
|
+
// provider=claudecode AND action=warn, under which no single finding
|
|
20986
|
+
// matches, so the facet would contradict the instanceCount this whole view
|
|
20987
|
+
// rests on. Findings also keep the toolbar in the same unit as the page
|
|
20988
|
+
// tally and the panel it sits above.
|
|
20989
|
+
facets: FindingFacets,
|
|
20990
|
+
/** Sorted by max severity, then most recent, then (repo, file). */
|
|
20991
|
+
items: external_exports.array(FindingLocationSummary),
|
|
20992
|
+
nextCursor: external_exports.string().nullable()
|
|
20936
20993
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
20937
20994
|
|
|
20938
20995
|
// ../../packages/schema/src/zod/meta.ts
|
|
@@ -22101,6 +22158,14 @@ var ControlPlaneErrorBody = external_exports.object({
|
|
|
22101
22158
|
message: external_exports.string().optional()
|
|
22102
22159
|
}).optional()
|
|
22103
22160
|
});
|
|
22161
|
+
var RemoteFailureKind = external_exports.enum([
|
|
22162
|
+
"unauthorized",
|
|
22163
|
+
"forbidden",
|
|
22164
|
+
"route-absent",
|
|
22165
|
+
"invalid-request",
|
|
22166
|
+
"rejected",
|
|
22167
|
+
"unreachable"
|
|
22168
|
+
]);
|
|
22104
22169
|
var AttachDeviceRequest = external_exports.object({
|
|
22105
22170
|
// This machine's own continuity id, so re-attaching ROTATES the credential
|
|
22106
22171
|
// on one machine record instead of producing a second one. Client-minted
|
|
@@ -22794,139 +22859,62 @@ function deriveFindingStatus(row) {
|
|
|
22794
22859
|
if (row.latestResolutionStatus === "dismissed") return "dismissed";
|
|
22795
22860
|
return "open";
|
|
22796
22861
|
}
|
|
22797
|
-
function distinctUsers(instances) {
|
|
22798
|
-
const seen = /* @__PURE__ */ new Set();
|
|
22799
|
-
const users = [];
|
|
22800
|
-
for (const i of instances) {
|
|
22801
|
-
if (i.user === void 0 || seen.has(i.user.id)) continue;
|
|
22802
|
-
seen.add(i.user.id);
|
|
22803
|
-
users.push(i.user);
|
|
22804
|
-
}
|
|
22805
|
-
return users;
|
|
22806
|
-
}
|
|
22807
22862
|
function sortUsers(users) {
|
|
22808
22863
|
return [...users].sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
22809
22864
|
}
|
|
22810
|
-
function
|
|
22811
|
-
const overrides = opts.overrides;
|
|
22865
|
+
function buildFindingTypes(aggregates, opts = {}) {
|
|
22812
22866
|
const packNames = opts.packNames;
|
|
22813
|
-
const
|
|
22814
|
-
const
|
|
22815
|
-
|
|
22816
|
-
const
|
|
22817
|
-
|
|
22818
|
-
else byRuleId.set(row.ruleId, [row]);
|
|
22819
|
-
}
|
|
22820
|
-
const groups = [];
|
|
22821
|
-
for (const [ruleId, ruleRows] of byRuleId) {
|
|
22822
|
-
const instances = ruleRows.map((r) => {
|
|
22823
|
-
const effectiveDbAction = overrides?.get(r.id) ?? r.actionTaken;
|
|
22824
|
-
return {
|
|
22825
|
-
id: r.id,
|
|
22826
|
-
provider: toApiProvider(r.sourceTool),
|
|
22827
|
-
repo: r.repo,
|
|
22828
|
-
file: r.file,
|
|
22829
|
-
...r.toolName === void 0 ? {} : { toolName: r.toolName },
|
|
22830
|
-
...r.eventId === void 0 ? {} : { eventId: r.eventId },
|
|
22831
|
-
...r.sessionId === void 0 ? {} : { sessionId: r.sessionId },
|
|
22832
|
-
...r.user === void 0 ? {} : { user: r.user },
|
|
22833
|
-
action: toApiAction(effectiveDbAction),
|
|
22834
|
-
detectedAt: r.occurredAt,
|
|
22835
|
-
confidence: r.confidence,
|
|
22836
|
-
status: r.status
|
|
22837
|
-
};
|
|
22838
|
-
});
|
|
22839
|
-
const agg = aggregates?.get(ruleId);
|
|
22840
|
-
const users = agg ? sortUsers(agg.users ?? []) : distinctUsers(instances);
|
|
22841
|
-
const latestDetectedAt = agg?.latestDetectedAt ?? ruleRows.reduce(
|
|
22842
|
-
(max, r) => r.occurredAt > max ? r.occurredAt : max,
|
|
22843
|
-
ruleRows[0]?.occurredAt ?? (/* @__PURE__ */ new Date(0)).toISOString()
|
|
22844
|
-
);
|
|
22845
|
-
const seenProviders = /* @__PURE__ */ new Set();
|
|
22846
|
-
const providers = (agg ? [...new Set(agg.sourceTools.map(toApiProvider))].sort() : instances.map((i) => i.provider)).filter((p) => {
|
|
22847
|
-
if (seenProviders.has(p)) return false;
|
|
22848
|
-
seenProviders.add(p);
|
|
22849
|
-
return true;
|
|
22850
|
-
});
|
|
22851
|
-
const actionSet = new Set(
|
|
22852
|
-
agg ? agg.actionsTaken.map(toApiAction) : instances.map((i) => i.action)
|
|
22853
|
-
);
|
|
22867
|
+
const types = [];
|
|
22868
|
+
for (const [ruleId, agg] of aggregates) {
|
|
22869
|
+
const users = sortUsers(agg.users ?? []);
|
|
22870
|
+
const providers = [...new Set(agg.sourceTools.map(toApiProvider))].sort();
|
|
22871
|
+
const actionSet = new Set(agg.actionsTaken.map(toApiAction));
|
|
22854
22872
|
const aggregateAction = actionSet.size === 1 ? [...actionSet][0] ?? null : null;
|
|
22855
|
-
const
|
|
22856
|
-
const
|
|
22857
|
-
id: ruleId,
|
|
22858
|
-
name: packNames?.get(ruleId) ?? null
|
|
22859
|
-
};
|
|
22860
|
-
const apiCategory = toApiCategory(ruleRows[0]?.category ?? "custom");
|
|
22861
|
-
const policy = { id: `category:${apiCategory}`, name: apiCategory };
|
|
22862
|
-
const match = {
|
|
22863
|
-
maskedValue: ruleRows[0]?.maskedMatch ?? "",
|
|
22864
|
-
contextPrefix: ""
|
|
22865
|
-
// empty (pending privacy review)
|
|
22866
|
-
};
|
|
22867
|
-
const status = foldGroupStatus(
|
|
22868
|
-
agg ? agg.statusInputs.map(deriveFindingStatus) : instances.map((i) => i.status)
|
|
22869
|
-
);
|
|
22870
|
-
const group = {
|
|
22873
|
+
const apiCategory = toApiCategory(agg.category ?? "custom");
|
|
22874
|
+
const type = {
|
|
22871
22875
|
id: ruleId,
|
|
22872
22876
|
category: apiCategory,
|
|
22873
22877
|
subtype: ruleId,
|
|
22874
22878
|
// human label comes with pack metadata later
|
|
22875
|
-
severity,
|
|
22876
|
-
|
|
22877
|
-
|
|
22878
|
-
|
|
22879
|
-
instanceCount: agg?.instanceCount ?? instances.length,
|
|
22879
|
+
severity: agg.severity ?? "low",
|
|
22880
|
+
detection: { id: ruleId, name: packNames?.get(ruleId) ?? null },
|
|
22881
|
+
policy: { id: `category:${apiCategory}`, name: apiCategory },
|
|
22882
|
+
instanceCount: agg.instanceCount,
|
|
22880
22883
|
providers,
|
|
22881
22884
|
aggregateAction,
|
|
22882
|
-
latestDetectedAt,
|
|
22883
|
-
|
|
22884
|
-
status,
|
|
22885
|
+
latestDetectedAt: agg.latestDetectedAt,
|
|
22886
|
+
status: foldGroupStatus(agg.statusInputs.map(deriveFindingStatus)),
|
|
22885
22887
|
...users.length > 0 ? { users } : {}
|
|
22886
22888
|
};
|
|
22887
|
-
|
|
22888
|
-
|
|
22889
|
-
|
|
22890
|
-
haystackCache.set(group, buildHaystack(group, agg.searchText));
|
|
22891
|
-
}
|
|
22889
|
+
actionsCache.set(type, [...actionSet]);
|
|
22890
|
+
if (agg.searchText !== void 0) {
|
|
22891
|
+
haystackCache.set(type, buildHaystack(type, agg.searchText));
|
|
22892
22892
|
}
|
|
22893
|
-
|
|
22893
|
+
types.push(type);
|
|
22894
22894
|
}
|
|
22895
|
-
return
|
|
22895
|
+
return types;
|
|
22896
22896
|
}
|
|
22897
22897
|
var haystackCache = /* @__PURE__ */ new WeakMap();
|
|
22898
|
-
function buildHaystack(
|
|
22898
|
+
function buildHaystack(t, extra) {
|
|
22899
22899
|
return [
|
|
22900
|
-
|
|
22901
|
-
|
|
22902
|
-
|
|
22903
|
-
|
|
22904
|
-
|
|
22905
|
-
...g.instances.map((i) => i.repo),
|
|
22906
|
-
...g.instances.map((i) => i.file),
|
|
22907
|
-
...g.instances.map((i) => i.toolName ? `via ${i.toolName}` : ""),
|
|
22908
|
-
...g.instances.map((i) => i.id),
|
|
22909
|
-
// The people: the whole group's list when the store folded one, plus the
|
|
22910
|
-
// preview's own — the two overlap, and a haystack does not mind.
|
|
22911
|
-
...(g.users ?? []).map((u) => u.name),
|
|
22912
|
-
...g.instances.map((i) => i.user?.name ?? ""),
|
|
22900
|
+
t.subtype,
|
|
22901
|
+
t.category,
|
|
22902
|
+
t.policy.name,
|
|
22903
|
+
t.id,
|
|
22904
|
+
...(t.users ?? []).map((u) => u.name),
|
|
22913
22905
|
...extra === void 0 ? [] : [extra]
|
|
22914
22906
|
].join(" ").toLowerCase();
|
|
22915
22907
|
}
|
|
22916
|
-
function
|
|
22917
|
-
const cached2 = haystackCache.get(
|
|
22908
|
+
function typeHaystack(t) {
|
|
22909
|
+
const cached2 = haystackCache.get(t);
|
|
22918
22910
|
if (cached2 !== void 0) return cached2;
|
|
22919
|
-
const haystack = buildHaystack(
|
|
22920
|
-
haystackCache.set(
|
|
22911
|
+
const haystack = buildHaystack(t);
|
|
22912
|
+
haystackCache.set(t, haystack);
|
|
22921
22913
|
return haystack;
|
|
22922
22914
|
}
|
|
22923
22915
|
var actionsCache = /* @__PURE__ */ new WeakMap();
|
|
22924
|
-
function
|
|
22925
|
-
|
|
22926
|
-
if (cached2 !== void 0) return cached2;
|
|
22927
|
-
const actions = [...new Set(g.instances.map((i) => i.action))];
|
|
22928
|
-
actionsCache.set(g, actions);
|
|
22929
|
-
return actions;
|
|
22916
|
+
function typeActions(t) {
|
|
22917
|
+
return actionsCache.get(t) ?? [];
|
|
22930
22918
|
}
|
|
22931
22919
|
function countInstancesByStatus(statusInputs, statuses) {
|
|
22932
22920
|
const statusSet = new Set(statuses);
|
|
@@ -22937,8 +22925,8 @@ function countInstancesByStatus(statusInputs, statuses) {
|
|
|
22937
22925
|
}
|
|
22938
22926
|
return sum;
|
|
22939
22927
|
}
|
|
22940
|
-
function applyFindingFilters(
|
|
22941
|
-
let filtered =
|
|
22928
|
+
function applyFindingFilters(types, opts) {
|
|
22929
|
+
let filtered = types;
|
|
22942
22930
|
if (opts.severity && opts.severity.length > 0) {
|
|
22943
22931
|
const sevSet = new Set(opts.severity);
|
|
22944
22932
|
filtered = filtered.filter((g) => sevSet.has(g.severity));
|
|
@@ -22949,7 +22937,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22949
22937
|
}
|
|
22950
22938
|
if (opts.actions && opts.actions.length > 0) {
|
|
22951
22939
|
const actionSet = new Set(opts.actions);
|
|
22952
|
-
filtered = filtered.filter((
|
|
22940
|
+
filtered = filtered.filter((t) => typeActions(t).some((a) => actionSet.has(a)));
|
|
22953
22941
|
}
|
|
22954
22942
|
if (opts.subtype && opts.subtype.length > 0) {
|
|
22955
22943
|
const subtypeSet = new Set(opts.subtype);
|
|
@@ -22961,7 +22949,7 @@ function applyFindingFilters(groups, opts) {
|
|
|
22961
22949
|
}
|
|
22962
22950
|
if (opts.q) {
|
|
22963
22951
|
const q = opts.q.toLowerCase();
|
|
22964
|
-
filtered = filtered.filter((
|
|
22952
|
+
filtered = filtered.filter((t) => typeHaystack(t).includes(q));
|
|
22965
22953
|
}
|
|
22966
22954
|
return filtered;
|
|
22967
22955
|
}
|
|
@@ -22976,11 +22964,11 @@ function compareFindingGroupOrder(a, b) {
|
|
|
22976
22964
|
if (recencyDiff !== 0) return recencyDiff;
|
|
22977
22965
|
return a.id.localeCompare(b.id);
|
|
22978
22966
|
}
|
|
22979
|
-
function
|
|
22980
|
-
return [...
|
|
22967
|
+
function sortFindingTypes(types) {
|
|
22968
|
+
return [...types].sort(compareFindingGroupOrder);
|
|
22981
22969
|
}
|
|
22982
|
-
function computeFindingFacets(
|
|
22983
|
-
const forSeverity = applyFindingFilters(
|
|
22970
|
+
function computeFindingFacets(allTypes, opts) {
|
|
22971
|
+
const forSeverity = applyFindingFilters(allTypes, {
|
|
22984
22972
|
providers: opts.providers,
|
|
22985
22973
|
actions: opts.actions,
|
|
22986
22974
|
statuses: opts.statuses,
|
|
@@ -22991,7 +22979,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
22991
22979
|
for (const g of forSeverity) {
|
|
22992
22980
|
severityMap.set(g.severity, (severityMap.get(g.severity) ?? 0) + 1);
|
|
22993
22981
|
}
|
|
22994
|
-
const forProvider = applyFindingFilters(
|
|
22982
|
+
const forProvider = applyFindingFilters(allTypes, {
|
|
22995
22983
|
actions: opts.actions,
|
|
22996
22984
|
statuses: opts.statuses,
|
|
22997
22985
|
q: opts.q,
|
|
@@ -23002,7 +22990,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23002
22990
|
for (const g of forProvider) {
|
|
23003
22991
|
for (const p of g.providers) providerMap.set(p, (providerMap.get(p) ?? 0) + 1);
|
|
23004
22992
|
}
|
|
23005
|
-
const forAction = applyFindingFilters(
|
|
22993
|
+
const forAction = applyFindingFilters(allTypes, {
|
|
23006
22994
|
providers: opts.providers,
|
|
23007
22995
|
statuses: opts.statuses,
|
|
23008
22996
|
q: opts.q,
|
|
@@ -23011,9 +22999,9 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23011
22999
|
});
|
|
23012
23000
|
const actionMap = /* @__PURE__ */ new Map();
|
|
23013
23001
|
for (const g of forAction) {
|
|
23014
|
-
for (const a of
|
|
23002
|
+
for (const a of typeActions(g)) actionMap.set(a, (actionMap.get(a) ?? 0) + 1);
|
|
23015
23003
|
}
|
|
23016
|
-
const forSubtype = applyFindingFilters(
|
|
23004
|
+
const forSubtype = applyFindingFilters(allTypes, {
|
|
23017
23005
|
providers: opts.providers,
|
|
23018
23006
|
actions: opts.actions,
|
|
23019
23007
|
statuses: opts.statuses,
|
|
@@ -23022,7 +23010,7 @@ function computeFindingFacets(allGroups, opts) {
|
|
|
23022
23010
|
});
|
|
23023
23011
|
const subtypeMap = /* @__PURE__ */ new Map();
|
|
23024
23012
|
for (const g of forSubtype) subtypeMap.set(g.subtype, (subtypeMap.get(g.subtype) ?? 0) + 1);
|
|
23025
|
-
const forStatus = applyFindingFilters(
|
|
23013
|
+
const forStatus = applyFindingFilters(allTypes, {
|
|
23026
23014
|
providers: opts.providers,
|
|
23027
23015
|
actions: opts.actions,
|
|
23028
23016
|
q: opts.q,
|
|
@@ -23070,10 +23058,20 @@ function matchesDimension(row, opts, dimension) {
|
|
|
23070
23058
|
return !opts.statuses?.length || row.status !== void 0 && opts.statuses.includes(row.status);
|
|
23071
23059
|
case "tools":
|
|
23072
23060
|
return !opts.tools?.length || row.toolName !== void 0 && opts.tools.includes(row.toolName);
|
|
23061
|
+
// An EMPTY value is a real filter here, not an absent one. The location
|
|
23062
|
+
// list buckets a finding whose event recorded no repo — or no file — under
|
|
23063
|
+
// the empty string, and selecting that bucket has to narrow the panel to
|
|
23064
|
+
// exactly it. Only `undefined` means "no filter"; a caller that wants every
|
|
23065
|
+
// row omits the key, which every call site already does.
|
|
23066
|
+
//
|
|
23067
|
+
// Reading '' as unset is what this replaced, and it failed in the one place
|
|
23068
|
+
// it mattered: the no-repo/no-file bucket is often the largest in a real
|
|
23069
|
+
// store, and its panel dropped both predicates and returned the WHOLE scope
|
|
23070
|
+
// — a row reading 3 findings beside a panel listing every finding there is.
|
|
23073
23071
|
case "repo":
|
|
23074
|
-
return opts.repo === void 0 ||
|
|
23072
|
+
return opts.repo === void 0 || row.repo === opts.repo;
|
|
23075
23073
|
case "file":
|
|
23076
|
-
return opts.file === void 0 ||
|
|
23074
|
+
return opts.file === void 0 || row.file === opts.file;
|
|
23077
23075
|
case "q":
|
|
23078
23076
|
return !opts.q || rowHaystack(row).includes(opts.q.toLowerCase());
|
|
23079
23077
|
}
|
|
@@ -23187,6 +23185,23 @@ function addToLocation(acc, row) {
|
|
|
23187
23185
|
acc.statuses.push(row.status);
|
|
23188
23186
|
acc.ruleIds.add(row.ruleId);
|
|
23189
23187
|
}
|
|
23188
|
+
function compareLocationOrder(a, b) {
|
|
23189
|
+
const rankA = SEVERITY_ORDER2[a.maxSeverity] ?? -1;
|
|
23190
|
+
const rankB = SEVERITY_ORDER2[b.maxSeverity] ?? -1;
|
|
23191
|
+
if (rankA !== rankB) return rankA - rankB;
|
|
23192
|
+
if (a.latestDetectedAt !== b.latestDetectedAt) {
|
|
23193
|
+
return a.latestDetectedAt < b.latestDetectedAt ? 1 : -1;
|
|
23194
|
+
}
|
|
23195
|
+
if (a.repo !== b.repo) return a.repo < b.repo ? -1 : 1;
|
|
23196
|
+
if (a.file !== b.file) return a.file < b.file ? -1 : 1;
|
|
23197
|
+
return 0;
|
|
23198
|
+
}
|
|
23199
|
+
function encodeLocationId(repo, file2) {
|
|
23200
|
+
return `${encodePart(repo)}/${encodePart(file2)}`;
|
|
23201
|
+
}
|
|
23202
|
+
function encodePart(value) {
|
|
23203
|
+
return encodeURIComponent(value.replace(/[\uD800-\uDFFF]/gu, "\uFFFD"));
|
|
23204
|
+
}
|
|
23190
23205
|
|
|
23191
23206
|
// ../../packages/schema/src/zod/installed-pack.ts
|
|
23192
23207
|
var InstalledPack = external_exports.object({
|
|
@@ -23690,11 +23705,11 @@ var WorkspaceSettings = external_exports.object({
|
|
|
23690
23705
|
// covers the current payload and must be re-granted.
|
|
23691
23706
|
modelJudgeConsent: ModelJudgeConsent.optional(),
|
|
23692
23707
|
// Records that the user consented to the DEFERRED send — the outbox — along
|
|
23693
|
-
// with the payload shape and the endpoint they agreed to. Since payload
|
|
23694
|
-
// that covers
|
|
23695
|
-
// carry prompt/reply text in `content
|
|
23696
|
-
// Absent until granted, and a grant for a different endpoint
|
|
23697
|
-
// payload no longer counts.
|
|
23708
|
+
// with the payload shape and the endpoint they agreed to. Since payload v3
|
|
23709
|
+
// that covers the pre-attach backlog AND undelivered captures alike, and both
|
|
23710
|
+
// carry prompt/reply/tool-output text in `content`; the key name predates
|
|
23711
|
+
// both widenings. Absent until granted, and a grant for a different endpoint
|
|
23712
|
+
// or an older payload no longer counts.
|
|
23698
23713
|
historySyncConsent: HistorySyncConsent.optional()
|
|
23699
23714
|
});
|
|
23700
23715
|
function defaultWorkspaceSettings() {
|
|
@@ -23825,6 +23840,9 @@ var ManagedSettingKey = external_exports.enum([
|
|
|
23825
23840
|
"dataSharesInPlace",
|
|
23826
23841
|
"redactFallback"
|
|
23827
23842
|
]).meta({ id: "ManagedSettingKey" });
|
|
23843
|
+
function isManagedSettingKey(value) {
|
|
23844
|
+
return ManagedSettingKey.safeParse(value).success;
|
|
23845
|
+
}
|
|
23828
23846
|
var ManagedSettingsValues = external_exports.object({
|
|
23829
23847
|
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
23830
23848
|
controlPlane: external_exports.object({
|
|
@@ -23849,7 +23867,27 @@ var ManagedSettings = external_exports.object({
|
|
|
23849
23867
|
// Which of those the user may not change. A key here with no matching value
|
|
23850
23868
|
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
23851
23869
|
// the user may still override. The two are separable on purpose.
|
|
23852
|
-
|
|
23870
|
+
//
|
|
23871
|
+
// Parsed as NAMES rather than as the enum, and split below: a name this
|
|
23872
|
+
// build does not know is dropped from the locked set and reported, never a
|
|
23873
|
+
// reason to refuse the file. The same shape reaches an older build whenever
|
|
23874
|
+
// an administrator locks a key a newer build added, and refusing it there
|
|
23875
|
+
// ran that build entirely unmanaged — every pin and lock gone — on exactly
|
|
23876
|
+
// the fleets most likely to carry a version skew. A name outside the enum
|
|
23877
|
+
// is still never HONOURED: the lockable set stays explicit above.
|
|
23878
|
+
lockedFields: external_exports.array(external_exports.string()).default([])
|
|
23879
|
+
}).transform(({ lockedFields, ...rest }) => {
|
|
23880
|
+
const known = [];
|
|
23881
|
+
const unknown2 = [];
|
|
23882
|
+
for (const name of lockedFields) {
|
|
23883
|
+
if (isManagedSettingKey(name)) known.push(name);
|
|
23884
|
+
else unknown2.push(name);
|
|
23885
|
+
}
|
|
23886
|
+
return {
|
|
23887
|
+
...rest,
|
|
23888
|
+
lockedFields: known,
|
|
23889
|
+
...unknown2.length > 0 ? { unknownLockedFields: unknown2 } : {}
|
|
23890
|
+
};
|
|
23853
23891
|
}).meta({ id: "ManagedSettings" });
|
|
23854
23892
|
|
|
23855
23893
|
// ../../packages/schema/src/zod/project-files.ts
|
|
@@ -23973,7 +24011,11 @@ var FindingsTimeseriesPoint = external_exports.object({
|
|
|
23973
24011
|
timestamp: external_exports.iso.date(),
|
|
23974
24012
|
critical: external_exports.number().int().nonnegative(),
|
|
23975
24013
|
high: external_exports.number().int().nonnegative(),
|
|
23976
|
-
medium: external_exports.number().int().nonnegative()
|
|
24014
|
+
medium: external_exports.number().int().nonnegative(),
|
|
24015
|
+
// Optional and additive, so a producer written against the earlier
|
|
24016
|
+
// three-series contract keeps validating. A consumer plotting it resolves the
|
|
24017
|
+
// absent case itself — the chart point requires a number.
|
|
24018
|
+
low: external_exports.number().int().nonnegative().optional()
|
|
23977
24019
|
}).meta({ id: "FindingsTimeseriesPoint" });
|
|
23978
24020
|
var FindingsTimeseriesResponse = external_exports.object({
|
|
23979
24021
|
range: TimeRange,
|
|
@@ -23999,6 +24041,10 @@ var ResolvedFeedItem = external_exports.object({
|
|
|
23999
24041
|
findingKey: external_exports.string(),
|
|
24000
24042
|
ruleId: external_exports.string(),
|
|
24001
24043
|
severity: Severity,
|
|
24044
|
+
// Repository slug, and the file path RELATIVE to it. The pair is what
|
|
24045
|
+
// identifies the file: a bare path matches the same name in every repo.
|
|
24046
|
+
// Optional and additive; empty when the event carried no repo.
|
|
24047
|
+
repo: external_exports.string().optional(),
|
|
24002
24048
|
path: external_exports.string(),
|
|
24003
24049
|
// ISO-8601 datetime (matches FindingInstance.detectedAt / the rest of the
|
|
24004
24050
|
// findings domain). The reader `.toISOString()`s the DB epoch-ms values.
|
|
@@ -25542,7 +25588,8 @@ var SqliteActivityRepository = class {
|
|
|
25542
25588
|
SELECT 1 FROM audit_events d
|
|
25543
25589
|
WHERE d.root_session_id = audit_events.id
|
|
25544
25590
|
AND (d.content LIKE ? ESCAPE '\\'
|
|
25545
|
-
OR json_extract(d.attributes, '$.detail')
|
|
25591
|
+
OR coalesce(json_extract(d.attributes, '$.detail'),
|
|
25592
|
+
json_extract(d.attributes, '$.target')) LIKE ? ESCAPE '\\')))`
|
|
25546
25593
|
);
|
|
25547
25594
|
params.push(pattern, pattern, pattern, pattern, pattern, pattern);
|
|
25548
25595
|
}
|
|
@@ -26880,23 +26927,6 @@ var LATEST_RESOLUTION_BY_KEY_SQL = `(
|
|
|
26880
26927
|
)`;
|
|
26881
26928
|
|
|
26882
26929
|
// ../../packages/persistence/src/repositories/findings.ts
|
|
26883
|
-
var PREVIEW_INSTANCES_PER_GROUP = 200;
|
|
26884
|
-
var DEFAULT_LOCATIONS_LIMIT = 100;
|
|
26885
|
-
var LOCATION_RULE_IDS_CAP = 20;
|
|
26886
|
-
function compareLocationOrder(a, b) {
|
|
26887
|
-
return compareFindingGroupOrder(
|
|
26888
|
-
{
|
|
26889
|
-
severity: a.maxSeverity,
|
|
26890
|
-
latestDetectedAt: a.latestDetectedAt,
|
|
26891
|
-
id: ""
|
|
26892
|
-
},
|
|
26893
|
-
{
|
|
26894
|
-
severity: b.maxSeverity,
|
|
26895
|
-
latestDetectedAt: b.latestDetectedAt,
|
|
26896
|
-
id: ""
|
|
26897
|
-
}
|
|
26898
|
-
);
|
|
26899
|
-
}
|
|
26900
26930
|
var CONCAT_SEP = ",";
|
|
26901
26931
|
var TUPLE_SEP = "|";
|
|
26902
26932
|
function splitConcat(value) {
|
|
@@ -26948,13 +26978,48 @@ function decodeGroupCursor(cursor) {
|
|
|
26948
26978
|
return null;
|
|
26949
26979
|
}
|
|
26950
26980
|
function firstAfter(sorted, cursor) {
|
|
26951
|
-
const index = sorted.findIndex((
|
|
26981
|
+
const index = sorted.findIndex((t) => compareFindingGroupOrder(t, cursor) > 0);
|
|
26952
26982
|
return index === -1 ? sorted.length : index;
|
|
26953
26983
|
}
|
|
26954
26984
|
function findDeepLinked(sorted, page, id) {
|
|
26955
|
-
if (page.some((
|
|
26956
|
-
return sorted.find((
|
|
26985
|
+
if (page.some((t) => t.id === id)) return void 0;
|
|
26986
|
+
return sorted.find((t) => t.id === id);
|
|
26987
|
+
}
|
|
26988
|
+
function encodeLocationCursor(location) {
|
|
26989
|
+
const payload = {
|
|
26990
|
+
sev: location.maxSeverity,
|
|
26991
|
+
t: location.latestDetectedAt,
|
|
26992
|
+
r: location.repo,
|
|
26993
|
+
f: location.file
|
|
26994
|
+
};
|
|
26995
|
+
return Buffer.from(JSON.stringify(payload)).toString("base64url");
|
|
26996
|
+
}
|
|
26997
|
+
function decodeLocationCursor(cursor) {
|
|
26998
|
+
const parsed2 = parseJsonObject(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
26999
|
+
if (parsed2 !== void 0 && typeof parsed2.sev === "string" && typeof parsed2.t === "string" && typeof parsed2.r === "string" && typeof parsed2.f === "string") {
|
|
27000
|
+
return { maxSeverity: parsed2.sev, latestDetectedAt: parsed2.t, repo: parsed2.r, file: parsed2.f };
|
|
27001
|
+
}
|
|
27002
|
+
return null;
|
|
26957
27003
|
}
|
|
27004
|
+
function firstLocationAfter(sorted, cursor) {
|
|
27005
|
+
const index = sorted.findIndex((l) => compareLocationOrder(l, cursor) > 0);
|
|
27006
|
+
return index === -1 ? sorted.length : index;
|
|
27007
|
+
}
|
|
27008
|
+
function findDeepLinkedLocation(sorted, page, id) {
|
|
27009
|
+
if (page.some((l) => l.id === id)) return void 0;
|
|
27010
|
+
return sorted.find((l) => l.id === id);
|
|
27011
|
+
}
|
|
27012
|
+
var FINDING_ROW_COLUMNS_SQL = `f.id AS id, d.rule_id AS rule_id, d.category AS category,
|
|
27013
|
+
d.severity AS severity, f.masked_match AS masked_match,
|
|
27014
|
+
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27015
|
+
e.started_at AS occurred_at,
|
|
27016
|
+
e.source_tool AS source_tool,
|
|
27017
|
+
e.repo AS repo,
|
|
27018
|
+
e.file_path AS file,
|
|
27019
|
+
e.tool_name AS tool_name,
|
|
27020
|
+
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27021
|
+
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27022
|
+
${latestResolutionStatusSql("f")} AS latest_status`;
|
|
26958
27023
|
var DAY_MS3 = 864e5;
|
|
26959
27024
|
var SqliteFindingsRepository = class {
|
|
26960
27025
|
constructor(db) {
|
|
@@ -27075,30 +27140,26 @@ var SqliteFindingsRepository = class {
|
|
|
27075
27140
|
);
|
|
27076
27141
|
}
|
|
27077
27142
|
/**
|
|
27078
|
-
*
|
|
27079
|
-
*
|
|
27080
|
-
*
|
|
27081
|
-
*
|
|
27082
|
-
*
|
|
27083
|
-
*
|
|
27084
|
-
*
|
|
27085
|
-
*
|
|
27086
|
-
*
|
|
27087
|
-
*
|
|
27088
|
-
*
|
|
27089
|
-
*
|
|
27143
|
+
* Finding TYPES for the dashboard — one row per rule, scoped to the four
|
|
27144
|
+
* capture kinds (audit_events also holds structural/reconciler/scan rows this
|
|
27145
|
+
* list must never surface), with per-filter-excluded facets, the requested
|
|
27146
|
+
* filters applied, and sorted by severity then recency. Filtering and faceting
|
|
27147
|
+
* run in JS via the shared @akasecurity/schema helpers. `totals` reflect the
|
|
27148
|
+
* full filtered set; `items` is the requested page (default 50), keyset-paged.
|
|
27149
|
+
* Under a `status` filter, `totals.findings` counts only findings whose
|
|
27150
|
+
* derived status was requested.
|
|
27151
|
+
*
|
|
27152
|
+
* ONE read, which materializes no findings: a single aggregate per rule_id,
|
|
27153
|
+
* folding EVERY finding into the numbers a type row and the filters need
|
|
27154
|
+
* (count, severity, category, providers, actions, statuses, latest, search
|
|
27155
|
+
* text). The findings OF a type come from listFindingInstances scoped to
|
|
27156
|
+
* `subtype`, so neither list bounds the other and no per-type cap exists.
|
|
27090
27157
|
*
|
|
27091
|
-
* Two reads, neither of which materializes a row per finding:
|
|
27092
|
-
* 1. one aggregate row per rule_id, folding EVERY instance into the numbers
|
|
27093
|
-
* the group and the filters need (count, providers, actions, statuses,
|
|
27094
|
-
* latest, search text);
|
|
27095
|
-
* 2. each group's newest PREVIEW_INSTANCES_PER_GROUP instances, which
|
|
27096
|
-
* populate `instances` for the table's expanded rows.
|
|
27097
27158
|
* The aggregates carry raw DB values and are translated by the same
|
|
27098
|
-
* @akasecurity/schema mappers
|
|
27099
|
-
* rule is ever restated in SQL.
|
|
27159
|
+
* @akasecurity/schema mappers every other path uses, so no enum mapping or
|
|
27160
|
+
* status rule is ever restated in SQL.
|
|
27100
27161
|
*/
|
|
27101
|
-
|
|
27162
|
+
listFindingTypes(query) {
|
|
27102
27163
|
const sessionPredicate = query.sessionId ? ` AND e.root_session_id = :sessionId` : "";
|
|
27103
27164
|
const fromMs = query.from === void 0 ? void 0 : isoToEpochMillis(query.from);
|
|
27104
27165
|
const fromPredicate = fromMs === void 0 ? "" : ` AND e.started_at >= :fromMs`;
|
|
@@ -27111,12 +27172,7 @@ var SqliteFindingsRepository = class {
|
|
|
27111
27172
|
predicate,
|
|
27112
27173
|
params: sessionParams
|
|
27113
27174
|
});
|
|
27114
|
-
const
|
|
27115
|
-
sessionId: query.sessionId,
|
|
27116
|
-
from: query.from
|
|
27117
|
-
});
|
|
27118
|
-
const groupable = rows.map(toFlatFindingRow);
|
|
27119
|
-
const allGroups = buildFindingGroups(groupable, { aggregates });
|
|
27175
|
+
const allTypes = buildFindingTypes(aggregates);
|
|
27120
27176
|
const filterOpts = {
|
|
27121
27177
|
severity: query.severity,
|
|
27122
27178
|
providers: query.provider,
|
|
@@ -27125,30 +27181,25 @@ var SqliteFindingsRepository = class {
|
|
|
27125
27181
|
subtype: query.subtype,
|
|
27126
27182
|
q: query.q
|
|
27127
27183
|
};
|
|
27128
|
-
const facets = computeFindingFacets(
|
|
27129
|
-
const sorted =
|
|
27184
|
+
const facets = computeFindingFacets(allTypes, filterOpts);
|
|
27185
|
+
const sorted = sortFindingTypes(applyFindingFilters(allTypes, filterOpts));
|
|
27130
27186
|
const statusFilter = query.status ?? [];
|
|
27131
27187
|
const totals = {
|
|
27132
|
-
findings: sorted.reduce((acc,
|
|
27133
|
-
if (statusFilter.length === 0) return acc +
|
|
27134
|
-
const agg = aggregates.get(
|
|
27135
|
-
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ??
|
|
27188
|
+
findings: sorted.reduce((acc, t) => {
|
|
27189
|
+
if (statusFilter.length === 0) return acc + t.instanceCount;
|
|
27190
|
+
const agg = aggregates.get(t.id);
|
|
27191
|
+
return acc + (agg ? countInstancesByStatus(agg.statusInputs, statusFilter) ?? t.instanceCount : t.instanceCount);
|
|
27136
27192
|
}, 0),
|
|
27137
|
-
|
|
27193
|
+
types: sorted.length
|
|
27138
27194
|
};
|
|
27139
|
-
const limit = query.limit ??
|
|
27195
|
+
const limit = query.limit ?? DEFAULT_FINDING_TYPES_LIMIT;
|
|
27140
27196
|
const cursor = query.cursor === void 0 ? null : decodeGroupCursor(query.cursor);
|
|
27141
27197
|
const start = cursor === null ? 0 : firstAfter(sorted, cursor);
|
|
27142
27198
|
const page = sorted.slice(start, start + limit);
|
|
27143
27199
|
const lastOnPage = page.at(-1);
|
|
27144
27200
|
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeGroupCursor(lastOnPage) : null;
|
|
27145
27201
|
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinked(sorted, page, query.includeId);
|
|
27146
|
-
const
|
|
27147
|
-
const narrow = (g) => statusSet ? {
|
|
27148
|
-
...g,
|
|
27149
|
-
instances: g.instances.filter((i) => i.status !== void 0 && statusSet.has(i.status))
|
|
27150
|
-
} : g;
|
|
27151
|
-
const items = [...page, ...deepLinked ? [deepLinked] : []].map(narrow);
|
|
27202
|
+
const items = [...page, ...deepLinked ? [deepLinked] : []];
|
|
27152
27203
|
return Promise.resolve({
|
|
27153
27204
|
totals,
|
|
27154
27205
|
facets,
|
|
@@ -27159,7 +27210,7 @@ var SqliteFindingsRepository = class {
|
|
|
27159
27210
|
}
|
|
27160
27211
|
/**
|
|
27161
27212
|
* One row per rule_id, folding EVERY instance of the group into the values
|
|
27162
|
-
*
|
|
27213
|
+
* buildFindingTypes cannot recover from an aggregate. Bounded by the number of
|
|
27163
27214
|
* distinct rule_ids (the installed packs' rules), not by the store's size.
|
|
27164
27215
|
*
|
|
27165
27216
|
* A single scan, folded in two levels: the inner SELECT groups by
|
|
@@ -27253,13 +27304,25 @@ var SqliteFindingsRepository = class {
|
|
|
27253
27304
|
});
|
|
27254
27305
|
}
|
|
27255
27306
|
/**
|
|
27256
|
-
* The same findings folded by
|
|
27307
|
+
* The same findings folded by WHERE they live — one row per (repo, file) pair.
|
|
27257
27308
|
*
|
|
27258
27309
|
* The grouping keys come from the capturing event's attributes, which is what
|
|
27259
|
-
* the local store relates a finding to
|
|
27260
|
-
*
|
|
27261
|
-
* empty-string bucket, which
|
|
27262
|
-
*
|
|
27310
|
+
* the local store relates a finding to; there is no finding↔asset row to group
|
|
27311
|
+
* by instead. A repo or file the event did not record folds into the
|
|
27312
|
+
* empty-string bucket, which is a real location like any other: it is listed,
|
|
27313
|
+
* it is selectable, and its `?loc=` token is as good as any other row's.
|
|
27314
|
+
*
|
|
27315
|
+
* ONE flat list rather than repos nesting files. A rollup can only be paged by
|
|
27316
|
+
* repo, which leaves the file list inside it unbounded — the shape the by-type
|
|
27317
|
+
* list was rebuilt to remove — and two-level pagination inside an
|
|
27318
|
+
* expand/collapse table is what pushed that view to master/detail in the first
|
|
27319
|
+
* place.
|
|
27320
|
+
*
|
|
27321
|
+
* Every filter narrows the FINDINGS and the locations fall out of what
|
|
27322
|
+
* survives, so each row's `instanceCount` is exactly what listFindingInstances
|
|
27323
|
+
* reports for the same filters scoped to that pair. The view depends on it:
|
|
27324
|
+
* one toolbar sits over both panels precisely because a location owns none of
|
|
27325
|
+
* its fields.
|
|
27263
27326
|
*/
|
|
27264
27327
|
listFindingLocations(query) {
|
|
27265
27328
|
const opts = {
|
|
@@ -27271,13 +27334,16 @@ var SqliteFindingsRepository = class {
|
|
|
27271
27334
|
tools: query.tool,
|
|
27272
27335
|
q: query.q
|
|
27273
27336
|
};
|
|
27274
|
-
const limit = query.limit ??
|
|
27337
|
+
const limit = query.limit ?? DEFAULT_FINDING_LOCATIONS_LIMIT;
|
|
27338
|
+
const cursor = query.cursor === void 0 ? null : decodeLocationCursor(query.cursor);
|
|
27275
27339
|
const byRepo = /* @__PURE__ */ new Map();
|
|
27340
|
+
const accumulator = createInstanceFacetAccumulator(opts);
|
|
27276
27341
|
let total = 0;
|
|
27277
27342
|
for (const row of this.scanFindingRows({
|
|
27278
27343
|
sessionId: query.sessionId,
|
|
27279
27344
|
from: query.from
|
|
27280
27345
|
})) {
|
|
27346
|
+
accumulator.add(row);
|
|
27281
27347
|
if (!matchesInstanceFilters(row, opts)) continue;
|
|
27282
27348
|
total += 1;
|
|
27283
27349
|
let files = byRepo.get(row.repo);
|
|
@@ -27292,103 +27358,35 @@ var SqliteFindingsRepository = class {
|
|
|
27292
27358
|
}
|
|
27293
27359
|
addToLocation(acc, row);
|
|
27294
27360
|
}
|
|
27295
|
-
|
|
27296
|
-
const
|
|
27297
|
-
|
|
27298
|
-
|
|
27299
|
-
|
|
27300
|
-
|
|
27301
|
-
|
|
27302
|
-
|
|
27303
|
-
|
|
27304
|
-
|
|
27305
|
-
|
|
27306
|
-
|
|
27307
|
-
|
|
27308
|
-
|
|
27309
|
-
|
|
27310
|
-
|
|
27311
|
-
|
|
27312
|
-
|
|
27313
|
-
|
|
27314
|
-
|
|
27315
|
-
|
|
27316
|
-
|
|
27317
|
-
);
|
|
27318
|
-
const statuses = fileRows.map((f) => f.status);
|
|
27319
|
-
const folded = foldGroupStatus(statuses);
|
|
27320
|
-
return {
|
|
27321
|
-
repo,
|
|
27322
|
-
instanceCount: rollup.instanceCount,
|
|
27323
|
-
maxSeverity: rollup.maxSeverity,
|
|
27324
|
-
latestDetectedAt: rollup.latestDetectedAt,
|
|
27325
|
-
...folded === void 0 ? {} : { status: folded },
|
|
27326
|
-
files: fileRows
|
|
27327
|
-
};
|
|
27328
|
-
});
|
|
27329
|
-
repos.sort(compareLocationOrder);
|
|
27361
|
+
const sorted = [];
|
|
27362
|
+
for (const [repo, files] of byRepo) {
|
|
27363
|
+
for (const [file2, acc] of files) {
|
|
27364
|
+
const status = foldGroupStatus(acc.statuses);
|
|
27365
|
+
sorted.push({
|
|
27366
|
+
id: encodeLocationId(repo, file2),
|
|
27367
|
+
repo,
|
|
27368
|
+
file: file2,
|
|
27369
|
+
instanceCount: acc.instanceCount,
|
|
27370
|
+
maxSeverity: acc.maxSeverity,
|
|
27371
|
+
latestDetectedAt: acc.latestDetectedAt,
|
|
27372
|
+
...status === void 0 ? {} : { status },
|
|
27373
|
+
ruleIds: [...acc.ruleIds]
|
|
27374
|
+
});
|
|
27375
|
+
}
|
|
27376
|
+
}
|
|
27377
|
+
sorted.sort(compareLocationOrder);
|
|
27378
|
+
const start = cursor === null ? 0 : firstLocationAfter(sorted, cursor);
|
|
27379
|
+
const page = sorted.slice(start, start + limit);
|
|
27380
|
+
const lastOnPage = page.at(-1);
|
|
27381
|
+
const nextCursor = start + limit < sorted.length && lastOnPage ? encodeLocationCursor(lastOnPage) : null;
|
|
27382
|
+
const deepLinked = query.includeId === void 0 || query.includeId === "" ? void 0 : findDeepLinkedLocation(sorted, page, query.includeId);
|
|
27330
27383
|
return Promise.resolve({
|
|
27331
|
-
totals: { findings: total,
|
|
27332
|
-
|
|
27333
|
-
|
|
27384
|
+
totals: { findings: total, locations: sorted.length },
|
|
27385
|
+
facets: accumulator.facets(),
|
|
27386
|
+
items: [...page, ...deepLinked ? [deepLinked] : []],
|
|
27387
|
+
nextCursor
|
|
27334
27388
|
});
|
|
27335
27389
|
}
|
|
27336
|
-
/**
|
|
27337
|
-
* Each group's newest instances, for the table's expanded rows.
|
|
27338
|
-
*
|
|
27339
|
-
* ONE index-ordered scan with early termination, and the shape is the point.
|
|
27340
|
-
* The natural spelling — `ROW_NUMBER() OVER (PARTITION BY rule_id ORDER BY
|
|
27341
|
-
* started_at DESC)` then `WHERE rn <= cap` — sorts EVERY finding in scope
|
|
27342
|
-
* through a temp B-tree to keep a bounded preview of each group, and then
|
|
27343
|
-
* sorts the survivors again for the page order. Both sorts grow with the
|
|
27344
|
-
* store while the answer does not.
|
|
27345
|
-
*
|
|
27346
|
-
* Instead the scan walks `audit_events` newest-first off `idx_audit_started_at`
|
|
27347
|
-
* (or the session or window index the scope names — see `findingScanSql`),
|
|
27348
|
-
* which is already the order the page wants, and keeps rows per rule until
|
|
27349
|
-
* each rule has as many as it can show. The aggregate the caller already holds
|
|
27350
|
-
* says how many that is: `min(instanceCount, PREVIEW_INSTANCES_PER_GROUP)`
|
|
27351
|
-
* per rule, summed, is the number of rows this scan has to find, and it stops
|
|
27352
|
-
* on the last one. That sum is bounded by `rules * PREVIEW_INSTANCES_PER_GROUP`
|
|
27353
|
-
* (8,000 at this repo's 40-rule bench corpus), not by a fixed row count — a
|
|
27354
|
-
* store with many firing rules widens it. The bound that DOES hold
|
|
27355
|
-
* unconditionally is the sorted form's floor: this scan visits at most as
|
|
27356
|
-
* many rows as `ROW_NUMBER() OVER (PARTITION BY rule_id …)` would have
|
|
27357
|
-
* sorted, and stops the moment every rule has its cap, where the sorted form
|
|
27358
|
-
* sorts the whole scope regardless. The true worst case — the rarest rule's
|
|
27359
|
-
* wanted instances sitting at the tail of the scope — is one pass over
|
|
27360
|
-
* everything in scope with a block sort of the id tie-break only, never a
|
|
27361
|
-
* sort of the scope, which is still that floor.
|
|
27362
|
-
*
|
|
27363
|
-
* A row whose rule the aggregate did not see is skipped: the two statements
|
|
27364
|
-
* run without a shared snapshot, so a capture landing between them can add a
|
|
27365
|
-
* rule here that has no counts there, and the counts are what the group is
|
|
27366
|
-
* built from.
|
|
27367
|
-
*/
|
|
27368
|
-
previewRows(aggregates, scope) {
|
|
27369
|
-
const wanted = /* @__PURE__ */ new Map();
|
|
27370
|
-
let remaining = 0;
|
|
27371
|
-
for (const [ruleId, agg] of aggregates) {
|
|
27372
|
-
const n = Math.min(agg.instanceCount, PREVIEW_INSTANCES_PER_GROUP);
|
|
27373
|
-
wanted.set(ruleId, n);
|
|
27374
|
-
remaining += n;
|
|
27375
|
-
}
|
|
27376
|
-
const rows = [];
|
|
27377
|
-
if (remaining === 0) return rows;
|
|
27378
|
-
const { sql, params } = this.findingScanSql(scope);
|
|
27379
|
-
const taken = /* @__PURE__ */ new Map();
|
|
27380
|
-
for (const r of iterateRows(this.db.prepare(sql), params)) {
|
|
27381
|
-
const want = wanted.get(r.rule_id);
|
|
27382
|
-
if (want === void 0) continue;
|
|
27383
|
-
const have = taken.get(r.rule_id) ?? 0;
|
|
27384
|
-
if (have >= want) continue;
|
|
27385
|
-
taken.set(r.rule_id, have + 1);
|
|
27386
|
-
rows.push(r);
|
|
27387
|
-
remaining -= 1;
|
|
27388
|
-
if (remaining === 0) break;
|
|
27389
|
-
}
|
|
27390
|
-
return rows;
|
|
27391
|
-
}
|
|
27392
27390
|
/**
|
|
27393
27391
|
* Every finding in scope as a FlatFindingRow, newest first, streamed.
|
|
27394
27392
|
*
|
|
@@ -27415,6 +27413,33 @@ var SqliteFindingsRepository = class {
|
|
|
27415
27413
|
yield toFlatFindingRow(r);
|
|
27416
27414
|
}
|
|
27417
27415
|
}
|
|
27416
|
+
/**
|
|
27417
|
+
* One finding by its own id, or null when no such row exists.
|
|
27418
|
+
*
|
|
27419
|
+
* A primary-key seek on `inspection_findings`, so its cost does not grow with
|
|
27420
|
+
* the store — and, unlike anything derived from a list page, it resolves a
|
|
27421
|
+
* finding of ANY age. That is what the Findings page's one-shot `?finding=`
|
|
27422
|
+
* deep link needs: the id it carries may name a finding thousands of rows
|
|
27423
|
+
* older than anything a first page holds.
|
|
27424
|
+
*
|
|
27425
|
+
* Deliberately UNFILTERED — no capture-kind, session or time predicate. It
|
|
27426
|
+
* RESOLVES an id; whether that row would survive the list's current filters is
|
|
27427
|
+
* a different question, and hiding the target because a filter excludes it is
|
|
27428
|
+
* worse than showing it.
|
|
27429
|
+
*
|
|
27430
|
+
* `groupId` on the result IS the rule id, so this one read answers both "which
|
|
27431
|
+
* type should the list select?" and "what does the drawer show?".
|
|
27432
|
+
*/
|
|
27433
|
+
findingInstance(id) {
|
|
27434
|
+
const row = this.db.prepare(
|
|
27435
|
+
`SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27436
|
+
FROM inspection_findings f
|
|
27437
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
27438
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
27439
|
+
WHERE f.id = ?`
|
|
27440
|
+
).get(id);
|
|
27441
|
+
return Promise.resolve(row === void 0 ? null : toInstanceDetail(toFlatFindingRow(row)));
|
|
27442
|
+
}
|
|
27418
27443
|
/**
|
|
27419
27444
|
* The one statement both instance-level scans run: every finding in scope,
|
|
27420
27445
|
* joined to its event and definition, newest first.
|
|
@@ -27448,17 +27473,7 @@ var SqliteFindingsRepository = class {
|
|
|
27448
27473
|
conditions.push("e.started_at >= ?");
|
|
27449
27474
|
params.push(isoToEpochMillis(scope.from));
|
|
27450
27475
|
}
|
|
27451
|
-
const sql = `SELECT
|
|
27452
|
-
d.severity AS severity, f.masked_match AS masked_match,
|
|
27453
|
-
f.action_taken AS action_taken, f.confidence AS confidence,
|
|
27454
|
-
e.started_at AS occurred_at,
|
|
27455
|
-
e.source_tool AS source_tool,
|
|
27456
|
-
e.repo AS repo,
|
|
27457
|
-
e.file_path AS file,
|
|
27458
|
-
e.tool_name AS tool_name,
|
|
27459
|
-
f.audit_event_id AS event_id, e.root_session_id AS session_id,
|
|
27460
|
-
e.event_type AS kind, f.finding_key AS finding_key,
|
|
27461
|
-
${latestResolutionStatusSql("f")} AS latest_status
|
|
27476
|
+
const sql = `SELECT ${FINDING_ROW_COLUMNS_SQL}
|
|
27462
27477
|
FROM audit_events e
|
|
27463
27478
|
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
27464
27479
|
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -27472,6 +27487,26 @@ var SqliteFindingsRepository = class {
|
|
|
27472
27487
|
group_concat(DISTINCT 'via ' || e.tool_name) AS tool_names` : `, NULL AS repos, NULL AS files, NULL AS tool_names`;
|
|
27473
27488
|
const rows = this.db.prepare(
|
|
27474
27489
|
`SELECT rule_id,
|
|
27490
|
+
-- BARE columns beside max(latest_at), which is deliberate and
|
|
27491
|
+
-- is SQLite's documented behaviour: with a single min()/max()
|
|
27492
|
+
-- in an aggregate query, every bare column takes its value from
|
|
27493
|
+
-- the row that produced the extremum. So these are the severity
|
|
27494
|
+
-- and category of the definition whose finding is NEWEST, which
|
|
27495
|
+
-- is what the row-based build they replaced read off its first
|
|
27496
|
+
-- (newest-first) row.
|
|
27497
|
+
--
|
|
27498
|
+
-- min() is WRONG here and was the defect: inspection_definitions
|
|
27499
|
+
-- holds one row per rule VERSION (see its writer \u2014 a version bump
|
|
27500
|
+
-- mints a new row), so a rule whose severity moved between
|
|
27501
|
+
-- versions has several, and min() picks the ALPHABETICALLY
|
|
27502
|
+
-- smallest \u2014 'low' over 'medium', but 'critical' over 'high'.
|
|
27503
|
+
-- That is arbitrary in direction, and it feeds the badge, the
|
|
27504
|
+
-- filter, the facet counts and the primary sort key.
|
|
27505
|
+
--
|
|
27506
|
+
-- Adding a second min()/max() aggregate here would make these
|
|
27507
|
+
-- bare columns ambiguous again; keep max(latest_at) the only one.
|
|
27508
|
+
severity,
|
|
27509
|
+
category,
|
|
27475
27510
|
sum(tuple_count) AS instance_count,
|
|
27476
27511
|
max(latest_at) AS latest_at,
|
|
27477
27512
|
group_concat(source_tools) AS source_tools,
|
|
@@ -27482,6 +27517,14 @@ var SqliteFindingsRepository = class {
|
|
|
27482
27517
|
group_concat(tool_names) AS tool_names
|
|
27483
27518
|
FROM (
|
|
27484
27519
|
SELECT d.rule_id AS rule_id,
|
|
27520
|
+
-- Severity and category are columns of the DEFINITION, and
|
|
27521
|
+
-- a rule can have SEVERAL definitions (one per version), so
|
|
27522
|
+
-- these are grouped on below and resolved to the newest
|
|
27523
|
+
-- firing version by the outer query's bare-column select.
|
|
27524
|
+
-- They ride the aggregate because the type build has no rows
|
|
27525
|
+
-- to read them off \u2014 see buildFindingTypes.
|
|
27526
|
+
d.severity AS severity,
|
|
27527
|
+
d.category AS category,
|
|
27485
27528
|
e.event_type || '${TUPLE_SEP}' ||
|
|
27486
27529
|
(CASE WHEN f.finding_key IS NULL THEN '' ELSE 'k' END) || '${TUPLE_SEP}' ||
|
|
27487
27530
|
coalesce(latest.status, '') AS status_tuple,
|
|
@@ -27496,7 +27539,7 @@ var SqliteFindingsRepository = class {
|
|
|
27496
27539
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
27497
27540
|
ON latest.finding_key = f.finding_key
|
|
27498
27541
|
${scope.predicate}
|
|
27499
|
-
GROUP BY d.rule_id, status_tuple
|
|
27542
|
+
GROUP BY d.rule_id, d.severity, d.category, status_tuple
|
|
27500
27543
|
)
|
|
27501
27544
|
GROUP BY rule_id`
|
|
27502
27545
|
).all(scope.params);
|
|
@@ -27505,6 +27548,8 @@ var SqliteFindingsRepository = class {
|
|
|
27505
27548
|
r.rule_id,
|
|
27506
27549
|
{
|
|
27507
27550
|
instanceCount: r.instance_count,
|
|
27551
|
+
severity: r.severity,
|
|
27552
|
+
category: r.category,
|
|
27508
27553
|
sourceTools: splitConcat(r.source_tools),
|
|
27509
27554
|
actionsTaken: splitConcat(r.actions_taken),
|
|
27510
27555
|
statusInputs: splitConcat(r.status_inputs).map((tuple2) => {
|
|
@@ -27521,7 +27566,7 @@ var SqliteFindingsRepository = class {
|
|
|
27521
27566
|
latestDetectedAt: epochMillisToIso(r.latest_at),
|
|
27522
27567
|
// Free text only — joined and substring-matched, so group_concat's
|
|
27523
27568
|
// commas need no unpicking (a repo/path containing one still matches).
|
|
27524
|
-
// Left undefined (not '') when unfetched, so
|
|
27569
|
+
// Left undefined (not '') when unfetched, so buildFindingTypes can
|
|
27525
27570
|
// tell "no q this request" from "a group with no repo/file at all"
|
|
27526
27571
|
// and skip priming a haystack nothing will read.
|
|
27527
27572
|
...withSearchText ? {
|
|
@@ -27673,6 +27718,12 @@ var SqliteHistorySyncRepository = class {
|
|
|
27673
27718
|
this.markOwedStmt = db.prepare(
|
|
27674
27719
|
`UPDATE audit_events SET outbox_owed = 1 WHERE id = :id AND synced_at IS NULL`
|
|
27675
27720
|
);
|
|
27721
|
+
this.markCaptureBacklogOwedStmt = db.prepare(
|
|
27722
|
+
`UPDATE audit_events SET outbox_owed = 1
|
|
27723
|
+
WHERE synced_at IS NULL
|
|
27724
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27725
|
+
AND started_at < :before`
|
|
27726
|
+
);
|
|
27676
27727
|
this.stampStmt = db.prepare(
|
|
27677
27728
|
`UPDATE audit_events SET synced_at = :at, sync_claimed_at = NULL WHERE id = :id`
|
|
27678
27729
|
);
|
|
@@ -27721,7 +27772,9 @@ var SqliteHistorySyncRepository = class {
|
|
|
27721
27772
|
);
|
|
27722
27773
|
this.disownCapturesStmt = db.prepare(
|
|
27723
27774
|
`UPDATE audit_events SET outbox_owed = NULL
|
|
27724
|
-
WHERE outbox_owed IS NOT NULL
|
|
27775
|
+
WHERE outbox_owed IS NOT NULL
|
|
27776
|
+
AND event_type IN (${CAPTURE_TYPE_LIST})
|
|
27777
|
+
AND started_at < :attachedAt`
|
|
27725
27778
|
);
|
|
27726
27779
|
this.rearmStmt = db.prepare(
|
|
27727
27780
|
`UPDATE audit_events SET synced_at = NULL
|
|
@@ -27797,6 +27850,7 @@ var SqliteHistorySyncRepository = class {
|
|
|
27797
27850
|
freezeBoundaryStmt;
|
|
27798
27851
|
captureRowsStmt;
|
|
27799
27852
|
markOwedStmt;
|
|
27853
|
+
markCaptureBacklogOwedStmt;
|
|
27800
27854
|
captureSkipCountStmt;
|
|
27801
27855
|
disownCapturesStmt;
|
|
27802
27856
|
partitionStmt;
|
|
@@ -27860,6 +27914,23 @@ var SqliteHistorySyncRepository = class {
|
|
|
27860
27914
|
markCaptureOwed(id) {
|
|
27861
27915
|
this.markOwedStmt.run({ id });
|
|
27862
27916
|
}
|
|
27917
|
+
/**
|
|
27918
|
+
* Mark every capture already on disk as owed, as of `before`.
|
|
27919
|
+
*
|
|
27920
|
+
* The consent-time backfill, called once from `aka attach` when a human
|
|
27921
|
+
* grants existing-history consent — never from an ongoing drain pass, and
|
|
27922
|
+
* never inferred from a boundary that could later move. `before` is the
|
|
27923
|
+
* caller's own "now" at the moment consent was granted, so what this marks
|
|
27924
|
+
* is exactly the backlog the consent prompt already counted, not whatever a
|
|
27925
|
+
* later re-attach or key rotation might widen it to.
|
|
27926
|
+
*
|
|
27927
|
+
* Returns how many rows matched, for the caller to log or test against. Not a
|
|
27928
|
+
* count of NEWLY marked rows — a row still unsynced from an earlier call
|
|
27929
|
+
* matches again and is counted again, the same as `UPDATE`'s own `changes`.
|
|
27930
|
+
*/
|
|
27931
|
+
markCaptureBacklogOwed(before) {
|
|
27932
|
+
return Number(this.markCaptureBacklogOwedStmt.run({ before }).changes);
|
|
27933
|
+
}
|
|
27863
27934
|
/** Record delivery. Called only AFTER the far side has accepted the rows. */
|
|
27864
27935
|
markSynced(ids, atMs) {
|
|
27865
27936
|
this.stampAll(ids, atMs);
|
|
@@ -27976,15 +28047,42 @@ var SqliteHistorySyncRepository = class {
|
|
|
27976
28047
|
*
|
|
27977
28048
|
* Delivery is a fact about ONE recipient: rows sent to the deployment a
|
|
27978
28049
|
* machine has just left are undelivered as far as the new one is concerned.
|
|
27979
|
-
* All
|
|
27980
|
-
* attributed to the wrong deployment,
|
|
28050
|
+
* All four in one transaction, so a crash between them cannot leave stamps
|
|
28051
|
+
* attributed to the wrong deployment, a boundary that belongs to another, or
|
|
28052
|
+
* a disown with no re-mark to follow it.
|
|
27981
28053
|
*
|
|
27982
28054
|
* The boundary is written HERE and only here, which is what freezes it: a
|
|
27983
28055
|
* re-attach to the SAME deployment (a key rotation) leaves the fingerprint
|
|
27984
28056
|
* unchanged, so this never runs and the backlog does not widen back over rows
|
|
27985
28057
|
* the live path has since delivered.
|
|
28058
|
+
*
|
|
28059
|
+
* `backfillCapturesBefore` is the caller's OWN "now" at the instant a human
|
|
28060
|
+
* granted existing-history consent for the deployment this call is arming —
|
|
28061
|
+
* a DIFFERENT instant from `backlogBefore`: the re-mark boundary is the GRANT
|
|
28062
|
+
* instant, `backlogBefore` is the ATTACH instant, and the two can be far
|
|
28063
|
+
* apart. Passed only when that grant is valid, since this method has no way
|
|
28064
|
+
* to check consent itself and must not mark a row owed for a machine that
|
|
28065
|
+
* never agreed to it. Applied AFTER the disown above, in the SAME
|
|
28066
|
+
* transaction: what the disown clears is every marker below `backlogBefore`,
|
|
28067
|
+
* which includes this deployment's OWN pre-attach rows — `aka attach` calls
|
|
28068
|
+
* `seedCaptureBacklogOwed` at the attach instant, so every row it marks sits
|
|
28069
|
+
* on the cleared side of that bound — and the re-mark in the same
|
|
28070
|
+
* transaction is what puts those rows back. A crash between the two cannot
|
|
28071
|
+
* strand the ledger disowned with nothing re-marked — the transaction either
|
|
28072
|
+
* lands whole or not at all, and a fingerprint mismatch that has not yet
|
|
28073
|
+
* committed re-enters this method on the very next pass. Omit it (the
|
|
28074
|
+
* structural-only tests do) to exercise the disown in isolation.
|
|
28075
|
+
*
|
|
28076
|
+
* The disown is bounded by `backlogBefore`, which is what keeps it from
|
|
28077
|
+
* touching a marker the NEW deployment's OWN live path has already set: B's
|
|
28078
|
+
* live path can mark a capture owed from the moment `aka attach` writes the
|
|
28079
|
+
* descriptor, before the drain's first pass ever reaches this method, and
|
|
28080
|
+
* such a row sits at or after the bound rather than below it. What keeps the
|
|
28081
|
+
* disown from eating THIS SAME CALL's own re-mark is the order, not the
|
|
28082
|
+
* bound — disown runs first, re-mark second, both inside the one
|
|
28083
|
+
* transaction above.
|
|
27986
28084
|
*/
|
|
27987
|
-
rearmFor(fingerprint, backlogBefore) {
|
|
28085
|
+
rearmFor(fingerprint, backlogBefore, backfillCapturesBefore) {
|
|
27988
28086
|
this.ensureRowStmt.run();
|
|
27989
28087
|
withTransaction(
|
|
27990
28088
|
this.db,
|
|
@@ -27992,7 +28090,10 @@ var SqliteHistorySyncRepository = class {
|
|
|
27992
28090
|
const previous = getRow(this.fingerprintStmt)?.fingerprint;
|
|
27993
28091
|
this.rearmStmt.run();
|
|
27994
28092
|
if (previous !== null && previous !== void 0 && previous !== fingerprint) {
|
|
27995
|
-
this.disownCapturesStmt.run();
|
|
28093
|
+
this.disownCapturesStmt.run({ attachedAt: backlogBefore });
|
|
28094
|
+
}
|
|
28095
|
+
if (backfillCapturesBefore !== void 0) {
|
|
28096
|
+
this.markCaptureBacklogOwedStmt.run({ before: backfillCapturesBefore });
|
|
27996
28097
|
}
|
|
27997
28098
|
this.setFingerprintStmt.run({ fingerprint, backlogBefore });
|
|
27998
28099
|
},
|
|
@@ -28272,7 +28373,8 @@ function managedSettingsPaths(platform2 = process.platform) {
|
|
|
28272
28373
|
}
|
|
28273
28374
|
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
28274
28375
|
}
|
|
28275
|
-
|
|
28376
|
+
var testOnlyManagedPaths = null;
|
|
28377
|
+
function readManagedSettings(paths = testOnlyManagedPaths ?? managedSettingsPaths()) {
|
|
28276
28378
|
for (const path of paths) {
|
|
28277
28379
|
let text;
|
|
28278
28380
|
try {
|
|
@@ -30752,7 +30854,7 @@ function toUtcDateString(ms) {
|
|
|
30752
30854
|
return new Date(ms).toISOString().slice(0, 10);
|
|
30753
30855
|
}
|
|
30754
30856
|
function isTimeseriesSeverity(s) {
|
|
30755
|
-
return s === "critical" || s === "high" || s === "medium";
|
|
30857
|
+
return s === "critical" || s === "high" || s === "medium" || s === "low";
|
|
30756
30858
|
}
|
|
30757
30859
|
var SqliteSecurityRepository = class {
|
|
30758
30860
|
constructor(db, now = () => Date.now()) {
|
|
@@ -30881,12 +30983,16 @@ var SqliteSecurityRepository = class {
|
|
|
30881
30983
|
const now = this.now();
|
|
30882
30984
|
const windowStart = startOfUtcDay2(now) - (lenDays - 1) * DAY_MS4;
|
|
30883
30985
|
const rows = this.findingsInRange(windowStart, now);
|
|
30884
|
-
const points = Array.from(
|
|
30885
|
-
|
|
30886
|
-
|
|
30887
|
-
|
|
30888
|
-
|
|
30889
|
-
|
|
30986
|
+
const points = Array.from(
|
|
30987
|
+
{ length: numBuckets },
|
|
30988
|
+
(_, i) => ({
|
|
30989
|
+
timestamp: toUtcDateString(windowStart + i * bucketMs),
|
|
30990
|
+
critical: 0,
|
|
30991
|
+
high: 0,
|
|
30992
|
+
medium: 0,
|
|
30993
|
+
low: 0
|
|
30994
|
+
})
|
|
30995
|
+
);
|
|
30890
30996
|
for (const r of rows) {
|
|
30891
30997
|
const idx = Math.floor((r.occurredAt - windowStart) / bucketMs);
|
|
30892
30998
|
const bucket = points[idx];
|
|
@@ -31104,6 +31210,7 @@ var SqliteSecurityRepository = class {
|
|
|
31104
31210
|
`SELECT f.finding_key AS finding_key,
|
|
31105
31211
|
d.rule_id AS rule_id,
|
|
31106
31212
|
d.severity AS severity,
|
|
31213
|
+
e.repo AS repo,
|
|
31107
31214
|
e.file_path AS path,
|
|
31108
31215
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
31109
31216
|
latest.resolved_at AS latest_resolved_at
|
|
@@ -31123,6 +31230,7 @@ var SqliteSecurityRepository = class {
|
|
|
31123
31230
|
const items = rows.map((r) => ({
|
|
31124
31231
|
findingKey: r.finding_key,
|
|
31125
31232
|
ruleId: r.rule_id,
|
|
31233
|
+
repo: r.repo ?? "",
|
|
31126
31234
|
severity: r.severity,
|
|
31127
31235
|
path: r.path ?? "",
|
|
31128
31236
|
resolvedAt: new Date(r.latest_resolved_at).toISOString(),
|
|
@@ -31132,13 +31240,66 @@ var SqliteSecurityRepository = class {
|
|
|
31132
31240
|
}));
|
|
31133
31241
|
return Promise.resolve({ items });
|
|
31134
31242
|
}
|
|
31243
|
+
/**
|
|
31244
|
+
* Per-rule tallies of the findings that are still OPEN, whole-store.
|
|
31245
|
+
*
|
|
31246
|
+
* Scoped by status rather than by time, because the card this feeds is a to-do
|
|
31247
|
+
* list: a secret committed three weeks ago and never rotated is still the most
|
|
31248
|
+
* important thing to fix, and any window hides it. It carried a "newest N
|
|
31249
|
+
* findings" cap and then a range; the first meant a different span on every
|
|
31250
|
+
* machine, and the second reported "no recommendations" over live exposure.
|
|
31251
|
+
*
|
|
31252
|
+
* `open` mirrors `deriveFindingStatus` — at-rest, minus resolved and dismissed —
|
|
31253
|
+
* so a row's count is exactly what `?status=open&type=<rule>` returns. Note that
|
|
31254
|
+
* is NOT `severitySummary`'s `openAtRest`, which keeps dismissed findings (a
|
|
31255
|
+
* dismissal is a judgement, not a remediation) and drops untracked legacy rows.
|
|
31256
|
+
* The two answer different questions and only this one has to match a link.
|
|
31257
|
+
*
|
|
31258
|
+
* Aggregated in SQL: the result is O(distinct rule × category × severity), so a
|
|
31259
|
+
* whole-store scope costs a grouped scan rather than a row per finding.
|
|
31260
|
+
*/
|
|
31261
|
+
recommendationInputs() {
|
|
31262
|
+
const rows = allRows(
|
|
31263
|
+
this.db.prepare(
|
|
31264
|
+
`SELECT d.rule_id AS rule_id,
|
|
31265
|
+
d.category AS category,
|
|
31266
|
+
d.severity AS severity,
|
|
31267
|
+
COUNT(*) AS count
|
|
31268
|
+
FROM inspection_findings f
|
|
31269
|
+
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31270
|
+
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
31271
|
+
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
31272
|
+
ON latest.finding_key = f.finding_key
|
|
31273
|
+
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
31274
|
+
AND e.event_type = 'code_change'
|
|
31275
|
+
AND (
|
|
31276
|
+
f.finding_key IS NULL
|
|
31277
|
+
OR latest.status IS NULL
|
|
31278
|
+
OR latest.status NOT IN ('resolved', 'dismissed')
|
|
31279
|
+
)
|
|
31280
|
+
GROUP BY d.rule_id, d.category, d.severity`
|
|
31281
|
+
)
|
|
31282
|
+
);
|
|
31283
|
+
return Promise.resolve(
|
|
31284
|
+
rows.map((r) => ({
|
|
31285
|
+
ruleId: r.rule_id,
|
|
31286
|
+
category: r.category,
|
|
31287
|
+
severity: r.severity,
|
|
31288
|
+
count: r.count
|
|
31289
|
+
}))
|
|
31290
|
+
);
|
|
31291
|
+
}
|
|
31135
31292
|
// Findings whose parent event occurred in [fromMs, toMs), with the parent's
|
|
31136
31293
|
// epoch-millis timestamp. started_at is an INTEGER column, so the bounds stay
|
|
31137
31294
|
// numeric and the JS aggregations bucket/split on ms directly.
|
|
31138
31295
|
findingsInRange(fromMs, toMs) {
|
|
31139
31296
|
const rows = allRows(
|
|
31140
31297
|
this.db.prepare(
|
|
31141
|
-
`
|
|
31298
|
+
// `rule_id`/`category` cost nothing extra: inspection_definitions is already
|
|
31299
|
+
// joined for `severity`, so they are two more columns off a row this read
|
|
31300
|
+
// already fetches. They feed the recommended-actions rollup.
|
|
31301
|
+
`SELECT e.started_at AS occurred_at, d.severity AS severity, f.action_taken AS action_taken,
|
|
31302
|
+
d.rule_id AS rule_id, d.category AS category
|
|
31142
31303
|
FROM inspection_findings f
|
|
31143
31304
|
JOIN audit_events e ON e.id = f.audit_event_id
|
|
31144
31305
|
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
@@ -31151,7 +31312,9 @@ var SqliteSecurityRepository = class {
|
|
|
31151
31312
|
return rows.map((r) => ({
|
|
31152
31313
|
occurredAt: r.occurred_at,
|
|
31153
31314
|
severity: r.severity,
|
|
31154
|
-
actionTaken: r.action_taken
|
|
31315
|
+
actionTaken: r.action_taken,
|
|
31316
|
+
ruleId: r.rule_id,
|
|
31317
|
+
category: r.category
|
|
31155
31318
|
}));
|
|
31156
31319
|
}
|
|
31157
31320
|
};
|
|
@@ -32321,8 +32484,45 @@ function openLocalDatabase(dir) {
|
|
|
32321
32484
|
};
|
|
32322
32485
|
}
|
|
32323
32486
|
|
|
32324
|
-
// ../../packages/persistence/src/
|
|
32487
|
+
// ../../packages/persistence/src/egress-wire.ts
|
|
32325
32488
|
import { createHash as createHash3 } from "crypto";
|
|
32489
|
+
function hashProjectKey(projectKey) {
|
|
32490
|
+
return createHash3("sha256").update(projectKey, "utf8").digest("hex");
|
|
32491
|
+
}
|
|
32492
|
+
function toIngestHit(hit) {
|
|
32493
|
+
return {
|
|
32494
|
+
host: hit.host,
|
|
32495
|
+
kind: hit.kind,
|
|
32496
|
+
name: hit.name,
|
|
32497
|
+
category: hit.category,
|
|
32498
|
+
trust: hit.trust,
|
|
32499
|
+
network: hit.network,
|
|
32500
|
+
method: hit.method,
|
|
32501
|
+
transport: hit.transport,
|
|
32502
|
+
url: hit.url,
|
|
32503
|
+
template: hit.template,
|
|
32504
|
+
dataClass: hit.dataClass,
|
|
32505
|
+
site: {
|
|
32506
|
+
file: hit.site.file,
|
|
32507
|
+
line: hit.site.line,
|
|
32508
|
+
dynamic: hit.site.dynamic,
|
|
32509
|
+
vendored: hit.site.vendored
|
|
32510
|
+
}
|
|
32511
|
+
};
|
|
32512
|
+
}
|
|
32513
|
+
function toEgressIngestRequest(input2) {
|
|
32514
|
+
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
32515
|
+
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
32516
|
+
return {
|
|
32517
|
+
projectKey: hashProjectKey(input2.projectKey),
|
|
32518
|
+
project: input2.project,
|
|
32519
|
+
reconcile,
|
|
32520
|
+
hits: hits.map(toIngestHit)
|
|
32521
|
+
};
|
|
32522
|
+
}
|
|
32523
|
+
|
|
32524
|
+
// ../../packages/persistence/src/finding-key.ts
|
|
32525
|
+
import { createHash as createHash4 } from "crypto";
|
|
32326
32526
|
|
|
32327
32527
|
// ../../packages/persistence/src/fingerprint.ts
|
|
32328
32528
|
import { createHmac, randomBytes } from "crypto";
|
|
@@ -32363,14 +32563,18 @@ function readFingerprintKey(dataDir2) {
|
|
|
32363
32563
|
return parseKeyFile(raw);
|
|
32364
32564
|
}
|
|
32365
32565
|
|
|
32366
|
-
// ../../packages/persistence/src/history-
|
|
32566
|
+
// ../../packages/persistence/src/history-backfill.ts
|
|
32367
32567
|
import { existsSync as existsSync4 } from "fs";
|
|
32368
32568
|
import { join as join9 } from "path";
|
|
32569
|
+
|
|
32570
|
+
// ../../packages/persistence/src/history-preview.ts
|
|
32571
|
+
import { existsSync as existsSync5 } from "fs";
|
|
32572
|
+
import { join as join10 } from "path";
|
|
32369
32573
|
import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
|
|
32370
32574
|
|
|
32371
32575
|
// ../../packages/persistence/src/store-symlinks.ts
|
|
32372
|
-
import { existsSync as
|
|
32373
|
-
import { dirname as dirname3, join as
|
|
32576
|
+
import { existsSync as existsSync6, lstatSync as lstatSync3, readlinkSync, realpathSync, statSync as statSync4 } from "fs";
|
|
32577
|
+
import { dirname as dirname3, join as join11, resolve } from "path";
|
|
32374
32578
|
var STORE_DB = "the store database (including the prompt corpus)";
|
|
32375
32579
|
var STORE_SETTINGS = "your settings file";
|
|
32376
32580
|
function storeContents(home) {
|
|
@@ -32379,7 +32583,7 @@ function storeContents(home) {
|
|
|
32379
32583
|
[settingsDir(home), STORE_SETTINGS],
|
|
32380
32584
|
[dataDir(home), STORE_DB],
|
|
32381
32585
|
[keysDir(home), "the vault key"],
|
|
32382
|
-
[
|
|
32586
|
+
[join11(settingsDir(home), "settings.json"), STORE_SETTINGS],
|
|
32383
32587
|
[dbPath(home), STORE_DB]
|
|
32384
32588
|
]);
|
|
32385
32589
|
}
|
|
@@ -32394,7 +32598,7 @@ function symlinkedStorePaths(home, platform2 = process.platform) {
|
|
|
32394
32598
|
holds,
|
|
32395
32599
|
// existsSync follows the link, so a target that is gone reads as
|
|
32396
32600
|
// absent here while lstat above still sees the link itself.
|
|
32397
|
-
missing: !
|
|
32601
|
+
missing: !existsSync6(path),
|
|
32398
32602
|
mode: targetMode(path, platform2)
|
|
32399
32603
|
}
|
|
32400
32604
|
];
|
|
@@ -32432,19 +32636,19 @@ import {
|
|
|
32432
32636
|
import { execFileSync } from "child_process";
|
|
32433
32637
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
32434
32638
|
import { chmodSync as chmodSync3, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, statSync as statSync5, writeFileSync as writeFileSync3 } from "fs";
|
|
32435
|
-
import { join as
|
|
32639
|
+
import { join as join12 } from "path";
|
|
32436
32640
|
|
|
32437
32641
|
// ../../packages/persistence/src/vault/vault.ts
|
|
32438
32642
|
import { randomBytes as randomBytes3, randomUUID as randomUUID12 } from "crypto";
|
|
32439
32643
|
|
|
32440
32644
|
// ../../packages/persistence/src/warn-era-cap.ts
|
|
32441
|
-
import { existsSync as
|
|
32442
|
-
import { join as
|
|
32645
|
+
import { existsSync as existsSync7, writeFileSync as writeFileSync4 } from "fs";
|
|
32646
|
+
import { join as join13 } from "path";
|
|
32443
32647
|
var MARKER = "warn-era-capped";
|
|
32444
32648
|
function capWarnEraEnforcementOnce(db, policyMode, dataDir2) {
|
|
32445
32649
|
if (policyMode !== "warn") return { capped: 0, skipped: "not-warn" };
|
|
32446
|
-
const marker =
|
|
32447
|
-
if (
|
|
32650
|
+
const marker = join13(dataDir2, MARKER);
|
|
32651
|
+
if (existsSync7(marker)) return { capped: 0, skipped: "already-run" };
|
|
32448
32652
|
const capped = db.policies.capCategoryActions();
|
|
32449
32653
|
writeFileSync4(marker, `${new Date(Date.now()).toISOString()}
|
|
32450
32654
|
`, { mode: DATA_FILE_MODE });
|
|
@@ -32503,8 +32707,8 @@ function resolveProvider() {
|
|
|
32503
32707
|
function loadConfig(base = defaultDataDir(), resolveProviderFn = resolveProvider) {
|
|
32504
32708
|
try {
|
|
32505
32709
|
ensureLayoutDirSync(base);
|
|
32506
|
-
const settingsFile =
|
|
32507
|
-
if (
|
|
32710
|
+
const settingsFile = join14(settingsDir(base), "settings.json");
|
|
32711
|
+
if (existsSync8(settingsFile)) tightenFile(settingsFile);
|
|
32508
32712
|
} catch {
|
|
32509
32713
|
}
|
|
32510
32714
|
migrateLegacyLayout(base);
|
|
@@ -32529,7 +32733,7 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
32529
32733
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
32530
32734
|
import { readdirSync as readdirSync2, readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync7 } from "fs";
|
|
32531
32735
|
import { homedir as homedir2 } from "os";
|
|
32532
|
-
import { basename as basename3, join as
|
|
32736
|
+
import { basename as basename3, join as join16 } from "path";
|
|
32533
32737
|
|
|
32534
32738
|
// ../../packages/detections/src/egress/registry.ts
|
|
32535
32739
|
var EXTRACTOR_VERSION = "1";
|
|
@@ -35312,24 +35516,20 @@ function bundledDetections() {
|
|
|
35312
35516
|
}
|
|
35313
35517
|
|
|
35314
35518
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
35315
|
-
import { existsSync as
|
|
35316
|
-
import { basename as basename2, dirname as dirname4, isAbsolute, join as
|
|
35519
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, statSync as statSync6 } from "fs";
|
|
35520
|
+
import { basename as basename2, dirname as dirname4, isAbsolute, join as join15, sep as sep2 } from "path";
|
|
35317
35521
|
|
|
35318
35522
|
// ../../packages/plugin-sdk/src/events.ts
|
|
35319
|
-
import { createHash as
|
|
35523
|
+
import { createHash as createHash5, randomUUID as randomUUID13 } from "crypto";
|
|
35320
35524
|
|
|
35321
35525
|
// ../../packages/plugin-sdk/src/isolated-scan.ts
|
|
35322
|
-
import { existsSync as
|
|
35526
|
+
import { existsSync as existsSync10 } from "fs";
|
|
35323
35527
|
import { fileURLToPath } from "url";
|
|
35324
35528
|
import { Worker } from "worker_threads";
|
|
35325
35529
|
|
|
35326
|
-
// ../../packages/plugin-sdk/src/
|
|
35327
|
-
|
|
35328
|
-
import {
|
|
35329
|
-
import { join as join16 } from "path";
|
|
35330
|
-
|
|
35331
|
-
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
35332
|
-
import { arch, hostname as hostname4, platform, release } from "os";
|
|
35530
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
35531
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
35532
|
+
import { join as join18 } from "path";
|
|
35333
35533
|
|
|
35334
35534
|
// ../../packages/plugin-sdk/src/model-governance.ts
|
|
35335
35535
|
import {
|
|
@@ -35337,7 +35537,7 @@ import {
|
|
|
35337
35537
|
fstatSync,
|
|
35338
35538
|
mkdirSync as mkdirSync2,
|
|
35339
35539
|
openSync as openSync2,
|
|
35340
|
-
readFileSync as
|
|
35540
|
+
readFileSync as readFileSync10,
|
|
35341
35541
|
readSync,
|
|
35342
35542
|
writeFileSync as writeFileSync5
|
|
35343
35543
|
} from "fs";
|
|
@@ -35393,17 +35593,43 @@ function buildModelRefusalEvent(input2) {
|
|
|
35393
35593
|
};
|
|
35394
35594
|
}
|
|
35395
35595
|
|
|
35596
|
+
// ../../packages/plugin-sdk/src/host-floor.ts
|
|
35597
|
+
var HOST_FEATURE = {
|
|
35598
|
+
ModelSwitch: "model-switch",
|
|
35599
|
+
VaultPointerDisplay: "vault-pointer-display"
|
|
35600
|
+
};
|
|
35601
|
+
var HOST_FLOORS = {
|
|
35602
|
+
[HOST_FEATURE.ModelSwitch]: {
|
|
35603
|
+
label: "model-switch protection",
|
|
35604
|
+
hookEvents: ["PreModelSwitch", "PostModelSwitch"],
|
|
35605
|
+
since: "2.1.251"
|
|
35606
|
+
},
|
|
35607
|
+
[HOST_FEATURE.VaultPointerDisplay]: {
|
|
35608
|
+
label: "vault pointer display",
|
|
35609
|
+
hookEvents: ["MessageDisplay"],
|
|
35610
|
+
since: "2.1.152"
|
|
35611
|
+
}
|
|
35612
|
+
};
|
|
35613
|
+
|
|
35614
|
+
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
35615
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
35616
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
35617
|
+
import { join as join19 } from "path";
|
|
35618
|
+
|
|
35619
|
+
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
35620
|
+
import { arch, hostname as hostname4, platform, release } from "os";
|
|
35621
|
+
|
|
35396
35622
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
35397
|
-
import { mkdirSync as mkdirSync3, readFileSync as
|
|
35398
|
-
import { join as
|
|
35623
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
35624
|
+
import { join as join20 } from "path";
|
|
35399
35625
|
|
|
35400
35626
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
35401
35627
|
import { readdirSync as readdirSync3, realpathSync as realpathSync3 } from "fs";
|
|
35402
35628
|
import { basename as basename4, dirname as dirname5, sep as sep3 } from "path";
|
|
35403
35629
|
|
|
35404
35630
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
35405
|
-
import { existsSync as
|
|
35406
|
-
import { basename as basename5, join as
|
|
35631
|
+
import { existsSync as existsSync11, readdirSync as readdirSync4 } from "fs";
|
|
35632
|
+
import { basename as basename5, join as join21 } from "path";
|
|
35407
35633
|
|
|
35408
35634
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
35409
35635
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -35439,16 +35665,16 @@ var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
|
35439
35665
|
|
|
35440
35666
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
35441
35667
|
import { mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
35442
|
-
import { join as
|
|
35668
|
+
import { join as join22 } from "path";
|
|
35443
35669
|
|
|
35444
35670
|
// src/hooks/model-switch-run.ts
|
|
35445
35671
|
import { randomUUID as randomUUID16 } from "crypto";
|
|
35446
35672
|
|
|
35447
35673
|
// src/hooks/model-guard.ts
|
|
35448
35674
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
35449
|
-
import { readFileSync as
|
|
35675
|
+
import { readFileSync as readFileSync14, statSync as statSync9 } from "fs";
|
|
35450
35676
|
import { homedir as homedir3 } from "os";
|
|
35451
|
-
import { dirname as dirname6, join as
|
|
35677
|
+
import { dirname as dirname6, join as join23 } from "path";
|
|
35452
35678
|
function decidePreModelSwitch(toModel, prohibitedModels) {
|
|
35453
35679
|
if (toModel === void 0 || toModel === "") return null;
|
|
35454
35680
|
if (!isModelProhibited(toModel, prohibitedModels)) return null;
|
|
@@ -35548,45 +35774,8 @@ function emit(output2) {
|
|
|
35548
35774
|
}
|
|
35549
35775
|
|
|
35550
35776
|
// src/hooks/store-health.ts
|
|
35551
|
-
import { mkdirSync as mkdirSync5, readFileSync as
|
|
35552
|
-
import { dirname as dirname7, join as
|
|
35553
|
-
|
|
35554
|
-
// ../../packages/plugin-runtime/src/attached/egress-wire.ts
|
|
35555
|
-
import { createHash as createHash5 } from "crypto";
|
|
35556
|
-
function hashProjectKey(projectKey) {
|
|
35557
|
-
return createHash5("sha256").update(projectKey, "utf8").digest("hex");
|
|
35558
|
-
}
|
|
35559
|
-
function toIngestHit(hit) {
|
|
35560
|
-
return {
|
|
35561
|
-
host: hit.host,
|
|
35562
|
-
kind: hit.kind,
|
|
35563
|
-
name: hit.name,
|
|
35564
|
-
category: hit.category,
|
|
35565
|
-
trust: hit.trust,
|
|
35566
|
-
network: hit.network,
|
|
35567
|
-
method: hit.method,
|
|
35568
|
-
transport: hit.transport,
|
|
35569
|
-
url: hit.url,
|
|
35570
|
-
template: hit.template,
|
|
35571
|
-
dataClass: hit.dataClass,
|
|
35572
|
-
site: {
|
|
35573
|
-
file: hit.site.file,
|
|
35574
|
-
line: hit.site.line,
|
|
35575
|
-
dynamic: hit.site.dynamic,
|
|
35576
|
-
vendored: hit.site.vendored
|
|
35577
|
-
}
|
|
35578
|
-
};
|
|
35579
|
-
}
|
|
35580
|
-
function toEgressIngestRequest(input2) {
|
|
35581
|
-
const { hits, droppedFiles } = capHits(input2.hits, input2.reconcile.mode);
|
|
35582
|
-
const reconcile = withoutDroppedFiles(input2.reconcile, droppedFiles);
|
|
35583
|
-
return {
|
|
35584
|
-
projectKey: hashProjectKey(input2.projectKey),
|
|
35585
|
-
project: input2.project,
|
|
35586
|
-
reconcile,
|
|
35587
|
-
hits: hits.map(toIngestHit)
|
|
35588
|
-
};
|
|
35589
|
-
}
|
|
35777
|
+
import { mkdirSync as mkdirSync5, readFileSync as readFileSync20, writeFileSync as writeFileSync8 } from "fs";
|
|
35778
|
+
import { dirname as dirname7, join as join30 } from "path";
|
|
35590
35779
|
|
|
35591
35780
|
// ../../packages/remote/src/http.ts
|
|
35592
35781
|
import { request as httpRequest } from "http";
|
|
@@ -35867,6 +36056,7 @@ function createRemoteClient(options) {
|
|
|
35867
36056
|
url: url2(ROUTES.shares),
|
|
35868
36057
|
body: JSON.stringify(validated.data)
|
|
35869
36058
|
});
|
|
36059
|
+
if (response.status === 404) throw new RemoteRouteAbsent(ROUTES.shares);
|
|
35870
36060
|
okBody(response);
|
|
35871
36061
|
},
|
|
35872
36062
|
async pollCommand() {
|
|
@@ -35889,19 +36079,51 @@ function createRemoteClient(options) {
|
|
|
35889
36079
|
};
|
|
35890
36080
|
}
|
|
35891
36081
|
|
|
35892
|
-
// ../../packages/
|
|
36082
|
+
// ../../packages/remote/src/failure-kind.ts
|
|
35893
36083
|
function statusOf(err) {
|
|
35894
36084
|
if (typeof err !== "object" || err === null || !("status" in err)) return null;
|
|
35895
36085
|
const { status } = err;
|
|
35896
36086
|
if (typeof status !== "number" || !Number.isInteger(status)) return null;
|
|
35897
36087
|
return status >= 100 && status <= 599 ? status : null;
|
|
35898
36088
|
}
|
|
35899
|
-
function
|
|
35900
|
-
|
|
36089
|
+
function nameOf(err) {
|
|
36090
|
+
if (typeof err !== "object" || err === null || !("name" in err)) return null;
|
|
36091
|
+
return typeof err.name === "string" ? err.name : null;
|
|
36092
|
+
}
|
|
36093
|
+
function classifyRemoteFailure(err) {
|
|
36094
|
+
switch (nameOf(err)) {
|
|
36095
|
+
case "RemoteRouteAbsent":
|
|
36096
|
+
return "route-absent";
|
|
36097
|
+
case "RemoteRequestInvalid":
|
|
36098
|
+
return "invalid-request";
|
|
36099
|
+
case "RemoteResponseInvalid":
|
|
36100
|
+
return "rejected";
|
|
36101
|
+
default:
|
|
36102
|
+
break;
|
|
36103
|
+
}
|
|
36104
|
+
const status = statusOf(err);
|
|
36105
|
+
if (status === null) return "unreachable";
|
|
36106
|
+
switch (status) {
|
|
35901
36107
|
case 401:
|
|
35902
36108
|
return "unauthorized";
|
|
35903
36109
|
case 403:
|
|
35904
36110
|
return "forbidden";
|
|
36111
|
+
case 429:
|
|
36112
|
+
return "unreachable";
|
|
36113
|
+
case 404:
|
|
36114
|
+
return "unreachable";
|
|
36115
|
+
default:
|
|
36116
|
+
return status >= 400 && status <= 499 ? "rejected" : "unreachable";
|
|
36117
|
+
}
|
|
36118
|
+
}
|
|
36119
|
+
|
|
36120
|
+
// ../../packages/plugin-runtime/src/attached/failure.ts
|
|
36121
|
+
function classifyFailure(err) {
|
|
36122
|
+
switch (classifyRemoteFailure(err)) {
|
|
36123
|
+
case "unauthorized":
|
|
36124
|
+
return "unauthorized";
|
|
36125
|
+
case "forbidden":
|
|
36126
|
+
return "forbidden";
|
|
35905
36127
|
default:
|
|
35906
36128
|
return "unreachable";
|
|
35907
36129
|
}
|
|
@@ -35923,11 +36145,11 @@ function withTimeout(promise2, ms) {
|
|
|
35923
36145
|
}
|
|
35924
36146
|
|
|
35925
36147
|
// ../../packages/plugin-runtime/src/attached/forward-drops.ts
|
|
35926
|
-
import { readFileSync as
|
|
35927
|
-
import { join as
|
|
36148
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
36149
|
+
import { join as join24 } from "path";
|
|
35928
36150
|
var FORWARD_DROPS_FILENAME = ATTACHED_FORWARD_DROPS_FILENAME;
|
|
35929
36151
|
function forwardDropsPath(dataDir2) {
|
|
35930
|
-
return
|
|
36152
|
+
return join24(dataDir2, FORWARD_DROPS_FILENAME);
|
|
35931
36153
|
}
|
|
35932
36154
|
function recordForwardDrops(dataDir2, count, nowMs) {
|
|
35933
36155
|
if (count <= 0) return;
|
|
@@ -35945,7 +36167,7 @@ function recordForwardDrops(dataDir2, count, nowMs) {
|
|
|
35945
36167
|
}
|
|
35946
36168
|
function readForwardDrops(dataDir2) {
|
|
35947
36169
|
try {
|
|
35948
|
-
const parsed2 = JSON.parse(
|
|
36170
|
+
const parsed2 = JSON.parse(readFileSync15(forwardDropsPath(dataDir2), "utf8"));
|
|
35949
36171
|
if (typeof parsed2 !== "object" || parsed2 === null) return null;
|
|
35950
36172
|
const record2 = parsed2;
|
|
35951
36173
|
if (typeof record2.droppedForwards !== "number" || !Number.isFinite(record2.droppedForwards)) {
|
|
@@ -35963,9 +36185,9 @@ function readForwardDrops(dataDir2) {
|
|
|
35963
36185
|
|
|
35964
36186
|
// ../../packages/plugin-runtime/src/attached/forward-policy.ts
|
|
35965
36187
|
import { randomUUID as randomUUID17 } from "crypto";
|
|
35966
|
-
import { readFileSync as
|
|
36188
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
35967
36189
|
import { readFile, rename, writeFile } from "fs/promises";
|
|
35968
|
-
import { join as
|
|
36190
|
+
import { join as join25 } from "path";
|
|
35969
36191
|
function isInvalidRequest(err) {
|
|
35970
36192
|
return typeof err === "object" && err !== null && err.name === "RemoteRequestInvalid";
|
|
35971
36193
|
}
|
|
@@ -36003,7 +36225,7 @@ function parseBreakerState(raw, nowMs) {
|
|
|
36003
36225
|
}
|
|
36004
36226
|
function createForwardPolicy(deps) {
|
|
36005
36227
|
const now = deps.now ?? (() => Date.now());
|
|
36006
|
-
const file2 =
|
|
36228
|
+
const file2 = join25(deps.dir, STATE_FILENAME);
|
|
36007
36229
|
let state = null;
|
|
36008
36230
|
let loading = null;
|
|
36009
36231
|
async function readState() {
|
|
@@ -36725,8 +36947,8 @@ function toolAuditEvent(input2) {
|
|
|
36725
36947
|
}
|
|
36726
36948
|
|
|
36727
36949
|
// ../../packages/plugin-runtime/src/attached/history-state.ts
|
|
36728
|
-
import { readFileSync as
|
|
36729
|
-
import { join as
|
|
36950
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
36951
|
+
import { join as join26 } from "path";
|
|
36730
36952
|
|
|
36731
36953
|
// ../../packages/plugin-runtime/src/attached/history-sync.ts
|
|
36732
36954
|
import { createHash as createHash6 } from "crypto";
|
|
@@ -36743,7 +36965,7 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
36743
36965
|
var HISTORY_SYNC_THROTTLE_MS = 5 * 60 * 1e3;
|
|
36744
36966
|
|
|
36745
36967
|
// ../../packages/plugin-runtime/src/attached/plugin-block.ts
|
|
36746
|
-
import { readFileSync as
|
|
36968
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
36747
36969
|
function createPluginBlock(build, policyStore) {
|
|
36748
36970
|
return async () => {
|
|
36749
36971
|
const cached2 = await policyStore.read();
|
|
@@ -36762,7 +36984,7 @@ function createPluginBlock(build, policyStore) {
|
|
|
36762
36984
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
36763
36985
|
import { randomUUID as randomUUID18 } from "crypto";
|
|
36764
36986
|
import { readFile as readFile2, rm, writeFile as writeFile2 } from "fs/promises";
|
|
36765
|
-
import { join as
|
|
36987
|
+
import { join as join27 } from "path";
|
|
36766
36988
|
|
|
36767
36989
|
// ../../packages/plugin-runtime/src/attached/atomic-publish.ts
|
|
36768
36990
|
import { rename as rename2 } from "fs/promises";
|
|
@@ -36786,7 +37008,7 @@ async function publishByRename(tmp, file2, move = rename2) {
|
|
|
36786
37008
|
|
|
36787
37009
|
// ../../packages/plugin-runtime/src/attached/policy-store.ts
|
|
36788
37010
|
function createPolicyStore(dir = dataDir()) {
|
|
36789
|
-
const file2 =
|
|
37011
|
+
const file2 = join27(dir, "policy-cache.json");
|
|
36790
37012
|
async function read() {
|
|
36791
37013
|
try {
|
|
36792
37014
|
const raw = await readFile2(file2, "utf8");
|
|
@@ -37017,11 +37239,11 @@ function readStorePosture(dbPath2) {
|
|
|
37017
37239
|
// ../../packages/plugin-runtime/src/attached/posture-store.ts
|
|
37018
37240
|
import { randomUUID as randomUUID19 } from "crypto";
|
|
37019
37241
|
import { readFile as readFile3, rm as rm2, writeFile as writeFile3 } from "fs/promises";
|
|
37020
|
-
import { join as
|
|
37242
|
+
import { join as join28 } from "path";
|
|
37021
37243
|
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
37022
37244
|
function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
37023
|
-
const file2 =
|
|
37024
|
-
const legacyFile = legacyDir === void 0 ? null :
|
|
37245
|
+
const file2 = join28(dir, "posture-state.json");
|
|
37246
|
+
const legacyFile = legacyDir === void 0 ? null : join28(legacyDir, "posture-state.json");
|
|
37025
37247
|
async function persist(state) {
|
|
37026
37248
|
await ensureDataDir(dir);
|
|
37027
37249
|
const tmp = `${file2}.${randomUUID19()}.tmp`;
|
|
@@ -37089,8 +37311,8 @@ function createPostureStore(dir = settingsDir(), legacyDir) {
|
|
|
37089
37311
|
}
|
|
37090
37312
|
|
|
37091
37313
|
// ../../packages/plugin-runtime/src/attached/sync-state.ts
|
|
37092
|
-
import { readFileSync as
|
|
37093
|
-
import { join as
|
|
37314
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
37315
|
+
import { join as join29 } from "path";
|
|
37094
37316
|
|
|
37095
37317
|
// ../../packages/plugin-runtime/src/attached/status.ts
|
|
37096
37318
|
var REFUSAL_LINES = {
|
|
@@ -37575,7 +37797,7 @@ function markerDirs(dataDir2) {
|
|
|
37575
37797
|
function alreadyClaimed(dirs, marker, sessionId) {
|
|
37576
37798
|
return dirs.some((dir) => {
|
|
37577
37799
|
try {
|
|
37578
|
-
return
|
|
37800
|
+
return readFileSync20(join30(dir, marker), "utf8") === sessionId;
|
|
37579
37801
|
} catch {
|
|
37580
37802
|
return false;
|
|
37581
37803
|
}
|
|
@@ -37585,7 +37807,7 @@ function recordClaim(dirs, marker, sessionId) {
|
|
|
37585
37807
|
for (const dir of dirs) {
|
|
37586
37808
|
try {
|
|
37587
37809
|
mkdirSync5(dir, { recursive: true, mode: DATA_DIR_MODE });
|
|
37588
|
-
writeFileSync8(
|
|
37810
|
+
writeFileSync8(join30(dir, marker), sessionId, { mode: DATA_FILE_MODE });
|
|
37589
37811
|
return;
|
|
37590
37812
|
} catch {
|
|
37591
37813
|
}
|