@akasecurity/ai-tc-claude-code 0.9.5 → 0.9.7
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 +7 -6
- package/scripts/apply-suppressions.js +1480 -974
- package/scripts/backfill.js +1589 -1023
- package/scripts/dashboard.js +131 -10
- package/scripts/filescan.js +1519 -1021
- package/scripts/firstrun.js +1358 -908
- package/scripts/intro.js +1048 -898
- package/scripts/message-display.js +1453 -980
- package/scripts/onboard.js +1378 -895
- package/scripts/post-tool-use.js +1575 -1009
- package/scripts/pre-tool-use.js +1580 -1014
- package/scripts/query.js +1420 -965
- package/scripts/reconcile.js +1538 -1013
- package/scripts/remediate.js +1585 -1019
- package/scripts/scan-worker.js +1056 -927
- package/scripts/session-start.js +1499 -1014
- package/scripts/start-light.js +1058 -909
- package/scripts/statusline.js +1357 -907
- package/scripts/stop.js +1113 -898
- package/scripts/user-prompt-submit.js +1791 -1207
package/scripts/backfill.js
CHANGED
|
@@ -588,6 +588,10 @@ var SQLITE_MIGRATIONS = [
|
|
|
588
588
|
{
|
|
589
589
|
tag: "0020_secret_vault_pagination_indexes",
|
|
590
590
|
sql: "CREATE INDEX `idx_secret_vault_last_seen` ON `secret_vault` (`last_seen`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_reuse` ON `secret_vault` (`occurrence_count`,`pointer_id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_at` ON `secret_vault_deref` (`at`,`id`);--> statement-breakpoint\nCREATE INDEX `idx_secret_vault_deref_signal` ON `secret_vault_deref` (`at`,`id`) WHERE `reason` NOT IN ('display', 'view-render');"
|
|
591
|
+
},
|
|
592
|
+
{
|
|
593
|
+
tag: "0021_finding_resolution_resolved_at_index",
|
|
594
|
+
sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
|
|
591
595
|
}
|
|
592
596
|
];
|
|
593
597
|
|
|
@@ -15303,6 +15307,50 @@ function date4(params) {
|
|
|
15303
15307
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
15304
15308
|
config(en_default());
|
|
15305
15309
|
|
|
15310
|
+
// ../../packages/schema/src/zod/harness-map.ts
|
|
15311
|
+
var HARNESS = {
|
|
15312
|
+
ClaudeCode: "claudecode",
|
|
15313
|
+
Cursor: "cursor",
|
|
15314
|
+
Copilot: "copilot",
|
|
15315
|
+
Codex: "codex",
|
|
15316
|
+
Antigravity: "antigravity",
|
|
15317
|
+
Windsurf: "windsurf",
|
|
15318
|
+
ClaudeDesktop: "claudedesktop",
|
|
15319
|
+
ChatGpt: "chatgpt",
|
|
15320
|
+
ClaudeAi: "claudeai",
|
|
15321
|
+
Api: "api"
|
|
15322
|
+
};
|
|
15323
|
+
var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
|
|
15324
|
+
var SOURCE_TOOL = {
|
|
15325
|
+
ClaudeCode: "claude-code",
|
|
15326
|
+
ClaudeDesktop: "claude-desktop",
|
|
15327
|
+
Cursor: "cursor",
|
|
15328
|
+
ChatGpt: "chatgpt",
|
|
15329
|
+
ClaudeAi: "claude-ai",
|
|
15330
|
+
Copilot: "github-copilot",
|
|
15331
|
+
Codex: "codex",
|
|
15332
|
+
Antigravity: "antigravity",
|
|
15333
|
+
// No harness counterpart, deliberately: the CLI's own captures and a capture
|
|
15334
|
+
// whose tool could not be identified both render through the read side's
|
|
15335
|
+
// miss path rather than as a harness of their own.
|
|
15336
|
+
Cli: "cli",
|
|
15337
|
+
Unknown: "unknown"
|
|
15338
|
+
};
|
|
15339
|
+
var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
|
|
15340
|
+
var TOOL_TO_HARNESS = {
|
|
15341
|
+
[SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
|
|
15342
|
+
[SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
|
|
15343
|
+
[SOURCE_TOOL.Copilot]: HARNESS.Copilot,
|
|
15344
|
+
[SOURCE_TOOL.Cursor]: HARNESS.Cursor,
|
|
15345
|
+
[SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
|
|
15346
|
+
[SOURCE_TOOL.Codex]: HARNESS.Codex,
|
|
15347
|
+
[SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
|
|
15348
|
+
[SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
|
|
15349
|
+
};
|
|
15350
|
+
function harnessFromTool(tool) {
|
|
15351
|
+
return TOOL_TO_HARNESS[tool] ?? tool;
|
|
15352
|
+
}
|
|
15353
|
+
|
|
15306
15354
|
// ../../packages/schema/src/zod/finding.ts
|
|
15307
15355
|
var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
|
|
15308
15356
|
var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
|
|
@@ -15325,21 +15373,22 @@ var Finding = external_exports.object({
|
|
|
15325
15373
|
}).meta({ id: "Finding" });
|
|
15326
15374
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15327
15375
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15328
|
-
var FindingProvider =
|
|
15329
|
-
"
|
|
15330
|
-
"
|
|
15331
|
-
"
|
|
15332
|
-
"
|
|
15333
|
-
"
|
|
15334
|
-
"
|
|
15335
|
-
"
|
|
15336
|
-
"
|
|
15337
|
-
"
|
|
15376
|
+
var FindingProvider = Harness.extract([
|
|
15377
|
+
"ClaudeCode",
|
|
15378
|
+
"ClaudeDesktop",
|
|
15379
|
+
"Cursor",
|
|
15380
|
+
"Copilot",
|
|
15381
|
+
"ChatGpt",
|
|
15382
|
+
"ClaudeAi",
|
|
15383
|
+
"Codex",
|
|
15384
|
+
"Antigravity",
|
|
15385
|
+
"Api"
|
|
15338
15386
|
]).meta({ id: "FindingProvider" });
|
|
15339
15387
|
var FindingCategory = external_exports.enum([
|
|
15340
15388
|
"secret",
|
|
15341
15389
|
"pii",
|
|
15342
15390
|
"source_code",
|
|
15391
|
+
"code_flaw",
|
|
15343
15392
|
"external_share",
|
|
15344
15393
|
"mcp_server",
|
|
15345
15394
|
"customer_data",
|
|
@@ -15519,6 +15568,7 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15519
15568
|
policy: FindingPolicyRef
|
|
15520
15569
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15521
15570
|
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15571
|
+
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
15522
15572
|
var ListFindingInstancesQuery = external_exports.object({
|
|
15523
15573
|
severity: external_exports.array(Severity).optional(),
|
|
15524
15574
|
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
@@ -15538,7 +15588,7 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
15538
15588
|
q: external_exports.string().optional(),
|
|
15539
15589
|
sessionId: external_exports.string().optional(),
|
|
15540
15590
|
from: external_exports.iso.datetime().optional(),
|
|
15541
|
-
limit: external_exports.coerce.number().int().min(1).max(
|
|
15591
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
|
|
15542
15592
|
cursor: external_exports.string().optional()
|
|
15543
15593
|
});
|
|
15544
15594
|
var ListFindingInstancesResponse = external_exports.object({
|
|
@@ -15600,33 +15650,6 @@ var ListFindingLocationsResponse = external_exports.object({
|
|
|
15600
15650
|
hasMore: external_exports.boolean()
|
|
15601
15651
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15602
15652
|
|
|
15603
|
-
// ../../packages/schema/src/zod/harness-map.ts
|
|
15604
|
-
var Harness = external_exports.enum([
|
|
15605
|
-
"claudecode",
|
|
15606
|
-
"cursor",
|
|
15607
|
-
"copilot",
|
|
15608
|
-
"codex",
|
|
15609
|
-
"antigravity",
|
|
15610
|
-
"windsurf",
|
|
15611
|
-
"claudedesktop",
|
|
15612
|
-
"chatgpt",
|
|
15613
|
-
"claudeai",
|
|
15614
|
-
"api"
|
|
15615
|
-
]).meta({ id: "Harness" });
|
|
15616
|
-
var TOOL_TO_HARNESS = {
|
|
15617
|
-
"claude-code": "claudecode",
|
|
15618
|
-
"claude-desktop": "claudedesktop",
|
|
15619
|
-
"github-copilot": "copilot",
|
|
15620
|
-
cursor: "cursor",
|
|
15621
|
-
chatgpt: "chatgpt",
|
|
15622
|
-
codex: "codex",
|
|
15623
|
-
antigravity: "antigravity",
|
|
15624
|
-
"claude-ai": "claudeai"
|
|
15625
|
-
};
|
|
15626
|
-
function harnessFromTool(tool) {
|
|
15627
|
-
return TOOL_TO_HARNESS[tool] ?? tool;
|
|
15628
|
-
}
|
|
15629
|
-
|
|
15630
15653
|
// ../../packages/schema/src/zod/meta.ts
|
|
15631
15654
|
var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
|
|
15632
15655
|
var AuditEventType = external_exports.enum([
|
|
@@ -16079,686 +16102,37 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
16079
16102
|
sessions: ListActivitySessionsResponse
|
|
16080
16103
|
}).meta({ id: "ActivityOverviewResponse" });
|
|
16081
16104
|
|
|
16082
|
-
// ../../packages/schema/src/zod/
|
|
16083
|
-
var
|
|
16084
|
-
var
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
|
|
16098
|
-
|
|
16099
|
-
|
|
16100
|
-
filePath: external_exports.string().optional(),
|
|
16101
|
-
// The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
|
|
16102
|
-
// set by the tool-scanning hooks. The tool NAME only — never the tool's
|
|
16103
|
-
// arguments or output, which can carry the very value a finding masked
|
|
16104
|
-
// (metadata is stored unredacted). Gives findings on non-file captures a
|
|
16105
|
-
// display location ("via Bash") when no filePath exists.
|
|
16106
|
-
toolName: external_exports.string().optional(),
|
|
16107
|
-
// Set (true) by the worktree scanner when the file is excluded by the
|
|
16108
|
-
// repo's .gitignore. Gitignored files ARE still scanned — local scratch and
|
|
16109
|
-
// generated code can leak real secrets — but the provenance is recorded so
|
|
16110
|
-
// policy/dashboards can treat those findings as informational rather than
|
|
16111
|
-
// blocking. Omitted (not false) for tracked files and non-scan events.
|
|
16112
|
-
gitignored: external_exports.boolean().optional(),
|
|
16113
|
-
// Set (true) ONLY when the event's `content` is the COMPLETE file at
|
|
16114
|
-
// capture time (a worktree scan reading from disk). Hook-captured edits
|
|
16115
|
-
// (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
|
|
16116
|
-
// this. The resolver-on-ingest keys its fixed-at-source dropout
|
|
16117
|
-
// diff on this marker: only a whole-file snapshot can prove a previously
|
|
16118
|
-
// open finding is gone; a fragment's absence proves nothing (the secret
|
|
16119
|
-
// may live outside the hunk). Omitted (not false) for fragments and
|
|
16120
|
-
// non-scan events, so pre-marker clients safely default to the
|
|
16121
|
-
// non-authoritative path.
|
|
16122
|
-
wholeFile: external_exports.boolean().optional(),
|
|
16123
|
-
model: external_exports.string().optional(),
|
|
16124
|
-
turnIndex: external_exports.number().int().nonnegative().optional(),
|
|
16125
|
-
// Distributed-tracing correlation. `correlationId` ties a recorded event back
|
|
16126
|
-
// to the request that captured/ingested it (a UUID, generated independently of
|
|
16127
|
-
// the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
|
|
16128
|
-
// originating span when telemetry is enabled. Both optional + backward
|
|
16129
|
-
// compatible — populated by the plugin (see @akasecurity/plugin-sdk).
|
|
16130
|
-
correlationId: external_exports.uuid().optional(),
|
|
16131
|
-
traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
|
|
16132
|
-
// Ids of the detection exceptions that downgraded findings in this capture
|
|
16133
|
-
// to 'allow' — the enforcement audit trail's link back to the grant that
|
|
16134
|
-
// authorized the bypass. Absent on captures where no exception applied.
|
|
16135
|
-
exceptionIds: external_exports.array(external_exports.guid()).optional()
|
|
16136
|
-
}).meta({ id: "EventMetadata" });
|
|
16137
|
-
var Event = external_exports.object({
|
|
16138
|
-
id: external_exports.guid(),
|
|
16139
|
-
sourceTool: SourceTool,
|
|
16140
|
-
kind: EventKind,
|
|
16141
|
-
occurredAt: external_exports.iso.datetime(),
|
|
16142
|
-
contentHash: external_exports.string(),
|
|
16143
|
-
content: external_exports.string(),
|
|
16144
|
-
metadata: EventMetadata.optional()
|
|
16145
|
-
}).meta({ id: "Event" });
|
|
16146
|
-
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
16147
|
-
var IngestBatch = external_exports.object({
|
|
16148
|
-
events: external_exports.array(IngestEvent).min(1).max(100),
|
|
16149
|
-
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
16150
|
-
// additionally rejects any event whose contentHash the store has already
|
|
16151
|
-
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
16152
|
-
// backfill), where a re-run mints fresh event ids for identical content and
|
|
16153
|
-
// would otherwise accumulate duplicates. Live hook traffic must NOT set it:
|
|
16154
|
-
// two genuinely separate prompts can be byte-identical and both belong on
|
|
16155
|
-
// the timeline.
|
|
16156
|
-
dedupe: external_exports.literal("content-hash").optional()
|
|
16157
|
-
}).meta({ id: "IngestBatch" });
|
|
16158
|
-
|
|
16159
|
-
// ../../packages/schema/src/zod/inventory.ts
|
|
16160
|
-
var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
|
|
16161
|
-
var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
|
|
16162
|
-
var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
|
|
16163
|
-
var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
|
|
16164
|
-
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16165
|
-
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16166
|
-
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16167
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16168
|
-
var AccessCounts = external_exports.object({
|
|
16169
|
-
open: external_exports.number().int().nonnegative(),
|
|
16170
|
-
approved: external_exports.number().int().nonnegative(),
|
|
16171
|
-
blocked: external_exports.number().int().nonnegative(),
|
|
16172
|
-
total: external_exports.number().int().nonnegative()
|
|
16173
|
-
}).meta({ id: "AccessCounts" });
|
|
16174
|
-
var AssetSummary = external_exports.object({
|
|
16175
|
-
id: external_exports.string(),
|
|
16176
|
-
type: AssetType,
|
|
16177
|
-
name: external_exports.string(),
|
|
16178
|
-
sub: external_exports.string(),
|
|
16179
|
-
flags: external_exports.array(Flag),
|
|
16180
|
-
/** MCP servers only — omitted for all other types. */
|
|
16181
|
-
trust: TrustLevel.optional()
|
|
16182
|
-
}).meta({ id: "AssetSummary" });
|
|
16183
|
-
var ProjectSummary = external_exports.object({
|
|
16184
|
-
id: external_exports.string(),
|
|
16185
|
-
name: external_exports.string(),
|
|
16186
|
-
repo: external_exports.string(),
|
|
16187
|
-
visibility: Visibility,
|
|
16188
|
-
language: external_exports.string(),
|
|
16189
|
-
policyDefault: AccessLevel,
|
|
16190
|
-
updatedAt: external_exports.iso.datetime(),
|
|
16191
|
-
accessCounts: AccessCounts,
|
|
16192
|
-
findingsCount: external_exports.number().int().nonnegative()
|
|
16193
|
-
}).meta({ id: "ProjectSummary" });
|
|
16194
|
-
var HarnessCategory = external_exports.object({
|
|
16195
|
-
/** One of config/skill/mcp/hook — never project (enforced at service layer). */
|
|
16196
|
-
type: AssetType,
|
|
16197
|
-
assets: external_exports.array(AssetSummary)
|
|
16105
|
+
// ../../packages/schema/src/zod/config-inventory.ts
|
|
16106
|
+
var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
|
|
16107
|
+
var SkillScanEntry = external_exports.object({
|
|
16108
|
+
name: external_exports.string().min(1),
|
|
16109
|
+
// The identity source: a marketplace repo for plugin skills (e.g.
|
|
16110
|
+
// 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
|
|
16111
|
+
// 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
|
|
16112
|
+
// a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
|
|
16113
|
+
source: external_exports.string().min(1),
|
|
16114
|
+
scope: ConfigScope,
|
|
16115
|
+
pluginName: external_exports.string().optional(),
|
|
16116
|
+
// Volatile — rides the attribute bag, never the identity hash.
|
|
16117
|
+
version: external_exports.string().optional(),
|
|
16118
|
+
description: external_exports.string().optional(),
|
|
16119
|
+
// Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
|
|
16120
|
+
updatedAt: external_exports.iso.datetime().optional(),
|
|
16121
|
+
// Filesystem path — the promoted inventory `location` column.
|
|
16122
|
+
location: external_exports.string().optional()
|
|
16198
16123
|
});
|
|
16199
|
-
var
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
|
|
16206
|
-
|
|
16207
|
-
|
|
16208
|
-
|
|
16209
|
-
|
|
16210
|
-
|
|
16211
|
-
var AssetGroup = external_exports.object({
|
|
16212
|
-
/** Group key — never project (enforced at service layer). */
|
|
16213
|
-
type: AssetType,
|
|
16214
|
-
total: external_exports.number().int().nonnegative(),
|
|
16215
|
-
/**
|
|
16216
|
-
* MCP group only — omitted for all other types.
|
|
16217
|
-
* Partial: only TrustLevel keys with non-zero counts are included.
|
|
16218
|
-
* Strict: unknown keys are rejected — only TrustLevel values are valid keys.
|
|
16219
|
-
*/
|
|
16220
|
-
trustRollup: external_exports.object({
|
|
16221
|
-
"known-good": external_exports.number().int().nonnegative(),
|
|
16222
|
-
risky: external_exports.number().int().nonnegative(),
|
|
16223
|
-
unapproved: external_exports.number().int().nonnegative()
|
|
16224
|
-
}).partial().strict().optional(),
|
|
16225
|
-
/**
|
|
16226
|
-
* Partial: only Flag keys with non-zero counts are included.
|
|
16227
|
-
* Strict: unknown keys are rejected — only Flag values are valid keys.
|
|
16228
|
-
*/
|
|
16229
|
-
flagRollup: external_exports.object({
|
|
16230
|
-
update: external_exports.number().int().nonnegative(),
|
|
16231
|
-
stale: external_exports.number().int().nonnegative(),
|
|
16232
|
-
conflict: external_exports.number().int().nonnegative(),
|
|
16233
|
-
unknown: external_exports.number().int().nonnegative(),
|
|
16234
|
-
change: external_exports.number().int().nonnegative(),
|
|
16235
|
-
untracked: external_exports.number().int().nonnegative(),
|
|
16236
|
-
risk: external_exports.number().int().nonnegative(),
|
|
16237
|
-
findings: external_exports.number().int().nonnegative()
|
|
16238
|
-
}).partial().strict(),
|
|
16239
|
-
items: external_exports.array(AssetSummary)
|
|
16240
|
-
}).meta({ id: "AssetGroup" });
|
|
16241
|
-
var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
|
|
16242
|
-
var McpTool = external_exports.object({
|
|
16243
|
-
name: external_exports.string(),
|
|
16244
|
-
signature: external_exports.string(),
|
|
16245
|
-
description: external_exports.string(),
|
|
16246
|
-
write: external_exports.boolean(),
|
|
16247
|
-
/** Non-null string when tool is dangerous / blocked; null otherwise. */
|
|
16248
|
-
risk: external_exports.string().nullable()
|
|
16249
|
-
}).meta({ id: "McpTool" });
|
|
16250
|
-
var AssetFindingRef = external_exports.object({
|
|
16251
|
-
id: external_exports.string(),
|
|
16252
|
-
title: external_exports.string(),
|
|
16253
|
-
note: external_exports.string()
|
|
16254
|
-
});
|
|
16255
|
-
var AssetDetail = AssetSummary.extend({
|
|
16256
|
-
/** string | null — null when no description is available. */
|
|
16257
|
-
description: external_exports.string().nullable(),
|
|
16258
|
-
/** trustLevel | null — null for non-MCP assets. */
|
|
16259
|
-
trust: TrustLevel.nullable(),
|
|
16260
|
-
/** Type-specific raw key/values — FE renders the grid. */
|
|
16261
|
-
meta: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
16262
|
-
/** always present — object when there is an active finding, null when absent. */
|
|
16263
|
-
finding: AssetFindingRef.nullable(),
|
|
16264
|
-
/** MCP exposed-tools list — omitted for non-mcp. */
|
|
16265
|
-
tools: external_exports.array(McpTool).optional()
|
|
16266
|
-
}).meta({ id: "AssetDetail" });
|
|
16267
|
-
var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
|
|
16268
|
-
var InventoryStats = external_exports.object({
|
|
16269
|
-
/** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
|
|
16270
|
-
attention: external_exports.number().int().nonnegative(),
|
|
16271
|
-
byType: external_exports.object({
|
|
16272
|
-
project: external_exports.number().int().nonnegative(),
|
|
16273
|
-
skill: external_exports.number().int().nonnegative(),
|
|
16274
|
-
mcp: external_exports.number().int().nonnegative(),
|
|
16275
|
-
hook: external_exports.number().int().nonnegative(),
|
|
16276
|
-
config: external_exports.number().int().nonnegative()
|
|
16277
|
-
}),
|
|
16278
|
-
harnesses: external_exports.number().int().nonnegative(),
|
|
16279
|
-
mcpTrust: external_exports.object({
|
|
16280
|
-
"known-good": external_exports.number().int().nonnegative(),
|
|
16281
|
-
risky: external_exports.number().int().nonnegative(),
|
|
16282
|
-
unapproved: external_exports.number().int().nonnegative()
|
|
16283
|
-
})
|
|
16284
|
-
}).meta({ id: "InventoryStats" });
|
|
16285
|
-
var FileSummary = external_exports.object({
|
|
16286
|
-
path: external_exports.string(),
|
|
16287
|
-
name: external_exports.string(),
|
|
16288
|
-
origin: Origin,
|
|
16289
|
-
/** Effective access (override applied). */
|
|
16290
|
-
access: AccessLevel,
|
|
16291
|
-
/** True when a file_access_override differs from the computed default. */
|
|
16292
|
-
isCustom: external_exports.boolean(),
|
|
16293
|
-
findings: external_exports.number().int().nonnegative(),
|
|
16294
|
-
/** When the file was auto-blocked by a detection; null when not blocked. */
|
|
16295
|
-
blockedAt: external_exports.iso.datetime().nullable().optional(),
|
|
16296
|
-
/** Why the file was blocked; null when absent. */
|
|
16297
|
-
note: external_exports.string().nullable().optional()
|
|
16298
|
-
}).meta({ id: "FileSummary" });
|
|
16299
|
-
var FolderSummary = external_exports.object({
|
|
16300
|
-
name: external_exports.string(),
|
|
16301
|
-
path: external_exports.string(),
|
|
16302
|
-
/** Rollup of effective access across all descendants. */
|
|
16303
|
-
accessCounts: AccessCounts
|
|
16304
|
-
}).meta({ id: "FolderSummary" });
|
|
16305
|
-
var ProjectTreeResponse = external_exports.object({
|
|
16306
|
-
project: external_exports.object({
|
|
16307
|
-
id: external_exports.string(),
|
|
16308
|
-
repo: external_exports.string(),
|
|
16309
|
-
visibility: Visibility
|
|
16310
|
-
}),
|
|
16311
|
-
path: external_exports.string(),
|
|
16312
|
-
/** Browse mode: one-level folders at the current path. Omitted in search mode. */
|
|
16313
|
-
folders: external_exports.array(FolderSummary).optional(),
|
|
16314
|
-
files: external_exports.array(FileSummary)
|
|
16315
|
-
}).meta({ id: "ProjectTreeResponse" });
|
|
16316
|
-
var FileDetail = FileSummary.extend({
|
|
16317
|
-
project: external_exports.object({
|
|
16318
|
-
repo: external_exports.string(),
|
|
16319
|
-
visibility: Visibility,
|
|
16320
|
-
language: external_exports.string(),
|
|
16321
|
-
policyDefault: AccessLevel,
|
|
16322
|
-
updatedAt: external_exports.iso.datetime()
|
|
16323
|
-
}),
|
|
16324
|
-
findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
|
|
16325
|
-
}).meta({ id: "FileDetail" });
|
|
16326
|
-
var SetFileAccessBody = external_exports.object({
|
|
16327
|
-
path: external_exports.string(),
|
|
16328
|
-
access: AccessLevel
|
|
16329
|
-
}).meta({ id: "SetFileAccessBody" });
|
|
16330
|
-
var SetFileAccessResponse = external_exports.object({
|
|
16331
|
-
file: FileSummary,
|
|
16332
|
-
accessCounts: AccessCounts
|
|
16333
|
-
}).meta({ id: "SetFileAccessResponse" });
|
|
16334
|
-
var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
|
|
16335
|
-
var HarnessEventItem = external_exports.object({
|
|
16336
|
-
kind: HarnessEventKind,
|
|
16337
|
-
title: external_exports.string(),
|
|
16338
|
-
detail: external_exports.string(),
|
|
16339
|
-
occurredAt: external_exports.iso.datetime(),
|
|
16340
|
-
findingId: external_exports.string().nullable().optional()
|
|
16341
|
-
}).meta({ id: "HarnessEventItem" });
|
|
16342
|
-
var HarnessEventsResponse = external_exports.object({
|
|
16343
|
-
counts: external_exports.object({
|
|
16344
|
-
block: external_exports.number().int().nonnegative(),
|
|
16345
|
-
redact: external_exports.number().int().nonnegative(),
|
|
16346
|
-
warn: external_exports.number().int().nonnegative()
|
|
16347
|
-
}),
|
|
16348
|
-
items: external_exports.array(HarnessEventItem)
|
|
16349
|
-
}).meta({ id: "HarnessEventsResponse" });
|
|
16350
|
-
var RescanResponse = external_exports.object({
|
|
16351
|
-
jobId: external_exports.string(),
|
|
16352
|
-
startedAt: external_exports.iso.datetime()
|
|
16353
|
-
}).meta({ id: "RescanResponse" });
|
|
16354
|
-
var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
|
|
16355
|
-
var ListAssetsQuery = external_exports.object({
|
|
16356
|
-
/** Filter by one or more AssetType values; absent means all types. */
|
|
16357
|
-
type: external_exports.array(AssetType).optional(),
|
|
16358
|
-
/** Free-text search term. */
|
|
16359
|
-
q: external_exports.string().optional()
|
|
16360
|
-
});
|
|
16361
|
-
var GetProjectTreeQuery = external_exports.object({
|
|
16362
|
-
/** Subtree root path; defaults to repository root when absent. */
|
|
16363
|
-
path: external_exports.string().optional(),
|
|
16364
|
-
/** Free-text filter applied to file paths. */
|
|
16365
|
-
q: external_exports.string().optional(),
|
|
16366
|
-
/**
|
|
16367
|
-
* Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
|
|
16368
|
-
* file across the whole repo (folders omitted, most-recent first), ignoring
|
|
16369
|
-
* `path`/`q` — powers the project-wide "recently blocked" strip.
|
|
16370
|
-
*/
|
|
16371
|
-
filter: external_exports.enum(["blocked"]).optional()
|
|
16372
|
-
});
|
|
16373
|
-
var GetProjectFileQuery = external_exports.object({
|
|
16374
|
-
/** Repository-relative file path; absent or empty → 400. */
|
|
16375
|
-
path: external_exports.string()
|
|
16376
|
-
});
|
|
16377
|
-
var GetHarnessEventsQuery = external_exports.object({
|
|
16378
|
-
/** Maximum number of events to return. Range: 1–50; default: 7. */
|
|
16379
|
-
limit: external_exports.coerce.number().int().min(1).max(50).default(7)
|
|
16380
|
-
});
|
|
16381
|
-
|
|
16382
|
-
// ../../packages/schema/src/zod/exception.ts
|
|
16383
|
-
var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
|
|
16384
|
-
var ExceptionConditions = external_exports.object({
|
|
16385
|
-
repo: external_exports.string().optional(),
|
|
16386
|
-
sourceTool: external_exports.string().optional(),
|
|
16387
|
-
provider: external_exports.string().optional()
|
|
16388
|
-
}).strict();
|
|
16389
|
-
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16390
|
-
var DetectionException = external_exports.object({
|
|
16391
|
-
id: external_exports.guid(),
|
|
16392
|
-
ruleId: external_exports.string(),
|
|
16393
|
-
// Denormalized from the rule, for reporting — never matched on.
|
|
16394
|
-
category: DetectionCategory,
|
|
16395
|
-
// HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
|
|
16396
|
-
// fingerprint, never the raw value, and never reversible. Matching recomputes
|
|
16397
|
-
// the fingerprint from a fresh capture; the value itself is never stored.
|
|
16398
|
-
// Shape-constrained so a malformed — or accidentally raw — value is rejected
|
|
16399
|
-
// at the boundary rather than persisted.
|
|
16400
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
16401
|
-
// Version of the fingerprint key the grant was written under; a rotated key
|
|
16402
|
-
// invalidates old grants rather than silently mismatching them.
|
|
16403
|
-
keyVersion: external_exports.number().int().positive(),
|
|
16404
|
-
// maskMatch() preview of the approved value — never the raw value.
|
|
16405
|
-
maskedValue: external_exports.string(),
|
|
16406
|
-
capability: ExceptionCapability.default("suppress"),
|
|
16407
|
-
scope: ExceptionScope,
|
|
16408
|
-
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16409
|
-
maxUses: external_exports.number().int().positive().nullable(),
|
|
16410
|
-
useCount: external_exports.number().int().nonnegative(),
|
|
16411
|
-
lastUsedAt: external_exports.iso.datetime().nullable(),
|
|
16412
|
-
// Mandatory: every grant carries the human reason it exists.
|
|
16413
|
-
justification: external_exports.string().min(1),
|
|
16414
|
-
conditions: ExceptionConditions.nullable(),
|
|
16415
|
-
createdBy: external_exports.string(),
|
|
16416
|
-
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
|
|
16417
|
-
createdAt: external_exports.iso.datetime(),
|
|
16418
|
-
updatedAt: external_exports.iso.datetime(),
|
|
16419
|
-
// Revocation is terminal and retained — consumed/expired/revoked rows are
|
|
16420
|
-
// audit evidence; nothing in the exception lifecycle hard-deletes.
|
|
16421
|
-
revokedAt: external_exports.iso.datetime().nullable(),
|
|
16422
|
-
revokedBy: external_exports.string().nullable(),
|
|
16423
|
-
revokeReason: external_exports.string().nullable()
|
|
16424
|
-
});
|
|
16425
|
-
var ExceptionBundleEntry = DetectionException.pick({
|
|
16426
|
-
id: true,
|
|
16427
|
-
ruleId: true,
|
|
16428
|
-
valueFingerprint: true,
|
|
16429
|
-
keyVersion: true,
|
|
16430
|
-
capability: true,
|
|
16431
|
-
expiresAt: true,
|
|
16432
|
-
maxUses: true,
|
|
16433
|
-
useCount: true,
|
|
16434
|
-
conditions: true
|
|
16435
|
-
});
|
|
16436
|
-
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16437
|
-
|
|
16438
|
-
// ../../packages/schema/src/zod/rule.ts
|
|
16439
|
-
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
16440
|
-
var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
|
|
16441
|
-
var KeywordMatcher = external_exports.object({
|
|
16442
|
-
type: external_exports.literal("keyword"),
|
|
16443
|
-
// An empty keyword matches at every position, yielding one zero-length span
|
|
16444
|
-
// per character. Rejected here because a keyword that matches everything is
|
|
16445
|
-
// never intentional.
|
|
16446
|
-
keywords: external_exports.array(external_exports.string().min(1)).min(1),
|
|
16447
|
-
caseSensitive: external_exports.boolean().default(false)
|
|
16448
|
-
});
|
|
16449
|
-
function isValidRegex(pattern, flags) {
|
|
16450
|
-
try {
|
|
16451
|
-
new RegExp(pattern, flags);
|
|
16452
|
-
return true;
|
|
16453
|
-
} catch {
|
|
16454
|
-
return false;
|
|
16455
|
-
}
|
|
16456
|
-
}
|
|
16457
|
-
function matchesEmptyString(pattern, flags) {
|
|
16458
|
-
try {
|
|
16459
|
-
const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
|
|
16460
|
-
return re.exec("")?.[0].length === 0;
|
|
16461
|
-
} catch {
|
|
16462
|
-
return false;
|
|
16463
|
-
}
|
|
16464
|
-
}
|
|
16465
|
-
var MAX_PATTERN_LENGTH = 2e3;
|
|
16466
|
-
var RegexMatcher = external_exports.object({
|
|
16467
|
-
type: external_exports.literal("regex"),
|
|
16468
|
-
pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
16469
|
-
flags: external_exports.string().default("gi"),
|
|
16470
|
-
captureGroup: external_exports.number().int().nonnegative().optional()
|
|
16471
|
-
}).refine((v) => isValidRegex(v.pattern, v.flags), {
|
|
16472
|
-
message: "pattern/flags do not form a valid JavaScript regular expression",
|
|
16473
|
-
path: ["pattern"]
|
|
16474
|
-
}).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
|
|
16475
|
-
message: 'a whole-match regex that can match the empty string (e.g. "\\d*", "a?", "(?:)") can hang the matcher \u2014 scope the quantifier to a captureGroup, or require at least one character',
|
|
16476
|
-
path: ["pattern"]
|
|
16477
|
-
});
|
|
16478
|
-
var ValidatorMatcher = external_exports.object({
|
|
16479
|
-
type: external_exports.literal("validator"),
|
|
16480
|
-
name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
|
|
16481
|
-
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16482
|
-
});
|
|
16483
|
-
var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
|
|
16484
|
-
var AppliesTo = external_exports.object({
|
|
16485
|
-
// Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
|
|
16486
|
-
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
|
|
16487
|
-
}).meta({ id: "AppliesTo" });
|
|
16488
|
-
var PostValidatorRef = external_exports.union([
|
|
16489
|
-
external_exports.string(),
|
|
16490
|
-
external_exports.object({
|
|
16491
|
-
name: external_exports.string(),
|
|
16492
|
-
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16493
|
-
})
|
|
16494
|
-
]).meta({ id: "PostValidatorRef" });
|
|
16495
|
-
var RequiresNearby = external_exports.object({
|
|
16496
|
-
// Each array, when present, must be non-empty and contain non-empty strings —
|
|
16497
|
-
// an empty/blank criterion would either never fire or (for labels) match
|
|
16498
|
-
// everything.
|
|
16499
|
-
categories: external_exports.array(DetectionCategory).min(1).optional(),
|
|
16500
|
-
ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16501
|
-
labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16502
|
-
windowChars: external_exports.number().int().positive().default(160),
|
|
16503
|
-
// Optional confidence bump applied when a gated match is corroborated. Capped
|
|
16504
|
-
// small: it nudges confidence, it does not assert certainty.
|
|
16505
|
-
confidenceBoost: external_exports.number().min(0).max(0.3).optional()
|
|
16506
|
-
}).refine(
|
|
16507
|
-
(v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
|
|
16508
|
-
{ message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
|
|
16509
|
-
).meta({ id: "RequiresNearby" });
|
|
16510
|
-
var RuleFixture = external_exports.object({
|
|
16511
|
-
label: external_exports.string(),
|
|
16512
|
-
text: external_exports.string().max(5e4),
|
|
16513
|
-
shouldMatch: external_exports.boolean(),
|
|
16514
|
-
// Simulated file context for the scan, so fixtures can assert `appliesTo`
|
|
16515
|
-
// gating (e.g. a Python-only pattern must NOT fire in a .ts file).
|
|
16516
|
-
filePath: external_exports.string().optional(),
|
|
16517
|
-
expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
|
|
16518
|
-
}).meta({ id: "RuleFixture" });
|
|
16519
|
-
var Rule = external_exports.object({
|
|
16520
|
-
specVersion: external_exports.literal(1),
|
|
16521
|
-
// `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
|
|
16522
|
-
// the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
|
|
16523
|
-
// detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
|
|
16524
|
-
// therefore carries no namespace and is not globally unique across publishers;
|
|
16525
|
-
// never feed one to splitDetectionId. `category` below (per-rule) is the
|
|
16526
|
-
// taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
|
|
16527
|
-
id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
|
|
16528
|
-
name: external_exports.string(),
|
|
16529
|
-
category: DetectionCategory,
|
|
16530
|
-
severity: Severity,
|
|
16531
|
-
matcher: Matcher,
|
|
16532
|
-
appliesTo: AppliesTo.optional(),
|
|
16533
|
-
postValidators: external_exports.array(PostValidatorRef).optional(),
|
|
16534
|
-
requiresNearby: RequiresNearby.optional(),
|
|
16535
|
-
examples: external_exports.array(external_exports.string()).optional()
|
|
16536
|
-
}).meta({ id: "Rule" });
|
|
16537
|
-
var Author = external_exports.object({
|
|
16538
|
-
name: external_exports.string(),
|
|
16539
|
-
email: external_exports.email().optional(),
|
|
16540
|
-
url: external_exports.url().optional()
|
|
16541
|
-
}).meta({ id: "Author" });
|
|
16542
|
-
var PackManifest = external_exports.object({
|
|
16543
|
-
specVersion: external_exports.literal(1),
|
|
16544
|
-
id: external_exports.string(),
|
|
16545
|
-
name: external_exports.string(),
|
|
16546
|
-
version: external_exports.string(),
|
|
16547
|
-
rules: external_exports.array(external_exports.string()),
|
|
16548
|
-
// Optional attribution/provenance — consumed by the rule marketplace.
|
|
16549
|
-
description: external_exports.string().optional(),
|
|
16550
|
-
author: Author.optional(),
|
|
16551
|
-
license: external_exports.string().optional(),
|
|
16552
|
-
sourceUrl: external_exports.url().optional()
|
|
16553
|
-
}).meta({ id: "PackManifest" });
|
|
16554
|
-
|
|
16555
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
16556
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
16557
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
16558
|
-
var Policy = external_exports.object({
|
|
16559
|
-
id: external_exports.guid(),
|
|
16560
|
-
scope: PolicyScope,
|
|
16561
|
-
target: PolicyTarget,
|
|
16562
|
-
action: ActionTaken,
|
|
16563
|
-
enabled: external_exports.boolean().default(true),
|
|
16564
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
16565
|
-
// Display name — optional so older policy rows without name still parse.
|
|
16566
|
-
// Added for the findings API (policy.name column migration).
|
|
16567
|
-
name: external_exports.string().optional()
|
|
16568
|
-
}).meta({ id: "Policy" });
|
|
16569
|
-
var PolicyBundle = external_exports.object({
|
|
16570
|
-
version: external_exports.string(),
|
|
16571
|
-
policies: external_exports.array(Policy),
|
|
16572
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
16573
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
16574
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
16575
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
16576
|
-
rules: external_exports.array(Rule).optional(),
|
|
16577
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
16578
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
16579
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
16580
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
16581
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
16582
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
16583
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
16584
|
-
// Active detection exceptions, evaluation subset only (see
|
|
16585
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
16586
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
16587
|
-
// `bundle.exceptions ?? []`.
|
|
16588
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
16589
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
16590
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
16591
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
16592
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
16593
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
16594
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
16595
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
16596
|
-
fetchedAt: external_exports.iso.datetime()
|
|
16597
|
-
}).meta({ id: "PolicyBundle" });
|
|
16598
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
16599
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
16600
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
16601
|
-
secret: "critical",
|
|
16602
|
-
financial: "critical",
|
|
16603
|
-
// core-financial/credit-card
|
|
16604
|
-
code_flaw: "critical",
|
|
16605
|
-
pii: "high",
|
|
16606
|
-
phi: "high",
|
|
16607
|
-
custom: "high",
|
|
16608
|
-
// user-defined; conservative
|
|
16609
|
-
code_context: "low",
|
|
16610
|
-
config: "low"
|
|
16611
|
-
// observe-only; floors to monitor regardless
|
|
16612
|
-
};
|
|
16613
|
-
function severityFloorPolicy(category) {
|
|
16614
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
16615
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
16616
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
16617
|
-
}
|
|
16618
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
16619
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
|
|
16620
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
16621
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
16622
|
-
var BUILTIN_POLICY_SPECS = {
|
|
16623
|
-
monitor: {
|
|
16624
|
-
name: "Monitor",
|
|
16625
|
-
action: "log",
|
|
16626
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
16627
|
-
},
|
|
16628
|
-
warn: {
|
|
16629
|
-
name: "Warn",
|
|
16630
|
-
action: "warn",
|
|
16631
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
16632
|
-
},
|
|
16633
|
-
redact: {
|
|
16634
|
-
name: "Redact",
|
|
16635
|
-
action: "redact",
|
|
16636
|
-
description: "Automatically strip the matched value from the request, then continue."
|
|
16637
|
-
},
|
|
16638
|
-
block: {
|
|
16639
|
-
name: "Block",
|
|
16640
|
-
action: "block",
|
|
16641
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
16642
|
-
}
|
|
16643
|
-
};
|
|
16644
|
-
function builtinPolicyToAction(id) {
|
|
16645
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
16646
|
-
}
|
|
16647
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
16648
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
16649
|
-
);
|
|
16650
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
16651
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
16652
|
-
);
|
|
16653
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
16654
|
-
function policyIdToAction(policyId) {
|
|
16655
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
16656
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
16657
|
-
return BUILTIN_POLICIES[id].action;
|
|
16658
|
-
}
|
|
16659
|
-
var UsedByItem = external_exports.object({
|
|
16660
|
-
id: external_exports.string(),
|
|
16661
|
-
name: external_exports.string(),
|
|
16662
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
16663
|
-
enabled: external_exports.boolean()
|
|
16664
|
-
}).meta({ id: "UsedByItem" });
|
|
16665
|
-
var PolicyListItem = external_exports.object({
|
|
16666
|
-
id: external_exports.string(),
|
|
16667
|
-
kind: PolicyKind,
|
|
16668
|
-
name: external_exports.string(),
|
|
16669
|
-
enabled: external_exports.boolean(),
|
|
16670
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
16671
|
-
}).meta({ id: "PolicyListItem" });
|
|
16672
|
-
var PolicyDetail = external_exports.object({
|
|
16673
|
-
specVersion: external_exports.literal(1),
|
|
16674
|
-
id: external_exports.string(),
|
|
16675
|
-
kind: PolicyKind,
|
|
16676
|
-
name: external_exports.string(),
|
|
16677
|
-
enabled: external_exports.boolean(),
|
|
16678
|
-
description: external_exports.string(),
|
|
16679
|
-
usedBy: external_exports.array(UsedByItem)
|
|
16680
|
-
}).meta({ id: "PolicyDetail" });
|
|
16681
|
-
var PolicyStatsResponse = external_exports.object({
|
|
16682
|
-
policies: external_exports.number().int().nonnegative(),
|
|
16683
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
16684
|
-
custom: external_exports.number().int().nonnegative(),
|
|
16685
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
16686
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
16687
|
-
|
|
16688
|
-
// ../../packages/schema/src/zod/api.ts
|
|
16689
|
-
var LIST_QUERY_MAX_LIMIT = 200;
|
|
16690
|
-
var ListEventsQuery = external_exports.object({
|
|
16691
|
-
cursor: external_exports.string().optional(),
|
|
16692
|
-
limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
|
|
16693
|
-
sourceTool: external_exports.string().optional(),
|
|
16694
|
-
kind: external_exports.string().optional(),
|
|
16695
|
-
from: external_exports.iso.datetime().optional(),
|
|
16696
|
-
to: external_exports.iso.datetime().optional()
|
|
16697
|
-
});
|
|
16698
|
-
var ListEventsResponse = external_exports.object({
|
|
16699
|
-
items: external_exports.array(Event),
|
|
16700
|
-
nextCursor: external_exports.string().nullable()
|
|
16701
|
-
}).meta({ id: "ListEventsResponse" });
|
|
16702
|
-
var IngestResponse = external_exports.object({
|
|
16703
|
-
accepted: external_exports.number().int().nonnegative(),
|
|
16704
|
-
duplicates: external_exports.number().int().nonnegative()
|
|
16705
|
-
}).meta({ id: "IngestResponse" });
|
|
16706
|
-
var ListFindingsQuery = external_exports.object({
|
|
16707
|
-
cursor: external_exports.string().optional(),
|
|
16708
|
-
limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
|
|
16709
|
-
severity: external_exports.string().optional(),
|
|
16710
|
-
category: external_exports.string().optional(),
|
|
16711
|
-
eventId: external_exports.guid().optional()
|
|
16712
|
-
});
|
|
16713
|
-
var ListFindingsResponse = external_exports.object({
|
|
16714
|
-
items: external_exports.array(Finding),
|
|
16715
|
-
nextCursor: external_exports.string().nullable()
|
|
16716
|
-
}).meta({ id: "ListFindingsResponse" });
|
|
16717
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
16718
|
-
var CreatePolicyRequest = Policy.omit({ id: true }).meta({
|
|
16719
|
-
id: "CreatePolicyRequest"
|
|
16720
|
-
});
|
|
16721
|
-
var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
|
|
16722
|
-
var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
|
|
16723
|
-
var ErrorResponse = external_exports.object({
|
|
16724
|
-
error: external_exports.object({
|
|
16725
|
-
code: external_exports.string(),
|
|
16726
|
-
message: external_exports.string(),
|
|
16727
|
-
details: external_exports.unknown().optional()
|
|
16728
|
-
})
|
|
16729
|
-
}).meta({ id: "ErrorResponse" });
|
|
16730
|
-
|
|
16731
|
-
// ../../packages/schema/src/zod/config-inventory.ts
|
|
16732
|
-
var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
|
|
16733
|
-
var SkillScanEntry = external_exports.object({
|
|
16734
|
-
name: external_exports.string().min(1),
|
|
16735
|
-
// The identity source: a marketplace repo for plugin skills (e.g.
|
|
16736
|
-
// 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
|
|
16737
|
-
// 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
|
|
16738
|
-
// a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
|
|
16739
|
-
source: external_exports.string().min(1),
|
|
16740
|
-
scope: ConfigScope,
|
|
16741
|
-
pluginName: external_exports.string().optional(),
|
|
16742
|
-
// Volatile — rides the attribute bag, never the identity hash.
|
|
16743
|
-
version: external_exports.string().optional(),
|
|
16744
|
-
description: external_exports.string().optional(),
|
|
16745
|
-
// Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
|
|
16746
|
-
updatedAt: external_exports.iso.datetime().optional(),
|
|
16747
|
-
// Filesystem path — the promoted inventory `location` column.
|
|
16748
|
-
location: external_exports.string().optional()
|
|
16749
|
-
});
|
|
16750
|
-
var HookScanEntry = external_exports.object({
|
|
16751
|
-
// Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
|
|
16752
|
-
// set is harness-defined and grows without a schema change.
|
|
16753
|
-
event: external_exports.string().min(1),
|
|
16754
|
-
// The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
|
|
16755
|
-
matcher: external_exports.string().optional(),
|
|
16756
|
-
command: external_exports.string().min(1),
|
|
16757
|
-
timeout: external_exports.number().optional(),
|
|
16758
|
-
scope: ConfigScope,
|
|
16759
|
-
pluginName: external_exports.string().optional(),
|
|
16760
|
-
// The settings file / hooks.json the entry came from.
|
|
16761
|
-
location: external_exports.string().optional()
|
|
16124
|
+
var HookScanEntry = external_exports.object({
|
|
16125
|
+
// Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
|
|
16126
|
+
// set is harness-defined and grows without a schema change.
|
|
16127
|
+
event: external_exports.string().min(1),
|
|
16128
|
+
// The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
|
|
16129
|
+
matcher: external_exports.string().optional(),
|
|
16130
|
+
command: external_exports.string().min(1),
|
|
16131
|
+
timeout: external_exports.number().optional(),
|
|
16132
|
+
scope: ConfigScope,
|
|
16133
|
+
pluginName: external_exports.string().optional(),
|
|
16134
|
+
// The settings file / hooks.json the entry came from.
|
|
16135
|
+
location: external_exports.string().optional()
|
|
16762
16136
|
});
|
|
16763
16137
|
var McpServerScanEntry = external_exports.object({
|
|
16764
16138
|
// The server's config key ("github", "filesystem", …) — identity, with the
|
|
@@ -16840,6 +16214,164 @@ var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
|
16840
16214
|
var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
|
|
16841
16215
|
var PublisherKind = external_exports.enum(["labs", "user", "org"]);
|
|
16842
16216
|
|
|
16217
|
+
// ../../packages/schema/src/zod/rule.ts
|
|
16218
|
+
var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
|
|
16219
|
+
var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
|
|
16220
|
+
var KeywordMatcher = external_exports.strictObject({
|
|
16221
|
+
type: external_exports.literal("keyword"),
|
|
16222
|
+
// An empty keyword matches at every position, yielding one zero-length span
|
|
16223
|
+
// per character. Rejected here because a keyword that matches everything is
|
|
16224
|
+
// never intentional.
|
|
16225
|
+
keywords: external_exports.array(external_exports.string().min(1)).min(1),
|
|
16226
|
+
caseSensitive: external_exports.boolean().default(false)
|
|
16227
|
+
});
|
|
16228
|
+
function isValidRegex(pattern, flags) {
|
|
16229
|
+
try {
|
|
16230
|
+
new RegExp(pattern, flags);
|
|
16231
|
+
return true;
|
|
16232
|
+
} catch {
|
|
16233
|
+
return false;
|
|
16234
|
+
}
|
|
16235
|
+
}
|
|
16236
|
+
function probeFlags(flags) {
|
|
16237
|
+
return flags.replace(/[gy]/g, "");
|
|
16238
|
+
}
|
|
16239
|
+
function matchesEmptyString(pattern, flags) {
|
|
16240
|
+
try {
|
|
16241
|
+
const re = new RegExp(pattern, probeFlags(flags));
|
|
16242
|
+
return re.exec("")?.[0].length === 0;
|
|
16243
|
+
} catch {
|
|
16244
|
+
return false;
|
|
16245
|
+
}
|
|
16246
|
+
}
|
|
16247
|
+
function spansWholeMatch(captureGroup) {
|
|
16248
|
+
return captureGroup === void 0 || captureGroup === 0;
|
|
16249
|
+
}
|
|
16250
|
+
function captureGroupCount(pattern, flags) {
|
|
16251
|
+
try {
|
|
16252
|
+
const probe = new RegExp(`${pattern}|`, probeFlags(flags));
|
|
16253
|
+
const result = probe.exec("");
|
|
16254
|
+
return result ? result.length - 1 : void 0;
|
|
16255
|
+
} catch {
|
|
16256
|
+
return void 0;
|
|
16257
|
+
}
|
|
16258
|
+
}
|
|
16259
|
+
var MAX_PATTERN_LENGTH = 2e3;
|
|
16260
|
+
var RegexMatcher = external_exports.strictObject({
|
|
16261
|
+
type: external_exports.literal("regex"),
|
|
16262
|
+
pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
16263
|
+
flags: external_exports.string().default("gi"),
|
|
16264
|
+
captureGroup: external_exports.number().int().nonnegative().optional()
|
|
16265
|
+
}).refine((v) => isValidRegex(v.pattern, v.flags), {
|
|
16266
|
+
message: "pattern/flags do not form a valid JavaScript regular expression",
|
|
16267
|
+
path: ["pattern"]
|
|
16268
|
+
}).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
|
|
16269
|
+
message: 'a whole-match regex that can match the empty string (e.g. "\\d*", "a?", "(?:)") can hang the matcher \u2014 scope the quantifier to a captureGroup, or require at least one character',
|
|
16270
|
+
path: ["pattern"]
|
|
16271
|
+
}).superRefine((v, ctx) => {
|
|
16272
|
+
if (v.captureGroup === void 0) return;
|
|
16273
|
+
const groups = captureGroupCount(v.pattern, v.flags);
|
|
16274
|
+
if (groups === void 0 || v.captureGroup <= groups) return;
|
|
16275
|
+
ctx.addIssue({
|
|
16276
|
+
code: "custom",
|
|
16277
|
+
path: ["captureGroup"],
|
|
16278
|
+
message: `captureGroup ${String(v.captureGroup)} is out of range \u2014 the pattern declares ${String(groups)} capture group(s), so valid values are 0-${String(groups)}. An out-of-range group never matches, which would make the rule silently never fire.`
|
|
16279
|
+
});
|
|
16280
|
+
});
|
|
16281
|
+
var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
|
|
16282
|
+
var MATCHER_TYPES = MatcherType.options;
|
|
16283
|
+
var AppliesTo = external_exports.strictObject({
|
|
16284
|
+
// Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
|
|
16285
|
+
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
|
|
16286
|
+
}).meta({ id: "AppliesTo" });
|
|
16287
|
+
var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
|
|
16288
|
+
var PostValidatorRef = external_exports.union(
|
|
16289
|
+
[
|
|
16290
|
+
PostValidatorName,
|
|
16291
|
+
external_exports.strictObject({
|
|
16292
|
+
name: PostValidatorName,
|
|
16293
|
+
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16294
|
+
})
|
|
16295
|
+
],
|
|
16296
|
+
{
|
|
16297
|
+
// A union reports one collapsed issue for every way its arms can fail, so
|
|
16298
|
+
// this has to describe the whole shape rather than just the name — it is
|
|
16299
|
+
// what an author sees for a misspelled name AND for a stray key in the
|
|
16300
|
+
// object form. The names come from the enum so the message cannot go
|
|
16301
|
+
// stale. Without it Zod says only "Invalid input", which is precisely the
|
|
16302
|
+
// no-feedback outcome this schema exists to remove.
|
|
16303
|
+
error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
|
|
16304
|
+
" or "
|
|
16305
|
+
)}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
|
|
16306
|
+
}
|
|
16307
|
+
).meta({ id: "PostValidatorRef" });
|
|
16308
|
+
var RequiresNearby = external_exports.strictObject({
|
|
16309
|
+
// Each array, when present, must be non-empty and contain non-empty strings —
|
|
16310
|
+
// an empty/blank criterion would either never fire or (for labels) match
|
|
16311
|
+
// everything.
|
|
16312
|
+
categories: external_exports.array(DetectionCategory).min(1).optional(),
|
|
16313
|
+
ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16314
|
+
labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16315
|
+
windowChars: external_exports.number().int().positive().default(160),
|
|
16316
|
+
// Optional confidence bump applied when a gated match is corroborated. Capped
|
|
16317
|
+
// small: it nudges confidence, it does not assert certainty.
|
|
16318
|
+
confidenceBoost: external_exports.number().min(0).max(0.3).optional()
|
|
16319
|
+
}).refine(
|
|
16320
|
+
(v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
|
|
16321
|
+
{ message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
|
|
16322
|
+
).meta({ id: "RequiresNearby" });
|
|
16323
|
+
var RuleFixture = external_exports.strictObject({
|
|
16324
|
+
label: external_exports.string(),
|
|
16325
|
+
text: external_exports.string().max(5e4),
|
|
16326
|
+
shouldMatch: external_exports.boolean(),
|
|
16327
|
+
// Simulated file context for the scan, so fixtures can assert `appliesTo`
|
|
16328
|
+
// gating (e.g. a Python-only pattern must NOT fire in a .ts file).
|
|
16329
|
+
filePath: external_exports.string().optional(),
|
|
16330
|
+
expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
|
|
16331
|
+
}).meta({ id: "RuleFixture" });
|
|
16332
|
+
var Rule = external_exports.strictObject({
|
|
16333
|
+
// A pinned literal over a STRICT object, and the two together decide how this
|
|
16334
|
+
// format may grow. A rule carrying a key not listed below is refused with
|
|
16335
|
+
// `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
|
|
16336
|
+
// `invalid_value`. So the only additive path is adding an OPTIONAL field here
|
|
16337
|
+
// — that keeps every rule authored before it valid — and a rule author has no
|
|
16338
|
+
// way to introduce a field of their own or to opt into a later version.
|
|
16339
|
+
// Widening the format means changing this literal and every consumer of it.
|
|
16340
|
+
specVersion: external_exports.literal(1),
|
|
16341
|
+
// `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
|
|
16342
|
+
// the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
|
|
16343
|
+
// detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
|
|
16344
|
+
// therefore carries no namespace and is not globally unique across publishers;
|
|
16345
|
+
// never feed one to splitDetectionId. `category` below (per-rule) is the
|
|
16346
|
+
// taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
|
|
16347
|
+
id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
|
|
16348
|
+
name: external_exports.string(),
|
|
16349
|
+
category: DetectionCategory,
|
|
16350
|
+
severity: Severity,
|
|
16351
|
+
matcher: Matcher,
|
|
16352
|
+
appliesTo: AppliesTo.optional(),
|
|
16353
|
+
postValidators: external_exports.array(PostValidatorRef).optional(),
|
|
16354
|
+
requiresNearby: RequiresNearby.optional(),
|
|
16355
|
+
examples: external_exports.array(external_exports.string()).optional()
|
|
16356
|
+
}).meta({ id: "Rule" });
|
|
16357
|
+
var Author = external_exports.object({
|
|
16358
|
+
name: external_exports.string(),
|
|
16359
|
+
email: external_exports.email().optional(),
|
|
16360
|
+
url: external_exports.url().optional()
|
|
16361
|
+
}).meta({ id: "Author" });
|
|
16362
|
+
var PackManifest = external_exports.object({
|
|
16363
|
+
specVersion: external_exports.literal(1),
|
|
16364
|
+
id: external_exports.string(),
|
|
16365
|
+
name: external_exports.string(),
|
|
16366
|
+
version: external_exports.string(),
|
|
16367
|
+
rules: external_exports.array(external_exports.string()),
|
|
16368
|
+
// Optional attribution/provenance — consumed by the rule marketplace.
|
|
16369
|
+
description: external_exports.string().optional(),
|
|
16370
|
+
author: Author.optional(),
|
|
16371
|
+
license: external_exports.string().optional(),
|
|
16372
|
+
sourceUrl: external_exports.url().optional()
|
|
16373
|
+
}).meta({ id: "PackManifest" });
|
|
16374
|
+
|
|
16843
16375
|
// ../../packages/schema/src/zod/detection.ts
|
|
16844
16376
|
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
16845
16377
|
var DetectionFilterEnum = external_exports.enum(["all", "library", "custom", "customized", "updates"]);
|
|
@@ -17038,6 +16570,231 @@ function buildDetectionsList(summaries, query) {
|
|
|
17038
16570
|
return { counts, items: filtered.map(summaryToDetectionListItem) };
|
|
17039
16571
|
}
|
|
17040
16572
|
|
|
16573
|
+
// ../../packages/schema/src/zod/inventory.ts
|
|
16574
|
+
var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
|
|
16575
|
+
var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
|
|
16576
|
+
var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
|
|
16577
|
+
var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
|
|
16578
|
+
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16579
|
+
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16580
|
+
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16581
|
+
var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
|
|
16582
|
+
id: "HarnessId"
|
|
16583
|
+
});
|
|
16584
|
+
var AccessCounts = external_exports.object({
|
|
16585
|
+
open: external_exports.number().int().nonnegative(),
|
|
16586
|
+
approved: external_exports.number().int().nonnegative(),
|
|
16587
|
+
blocked: external_exports.number().int().nonnegative(),
|
|
16588
|
+
total: external_exports.number().int().nonnegative()
|
|
16589
|
+
}).meta({ id: "AccessCounts" });
|
|
16590
|
+
var AssetSummary = external_exports.object({
|
|
16591
|
+
id: external_exports.string(),
|
|
16592
|
+
type: AssetType,
|
|
16593
|
+
name: external_exports.string(),
|
|
16594
|
+
sub: external_exports.string(),
|
|
16595
|
+
flags: external_exports.array(Flag),
|
|
16596
|
+
/** MCP servers only — omitted for all other types. */
|
|
16597
|
+
trust: TrustLevel.optional()
|
|
16598
|
+
}).meta({ id: "AssetSummary" });
|
|
16599
|
+
var ProjectSummary = external_exports.object({
|
|
16600
|
+
id: external_exports.string(),
|
|
16601
|
+
name: external_exports.string(),
|
|
16602
|
+
repo: external_exports.string(),
|
|
16603
|
+
visibility: Visibility,
|
|
16604
|
+
language: external_exports.string(),
|
|
16605
|
+
policyDefault: AccessLevel,
|
|
16606
|
+
updatedAt: external_exports.iso.datetime(),
|
|
16607
|
+
accessCounts: AccessCounts,
|
|
16608
|
+
findingsCount: external_exports.number().int().nonnegative()
|
|
16609
|
+
}).meta({ id: "ProjectSummary" });
|
|
16610
|
+
var HarnessCategory = external_exports.object({
|
|
16611
|
+
/** One of config/skill/mcp/hook — never project (enforced at service layer). */
|
|
16612
|
+
type: AssetType,
|
|
16613
|
+
assets: external_exports.array(AssetSummary)
|
|
16614
|
+
});
|
|
16615
|
+
var HarnessSummary = external_exports.object({
|
|
16616
|
+
id: HarnessId,
|
|
16617
|
+
label: external_exports.string(),
|
|
16618
|
+
kind: external_exports.string(),
|
|
16619
|
+
version: external_exports.string(),
|
|
16620
|
+
sessions: external_exports.number().int().nonnegative(),
|
|
16621
|
+
assetCount: external_exports.number().int().nonnegative(),
|
|
16622
|
+
flagCount: external_exports.number().int().nonnegative(),
|
|
16623
|
+
projects: external_exports.array(ProjectSummary),
|
|
16624
|
+
categories: external_exports.array(HarnessCategory)
|
|
16625
|
+
}).meta({ id: "HarnessSummary" });
|
|
16626
|
+
var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
|
|
16627
|
+
var AssetGroup = external_exports.object({
|
|
16628
|
+
/** Group key — never project (enforced at service layer). */
|
|
16629
|
+
type: AssetType,
|
|
16630
|
+
total: external_exports.number().int().nonnegative(),
|
|
16631
|
+
/**
|
|
16632
|
+
* MCP group only — omitted for all other types.
|
|
16633
|
+
* Partial: only TrustLevel keys with non-zero counts are included.
|
|
16634
|
+
* Strict: unknown keys are rejected — only TrustLevel values are valid keys.
|
|
16635
|
+
*/
|
|
16636
|
+
trustRollup: external_exports.object({
|
|
16637
|
+
"known-good": external_exports.number().int().nonnegative(),
|
|
16638
|
+
risky: external_exports.number().int().nonnegative(),
|
|
16639
|
+
unapproved: external_exports.number().int().nonnegative()
|
|
16640
|
+
}).partial().strict().optional(),
|
|
16641
|
+
/**
|
|
16642
|
+
* Partial: only Flag keys with non-zero counts are included.
|
|
16643
|
+
* Strict: unknown keys are rejected — only Flag values are valid keys.
|
|
16644
|
+
*/
|
|
16645
|
+
flagRollup: external_exports.object({
|
|
16646
|
+
update: external_exports.number().int().nonnegative(),
|
|
16647
|
+
stale: external_exports.number().int().nonnegative(),
|
|
16648
|
+
conflict: external_exports.number().int().nonnegative(),
|
|
16649
|
+
unknown: external_exports.number().int().nonnegative(),
|
|
16650
|
+
change: external_exports.number().int().nonnegative(),
|
|
16651
|
+
untracked: external_exports.number().int().nonnegative(),
|
|
16652
|
+
risk: external_exports.number().int().nonnegative(),
|
|
16653
|
+
findings: external_exports.number().int().nonnegative()
|
|
16654
|
+
}).partial().strict(),
|
|
16655
|
+
items: external_exports.array(AssetSummary)
|
|
16656
|
+
}).meta({ id: "AssetGroup" });
|
|
16657
|
+
var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
|
|
16658
|
+
var McpTool = external_exports.object({
|
|
16659
|
+
name: external_exports.string(),
|
|
16660
|
+
signature: external_exports.string(),
|
|
16661
|
+
description: external_exports.string(),
|
|
16662
|
+
write: external_exports.boolean(),
|
|
16663
|
+
/** Non-null string when tool is dangerous / blocked; null otherwise. */
|
|
16664
|
+
risk: external_exports.string().nullable()
|
|
16665
|
+
}).meta({ id: "McpTool" });
|
|
16666
|
+
var AssetFindingRef = external_exports.object({
|
|
16667
|
+
id: external_exports.string(),
|
|
16668
|
+
title: external_exports.string(),
|
|
16669
|
+
note: external_exports.string()
|
|
16670
|
+
});
|
|
16671
|
+
var AssetDetail = AssetSummary.extend({
|
|
16672
|
+
/** string | null — null when no description is available. */
|
|
16673
|
+
description: external_exports.string().nullable(),
|
|
16674
|
+
/** trustLevel | null — null for non-MCP assets. */
|
|
16675
|
+
trust: TrustLevel.nullable(),
|
|
16676
|
+
/** Type-specific raw key/values — FE renders the grid. */
|
|
16677
|
+
meta: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
16678
|
+
/** always present — object when there is an active finding, null when absent. */
|
|
16679
|
+
finding: AssetFindingRef.nullable(),
|
|
16680
|
+
/** MCP exposed-tools list — omitted for non-mcp. */
|
|
16681
|
+
tools: external_exports.array(McpTool).optional()
|
|
16682
|
+
}).meta({ id: "AssetDetail" });
|
|
16683
|
+
var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
|
|
16684
|
+
var InventoryStats = external_exports.object({
|
|
16685
|
+
/** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
|
|
16686
|
+
attention: external_exports.number().int().nonnegative(),
|
|
16687
|
+
byType: external_exports.object({
|
|
16688
|
+
project: external_exports.number().int().nonnegative(),
|
|
16689
|
+
skill: external_exports.number().int().nonnegative(),
|
|
16690
|
+
mcp: external_exports.number().int().nonnegative(),
|
|
16691
|
+
hook: external_exports.number().int().nonnegative(),
|
|
16692
|
+
config: external_exports.number().int().nonnegative()
|
|
16693
|
+
}),
|
|
16694
|
+
harnesses: external_exports.number().int().nonnegative(),
|
|
16695
|
+
mcpTrust: external_exports.object({
|
|
16696
|
+
"known-good": external_exports.number().int().nonnegative(),
|
|
16697
|
+
risky: external_exports.number().int().nonnegative(),
|
|
16698
|
+
unapproved: external_exports.number().int().nonnegative()
|
|
16699
|
+
})
|
|
16700
|
+
}).meta({ id: "InventoryStats" });
|
|
16701
|
+
var FileSummary = external_exports.object({
|
|
16702
|
+
path: external_exports.string(),
|
|
16703
|
+
name: external_exports.string(),
|
|
16704
|
+
origin: Origin,
|
|
16705
|
+
/** Effective access (override applied). */
|
|
16706
|
+
access: AccessLevel,
|
|
16707
|
+
/** True when a file_access_override differs from the computed default. */
|
|
16708
|
+
isCustom: external_exports.boolean(),
|
|
16709
|
+
findings: external_exports.number().int().nonnegative(),
|
|
16710
|
+
/** When the file was auto-blocked by a detection; null when not blocked. */
|
|
16711
|
+
blockedAt: external_exports.iso.datetime().nullable().optional(),
|
|
16712
|
+
/** Why the file was blocked; null when absent. */
|
|
16713
|
+
note: external_exports.string().nullable().optional()
|
|
16714
|
+
}).meta({ id: "FileSummary" });
|
|
16715
|
+
var FolderSummary = external_exports.object({
|
|
16716
|
+
name: external_exports.string(),
|
|
16717
|
+
path: external_exports.string(),
|
|
16718
|
+
/** Rollup of effective access across all descendants. */
|
|
16719
|
+
accessCounts: AccessCounts
|
|
16720
|
+
}).meta({ id: "FolderSummary" });
|
|
16721
|
+
var ProjectTreeResponse = external_exports.object({
|
|
16722
|
+
project: external_exports.object({
|
|
16723
|
+
id: external_exports.string(),
|
|
16724
|
+
repo: external_exports.string(),
|
|
16725
|
+
visibility: Visibility
|
|
16726
|
+
}),
|
|
16727
|
+
path: external_exports.string(),
|
|
16728
|
+
/** Browse mode: one-level folders at the current path. Omitted in search mode. */
|
|
16729
|
+
folders: external_exports.array(FolderSummary).optional(),
|
|
16730
|
+
files: external_exports.array(FileSummary)
|
|
16731
|
+
}).meta({ id: "ProjectTreeResponse" });
|
|
16732
|
+
var FileDetail = FileSummary.extend({
|
|
16733
|
+
project: external_exports.object({
|
|
16734
|
+
repo: external_exports.string(),
|
|
16735
|
+
visibility: Visibility,
|
|
16736
|
+
language: external_exports.string(),
|
|
16737
|
+
policyDefault: AccessLevel,
|
|
16738
|
+
updatedAt: external_exports.iso.datetime()
|
|
16739
|
+
}),
|
|
16740
|
+
findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
|
|
16741
|
+
}).meta({ id: "FileDetail" });
|
|
16742
|
+
var SetFileAccessBody = external_exports.object({
|
|
16743
|
+
path: external_exports.string(),
|
|
16744
|
+
access: AccessLevel
|
|
16745
|
+
}).meta({ id: "SetFileAccessBody" });
|
|
16746
|
+
var SetFileAccessResponse = external_exports.object({
|
|
16747
|
+
file: FileSummary,
|
|
16748
|
+
accessCounts: AccessCounts
|
|
16749
|
+
}).meta({ id: "SetFileAccessResponse" });
|
|
16750
|
+
var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
|
|
16751
|
+
var HarnessEventItem = external_exports.object({
|
|
16752
|
+
kind: HarnessEventKind,
|
|
16753
|
+
title: external_exports.string(),
|
|
16754
|
+
detail: external_exports.string(),
|
|
16755
|
+
occurredAt: external_exports.iso.datetime(),
|
|
16756
|
+
findingId: external_exports.string().nullable().optional()
|
|
16757
|
+
}).meta({ id: "HarnessEventItem" });
|
|
16758
|
+
var HarnessEventsResponse = external_exports.object({
|
|
16759
|
+
counts: external_exports.object({
|
|
16760
|
+
block: external_exports.number().int().nonnegative(),
|
|
16761
|
+
redact: external_exports.number().int().nonnegative(),
|
|
16762
|
+
warn: external_exports.number().int().nonnegative()
|
|
16763
|
+
}),
|
|
16764
|
+
items: external_exports.array(HarnessEventItem)
|
|
16765
|
+
}).meta({ id: "HarnessEventsResponse" });
|
|
16766
|
+
var RescanResponse = external_exports.object({
|
|
16767
|
+
jobId: external_exports.string(),
|
|
16768
|
+
startedAt: external_exports.iso.datetime()
|
|
16769
|
+
}).meta({ id: "RescanResponse" });
|
|
16770
|
+
var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
|
|
16771
|
+
var ListAssetsQuery = external_exports.object({
|
|
16772
|
+
/** Filter by one or more AssetType values; absent means all types. */
|
|
16773
|
+
type: external_exports.array(AssetType).optional(),
|
|
16774
|
+
/** Free-text search term. */
|
|
16775
|
+
q: external_exports.string().optional()
|
|
16776
|
+
});
|
|
16777
|
+
var GetProjectTreeQuery = external_exports.object({
|
|
16778
|
+
/** Subtree root path; defaults to repository root when absent. */
|
|
16779
|
+
path: external_exports.string().optional(),
|
|
16780
|
+
/** Free-text filter applied to file paths. */
|
|
16781
|
+
q: external_exports.string().optional(),
|
|
16782
|
+
/**
|
|
16783
|
+
* Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
|
|
16784
|
+
* file across the whole repo (folders omitted, most-recent first), ignoring
|
|
16785
|
+
* `path`/`q` — powers the project-wide "recently blocked" strip.
|
|
16786
|
+
*/
|
|
16787
|
+
filter: external_exports.enum(["blocked"]).optional()
|
|
16788
|
+
});
|
|
16789
|
+
var GetProjectFileQuery = external_exports.object({
|
|
16790
|
+
/** Repository-relative file path; absent or empty → 400. */
|
|
16791
|
+
path: external_exports.string()
|
|
16792
|
+
});
|
|
16793
|
+
var GetHarnessEventsQuery = external_exports.object({
|
|
16794
|
+
/** Maximum number of events to return. Range: 1–50; default: 7. */
|
|
16795
|
+
limit: external_exports.coerce.number().int().min(1).max(50).default(7)
|
|
16796
|
+
});
|
|
16797
|
+
|
|
17041
16798
|
// ../../packages/schema/src/zod/shares.ts
|
|
17042
16799
|
var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
|
|
17043
16800
|
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
|
|
@@ -17244,6 +17001,127 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17244
17001
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17245
17002
|
}).meta({ id: "EgressWriteSummary" });
|
|
17246
17003
|
|
|
17004
|
+
// ../../packages/schema/src/zod/event.ts
|
|
17005
|
+
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
17006
|
+
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
17007
|
+
var EventMetadata = external_exports.object({
|
|
17008
|
+
sessionId: external_exports.string().optional(),
|
|
17009
|
+
repo: external_exports.string().optional(),
|
|
17010
|
+
filePath: external_exports.string().optional(),
|
|
17011
|
+
// The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
|
|
17012
|
+
// set by the tool-scanning hooks. The tool NAME only — never the tool's
|
|
17013
|
+
// arguments or output, which can carry the very value a finding masked
|
|
17014
|
+
// (metadata is stored unredacted). Gives findings on non-file captures a
|
|
17015
|
+
// display location ("via Bash") when no filePath exists.
|
|
17016
|
+
toolName: external_exports.string().optional(),
|
|
17017
|
+
// Set (true) by the worktree scanner when the file is excluded by the
|
|
17018
|
+
// repo's .gitignore. Gitignored files ARE still scanned — local scratch and
|
|
17019
|
+
// generated code can leak real secrets — but the provenance is recorded so
|
|
17020
|
+
// policy/dashboards can treat those findings as informational rather than
|
|
17021
|
+
// blocking. Omitted (not false) for tracked files and non-scan events.
|
|
17022
|
+
gitignored: external_exports.boolean().optional(),
|
|
17023
|
+
// Set (true) ONLY when the event's `content` is the COMPLETE file at
|
|
17024
|
+
// capture time (a worktree scan reading from disk). Hook-captured edits
|
|
17025
|
+
// (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
|
|
17026
|
+
// this. The resolver-on-ingest keys its fixed-at-source dropout
|
|
17027
|
+
// diff on this marker: only a whole-file snapshot can prove a previously
|
|
17028
|
+
// open finding is gone; a fragment's absence proves nothing (the secret
|
|
17029
|
+
// may live outside the hunk). Omitted (not false) for fragments and
|
|
17030
|
+
// non-scan events, so pre-marker clients safely default to the
|
|
17031
|
+
// non-authoritative path.
|
|
17032
|
+
wholeFile: external_exports.boolean().optional(),
|
|
17033
|
+
model: external_exports.string().optional(),
|
|
17034
|
+
turnIndex: external_exports.number().int().nonnegative().optional(),
|
|
17035
|
+
// Distributed-tracing correlation. `correlationId` ties a recorded event back
|
|
17036
|
+
// to the request that captured/ingested it (a UUID, generated independently of
|
|
17037
|
+
// the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
|
|
17038
|
+
// originating span when telemetry is enabled. Both optional + backward
|
|
17039
|
+
// compatible — populated by the plugin (see @akasecurity/plugin-sdk).
|
|
17040
|
+
correlationId: external_exports.uuid().optional(),
|
|
17041
|
+
traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
|
|
17042
|
+
// Ids of the detection exceptions that downgraded findings in this capture
|
|
17043
|
+
// to 'allow' — the enforcement audit trail's link back to the grant that
|
|
17044
|
+
// authorized the bypass. Absent on captures where no exception applied.
|
|
17045
|
+
exceptionIds: external_exports.array(external_exports.guid()).optional()
|
|
17046
|
+
}).meta({ id: "EventMetadata" });
|
|
17047
|
+
var Event = external_exports.object({
|
|
17048
|
+
id: external_exports.guid(),
|
|
17049
|
+
sourceTool: SourceTool,
|
|
17050
|
+
kind: EventKind,
|
|
17051
|
+
occurredAt: external_exports.iso.datetime(),
|
|
17052
|
+
contentHash: external_exports.string(),
|
|
17053
|
+
content: external_exports.string(),
|
|
17054
|
+
metadata: EventMetadata.optional()
|
|
17055
|
+
}).meta({ id: "Event" });
|
|
17056
|
+
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
17057
|
+
var IngestBatch = external_exports.object({
|
|
17058
|
+
events: external_exports.array(IngestEvent).min(1).max(100),
|
|
17059
|
+
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
17060
|
+
// additionally rejects any event whose contentHash the store has already
|
|
17061
|
+
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
17062
|
+
// backfill), where a re-run mints fresh event ids for identical content and
|
|
17063
|
+
// would otherwise accumulate duplicates. Live hook traffic must NOT set it:
|
|
17064
|
+
// two genuinely separate prompts can be byte-identical and both belong on
|
|
17065
|
+
// the timeline.
|
|
17066
|
+
dedupe: external_exports.literal("content-hash").optional()
|
|
17067
|
+
}).meta({ id: "IngestBatch" });
|
|
17068
|
+
|
|
17069
|
+
// ../../packages/schema/src/zod/exception.ts
|
|
17070
|
+
var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
|
|
17071
|
+
var ExceptionConditions = external_exports.object({
|
|
17072
|
+
repo: external_exports.string().optional(),
|
|
17073
|
+
sourceTool: external_exports.string().optional(),
|
|
17074
|
+
provider: external_exports.string().optional()
|
|
17075
|
+
}).strict();
|
|
17076
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
17077
|
+
var DetectionException = external_exports.object({
|
|
17078
|
+
id: external_exports.guid(),
|
|
17079
|
+
ruleId: external_exports.string(),
|
|
17080
|
+
// Denormalized from the rule, for reporting — never matched on.
|
|
17081
|
+
category: DetectionCategory,
|
|
17082
|
+
// HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
|
|
17083
|
+
// fingerprint, never the raw value, and never reversible. Matching recomputes
|
|
17084
|
+
// the fingerprint from a fresh capture; the value itself is never stored.
|
|
17085
|
+
// Shape-constrained so a malformed — or accidentally raw — value is rejected
|
|
17086
|
+
// at the boundary rather than persisted.
|
|
17087
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17088
|
+
// Version of the fingerprint key the grant was written under; a rotated key
|
|
17089
|
+
// invalidates old grants rather than silently mismatching them.
|
|
17090
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17091
|
+
// maskMatch() preview of the approved value — never the raw value.
|
|
17092
|
+
maskedValue: external_exports.string(),
|
|
17093
|
+
capability: ExceptionCapability.default("suppress"),
|
|
17094
|
+
scope: ExceptionScope,
|
|
17095
|
+
expiresAt: external_exports.iso.datetime().nullable(),
|
|
17096
|
+
maxUses: external_exports.number().int().positive().nullable(),
|
|
17097
|
+
useCount: external_exports.number().int().nonnegative(),
|
|
17098
|
+
lastUsedAt: external_exports.iso.datetime().nullable(),
|
|
17099
|
+
// Mandatory: every grant carries the human reason it exists.
|
|
17100
|
+
justification: external_exports.string().min(1),
|
|
17101
|
+
conditions: ExceptionConditions.nullable(),
|
|
17102
|
+
createdBy: external_exports.string(),
|
|
17103
|
+
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
|
|
17104
|
+
createdAt: external_exports.iso.datetime(),
|
|
17105
|
+
updatedAt: external_exports.iso.datetime(),
|
|
17106
|
+
// Revocation is terminal and retained — consumed/expired/revoked rows are
|
|
17107
|
+
// audit evidence; nothing in the exception lifecycle hard-deletes.
|
|
17108
|
+
revokedAt: external_exports.iso.datetime().nullable(),
|
|
17109
|
+
revokedBy: external_exports.string().nullable(),
|
|
17110
|
+
revokeReason: external_exports.string().nullable()
|
|
17111
|
+
});
|
|
17112
|
+
var ExceptionBundleEntry = DetectionException.pick({
|
|
17113
|
+
id: true,
|
|
17114
|
+
ruleId: true,
|
|
17115
|
+
valueFingerprint: true,
|
|
17116
|
+
keyVersion: true,
|
|
17117
|
+
capability: true,
|
|
17118
|
+
expiresAt: true,
|
|
17119
|
+
maxUses: true,
|
|
17120
|
+
useCount: true,
|
|
17121
|
+
conditions: true
|
|
17122
|
+
});
|
|
17123
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
17124
|
+
|
|
17247
17125
|
// ../../packages/schema/src/zod/exception-action.ts
|
|
17248
17126
|
var confirmation = external_exports.string().optional();
|
|
17249
17127
|
var ApproveBlockedInput = external_exports.object({
|
|
@@ -17286,10 +17164,11 @@ function toApiAction(dbVal) {
|
|
|
17286
17164
|
}
|
|
17287
17165
|
function toApiCategory(dbVal) {
|
|
17288
17166
|
if (dbVal === "code_context") return "source_code";
|
|
17289
|
-
|
|
17167
|
+
const parsed = FindingCategory.safeParse(dbVal);
|
|
17168
|
+
return parsed.success ? parsed.data : "custom";
|
|
17290
17169
|
}
|
|
17291
17170
|
function toApiProvider(sourceTool) {
|
|
17292
|
-
return TOOL_TO_HARNESS[sourceTool] ??
|
|
17171
|
+
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
17293
17172
|
}
|
|
17294
17173
|
var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
|
|
17295
17174
|
function foldGroupStatus(instanceStatuses) {
|
|
@@ -17876,7 +17755,14 @@ function isVaultConsentValid(consent) {
|
|
|
17876
17755
|
|
|
17877
17756
|
// ../../packages/schema/src/zod/local.ts
|
|
17878
17757
|
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17879
|
-
var
|
|
17758
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
17759
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
17760
|
+
var ControlPlaneConnection = external_exports.object({
|
|
17761
|
+
endpoint: external_exports.string().min(1),
|
|
17762
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
17763
|
+
label: external_exports.string().min(1).optional(),
|
|
17764
|
+
attachedAt: external_exports.iso.datetime()
|
|
17765
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
17880
17766
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17881
17767
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
17882
17768
|
var ModelJudgeConsent = external_exports.object({
|
|
@@ -17885,12 +17771,10 @@ var ModelJudgeConsent = external_exports.object({
|
|
|
17885
17771
|
});
|
|
17886
17772
|
var WorkspaceSettings = external_exports.object({
|
|
17887
17773
|
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
17888
|
-
|
|
17889
|
-
//
|
|
17890
|
-
runMode:
|
|
17891
|
-
|
|
17892
|
-
RunMode.default("standalone")
|
|
17893
|
-
),
|
|
17774
|
+
runMode: RunMode.default("standalone"),
|
|
17775
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
17776
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
17777
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
17894
17778
|
policy: SimpleDetectionPolicy.default("redact"),
|
|
17895
17779
|
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
17896
17780
|
historicalAccess: HistoricalAccess.default("session-only"),
|
|
@@ -17992,39 +17876,244 @@ function toInspectionFindingRow(input) {
|
|
|
17992
17876
|
firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
|
|
17993
17877
|
};
|
|
17994
17878
|
}
|
|
17995
|
-
function toCaptureAttributes(event) {
|
|
17996
|
-
const metadata = event.metadata;
|
|
17997
|
-
return {
|
|
17998
|
-
source_tool: event.sourceTool,
|
|
17999
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
18000
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
18001
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
18002
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
18003
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
18004
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
18005
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
18006
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
18007
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
18008
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
18009
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
18010
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
18011
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
18012
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
18013
|
-
};
|
|
17879
|
+
function toCaptureAttributes(event) {
|
|
17880
|
+
const metadata = event.metadata;
|
|
17881
|
+
return {
|
|
17882
|
+
source_tool: event.sourceTool,
|
|
17883
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
17884
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
17885
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
17886
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
17887
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
17888
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
17889
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
17890
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
17891
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
17892
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
17893
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
17894
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
17895
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
17896
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
17897
|
+
};
|
|
17898
|
+
}
|
|
17899
|
+
function captureDefinitionVersion(finding) {
|
|
17900
|
+
return `capture/${finding.category}/${finding.severity}`;
|
|
17901
|
+
}
|
|
17902
|
+
function toCaptureDefinitionInput(finding) {
|
|
17903
|
+
return {
|
|
17904
|
+
ruleId: finding.ruleId,
|
|
17905
|
+
version: captureDefinitionVersion(finding),
|
|
17906
|
+
name: finding.ruleId,
|
|
17907
|
+
category: finding.category,
|
|
17908
|
+
severity: finding.severity,
|
|
17909
|
+
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
17910
|
+
};
|
|
17911
|
+
}
|
|
17912
|
+
|
|
17913
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
17914
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
17915
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
17916
|
+
var ManagedSettingKey = external_exports.enum([
|
|
17917
|
+
"runMode",
|
|
17918
|
+
"historicalAccess",
|
|
17919
|
+
"vaultConsent",
|
|
17920
|
+
"vaultKeyCustody",
|
|
17921
|
+
"vaultInlineReveal",
|
|
17922
|
+
"modelJudgeConsent",
|
|
17923
|
+
"dataSharesInPlace"
|
|
17924
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
17925
|
+
var ManagedSettingsValues = external_exports.object({
|
|
17926
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
17927
|
+
controlPlane: external_exports.object({
|
|
17928
|
+
endpoint: external_exports.string().min(1),
|
|
17929
|
+
label: external_exports.string().min(1).optional()
|
|
17930
|
+
}).optional(),
|
|
17931
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
17932
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
17933
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
17934
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
17935
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
17936
|
+
dataSharesInPlace: external_exports.boolean().optional()
|
|
17937
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
17938
|
+
var ManagedSettings = external_exports.object({
|
|
17939
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
17940
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
17941
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
17942
|
+
organization: external_exports.string().min(1).optional(),
|
|
17943
|
+
// What the administrator pinned.
|
|
17944
|
+
values: ManagedSettingsValues.default({}),
|
|
17945
|
+
// Which of those the user may not change. A key here with no matching value
|
|
17946
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
17947
|
+
// the user may still override. The two are separable on purpose.
|
|
17948
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
17949
|
+
}).meta({ id: "ManagedSettings" });
|
|
17950
|
+
|
|
17951
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
17952
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
17953
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
17954
|
+
var Policy = external_exports.object({
|
|
17955
|
+
id: external_exports.guid(),
|
|
17956
|
+
scope: PolicyScope,
|
|
17957
|
+
target: PolicyTarget,
|
|
17958
|
+
action: ActionTaken,
|
|
17959
|
+
enabled: external_exports.boolean().default(true),
|
|
17960
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
17961
|
+
// Display name — optional so older policy rows without name still parse.
|
|
17962
|
+
// Added for the findings API (policy.name column migration).
|
|
17963
|
+
name: external_exports.string().optional()
|
|
17964
|
+
}).meta({ id: "Policy" });
|
|
17965
|
+
var PolicyBundle = external_exports.object({
|
|
17966
|
+
version: external_exports.string(),
|
|
17967
|
+
policies: external_exports.array(Policy),
|
|
17968
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
17969
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
17970
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
17971
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
17972
|
+
rules: external_exports.array(Rule).optional(),
|
|
17973
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
17974
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
17975
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
17976
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
17977
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
17978
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
17979
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
17980
|
+
// Active detection exceptions, evaluation subset only (see
|
|
17981
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
17982
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
17983
|
+
// `bundle.exceptions ?? []`.
|
|
17984
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
17985
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
17986
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
17987
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
17988
|
+
// widening Policy itself would change a persisted shape to express something
|
|
17989
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
17990
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
17991
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
17992
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
17993
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
17994
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
17995
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
17996
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
17997
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
17998
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
17999
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
18000
|
+
fetchedAt: external_exports.iso.datetime()
|
|
18001
|
+
}).meta({ id: "PolicyBundle" });
|
|
18002
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
18003
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
18004
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
18005
|
+
secret: "critical",
|
|
18006
|
+
financial: "critical",
|
|
18007
|
+
// core-financial/credit-card
|
|
18008
|
+
code_flaw: "critical",
|
|
18009
|
+
pii: "high",
|
|
18010
|
+
phi: "high",
|
|
18011
|
+
custom: "high",
|
|
18012
|
+
// user-defined; conservative
|
|
18013
|
+
code_context: "low",
|
|
18014
|
+
config: "low"
|
|
18015
|
+
// observe-only; floors to monitor regardless
|
|
18016
|
+
};
|
|
18017
|
+
function severityFloorPolicy(category) {
|
|
18018
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
18019
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
18020
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
18021
|
+
}
|
|
18022
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
18023
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
18024
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
18025
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
18026
|
+
var BUILTIN_POLICY_SPECS = {
|
|
18027
|
+
monitor: {
|
|
18028
|
+
name: "Monitor",
|
|
18029
|
+
action: "log",
|
|
18030
|
+
reversible: false,
|
|
18031
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
18032
|
+
},
|
|
18033
|
+
warn: {
|
|
18034
|
+
name: "Warn",
|
|
18035
|
+
action: "warn",
|
|
18036
|
+
reversible: false,
|
|
18037
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
18038
|
+
},
|
|
18039
|
+
redact: {
|
|
18040
|
+
name: "Redact",
|
|
18041
|
+
action: "redact",
|
|
18042
|
+
reversible: false,
|
|
18043
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
18044
|
+
},
|
|
18045
|
+
vault: {
|
|
18046
|
+
name: "Redact & Vault",
|
|
18047
|
+
action: "redact",
|
|
18048
|
+
reversible: true,
|
|
18049
|
+
description: "Strip the matched value from the request and keep an encrypted, recoverable copy in the local vault, leaving a pointer in its place. Needs the vault consent granted under Settings; without it this behaves as Redact."
|
|
18050
|
+
},
|
|
18051
|
+
block: {
|
|
18052
|
+
name: "Block",
|
|
18053
|
+
action: "block",
|
|
18054
|
+
reversible: false,
|
|
18055
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
18056
|
+
}
|
|
18057
|
+
};
|
|
18058
|
+
function builtinPolicyToAction(id) {
|
|
18059
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
18060
|
+
}
|
|
18061
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
18062
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
18063
|
+
);
|
|
18064
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
18065
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
18066
|
+
);
|
|
18067
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
18068
|
+
function builtinPolicyIsReversible(id) {
|
|
18069
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18014
18070
|
}
|
|
18015
|
-
function
|
|
18016
|
-
|
|
18071
|
+
function policyIdIsReversible(policyId) {
|
|
18072
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18073
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
18074
|
+
return builtinPolicyIsReversible(id);
|
|
18017
18075
|
}
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18021
|
-
|
|
18022
|
-
|
|
18023
|
-
|
|
18024
|
-
|
|
18025
|
-
|
|
18026
|
-
|
|
18076
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
18077
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
18078
|
+
);
|
|
18079
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
18080
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
18081
|
+
);
|
|
18082
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18083
|
+
function policyIdToAction(policyId) {
|
|
18084
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18085
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
18086
|
+
return BUILTIN_POLICIES[id].action;
|
|
18027
18087
|
}
|
|
18088
|
+
var UsedByItem = external_exports.object({
|
|
18089
|
+
id: external_exports.string(),
|
|
18090
|
+
name: external_exports.string(),
|
|
18091
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
18092
|
+
enabled: external_exports.boolean()
|
|
18093
|
+
}).meta({ id: "UsedByItem" });
|
|
18094
|
+
var PolicyListItem = external_exports.object({
|
|
18095
|
+
id: external_exports.string(),
|
|
18096
|
+
kind: PolicyKind,
|
|
18097
|
+
name: external_exports.string(),
|
|
18098
|
+
enabled: external_exports.boolean(),
|
|
18099
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
18100
|
+
}).meta({ id: "PolicyListItem" });
|
|
18101
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
18102
|
+
var PolicyDetail = external_exports.object({
|
|
18103
|
+
specVersion: external_exports.literal(1),
|
|
18104
|
+
id: external_exports.string(),
|
|
18105
|
+
kind: PolicyKind,
|
|
18106
|
+
name: external_exports.string(),
|
|
18107
|
+
enabled: external_exports.boolean(),
|
|
18108
|
+
description: external_exports.string(),
|
|
18109
|
+
usedBy: external_exports.array(UsedByItem)
|
|
18110
|
+
}).meta({ id: "PolicyDetail" });
|
|
18111
|
+
var PolicyStatsResponse = external_exports.object({
|
|
18112
|
+
policies: external_exports.number().int().nonnegative(),
|
|
18113
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
18114
|
+
custom: external_exports.number().int().nonnegative(),
|
|
18115
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
18116
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
18028
18117
|
|
|
18029
18118
|
// ../../packages/schema/src/zod/project-files.ts
|
|
18030
18119
|
var ProjectFileInput = external_exports.object({
|
|
@@ -18104,44 +18193,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
|
|
|
18104
18193
|
NoRemediationDecision
|
|
18105
18194
|
]);
|
|
18106
18195
|
|
|
18107
|
-
// ../../packages/schema/src/zod/rule-test.ts
|
|
18108
|
-
var TestRulesRequest = external_exports.object({
|
|
18109
|
-
rules: external_exports.array(Rule).min(1).max(100),
|
|
18110
|
-
text: external_exports.string().max(5e4).optional(),
|
|
18111
|
-
fixtures: external_exports.array(RuleFixture).max(200).optional()
|
|
18112
|
-
}).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
|
|
18113
|
-
message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
|
|
18114
|
-
}).meta({ id: "TestRulesRequest" });
|
|
18115
|
-
var RuleTestMatch = external_exports.object({
|
|
18116
|
-
ruleId: external_exports.string(),
|
|
18117
|
-
category: DetectionCategory,
|
|
18118
|
-
severity: Severity,
|
|
18119
|
-
span: Span,
|
|
18120
|
-
confidence: external_exports.number().min(0).max(1),
|
|
18121
|
-
match: external_exports.string()
|
|
18122
|
-
}).meta({ id: "RuleTestMatch" });
|
|
18123
|
-
var FixtureResult = external_exports.object({
|
|
18124
|
-
label: external_exports.string(),
|
|
18125
|
-
shouldMatch: external_exports.boolean(),
|
|
18126
|
-
didMatch: external_exports.boolean(),
|
|
18127
|
-
passed: external_exports.boolean(),
|
|
18128
|
-
matches: external_exports.array(RuleTestMatch)
|
|
18129
|
-
}).meta({ id: "FixtureResult" });
|
|
18130
|
-
var TestRulesResponse = external_exports.object({
|
|
18131
|
-
// Present only when the request supplied `text`.
|
|
18132
|
-
adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
|
|
18133
|
-
fixtures: external_exports.array(FixtureResult),
|
|
18134
|
-
summary: external_exports.object({
|
|
18135
|
-
total: external_exports.number().int().nonnegative(),
|
|
18136
|
-
passed: external_exports.number().int().nonnegative(),
|
|
18137
|
-
failed: external_exports.number().int().nonnegative()
|
|
18138
|
-
}),
|
|
18139
|
-
// Ids of rules whose matcher type the engine cannot evaluate today (e.g.
|
|
18140
|
-
// `validator`), so they silently never match. Surfaced so an author is not
|
|
18141
|
-
// misled by a green run that actually skipped a rule.
|
|
18142
|
-
unsupportedRuleIds: external_exports.array(external_exports.string())
|
|
18143
|
-
}).meta({ id: "TestRulesResponse" });
|
|
18144
|
-
|
|
18145
18196
|
// ../../packages/schema/src/zod/security.ts
|
|
18146
18197
|
var SeveritySummaryItem = external_exports.object({
|
|
18147
18198
|
severity: Severity,
|
|
@@ -18239,10 +18290,22 @@ var TopSourcesQuery = external_exports.object({
|
|
|
18239
18290
|
// Omit for both kinds.
|
|
18240
18291
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
18241
18292
|
});
|
|
18242
|
-
var Provider =
|
|
18293
|
+
var Provider = Harness.extract([
|
|
18294
|
+
"ClaudeCode",
|
|
18295
|
+
"Cursor",
|
|
18296
|
+
"Codex",
|
|
18297
|
+
"Antigravity",
|
|
18298
|
+
"ClaudeAi",
|
|
18299
|
+
"ChatGpt",
|
|
18300
|
+
"Copilot",
|
|
18301
|
+
"Api"
|
|
18302
|
+
]).meta({ id: "Provider" });
|
|
18243
18303
|
var ScanCoverageProvider = external_exports.object({
|
|
18244
18304
|
provider: Provider,
|
|
18245
|
-
// Percent of that provider's traffic
|
|
18305
|
+
// Percent of that provider's traffic the shipped capture surface reaches.
|
|
18306
|
+
// A curated business fact, constant across every `range` — not a measured
|
|
18307
|
+
// per-window metric. 0 exactly when `supported` is false. See the comment
|
|
18308
|
+
// above the block for where these numbers are decided.
|
|
18246
18309
|
coverage: external_exports.number().int().min(0).max(100),
|
|
18247
18310
|
supported: external_exports.boolean()
|
|
18248
18311
|
}).meta({ id: "ScanCoverageProvider" });
|
|
@@ -18296,6 +18359,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
|
|
|
18296
18359
|
var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
|
|
18297
18360
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
18298
18361
|
|
|
18362
|
+
// ../../packages/schema/src/zod/settings-action.ts
|
|
18363
|
+
var SaveSettingsInput = external_exports.object({
|
|
18364
|
+
historicalAccess: external_exports.string(),
|
|
18365
|
+
modelJudgeConsent: external_exports.boolean(),
|
|
18366
|
+
vaultConsent: external_exports.string(),
|
|
18367
|
+
vaultInlineReveal: external_exports.string()
|
|
18368
|
+
});
|
|
18369
|
+
var AttachInput = external_exports.object({
|
|
18370
|
+
endpoint: external_exports.string(),
|
|
18371
|
+
label: external_exports.string().optional()
|
|
18372
|
+
});
|
|
18373
|
+
|
|
18299
18374
|
// ../../packages/schema/src/zod/triage.ts
|
|
18300
18375
|
var TriageHit = external_exports.object({
|
|
18301
18376
|
ruleId: external_exports.string(),
|
|
@@ -18310,7 +18385,7 @@ var TriageHit = external_exports.object({
|
|
|
18310
18385
|
valueFingerprint: external_exports.string().optional(),
|
|
18311
18386
|
keyVersion: external_exports.number().int().nonnegative().optional()
|
|
18312
18387
|
});
|
|
18313
|
-
var TriagePolicy =
|
|
18388
|
+
var TriagePolicy = CategoryPolicyId;
|
|
18314
18389
|
var TriageCategoryRec = external_exports.object({
|
|
18315
18390
|
category: DetectionCategory,
|
|
18316
18391
|
action: TriagePolicy,
|
|
@@ -18528,8 +18603,11 @@ function chmodBestEffort(path, mode) {
|
|
|
18528
18603
|
function tightenDir(dir) {
|
|
18529
18604
|
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18530
18605
|
}
|
|
18606
|
+
function mkdirOwnerOnlySync(dir, recursive = false) {
|
|
18607
|
+
mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
|
|
18608
|
+
}
|
|
18531
18609
|
function ensureDataDirSync(dir) {
|
|
18532
|
-
|
|
18610
|
+
mkdirOwnerOnlySync(dir, true);
|
|
18533
18611
|
tightenDir(dir);
|
|
18534
18612
|
}
|
|
18535
18613
|
function dbSidecars(file2) {
|
|
@@ -18541,6 +18619,26 @@ function tightenFile(file2) {
|
|
|
18541
18619
|
function tightenPerms(file2) {
|
|
18542
18620
|
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18543
18621
|
}
|
|
18622
|
+
function writeExclusiveOwnerOnlySync(file2, data) {
|
|
18623
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18624
|
+
}
|
|
18625
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
18626
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18627
|
+
try {
|
|
18628
|
+
rmSync(tmp, { force: true });
|
|
18629
|
+
} catch {
|
|
18630
|
+
}
|
|
18631
|
+
try {
|
|
18632
|
+
writeExclusiveOwnerOnlySync(tmp, data);
|
|
18633
|
+
renameSync(tmp, file2);
|
|
18634
|
+
} finally {
|
|
18635
|
+
try {
|
|
18636
|
+
rmSync(tmp, { force: true });
|
|
18637
|
+
} catch {
|
|
18638
|
+
}
|
|
18639
|
+
}
|
|
18640
|
+
tightenFile(file2);
|
|
18641
|
+
}
|
|
18544
18642
|
function classifyOccupant(file2) {
|
|
18545
18643
|
try {
|
|
18546
18644
|
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
@@ -18569,7 +18667,7 @@ function createOwnerOnlyFileSync(file2, data) {
|
|
|
18569
18667
|
}
|
|
18570
18668
|
let created;
|
|
18571
18669
|
try {
|
|
18572
|
-
|
|
18670
|
+
writeExclusiveOwnerOnlySync(tmp, data);
|
|
18573
18671
|
created = publishByLink(tmp, file2, data);
|
|
18574
18672
|
} finally {
|
|
18575
18673
|
try {
|
|
@@ -18591,7 +18689,7 @@ function publishByLink(tmp, file2, data) {
|
|
|
18591
18689
|
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18592
18690
|
}
|
|
18593
18691
|
try {
|
|
18594
|
-
|
|
18692
|
+
writeExclusiveOwnerOnlySync(file2, data);
|
|
18595
18693
|
return true;
|
|
18596
18694
|
} catch (err) {
|
|
18597
18695
|
if (err.code === "EEXIST") return false;
|
|
@@ -18604,6 +18702,25 @@ function backupPath(file2, tag) {
|
|
|
18604
18702
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18605
18703
|
}
|
|
18606
18704
|
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18705
|
+
var SNAPSHOT_STAGING_SUFFIX = ".partial";
|
|
18706
|
+
var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18707
|
+
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18708
|
+
function createSnapshotStaging(backup) {
|
|
18709
|
+
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18710
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18711
|
+
mkdirOwnerOnlySync(stage);
|
|
18712
|
+
tightenDir(stage);
|
|
18713
|
+
return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
|
|
18714
|
+
}
|
|
18715
|
+
function idleMs(entry) {
|
|
18716
|
+
for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18717
|
+
try {
|
|
18718
|
+
return Date.now() - statSync(candidate).mtimeMs;
|
|
18719
|
+
} catch {
|
|
18720
|
+
}
|
|
18721
|
+
}
|
|
18722
|
+
return null;
|
|
18723
|
+
}
|
|
18607
18724
|
function reapStalePartials(file2) {
|
|
18608
18725
|
const dir = dirname(file2);
|
|
18609
18726
|
const prefix = `${basename(file2)}.`;
|
|
@@ -18614,30 +18731,34 @@ function reapStalePartials(file2) {
|
|
|
18614
18731
|
return;
|
|
18615
18732
|
}
|
|
18616
18733
|
for (const name of entries) {
|
|
18617
|
-
if (!name.startsWith(prefix) || !name.endsWith(
|
|
18618
|
-
const
|
|
18734
|
+
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18735
|
+
const staging = join(dir, name);
|
|
18619
18736
|
try {
|
|
18620
|
-
|
|
18621
|
-
|
|
18737
|
+
const idle = idleMs(staging);
|
|
18738
|
+
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18739
|
+
rmSync2(staging, { recursive: true, force: true });
|
|
18622
18740
|
}
|
|
18623
18741
|
} catch {
|
|
18624
18742
|
}
|
|
18625
18743
|
}
|
|
18626
18744
|
}
|
|
18627
18745
|
function snapshotStore(db, backup) {
|
|
18628
|
-
const
|
|
18746
|
+
const { stage, copy } = createSnapshotStaging(backup);
|
|
18629
18747
|
try {
|
|
18630
|
-
|
|
18631
|
-
|
|
18632
|
-
|
|
18633
|
-
renameSync2(partial2, backup);
|
|
18748
|
+
db.prepare("VACUUM INTO ?").run(copy);
|
|
18749
|
+
tightenFile(copy);
|
|
18750
|
+
renameSync2(copy, backup);
|
|
18634
18751
|
} catch (error51) {
|
|
18635
18752
|
try {
|
|
18636
|
-
rmSync2(
|
|
18753
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18637
18754
|
} catch {
|
|
18638
18755
|
}
|
|
18639
18756
|
throw error51;
|
|
18640
18757
|
}
|
|
18758
|
+
try {
|
|
18759
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18760
|
+
} catch {
|
|
18761
|
+
}
|
|
18641
18762
|
}
|
|
18642
18763
|
function moveStoreAside(file2, backup) {
|
|
18643
18764
|
const undo = [];
|
|
@@ -19365,9 +19486,10 @@ function safeParseStringArray(raw) {
|
|
|
19365
19486
|
const parsed = safeJson(raw, null);
|
|
19366
19487
|
return Array.isArray(parsed) ? parsed : [];
|
|
19367
19488
|
}
|
|
19489
|
+
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19368
19490
|
function toHarness(raw) {
|
|
19369
19491
|
const parsed = Harness.safeParse(raw);
|
|
19370
|
-
return parsed.success ? parsed.data :
|
|
19492
|
+
return parsed.success ? parsed.data : DEFAULT_HARNESS;
|
|
19371
19493
|
}
|
|
19372
19494
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19373
19495
|
if (row.status) {
|
|
@@ -19498,7 +19620,7 @@ var SqliteActivityRepository = class {
|
|
|
19498
19620
|
const params = [];
|
|
19499
19621
|
if (query.harness && query.harness.length > 0) {
|
|
19500
19622
|
conditions.push(
|
|
19501
|
-
`coalesce(json_extract(attributes, '$.harness'), '
|
|
19623
|
+
`coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
|
|
19502
19624
|
);
|
|
19503
19625
|
params.push(...query.harness);
|
|
19504
19626
|
}
|
|
@@ -19705,13 +19827,13 @@ var SqliteActivityRepository = class {
|
|
|
19705
19827
|
* The DISTINCT harnesses that actually have sessions (optionally within a
|
|
19706
19828
|
* `started_at >= fromMs` window), so the filter can offer only the harnesses
|
|
19707
19829
|
* present rather than the full enum. Each stored value is normalized through
|
|
19708
|
-
* the SAME `toHarness` default the list uses (missing →
|
|
19709
|
-
* store of bare (harness-less) roots surfaces exactly
|
|
19830
|
+
* the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
|
|
19831
|
+
* a store of bare (harness-less) roots surfaces exactly that one harness.
|
|
19710
19832
|
*/
|
|
19711
19833
|
harnessFacets(fromMs) {
|
|
19712
19834
|
const where = fromMs === void 0 ? "" : " AND started_at >= ?";
|
|
19713
19835
|
const stmt = this.db.prepare(
|
|
19714
|
-
`SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '
|
|
19836
|
+
`SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
|
|
19715
19837
|
FROM audit_events WHERE ${SESSION_ROOT}${where}`
|
|
19716
19838
|
);
|
|
19717
19839
|
const rows = allRows(
|
|
@@ -19925,8 +20047,8 @@ var SqliteAuditEventsRepository = class {
|
|
|
19925
20047
|
}
|
|
19926
20048
|
// Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
|
|
19927
20049
|
// (which takes a caller-supplied random id), the id here is MINTED internally
|
|
19928
|
-
// from the natural key — `llmCallId(sessionId, messageId)` —
|
|
19929
|
-
//
|
|
20050
|
+
// from the natural key — `llmCallId(sessionId, messageId)` — derived from the
|
|
20051
|
+
// session and message alone, like the sibling local-store ids. The deterministic
|
|
19930
20052
|
// id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
|
|
19931
20053
|
// AND converge a streaming partial/final split across two incremental passes:
|
|
19932
20054
|
// a whole-file re-read no-ops (equal output), a lagging final replaces a
|
|
@@ -20891,6 +21013,42 @@ var SqliteFindingsRepository = class {
|
|
|
20891
21013
|
this.db = db;
|
|
20892
21014
|
}
|
|
20893
21015
|
db;
|
|
21016
|
+
/**
|
|
21017
|
+
* The newest `limit` findings, newest first.
|
|
21018
|
+
*
|
|
21019
|
+
* THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
|
|
21020
|
+
* pin it. The natural spelling — drive from `inspection_findings`, order by the
|
|
21021
|
+
* JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
|
|
21022
|
+
* not on the driving table: SQLite sorts every finding in the store through a
|
|
21023
|
+
* temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
|
|
21024
|
+
* against 0.9 ms for the form below, and the gap is a ratio of the store size
|
|
21025
|
+
* rather than a constant.
|
|
21026
|
+
*
|
|
21027
|
+
* What it takes to make `started_at` order come out of an index instead:
|
|
21028
|
+
*
|
|
21029
|
+
* - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
|
|
21030
|
+
* planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
|
|
21031
|
+
* index cannot serve the ORDER BY: the predicate spans four event types, so
|
|
21032
|
+
* satisfying a global `started_at` order across them needs a range merge
|
|
21033
|
+
* SQLite will not do, and it sorts instead. Freed of it, the planner scans
|
|
21034
|
+
* `idx_audit_started_at` — a bare `started_at` index — in DESC order and
|
|
21035
|
+
* filters the type per row, which lets the LIMIT stop the scan early.
|
|
21036
|
+
* - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
|
|
21037
|
+
* purely to stop the tables being reordered. With plain JOINs the planner
|
|
21038
|
+
* drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
|
|
21039
|
+
* unary plus ALONE recovers almost none of the win. Both are needed.
|
|
21040
|
+
*
|
|
21041
|
+
* Neither is a micro-optimisation that a later reader should tidy away, and
|
|
21042
|
+
* `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
|
|
21043
|
+
* the temp B-tree comes back.
|
|
21044
|
+
*
|
|
21045
|
+
* Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
|
|
21046
|
+
* identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
|
|
21047
|
+
* renaming the index turns this read into an ERROR, where `+` turns it into a
|
|
21048
|
+
* scan-and-sort — slower, still correct. The worst case for the chosen form is
|
|
21049
|
+
* a store whose recent captures carry no findings at all, where the scan walks
|
|
21050
|
+
* the whole index; that is still no worse than the full sort it replaced.
|
|
21051
|
+
*/
|
|
20894
21052
|
recentFindings(opts) {
|
|
20895
21053
|
const limit = opts?.limit ?? 50;
|
|
20896
21054
|
const rows = allRows(
|
|
@@ -20899,10 +21057,10 @@ var SqliteFindingsRepository = class {
|
|
|
20899
21057
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
20900
21058
|
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
20901
21059
|
e.event_type AS kind
|
|
20902
|
-
FROM
|
|
20903
|
-
JOIN
|
|
20904
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
20905
|
-
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
21060
|
+
FROM audit_events e
|
|
21061
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
21062
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21063
|
+
WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
20906
21064
|
ORDER BY e.started_at DESC, f.rowid DESC
|
|
20907
21065
|
LIMIT :limit`
|
|
20908
21066
|
),
|
|
@@ -21546,7 +21704,8 @@ var SqliteInspectionDefinitionsRepository = class {
|
|
|
21546
21704
|
}
|
|
21547
21705
|
db;
|
|
21548
21706
|
insertStmt;
|
|
21549
|
-
//
|
|
21707
|
+
// Insert-if-absent; returns the content-addressed definition id. An id already
|
|
21708
|
+
// present keeps the stored row untouched — see the class doc.
|
|
21550
21709
|
upsert(input) {
|
|
21551
21710
|
const id = inspectionDefinitionId(input.ruleId, input.version);
|
|
21552
21711
|
const row = toInspectionDefinitionRow(input, id);
|
|
@@ -21690,11 +21849,23 @@ function isParseableBinaryVersion(version2) {
|
|
|
21690
21849
|
|
|
21691
21850
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
21692
21851
|
var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
|
|
21852
|
+
function printableRuleId(entry) {
|
|
21853
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
21854
|
+
const candidate = entry.id;
|
|
21855
|
+
return Rule.shape.id.safeParse(candidate).success ? candidate : null;
|
|
21856
|
+
}
|
|
21857
|
+
function firstIssueReason(error51) {
|
|
21858
|
+
const issue2 = error51.issues[0];
|
|
21859
|
+
if (!issue2) return "unknown";
|
|
21860
|
+
const path = issue2.path.map((segment) => String(segment)).join(".");
|
|
21861
|
+
return path ? `${path}: ${issue2.code}` : issue2.code;
|
|
21862
|
+
}
|
|
21863
|
+
var REJECTED_RULE_DETAIL_CAP = 10;
|
|
21693
21864
|
function inventorySignature(packs2) {
|
|
21694
21865
|
return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
|
|
21695
21866
|
}
|
|
21696
21867
|
function hashRules(rulesJson) {
|
|
21697
|
-
return createHash2("
|
|
21868
|
+
return createHash2("sha256").update(rulesJson).digest("hex");
|
|
21698
21869
|
}
|
|
21699
21870
|
function parseVersion(v) {
|
|
21700
21871
|
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
@@ -21906,10 +22077,21 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21906
22077
|
* (all detection off) instead of falling back to the bundled packs. Every
|
|
21907
22078
|
* JSON-level failure therefore counts as invalid.
|
|
21908
22079
|
*/
|
|
22080
|
+
/**
|
|
22081
|
+
* ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
|
|
22082
|
+
* index is (namespace, pack_id) — so two enabled packs may contribute the same
|
|
22083
|
+
* id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
|
|
22084
|
+
* winner is whatever order SQLite happens to return, which makes a collision
|
|
22085
|
+
* resolve differently on two machines holding identical stores. Ordering by
|
|
22086
|
+
* (namespace, pack_id) makes the loser deterministic and therefore testable.
|
|
22087
|
+
*/
|
|
21909
22088
|
installedRuleset() {
|
|
21910
22089
|
const rows = allRows(
|
|
21911
22090
|
this.db.prepare(
|
|
21912
|
-
`SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version
|
|
22091
|
+
`SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
|
|
22092
|
+
namespace, pack_id AS packId
|
|
22093
|
+
FROM installed_packs
|
|
22094
|
+
ORDER BY namespace, pack_id`
|
|
21913
22095
|
)
|
|
21914
22096
|
);
|
|
21915
22097
|
const out = {
|
|
@@ -21917,22 +22099,32 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21917
22099
|
enabledPacks: 0,
|
|
21918
22100
|
rules: [],
|
|
21919
22101
|
invalidRules: 0,
|
|
22102
|
+
rejectedRules: [],
|
|
21920
22103
|
ruleActions: /* @__PURE__ */ new Map(),
|
|
21921
|
-
ruleVersions: /* @__PURE__ */ new Map()
|
|
22104
|
+
ruleVersions: /* @__PURE__ */ new Map(),
|
|
22105
|
+
reversibleRules: /* @__PURE__ */ new Set()
|
|
22106
|
+
};
|
|
22107
|
+
const reject = (pack, ruleId, reason) => {
|
|
22108
|
+
if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
|
|
22109
|
+
out.rejectedRules.push({ pack, ruleId, reason });
|
|
21922
22110
|
};
|
|
21923
22111
|
for (const row of rows) {
|
|
21924
22112
|
if (!intToBool(row.enabled)) continue;
|
|
21925
22113
|
out.enabledPacks += 1;
|
|
21926
22114
|
const action = policyIdToAction(row.policyId);
|
|
22115
|
+
const reversible = policyIdIsReversible(row.policyId);
|
|
22116
|
+
const pack = `${row.namespace}/${row.packId}`;
|
|
21927
22117
|
let raw;
|
|
21928
22118
|
try {
|
|
21929
22119
|
raw = JSON.parse(row.rulesJson);
|
|
21930
22120
|
} catch {
|
|
21931
22121
|
out.invalidRules += 1;
|
|
22122
|
+
reject(pack, null, "rules_json: malformed JSON");
|
|
21932
22123
|
continue;
|
|
21933
22124
|
}
|
|
21934
22125
|
if (!Array.isArray(raw)) {
|
|
21935
22126
|
out.invalidRules += 1;
|
|
22127
|
+
reject(pack, null, "rules_json: not an array");
|
|
21936
22128
|
continue;
|
|
21937
22129
|
}
|
|
21938
22130
|
for (const entry of raw) {
|
|
@@ -21941,7 +22133,12 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21941
22133
|
out.rules.push(parsed.data);
|
|
21942
22134
|
out.ruleActions.set(parsed.data.id, action);
|
|
21943
22135
|
out.ruleVersions.set(parsed.data.id, row.version);
|
|
21944
|
-
|
|
22136
|
+
if (reversible) out.reversibleRules.add(parsed.data.id);
|
|
22137
|
+
else out.reversibleRules.delete(parsed.data.id);
|
|
22138
|
+
} else {
|
|
22139
|
+
out.invalidRules += 1;
|
|
22140
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
|
|
22141
|
+
}
|
|
21945
22142
|
}
|
|
21946
22143
|
}
|
|
21947
22144
|
return out;
|
|
@@ -22135,31 +22332,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
|
22135
22332
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
22136
22333
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
22137
22334
|
var HARNESS_LABELS = {
|
|
22138
|
-
|
|
22139
|
-
|
|
22140
|
-
|
|
22141
|
-
|
|
22335
|
+
[HARNESS.ClaudeCode]: "Claude Code",
|
|
22336
|
+
[HARNESS.Cursor]: "Cursor",
|
|
22337
|
+
[HARNESS.Codex]: "Codex",
|
|
22338
|
+
[HARNESS.Antigravity]: "Antigravity"
|
|
22142
22339
|
};
|
|
22143
22340
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
22144
22341
|
var EMPTY_PROJECT_AGG = {
|
|
22145
22342
|
accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
|
|
22146
22343
|
findingsCount: 0
|
|
22147
22344
|
};
|
|
22345
|
+
var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
|
|
22346
|
+
var TITLE_NEEDLES = {
|
|
22347
|
+
ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
|
|
22348
|
+
Cursor: stripSeparators(SOURCE_TOOL.Cursor),
|
|
22349
|
+
Codex: stripSeparators(SOURCE_TOOL.Codex),
|
|
22350
|
+
Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
|
|
22351
|
+
};
|
|
22148
22352
|
function resolveHarnessId(attrs, row) {
|
|
22149
22353
|
if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
|
|
22150
22354
|
return attrs.provider;
|
|
22151
22355
|
}
|
|
22152
|
-
const t = (row.title ?? "")
|
|
22153
|
-
if (t.includes(
|
|
22154
|
-
if (t.includes(
|
|
22155
|
-
if (t.includes(
|
|
22156
|
-
if (t.includes(
|
|
22356
|
+
const t = stripSeparators(row.title ?? "");
|
|
22357
|
+
if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
|
|
22358
|
+
if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
|
|
22359
|
+
if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
|
|
22360
|
+
if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
|
|
22157
22361
|
return null;
|
|
22158
22362
|
}
|
|
22159
22363
|
function isLiveRealClaudeCode(rows) {
|
|
22160
22364
|
return rows.some((r) => {
|
|
22161
22365
|
const attrs = safeJson(r.attributes, {});
|
|
22162
|
-
return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) ===
|
|
22366
|
+
return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
|
|
22163
22367
|
});
|
|
22164
22368
|
}
|
|
22165
22369
|
function toAssetSummary(row) {
|
|
@@ -22427,7 +22631,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22427
22631
|
const isRealHarness = rows.some(
|
|
22428
22632
|
(r) => safeJson(r.attributes, {}).provenance !== "sample"
|
|
22429
22633
|
);
|
|
22430
|
-
const attachConfig = isRealHarness && harnessId ===
|
|
22634
|
+
const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
|
|
22431
22635
|
const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
|
|
22432
22636
|
if (q && assets.length === 0) continue;
|
|
22433
22637
|
const firstRow = rows[0];
|
|
@@ -23101,9 +23305,10 @@ var SqliteProjectFilesRepository = class {
|
|
|
23101
23305
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
23102
23306
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
23103
23307
|
var SqliteResolutionsRepository = class {
|
|
23104
|
-
constructor(db, now = () => Date.now()) {
|
|
23308
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
|
|
23105
23309
|
this.db = db;
|
|
23106
23310
|
this.now = now;
|
|
23311
|
+
this.newId = newId;
|
|
23107
23312
|
this.insertStmt = db.prepare(
|
|
23108
23313
|
`INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
|
|
23109
23314
|
VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
|
|
@@ -23136,12 +23341,14 @@ var SqliteResolutionsRepository = class {
|
|
|
23136
23341
|
}
|
|
23137
23342
|
db;
|
|
23138
23343
|
now;
|
|
23344
|
+
newId;
|
|
23139
23345
|
insertStmt;
|
|
23140
23346
|
latestStmt;
|
|
23141
23347
|
openAtRestStmt;
|
|
23142
23348
|
resolvedAtRestStmt;
|
|
23143
23349
|
/**
|
|
23144
|
-
* Insert one disposition row. The repo mints the id and stamps created_at
|
|
23350
|
+
* Insert one disposition row. The repo mints the id and stamps created_at,
|
|
23351
|
+
* both through the constructor's injectable seams.
|
|
23145
23352
|
* `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
|
|
23146
23353
|
* FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
|
|
23147
23354
|
* from the schema enums. NOTE for future manual-resolution writers: this is
|
|
@@ -23153,7 +23360,7 @@ var SqliteResolutionsRepository = class {
|
|
|
23153
23360
|
*/
|
|
23154
23361
|
insertResolution(r) {
|
|
23155
23362
|
this.insertStmt.run({
|
|
23156
|
-
id:
|
|
23363
|
+
id: this.newId(),
|
|
23157
23364
|
findingKey: r.findingKey,
|
|
23158
23365
|
status: FindingStatus.parse(r.status),
|
|
23159
23366
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -23732,16 +23939,16 @@ var ACTION_TO_KIND = {
|
|
|
23732
23939
|
warn: "warned"
|
|
23733
23940
|
};
|
|
23734
23941
|
var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
23735
|
-
var SCAN_COVERAGE =
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23739
|
-
|
|
23740
|
-
|
|
23741
|
-
|
|
23742
|
-
|
|
23743
|
-
|
|
23744
|
-
|
|
23942
|
+
var SCAN_COVERAGE = {
|
|
23943
|
+
[HARNESS.Antigravity]: { coverage: 60, supported: true },
|
|
23944
|
+
[HARNESS.Api]: { coverage: 0, supported: false },
|
|
23945
|
+
[HARNESS.ChatGpt]: { coverage: 40, supported: true },
|
|
23946
|
+
[HARNESS.ClaudeAi]: { coverage: 40, supported: true },
|
|
23947
|
+
[HARNESS.ClaudeCode]: { coverage: 100, supported: true },
|
|
23948
|
+
[HARNESS.Codex]: { coverage: 80, supported: true },
|
|
23949
|
+
[HARNESS.Copilot]: { coverage: 0, supported: false },
|
|
23950
|
+
[HARNESS.Cursor]: { coverage: 0, supported: false }
|
|
23951
|
+
};
|
|
23745
23952
|
var GRANULARITY = {
|
|
23746
23953
|
"7d": "day",
|
|
23747
23954
|
"30d": "day",
|
|
@@ -23840,9 +24047,22 @@ var SqliteSecurityRepository = class {
|
|
|
23840
24047
|
return Promise.resolve({ total, needsRemediation, bySeverity });
|
|
23841
24048
|
}
|
|
23842
24049
|
// Range is echoed but does not change the result today — coverage is a constant
|
|
23843
|
-
// business fact (see SCAN_COVERAGE), not a measured per-window metric.
|
|
24050
|
+
// business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
|
|
24051
|
+
// comes from Provider.options (the enum's declaration order), not from
|
|
24052
|
+
// SCAN_COVERAGE's own key order — deliberately, not because object literals
|
|
24053
|
+
// leave key order unspecified (ES2015 guarantees insertion order for these
|
|
24054
|
+
// non-integer string keys, so iterating SCAN_COVERAGE directly would be
|
|
24055
|
+
// reliable too). The reason is the schema comment's promise: the returned
|
|
24056
|
+
// order must mirror the generated OpenAPI enum list, which is Provider's
|
|
24057
|
+
// contract, not this table's.
|
|
23844
24058
|
scanCoverage(range) {
|
|
23845
|
-
return Promise.resolve({
|
|
24059
|
+
return Promise.resolve({
|
|
24060
|
+
range,
|
|
24061
|
+
providers: Provider.options.map((provider) => ({
|
|
24062
|
+
provider,
|
|
24063
|
+
...SCAN_COVERAGE[provider]
|
|
24064
|
+
}))
|
|
24065
|
+
});
|
|
23846
24066
|
}
|
|
23847
24067
|
enforcementActions(range) {
|
|
23848
24068
|
const lenMs = RANGE_DAYS[range] * DAY_MS4;
|
|
@@ -23900,10 +24120,10 @@ var SqliteSecurityRepository = class {
|
|
|
23900
24120
|
// count; a superseding open/redetected row means the finding is not
|
|
23901
24121
|
// remediated and is excluded, same invariant as severitySummary. Legacy
|
|
23902
24122
|
// at-rest findings with finding_key IS NULL can never have a resolution row
|
|
23903
|
-
// (the lifecycle is keyed by finding_key), so
|
|
23904
|
-
//
|
|
23905
|
-
//
|
|
23906
|
-
// mirroring this file's other methods.
|
|
24123
|
+
// (the lifecycle is keyed by finding_key), so they cannot reach the driving
|
|
24124
|
+
// set below. One raw-row query (fetch the findings with resolution activity in
|
|
24125
|
+
// the window + each one's latest resolution status/method/resolved_at) +
|
|
24126
|
+
// pure-JS filter/bucket/mean, mirroring this file's other methods.
|
|
23907
24127
|
mttrTrend(range) {
|
|
23908
24128
|
const granularity = granularityFor(range);
|
|
23909
24129
|
const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
|
|
@@ -23919,29 +24139,80 @@ var SqliteSecurityRepository = class {
|
|
|
23919
24139
|
// started_at the upsert overwrites onto inspection_findings.audit_event_id.
|
|
23920
24140
|
// COALESCE onto the parent event's started_at defends against any
|
|
23921
24141
|
// legacy/edge row the backfill left null.
|
|
23922
|
-
`SELECT
|
|
24142
|
+
`SELECT DISTINCT f.finding_key AS finding_key,
|
|
24143
|
+
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
|
|
23923
24144
|
latest.status AS latest_status,
|
|
23924
24145
|
latest.method AS latest_method,
|
|
23925
24146
|
latest.resolved_at AS latest_resolved_at
|
|
23926
|
-
FROM
|
|
23927
|
-
JOIN
|
|
23928
|
-
JOIN
|
|
24147
|
+
FROM finding_resolution fr
|
|
24148
|
+
CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
|
|
24149
|
+
CROSS JOIN audit_events e ON e.id = f.audit_event_id
|
|
24150
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
23929
24151
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
23930
24152
|
ON latest.finding_key = f.finding_key
|
|
23931
|
-
WHERE
|
|
23932
|
-
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
23933
|
-
|
|
23934
|
-
SELECT 1 FROM finding_resolution fr
|
|
23935
|
-
WHERE fr.finding_key = f.finding_key
|
|
23936
|
-
AND fr.resolved_at >= :windowStart
|
|
23937
|
-
)`
|
|
23938
|
-
// The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
|
|
23939
|
-
// any resolution activity at/after the window start — a row this method
|
|
24153
|
+
WHERE fr.resolved_at >= :windowStart
|
|
24154
|
+
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
|
|
24155
|
+
// `fr` is a SUPERSET prefilter, not the answer: a finding this method
|
|
23940
24156
|
// ultimately counts has its LATEST resolution inside the window, which
|
|
23941
|
-
// implies
|
|
23942
|
-
// latest-wins + status/method + window gate
|
|
23943
|
-
// dialect-agnostic.
|
|
23944
|
-
//
|
|
24157
|
+
// implies a resolution row at/after the window start exists, so nothing
|
|
24158
|
+
// wanted is dropped. The exact latest-wins + status/method + window gate
|
|
24159
|
+
// stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
|
|
24160
|
+
// implied rather than dropped — the join key comes from
|
|
24161
|
+
// finding_resolution, whose finding_key is NOT NULL.
|
|
24162
|
+
//
|
|
24163
|
+
// IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
|
|
24164
|
+
// the correction this replaced. Spelled as an `EXISTS` in the WHERE it
|
|
24165
|
+
// READ as a bound and was not one: SQLite drove from `audit_events` on
|
|
24166
|
+
// event_type, joined every capture event to its findings, and evaluated
|
|
24167
|
+
// the EXISTS last — bounding the RESULT and not the scan, so a 7d request
|
|
24168
|
+
// still cost the store's whole trackable history. Measured at 44.6 ms on
|
|
24169
|
+
// 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
|
|
24170
|
+
// both cases returning rows for a window holding a fraction of it.
|
|
24171
|
+
//
|
|
24172
|
+
// Two things carry it, and they answer DIFFERENT halves — which is worth
|
|
24173
|
+
// stating precisely, because the obvious reading (both are needed for the
|
|
24174
|
+
// speed) is wrong and was measured to be wrong:
|
|
24175
|
+
//
|
|
24176
|
+
// - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
|
|
24177
|
+
// keyword is semantically identical to JOIN and exists only to stop the
|
|
24178
|
+
// tables being reordered; with plain JOINs the planner puts `e` back on
|
|
24179
|
+
// the outside, because with no ANALYZE statistics it prices
|
|
24180
|
+
// `event_type IN (...)` as a selective probe. Reverting it alone takes
|
|
24181
|
+
// the 2k->20k flatness ratio from 1.32 to 16.87.
|
|
24182
|
+
// - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
|
|
24183
|
+
// `resolved_at >= :windowStart` a range SEARCH instead of a bare
|
|
24184
|
+
// `SCAN fr` — finding_key was this table's only index before it, so the
|
|
24185
|
+
// range had none. It buys NO flatness in store size: remove it and the
|
|
24186
|
+
// ratio above does not move, because the latest-resolution derived
|
|
24187
|
+
// table already passes over the whole of finding_resolution, so this
|
|
24188
|
+
// read is O(resolutions) either way and resolutions are not the store.
|
|
24189
|
+
// What it buys is the criterion `hot-read-query-plans.test.ts` enforces
|
|
24190
|
+
// — no hot read may pass over a table with no index — and that is the
|
|
24191
|
+
// guard that goes red when it is dropped. Neither test catches the
|
|
24192
|
+
// other's defect.
|
|
24193
|
+
//
|
|
24194
|
+
// SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
|
|
24195
|
+
// tidy-up. finding_resolution is append-only, so a key that was fixed,
|
|
24196
|
+
// redetected and fixed again carries several rows inside one window and
|
|
24197
|
+
// matches once per row — and the value below is a MEAN, so a key matched
|
|
24198
|
+
// three times is a key weighted three times.
|
|
24199
|
+
//
|
|
24200
|
+
// The skew is easy to argue away and the argument is wrong, so it is worth
|
|
24201
|
+
// recording. Duplicate rows for ONE key are identical (every projected
|
|
24202
|
+
// column is per-key: `latest.*` is latest-wins, `first_detected_at` is
|
|
24203
|
+
// preserved), so sums and counts scale together and that key's own mean
|
|
24204
|
+
// does not move. What moves is a bucket holding TWO findings that duplicate
|
|
24205
|
+
// UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
|
|
24206
|
+
// 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
|
|
24207
|
+
// `security.test.ts`'s "weights a finding ONCE however many resolution rows
|
|
24208
|
+
// it has inside the window" — which needed a fixture built for it, since no
|
|
24209
|
+
// single-key case can show it.
|
|
24210
|
+
//
|
|
24211
|
+
// `finding_key` is selected to make the DISTINCT dedup by KEY rather than
|
|
24212
|
+
// by value tuple. On the other columns alone, two genuinely different
|
|
24213
|
+
// findings sharing a severity, a first-detection event and a resolution
|
|
24214
|
+
// instant — one commit fixing two secrets in one file — are one tuple, and
|
|
24215
|
+
// collapsing them would under-count in the other direction.
|
|
23945
24216
|
),
|
|
23946
24217
|
{ windowStart }
|
|
23947
24218
|
);
|
|
@@ -23999,16 +24270,47 @@ var SqliteSecurityRepository = class {
|
|
|
23999
24270
|
}
|
|
24000
24271
|
// Recently-resolved activity feed: findings whose finding_key's LATEST
|
|
24001
24272
|
// finding_resolution row is status:'resolved'/method:'fixed-at-source' —
|
|
24002
|
-
// same latest-resolution-wins
|
|
24003
|
-
//
|
|
24004
|
-
//
|
|
24005
|
-
//
|
|
24006
|
-
//
|
|
24007
|
-
//
|
|
24008
|
-
//
|
|
24009
|
-
//
|
|
24010
|
-
//
|
|
24011
|
-
//
|
|
24273
|
+
// same latest-resolution-wins derived table as severitySummary / mttrTrend
|
|
24274
|
+
// (NOT a plain JOIN, which would surface every historical resolution row for
|
|
24275
|
+
// a key rather than just its current disposition). A key whose latest row is
|
|
24276
|
+
// a superseding 'open'/'redetected' row (the same secret came back) is
|
|
24277
|
+
// excluded — it is not currently resolved. Legacy at-rest findings with
|
|
24278
|
+
// finding_key IS NULL are excluded outright (the resolution lifecycle can
|
|
24279
|
+
// never attach to them). Path comes from the finding's parent event
|
|
24280
|
+
// (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
|
|
24281
|
+
// openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
|
|
24282
|
+
//
|
|
24283
|
+
// THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
|
|
24284
|
+
// the plan rather than a preference. Written the other way round — driving
|
|
24285
|
+
// from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
|
|
24286
|
+
// cannot use the join key: `f` is reached FROM `latest` by finding_key, so
|
|
24287
|
+
// `latest` gets probed on (rn, status, method) instead and the plan enumerates
|
|
24288
|
+
// every (code_change event x resolved key) pair before `f` can reject it. That
|
|
24289
|
+
// is a cross product, and it is quadratic in the store: measured at 10,966 ms
|
|
24290
|
+
// on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
|
|
24291
|
+
// returned. It was invisible for as long as it was, and reported at 8 ms,
|
|
24292
|
+
// because an empty finding_resolution table makes the inner side empty and the
|
|
24293
|
+
// cross product collapses to nothing — so the shape is only observable on a
|
|
24294
|
+
// corpus that seeds resolutions.
|
|
24295
|
+
//
|
|
24296
|
+
// Driving from `latest` instead makes every step below it a unique-index or
|
|
24297
|
+
// primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
|
|
24298
|
+
// so the cost is the derived table's own — linear in resolutions, which is
|
|
24299
|
+
// what this feed is legitimately about.
|
|
24300
|
+
//
|
|
24301
|
+
// CROSS JOIN is what actually pins that, and it is load-bearing rather than
|
|
24302
|
+
// decorative: in SQLite the keyword is semantically identical to JOIN and
|
|
24303
|
+
// exists only to stop the optimizer reordering the tables. Written as plain
|
|
24304
|
+
// JOINs in this order the planner puts `e` back on the outside — it has no
|
|
24305
|
+
// ANALYZE statistics to price the alternatives with, so it takes
|
|
24306
|
+
// `event_type = 'code_change'` for a selective index probe and rebuilds the
|
|
24307
|
+
// cross product. The FROM order alone was measured to change the plan not at
|
|
24308
|
+
// all.
|
|
24309
|
+
//
|
|
24310
|
+
// The LEFT JOIN it replaced was already an inner join in effect: three
|
|
24311
|
+
// `latest.*` predicates sit in the WHERE, and each of them is false for a
|
|
24312
|
+
// null-extended row. Spelling it JOIN changes no row and stops the plan
|
|
24313
|
+
// reading as though the findings side could drive.
|
|
24012
24314
|
recentlyResolved(limit = 20) {
|
|
24013
24315
|
const rows = allRows(
|
|
24014
24316
|
this.db.prepare(
|
|
@@ -24018,17 +24320,15 @@ var SqliteSecurityRepository = class {
|
|
|
24018
24320
|
json_extract(e.attributes, '$.file_path') AS path,
|
|
24019
24321
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
24020
24322
|
latest.resolved_at AS latest_resolved_at
|
|
24021
|
-
FROM
|
|
24022
|
-
JOIN
|
|
24023
|
-
JOIN
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
WHERE e.event_type = 'code_change'
|
|
24027
|
-
AND f.finding_key IS NOT NULL
|
|
24028
|
-
AND latest.status = 'resolved'
|
|
24323
|
+
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
24324
|
+
CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
|
|
24325
|
+
CROSS JOIN audit_events e ON e.id = f.audit_event_id
|
|
24326
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
24327
|
+
WHERE latest.status = 'resolved'
|
|
24029
24328
|
AND latest.method = 'fixed-at-source'
|
|
24030
24329
|
AND latest.resolved_at IS NOT NULL
|
|
24031
|
-
|
|
24330
|
+
AND e.event_type = 'code_change'
|
|
24331
|
+
ORDER BY latest.resolved_at DESC
|
|
24032
24332
|
LIMIT :limit`
|
|
24033
24333
|
),
|
|
24034
24334
|
{ limit }
|
|
@@ -24965,6 +25265,7 @@ function openAndInitialize(file2) {
|
|
|
24965
25265
|
function openLocalDatabase(dir) {
|
|
24966
25266
|
ensureDataDirSync(dir);
|
|
24967
25267
|
const file2 = join2(dir, DB_FILENAME);
|
|
25268
|
+
reapStalePartials(file2);
|
|
24968
25269
|
const {
|
|
24969
25270
|
db,
|
|
24970
25271
|
events,
|
|
@@ -25399,11 +25700,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25399
25700
|
}
|
|
25400
25701
|
}
|
|
25401
25702
|
|
|
25402
|
-
// ../../packages/persistence/src/settings.ts
|
|
25703
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
25403
25704
|
import { readFileSync as readFileSync3 } from "fs";
|
|
25705
|
+
import { posix, win32 } from "path";
|
|
25706
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
25707
|
+
if (platform2 === "darwin") {
|
|
25708
|
+
return [
|
|
25709
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
25710
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
25711
|
+
];
|
|
25712
|
+
}
|
|
25713
|
+
if (platform2 === "win32") {
|
|
25714
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
25715
|
+
}
|
|
25716
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
25717
|
+
}
|
|
25718
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
25719
|
+
for (const path of paths) {
|
|
25720
|
+
let text;
|
|
25721
|
+
try {
|
|
25722
|
+
text = readFileSync3(path, "utf8");
|
|
25723
|
+
} catch {
|
|
25724
|
+
continue;
|
|
25725
|
+
}
|
|
25726
|
+
const record2 = parseJsonObject(text);
|
|
25727
|
+
if (!record2) continue;
|
|
25728
|
+
const parsed = ManagedSettings.safeParse(record2);
|
|
25729
|
+
if (parsed.success) return parsed.data;
|
|
25730
|
+
}
|
|
25731
|
+
return null;
|
|
25732
|
+
}
|
|
25733
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
25734
|
+
if (!managed) return settings;
|
|
25735
|
+
const { values } = managed;
|
|
25736
|
+
const merged = { ...settings };
|
|
25737
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
25738
|
+
if (values.controlPlane !== void 0) {
|
|
25739
|
+
merged.controlPlane = {
|
|
25740
|
+
...values.controlPlane,
|
|
25741
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
25742
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
25743
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
25744
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
25745
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
25746
|
+
};
|
|
25747
|
+
}
|
|
25748
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
25749
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
25750
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
25751
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
25752
|
+
if (values.vaultConsent !== void 0) {
|
|
25753
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
25754
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
25755
|
+
// at the current version otherwise.
|
|
25756
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
25757
|
+
) : void 0;
|
|
25758
|
+
}
|
|
25759
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
25760
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
25761
|
+
acknowledgedAt: now().toISOString(),
|
|
25762
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
25763
|
+
} : void 0;
|
|
25764
|
+
}
|
|
25765
|
+
return merged;
|
|
25766
|
+
}
|
|
25767
|
+
|
|
25768
|
+
// ../../packages/persistence/src/settings.ts
|
|
25769
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25404
25770
|
import { join as join5 } from "path";
|
|
25405
25771
|
var SETTINGS_FILENAME = "settings.json";
|
|
25406
25772
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25773
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25774
|
+
}
|
|
25775
|
+
function readUserSettings(base) {
|
|
25407
25776
|
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
25408
25777
|
if (!record2) return defaultWorkspaceSettings();
|
|
25409
25778
|
try {
|
|
@@ -25415,7 +25784,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
25415
25784
|
function readJson(file2) {
|
|
25416
25785
|
let text;
|
|
25417
25786
|
try {
|
|
25418
|
-
text =
|
|
25787
|
+
text = readFileSync4(file2, "utf8");
|
|
25419
25788
|
} catch {
|
|
25420
25789
|
return null;
|
|
25421
25790
|
}
|
|
@@ -25533,15 +25902,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25533
25902
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25534
25903
|
import { execFileSync } from "child_process";
|
|
25535
25904
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25536
|
-
import {
|
|
25537
|
-
chmodSync as chmodSync2,
|
|
25538
|
-
mkdirSync as mkdirSync2,
|
|
25539
|
-
readFileSync as readFileSync4,
|
|
25540
|
-
renameSync as renameSync4,
|
|
25541
|
-
rmSync as rmSync4,
|
|
25542
|
-
statSync as statSync3,
|
|
25543
|
-
writeFileSync as writeFileSync3
|
|
25544
|
-
} from "fs";
|
|
25905
|
+
import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
25545
25906
|
import { join as join6 } from "path";
|
|
25546
25907
|
var VAULT_OCCUPANT_REASON = {
|
|
25547
25908
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
@@ -25634,7 +25995,8 @@ var LOCK_OWNER_FILE = "owner";
|
|
|
25634
25995
|
var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
|
|
25635
25996
|
function claimRotationLock(lock, owner) {
|
|
25636
25997
|
try {
|
|
25637
|
-
|
|
25998
|
+
mkdirOwnerOnlySync(lock);
|
|
25999
|
+
tightenDir(lock);
|
|
25638
26000
|
} catch (err) {
|
|
25639
26001
|
if (err.code === "EEXIST") return false;
|
|
25640
26002
|
throw asError(err);
|
|
@@ -25676,7 +26038,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
25676
26038
|
}
|
|
25677
26039
|
function releaseRotationLock(lease) {
|
|
25678
26040
|
try {
|
|
25679
|
-
if (
|
|
26041
|
+
if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
25680
26042
|
} catch {
|
|
25681
26043
|
return;
|
|
25682
26044
|
}
|
|
@@ -25727,7 +26089,7 @@ var FileKeyProvider = class {
|
|
|
25727
26089
|
#read() {
|
|
25728
26090
|
let raw;
|
|
25729
26091
|
try {
|
|
25730
|
-
raw =
|
|
26092
|
+
raw = readFileSync5(this.filePath, "utf8");
|
|
25731
26093
|
} catch (err) {
|
|
25732
26094
|
if (err.code === "ENOENT") return null;
|
|
25733
26095
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25766,15 +26128,19 @@ var FileKeyProvider = class {
|
|
|
25766
26128
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
25767
26129
|
* Used only for rotation, under the rotation lock — first creation goes
|
|
25768
26130
|
* through the creation-exclusive path instead.
|
|
26131
|
+
*
|
|
26132
|
+
* Delegated to the shared owner-only write rather than spelled here, so the
|
|
26133
|
+
* create mode this file is published at is the one paths.ts owns and tests
|
|
26134
|
+
* directly. A local copy of the pair was a second place the mode could be
|
|
26135
|
+
* dropped with the trailing tighten still repairing the end state, which is
|
|
26136
|
+
* the shape no assertion on a published file can see. It also picks up that
|
|
26137
|
+
* primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
|
|
26138
|
+
* create that refuses to follow a symlink planted at the tmp path.
|
|
25769
26139
|
*/
|
|
25770
26140
|
#write(keyring) {
|
|
25771
26141
|
ensureDataDirSync(this.#keysDir);
|
|
25772
|
-
|
|
25773
|
-
|
|
25774
|
-
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
25775
|
-
`, { mode: DATA_FILE_MODE });
|
|
25776
|
-
renameSync4(tmp, file2);
|
|
25777
|
-
tightenFileMode(file2);
|
|
26142
|
+
writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
26143
|
+
`);
|
|
25778
26144
|
return keyring;
|
|
25779
26145
|
}
|
|
25780
26146
|
};
|
|
@@ -25784,15 +26150,73 @@ function tightenFileMode(file2) {
|
|
|
25784
26150
|
} catch {
|
|
25785
26151
|
}
|
|
25786
26152
|
}
|
|
25787
|
-
var
|
|
26153
|
+
var SECURITY_TIMEOUT_MS = 5e3;
|
|
26154
|
+
var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
|
|
25788
26155
|
encoding: "utf8",
|
|
25789
|
-
|
|
26156
|
+
input: stdin,
|
|
26157
|
+
timeout: SECURITY_TIMEOUT_MS,
|
|
26158
|
+
// stderr is discarded rather than captured, and that is deliberate: a
|
|
26159
|
+
// captured stream rides out on an execFileSync error's `.stderr`, and the
|
|
26160
|
+
// write paths here carry the keyring. Exit status is the only thing any
|
|
26161
|
+
// branch below reads.
|
|
26162
|
+
stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
|
|
25790
26163
|
});
|
|
25791
26164
|
var SECURITY_ITEM_NOT_FOUND = 44;
|
|
26165
|
+
function corruptReason(err) {
|
|
26166
|
+
if (err instanceof SyntaxError) return "malformed JSON";
|
|
26167
|
+
return err instanceof Error ? err.message : "unknown";
|
|
26168
|
+
}
|
|
26169
|
+
function securityFailureMeta(err) {
|
|
26170
|
+
const e = err;
|
|
26171
|
+
const parts = [];
|
|
26172
|
+
if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
|
|
26173
|
+
if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
|
|
26174
|
+
if (typeof e.code === "string" && e.code) parts.push(e.code);
|
|
26175
|
+
return parts.length > 0 ? parts.join(", ") : "unknown error";
|
|
26176
|
+
}
|
|
26177
|
+
function writeCommand(keyring, update, keychain) {
|
|
26178
|
+
const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
|
|
26179
|
+
const parts = [
|
|
26180
|
+
"add-generic-password",
|
|
26181
|
+
...update ? ["-U"] : [],
|
|
26182
|
+
"-s",
|
|
26183
|
+
KEYCHAIN_SERVICE,
|
|
26184
|
+
"-a",
|
|
26185
|
+
KEYCHAIN_ACCOUNT,
|
|
26186
|
+
"-X",
|
|
26187
|
+
hex3
|
|
26188
|
+
];
|
|
26189
|
+
if (keychain !== void 0) {
|
|
26190
|
+
if (/['\\\n\r\0]/.test(keychain)) {
|
|
26191
|
+
throw new Error(
|
|
26192
|
+
"vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
|
|
26193
|
+
);
|
|
26194
|
+
}
|
|
26195
|
+
parts.push(`'${keychain}'`);
|
|
26196
|
+
}
|
|
26197
|
+
return `${parts.join(" ")}
|
|
26198
|
+
`;
|
|
26199
|
+
}
|
|
25792
26200
|
var KeychainKeyProvider = class {
|
|
25793
26201
|
#keysDir;
|
|
25794
26202
|
#exec;
|
|
25795
|
-
|
|
26203
|
+
#keychain;
|
|
26204
|
+
/**
|
|
26205
|
+
* The trailing keychain argument, or nothing. Every subcommand used here
|
|
26206
|
+
* takes it last (`add-generic-password [keychain]`,
|
|
26207
|
+
* `find-generic-password [keychain...]`), and omitting it means the default
|
|
26208
|
+
* search list. Fixed at construction, so it is built once rather than per
|
|
26209
|
+
* call on the capture path.
|
|
26210
|
+
*/
|
|
26211
|
+
#target;
|
|
26212
|
+
/**
|
|
26213
|
+
* `keychain` names the keychain to operate on, as `security`'s trailing
|
|
26214
|
+
* argument. Production passes nothing and gets the user's default keychain,
|
|
26215
|
+
* which is the whole point of the backend. A test driving the REAL binary
|
|
26216
|
+
* passes a throwaway one, because the alternative is writing vault key
|
|
26217
|
+
* material into the developer's own login keychain and leaving it there.
|
|
26218
|
+
*/
|
|
26219
|
+
constructor(keysDir2, exec = runSecurity, keychain) {
|
|
25796
26220
|
if (exec === runSecurity && process.platform !== "darwin") {
|
|
25797
26221
|
throw new Error(
|
|
25798
26222
|
`keychain custody is not available on this platform (${process.platform}); use file custody`
|
|
@@ -25800,6 +26224,8 @@ var KeychainKeyProvider = class {
|
|
|
25800
26224
|
}
|
|
25801
26225
|
this.#keysDir = keysDir2;
|
|
25802
26226
|
this.#exec = exec;
|
|
26227
|
+
this.#keychain = keychain;
|
|
26228
|
+
this.#target = keychain === void 0 ? [] : [keychain];
|
|
25803
26229
|
}
|
|
25804
26230
|
/** Where a fallback file provider for the same vault would keep its keyring. */
|
|
25805
26231
|
get keysDir() {
|
|
@@ -25838,18 +26264,22 @@ var KeychainKeyProvider = class {
|
|
|
25838
26264
|
KEYCHAIN_SERVICE,
|
|
25839
26265
|
"-a",
|
|
25840
26266
|
KEYCHAIN_ACCOUNT,
|
|
25841
|
-
"-w"
|
|
26267
|
+
"-w",
|
|
26268
|
+
...this.#target
|
|
25842
26269
|
]);
|
|
25843
26270
|
} catch (err) {
|
|
25844
26271
|
if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
|
|
25845
26272
|
throw new Error(
|
|
25846
|
-
`vault: keychain read failed (${
|
|
25847
|
-
{ cause: err }
|
|
26273
|
+
`vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
|
|
25848
26274
|
);
|
|
25849
26275
|
}
|
|
25850
26276
|
const body = raw.trim();
|
|
25851
26277
|
if (body.length === 0) return null;
|
|
25852
|
-
|
|
26278
|
+
try {
|
|
26279
|
+
return parseKeyring(body);
|
|
26280
|
+
} catch (err) {
|
|
26281
|
+
throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
|
|
26282
|
+
}
|
|
25853
26283
|
}
|
|
25854
26284
|
/**
|
|
25855
26285
|
* First mint: a plain `add-generic-password` (no `-U`) fails when an item
|
|
@@ -25857,37 +26287,25 @@ var KeychainKeyProvider = class {
|
|
|
25857
26287
|
* keyring — the loser re-reads and adopts it instead.
|
|
25858
26288
|
*/
|
|
25859
26289
|
#create(keyring) {
|
|
25860
|
-
const
|
|
25861
|
-
"add-generic-password",
|
|
25862
|
-
"-s",
|
|
25863
|
-
KEYCHAIN_SERVICE,
|
|
25864
|
-
"-a",
|
|
25865
|
-
KEYCHAIN_ACCOUNT,
|
|
25866
|
-
"-w",
|
|
25867
|
-
serializeKeyring(keyring)
|
|
25868
|
-
];
|
|
26290
|
+
const line = writeCommand(keyring, false, this.#keychain);
|
|
25869
26291
|
try {
|
|
25870
|
-
this.#exec(
|
|
26292
|
+
this.#exec(["-i"], line);
|
|
25871
26293
|
} catch (err) {
|
|
25872
26294
|
const winner = this.#read();
|
|
25873
26295
|
if (winner) return winner;
|
|
25874
|
-
throw
|
|
26296
|
+
throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
|
|
25875
26297
|
}
|
|
25876
26298
|
return keyring;
|
|
25877
26299
|
}
|
|
25878
26300
|
// `-U` updates the item in place, deliberately replacing the stored map with
|
|
25879
26301
|
// one that contains it — used only for rotation, under the rotation lock.
|
|
25880
26302
|
#replace(keyring) {
|
|
25881
|
-
this.#
|
|
25882
|
-
|
|
25883
|
-
"-
|
|
25884
|
-
|
|
25885
|
-
|
|
25886
|
-
|
|
25887
|
-
KEYCHAIN_ACCOUNT,
|
|
25888
|
-
"-w",
|
|
25889
|
-
serializeKeyring(keyring)
|
|
25890
|
-
]);
|
|
26303
|
+
const line = writeCommand(keyring, true, this.#keychain);
|
|
26304
|
+
try {
|
|
26305
|
+
this.#exec(["-i"], line);
|
|
26306
|
+
} catch (err) {
|
|
26307
|
+
throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
|
|
26308
|
+
}
|
|
25891
26309
|
return keyring;
|
|
25892
26310
|
}
|
|
25893
26311
|
};
|
|
@@ -26397,7 +26815,7 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26397
26815
|
}
|
|
26398
26816
|
|
|
26399
26817
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26400
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
26818
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
|
|
26401
26819
|
import { homedir as homedir2 } from "os";
|
|
26402
26820
|
import { basename as basename3, join as join10 } from "path";
|
|
26403
26821
|
|
|
@@ -26973,6 +27391,40 @@ function escapeRegExp2(value) {
|
|
|
26973
27391
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
26974
27392
|
}
|
|
26975
27393
|
|
|
27394
|
+
// ../../packages/detections/src/regex-cache.ts
|
|
27395
|
+
var singles = /* @__PURE__ */ new WeakMap();
|
|
27396
|
+
var keywordLists = /* @__PURE__ */ new WeakMap();
|
|
27397
|
+
var labelLists = /* @__PURE__ */ new WeakMap();
|
|
27398
|
+
function listCache(kind) {
|
|
27399
|
+
return kind === "keyword" ? keywordLists : labelLists;
|
|
27400
|
+
}
|
|
27401
|
+
function memoizedRegExp(owner, build) {
|
|
27402
|
+
const cached2 = singles.get(owner);
|
|
27403
|
+
if (cached2 !== void 0) {
|
|
27404
|
+
cached2.lastIndex = 0;
|
|
27405
|
+
return cached2;
|
|
27406
|
+
}
|
|
27407
|
+
const compiled = build();
|
|
27408
|
+
singles.set(owner, compiled);
|
|
27409
|
+
return compiled;
|
|
27410
|
+
}
|
|
27411
|
+
function memoizedRegExpList(kind, owner, build) {
|
|
27412
|
+
const cache = listCache(kind);
|
|
27413
|
+
const cached2 = cache.get(owner);
|
|
27414
|
+
if (cached2 !== void 0) {
|
|
27415
|
+
if (cached2.stateful) {
|
|
27416
|
+
for (const re of cached2.entries) if (re !== void 0) re.lastIndex = 0;
|
|
27417
|
+
}
|
|
27418
|
+
return cached2.entries;
|
|
27419
|
+
}
|
|
27420
|
+
const entries = build();
|
|
27421
|
+
cache.set(owner, {
|
|
27422
|
+
entries,
|
|
27423
|
+
stateful: entries.some((re) => re !== void 0 && (re.global || re.sticky))
|
|
27424
|
+
});
|
|
27425
|
+
return entries;
|
|
27426
|
+
}
|
|
27427
|
+
|
|
26976
27428
|
// ../../packages/detections/src/matchers/limits.ts
|
|
26977
27429
|
var MAX_MATCHES_PER_RULE = 1e4;
|
|
26978
27430
|
var MAX_REGEX_INPUT_LENGTH = 2e5;
|
|
@@ -26983,10 +27435,17 @@ var KeywordMatcher2 = class {
|
|
|
26983
27435
|
if (rule.matcher.type !== "keyword") return [];
|
|
26984
27436
|
const { keywords, caseSensitive } = rule.matcher;
|
|
26985
27437
|
const spans = [];
|
|
26986
|
-
|
|
26987
|
-
|
|
27438
|
+
const compiled = memoizedRegExpList(
|
|
27439
|
+
"keyword",
|
|
27440
|
+
rule.matcher,
|
|
27441
|
+
() => keywords.map((kw) => {
|
|
27442
|
+
if (kw.length === 0) return void 0;
|
|
27443
|
+
return new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
27444
|
+
})
|
|
27445
|
+
);
|
|
27446
|
+
for (const re of compiled) {
|
|
27447
|
+
if (re === void 0) continue;
|
|
26988
27448
|
if (spans.length >= MAX_MATCHES_PER_RULE) break;
|
|
26989
|
-
const re = new RegExp(escapeRegExp2(kw), caseSensitive ? "gu" : "giu");
|
|
26990
27449
|
let m;
|
|
26991
27450
|
while ((m = re.exec(text)) !== null) {
|
|
26992
27451
|
spans.push({ start: m.index, end: m.index + m[0].length });
|
|
@@ -27002,7 +27461,10 @@ var RegexMatcher2 = class {
|
|
|
27002
27461
|
match(text, rule) {
|
|
27003
27462
|
if (rule.matcher.type !== "regex") return [];
|
|
27004
27463
|
const { pattern, flags, captureGroup } = rule.matcher;
|
|
27005
|
-
const re =
|
|
27464
|
+
const re = memoizedRegExp(
|
|
27465
|
+
rule.matcher,
|
|
27466
|
+
() => new RegExp(pattern, flags.includes("d") ? flags : `${flags}d`)
|
|
27467
|
+
);
|
|
27006
27468
|
const scanText2 = text.length > MAX_REGEX_INPUT_LENGTH ? text.slice(0, MAX_REGEX_INPUT_LENGTH) : text;
|
|
27007
27469
|
const spans = [];
|
|
27008
27470
|
let m;
|
|
@@ -27061,6 +27523,10 @@ function luhnCheck(digits) {
|
|
|
27061
27523
|
// ../../packages/detections/src/engine.ts
|
|
27062
27524
|
var keywordMatcher = new KeywordMatcher2();
|
|
27063
27525
|
var regexMatcher = new RegexMatcher2();
|
|
27526
|
+
var MATCHERS = {
|
|
27527
|
+
keyword: (text, rule) => keywordMatcher.match(text, rule),
|
|
27528
|
+
regex: (text, rule) => regexMatcher.match(text, rule)
|
|
27529
|
+
};
|
|
27064
27530
|
var packs = /* @__PURE__ */ new Map();
|
|
27065
27531
|
var POST_VALIDATORS = {
|
|
27066
27532
|
entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
|
|
@@ -27076,8 +27542,7 @@ function passesPostValidators(rule, value) {
|
|
|
27076
27542
|
for (const ref of validators) {
|
|
27077
27543
|
const name = typeof ref === "string" ? ref : ref.name;
|
|
27078
27544
|
const config2 = typeof ref === "string" ? void 0 : ref.config;
|
|
27079
|
-
|
|
27080
|
-
if (validate && !validate(value, config2)) return false;
|
|
27545
|
+
if (!POST_VALIDATORS[name](value, config2)) return false;
|
|
27081
27546
|
}
|
|
27082
27547
|
return true;
|
|
27083
27548
|
}
|
|
@@ -27110,11 +27575,15 @@ function isCorroborated(candidate, candidates, text) {
|
|
|
27110
27575
|
const labels = req.labels;
|
|
27111
27576
|
if (labels && labels.length > 0) {
|
|
27112
27577
|
const haystack = text.slice(Math.max(0, winStart), winEnd);
|
|
27113
|
-
for (const
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
|
|
27117
|
-
|
|
27578
|
+
for (const re of memoizedRegExpList(
|
|
27579
|
+
"label",
|
|
27580
|
+
req,
|
|
27581
|
+
() => labels.map((label) => {
|
|
27582
|
+
const trimmed = label.trim();
|
|
27583
|
+
return trimmed.length === 0 ? void 0 : new RegExp(`(?<![A-Za-z0-9])${escapeRegExp2(trimmed)}(?![A-Za-z0-9])`, "i");
|
|
27584
|
+
})
|
|
27585
|
+
)) {
|
|
27586
|
+
if (re?.test(haystack)) return true;
|
|
27118
27587
|
}
|
|
27119
27588
|
}
|
|
27120
27589
|
return false;
|
|
@@ -27134,14 +27603,7 @@ function scan(text, rules, context) {
|
|
|
27134
27603
|
const candidates = [];
|
|
27135
27604
|
for (const rule of ruleset) {
|
|
27136
27605
|
if (!ruleApplies(rule, extension)) continue;
|
|
27137
|
-
|
|
27138
|
-
if (rule.matcher.type === "keyword") {
|
|
27139
|
-
spans = keywordMatcher.match(text, rule);
|
|
27140
|
-
} else if (rule.matcher.type === "regex") {
|
|
27141
|
-
spans = regexMatcher.match(text, rule);
|
|
27142
|
-
} else {
|
|
27143
|
-
continue;
|
|
27144
|
-
}
|
|
27606
|
+
const spans = MATCHERS[rule.matcher.type](text, rule);
|
|
27145
27607
|
for (const span of spans) {
|
|
27146
27608
|
const rawMatch = text.slice(span.start, span.end);
|
|
27147
27609
|
if (!passesPostValidators(rule, rawMatch)) continue;
|
|
@@ -27382,24 +27844,33 @@ function probesFor(rule) {
|
|
|
27382
27844
|
const derived = rule.matcher.type === "regex" ? derivedProbes(rule.matcher.pattern) : [];
|
|
27383
27845
|
return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
|
|
27384
27846
|
}
|
|
27385
|
-
|
|
27847
|
+
var wallClock = () => performance.now();
|
|
27848
|
+
function worstProbeMs(rule, now = wallClock, corroborate) {
|
|
27386
27849
|
let ms = 0;
|
|
27387
27850
|
let probe = "";
|
|
27851
|
+
let corroboratedMs;
|
|
27388
27852
|
for (const text of probesFor(rule)) {
|
|
27389
|
-
const start =
|
|
27853
|
+
const start = now();
|
|
27854
|
+
const corroborateStart = corroborate?.();
|
|
27390
27855
|
scan(text, [rule]);
|
|
27391
|
-
const elapsed =
|
|
27856
|
+
const elapsed = now() - start;
|
|
27857
|
+
const corroborateEnd = corroborate?.();
|
|
27392
27858
|
if (elapsed > ms) {
|
|
27393
27859
|
ms = elapsed;
|
|
27394
27860
|
probe = text;
|
|
27861
|
+
corroboratedMs = corroborateStart === void 0 || corroborateEnd === void 0 ? void 0 : corroborateEnd - corroborateStart;
|
|
27395
27862
|
}
|
|
27396
27863
|
if (ms >= BUDGET_MS) break;
|
|
27397
27864
|
}
|
|
27398
|
-
return { ms, probe };
|
|
27865
|
+
return { ms, probe, corroboratedMs };
|
|
27399
27866
|
}
|
|
27400
|
-
|
|
27401
|
-
|
|
27402
|
-
|
|
27867
|
+
var CPU_CORROBORATION_SHARE = 0.2;
|
|
27868
|
+
var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
|
|
27869
|
+
function checkRuleTiming(rule, corroborate) {
|
|
27870
|
+
const { ms, probe, corroboratedMs } = worstProbeMs(rule, wallClock, corroborate);
|
|
27871
|
+
const work = corroboratedMs ?? 0;
|
|
27872
|
+
const verdict = ms < BUDGET_MS ? "safe" : work >= CORROBORATION_FLOOR_MS ? "over-budget" : "uncorroborated";
|
|
27873
|
+
return { verdict, worstMs: ms, corroboratedMs: work, probe };
|
|
27403
27874
|
}
|
|
27404
27875
|
|
|
27405
27876
|
// ../../rules/code-flaws/auth-jwt-no-verify.json
|
|
@@ -29477,7 +29948,7 @@ function scanText(text, ruleVersions) {
|
|
|
29477
29948
|
}
|
|
29478
29949
|
|
|
29479
29950
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29480
|
-
import { existsSync as existsSync6, readFileSync as
|
|
29951
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
29481
29952
|
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
29482
29953
|
function resolveRepoIdentity(cwd) {
|
|
29483
29954
|
try {
|
|
@@ -29540,7 +30011,7 @@ function resolveGitContext(root) {
|
|
|
29540
30011
|
}
|
|
29541
30012
|
function safeRead(path) {
|
|
29542
30013
|
try {
|
|
29543
|
-
return
|
|
30014
|
+
return readFileSync6(path, "utf8");
|
|
29544
30015
|
} catch {
|
|
29545
30016
|
return void 0;
|
|
29546
30017
|
}
|
|
@@ -29854,7 +30325,12 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
29854
30325
|
build: (id) => ({ kind: "probe", id, rule }),
|
|
29855
30326
|
reply: (message) => {
|
|
29856
30327
|
if (message.kind !== "probed") return false;
|
|
29857
|
-
resolve2({
|
|
30328
|
+
resolve2({
|
|
30329
|
+
status: "ok",
|
|
30330
|
+
verdict: message.verdict,
|
|
30331
|
+
worstMs: message.worstMs,
|
|
30332
|
+
corroboratedMs: message.corroboratedMs
|
|
30333
|
+
});
|
|
29858
30334
|
return true;
|
|
29859
30335
|
}
|
|
29860
30336
|
},
|
|
@@ -29874,6 +30350,12 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
29874
30350
|
};
|
|
29875
30351
|
}
|
|
29876
30352
|
|
|
30353
|
+
// ../../packages/plugin-sdk/src/work-clock.ts
|
|
30354
|
+
function workClockMs() {
|
|
30355
|
+
const usage = typeof process.threadCpuUsage === "function" ? process.threadCpuUsage() : process.cpuUsage();
|
|
30356
|
+
return (usage.user + usage.system) / 1e3;
|
|
30357
|
+
}
|
|
30358
|
+
|
|
29877
30359
|
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29878
30360
|
var PASS_BUDGET_MS = 2e3;
|
|
29879
30361
|
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
@@ -29902,6 +30384,14 @@ function warnUnmeasured(rule) {
|
|
|
29902
30384
|
false
|
|
29903
30385
|
);
|
|
29904
30386
|
}
|
|
30387
|
+
function warnUncorroborated(rule, worstMs, corroboratedMs) {
|
|
30388
|
+
warn(
|
|
30389
|
+
rule,
|
|
30390
|
+
"deferred",
|
|
30391
|
+
`its regex matcher ran past the ReDoS timing budget (${worstMs.toFixed(1)}ms) but spent only ${corroboratedMs.toFixed(1)}ms of CPU doing it, which is a busy machine rather than a slow pattern; excluded from this run and measured again next time, with nothing recorded against it.`,
|
|
30392
|
+
false
|
|
30393
|
+
);
|
|
30394
|
+
}
|
|
29905
30395
|
function warnUnmeasurable(reason, count) {
|
|
29906
30396
|
process.stderr.write(
|
|
29907
30397
|
`[aka] ${String(count)} pulled/custom-pack rule(s) could not be time-checked: ${reason}. That is a problem with this install, not with the rules \u2014 until it is fixed they are excluded from every scan on this machine. Nothing was quarantined, so reinstalling AKA brings them straight back.
|
|
@@ -29948,24 +30438,31 @@ async function filterUnsafeRules(rules, gateway, opts) {
|
|
|
29948
30438
|
warnUnmeasured(rule);
|
|
29949
30439
|
continue;
|
|
29950
30440
|
}
|
|
29951
|
-
let
|
|
30441
|
+
let verdict;
|
|
29952
30442
|
let worstMs;
|
|
30443
|
+
let corroboratedMs = 0;
|
|
29953
30444
|
if (prober) {
|
|
29954
30445
|
const outcome = await prober.probe(rule);
|
|
29955
30446
|
if (outcome.status === "unavailable") {
|
|
29956
30447
|
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29957
30448
|
continue;
|
|
29958
30449
|
}
|
|
29959
|
-
|
|
30450
|
+
verdict = outcome.status === "ok" ? outcome.verdict : "over-budget";
|
|
29960
30451
|
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
30452
|
+
if (outcome.status === "ok") corroboratedMs = outcome.corroboratedMs;
|
|
29961
30453
|
} else {
|
|
29962
30454
|
try {
|
|
29963
|
-
({
|
|
30455
|
+
({ verdict, worstMs, corroboratedMs } = checkRuleTiming(rule, workClockMs));
|
|
29964
30456
|
} catch {
|
|
29965
|
-
|
|
30457
|
+
verdict = "over-budget";
|
|
29966
30458
|
worstMs = Number.POSITIVE_INFINITY;
|
|
29967
30459
|
}
|
|
29968
30460
|
}
|
|
30461
|
+
if (verdict === "uncorroborated") {
|
|
30462
|
+
warnUncorroborated(rule, worstMs, corroboratedMs);
|
|
30463
|
+
continue;
|
|
30464
|
+
}
|
|
30465
|
+
const isSafe = verdict === "safe";
|
|
29969
30466
|
let persisted = false;
|
|
29970
30467
|
try {
|
|
29971
30468
|
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
@@ -30065,6 +30562,11 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
30065
30562
|
};
|
|
30066
30563
|
}
|
|
30067
30564
|
|
|
30565
|
+
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
30566
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
30567
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
30568
|
+
import { join as join11 } from "path";
|
|
30569
|
+
|
|
30068
30570
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
30069
30571
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
30070
30572
|
function resolveInventoryContext(input) {
|
|
@@ -30095,17 +30597,16 @@ function resolveInventoryContext(input) {
|
|
|
30095
30597
|
}
|
|
30096
30598
|
|
|
30097
30599
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30098
|
-
import { mkdirSync as
|
|
30099
|
-
import { join as
|
|
30600
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
30601
|
+
import { join as join12 } from "path";
|
|
30100
30602
|
|
|
30101
30603
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30102
30604
|
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
30103
30605
|
import { basename as basename4, dirname as dirname3, sep as sep3 } from "path";
|
|
30104
30606
|
|
|
30105
30607
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30106
|
-
|
|
30107
|
-
import {
|
|
30108
|
-
import { basename as basename5, join as join12, relative, sep as sep4 } from "path";
|
|
30608
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
|
|
30609
|
+
import { basename as basename5, join as join13 } from "path";
|
|
30109
30610
|
|
|
30110
30611
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30111
30612
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -30204,6 +30705,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30204
30705
|
let initialized = false;
|
|
30205
30706
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
30206
30707
|
const categoryActionIndex = /* @__PURE__ */ new Map();
|
|
30708
|
+
let reversibleRuleIndex = /* @__PURE__ */ new Set();
|
|
30207
30709
|
async function ensureInitialized() {
|
|
30208
30710
|
if (initialized) return;
|
|
30209
30711
|
const bundle = await gateway.getPolicyBundle();
|
|
@@ -30216,6 +30718,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30216
30718
|
categoryActionIndex.set(p.target.category, p.action);
|
|
30217
30719
|
}
|
|
30218
30720
|
}
|
|
30721
|
+
reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
|
|
30219
30722
|
const bundledProbeKeys = new Set(
|
|
30220
30723
|
getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
|
|
30221
30724
|
);
|
|
@@ -30294,11 +30797,13 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30294
30797
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
30295
30798
|
if (worst === "redact") {
|
|
30296
30799
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
30800
|
+
const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
|
|
30297
30801
|
return {
|
|
30298
30802
|
action: "redact",
|
|
30299
30803
|
text: redact(text, redactFindings),
|
|
30300
30804
|
findings,
|
|
30301
|
-
enforcedFindings: redactFindings
|
|
30805
|
+
enforcedFindings: redactFindings,
|
|
30806
|
+
reversibleFindings
|
|
30302
30807
|
};
|
|
30303
30808
|
}
|
|
30304
30809
|
return { action: worst, text, findings };
|
|
@@ -30512,8 +31017,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30512
31017
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
30513
31018
|
|
|
30514
31019
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
30515
|
-
import { mkdirSync as
|
|
30516
|
-
import { join as
|
|
31020
|
+
import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
31021
|
+
import { join as join14 } from "path";
|
|
30517
31022
|
|
|
30518
31023
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
30519
31024
|
function redactedPlaceholder(category) {
|
|
@@ -30576,6 +31081,8 @@ var SecretVaultGlue = class {
|
|
|
30576
31081
|
async tokenizeText(text, opts) {
|
|
30577
31082
|
try {
|
|
30578
31083
|
const findings = opts?.findings ?? this.#selfScan(text);
|
|
31084
|
+
const reversible = opts?.reversible;
|
|
31085
|
+
const keeps = (finding) => reversible === void 0 || reversible.has(finding);
|
|
30579
31086
|
if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
30580
31087
|
if (findings.length === 0) return { text, pointers: [], degraded: [] };
|
|
30581
31088
|
const groups = groupSpans(text, findings);
|
|
@@ -30592,6 +31099,8 @@ var SecretVaultGlue = class {
|
|
|
30592
31099
|
} else if (original !== finding.rawMatch) {
|
|
30593
31100
|
replacement = redactedPlaceholder(group.category);
|
|
30594
31101
|
degraded.unshift({ category: group.category });
|
|
31102
|
+
} else if (!keeps(finding)) {
|
|
31103
|
+
replacement = redactedPlaceholder(finding.category);
|
|
30595
31104
|
} else {
|
|
30596
31105
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
30597
31106
|
ruleId: finding.ruleId,
|
|
@@ -30813,12 +31322,17 @@ var UNOPENABLE_VAULT = {
|
|
|
30813
31322
|
|
|
30814
31323
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
30815
31324
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
30816
|
-
import { join as
|
|
31325
|
+
import { join as join15 } from "path";
|
|
31326
|
+
|
|
31327
|
+
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
31328
|
+
var RANK = Object.fromEntries(
|
|
31329
|
+
KNOWN_BUILTIN_IDS.map((id, i) => [id, i])
|
|
31330
|
+
);
|
|
30817
31331
|
|
|
30818
31332
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
30819
|
-
import { mkdtempSync, readFileSync as
|
|
31333
|
+
import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
30820
31334
|
import { tmpdir } from "os";
|
|
30821
|
-
import { basename as basename6, dirname as dirname4, join as
|
|
31335
|
+
import { basename as basename6, dirname as dirname4, join as join16 } from "path";
|
|
30822
31336
|
var SuppressionEntrySchema = external_exports.object({
|
|
30823
31337
|
ruleId: external_exports.string(),
|
|
30824
31338
|
category: DetectionCategory,
|
|
@@ -30873,6 +31387,8 @@ var StandaloneDataGateway = class {
|
|
|
30873
31387
|
db;
|
|
30874
31388
|
// Kept for the fingerprint key lookup (exception.key lives beside the store).
|
|
30875
31389
|
dataDir;
|
|
31390
|
+
// One notice per gateway — see warnRulesetDiscarded.
|
|
31391
|
+
warnedRulesetDiscarded = false;
|
|
30876
31392
|
constructor(dataDir2, detections = [], meta3) {
|
|
30877
31393
|
this.db = openLocalDatabase(dataDir2);
|
|
30878
31394
|
this.dataDir = dataDir2;
|
|
@@ -30992,28 +31508,69 @@ var StandaloneDataGateway = class {
|
|
|
30992
31508
|
// - ANY invalid rule among enabled packs (all-invalid, partial corruption,
|
|
30993
31509
|
// or a single malformed entry) → undefined → bundled fallback. Serving a
|
|
30994
31510
|
// reduced "complete" set would silently drop exactly the corrupted rules
|
|
30995
|
-
// with no fallback
|
|
30996
|
-
// never loses coverage
|
|
30997
|
-
//
|
|
30998
|
-
//
|
|
31511
|
+
// with no fallback. The bundled packs are a superset of AKA's OWN packs,
|
|
31512
|
+
// so falling back never loses coverage there — but they contain no
|
|
31513
|
+
// pulled or custom pack, so for those this trades a partial ruleset for
|
|
31514
|
+
// none of them plus the loss of every pack's per-detection enforcement
|
|
31515
|
+
// action. That is deliberate (a store this machine cannot fully validate
|
|
31516
|
+
// is not authoritative), and it is why the cost of REJECTING a rule
|
|
31517
|
+
// matters: `Rule` is strict, so one unrecognized key in one custom rule
|
|
31518
|
+
// reaches this branch, not just a genuinely malformed or foreign store.
|
|
31519
|
+
// `installed-packs.test.ts` pins that per-rule counting;
|
|
30999
31520
|
// - enabled packs that produce ZERO rules with no invalids (e.g. every
|
|
31000
31521
|
// enabled pack's rules_json is `[]`) → undefined → bundled fallback: an
|
|
31001
31522
|
// enabled pack contributing nothing is untrustworthy, not a real
|
|
31002
31523
|
// "detect nothing" (that is expressed by disabling packs, handled above);
|
|
31003
31524
|
// - otherwise → the enabled packs' validated rules, marked complete.
|
|
31525
|
+
/**
|
|
31526
|
+
* The discard above is the one ruleset decision this gateway reaches on its
|
|
31527
|
+
* own, and it is the most expensive one here: ONE rejected entry costs the
|
|
31528
|
+
* user every custom rule and every per-detection enforcement action, replaced
|
|
31529
|
+
* by bundled packs that contain neither. Nothing else reports it — a hook is a
|
|
31530
|
+
* short-lived process whose stderr is the only channel it has — so name what
|
|
31531
|
+
* was rejected and where the rest of the list lives.
|
|
31532
|
+
*
|
|
31533
|
+
* Unlike a quarantine verdict this caches nothing: the rejection is re-derived
|
|
31534
|
+
* from the store on every run, so the recovery is to fix or reinstall the pack,
|
|
31535
|
+
* and no line here may offer a command that clears a stored verdict.
|
|
31536
|
+
*
|
|
31537
|
+
* Written at most once per gateway — a second getPolicyBundle() in the same
|
|
31538
|
+
* process would re-report the same finding.
|
|
31539
|
+
*/
|
|
31540
|
+
warnRulesetDiscarded(snapshot) {
|
|
31541
|
+
if (this.warnedRulesetDiscarded) return;
|
|
31542
|
+
this.warnedRulesetDiscarded = true;
|
|
31543
|
+
const listed = snapshot.rejectedRules.map((r) => `${r.pack}${r.ruleId === null ? "" : ` "${r.ruleId}"`} (${r.reason})`).join(", ");
|
|
31544
|
+
const undisclosed = snapshot.invalidRules - snapshot.rejectedRules.length;
|
|
31545
|
+
const more = undisclosed > 0 ? `, and ${String(undisclosed)} more` : "";
|
|
31546
|
+
process.stderr.write(
|
|
31547
|
+
`[aka] installed ruleset not used: ${String(snapshot.invalidRules)} rule(s) under enabled packs failed validation, so scanning fell back to the bundled packs and no custom rule or per-detection action is enforced \u2014 rejected: ${listed}${more}; review them with \`aka detections\`
|
|
31548
|
+
`
|
|
31549
|
+
);
|
|
31550
|
+
}
|
|
31004
31551
|
installedScanRules() {
|
|
31005
31552
|
try {
|
|
31006
31553
|
const snapshot = this.db.installedPacks.installedRuleset();
|
|
31007
31554
|
if (snapshot.installedPacks === 0) return void 0;
|
|
31008
31555
|
if (snapshot.enabledPacks === 0) {
|
|
31009
|
-
return {
|
|
31556
|
+
return {
|
|
31557
|
+
rules: [],
|
|
31558
|
+
ruleActions: /* @__PURE__ */ new Map(),
|
|
31559
|
+
ruleVersions: /* @__PURE__ */ new Map(),
|
|
31560
|
+
reversibleRules: /* @__PURE__ */ new Set(),
|
|
31561
|
+
complete: true
|
|
31562
|
+
};
|
|
31563
|
+
}
|
|
31564
|
+
if (snapshot.invalidRules > 0) {
|
|
31565
|
+
this.warnRulesetDiscarded(snapshot);
|
|
31566
|
+
return void 0;
|
|
31010
31567
|
}
|
|
31011
|
-
if (snapshot.invalidRules > 0) return void 0;
|
|
31012
31568
|
if (snapshot.rules.length === 0) return void 0;
|
|
31013
31569
|
return {
|
|
31014
31570
|
rules: snapshot.rules,
|
|
31015
31571
|
ruleActions: snapshot.ruleActions,
|
|
31016
31572
|
ruleVersions: snapshot.ruleVersions,
|
|
31573
|
+
reversibleRules: snapshot.reversibleRules,
|
|
31017
31574
|
complete: true
|
|
31018
31575
|
};
|
|
31019
31576
|
} catch {
|
|
@@ -31041,6 +31598,11 @@ var StandaloneDataGateway = class {
|
|
|
31041
31598
|
return {
|
|
31042
31599
|
version: "local",
|
|
31043
31600
|
policies: [...policies, ...rulePolicies],
|
|
31601
|
+
// The reversibility half of each pack's assignment. Emitted only under the
|
|
31602
|
+
// authoritative installed snapshot, exactly like rulePolicies above: the
|
|
31603
|
+
// bundled-packs fallback carries no per-pack assignment, so it carries no
|
|
31604
|
+
// reversibility either and every redaction there stays one-way.
|
|
31605
|
+
reversibleRuleIds: installed ? [...installed.reversibleRules] : [],
|
|
31044
31606
|
rules: installed ? installed.rules : [],
|
|
31045
31607
|
...installed ? { rulesComplete: true } : {},
|
|
31046
31608
|
...installed ? { ruleVersions: Object.fromEntries(installed.ruleVersions) } : {},
|
|
@@ -31059,29 +31621,32 @@ var StandaloneDataGateway = class {
|
|
|
31059
31621
|
return this.db.exceptions.recordBlocked(entry);
|
|
31060
31622
|
}
|
|
31061
31623
|
// Retention sweep over TERMINAL exception rows (revoked / expired / budget
|
|
31062
|
-
// exhausted) —
|
|
31063
|
-
//
|
|
31624
|
+
// exhausted) — local-store maintenance, invoked from SessionStart through the
|
|
31625
|
+
// LocalStoreMaintenance capability rather than the DataGateway port. Active
|
|
31626
|
+
// grants are never touched.
|
|
31064
31627
|
sweepTerminalExceptions(retentionMs) {
|
|
31065
31628
|
return this.db.exceptions.sweepTerminal(retentionMs);
|
|
31066
31629
|
}
|
|
31067
|
-
// The warn-era enforcement cap
|
|
31068
|
-
//
|
|
31069
|
-
// of block/redact rows capped to
|
|
31070
|
-
// already-capped one).
|
|
31630
|
+
// The warn-era enforcement cap — local-store maintenance, invoked from
|
|
31631
|
+
// SessionStart through the LocalStoreMaintenance capability rather than
|
|
31632
|
+
// the DataGateway port. Returns the number of block/redact rows capped to
|
|
31633
|
+
// warn (0 for a redact-policy store or an already-capped one).
|
|
31071
31634
|
capWarnEraEnforcement(policyMode) {
|
|
31072
31635
|
const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
|
|
31073
31636
|
return { capped };
|
|
31074
31637
|
}
|
|
31075
31638
|
// One project-file scan → the local project_file tree (one transaction inside
|
|
31076
|
-
// the LocalDatabase, fail-open there). Like the sweep above, this is
|
|
31077
|
-
//
|
|
31639
|
+
// the LocalDatabase, fail-open there). Like the sweep above, this is reached
|
|
31640
|
+
// through the LocalStoreMaintenance capability rather than the DataGateway
|
|
31641
|
+
// port: the file tree is a local-store read model.
|
|
31078
31642
|
recordProjectFiles(projectId, scan2) {
|
|
31079
31643
|
this.db.recordProjectFiles(projectId, scan2);
|
|
31080
31644
|
return Promise.resolve();
|
|
31081
31645
|
}
|
|
31082
31646
|
// Fold ghost source_project rows minted by the pre-worktree-fix resolver
|
|
31083
|
-
// (checkout-path identities) into the repo's canonical row.
|
|
31084
|
-
//
|
|
31647
|
+
// (checkout-path identities) into the repo's canonical row. Local-store
|
|
31648
|
+
// maintenance, invoked from SessionStart through the LocalStoreMaintenance
|
|
31649
|
+
// capability. Fail-open in the store.
|
|
31085
31650
|
reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
31086
31651
|
this.db.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
31087
31652
|
return Promise.resolve();
|
|
@@ -31092,10 +31657,10 @@ var StandaloneDataGateway = class {
|
|
|
31092
31657
|
* executing the plugin generation they started with (Claude Code caches
|
|
31093
31658
|
* plugin versions), and the write gate makes their installed-pack writes
|
|
31094
31659
|
* silent no-ops — this is the one-line nudge telling the user WHY, and that
|
|
31095
|
-
* a restart picks the newer plugin up.
|
|
31096
|
-
* SessionStart
|
|
31097
|
-
* null (no notice), and
|
|
31098
|
-
* never fire it.
|
|
31660
|
+
* a restart picks the newer plugin up. Local-store maintenance, invoked
|
|
31661
|
+
* from SessionStart through the LocalStoreMaintenance capability rather
|
|
31662
|
+
* than the DataGateway port. Fail-open: any error → null (no notice), and
|
|
31663
|
+
* unparseable versions compare equal so garbage can never fire it.
|
|
31099
31664
|
*/
|
|
31100
31665
|
staleBinaryNotice(currentVersion) {
|
|
31101
31666
|
try {
|
|
@@ -31173,7 +31738,8 @@ var StandaloneDataGateway = class {
|
|
|
31173
31738
|
|
|
31174
31739
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
31175
31740
|
var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
31176
|
-
|
|
31741
|
+
var defaultGatewayFactory = standaloneGatewayFactory;
|
|
31742
|
+
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
31177
31743
|
return gatewayFactory(config2, meta3);
|
|
31178
31744
|
}
|
|
31179
31745
|
|
|
@@ -31182,11 +31748,11 @@ import { randomUUID as randomUUID16 } from "crypto";
|
|
|
31182
31748
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31183
31749
|
|
|
31184
31750
|
// src/history/transcripts.ts
|
|
31185
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
31751
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync11 } from "fs";
|
|
31186
31752
|
import { homedir as homedir3 } from "os";
|
|
31187
|
-
import { join as
|
|
31753
|
+
import { join as join17 } from "path";
|
|
31188
31754
|
function transcriptsDir(home) {
|
|
31189
|
-
return
|
|
31755
|
+
return join17(home ?? homedir3(), ".claude", "projects");
|
|
31190
31756
|
}
|
|
31191
31757
|
function isRecord(value) {
|
|
31192
31758
|
return typeof value === "object" && value !== null;
|
|
@@ -31434,7 +32000,7 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
31434
32000
|
return;
|
|
31435
32001
|
}
|
|
31436
32002
|
for (const project of projects) {
|
|
31437
|
-
const projectDir =
|
|
32003
|
+
const projectDir = join17(dir, project);
|
|
31438
32004
|
let files;
|
|
31439
32005
|
try {
|
|
31440
32006
|
files = readdirSync5(projectDir).filter((name) => name.endsWith(".jsonl"));
|
|
@@ -31444,10 +32010,10 @@ function* iterateFileContents(dir, excludeSessionId) {
|
|
|
31444
32010
|
for (const file2 of files) {
|
|
31445
32011
|
if (excludeSessionId !== void 0 && file2.slice(0, -".jsonl".length) === excludeSessionId)
|
|
31446
32012
|
continue;
|
|
31447
|
-
const filePath =
|
|
32013
|
+
const filePath = join17(projectDir, file2);
|
|
31448
32014
|
let content;
|
|
31449
32015
|
try {
|
|
31450
|
-
content =
|
|
32016
|
+
content = readFileSync11(filePath, "utf8");
|
|
31451
32017
|
} catch {
|
|
31452
32018
|
continue;
|
|
31453
32019
|
}
|
|
@@ -31542,7 +32108,7 @@ async function scanHistory(config2, opts = {}, onHit) {
|
|
|
31542
32108
|
const result = await runtime.capture(
|
|
31543
32109
|
{
|
|
31544
32110
|
kind: message.kind,
|
|
31545
|
-
sourceTool:
|
|
32111
|
+
sourceTool: SOURCE_TOOL.ClaudeCode,
|
|
31546
32112
|
text: message.text,
|
|
31547
32113
|
occurredAt: message.occurredAt
|
|
31548
32114
|
},
|
|
@@ -31579,11 +32145,11 @@ async function scanHistory(config2, opts = {}, onHit) {
|
|
|
31579
32145
|
}
|
|
31580
32146
|
|
|
31581
32147
|
// src/history/tail-scrub.ts
|
|
31582
|
-
import { readFileSync as
|
|
32148
|
+
import { readFileSync as readFileSync13, renameSync as renameSync6, rmSync as rmSync7, statSync as statSync7, writeFileSync as writeFileSync10 } from "fs";
|
|
31583
32149
|
|
|
31584
32150
|
// src/remediation/redact.ts
|
|
31585
|
-
import { readFileSync as
|
|
31586
|
-
import { isAbsolute as isAbsolute2, relative
|
|
32151
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
32152
|
+
import { isAbsolute as isAbsolute2, relative, resolve } from "path";
|
|
31587
32153
|
function platformRedactionScope(home) {
|
|
31588
32154
|
return { artifactRoots: [transcriptsDir(home)] };
|
|
31589
32155
|
}
|
|
@@ -31597,7 +32163,7 @@ function realPathOrNull(path) {
|
|
|
31597
32163
|
function isWithinRoot(realTarget, root) {
|
|
31598
32164
|
const realRoot = realPathOrNull(root);
|
|
31599
32165
|
if (realRoot === null) return false;
|
|
31600
|
-
const rel =
|
|
32166
|
+
const rel = relative(realRoot, realTarget);
|
|
31601
32167
|
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
31602
32168
|
}
|
|
31603
32169
|
function resolveRedactableArtifact(filePath, scope) {
|
|
@@ -31614,7 +32180,7 @@ async function scrubTranscriptTail(filePath, deps) {
|
|
|
31614
32180
|
if (realPath === null) return null;
|
|
31615
32181
|
const statBefore = statSync7(realPath);
|
|
31616
32182
|
if (statBefore.size > (deps.maxBytes ?? DEFAULT_MAX_SCRUB_BYTES)) return null;
|
|
31617
|
-
const content =
|
|
32183
|
+
const content = readFileSync13(realPath, "utf8");
|
|
31618
32184
|
const lines = content.split("\n");
|
|
31619
32185
|
let rewritten = 0;
|
|
31620
32186
|
for (const [i, line] of lines.entries()) {
|
|
@@ -31653,13 +32219,13 @@ import { createHash as createHash5 } from "crypto";
|
|
|
31653
32219
|
import {
|
|
31654
32220
|
closeSync as closeSync2,
|
|
31655
32221
|
fstatSync,
|
|
31656
|
-
mkdirSync as
|
|
32222
|
+
mkdirSync as mkdirSync4,
|
|
31657
32223
|
openSync as openSync2,
|
|
31658
|
-
readFileSync as
|
|
32224
|
+
readFileSync as readFileSync14,
|
|
31659
32225
|
readSync,
|
|
31660
32226
|
writeFileSync as writeFileSync11
|
|
31661
32227
|
} from "fs";
|
|
31662
|
-
import { join as
|
|
32228
|
+
import { join as join18 } from "path";
|
|
31663
32229
|
|
|
31664
32230
|
// src/history/usage.ts
|
|
31665
32231
|
var NO_PROJECT_CWD = "/nonexistent/aka-reconciler/no-project";
|
|
@@ -31678,7 +32244,7 @@ async function reconcileSession(gateway, sessionId, records, opts = {}) {
|
|
|
31678
32244
|
if (anchor === void 0) return { llmCalls: 0, skipped: 0, lastPromptId };
|
|
31679
32245
|
const ctx = resolveInventoryContext({
|
|
31680
32246
|
cwd: anchor.cwd ?? NO_PROJECT_CWD,
|
|
31681
|
-
tool:
|
|
32247
|
+
tool: SOURCE_TOOL.ClaudeCode,
|
|
31682
32248
|
harnessVersion: anchor.version,
|
|
31683
32249
|
harnessInterface: anchor.entrypoint
|
|
31684
32250
|
});
|
|
@@ -31815,7 +32381,7 @@ function buildSessionRoot(sessionId, ctx, resolved, anchor, provider) {
|
|
|
31815
32381
|
if (typeof osVersion === "string") attributes.os_version = osVersion;
|
|
31816
32382
|
if (anchor.version !== void 0) attributes.harness_version = anchor.version;
|
|
31817
32383
|
attributes.provider = provider;
|
|
31818
|
-
attributes.harness = harnessFromTool(
|
|
32384
|
+
attributes.harness = harnessFromTool(SOURCE_TOOL.ClaudeCode);
|
|
31819
32385
|
if (anchor.cwd !== void 0) attributes.cwd = anchor.cwd;
|
|
31820
32386
|
if (anchor.version !== void 0) attributes.version = anchor.version;
|
|
31821
32387
|
const hostName = ctx.host?.attributes.host_name;
|