@akasecurity/ai-tc-claude-code 0.9.6 → 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/commands/setup.md +0 -23
- package/package.json +6 -5
- package/scripts/apply-suppressions.js +1428 -970
- package/scripts/backfill.js +1533 -1016
- package/scripts/dashboard.js +131 -10
- package/scripts/filescan.js +1458 -1009
- package/scripts/firstrun.js +1307 -901
- package/scripts/intro.js +1000 -894
- package/scripts/message-display.js +1404 -979
- package/scripts/onboard.js +1330 -891
- package/scripts/post-tool-use.js +1521 -1004
- package/scripts/pre-tool-use.js +1526 -1009
- package/scripts/query.js +1313 -902
- package/scripts/reconcile.js +1479 -1002
- package/scripts/remediate.js +1526 -1009
- package/scripts/scan-worker.js +996 -916
- package/scripts/session-start.js +1435 -1004
- package/scripts/start-light.js +1007 -902
- package/scripts/statusline.js +1307 -901
- package/scripts/stop.js +1069 -898
- package/scripts/user-prompt-submit.js +1525 -1008
package/scripts/remediate.js
CHANGED
|
@@ -492,7 +492,7 @@ var require_ignore = __commonJS({
|
|
|
492
492
|
});
|
|
493
493
|
|
|
494
494
|
// src/remediation/entry.ts
|
|
495
|
-
import { readFileSync as
|
|
495
|
+
import { readFileSync as readFileSync14 } from "fs";
|
|
496
496
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
497
497
|
|
|
498
498
|
// ../../packages/persistence/src/database.ts
|
|
@@ -585,6 +585,10 @@ var SQLITE_MIGRATIONS = [
|
|
|
585
585
|
{
|
|
586
586
|
tag: "0020_secret_vault_pagination_indexes",
|
|
587
587
|
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');"
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
tag: "0021_finding_resolution_resolved_at_index",
|
|
591
|
+
sql: "CREATE INDEX `idx_finding_resolution_resolved_at` ON `finding_resolution` (`resolved_at`);\n"
|
|
588
592
|
}
|
|
589
593
|
];
|
|
590
594
|
|
|
@@ -15300,6 +15304,47 @@ function date4(params) {
|
|
|
15300
15304
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/external.js
|
|
15301
15305
|
config(en_default());
|
|
15302
15306
|
|
|
15307
|
+
// ../../packages/schema/src/zod/harness-map.ts
|
|
15308
|
+
var HARNESS = {
|
|
15309
|
+
ClaudeCode: "claudecode",
|
|
15310
|
+
Cursor: "cursor",
|
|
15311
|
+
Copilot: "copilot",
|
|
15312
|
+
Codex: "codex",
|
|
15313
|
+
Antigravity: "antigravity",
|
|
15314
|
+
Windsurf: "windsurf",
|
|
15315
|
+
ClaudeDesktop: "claudedesktop",
|
|
15316
|
+
ChatGpt: "chatgpt",
|
|
15317
|
+
ClaudeAi: "claudeai",
|
|
15318
|
+
Api: "api"
|
|
15319
|
+
};
|
|
15320
|
+
var Harness = external_exports.enum(HARNESS).meta({ id: "Harness" });
|
|
15321
|
+
var SOURCE_TOOL = {
|
|
15322
|
+
ClaudeCode: "claude-code",
|
|
15323
|
+
ClaudeDesktop: "claude-desktop",
|
|
15324
|
+
Cursor: "cursor",
|
|
15325
|
+
ChatGpt: "chatgpt",
|
|
15326
|
+
ClaudeAi: "claude-ai",
|
|
15327
|
+
Copilot: "github-copilot",
|
|
15328
|
+
Codex: "codex",
|
|
15329
|
+
Antigravity: "antigravity",
|
|
15330
|
+
// No harness counterpart, deliberately: the CLI's own captures and a capture
|
|
15331
|
+
// whose tool could not be identified both render through the read side's
|
|
15332
|
+
// miss path rather than as a harness of their own.
|
|
15333
|
+
Cli: "cli",
|
|
15334
|
+
Unknown: "unknown"
|
|
15335
|
+
};
|
|
15336
|
+
var SourceTool = external_exports.enum(SOURCE_TOOL).meta({ id: "SourceTool" });
|
|
15337
|
+
var TOOL_TO_HARNESS = {
|
|
15338
|
+
[SOURCE_TOOL.ClaudeCode]: HARNESS.ClaudeCode,
|
|
15339
|
+
[SOURCE_TOOL.ClaudeDesktop]: HARNESS.ClaudeDesktop,
|
|
15340
|
+
[SOURCE_TOOL.Copilot]: HARNESS.Copilot,
|
|
15341
|
+
[SOURCE_TOOL.Cursor]: HARNESS.Cursor,
|
|
15342
|
+
[SOURCE_TOOL.ChatGpt]: HARNESS.ChatGpt,
|
|
15343
|
+
[SOURCE_TOOL.Codex]: HARNESS.Codex,
|
|
15344
|
+
[SOURCE_TOOL.Antigravity]: HARNESS.Antigravity,
|
|
15345
|
+
[SOURCE_TOOL.ClaudeAi]: HARNESS.ClaudeAi
|
|
15346
|
+
};
|
|
15347
|
+
|
|
15303
15348
|
// ../../packages/schema/src/zod/finding.ts
|
|
15304
15349
|
var DetectionCategory = external_exports.enum(["pii", "financial", "secret", "phi", "code_context", "code_flaw", "custom", "config"]).meta({ id: "DetectionCategory" });
|
|
15305
15350
|
var Severity = external_exports.enum(["critical", "high", "medium", "low"]).meta({ id: "Severity" });
|
|
@@ -15322,21 +15367,22 @@ var Finding = external_exports.object({
|
|
|
15322
15367
|
}).meta({ id: "Finding" });
|
|
15323
15368
|
var DetectedFinding = Finding.meta({ id: "DetectedFinding" });
|
|
15324
15369
|
var FindingAction = external_exports.enum(["blocked", "redacted", "warned", "allowed", "quarantined", "monitored"]).meta({ id: "FindingAction" });
|
|
15325
|
-
var FindingProvider =
|
|
15326
|
-
"
|
|
15327
|
-
"
|
|
15328
|
-
"
|
|
15329
|
-
"
|
|
15330
|
-
"
|
|
15331
|
-
"
|
|
15332
|
-
"
|
|
15333
|
-
"
|
|
15334
|
-
"
|
|
15370
|
+
var FindingProvider = Harness.extract([
|
|
15371
|
+
"ClaudeCode",
|
|
15372
|
+
"ClaudeDesktop",
|
|
15373
|
+
"Cursor",
|
|
15374
|
+
"Copilot",
|
|
15375
|
+
"ChatGpt",
|
|
15376
|
+
"ClaudeAi",
|
|
15377
|
+
"Codex",
|
|
15378
|
+
"Antigravity",
|
|
15379
|
+
"Api"
|
|
15335
15380
|
]).meta({ id: "FindingProvider" });
|
|
15336
15381
|
var FindingCategory = external_exports.enum([
|
|
15337
15382
|
"secret",
|
|
15338
15383
|
"pii",
|
|
15339
15384
|
"source_code",
|
|
15385
|
+
"code_flaw",
|
|
15340
15386
|
"external_share",
|
|
15341
15387
|
"mcp_server",
|
|
15342
15388
|
"customer_data",
|
|
@@ -15516,6 +15562,7 @@ var FindingInstanceDetail = FindingInstance.extend({
|
|
|
15516
15562
|
policy: FindingPolicyRef
|
|
15517
15563
|
}).meta({ id: "FindingInstanceDetail" });
|
|
15518
15564
|
var DEFAULT_FLAT_FINDINGS_LIMIT = 50;
|
|
15565
|
+
var MAX_FLAT_FINDINGS_LIMIT = 200;
|
|
15519
15566
|
var ListFindingInstancesQuery = external_exports.object({
|
|
15520
15567
|
severity: external_exports.array(Severity).optional(),
|
|
15521
15568
|
// Rule ids, the same vocabulary the grouped list's `subtype` carries.
|
|
@@ -15535,7 +15582,7 @@ var ListFindingInstancesQuery = external_exports.object({
|
|
|
15535
15582
|
q: external_exports.string().optional(),
|
|
15536
15583
|
sessionId: external_exports.string().optional(),
|
|
15537
15584
|
from: external_exports.iso.datetime().optional(),
|
|
15538
|
-
limit: external_exports.coerce.number().int().min(1).max(
|
|
15585
|
+
limit: external_exports.coerce.number().int().min(1).max(MAX_FLAT_FINDINGS_LIMIT).optional(),
|
|
15539
15586
|
cursor: external_exports.string().optional()
|
|
15540
15587
|
});
|
|
15541
15588
|
var ListFindingInstancesResponse = external_exports.object({
|
|
@@ -15597,30 +15644,6 @@ var ListFindingLocationsResponse = external_exports.object({
|
|
|
15597
15644
|
hasMore: external_exports.boolean()
|
|
15598
15645
|
}).meta({ id: "ListFindingLocationsResponse" });
|
|
15599
15646
|
|
|
15600
|
-
// ../../packages/schema/src/zod/harness-map.ts
|
|
15601
|
-
var Harness = external_exports.enum([
|
|
15602
|
-
"claudecode",
|
|
15603
|
-
"cursor",
|
|
15604
|
-
"copilot",
|
|
15605
|
-
"codex",
|
|
15606
|
-
"antigravity",
|
|
15607
|
-
"windsurf",
|
|
15608
|
-
"claudedesktop",
|
|
15609
|
-
"chatgpt",
|
|
15610
|
-
"claudeai",
|
|
15611
|
-
"api"
|
|
15612
|
-
]).meta({ id: "Harness" });
|
|
15613
|
-
var TOOL_TO_HARNESS = {
|
|
15614
|
-
"claude-code": "claudecode",
|
|
15615
|
-
"claude-desktop": "claudedesktop",
|
|
15616
|
-
"github-copilot": "copilot",
|
|
15617
|
-
cursor: "cursor",
|
|
15618
|
-
chatgpt: "chatgpt",
|
|
15619
|
-
codex: "codex",
|
|
15620
|
-
antigravity: "antigravity",
|
|
15621
|
-
"claude-ai": "claudeai"
|
|
15622
|
-
};
|
|
15623
|
-
|
|
15624
15647
|
// ../../packages/schema/src/zod/meta.ts
|
|
15625
15648
|
var InventoryObjectType = external_exports.enum(["host", "harness", "user", "skill", "hook", "mcp_server", "config_file"]).meta({ id: "InventoryObjectType" });
|
|
15626
15649
|
var AuditEventType = external_exports.enum([
|
|
@@ -16073,686 +16096,37 @@ var ActivityOverviewResponse = external_exports.object({
|
|
|
16073
16096
|
sessions: ListActivitySessionsResponse
|
|
16074
16097
|
}).meta({ id: "ActivityOverviewResponse" });
|
|
16075
16098
|
|
|
16076
|
-
// ../../packages/schema/src/zod/
|
|
16077
|
-
var
|
|
16078
|
-
var
|
|
16079
|
-
|
|
16080
|
-
|
|
16081
|
-
|
|
16082
|
-
|
|
16083
|
-
|
|
16084
|
-
|
|
16085
|
-
|
|
16086
|
-
|
|
16087
|
-
|
|
16088
|
-
|
|
16089
|
-
|
|
16090
|
-
|
|
16091
|
-
|
|
16092
|
-
|
|
16093
|
-
|
|
16094
|
-
filePath: external_exports.string().optional(),
|
|
16095
|
-
// The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
|
|
16096
|
-
// set by the tool-scanning hooks. The tool NAME only — never the tool's
|
|
16097
|
-
// arguments or output, which can carry the very value a finding masked
|
|
16098
|
-
// (metadata is stored unredacted). Gives findings on non-file captures a
|
|
16099
|
-
// display location ("via Bash") when no filePath exists.
|
|
16100
|
-
toolName: external_exports.string().optional(),
|
|
16101
|
-
// Set (true) by the worktree scanner when the file is excluded by the
|
|
16102
|
-
// repo's .gitignore. Gitignored files ARE still scanned — local scratch and
|
|
16103
|
-
// generated code can leak real secrets — but the provenance is recorded so
|
|
16104
|
-
// policy/dashboards can treat those findings as informational rather than
|
|
16105
|
-
// blocking. Omitted (not false) for tracked files and non-scan events.
|
|
16106
|
-
gitignored: external_exports.boolean().optional(),
|
|
16107
|
-
// Set (true) ONLY when the event's `content` is the COMPLETE file at
|
|
16108
|
-
// capture time (a worktree scan reading from disk). Hook-captured edits
|
|
16109
|
-
// (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
|
|
16110
|
-
// this. The resolver-on-ingest keys its fixed-at-source dropout
|
|
16111
|
-
// diff on this marker: only a whole-file snapshot can prove a previously
|
|
16112
|
-
// open finding is gone; a fragment's absence proves nothing (the secret
|
|
16113
|
-
// may live outside the hunk). Omitted (not false) for fragments and
|
|
16114
|
-
// non-scan events, so pre-marker clients safely default to the
|
|
16115
|
-
// non-authoritative path.
|
|
16116
|
-
wholeFile: external_exports.boolean().optional(),
|
|
16117
|
-
model: external_exports.string().optional(),
|
|
16118
|
-
turnIndex: external_exports.number().int().nonnegative().optional(),
|
|
16119
|
-
// Distributed-tracing correlation. `correlationId` ties a recorded event back
|
|
16120
|
-
// to the request that captured/ingested it (a UUID, generated independently of
|
|
16121
|
-
// the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
|
|
16122
|
-
// originating span when telemetry is enabled. Both optional + backward
|
|
16123
|
-
// compatible — populated by the plugin (see @akasecurity/plugin-sdk).
|
|
16124
|
-
correlationId: external_exports.uuid().optional(),
|
|
16125
|
-
traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
|
|
16126
|
-
// Ids of the detection exceptions that downgraded findings in this capture
|
|
16127
|
-
// to 'allow' — the enforcement audit trail's link back to the grant that
|
|
16128
|
-
// authorized the bypass. Absent on captures where no exception applied.
|
|
16129
|
-
exceptionIds: external_exports.array(external_exports.guid()).optional()
|
|
16130
|
-
}).meta({ id: "EventMetadata" });
|
|
16131
|
-
var Event = external_exports.object({
|
|
16132
|
-
id: external_exports.guid(),
|
|
16133
|
-
sourceTool: SourceTool,
|
|
16134
|
-
kind: EventKind,
|
|
16135
|
-
occurredAt: external_exports.iso.datetime(),
|
|
16136
|
-
contentHash: external_exports.string(),
|
|
16137
|
-
content: external_exports.string(),
|
|
16138
|
-
metadata: EventMetadata.optional()
|
|
16139
|
-
}).meta({ id: "Event" });
|
|
16140
|
-
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
16141
|
-
var IngestBatch = external_exports.object({
|
|
16142
|
-
events: external_exports.array(IngestEvent).min(1).max(100),
|
|
16143
|
-
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
16144
|
-
// additionally rejects any event whose contentHash the store has already
|
|
16145
|
-
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
16146
|
-
// backfill), where a re-run mints fresh event ids for identical content and
|
|
16147
|
-
// would otherwise accumulate duplicates. Live hook traffic must NOT set it:
|
|
16148
|
-
// two genuinely separate prompts can be byte-identical and both belong on
|
|
16149
|
-
// the timeline.
|
|
16150
|
-
dedupe: external_exports.literal("content-hash").optional()
|
|
16151
|
-
}).meta({ id: "IngestBatch" });
|
|
16152
|
-
|
|
16153
|
-
// ../../packages/schema/src/zod/inventory.ts
|
|
16154
|
-
var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
|
|
16155
|
-
var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
|
|
16156
|
-
var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
|
|
16157
|
-
var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
|
|
16158
|
-
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16159
|
-
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16160
|
-
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16161
|
-
var HarnessId = external_exports.enum(["claudecode", "cursor", "codex", "antigravity"]).meta({ id: "HarnessId" });
|
|
16162
|
-
var AccessCounts = external_exports.object({
|
|
16163
|
-
open: external_exports.number().int().nonnegative(),
|
|
16164
|
-
approved: external_exports.number().int().nonnegative(),
|
|
16165
|
-
blocked: external_exports.number().int().nonnegative(),
|
|
16166
|
-
total: external_exports.number().int().nonnegative()
|
|
16167
|
-
}).meta({ id: "AccessCounts" });
|
|
16168
|
-
var AssetSummary = external_exports.object({
|
|
16169
|
-
id: external_exports.string(),
|
|
16170
|
-
type: AssetType,
|
|
16171
|
-
name: external_exports.string(),
|
|
16172
|
-
sub: external_exports.string(),
|
|
16173
|
-
flags: external_exports.array(Flag),
|
|
16174
|
-
/** MCP servers only — omitted for all other types. */
|
|
16175
|
-
trust: TrustLevel.optional()
|
|
16176
|
-
}).meta({ id: "AssetSummary" });
|
|
16177
|
-
var ProjectSummary = external_exports.object({
|
|
16178
|
-
id: external_exports.string(),
|
|
16179
|
-
name: external_exports.string(),
|
|
16180
|
-
repo: external_exports.string(),
|
|
16181
|
-
visibility: Visibility,
|
|
16182
|
-
language: external_exports.string(),
|
|
16183
|
-
policyDefault: AccessLevel,
|
|
16184
|
-
updatedAt: external_exports.iso.datetime(),
|
|
16185
|
-
accessCounts: AccessCounts,
|
|
16186
|
-
findingsCount: external_exports.number().int().nonnegative()
|
|
16187
|
-
}).meta({ id: "ProjectSummary" });
|
|
16188
|
-
var HarnessCategory = external_exports.object({
|
|
16189
|
-
/** One of config/skill/mcp/hook — never project (enforced at service layer). */
|
|
16190
|
-
type: AssetType,
|
|
16191
|
-
assets: external_exports.array(AssetSummary)
|
|
16099
|
+
// ../../packages/schema/src/zod/config-inventory.ts
|
|
16100
|
+
var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
|
|
16101
|
+
var SkillScanEntry = external_exports.object({
|
|
16102
|
+
name: external_exports.string().min(1),
|
|
16103
|
+
// The identity source: a marketplace repo for plugin skills (e.g.
|
|
16104
|
+
// 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
|
|
16105
|
+
// 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
|
|
16106
|
+
// a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
|
|
16107
|
+
source: external_exports.string().min(1),
|
|
16108
|
+
scope: ConfigScope,
|
|
16109
|
+
pluginName: external_exports.string().optional(),
|
|
16110
|
+
// Volatile — rides the attribute bag, never the identity hash.
|
|
16111
|
+
version: external_exports.string().optional(),
|
|
16112
|
+
description: external_exports.string().optional(),
|
|
16113
|
+
// Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
|
|
16114
|
+
updatedAt: external_exports.iso.datetime().optional(),
|
|
16115
|
+
// Filesystem path — the promoted inventory `location` column.
|
|
16116
|
+
location: external_exports.string().optional()
|
|
16192
16117
|
});
|
|
16193
|
-
var
|
|
16194
|
-
|
|
16195
|
-
|
|
16196
|
-
|
|
16197
|
-
|
|
16198
|
-
|
|
16199
|
-
|
|
16200
|
-
|
|
16201
|
-
|
|
16202
|
-
|
|
16203
|
-
|
|
16204
|
-
|
|
16205
|
-
var AssetGroup = external_exports.object({
|
|
16206
|
-
/** Group key — never project (enforced at service layer). */
|
|
16207
|
-
type: AssetType,
|
|
16208
|
-
total: external_exports.number().int().nonnegative(),
|
|
16209
|
-
/**
|
|
16210
|
-
* MCP group only — omitted for all other types.
|
|
16211
|
-
* Partial: only TrustLevel keys with non-zero counts are included.
|
|
16212
|
-
* Strict: unknown keys are rejected — only TrustLevel values are valid keys.
|
|
16213
|
-
*/
|
|
16214
|
-
trustRollup: external_exports.object({
|
|
16215
|
-
"known-good": external_exports.number().int().nonnegative(),
|
|
16216
|
-
risky: external_exports.number().int().nonnegative(),
|
|
16217
|
-
unapproved: external_exports.number().int().nonnegative()
|
|
16218
|
-
}).partial().strict().optional(),
|
|
16219
|
-
/**
|
|
16220
|
-
* Partial: only Flag keys with non-zero counts are included.
|
|
16221
|
-
* Strict: unknown keys are rejected — only Flag values are valid keys.
|
|
16222
|
-
*/
|
|
16223
|
-
flagRollup: external_exports.object({
|
|
16224
|
-
update: external_exports.number().int().nonnegative(),
|
|
16225
|
-
stale: external_exports.number().int().nonnegative(),
|
|
16226
|
-
conflict: external_exports.number().int().nonnegative(),
|
|
16227
|
-
unknown: external_exports.number().int().nonnegative(),
|
|
16228
|
-
change: external_exports.number().int().nonnegative(),
|
|
16229
|
-
untracked: external_exports.number().int().nonnegative(),
|
|
16230
|
-
risk: external_exports.number().int().nonnegative(),
|
|
16231
|
-
findings: external_exports.number().int().nonnegative()
|
|
16232
|
-
}).partial().strict(),
|
|
16233
|
-
items: external_exports.array(AssetSummary)
|
|
16234
|
-
}).meta({ id: "AssetGroup" });
|
|
16235
|
-
var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
|
|
16236
|
-
var McpTool = external_exports.object({
|
|
16237
|
-
name: external_exports.string(),
|
|
16238
|
-
signature: external_exports.string(),
|
|
16239
|
-
description: external_exports.string(),
|
|
16240
|
-
write: external_exports.boolean(),
|
|
16241
|
-
/** Non-null string when tool is dangerous / blocked; null otherwise. */
|
|
16242
|
-
risk: external_exports.string().nullable()
|
|
16243
|
-
}).meta({ id: "McpTool" });
|
|
16244
|
-
var AssetFindingRef = external_exports.object({
|
|
16245
|
-
id: external_exports.string(),
|
|
16246
|
-
title: external_exports.string(),
|
|
16247
|
-
note: external_exports.string()
|
|
16248
|
-
});
|
|
16249
|
-
var AssetDetail = AssetSummary.extend({
|
|
16250
|
-
/** string | null — null when no description is available. */
|
|
16251
|
-
description: external_exports.string().nullable(),
|
|
16252
|
-
/** trustLevel | null — null for non-MCP assets. */
|
|
16253
|
-
trust: TrustLevel.nullable(),
|
|
16254
|
-
/** Type-specific raw key/values — FE renders the grid. */
|
|
16255
|
-
meta: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
16256
|
-
/** always present — object when there is an active finding, null when absent. */
|
|
16257
|
-
finding: AssetFindingRef.nullable(),
|
|
16258
|
-
/** MCP exposed-tools list — omitted for non-mcp. */
|
|
16259
|
-
tools: external_exports.array(McpTool).optional()
|
|
16260
|
-
}).meta({ id: "AssetDetail" });
|
|
16261
|
-
var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
|
|
16262
|
-
var InventoryStats = external_exports.object({
|
|
16263
|
-
/** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
|
|
16264
|
-
attention: external_exports.number().int().nonnegative(),
|
|
16265
|
-
byType: external_exports.object({
|
|
16266
|
-
project: external_exports.number().int().nonnegative(),
|
|
16267
|
-
skill: external_exports.number().int().nonnegative(),
|
|
16268
|
-
mcp: external_exports.number().int().nonnegative(),
|
|
16269
|
-
hook: external_exports.number().int().nonnegative(),
|
|
16270
|
-
config: external_exports.number().int().nonnegative()
|
|
16271
|
-
}),
|
|
16272
|
-
harnesses: external_exports.number().int().nonnegative(),
|
|
16273
|
-
mcpTrust: external_exports.object({
|
|
16274
|
-
"known-good": external_exports.number().int().nonnegative(),
|
|
16275
|
-
risky: external_exports.number().int().nonnegative(),
|
|
16276
|
-
unapproved: external_exports.number().int().nonnegative()
|
|
16277
|
-
})
|
|
16278
|
-
}).meta({ id: "InventoryStats" });
|
|
16279
|
-
var FileSummary = external_exports.object({
|
|
16280
|
-
path: external_exports.string(),
|
|
16281
|
-
name: external_exports.string(),
|
|
16282
|
-
origin: Origin,
|
|
16283
|
-
/** Effective access (override applied). */
|
|
16284
|
-
access: AccessLevel,
|
|
16285
|
-
/** True when a file_access_override differs from the computed default. */
|
|
16286
|
-
isCustom: external_exports.boolean(),
|
|
16287
|
-
findings: external_exports.number().int().nonnegative(),
|
|
16288
|
-
/** When the file was auto-blocked by a detection; null when not blocked. */
|
|
16289
|
-
blockedAt: external_exports.iso.datetime().nullable().optional(),
|
|
16290
|
-
/** Why the file was blocked; null when absent. */
|
|
16291
|
-
note: external_exports.string().nullable().optional()
|
|
16292
|
-
}).meta({ id: "FileSummary" });
|
|
16293
|
-
var FolderSummary = external_exports.object({
|
|
16294
|
-
name: external_exports.string(),
|
|
16295
|
-
path: external_exports.string(),
|
|
16296
|
-
/** Rollup of effective access across all descendants. */
|
|
16297
|
-
accessCounts: AccessCounts
|
|
16298
|
-
}).meta({ id: "FolderSummary" });
|
|
16299
|
-
var ProjectTreeResponse = external_exports.object({
|
|
16300
|
-
project: external_exports.object({
|
|
16301
|
-
id: external_exports.string(),
|
|
16302
|
-
repo: external_exports.string(),
|
|
16303
|
-
visibility: Visibility
|
|
16304
|
-
}),
|
|
16305
|
-
path: external_exports.string(),
|
|
16306
|
-
/** Browse mode: one-level folders at the current path. Omitted in search mode. */
|
|
16307
|
-
folders: external_exports.array(FolderSummary).optional(),
|
|
16308
|
-
files: external_exports.array(FileSummary)
|
|
16309
|
-
}).meta({ id: "ProjectTreeResponse" });
|
|
16310
|
-
var FileDetail = FileSummary.extend({
|
|
16311
|
-
project: external_exports.object({
|
|
16312
|
-
repo: external_exports.string(),
|
|
16313
|
-
visibility: Visibility,
|
|
16314
|
-
language: external_exports.string(),
|
|
16315
|
-
policyDefault: AccessLevel,
|
|
16316
|
-
updatedAt: external_exports.iso.datetime()
|
|
16317
|
-
}),
|
|
16318
|
-
findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
|
|
16319
|
-
}).meta({ id: "FileDetail" });
|
|
16320
|
-
var SetFileAccessBody = external_exports.object({
|
|
16321
|
-
path: external_exports.string(),
|
|
16322
|
-
access: AccessLevel
|
|
16323
|
-
}).meta({ id: "SetFileAccessBody" });
|
|
16324
|
-
var SetFileAccessResponse = external_exports.object({
|
|
16325
|
-
file: FileSummary,
|
|
16326
|
-
accessCounts: AccessCounts
|
|
16327
|
-
}).meta({ id: "SetFileAccessResponse" });
|
|
16328
|
-
var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
|
|
16329
|
-
var HarnessEventItem = external_exports.object({
|
|
16330
|
-
kind: HarnessEventKind,
|
|
16331
|
-
title: external_exports.string(),
|
|
16332
|
-
detail: external_exports.string(),
|
|
16333
|
-
occurredAt: external_exports.iso.datetime(),
|
|
16334
|
-
findingId: external_exports.string().nullable().optional()
|
|
16335
|
-
}).meta({ id: "HarnessEventItem" });
|
|
16336
|
-
var HarnessEventsResponse = external_exports.object({
|
|
16337
|
-
counts: external_exports.object({
|
|
16338
|
-
block: external_exports.number().int().nonnegative(),
|
|
16339
|
-
redact: external_exports.number().int().nonnegative(),
|
|
16340
|
-
warn: external_exports.number().int().nonnegative()
|
|
16341
|
-
}),
|
|
16342
|
-
items: external_exports.array(HarnessEventItem)
|
|
16343
|
-
}).meta({ id: "HarnessEventsResponse" });
|
|
16344
|
-
var RescanResponse = external_exports.object({
|
|
16345
|
-
jobId: external_exports.string(),
|
|
16346
|
-
startedAt: external_exports.iso.datetime()
|
|
16347
|
-
}).meta({ id: "RescanResponse" });
|
|
16348
|
-
var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
|
|
16349
|
-
var ListAssetsQuery = external_exports.object({
|
|
16350
|
-
/** Filter by one or more AssetType values; absent means all types. */
|
|
16351
|
-
type: external_exports.array(AssetType).optional(),
|
|
16352
|
-
/** Free-text search term. */
|
|
16353
|
-
q: external_exports.string().optional()
|
|
16354
|
-
});
|
|
16355
|
-
var GetProjectTreeQuery = external_exports.object({
|
|
16356
|
-
/** Subtree root path; defaults to repository root when absent. */
|
|
16357
|
-
path: external_exports.string().optional(),
|
|
16358
|
-
/** Free-text filter applied to file paths. */
|
|
16359
|
-
q: external_exports.string().optional(),
|
|
16360
|
-
/**
|
|
16361
|
-
* Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
|
|
16362
|
-
* file across the whole repo (folders omitted, most-recent first), ignoring
|
|
16363
|
-
* `path`/`q` — powers the project-wide "recently blocked" strip.
|
|
16364
|
-
*/
|
|
16365
|
-
filter: external_exports.enum(["blocked"]).optional()
|
|
16366
|
-
});
|
|
16367
|
-
var GetProjectFileQuery = external_exports.object({
|
|
16368
|
-
/** Repository-relative file path; absent or empty → 400. */
|
|
16369
|
-
path: external_exports.string()
|
|
16370
|
-
});
|
|
16371
|
-
var GetHarnessEventsQuery = external_exports.object({
|
|
16372
|
-
/** Maximum number of events to return. Range: 1–50; default: 7. */
|
|
16373
|
-
limit: external_exports.coerce.number().int().min(1).max(50).default(7)
|
|
16374
|
-
});
|
|
16375
|
-
|
|
16376
|
-
// ../../packages/schema/src/zod/exception.ts
|
|
16377
|
-
var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
|
|
16378
|
-
var ExceptionConditions = external_exports.object({
|
|
16379
|
-
repo: external_exports.string().optional(),
|
|
16380
|
-
sourceTool: external_exports.string().optional(),
|
|
16381
|
-
provider: external_exports.string().optional()
|
|
16382
|
-
}).strict();
|
|
16383
|
-
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
16384
|
-
var DetectionException = external_exports.object({
|
|
16385
|
-
id: external_exports.guid(),
|
|
16386
|
-
ruleId: external_exports.string(),
|
|
16387
|
-
// Denormalized from the rule, for reporting — never matched on.
|
|
16388
|
-
category: DetectionCategory,
|
|
16389
|
-
// HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
|
|
16390
|
-
// fingerprint, never the raw value, and never reversible. Matching recomputes
|
|
16391
|
-
// the fingerprint from a fresh capture; the value itself is never stored.
|
|
16392
|
-
// Shape-constrained so a malformed — or accidentally raw — value is rejected
|
|
16393
|
-
// at the boundary rather than persisted.
|
|
16394
|
-
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
16395
|
-
// Version of the fingerprint key the grant was written under; a rotated key
|
|
16396
|
-
// invalidates old grants rather than silently mismatching them.
|
|
16397
|
-
keyVersion: external_exports.number().int().positive(),
|
|
16398
|
-
// maskMatch() preview of the approved value — never the raw value.
|
|
16399
|
-
maskedValue: external_exports.string(),
|
|
16400
|
-
capability: ExceptionCapability.default("suppress"),
|
|
16401
|
-
scope: ExceptionScope,
|
|
16402
|
-
expiresAt: external_exports.iso.datetime().nullable(),
|
|
16403
|
-
maxUses: external_exports.number().int().positive().nullable(),
|
|
16404
|
-
useCount: external_exports.number().int().nonnegative(),
|
|
16405
|
-
lastUsedAt: external_exports.iso.datetime().nullable(),
|
|
16406
|
-
// Mandatory: every grant carries the human reason it exists.
|
|
16407
|
-
justification: external_exports.string().min(1),
|
|
16408
|
-
conditions: ExceptionConditions.nullable(),
|
|
16409
|
-
createdBy: external_exports.string(),
|
|
16410
|
-
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
|
|
16411
|
-
createdAt: external_exports.iso.datetime(),
|
|
16412
|
-
updatedAt: external_exports.iso.datetime(),
|
|
16413
|
-
// Revocation is terminal and retained — consumed/expired/revoked rows are
|
|
16414
|
-
// audit evidence; nothing in the exception lifecycle hard-deletes.
|
|
16415
|
-
revokedAt: external_exports.iso.datetime().nullable(),
|
|
16416
|
-
revokedBy: external_exports.string().nullable(),
|
|
16417
|
-
revokeReason: external_exports.string().nullable()
|
|
16418
|
-
});
|
|
16419
|
-
var ExceptionBundleEntry = DetectionException.pick({
|
|
16420
|
-
id: true,
|
|
16421
|
-
ruleId: true,
|
|
16422
|
-
valueFingerprint: true,
|
|
16423
|
-
keyVersion: true,
|
|
16424
|
-
capability: true,
|
|
16425
|
-
expiresAt: true,
|
|
16426
|
-
maxUses: true,
|
|
16427
|
-
useCount: true,
|
|
16428
|
-
conditions: true
|
|
16429
|
-
});
|
|
16430
|
-
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
16431
|
-
|
|
16432
|
-
// ../../packages/schema/src/zod/rule.ts
|
|
16433
|
-
var MatcherType = external_exports.enum(["keyword", "regex", "validator"]).meta({ id: "MatcherType" });
|
|
16434
|
-
var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
|
|
16435
|
-
var KeywordMatcher = external_exports.object({
|
|
16436
|
-
type: external_exports.literal("keyword"),
|
|
16437
|
-
// An empty keyword matches at every position, yielding one zero-length span
|
|
16438
|
-
// per character. Rejected here because a keyword that matches everything is
|
|
16439
|
-
// never intentional.
|
|
16440
|
-
keywords: external_exports.array(external_exports.string().min(1)).min(1),
|
|
16441
|
-
caseSensitive: external_exports.boolean().default(false)
|
|
16442
|
-
});
|
|
16443
|
-
function isValidRegex(pattern, flags) {
|
|
16444
|
-
try {
|
|
16445
|
-
new RegExp(pattern, flags);
|
|
16446
|
-
return true;
|
|
16447
|
-
} catch {
|
|
16448
|
-
return false;
|
|
16449
|
-
}
|
|
16450
|
-
}
|
|
16451
|
-
function matchesEmptyString(pattern, flags) {
|
|
16452
|
-
try {
|
|
16453
|
-
const re = new RegExp(pattern, flags.replace(/[gy]/g, ""));
|
|
16454
|
-
return re.exec("")?.[0].length === 0;
|
|
16455
|
-
} catch {
|
|
16456
|
-
return false;
|
|
16457
|
-
}
|
|
16458
|
-
}
|
|
16459
|
-
var MAX_PATTERN_LENGTH = 2e3;
|
|
16460
|
-
var RegexMatcher = external_exports.object({
|
|
16461
|
-
type: external_exports.literal("regex"),
|
|
16462
|
-
pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
16463
|
-
flags: external_exports.string().default("gi"),
|
|
16464
|
-
captureGroup: external_exports.number().int().nonnegative().optional()
|
|
16465
|
-
}).refine((v) => isValidRegex(v.pattern, v.flags), {
|
|
16466
|
-
message: "pattern/flags do not form a valid JavaScript regular expression",
|
|
16467
|
-
path: ["pattern"]
|
|
16468
|
-
}).refine((v) => v.captureGroup !== void 0 || !matchesEmptyString(v.pattern, v.flags), {
|
|
16469
|
-
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',
|
|
16470
|
-
path: ["pattern"]
|
|
16471
|
-
});
|
|
16472
|
-
var ValidatorMatcher = external_exports.object({
|
|
16473
|
-
type: external_exports.literal("validator"),
|
|
16474
|
-
name: external_exports.enum(["luhn", "entropy", "ssn-checksum"]),
|
|
16475
|
-
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16476
|
-
});
|
|
16477
|
-
var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher, ValidatorMatcher]).meta({ id: "Matcher" });
|
|
16478
|
-
var AppliesTo = external_exports.object({
|
|
16479
|
-
// Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
|
|
16480
|
-
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
|
|
16481
|
-
}).meta({ id: "AppliesTo" });
|
|
16482
|
-
var PostValidatorRef = external_exports.union([
|
|
16483
|
-
external_exports.string(),
|
|
16484
|
-
external_exports.object({
|
|
16485
|
-
name: external_exports.string(),
|
|
16486
|
-
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16487
|
-
})
|
|
16488
|
-
]).meta({ id: "PostValidatorRef" });
|
|
16489
|
-
var RequiresNearby = external_exports.object({
|
|
16490
|
-
// Each array, when present, must be non-empty and contain non-empty strings —
|
|
16491
|
-
// an empty/blank criterion would either never fire or (for labels) match
|
|
16492
|
-
// everything.
|
|
16493
|
-
categories: external_exports.array(DetectionCategory).min(1).optional(),
|
|
16494
|
-
ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16495
|
-
labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16496
|
-
windowChars: external_exports.number().int().positive().default(160),
|
|
16497
|
-
// Optional confidence bump applied when a gated match is corroborated. Capped
|
|
16498
|
-
// small: it nudges confidence, it does not assert certainty.
|
|
16499
|
-
confidenceBoost: external_exports.number().min(0).max(0.3).optional()
|
|
16500
|
-
}).refine(
|
|
16501
|
-
(v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
|
|
16502
|
-
{ message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
|
|
16503
|
-
).meta({ id: "RequiresNearby" });
|
|
16504
|
-
var RuleFixture = external_exports.object({
|
|
16505
|
-
label: external_exports.string(),
|
|
16506
|
-
text: external_exports.string().max(5e4),
|
|
16507
|
-
shouldMatch: external_exports.boolean(),
|
|
16508
|
-
// Simulated file context for the scan, so fixtures can assert `appliesTo`
|
|
16509
|
-
// gating (e.g. a Python-only pattern must NOT fire in a .ts file).
|
|
16510
|
-
filePath: external_exports.string().optional(),
|
|
16511
|
-
expectedSpans: external_exports.array(external_exports.object({ start: external_exports.number(), end: external_exports.number() })).optional()
|
|
16512
|
-
}).meta({ id: "RuleFixture" });
|
|
16513
|
-
var Rule = external_exports.object({
|
|
16514
|
-
specVersion: external_exports.literal(1),
|
|
16515
|
-
// `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
|
|
16516
|
-
// the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
|
|
16517
|
-
// detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
|
|
16518
|
-
// therefore carries no namespace and is not globally unique across publishers;
|
|
16519
|
-
// never feed one to splitDetectionId. `category` below (per-rule) is the
|
|
16520
|
-
// taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
|
|
16521
|
-
id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
|
|
16522
|
-
name: external_exports.string(),
|
|
16523
|
-
category: DetectionCategory,
|
|
16524
|
-
severity: Severity,
|
|
16525
|
-
matcher: Matcher,
|
|
16526
|
-
appliesTo: AppliesTo.optional(),
|
|
16527
|
-
postValidators: external_exports.array(PostValidatorRef).optional(),
|
|
16528
|
-
requiresNearby: RequiresNearby.optional(),
|
|
16529
|
-
examples: external_exports.array(external_exports.string()).optional()
|
|
16530
|
-
}).meta({ id: "Rule" });
|
|
16531
|
-
var Author = external_exports.object({
|
|
16532
|
-
name: external_exports.string(),
|
|
16533
|
-
email: external_exports.email().optional(),
|
|
16534
|
-
url: external_exports.url().optional()
|
|
16535
|
-
}).meta({ id: "Author" });
|
|
16536
|
-
var PackManifest = external_exports.object({
|
|
16537
|
-
specVersion: external_exports.literal(1),
|
|
16538
|
-
id: external_exports.string(),
|
|
16539
|
-
name: external_exports.string(),
|
|
16540
|
-
version: external_exports.string(),
|
|
16541
|
-
rules: external_exports.array(external_exports.string()),
|
|
16542
|
-
// Optional attribution/provenance — consumed by the rule marketplace.
|
|
16543
|
-
description: external_exports.string().optional(),
|
|
16544
|
-
author: Author.optional(),
|
|
16545
|
-
license: external_exports.string().optional(),
|
|
16546
|
-
sourceUrl: external_exports.url().optional()
|
|
16547
|
-
}).meta({ id: "PackManifest" });
|
|
16548
|
-
|
|
16549
|
-
// ../../packages/schema/src/zod/policy.ts
|
|
16550
|
-
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
16551
|
-
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
16552
|
-
var Policy = external_exports.object({
|
|
16553
|
-
id: external_exports.guid(),
|
|
16554
|
-
scope: PolicyScope,
|
|
16555
|
-
target: PolicyTarget,
|
|
16556
|
-
action: ActionTaken,
|
|
16557
|
-
enabled: external_exports.boolean().default(true),
|
|
16558
|
-
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
16559
|
-
// Display name — optional so older policy rows without name still parse.
|
|
16560
|
-
// Added for the findings API (policy.name column migration).
|
|
16561
|
-
name: external_exports.string().optional()
|
|
16562
|
-
}).meta({ id: "Policy" });
|
|
16563
|
-
var PolicyBundle = external_exports.object({
|
|
16564
|
-
version: external_exports.string(),
|
|
16565
|
-
policies: external_exports.array(Policy),
|
|
16566
|
-
// Rules from the installed marketplace packs (snapshotted by the
|
|
16567
|
-
// control plane). The plugin registers these in addition to its bundled
|
|
16568
|
-
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
16569
|
-
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
16570
|
-
rules: external_exports.array(Rule).optional(),
|
|
16571
|
-
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
16572
|
-
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
16573
|
-
// after reading the user's installed snapshot (installed_packs, enabled
|
|
16574
|
-
// packs only), which is how detection updates stay manual: new bundled
|
|
16575
|
-
// rules run only after the user applies the pack update. Absent/false keeps
|
|
16576
|
-
// the historical composition (bundled packs + rules) — older caches.
|
|
16577
|
-
rulesComplete: external_exports.boolean().optional(),
|
|
16578
|
-
// Active detection exceptions, evaluation subset only (see
|
|
16579
|
-
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
16580
|
-
// on-disk caches — that omit the field still parse; consumers read
|
|
16581
|
-
// `bundle.exceptions ?? []`.
|
|
16582
|
-
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
16583
|
-
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
16584
|
-
// from a versioned installed pack. Optional so older backends — and older
|
|
16585
|
-
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
16586
|
-
// the rule's own spec version. NOT the bundle version above — see
|
|
16587
|
-
// installedRuleset's ruleVersions for the source of truth.
|
|
16588
|
-
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
16589
|
-
customKeywords: external_exports.array(external_exports.string()),
|
|
16590
|
-
fetchedAt: external_exports.iso.datetime()
|
|
16591
|
-
}).meta({ id: "PolicyBundle" });
|
|
16592
|
-
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
16593
|
-
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
16594
|
-
var CATEGORY_PEAK_SEVERITY = {
|
|
16595
|
-
secret: "critical",
|
|
16596
|
-
financial: "critical",
|
|
16597
|
-
// core-financial/credit-card
|
|
16598
|
-
code_flaw: "critical",
|
|
16599
|
-
pii: "high",
|
|
16600
|
-
phi: "high",
|
|
16601
|
-
custom: "high",
|
|
16602
|
-
// user-defined; conservative
|
|
16603
|
-
code_context: "low",
|
|
16604
|
-
config: "low"
|
|
16605
|
-
// observe-only; floors to monitor regardless
|
|
16606
|
-
};
|
|
16607
|
-
function severityFloorPolicy(category) {
|
|
16608
|
-
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
16609
|
-
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
16610
|
-
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
16611
|
-
}
|
|
16612
|
-
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
16613
|
-
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "block"];
|
|
16614
|
-
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
16615
|
-
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
16616
|
-
var BUILTIN_POLICY_SPECS = {
|
|
16617
|
-
monitor: {
|
|
16618
|
-
name: "Monitor",
|
|
16619
|
-
action: "log",
|
|
16620
|
-
description: "Log every match for audit. The request is allowed through untouched."
|
|
16621
|
-
},
|
|
16622
|
-
warn: {
|
|
16623
|
-
name: "Warn",
|
|
16624
|
-
action: "warn",
|
|
16625
|
-
description: "Allow the request, but warn the user inline before it is sent."
|
|
16626
|
-
},
|
|
16627
|
-
redact: {
|
|
16628
|
-
name: "Redact",
|
|
16629
|
-
action: "redact",
|
|
16630
|
-
description: "Automatically strip the matched value from the request, then continue."
|
|
16631
|
-
},
|
|
16632
|
-
block: {
|
|
16633
|
-
name: "Block",
|
|
16634
|
-
action: "block",
|
|
16635
|
-
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
16636
|
-
}
|
|
16637
|
-
};
|
|
16638
|
-
function builtinPolicyToAction(id) {
|
|
16639
|
-
return BUILTIN_POLICY_SPECS[id].action;
|
|
16640
|
-
}
|
|
16641
|
-
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
16642
|
-
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
16643
|
-
);
|
|
16644
|
-
var BUILTIN_POLICIES = Object.fromEntries(
|
|
16645
|
-
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
16646
|
-
);
|
|
16647
|
-
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
16648
|
-
function policyIdToAction(policyId) {
|
|
16649
|
-
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
16650
|
-
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
16651
|
-
return BUILTIN_POLICIES[id].action;
|
|
16652
|
-
}
|
|
16653
|
-
var UsedByItem = external_exports.object({
|
|
16654
|
-
id: external_exports.string(),
|
|
16655
|
-
name: external_exports.string(),
|
|
16656
|
-
ruleCount: external_exports.number().int().nonnegative(),
|
|
16657
|
-
enabled: external_exports.boolean()
|
|
16658
|
-
}).meta({ id: "UsedByItem" });
|
|
16659
|
-
var PolicyListItem = external_exports.object({
|
|
16660
|
-
id: external_exports.string(),
|
|
16661
|
-
kind: PolicyKind,
|
|
16662
|
-
name: external_exports.string(),
|
|
16663
|
-
enabled: external_exports.boolean(),
|
|
16664
|
-
usedByCount: external_exports.number().int().nonnegative()
|
|
16665
|
-
}).meta({ id: "PolicyListItem" });
|
|
16666
|
-
var PolicyDetail = external_exports.object({
|
|
16667
|
-
specVersion: external_exports.literal(1),
|
|
16668
|
-
id: external_exports.string(),
|
|
16669
|
-
kind: PolicyKind,
|
|
16670
|
-
name: external_exports.string(),
|
|
16671
|
-
enabled: external_exports.boolean(),
|
|
16672
|
-
description: external_exports.string(),
|
|
16673
|
-
usedBy: external_exports.array(UsedByItem)
|
|
16674
|
-
}).meta({ id: "PolicyDetail" });
|
|
16675
|
-
var PolicyStatsResponse = external_exports.object({
|
|
16676
|
-
policies: external_exports.number().int().nonnegative(),
|
|
16677
|
-
builtin: external_exports.number().int().nonnegative(),
|
|
16678
|
-
custom: external_exports.number().int().nonnegative(),
|
|
16679
|
-
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
16680
|
-
}).meta({ id: "PolicyStatsResponse" });
|
|
16681
|
-
|
|
16682
|
-
// ../../packages/schema/src/zod/api.ts
|
|
16683
|
-
var LIST_QUERY_MAX_LIMIT = 200;
|
|
16684
|
-
var ListEventsQuery = external_exports.object({
|
|
16685
|
-
cursor: external_exports.string().optional(),
|
|
16686
|
-
limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
|
|
16687
|
-
sourceTool: external_exports.string().optional(),
|
|
16688
|
-
kind: external_exports.string().optional(),
|
|
16689
|
-
from: external_exports.iso.datetime().optional(),
|
|
16690
|
-
to: external_exports.iso.datetime().optional()
|
|
16691
|
-
});
|
|
16692
|
-
var ListEventsResponse = external_exports.object({
|
|
16693
|
-
items: external_exports.array(Event),
|
|
16694
|
-
nextCursor: external_exports.string().nullable()
|
|
16695
|
-
}).meta({ id: "ListEventsResponse" });
|
|
16696
|
-
var IngestResponse = external_exports.object({
|
|
16697
|
-
accepted: external_exports.number().int().nonnegative(),
|
|
16698
|
-
duplicates: external_exports.number().int().nonnegative()
|
|
16699
|
-
}).meta({ id: "IngestResponse" });
|
|
16700
|
-
var ListFindingsQuery = external_exports.object({
|
|
16701
|
-
cursor: external_exports.string().optional(),
|
|
16702
|
-
limit: external_exports.coerce.number().int().min(1).max(LIST_QUERY_MAX_LIMIT).default(50),
|
|
16703
|
-
severity: external_exports.string().optional(),
|
|
16704
|
-
category: external_exports.string().optional(),
|
|
16705
|
-
eventId: external_exports.guid().optional()
|
|
16706
|
-
});
|
|
16707
|
-
var ListFindingsResponse = external_exports.object({
|
|
16708
|
-
items: external_exports.array(Finding),
|
|
16709
|
-
nextCursor: external_exports.string().nullable()
|
|
16710
|
-
}).meta({ id: "ListFindingsResponse" });
|
|
16711
|
-
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
16712
|
-
var CreatePolicyRequest = Policy.omit({ id: true }).meta({
|
|
16713
|
-
id: "CreatePolicyRequest"
|
|
16714
|
-
});
|
|
16715
|
-
var UpdatePolicyRequest = Policy.partial().required({ id: true }).meta({ id: "UpdatePolicyRequest" });
|
|
16716
|
-
var RecordAuditEventResponse = external_exports.object({ accepted: external_exports.boolean() }).meta({ id: "RecordAuditEventResponse" });
|
|
16717
|
-
var ErrorResponse = external_exports.object({
|
|
16718
|
-
error: external_exports.object({
|
|
16719
|
-
code: external_exports.string(),
|
|
16720
|
-
message: external_exports.string(),
|
|
16721
|
-
details: external_exports.unknown().optional()
|
|
16722
|
-
})
|
|
16723
|
-
}).meta({ id: "ErrorResponse" });
|
|
16724
|
-
|
|
16725
|
-
// ../../packages/schema/src/zod/config-inventory.ts
|
|
16726
|
-
var ConfigScope = external_exports.enum(["user", "project", "local", "plugin"]);
|
|
16727
|
-
var SkillScanEntry = external_exports.object({
|
|
16728
|
-
name: external_exports.string().min(1),
|
|
16729
|
-
// The identity source: a marketplace repo for plugin skills (e.g.
|
|
16730
|
-
// 'anthropics/skills'), 'local' for personal ~/.claude/skills, or
|
|
16731
|
-
// 'project:<repo-identity>' for checked-in project skills. The surrogate keeps
|
|
16732
|
-
// a personal skill named 'pdf' from colliding with the marketplace 'pdf'.
|
|
16733
|
-
source: external_exports.string().min(1),
|
|
16734
|
-
scope: ConfigScope,
|
|
16735
|
-
pluginName: external_exports.string().optional(),
|
|
16736
|
-
// Volatile — rides the attribute bag, never the identity hash.
|
|
16737
|
-
version: external_exports.string().optional(),
|
|
16738
|
-
description: external_exports.string().optional(),
|
|
16739
|
-
// Skill directory mtime (ISO) — the "updated Nd ago" freshness signal.
|
|
16740
|
-
updatedAt: external_exports.iso.datetime().optional(),
|
|
16741
|
-
// Filesystem path — the promoted inventory `location` column.
|
|
16742
|
-
location: external_exports.string().optional()
|
|
16743
|
-
});
|
|
16744
|
-
var HookScanEntry = external_exports.object({
|
|
16745
|
-
// Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
|
|
16746
|
-
// set is harness-defined and grows without a schema change.
|
|
16747
|
-
event: external_exports.string().min(1),
|
|
16748
|
-
// The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
|
|
16749
|
-
matcher: external_exports.string().optional(),
|
|
16750
|
-
command: external_exports.string().min(1),
|
|
16751
|
-
timeout: external_exports.number().optional(),
|
|
16752
|
-
scope: ConfigScope,
|
|
16753
|
-
pluginName: external_exports.string().optional(),
|
|
16754
|
-
// The settings file / hooks.json the entry came from.
|
|
16755
|
-
location: external_exports.string().optional()
|
|
16118
|
+
var HookScanEntry = external_exports.object({
|
|
16119
|
+
// Hook event name (PreToolUse, PostToolUse, …). Open string, not an enum: the
|
|
16120
|
+
// set is harness-defined and grows without a schema change.
|
|
16121
|
+
event: external_exports.string().min(1),
|
|
16122
|
+
// The tool matcher ('Bash', 'Edit|Write', …). Absent = matches all tools.
|
|
16123
|
+
matcher: external_exports.string().optional(),
|
|
16124
|
+
command: external_exports.string().min(1),
|
|
16125
|
+
timeout: external_exports.number().optional(),
|
|
16126
|
+
scope: ConfigScope,
|
|
16127
|
+
pluginName: external_exports.string().optional(),
|
|
16128
|
+
// The settings file / hooks.json the entry came from.
|
|
16129
|
+
location: external_exports.string().optional()
|
|
16756
16130
|
});
|
|
16757
16131
|
var McpServerScanEntry = external_exports.object({
|
|
16758
16132
|
// The server's config key ("github", "filesystem", …) — identity, with the
|
|
@@ -16827,12 +16201,170 @@ var ConfigScanRecord = external_exports.object({
|
|
|
16827
16201
|
definitions: external_exports.array(InspectionDefinitionInput).optional(),
|
|
16828
16202
|
findings: external_exports.array(ConfigPostureFindingInput).optional()
|
|
16829
16203
|
});
|
|
16830
|
-
|
|
16831
|
-
// ../../packages/schema/src/zod/registry.ts
|
|
16832
|
-
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16833
|
-
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16834
|
-
var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
|
|
16835
|
-
var PublisherKind = external_exports.enum(["labs", "user", "org"]);
|
|
16204
|
+
|
|
16205
|
+
// ../../packages/schema/src/zod/registry.ts
|
|
16206
|
+
var Namespace = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16207
|
+
var PackId = external_exports.string().regex(/^[a-z][a-z0-9-]*$/);
|
|
16208
|
+
var SemVer = external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z-.]+)?$/);
|
|
16209
|
+
var PublisherKind = external_exports.enum(["labs", "user", "org"]);
|
|
16210
|
+
|
|
16211
|
+
// ../../packages/schema/src/zod/rule.ts
|
|
16212
|
+
var MatcherType = external_exports.enum(["keyword", "regex"]).meta({ id: "MatcherType" });
|
|
16213
|
+
var RuleProbeVerdict = external_exports.enum(["safe", "quarantined"]).meta({ id: "RuleProbeVerdict" });
|
|
16214
|
+
var KeywordMatcher = external_exports.strictObject({
|
|
16215
|
+
type: external_exports.literal("keyword"),
|
|
16216
|
+
// An empty keyword matches at every position, yielding one zero-length span
|
|
16217
|
+
// per character. Rejected here because a keyword that matches everything is
|
|
16218
|
+
// never intentional.
|
|
16219
|
+
keywords: external_exports.array(external_exports.string().min(1)).min(1),
|
|
16220
|
+
caseSensitive: external_exports.boolean().default(false)
|
|
16221
|
+
});
|
|
16222
|
+
function isValidRegex(pattern, flags) {
|
|
16223
|
+
try {
|
|
16224
|
+
new RegExp(pattern, flags);
|
|
16225
|
+
return true;
|
|
16226
|
+
} catch {
|
|
16227
|
+
return false;
|
|
16228
|
+
}
|
|
16229
|
+
}
|
|
16230
|
+
function probeFlags(flags) {
|
|
16231
|
+
return flags.replace(/[gy]/g, "");
|
|
16232
|
+
}
|
|
16233
|
+
function matchesEmptyString(pattern, flags) {
|
|
16234
|
+
try {
|
|
16235
|
+
const re = new RegExp(pattern, probeFlags(flags));
|
|
16236
|
+
return re.exec("")?.[0].length === 0;
|
|
16237
|
+
} catch {
|
|
16238
|
+
return false;
|
|
16239
|
+
}
|
|
16240
|
+
}
|
|
16241
|
+
function spansWholeMatch(captureGroup) {
|
|
16242
|
+
return captureGroup === void 0 || captureGroup === 0;
|
|
16243
|
+
}
|
|
16244
|
+
function captureGroupCount(pattern, flags) {
|
|
16245
|
+
try {
|
|
16246
|
+
const probe = new RegExp(`${pattern}|`, probeFlags(flags));
|
|
16247
|
+
const result = probe.exec("");
|
|
16248
|
+
return result ? result.length - 1 : void 0;
|
|
16249
|
+
} catch {
|
|
16250
|
+
return void 0;
|
|
16251
|
+
}
|
|
16252
|
+
}
|
|
16253
|
+
var MAX_PATTERN_LENGTH = 2e3;
|
|
16254
|
+
var RegexMatcher = external_exports.strictObject({
|
|
16255
|
+
type: external_exports.literal("regex"),
|
|
16256
|
+
pattern: external_exports.string().min(1).max(MAX_PATTERN_LENGTH),
|
|
16257
|
+
flags: external_exports.string().default("gi"),
|
|
16258
|
+
captureGroup: external_exports.number().int().nonnegative().optional()
|
|
16259
|
+
}).refine((v) => isValidRegex(v.pattern, v.flags), {
|
|
16260
|
+
message: "pattern/flags do not form a valid JavaScript regular expression",
|
|
16261
|
+
path: ["pattern"]
|
|
16262
|
+
}).refine((v) => !spansWholeMatch(v.captureGroup) || !matchesEmptyString(v.pattern, v.flags), {
|
|
16263
|
+
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',
|
|
16264
|
+
path: ["pattern"]
|
|
16265
|
+
}).superRefine((v, ctx) => {
|
|
16266
|
+
if (v.captureGroup === void 0) return;
|
|
16267
|
+
const groups = captureGroupCount(v.pattern, v.flags);
|
|
16268
|
+
if (groups === void 0 || v.captureGroup <= groups) return;
|
|
16269
|
+
ctx.addIssue({
|
|
16270
|
+
code: "custom",
|
|
16271
|
+
path: ["captureGroup"],
|
|
16272
|
+
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.`
|
|
16273
|
+
});
|
|
16274
|
+
});
|
|
16275
|
+
var Matcher = external_exports.discriminatedUnion("type", [KeywordMatcher, RegexMatcher]).meta({ id: "Matcher" });
|
|
16276
|
+
var MATCHER_TYPES = MatcherType.options;
|
|
16277
|
+
var AppliesTo = external_exports.strictObject({
|
|
16278
|
+
// Dot-prefixed, e.g. ".py" — matches the scanner's SOURCE_EXTENSIONS shape.
|
|
16279
|
+
extensions: external_exports.array(external_exports.string().regex(/^\.[A-Za-z0-9]+$/)).min(1)
|
|
16280
|
+
}).meta({ id: "AppliesTo" });
|
|
16281
|
+
var PostValidatorName = external_exports.enum(["entropy", "luhn"]).meta({ id: "PostValidatorName" });
|
|
16282
|
+
var PostValidatorRef = external_exports.union(
|
|
16283
|
+
[
|
|
16284
|
+
PostValidatorName,
|
|
16285
|
+
external_exports.strictObject({
|
|
16286
|
+
name: PostValidatorName,
|
|
16287
|
+
config: external_exports.record(external_exports.string(), external_exports.unknown()).optional()
|
|
16288
|
+
})
|
|
16289
|
+
],
|
|
16290
|
+
{
|
|
16291
|
+
// A union reports one collapsed issue for every way its arms can fail, so
|
|
16292
|
+
// this has to describe the whole shape rather than just the name — it is
|
|
16293
|
+
// what an author sees for a misspelled name AND for a stray key in the
|
|
16294
|
+
// object form. The names come from the enum so the message cannot go
|
|
16295
|
+
// stale. Without it Zod says only "Invalid input", which is precisely the
|
|
16296
|
+
// no-feedback outcome this schema exists to remove.
|
|
16297
|
+
error: () => `not a valid post-validator: use a bare name (${PostValidatorName.options.map((name) => JSON.stringify(name)).join(
|
|
16298
|
+
" or "
|
|
16299
|
+
)}) or { "name": ..., "config": { ... } }. An unrecognized name would be a false-positive guard that never runs.`
|
|
16300
|
+
}
|
|
16301
|
+
).meta({ id: "PostValidatorRef" });
|
|
16302
|
+
var RequiresNearby = external_exports.strictObject({
|
|
16303
|
+
// Each array, when present, must be non-empty and contain non-empty strings —
|
|
16304
|
+
// an empty/blank criterion would either never fire or (for labels) match
|
|
16305
|
+
// everything.
|
|
16306
|
+
categories: external_exports.array(DetectionCategory).min(1).optional(),
|
|
16307
|
+
ruleIds: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16308
|
+
labels: external_exports.array(external_exports.string().min(1)).min(1).optional(),
|
|
16309
|
+
windowChars: external_exports.number().int().positive().default(160),
|
|
16310
|
+
// Optional confidence bump applied when a gated match is corroborated. Capped
|
|
16311
|
+
// small: it nudges confidence, it does not assert certainty.
|
|
16312
|
+
confidenceBoost: external_exports.number().min(0).max(0.3).optional()
|
|
16313
|
+
}).refine(
|
|
16314
|
+
(v) => (v.categories?.length ?? 0) + (v.ruleIds?.length ?? 0) + (v.labels?.length ?? 0) > 0,
|
|
16315
|
+
{ message: "requiresNearby needs at least one of categories, ruleIds, or labels" }
|
|
16316
|
+
).meta({ id: "RequiresNearby" });
|
|
16317
|
+
var RuleFixture = external_exports.strictObject({
|
|
16318
|
+
label: external_exports.string(),
|
|
16319
|
+
text: external_exports.string().max(5e4),
|
|
16320
|
+
shouldMatch: external_exports.boolean(),
|
|
16321
|
+
// Simulated file context for the scan, so fixtures can assert `appliesTo`
|
|
16322
|
+
// gating (e.g. a Python-only pattern must NOT fire in a .ts file).
|
|
16323
|
+
filePath: external_exports.string().optional(),
|
|
16324
|
+
expectedSpans: external_exports.array(external_exports.strictObject({ start: external_exports.number(), end: external_exports.number() })).optional()
|
|
16325
|
+
}).meta({ id: "RuleFixture" });
|
|
16326
|
+
var Rule = external_exports.strictObject({
|
|
16327
|
+
// A pinned literal over a STRICT object, and the two together decide how this
|
|
16328
|
+
// format may grow. A rule carrying a key not listed below is refused with
|
|
16329
|
+
// `unrecognized_keys`; a rule declaring `specVersion: 2` is refused with
|
|
16330
|
+
// `invalid_value`. So the only additive path is adding an OPTIONAL field here
|
|
16331
|
+
// — that keeps every rule authored before it valid — and a rule author has no
|
|
16332
|
+
// way to introduce a field of their own or to opt into a later version.
|
|
16333
|
+
// Widening the format means changing this literal and every consumer of it.
|
|
16334
|
+
specVersion: external_exports.literal(1),
|
|
16335
|
+
// `packId/ruleName` (e.g. `secrets/aws-access-key`). NOTE the first segment is
|
|
16336
|
+
// the PACK id, NOT a namespace — this is a DIFFERENT slug space from a
|
|
16337
|
+
// detection id (`namespace/packId`, decoded by splitDetectionId). A Rule.id
|
|
16338
|
+
// therefore carries no namespace and is not globally unique across publishers;
|
|
16339
|
+
// never feed one to splitDetectionId. `category` below (per-rule) is the
|
|
16340
|
+
// taxonomy axis; the pack's enforcement policy is installed_packs.policy_id.
|
|
16341
|
+
id: external_exports.string().regex(/^[a-z][a-z0-9-]*\/[a-z][a-z0-9-]*$/),
|
|
16342
|
+
name: external_exports.string(),
|
|
16343
|
+
category: DetectionCategory,
|
|
16344
|
+
severity: Severity,
|
|
16345
|
+
matcher: Matcher,
|
|
16346
|
+
appliesTo: AppliesTo.optional(),
|
|
16347
|
+
postValidators: external_exports.array(PostValidatorRef).optional(),
|
|
16348
|
+
requiresNearby: RequiresNearby.optional(),
|
|
16349
|
+
examples: external_exports.array(external_exports.string()).optional()
|
|
16350
|
+
}).meta({ id: "Rule" });
|
|
16351
|
+
var Author = external_exports.object({
|
|
16352
|
+
name: external_exports.string(),
|
|
16353
|
+
email: external_exports.email().optional(),
|
|
16354
|
+
url: external_exports.url().optional()
|
|
16355
|
+
}).meta({ id: "Author" });
|
|
16356
|
+
var PackManifest = external_exports.object({
|
|
16357
|
+
specVersion: external_exports.literal(1),
|
|
16358
|
+
id: external_exports.string(),
|
|
16359
|
+
name: external_exports.string(),
|
|
16360
|
+
version: external_exports.string(),
|
|
16361
|
+
rules: external_exports.array(external_exports.string()),
|
|
16362
|
+
// Optional attribution/provenance — consumed by the rule marketplace.
|
|
16363
|
+
description: external_exports.string().optional(),
|
|
16364
|
+
author: Author.optional(),
|
|
16365
|
+
license: external_exports.string().optional(),
|
|
16366
|
+
sourceUrl: external_exports.url().optional()
|
|
16367
|
+
}).meta({ id: "PackManifest" });
|
|
16836
16368
|
|
|
16837
16369
|
// ../../packages/schema/src/zod/detection.ts
|
|
16838
16370
|
var OriginEnum = external_exports.enum(["library"]).meta({ id: "OriginEnum" });
|
|
@@ -17032,6 +16564,231 @@ function buildDetectionsList(summaries, query) {
|
|
|
17032
16564
|
return { counts, items: filtered.map(summaryToDetectionListItem) };
|
|
17033
16565
|
}
|
|
17034
16566
|
|
|
16567
|
+
// ../../packages/schema/src/zod/inventory.ts
|
|
16568
|
+
var AssetType = external_exports.enum(["project", "skill", "mcp", "hook", "config"]).meta({ id: "AssetType" });
|
|
16569
|
+
var AccessLevel = external_exports.enum(["open", "approved", "blocked"]).meta({ id: "AccessLevel" });
|
|
16570
|
+
var Origin = external_exports.enum(["source", "public-dep", "vendored", "config", "data", "docs", "generated"]).meta({ id: "Origin" });
|
|
16571
|
+
var TrustLevel = external_exports.enum(["known-good", "risky", "unapproved"]).meta({ id: "TrustLevel" });
|
|
16572
|
+
var Flag = external_exports.enum(["update", "stale", "conflict", "unknown", "change", "untracked", "risk", "findings"]).meta({ id: "Flag" });
|
|
16573
|
+
var Visibility = external_exports.enum(["public", "private"]).meta({ id: "Visibility" });
|
|
16574
|
+
var HarnessEventKind = external_exports.enum(["block", "redact", "warn"]).meta({ id: "HarnessEventKind" });
|
|
16575
|
+
var HarnessId = Harness.extract(["ClaudeCode", "Cursor", "Codex", "Antigravity"]).meta({
|
|
16576
|
+
id: "HarnessId"
|
|
16577
|
+
});
|
|
16578
|
+
var AccessCounts = external_exports.object({
|
|
16579
|
+
open: external_exports.number().int().nonnegative(),
|
|
16580
|
+
approved: external_exports.number().int().nonnegative(),
|
|
16581
|
+
blocked: external_exports.number().int().nonnegative(),
|
|
16582
|
+
total: external_exports.number().int().nonnegative()
|
|
16583
|
+
}).meta({ id: "AccessCounts" });
|
|
16584
|
+
var AssetSummary = external_exports.object({
|
|
16585
|
+
id: external_exports.string(),
|
|
16586
|
+
type: AssetType,
|
|
16587
|
+
name: external_exports.string(),
|
|
16588
|
+
sub: external_exports.string(),
|
|
16589
|
+
flags: external_exports.array(Flag),
|
|
16590
|
+
/** MCP servers only — omitted for all other types. */
|
|
16591
|
+
trust: TrustLevel.optional()
|
|
16592
|
+
}).meta({ id: "AssetSummary" });
|
|
16593
|
+
var ProjectSummary = external_exports.object({
|
|
16594
|
+
id: external_exports.string(),
|
|
16595
|
+
name: external_exports.string(),
|
|
16596
|
+
repo: external_exports.string(),
|
|
16597
|
+
visibility: Visibility,
|
|
16598
|
+
language: external_exports.string(),
|
|
16599
|
+
policyDefault: AccessLevel,
|
|
16600
|
+
updatedAt: external_exports.iso.datetime(),
|
|
16601
|
+
accessCounts: AccessCounts,
|
|
16602
|
+
findingsCount: external_exports.number().int().nonnegative()
|
|
16603
|
+
}).meta({ id: "ProjectSummary" });
|
|
16604
|
+
var HarnessCategory = external_exports.object({
|
|
16605
|
+
/** One of config/skill/mcp/hook — never project (enforced at service layer). */
|
|
16606
|
+
type: AssetType,
|
|
16607
|
+
assets: external_exports.array(AssetSummary)
|
|
16608
|
+
});
|
|
16609
|
+
var HarnessSummary = external_exports.object({
|
|
16610
|
+
id: HarnessId,
|
|
16611
|
+
label: external_exports.string(),
|
|
16612
|
+
kind: external_exports.string(),
|
|
16613
|
+
version: external_exports.string(),
|
|
16614
|
+
sessions: external_exports.number().int().nonnegative(),
|
|
16615
|
+
assetCount: external_exports.number().int().nonnegative(),
|
|
16616
|
+
flagCount: external_exports.number().int().nonnegative(),
|
|
16617
|
+
projects: external_exports.array(ProjectSummary),
|
|
16618
|
+
categories: external_exports.array(HarnessCategory)
|
|
16619
|
+
}).meta({ id: "HarnessSummary" });
|
|
16620
|
+
var ListHarnessesResponse = external_exports.object({ items: external_exports.array(HarnessSummary) }).meta({ id: "ListHarnessesResponse" });
|
|
16621
|
+
var AssetGroup = external_exports.object({
|
|
16622
|
+
/** Group key — never project (enforced at service layer). */
|
|
16623
|
+
type: AssetType,
|
|
16624
|
+
total: external_exports.number().int().nonnegative(),
|
|
16625
|
+
/**
|
|
16626
|
+
* MCP group only — omitted for all other types.
|
|
16627
|
+
* Partial: only TrustLevel keys with non-zero counts are included.
|
|
16628
|
+
* Strict: unknown keys are rejected — only TrustLevel values are valid keys.
|
|
16629
|
+
*/
|
|
16630
|
+
trustRollup: external_exports.object({
|
|
16631
|
+
"known-good": external_exports.number().int().nonnegative(),
|
|
16632
|
+
risky: external_exports.number().int().nonnegative(),
|
|
16633
|
+
unapproved: external_exports.number().int().nonnegative()
|
|
16634
|
+
}).partial().strict().optional(),
|
|
16635
|
+
/**
|
|
16636
|
+
* Partial: only Flag keys with non-zero counts are included.
|
|
16637
|
+
* Strict: unknown keys are rejected — only Flag values are valid keys.
|
|
16638
|
+
*/
|
|
16639
|
+
flagRollup: external_exports.object({
|
|
16640
|
+
update: external_exports.number().int().nonnegative(),
|
|
16641
|
+
stale: external_exports.number().int().nonnegative(),
|
|
16642
|
+
conflict: external_exports.number().int().nonnegative(),
|
|
16643
|
+
unknown: external_exports.number().int().nonnegative(),
|
|
16644
|
+
change: external_exports.number().int().nonnegative(),
|
|
16645
|
+
untracked: external_exports.number().int().nonnegative(),
|
|
16646
|
+
risk: external_exports.number().int().nonnegative(),
|
|
16647
|
+
findings: external_exports.number().int().nonnegative()
|
|
16648
|
+
}).partial().strict(),
|
|
16649
|
+
items: external_exports.array(AssetSummary)
|
|
16650
|
+
}).meta({ id: "AssetGroup" });
|
|
16651
|
+
var ListAssetsResponse = external_exports.object({ groups: external_exports.array(AssetGroup) }).meta({ id: "ListAssetsResponse" });
|
|
16652
|
+
var McpTool = external_exports.object({
|
|
16653
|
+
name: external_exports.string(),
|
|
16654
|
+
signature: external_exports.string(),
|
|
16655
|
+
description: external_exports.string(),
|
|
16656
|
+
write: external_exports.boolean(),
|
|
16657
|
+
/** Non-null string when tool is dangerous / blocked; null otherwise. */
|
|
16658
|
+
risk: external_exports.string().nullable()
|
|
16659
|
+
}).meta({ id: "McpTool" });
|
|
16660
|
+
var AssetFindingRef = external_exports.object({
|
|
16661
|
+
id: external_exports.string(),
|
|
16662
|
+
title: external_exports.string(),
|
|
16663
|
+
note: external_exports.string()
|
|
16664
|
+
});
|
|
16665
|
+
var AssetDetail = AssetSummary.extend({
|
|
16666
|
+
/** string | null — null when no description is available. */
|
|
16667
|
+
description: external_exports.string().nullable(),
|
|
16668
|
+
/** trustLevel | null — null for non-MCP assets. */
|
|
16669
|
+
trust: TrustLevel.nullable(),
|
|
16670
|
+
/** Type-specific raw key/values — FE renders the grid. */
|
|
16671
|
+
meta: external_exports.record(external_exports.string(), external_exports.unknown()),
|
|
16672
|
+
/** always present — object when there is an active finding, null when absent. */
|
|
16673
|
+
finding: AssetFindingRef.nullable(),
|
|
16674
|
+
/** MCP exposed-tools list — omitted for non-mcp. */
|
|
16675
|
+
tools: external_exports.array(McpTool).optional()
|
|
16676
|
+
}).meta({ id: "AssetDetail" });
|
|
16677
|
+
var ListProjectsResponse = external_exports.object({ items: external_exports.array(ProjectSummary) }).meta({ id: "ListProjectsResponse" });
|
|
16678
|
+
var InventoryStats = external_exports.object({
|
|
16679
|
+
/** Total assets/projects with ≥1 flag or finding (drives the attention header chip). */
|
|
16680
|
+
attention: external_exports.number().int().nonnegative(),
|
|
16681
|
+
byType: external_exports.object({
|
|
16682
|
+
project: external_exports.number().int().nonnegative(),
|
|
16683
|
+
skill: external_exports.number().int().nonnegative(),
|
|
16684
|
+
mcp: external_exports.number().int().nonnegative(),
|
|
16685
|
+
hook: external_exports.number().int().nonnegative(),
|
|
16686
|
+
config: external_exports.number().int().nonnegative()
|
|
16687
|
+
}),
|
|
16688
|
+
harnesses: external_exports.number().int().nonnegative(),
|
|
16689
|
+
mcpTrust: external_exports.object({
|
|
16690
|
+
"known-good": external_exports.number().int().nonnegative(),
|
|
16691
|
+
risky: external_exports.number().int().nonnegative(),
|
|
16692
|
+
unapproved: external_exports.number().int().nonnegative()
|
|
16693
|
+
})
|
|
16694
|
+
}).meta({ id: "InventoryStats" });
|
|
16695
|
+
var FileSummary = external_exports.object({
|
|
16696
|
+
path: external_exports.string(),
|
|
16697
|
+
name: external_exports.string(),
|
|
16698
|
+
origin: Origin,
|
|
16699
|
+
/** Effective access (override applied). */
|
|
16700
|
+
access: AccessLevel,
|
|
16701
|
+
/** True when a file_access_override differs from the computed default. */
|
|
16702
|
+
isCustom: external_exports.boolean(),
|
|
16703
|
+
findings: external_exports.number().int().nonnegative(),
|
|
16704
|
+
/** When the file was auto-blocked by a detection; null when not blocked. */
|
|
16705
|
+
blockedAt: external_exports.iso.datetime().nullable().optional(),
|
|
16706
|
+
/** Why the file was blocked; null when absent. */
|
|
16707
|
+
note: external_exports.string().nullable().optional()
|
|
16708
|
+
}).meta({ id: "FileSummary" });
|
|
16709
|
+
var FolderSummary = external_exports.object({
|
|
16710
|
+
name: external_exports.string(),
|
|
16711
|
+
path: external_exports.string(),
|
|
16712
|
+
/** Rollup of effective access across all descendants. */
|
|
16713
|
+
accessCounts: AccessCounts
|
|
16714
|
+
}).meta({ id: "FolderSummary" });
|
|
16715
|
+
var ProjectTreeResponse = external_exports.object({
|
|
16716
|
+
project: external_exports.object({
|
|
16717
|
+
id: external_exports.string(),
|
|
16718
|
+
repo: external_exports.string(),
|
|
16719
|
+
visibility: Visibility
|
|
16720
|
+
}),
|
|
16721
|
+
path: external_exports.string(),
|
|
16722
|
+
/** Browse mode: one-level folders at the current path. Omitted in search mode. */
|
|
16723
|
+
folders: external_exports.array(FolderSummary).optional(),
|
|
16724
|
+
files: external_exports.array(FileSummary)
|
|
16725
|
+
}).meta({ id: "ProjectTreeResponse" });
|
|
16726
|
+
var FileDetail = FileSummary.extend({
|
|
16727
|
+
project: external_exports.object({
|
|
16728
|
+
repo: external_exports.string(),
|
|
16729
|
+
visibility: Visibility,
|
|
16730
|
+
language: external_exports.string(),
|
|
16731
|
+
policyDefault: AccessLevel,
|
|
16732
|
+
updatedAt: external_exports.iso.datetime()
|
|
16733
|
+
}),
|
|
16734
|
+
findingsRefs: external_exports.array(external_exports.object({ id: external_exports.string(), title: external_exports.string() }))
|
|
16735
|
+
}).meta({ id: "FileDetail" });
|
|
16736
|
+
var SetFileAccessBody = external_exports.object({
|
|
16737
|
+
path: external_exports.string(),
|
|
16738
|
+
access: AccessLevel
|
|
16739
|
+
}).meta({ id: "SetFileAccessBody" });
|
|
16740
|
+
var SetFileAccessResponse = external_exports.object({
|
|
16741
|
+
file: FileSummary,
|
|
16742
|
+
accessCounts: AccessCounts
|
|
16743
|
+
}).meta({ id: "SetFileAccessResponse" });
|
|
16744
|
+
var SetMcpTrustBody = external_exports.object({ trust: TrustLevel }).meta({ id: "SetMcpTrustBody" });
|
|
16745
|
+
var HarnessEventItem = external_exports.object({
|
|
16746
|
+
kind: HarnessEventKind,
|
|
16747
|
+
title: external_exports.string(),
|
|
16748
|
+
detail: external_exports.string(),
|
|
16749
|
+
occurredAt: external_exports.iso.datetime(),
|
|
16750
|
+
findingId: external_exports.string().nullable().optional()
|
|
16751
|
+
}).meta({ id: "HarnessEventItem" });
|
|
16752
|
+
var HarnessEventsResponse = external_exports.object({
|
|
16753
|
+
counts: external_exports.object({
|
|
16754
|
+
block: external_exports.number().int().nonnegative(),
|
|
16755
|
+
redact: external_exports.number().int().nonnegative(),
|
|
16756
|
+
warn: external_exports.number().int().nonnegative()
|
|
16757
|
+
}),
|
|
16758
|
+
items: external_exports.array(HarnessEventItem)
|
|
16759
|
+
}).meta({ id: "HarnessEventsResponse" });
|
|
16760
|
+
var RescanResponse = external_exports.object({
|
|
16761
|
+
jobId: external_exports.string(),
|
|
16762
|
+
startedAt: external_exports.iso.datetime()
|
|
16763
|
+
}).meta({ id: "RescanResponse" });
|
|
16764
|
+
var ConnectProjectBody = external_exports.object({ repo: external_exports.string() }).meta({ id: "ConnectProjectBody" });
|
|
16765
|
+
var ListAssetsQuery = external_exports.object({
|
|
16766
|
+
/** Filter by one or more AssetType values; absent means all types. */
|
|
16767
|
+
type: external_exports.array(AssetType).optional(),
|
|
16768
|
+
/** Free-text search term. */
|
|
16769
|
+
q: external_exports.string().optional()
|
|
16770
|
+
});
|
|
16771
|
+
var GetProjectTreeQuery = external_exports.object({
|
|
16772
|
+
/** Subtree root path; defaults to repository root when absent. */
|
|
16773
|
+
path: external_exports.string().optional(),
|
|
16774
|
+
/** Free-text filter applied to file paths. */
|
|
16775
|
+
q: external_exports.string().optional(),
|
|
16776
|
+
/**
|
|
16777
|
+
* Special listing mode. `blocked` returns every effectively-blocked, auto-blocked
|
|
16778
|
+
* file across the whole repo (folders omitted, most-recent first), ignoring
|
|
16779
|
+
* `path`/`q` — powers the project-wide "recently blocked" strip.
|
|
16780
|
+
*/
|
|
16781
|
+
filter: external_exports.enum(["blocked"]).optional()
|
|
16782
|
+
});
|
|
16783
|
+
var GetProjectFileQuery = external_exports.object({
|
|
16784
|
+
/** Repository-relative file path; absent or empty → 400. */
|
|
16785
|
+
path: external_exports.string()
|
|
16786
|
+
});
|
|
16787
|
+
var GetHarnessEventsQuery = external_exports.object({
|
|
16788
|
+
/** Maximum number of events to return. Range: 1–50; default: 7. */
|
|
16789
|
+
limit: external_exports.coerce.number().int().min(1).max(50).default(7)
|
|
16790
|
+
});
|
|
16791
|
+
|
|
17035
16792
|
// ../../packages/schema/src/zod/shares.ts
|
|
17036
16793
|
var DestinationKind = external_exports.enum(["provider", "internal", "external", "ip"]).meta({ id: "DestinationKind" });
|
|
17037
16794
|
var Transport = external_exports.enum(["https", "http", "sftp", "grpc", "smtp", "ws", "wss"]).meta({ id: "Transport" });
|
|
@@ -17238,6 +16995,127 @@ var EgressWriteSummary = external_exports.object({
|
|
|
17238
16995
|
droppedFiles: external_exports.array(external_exports.string()).default([])
|
|
17239
16996
|
}).meta({ id: "EgressWriteSummary" });
|
|
17240
16997
|
|
|
16998
|
+
// ../../packages/schema/src/zod/event.ts
|
|
16999
|
+
var EventKind = external_exports.enum(["prompt", "response", "code_change", "tool_use"]).meta({ id: "EventKind" });
|
|
17000
|
+
var CAPTURE_EVENT_TYPES_SQL = EventKind.options.map((k) => `'${k}'`).join(",");
|
|
17001
|
+
var EventMetadata = external_exports.object({
|
|
17002
|
+
sessionId: external_exports.string().optional(),
|
|
17003
|
+
repo: external_exports.string().optional(),
|
|
17004
|
+
filePath: external_exports.string().optional(),
|
|
17005
|
+
// The host tool whose input/output was scanned (e.g. 'Bash', 'WebFetch'),
|
|
17006
|
+
// set by the tool-scanning hooks. The tool NAME only — never the tool's
|
|
17007
|
+
// arguments or output, which can carry the very value a finding masked
|
|
17008
|
+
// (metadata is stored unredacted). Gives findings on non-file captures a
|
|
17009
|
+
// display location ("via Bash") when no filePath exists.
|
|
17010
|
+
toolName: external_exports.string().optional(),
|
|
17011
|
+
// Set (true) by the worktree scanner when the file is excluded by the
|
|
17012
|
+
// repo's .gitignore. Gitignored files ARE still scanned — local scratch and
|
|
17013
|
+
// generated code can leak real secrets — but the provenance is recorded so
|
|
17014
|
+
// policy/dashboards can treat those findings as informational rather than
|
|
17015
|
+
// blocking. Omitted (not false) for tracked files and non-scan events.
|
|
17016
|
+
gitignored: external_exports.boolean().optional(),
|
|
17017
|
+
// Set (true) ONLY when the event's `content` is the COMPLETE file at
|
|
17018
|
+
// capture time (a worktree scan reading from disk). Hook-captured edits
|
|
17019
|
+
// (e.g. an Edit tool's new_string) are partial fragments and MUST NOT set
|
|
17020
|
+
// this. The resolver-on-ingest keys its fixed-at-source dropout
|
|
17021
|
+
// diff on this marker: only a whole-file snapshot can prove a previously
|
|
17022
|
+
// open finding is gone; a fragment's absence proves nothing (the secret
|
|
17023
|
+
// may live outside the hunk). Omitted (not false) for fragments and
|
|
17024
|
+
// non-scan events, so pre-marker clients safely default to the
|
|
17025
|
+
// non-authoritative path.
|
|
17026
|
+
wholeFile: external_exports.boolean().optional(),
|
|
17027
|
+
model: external_exports.string().optional(),
|
|
17028
|
+
turnIndex: external_exports.number().int().nonnegative().optional(),
|
|
17029
|
+
// Distributed-tracing correlation. `correlationId` ties a recorded event back
|
|
17030
|
+
// to the request that captured/ingested it (a UUID, generated independently of
|
|
17031
|
+
// the trace id); `traceId` is the W3C trace id (32 lowercase hex chars) of the
|
|
17032
|
+
// originating span when telemetry is enabled. Both optional + backward
|
|
17033
|
+
// compatible — populated by the plugin (see @akasecurity/plugin-sdk).
|
|
17034
|
+
correlationId: external_exports.uuid().optional(),
|
|
17035
|
+
traceId: external_exports.string().regex(/^[0-9a-f]{32}$/).optional(),
|
|
17036
|
+
// Ids of the detection exceptions that downgraded findings in this capture
|
|
17037
|
+
// to 'allow' — the enforcement audit trail's link back to the grant that
|
|
17038
|
+
// authorized the bypass. Absent on captures where no exception applied.
|
|
17039
|
+
exceptionIds: external_exports.array(external_exports.guid()).optional()
|
|
17040
|
+
}).meta({ id: "EventMetadata" });
|
|
17041
|
+
var Event = external_exports.object({
|
|
17042
|
+
id: external_exports.guid(),
|
|
17043
|
+
sourceTool: SourceTool,
|
|
17044
|
+
kind: EventKind,
|
|
17045
|
+
occurredAt: external_exports.iso.datetime(),
|
|
17046
|
+
contentHash: external_exports.string(),
|
|
17047
|
+
content: external_exports.string(),
|
|
17048
|
+
metadata: EventMetadata.optional()
|
|
17049
|
+
}).meta({ id: "Event" });
|
|
17050
|
+
var IngestEvent = Event.meta({ id: "IngestEvent" });
|
|
17051
|
+
var IngestBatch = external_exports.object({
|
|
17052
|
+
events: external_exports.array(IngestEvent).min(1).max(100),
|
|
17053
|
+
// Dedup policy for this batch. Id-dedup ALWAYS applies. 'content-hash'
|
|
17054
|
+
// additionally rejects any event whose contentHash the store has already
|
|
17055
|
+
// recorded — for re-runnable bulk ingest (worktree scan, transcript
|
|
17056
|
+
// backfill), where a re-run mints fresh event ids for identical content and
|
|
17057
|
+
// would otherwise accumulate duplicates. Live hook traffic must NOT set it:
|
|
17058
|
+
// two genuinely separate prompts can be byte-identical and both belong on
|
|
17059
|
+
// the timeline.
|
|
17060
|
+
dedupe: external_exports.literal("content-hash").optional()
|
|
17061
|
+
}).meta({ id: "IngestBatch" });
|
|
17062
|
+
|
|
17063
|
+
// ../../packages/schema/src/zod/exception.ts
|
|
17064
|
+
var ExceptionScope = external_exports.enum(["once", "temporary", "permanent"]);
|
|
17065
|
+
var ExceptionConditions = external_exports.object({
|
|
17066
|
+
repo: external_exports.string().optional(),
|
|
17067
|
+
sourceTool: external_exports.string().optional(),
|
|
17068
|
+
provider: external_exports.string().optional()
|
|
17069
|
+
}).strict();
|
|
17070
|
+
var ExceptionCapability = external_exports.enum(["suppress", "reveal_to_model"]);
|
|
17071
|
+
var DetectionException = external_exports.object({
|
|
17072
|
+
id: external_exports.guid(),
|
|
17073
|
+
ruleId: external_exports.string(),
|
|
17074
|
+
// Denormalized from the rule, for reporting — never matched on.
|
|
17075
|
+
category: DetectionCategory,
|
|
17076
|
+
// HMAC-SHA256 hex of the raw match under a machine-local key: a KEYED
|
|
17077
|
+
// fingerprint, never the raw value, and never reversible. Matching recomputes
|
|
17078
|
+
// the fingerprint from a fresh capture; the value itself is never stored.
|
|
17079
|
+
// Shape-constrained so a malformed — or accidentally raw — value is rejected
|
|
17080
|
+
// at the boundary rather than persisted.
|
|
17081
|
+
valueFingerprint: external_exports.string().regex(/^[0-9a-f]{64}$/),
|
|
17082
|
+
// Version of the fingerprint key the grant was written under; a rotated key
|
|
17083
|
+
// invalidates old grants rather than silently mismatching them.
|
|
17084
|
+
keyVersion: external_exports.number().int().positive(),
|
|
17085
|
+
// maskMatch() preview of the approved value — never the raw value.
|
|
17086
|
+
maskedValue: external_exports.string(),
|
|
17087
|
+
capability: ExceptionCapability.default("suppress"),
|
|
17088
|
+
scope: ExceptionScope,
|
|
17089
|
+
expiresAt: external_exports.iso.datetime().nullable(),
|
|
17090
|
+
maxUses: external_exports.number().int().positive().nullable(),
|
|
17091
|
+
useCount: external_exports.number().int().nonnegative(),
|
|
17092
|
+
lastUsedAt: external_exports.iso.datetime().nullable(),
|
|
17093
|
+
// Mandatory: every grant carries the human reason it exists.
|
|
17094
|
+
justification: external_exports.string().min(1),
|
|
17095
|
+
conditions: ExceptionConditions.nullable(),
|
|
17096
|
+
createdBy: external_exports.string(),
|
|
17097
|
+
createdVia: external_exports.enum(["cli-approve", "cli-add", "web-approve", "web-add", "api", "setup-triage"]),
|
|
17098
|
+
createdAt: external_exports.iso.datetime(),
|
|
17099
|
+
updatedAt: external_exports.iso.datetime(),
|
|
17100
|
+
// Revocation is terminal and retained — consumed/expired/revoked rows are
|
|
17101
|
+
// audit evidence; nothing in the exception lifecycle hard-deletes.
|
|
17102
|
+
revokedAt: external_exports.iso.datetime().nullable(),
|
|
17103
|
+
revokedBy: external_exports.string().nullable(),
|
|
17104
|
+
revokeReason: external_exports.string().nullable()
|
|
17105
|
+
});
|
|
17106
|
+
var ExceptionBundleEntry = DetectionException.pick({
|
|
17107
|
+
id: true,
|
|
17108
|
+
ruleId: true,
|
|
17109
|
+
valueFingerprint: true,
|
|
17110
|
+
keyVersion: true,
|
|
17111
|
+
capability: true,
|
|
17112
|
+
expiresAt: true,
|
|
17113
|
+
maxUses: true,
|
|
17114
|
+
useCount: true,
|
|
17115
|
+
conditions: true
|
|
17116
|
+
});
|
|
17117
|
+
var ExceptionDescriptor = DetectionException.omit({ valueFingerprint: true });
|
|
17118
|
+
|
|
17241
17119
|
// ../../packages/schema/src/zod/exception-action.ts
|
|
17242
17120
|
var confirmation = external_exports.string().optional();
|
|
17243
17121
|
var ApproveBlockedInput = external_exports.object({
|
|
@@ -17280,10 +17158,11 @@ function toApiAction(dbVal) {
|
|
|
17280
17158
|
}
|
|
17281
17159
|
function toApiCategory(dbVal) {
|
|
17282
17160
|
if (dbVal === "code_context") return "source_code";
|
|
17283
|
-
|
|
17161
|
+
const parsed = FindingCategory.safeParse(dbVal);
|
|
17162
|
+
return parsed.success ? parsed.data : "custom";
|
|
17284
17163
|
}
|
|
17285
17164
|
function toApiProvider(sourceTool) {
|
|
17286
|
-
return TOOL_TO_HARNESS[sourceTool] ??
|
|
17165
|
+
return TOOL_TO_HARNESS[sourceTool] ?? HARNESS.Api;
|
|
17287
17166
|
}
|
|
17288
17167
|
var STATUS_PRECEDENCE = ["open", "handled", "dismissed", "resolved"];
|
|
17289
17168
|
function foldGroupStatus(instanceStatuses) {
|
|
@@ -17870,7 +17749,14 @@ function isVaultConsentValid(consent) {
|
|
|
17870
17749
|
|
|
17871
17750
|
// ../../packages/schema/src/zod/local.ts
|
|
17872
17751
|
var WORKSPACE_SETTINGS_SPEC_VERSION = 5;
|
|
17873
|
-
var
|
|
17752
|
+
var MODEL_JUDGE_PAYLOAD_VERSION = 1;
|
|
17753
|
+
var RunMode = external_exports.enum(["standalone", "attached"]);
|
|
17754
|
+
var ControlPlaneConnection = external_exports.object({
|
|
17755
|
+
endpoint: external_exports.string().min(1),
|
|
17756
|
+
// Display name for the deployment, shown instead of the raw endpoint.
|
|
17757
|
+
label: external_exports.string().min(1).optional(),
|
|
17758
|
+
attachedAt: external_exports.iso.datetime()
|
|
17759
|
+
}).meta({ id: "ControlPlaneConnection" });
|
|
17874
17760
|
var SimpleDetectionPolicy = external_exports.enum(["redact", "warn"]);
|
|
17875
17761
|
var HistoricalAccess = external_exports.enum(["full", "session-only"]);
|
|
17876
17762
|
var ModelJudgeConsent = external_exports.object({
|
|
@@ -17879,12 +17765,10 @@ var ModelJudgeConsent = external_exports.object({
|
|
|
17879
17765
|
});
|
|
17880
17766
|
var WorkspaceSettings = external_exports.object({
|
|
17881
17767
|
specVersion: external_exports.number().int().positive().default(WORKSPACE_SETTINGS_SPEC_VERSION),
|
|
17882
|
-
|
|
17883
|
-
//
|
|
17884
|
-
runMode:
|
|
17885
|
-
|
|
17886
|
-
RunMode.default("standalone")
|
|
17887
|
-
),
|
|
17768
|
+
runMode: RunMode.default("standalone"),
|
|
17769
|
+
// Present only while attached; a detach clears it. Its presence is what makes
|
|
17770
|
+
// `runMode: 'attached'` mean anything — see isAttached.
|
|
17771
|
+
controlPlane: ControlPlaneConnection.optional(),
|
|
17888
17772
|
policy: SimpleDetectionPolicy.default("redact"),
|
|
17889
17773
|
// Consent for scanning pre-install surfaces; opt-in (see HistoricalAccess).
|
|
17890
17774
|
historicalAccess: HistoricalAccess.default("session-only"),
|
|
@@ -17986,39 +17870,244 @@ function toInspectionFindingRow(input) {
|
|
|
17986
17870
|
firstDetectedAt: input.firstDetectedAt ? isoToEpochMillis(input.firstDetectedAt) : null
|
|
17987
17871
|
};
|
|
17988
17872
|
}
|
|
17989
|
-
function toCaptureAttributes(event) {
|
|
17990
|
-
const metadata = event.metadata;
|
|
17991
|
-
return {
|
|
17992
|
-
source_tool: event.sourceTool,
|
|
17993
|
-
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
17994
|
-
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
17995
|
-
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
17996
|
-
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
17997
|
-
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
17998
|
-
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
17999
|
-
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
18000
|
-
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
18001
|
-
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
18002
|
-
// has ever populated either), but every legacy metadata key still rides
|
|
18003
|
-
// the bag rather than being silently dropped — CaptureAttributes'
|
|
18004
|
-
// `.catchall(z.unknown())` carries the long tail.
|
|
18005
|
-
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
18006
|
-
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
18007
|
-
};
|
|
17873
|
+
function toCaptureAttributes(event) {
|
|
17874
|
+
const metadata = event.metadata;
|
|
17875
|
+
return {
|
|
17876
|
+
source_tool: event.sourceTool,
|
|
17877
|
+
...metadata?.repo !== void 0 ? { repo: metadata.repo } : {},
|
|
17878
|
+
...metadata?.filePath !== void 0 ? { file_path: metadata.filePath } : {},
|
|
17879
|
+
...metadata?.toolName !== void 0 ? { tool_name: metadata.toolName } : {},
|
|
17880
|
+
...metadata?.gitignored !== void 0 ? { gitignored: metadata.gitignored } : {},
|
|
17881
|
+
...metadata?.wholeFile !== void 0 ? { whole_file: metadata.wholeFile } : {},
|
|
17882
|
+
...metadata?.correlationId !== void 0 ? { correlation_id: metadata.correlationId } : {},
|
|
17883
|
+
...metadata?.traceId !== void 0 ? { trace_id: metadata.traceId } : {},
|
|
17884
|
+
...metadata?.exceptionIds !== void 0 ? { exception_ids: metadata.exceptionIds } : {},
|
|
17885
|
+
// `model`/`turnIndex` have no dedicated CaptureAttributes field (no writer
|
|
17886
|
+
// has ever populated either), but every legacy metadata key still rides
|
|
17887
|
+
// the bag rather than being silently dropped — CaptureAttributes'
|
|
17888
|
+
// `.catchall(z.unknown())` carries the long tail.
|
|
17889
|
+
...metadata?.model !== void 0 ? { model: metadata.model } : {},
|
|
17890
|
+
...metadata?.turnIndex !== void 0 ? { turn_index: metadata.turnIndex } : {}
|
|
17891
|
+
};
|
|
17892
|
+
}
|
|
17893
|
+
function captureDefinitionVersion(finding) {
|
|
17894
|
+
return `capture/${finding.category}/${finding.severity}`;
|
|
17895
|
+
}
|
|
17896
|
+
function toCaptureDefinitionInput(finding) {
|
|
17897
|
+
return {
|
|
17898
|
+
ruleId: finding.ruleId,
|
|
17899
|
+
version: captureDefinitionVersion(finding),
|
|
17900
|
+
name: finding.ruleId,
|
|
17901
|
+
category: finding.category,
|
|
17902
|
+
severity: finding.severity,
|
|
17903
|
+
definition: JSON.stringify({ ruleId: finding.ruleId })
|
|
17904
|
+
};
|
|
17905
|
+
}
|
|
17906
|
+
|
|
17907
|
+
// ../../packages/schema/src/zod/managed.ts
|
|
17908
|
+
var MANAGED_SETTINGS_FILENAME = "managed-settings.json";
|
|
17909
|
+
var MANAGED_SETTINGS_SPEC_VERSION = 1;
|
|
17910
|
+
var ManagedSettingKey = external_exports.enum([
|
|
17911
|
+
"runMode",
|
|
17912
|
+
"historicalAccess",
|
|
17913
|
+
"vaultConsent",
|
|
17914
|
+
"vaultKeyCustody",
|
|
17915
|
+
"vaultInlineReveal",
|
|
17916
|
+
"modelJudgeConsent",
|
|
17917
|
+
"dataSharesInPlace"
|
|
17918
|
+
]).meta({ id: "ManagedSettingKey" });
|
|
17919
|
+
var ManagedSettingsValues = external_exports.object({
|
|
17920
|
+
runMode: external_exports.enum(["standalone", "attached"]).optional(),
|
|
17921
|
+
controlPlane: external_exports.object({
|
|
17922
|
+
endpoint: external_exports.string().min(1),
|
|
17923
|
+
label: external_exports.string().min(1).optional()
|
|
17924
|
+
}).optional(),
|
|
17925
|
+
historicalAccess: external_exports.enum(["full", "session-only"]).optional(),
|
|
17926
|
+
vaultConsent: external_exports.boolean().optional(),
|
|
17927
|
+
vaultKeyCustody: external_exports.enum(["file", "keychain"]).optional(),
|
|
17928
|
+
vaultInlineReveal: external_exports.enum(["masked", "full", "off"]).optional(),
|
|
17929
|
+
modelJudgeConsent: external_exports.boolean().optional(),
|
|
17930
|
+
dataSharesInPlace: external_exports.boolean().optional()
|
|
17931
|
+
}).meta({ id: "ManagedSettingsValues" });
|
|
17932
|
+
var ManagedSettings = external_exports.object({
|
|
17933
|
+
specVersion: external_exports.number().int().positive().default(MANAGED_SETTINGS_SPEC_VERSION),
|
|
17934
|
+
// Shown on every locked control, so the user can tell an administrative
|
|
17935
|
+
// decision from a bug. Absent renders as a generic "your organization".
|
|
17936
|
+
organization: external_exports.string().min(1).optional(),
|
|
17937
|
+
// What the administrator pinned.
|
|
17938
|
+
values: ManagedSettingsValues.default({}),
|
|
17939
|
+
// Which of those the user may not change. A key here with no matching value
|
|
17940
|
+
// freezes whatever the user last chose; a value with no lock is a DEFAULT
|
|
17941
|
+
// the user may still override. The two are separable on purpose.
|
|
17942
|
+
lockedFields: external_exports.array(ManagedSettingKey).default([])
|
|
17943
|
+
}).meta({ id: "ManagedSettings" });
|
|
17944
|
+
|
|
17945
|
+
// ../../packages/schema/src/zod/policy.ts
|
|
17946
|
+
var PolicyScope = external_exports.enum(["global", "repo", "user"]).meta({ id: "PolicyScope" });
|
|
17947
|
+
var PolicyTarget = external_exports.union([external_exports.object({ ruleId: external_exports.string() }), external_exports.object({ category: DetectionCategory })]).meta({ id: "PolicyTarget" });
|
|
17948
|
+
var Policy = external_exports.object({
|
|
17949
|
+
id: external_exports.guid(),
|
|
17950
|
+
scope: PolicyScope,
|
|
17951
|
+
target: PolicyTarget,
|
|
17952
|
+
action: ActionTaken,
|
|
17953
|
+
enabled: external_exports.boolean().default(true),
|
|
17954
|
+
customKeywords: external_exports.array(external_exports.string()).optional(),
|
|
17955
|
+
// Display name — optional so older policy rows without name still parse.
|
|
17956
|
+
// Added for the findings API (policy.name column migration).
|
|
17957
|
+
name: external_exports.string().optional()
|
|
17958
|
+
}).meta({ id: "Policy" });
|
|
17959
|
+
var PolicyBundle = external_exports.object({
|
|
17960
|
+
version: external_exports.string(),
|
|
17961
|
+
policies: external_exports.array(Policy),
|
|
17962
|
+
// Rules from the installed marketplace packs (snapshotted by the
|
|
17963
|
+
// control plane). The plugin registers these in addition to its bundled
|
|
17964
|
+
// packs. Optional so older backends — and older on-disk caches — that omit
|
|
17965
|
+
// the field still parse; consumers read `bundle.rules ?? []`.
|
|
17966
|
+
rules: external_exports.array(Rule).optional(),
|
|
17967
|
+
// When true, `rules` IS the complete effective ruleset and the runtime must
|
|
17968
|
+
// NOT merge its compiled-in bundled packs — the standalone gateway sets this
|
|
17969
|
+
// after reading the user's installed snapshot (installed_packs, enabled
|
|
17970
|
+
// packs only), which is how detection updates stay manual: new bundled
|
|
17971
|
+
// rules run only after the user applies the pack update. Absent/false keeps
|
|
17972
|
+
// the historical composition (bundled packs + rules) — older caches.
|
|
17973
|
+
rulesComplete: external_exports.boolean().optional(),
|
|
17974
|
+
// Active detection exceptions, evaluation subset only (see
|
|
17975
|
+
// ExceptionBundleEntry). Optional so older bundle producers — and older
|
|
17976
|
+
// on-disk caches — that omit the field still parse; consumers read
|
|
17977
|
+
// `bundle.exceptions ?? []`.
|
|
17978
|
+
exceptions: external_exports.array(ExceptionBundleEntry).optional(),
|
|
17979
|
+
// Rule ids whose pack is assigned a REVERSIBLE archetype (Redact & Vault).
|
|
17980
|
+
// A second axis over the same `redact` action, carried beside the policies
|
|
17981
|
+
// rather than on them: nothing writes ruleId-targeted policies to disk, so
|
|
17982
|
+
// widening Policy itself would change a persisted shape to express something
|
|
17983
|
+
// only the in-memory bundle needs. Optional so an older producer — or an
|
|
17984
|
+
// older on-disk cache — still parses; consumers read `?? []` and get the
|
|
17985
|
+
// pre-existing one-way behaviour, which is the safe direction to default.
|
|
17986
|
+
reversibleRuleIds: external_exports.array(external_exports.string()).optional(),
|
|
17987
|
+
// Installed pack version, keyed by ruleId, for rules in `rules` that came
|
|
17988
|
+
// from a versioned installed pack. Optional so older backends — and older
|
|
17989
|
+
// on-disk caches — that omit the field still parse; consumers fall back to
|
|
17990
|
+
// the rule's own spec version. NOT the bundle version above — see
|
|
17991
|
+
// installedRuleset's ruleVersions for the source of truth.
|
|
17992
|
+
ruleVersions: external_exports.record(external_exports.string(), external_exports.string()).optional(),
|
|
17993
|
+
customKeywords: external_exports.array(external_exports.string()),
|
|
17994
|
+
fetchedAt: external_exports.iso.datetime()
|
|
17995
|
+
}).meta({ id: "PolicyBundle" });
|
|
17996
|
+
var OBSERVE_ONLY_CATEGORIES = ["config"];
|
|
17997
|
+
var ENFORCEABLE_CATEGORIES = DetectionCategory.options.filter((c) => !OBSERVE_ONLY_CATEGORIES.includes(c));
|
|
17998
|
+
var CATEGORY_PEAK_SEVERITY = {
|
|
17999
|
+
secret: "critical",
|
|
18000
|
+
financial: "critical",
|
|
18001
|
+
// core-financial/credit-card
|
|
18002
|
+
code_flaw: "critical",
|
|
18003
|
+
pii: "high",
|
|
18004
|
+
phi: "high",
|
|
18005
|
+
custom: "high",
|
|
18006
|
+
// user-defined; conservative
|
|
18007
|
+
code_context: "low",
|
|
18008
|
+
config: "low"
|
|
18009
|
+
// observe-only; floors to monitor regardless
|
|
18010
|
+
};
|
|
18011
|
+
function severityFloorPolicy(category) {
|
|
18012
|
+
if (OBSERVE_ONLY_CATEGORIES.includes(category)) return "monitor";
|
|
18013
|
+
const peak = CATEGORY_PEAK_SEVERITY[category];
|
|
18014
|
+
return peak === "critical" || peak === "high" ? "warn" : "monitor";
|
|
18015
|
+
}
|
|
18016
|
+
var PolicyKind = external_exports.enum(["builtin", "custom"]).meta({ id: "PolicyKind" });
|
|
18017
|
+
var KNOWN_BUILTIN_IDS = ["monitor", "warn", "redact", "vault", "block"];
|
|
18018
|
+
var BuiltinPolicyId = external_exports.enum(KNOWN_BUILTIN_IDS).meta({ id: "BuiltinPolicyId" });
|
|
18019
|
+
var BUILTIN_ORDER = KNOWN_BUILTIN_IDS;
|
|
18020
|
+
var BUILTIN_POLICY_SPECS = {
|
|
18021
|
+
monitor: {
|
|
18022
|
+
name: "Monitor",
|
|
18023
|
+
action: "log",
|
|
18024
|
+
reversible: false,
|
|
18025
|
+
description: "Log every match for audit. The request is allowed through untouched."
|
|
18026
|
+
},
|
|
18027
|
+
warn: {
|
|
18028
|
+
name: "Warn",
|
|
18029
|
+
action: "warn",
|
|
18030
|
+
reversible: false,
|
|
18031
|
+
description: "Allow the request, but warn the user inline before it is sent."
|
|
18032
|
+
},
|
|
18033
|
+
redact: {
|
|
18034
|
+
name: "Redact",
|
|
18035
|
+
action: "redact",
|
|
18036
|
+
reversible: false,
|
|
18037
|
+
description: "Strip the matched value from the request and destroy it, then continue. What was removed cannot be recovered."
|
|
18038
|
+
},
|
|
18039
|
+
vault: {
|
|
18040
|
+
name: "Redact & Vault",
|
|
18041
|
+
action: "redact",
|
|
18042
|
+
reversible: true,
|
|
18043
|
+
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."
|
|
18044
|
+
},
|
|
18045
|
+
block: {
|
|
18046
|
+
name: "Block",
|
|
18047
|
+
action: "block",
|
|
18048
|
+
reversible: false,
|
|
18049
|
+
description: "Refuse the request entirely whenever any rule in this detection matches."
|
|
18050
|
+
}
|
|
18051
|
+
};
|
|
18052
|
+
function builtinPolicyToAction(id) {
|
|
18053
|
+
return BUILTIN_POLICY_SPECS[id].action;
|
|
18054
|
+
}
|
|
18055
|
+
var CATEGORY_EXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
18056
|
+
(id) => !BUILTIN_POLICY_SPECS[id].reversible
|
|
18057
|
+
);
|
|
18058
|
+
var CATEGORY_INEXPRESSIBLE_IDS = KNOWN_BUILTIN_IDS.filter(
|
|
18059
|
+
(id) => BUILTIN_POLICY_SPECS[id].reversible
|
|
18060
|
+
);
|
|
18061
|
+
var CategoryPolicyId = external_exports.enum(CATEGORY_EXPRESSIBLE_IDS).meta({ id: "CategoryPolicyId" });
|
|
18062
|
+
function builtinPolicyIsReversible(id) {
|
|
18063
|
+
return BUILTIN_POLICY_SPECS[id].reversible;
|
|
18008
18064
|
}
|
|
18009
|
-
function
|
|
18010
|
-
|
|
18065
|
+
function policyIdIsReversible(policyId) {
|
|
18066
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18067
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
18068
|
+
return builtinPolicyIsReversible(id);
|
|
18011
18069
|
}
|
|
18012
|
-
|
|
18013
|
-
|
|
18014
|
-
|
|
18015
|
-
|
|
18016
|
-
|
|
18017
|
-
|
|
18018
|
-
|
|
18019
|
-
|
|
18020
|
-
|
|
18070
|
+
var DEFAULT_ACTIONS = Object.fromEntries(
|
|
18071
|
+
DetectionCategory.options.map((c) => [c, builtinPolicyToAction(severityFloorPolicy(c))])
|
|
18072
|
+
);
|
|
18073
|
+
var BUILTIN_POLICIES = Object.fromEntries(
|
|
18074
|
+
KNOWN_BUILTIN_IDS.map((id) => [id, { id, ...BUILTIN_POLICY_SPECS[id] }])
|
|
18075
|
+
);
|
|
18076
|
+
var DEFAULT_PACK_POLICY_ID = "monitor";
|
|
18077
|
+
function policyIdToAction(policyId) {
|
|
18078
|
+
const parsed = BuiltinPolicyId.safeParse(policyId ?? DEFAULT_PACK_POLICY_ID);
|
|
18079
|
+
const id = parsed.success ? parsed.data : DEFAULT_PACK_POLICY_ID;
|
|
18080
|
+
return BUILTIN_POLICIES[id].action;
|
|
18021
18081
|
}
|
|
18082
|
+
var UsedByItem = external_exports.object({
|
|
18083
|
+
id: external_exports.string(),
|
|
18084
|
+
name: external_exports.string(),
|
|
18085
|
+
ruleCount: external_exports.number().int().nonnegative(),
|
|
18086
|
+
enabled: external_exports.boolean()
|
|
18087
|
+
}).meta({ id: "UsedByItem" });
|
|
18088
|
+
var PolicyListItem = external_exports.object({
|
|
18089
|
+
id: external_exports.string(),
|
|
18090
|
+
kind: PolicyKind,
|
|
18091
|
+
name: external_exports.string(),
|
|
18092
|
+
enabled: external_exports.boolean(),
|
|
18093
|
+
usedByCount: external_exports.number().int().nonnegative()
|
|
18094
|
+
}).meta({ id: "PolicyListItem" });
|
|
18095
|
+
var ListPoliciesResponse = external_exports.object({ items: external_exports.array(PolicyListItem) }).meta({ id: "ListPoliciesResponse" });
|
|
18096
|
+
var PolicyDetail = external_exports.object({
|
|
18097
|
+
specVersion: external_exports.literal(1),
|
|
18098
|
+
id: external_exports.string(),
|
|
18099
|
+
kind: PolicyKind,
|
|
18100
|
+
name: external_exports.string(),
|
|
18101
|
+
enabled: external_exports.boolean(),
|
|
18102
|
+
description: external_exports.string(),
|
|
18103
|
+
usedBy: external_exports.array(UsedByItem)
|
|
18104
|
+
}).meta({ id: "PolicyDetail" });
|
|
18105
|
+
var PolicyStatsResponse = external_exports.object({
|
|
18106
|
+
policies: external_exports.number().int().nonnegative(),
|
|
18107
|
+
builtin: external_exports.number().int().nonnegative(),
|
|
18108
|
+
custom: external_exports.number().int().nonnegative(),
|
|
18109
|
+
detectionsGoverned: external_exports.number().int().nonnegative()
|
|
18110
|
+
}).meta({ id: "PolicyStatsResponse" });
|
|
18022
18111
|
|
|
18023
18112
|
// ../../packages/schema/src/zod/project-files.ts
|
|
18024
18113
|
var ProjectFileInput = external_exports.object({
|
|
@@ -18098,44 +18187,6 @@ var BatchedRemediation = external_exports.discriminatedUnion("kind", [
|
|
|
18098
18187
|
NoRemediationDecision
|
|
18099
18188
|
]);
|
|
18100
18189
|
|
|
18101
|
-
// ../../packages/schema/src/zod/rule-test.ts
|
|
18102
|
-
var TestRulesRequest = external_exports.object({
|
|
18103
|
-
rules: external_exports.array(Rule).min(1).max(100),
|
|
18104
|
-
text: external_exports.string().max(5e4).optional(),
|
|
18105
|
-
fixtures: external_exports.array(RuleFixture).max(200).optional()
|
|
18106
|
-
}).refine((v) => v.text !== void 0 || (v.fixtures?.length ?? 0) > 0, {
|
|
18107
|
-
message: "Provide `text`, `fixtures`, or both \u2014 there must be something to test"
|
|
18108
|
-
}).meta({ id: "TestRulesRequest" });
|
|
18109
|
-
var RuleTestMatch = external_exports.object({
|
|
18110
|
-
ruleId: external_exports.string(),
|
|
18111
|
-
category: DetectionCategory,
|
|
18112
|
-
severity: Severity,
|
|
18113
|
-
span: Span,
|
|
18114
|
-
confidence: external_exports.number().min(0).max(1),
|
|
18115
|
-
match: external_exports.string()
|
|
18116
|
-
}).meta({ id: "RuleTestMatch" });
|
|
18117
|
-
var FixtureResult = external_exports.object({
|
|
18118
|
-
label: external_exports.string(),
|
|
18119
|
-
shouldMatch: external_exports.boolean(),
|
|
18120
|
-
didMatch: external_exports.boolean(),
|
|
18121
|
-
passed: external_exports.boolean(),
|
|
18122
|
-
matches: external_exports.array(RuleTestMatch)
|
|
18123
|
-
}).meta({ id: "FixtureResult" });
|
|
18124
|
-
var TestRulesResponse = external_exports.object({
|
|
18125
|
-
// Present only when the request supplied `text`.
|
|
18126
|
-
adhoc: external_exports.object({ matches: external_exports.array(RuleTestMatch) }).optional(),
|
|
18127
|
-
fixtures: external_exports.array(FixtureResult),
|
|
18128
|
-
summary: external_exports.object({
|
|
18129
|
-
total: external_exports.number().int().nonnegative(),
|
|
18130
|
-
passed: external_exports.number().int().nonnegative(),
|
|
18131
|
-
failed: external_exports.number().int().nonnegative()
|
|
18132
|
-
}),
|
|
18133
|
-
// Ids of rules whose matcher type the engine cannot evaluate today (e.g.
|
|
18134
|
-
// `validator`), so they silently never match. Surfaced so an author is not
|
|
18135
|
-
// misled by a green run that actually skipped a rule.
|
|
18136
|
-
unsupportedRuleIds: external_exports.array(external_exports.string())
|
|
18137
|
-
}).meta({ id: "TestRulesResponse" });
|
|
18138
|
-
|
|
18139
18190
|
// ../../packages/schema/src/zod/security.ts
|
|
18140
18191
|
var SeveritySummaryItem = external_exports.object({
|
|
18141
18192
|
severity: Severity,
|
|
@@ -18233,10 +18284,22 @@ var TopSourcesQuery = external_exports.object({
|
|
|
18233
18284
|
// Omit for both kinds.
|
|
18234
18285
|
kind: external_exports.enum(SOURCE_KINDS).optional()
|
|
18235
18286
|
});
|
|
18236
|
-
var Provider =
|
|
18287
|
+
var Provider = Harness.extract([
|
|
18288
|
+
"ClaudeCode",
|
|
18289
|
+
"Cursor",
|
|
18290
|
+
"Codex",
|
|
18291
|
+
"Antigravity",
|
|
18292
|
+
"ClaudeAi",
|
|
18293
|
+
"ChatGpt",
|
|
18294
|
+
"Copilot",
|
|
18295
|
+
"Api"
|
|
18296
|
+
]).meta({ id: "Provider" });
|
|
18237
18297
|
var ScanCoverageProvider = external_exports.object({
|
|
18238
18298
|
provider: Provider,
|
|
18239
|
-
// Percent of that provider's traffic
|
|
18299
|
+
// Percent of that provider's traffic the shipped capture surface reaches.
|
|
18300
|
+
// A curated business fact, constant across every `range` — not a measured
|
|
18301
|
+
// per-window metric. 0 exactly when `supported` is false. See the comment
|
|
18302
|
+
// above the block for where these numbers are decided.
|
|
18240
18303
|
coverage: external_exports.number().int().min(0).max(100),
|
|
18241
18304
|
supported: external_exports.boolean()
|
|
18242
18305
|
}).meta({ id: "ScanCoverageProvider" });
|
|
@@ -18290,6 +18353,18 @@ var ApplyRecommendedActionResponse = external_exports.object({
|
|
|
18290
18353
|
var DismissRecommendedActionResponse = external_exports.object({ id: external_exports.string(), status: external_exports.literal("dismissed") }).meta({ id: "DismissRecommendedActionResponse" });
|
|
18291
18354
|
var RecommendedActionIdParam = external_exports.object({ id: external_exports.string() });
|
|
18292
18355
|
|
|
18356
|
+
// ../../packages/schema/src/zod/settings-action.ts
|
|
18357
|
+
var SaveSettingsInput = external_exports.object({
|
|
18358
|
+
historicalAccess: external_exports.string(),
|
|
18359
|
+
modelJudgeConsent: external_exports.boolean(),
|
|
18360
|
+
vaultConsent: external_exports.string(),
|
|
18361
|
+
vaultInlineReveal: external_exports.string()
|
|
18362
|
+
});
|
|
18363
|
+
var AttachInput = external_exports.object({
|
|
18364
|
+
endpoint: external_exports.string(),
|
|
18365
|
+
label: external_exports.string().optional()
|
|
18366
|
+
});
|
|
18367
|
+
|
|
18293
18368
|
// ../../packages/schema/src/zod/triage.ts
|
|
18294
18369
|
var TriageHit = external_exports.object({
|
|
18295
18370
|
ruleId: external_exports.string(),
|
|
@@ -18304,7 +18379,7 @@ var TriageHit = external_exports.object({
|
|
|
18304
18379
|
valueFingerprint: external_exports.string().optional(),
|
|
18305
18380
|
keyVersion: external_exports.number().int().nonnegative().optional()
|
|
18306
18381
|
});
|
|
18307
|
-
var TriagePolicy =
|
|
18382
|
+
var TriagePolicy = CategoryPolicyId;
|
|
18308
18383
|
var TriageCategoryRec = external_exports.object({
|
|
18309
18384
|
category: DetectionCategory,
|
|
18310
18385
|
action: TriagePolicy,
|
|
@@ -18522,8 +18597,11 @@ function chmodBestEffort(path, mode) {
|
|
|
18522
18597
|
function tightenDir(dir) {
|
|
18523
18598
|
chmodBestEffort(dir, DATA_DIR_MODE);
|
|
18524
18599
|
}
|
|
18600
|
+
function mkdirOwnerOnlySync(dir, recursive = false) {
|
|
18601
|
+
mkdirSync(dir, { recursive, mode: DATA_DIR_MODE });
|
|
18602
|
+
}
|
|
18525
18603
|
function ensureDataDirSync(dir) {
|
|
18526
|
-
|
|
18604
|
+
mkdirOwnerOnlySync(dir, true);
|
|
18527
18605
|
tightenDir(dir);
|
|
18528
18606
|
}
|
|
18529
18607
|
function dbSidecars(file2) {
|
|
@@ -18535,6 +18613,26 @@ function tightenFile(file2) {
|
|
|
18535
18613
|
function tightenPerms(file2) {
|
|
18536
18614
|
for (const path of [file2, ...dbSidecars(file2)]) chmodBestEffort(path, DATA_FILE_MODE);
|
|
18537
18615
|
}
|
|
18616
|
+
function writeExclusiveOwnerOnlySync(file2, data) {
|
|
18617
|
+
writeFileSync(file2, data, { mode: DATA_FILE_MODE, flag: "wx" });
|
|
18618
|
+
}
|
|
18619
|
+
function writeOwnerOnlyFileSync(file2, data) {
|
|
18620
|
+
const tmp = `${file2}.${String(process.pid)}.tmp`;
|
|
18621
|
+
try {
|
|
18622
|
+
rmSync(tmp, { force: true });
|
|
18623
|
+
} catch {
|
|
18624
|
+
}
|
|
18625
|
+
try {
|
|
18626
|
+
writeExclusiveOwnerOnlySync(tmp, data);
|
|
18627
|
+
renameSync(tmp, file2);
|
|
18628
|
+
} finally {
|
|
18629
|
+
try {
|
|
18630
|
+
rmSync(tmp, { force: true });
|
|
18631
|
+
} catch {
|
|
18632
|
+
}
|
|
18633
|
+
}
|
|
18634
|
+
tightenFile(file2);
|
|
18635
|
+
}
|
|
18538
18636
|
function classifyOccupant(file2) {
|
|
18539
18637
|
try {
|
|
18540
18638
|
if (lstatSync(file2).isSymbolicLink()) return { kind: "symlink" };
|
|
@@ -18563,7 +18661,7 @@ function createOwnerOnlyFileSync(file2, data) {
|
|
|
18563
18661
|
}
|
|
18564
18662
|
let created;
|
|
18565
18663
|
try {
|
|
18566
|
-
|
|
18664
|
+
writeExclusiveOwnerOnlySync(tmp, data);
|
|
18567
18665
|
created = publishByLink(tmp, file2, data);
|
|
18568
18666
|
} finally {
|
|
18569
18667
|
try {
|
|
@@ -18585,7 +18683,7 @@ function publishByLink(tmp, file2, data) {
|
|
|
18585
18683
|
if (!LINK_UNSUPPORTED.has(code ?? "")) throw err;
|
|
18586
18684
|
}
|
|
18587
18685
|
try {
|
|
18588
|
-
|
|
18686
|
+
writeExclusiveOwnerOnlySync(file2, data);
|
|
18589
18687
|
return true;
|
|
18590
18688
|
} catch (err) {
|
|
18591
18689
|
if (err.code === "EEXIST") return false;
|
|
@@ -18598,6 +18696,25 @@ function backupPath(file2, tag) {
|
|
|
18598
18696
|
return `${file2}.${tag}.${String(Date.now())}.${randomUUID().slice(0, 8)}.bak`;
|
|
18599
18697
|
}
|
|
18600
18698
|
var STALE_PARTIAL_MS = 5 * 6e4;
|
|
18699
|
+
var SNAPSHOT_STAGING_SUFFIX = ".partial";
|
|
18700
|
+
var STAGED_NAME_SUFFIX = `.bak${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18701
|
+
var SNAPSHOT_STAGING_COPY = "copy";
|
|
18702
|
+
function createSnapshotStaging(backup) {
|
|
18703
|
+
const stage = `${backup}${SNAPSHOT_STAGING_SUFFIX}`;
|
|
18704
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18705
|
+
mkdirOwnerOnlySync(stage);
|
|
18706
|
+
tightenDir(stage);
|
|
18707
|
+
return { stage, copy: join(stage, SNAPSHOT_STAGING_COPY) };
|
|
18708
|
+
}
|
|
18709
|
+
function idleMs(entry) {
|
|
18710
|
+
for (const candidate of [join(entry, SNAPSHOT_STAGING_COPY), entry]) {
|
|
18711
|
+
try {
|
|
18712
|
+
return Date.now() - statSync(candidate).mtimeMs;
|
|
18713
|
+
} catch {
|
|
18714
|
+
}
|
|
18715
|
+
}
|
|
18716
|
+
return null;
|
|
18717
|
+
}
|
|
18601
18718
|
function reapStalePartials(file2) {
|
|
18602
18719
|
const dir = dirname(file2);
|
|
18603
18720
|
const prefix = `${basename(file2)}.`;
|
|
@@ -18608,30 +18725,34 @@ function reapStalePartials(file2) {
|
|
|
18608
18725
|
return;
|
|
18609
18726
|
}
|
|
18610
18727
|
for (const name of entries) {
|
|
18611
|
-
if (!name.startsWith(prefix) || !name.endsWith(
|
|
18612
|
-
const
|
|
18728
|
+
if (!name.startsWith(prefix) || !name.endsWith(STAGED_NAME_SUFFIX)) continue;
|
|
18729
|
+
const staging = join(dir, name);
|
|
18613
18730
|
try {
|
|
18614
|
-
|
|
18615
|
-
|
|
18731
|
+
const idle = idleMs(staging);
|
|
18732
|
+
if (idle !== null && idle > STALE_PARTIAL_MS) {
|
|
18733
|
+
rmSync2(staging, { recursive: true, force: true });
|
|
18616
18734
|
}
|
|
18617
18735
|
} catch {
|
|
18618
18736
|
}
|
|
18619
18737
|
}
|
|
18620
18738
|
}
|
|
18621
18739
|
function snapshotStore(db, backup) {
|
|
18622
|
-
const
|
|
18740
|
+
const { stage, copy } = createSnapshotStaging(backup);
|
|
18623
18741
|
try {
|
|
18624
|
-
|
|
18625
|
-
|
|
18626
|
-
|
|
18627
|
-
renameSync2(partial2, backup);
|
|
18742
|
+
db.prepare("VACUUM INTO ?").run(copy);
|
|
18743
|
+
tightenFile(copy);
|
|
18744
|
+
renameSync2(copy, backup);
|
|
18628
18745
|
} catch (error51) {
|
|
18629
18746
|
try {
|
|
18630
|
-
rmSync2(
|
|
18747
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18631
18748
|
} catch {
|
|
18632
18749
|
}
|
|
18633
18750
|
throw error51;
|
|
18634
18751
|
}
|
|
18752
|
+
try {
|
|
18753
|
+
rmSync2(stage, { recursive: true, force: true });
|
|
18754
|
+
} catch {
|
|
18755
|
+
}
|
|
18635
18756
|
}
|
|
18636
18757
|
function moveStoreAside(file2, backup) {
|
|
18637
18758
|
const undo = [];
|
|
@@ -19359,9 +19480,10 @@ function safeParseStringArray(raw) {
|
|
|
19359
19480
|
const parsed = safeJson(raw, null);
|
|
19360
19481
|
return Array.isArray(parsed) ? parsed : [];
|
|
19361
19482
|
}
|
|
19483
|
+
var DEFAULT_HARNESS = HARNESS.ClaudeCode;
|
|
19362
19484
|
function toHarness(raw) {
|
|
19363
19485
|
const parsed = Harness.safeParse(raw);
|
|
19364
|
-
return parsed.success ? parsed.data :
|
|
19486
|
+
return parsed.success ? parsed.data : DEFAULT_HARNESS;
|
|
19365
19487
|
}
|
|
19366
19488
|
function resolveLifecycle(row, lastActivityMs, nowMs) {
|
|
19367
19489
|
if (row.status) {
|
|
@@ -19492,7 +19614,7 @@ var SqliteActivityRepository = class {
|
|
|
19492
19614
|
const params = [];
|
|
19493
19615
|
if (query.harness && query.harness.length > 0) {
|
|
19494
19616
|
conditions.push(
|
|
19495
|
-
`coalesce(json_extract(attributes, '$.harness'), '
|
|
19617
|
+
`coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') IN (${placeholders(query.harness.length)})`
|
|
19496
19618
|
);
|
|
19497
19619
|
params.push(...query.harness);
|
|
19498
19620
|
}
|
|
@@ -19699,13 +19821,13 @@ var SqliteActivityRepository = class {
|
|
|
19699
19821
|
* The DISTINCT harnesses that actually have sessions (optionally within a
|
|
19700
19822
|
* `started_at >= fromMs` window), so the filter can offer only the harnesses
|
|
19701
19823
|
* present rather than the full enum. Each stored value is normalized through
|
|
19702
|
-
* the SAME `toHarness` default the list uses (missing →
|
|
19703
|
-
* store of bare (harness-less) roots surfaces exactly
|
|
19824
|
+
* the SAME `toHarness` default the list uses (missing → DEFAULT_HARNESS), so
|
|
19825
|
+
* a store of bare (harness-less) roots surfaces exactly that one harness.
|
|
19704
19826
|
*/
|
|
19705
19827
|
harnessFacets(fromMs) {
|
|
19706
19828
|
const where = fromMs === void 0 ? "" : " AND started_at >= ?";
|
|
19707
19829
|
const stmt = this.db.prepare(
|
|
19708
|
-
`SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '
|
|
19830
|
+
`SELECT DISTINCT coalesce(json_extract(attributes, '$.harness'), '${DEFAULT_HARNESS}') AS harness
|
|
19709
19831
|
FROM audit_events WHERE ${SESSION_ROOT}${where}`
|
|
19710
19832
|
);
|
|
19711
19833
|
const rows = allRows(
|
|
@@ -19919,8 +20041,8 @@ var SqliteAuditEventsRepository = class {
|
|
|
19919
20041
|
}
|
|
19920
20042
|
// Insert one transcript-derived `llm_call` leaf. Unlike `insertAuditEvent`
|
|
19921
20043
|
// (which takes a caller-supplied random id), the id here is MINTED internally
|
|
19922
|
-
// from the natural key — `llmCallId(sessionId, messageId)` —
|
|
19923
|
-
//
|
|
20044
|
+
// from the natural key — `llmCallId(sessionId, messageId)` — derived from the
|
|
20045
|
+
// session and message alone, like the sibling local-store ids. The deterministic
|
|
19924
20046
|
// id + the UPSERT-take-MAX(output_tokens) statement make every re-read idempotent
|
|
19925
20047
|
// AND converge a streaming partial/final split across two incremental passes:
|
|
19926
20048
|
// a whole-file re-read no-ops (equal output), a lagging final replaces a
|
|
@@ -20885,6 +21007,42 @@ var SqliteFindingsRepository = class {
|
|
|
20885
21007
|
this.db = db;
|
|
20886
21008
|
}
|
|
20887
21009
|
db;
|
|
21010
|
+
/**
|
|
21011
|
+
* The newest `limit` findings, newest first.
|
|
21012
|
+
*
|
|
21013
|
+
* THE PLAN IS THE POINT HERE, and two things in the SQL below exist only to
|
|
21014
|
+
* pin it. The natural spelling — drive from `inspection_findings`, order by the
|
|
21015
|
+
* JOINED `e.started_at` — cannot push the LIMIT down, because the sort key is
|
|
21016
|
+
* not on the driving table: SQLite sorts every finding in the store through a
|
|
21017
|
+
* temp B-tree to return 500 rows. Measured at 35.0 ms on a 40,000-event corpus
|
|
21018
|
+
* against 0.9 ms for the form below, and the gap is a ratio of the store size
|
|
21019
|
+
* rather than a constant.
|
|
21020
|
+
*
|
|
21021
|
+
* What it takes to make `started_at` order come out of an index instead:
|
|
21022
|
+
*
|
|
21023
|
+
* - **`+e.event_type`** — the unary plus makes that term non-indexable, so the
|
|
21024
|
+
* planner stops choosing `idx_audit_type_t` (`event_type, started_at`). That
|
|
21025
|
+
* index cannot serve the ORDER BY: the predicate spans four event types, so
|
|
21026
|
+
* satisfying a global `started_at` order across them needs a range merge
|
|
21027
|
+
* SQLite will not do, and it sorts instead. Freed of it, the planner scans
|
|
21028
|
+
* `idx_audit_started_at` — a bare `started_at` index — in DESC order and
|
|
21029
|
+
* filters the type per row, which lets the LIMIT stop the scan early.
|
|
21030
|
+
* - **`CROSS JOIN`** — semantically identical to JOIN in SQLite, and there
|
|
21031
|
+
* purely to stop the tables being reordered. With plain JOINs the planner
|
|
21032
|
+
* drives from `f` and sorts everything again: measured at 23.6 ms, i.e. the
|
|
21033
|
+
* unary plus ALONE recovers almost none of the win. Both are needed.
|
|
21034
|
+
*
|
|
21035
|
+
* Neither is a micro-optimisation that a later reader should tidy away, and
|
|
21036
|
+
* `packages/persistence/test/performance/hot-read-query-plans.test.ts` fails if
|
|
21037
|
+
* the temp B-tree comes back.
|
|
21038
|
+
*
|
|
21039
|
+
* Degrading gracefully was the reason for `+` over `INDEXED BY`, which measured
|
|
21040
|
+
* identically (0.9 ms): `INDEXED BY` is a hard requirement, so dropping or
|
|
21041
|
+
* renaming the index turns this read into an ERROR, where `+` turns it into a
|
|
21042
|
+
* scan-and-sort — slower, still correct. The worst case for the chosen form is
|
|
21043
|
+
* a store whose recent captures carry no findings at all, where the scan walks
|
|
21044
|
+
* the whole index; that is still no worse than the full sort it replaced.
|
|
21045
|
+
*/
|
|
20888
21046
|
recentFindings(opts) {
|
|
20889
21047
|
const limit = opts?.limit ?? 50;
|
|
20890
21048
|
const rows = allRows(
|
|
@@ -20893,10 +21051,10 @@ var SqliteFindingsRepository = class {
|
|
|
20893
21051
|
f.masked_match, f.action_taken, f.confidence, e.started_at AS occurred_at,
|
|
20894
21052
|
json_extract(e.attributes, '$.source_tool') AS source_tool,
|
|
20895
21053
|
e.event_type AS kind
|
|
20896
|
-
FROM
|
|
20897
|
-
JOIN
|
|
20898
|
-
JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
20899
|
-
WHERE e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
21054
|
+
FROM audit_events e
|
|
21055
|
+
CROSS JOIN inspection_findings f ON f.audit_event_id = e.id
|
|
21056
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
21057
|
+
WHERE +e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
20900
21058
|
ORDER BY e.started_at DESC, f.rowid DESC
|
|
20901
21059
|
LIMIT :limit`
|
|
20902
21060
|
),
|
|
@@ -21540,7 +21698,8 @@ var SqliteInspectionDefinitionsRepository = class {
|
|
|
21540
21698
|
}
|
|
21541
21699
|
db;
|
|
21542
21700
|
insertStmt;
|
|
21543
|
-
//
|
|
21701
|
+
// Insert-if-absent; returns the content-addressed definition id. An id already
|
|
21702
|
+
// present keeps the stored row untouched — see the class doc.
|
|
21544
21703
|
upsert(input) {
|
|
21545
21704
|
const id = inspectionDefinitionId(input.ruleId, input.version);
|
|
21546
21705
|
const row = toInspectionDefinitionRow(input, id);
|
|
@@ -21684,11 +21843,23 @@ function isParseableBinaryVersion(version2) {
|
|
|
21684
21843
|
|
|
21685
21844
|
// ../../packages/persistence/src/repositories/installed-packs.ts
|
|
21686
21845
|
var DEFAULT_POLICY_ID = DEFAULT_PACK_POLICY_ID;
|
|
21846
|
+
function printableRuleId(entry) {
|
|
21847
|
+
if (typeof entry !== "object" || entry === null) return null;
|
|
21848
|
+
const candidate = entry.id;
|
|
21849
|
+
return Rule.shape.id.safeParse(candidate).success ? candidate : null;
|
|
21850
|
+
}
|
|
21851
|
+
function firstIssueReason(error51) {
|
|
21852
|
+
const issue2 = error51.issues[0];
|
|
21853
|
+
if (!issue2) return "unknown";
|
|
21854
|
+
const path = issue2.path.map((segment) => String(segment)).join(".");
|
|
21855
|
+
return path ? `${path}: ${issue2.code}` : issue2.code;
|
|
21856
|
+
}
|
|
21857
|
+
var REJECTED_RULE_DETAIL_CAP = 10;
|
|
21687
21858
|
function inventorySignature(packs2) {
|
|
21688
21859
|
return packs2.map((p) => `${p.namespace}/${p.packId}@${p.version}#${hashRules(p.rulesJson)}`).sort().join(",");
|
|
21689
21860
|
}
|
|
21690
21861
|
function hashRules(rulesJson) {
|
|
21691
|
-
return createHash2("
|
|
21862
|
+
return createHash2("sha256").update(rulesJson).digest("hex");
|
|
21692
21863
|
}
|
|
21693
21864
|
function parseVersion(v) {
|
|
21694
21865
|
const m = /^(\d+)\.(\d+)\.(\d+)/.exec(v);
|
|
@@ -21900,10 +22071,21 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21900
22071
|
* (all detection off) instead of falling back to the bundled packs. Every
|
|
21901
22072
|
* JSON-level failure therefore counts as invalid.
|
|
21902
22073
|
*/
|
|
22074
|
+
/**
|
|
22075
|
+
* ORDERED, because a rule id is unique only WITHIN a pack — the sole unique
|
|
22076
|
+
* index is (namespace, pack_id) — so two enabled packs may contribute the same
|
|
22077
|
+
* id, and the per-rule maps below are last-write-wins. Without an ORDER BY the
|
|
22078
|
+
* winner is whatever order SQLite happens to return, which makes a collision
|
|
22079
|
+
* resolve differently on two machines holding identical stores. Ordering by
|
|
22080
|
+
* (namespace, pack_id) makes the loser deterministic and therefore testable.
|
|
22081
|
+
*/
|
|
21903
22082
|
installedRuleset() {
|
|
21904
22083
|
const rows = allRows(
|
|
21905
22084
|
this.db.prepare(
|
|
21906
|
-
`SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version
|
|
22085
|
+
`SELECT enabled, policy_id AS policyId, rules_json AS rulesJson, version,
|
|
22086
|
+
namespace, pack_id AS packId
|
|
22087
|
+
FROM installed_packs
|
|
22088
|
+
ORDER BY namespace, pack_id`
|
|
21907
22089
|
)
|
|
21908
22090
|
);
|
|
21909
22091
|
const out = {
|
|
@@ -21911,22 +22093,32 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21911
22093
|
enabledPacks: 0,
|
|
21912
22094
|
rules: [],
|
|
21913
22095
|
invalidRules: 0,
|
|
22096
|
+
rejectedRules: [],
|
|
21914
22097
|
ruleActions: /* @__PURE__ */ new Map(),
|
|
21915
|
-
ruleVersions: /* @__PURE__ */ new Map()
|
|
22098
|
+
ruleVersions: /* @__PURE__ */ new Map(),
|
|
22099
|
+
reversibleRules: /* @__PURE__ */ new Set()
|
|
22100
|
+
};
|
|
22101
|
+
const reject = (pack, ruleId, reason) => {
|
|
22102
|
+
if (out.rejectedRules.length >= REJECTED_RULE_DETAIL_CAP) return;
|
|
22103
|
+
out.rejectedRules.push({ pack, ruleId, reason });
|
|
21916
22104
|
};
|
|
21917
22105
|
for (const row of rows) {
|
|
21918
22106
|
if (!intToBool(row.enabled)) continue;
|
|
21919
22107
|
out.enabledPacks += 1;
|
|
21920
22108
|
const action = policyIdToAction(row.policyId);
|
|
22109
|
+
const reversible = policyIdIsReversible(row.policyId);
|
|
22110
|
+
const pack = `${row.namespace}/${row.packId}`;
|
|
21921
22111
|
let raw;
|
|
21922
22112
|
try {
|
|
21923
22113
|
raw = JSON.parse(row.rulesJson);
|
|
21924
22114
|
} catch {
|
|
21925
22115
|
out.invalidRules += 1;
|
|
22116
|
+
reject(pack, null, "rules_json: malformed JSON");
|
|
21926
22117
|
continue;
|
|
21927
22118
|
}
|
|
21928
22119
|
if (!Array.isArray(raw)) {
|
|
21929
22120
|
out.invalidRules += 1;
|
|
22121
|
+
reject(pack, null, "rules_json: not an array");
|
|
21930
22122
|
continue;
|
|
21931
22123
|
}
|
|
21932
22124
|
for (const entry of raw) {
|
|
@@ -21935,7 +22127,12 @@ var SqliteInstalledPacksRepository = class {
|
|
|
21935
22127
|
out.rules.push(parsed.data);
|
|
21936
22128
|
out.ruleActions.set(parsed.data.id, action);
|
|
21937
22129
|
out.ruleVersions.set(parsed.data.id, row.version);
|
|
21938
|
-
|
|
22130
|
+
if (reversible) out.reversibleRules.add(parsed.data.id);
|
|
22131
|
+
else out.reversibleRules.delete(parsed.data.id);
|
|
22132
|
+
} else {
|
|
22133
|
+
out.invalidRules += 1;
|
|
22134
|
+
reject(pack, printableRuleId(entry), firstIssueReason(parsed.error));
|
|
22135
|
+
}
|
|
21939
22136
|
}
|
|
21940
22137
|
}
|
|
21941
22138
|
return out;
|
|
@@ -22129,31 +22326,38 @@ var CATEGORY_ORDER = ["config", "skill", "mcp", "hook"];
|
|
|
22129
22326
|
var VALID_HARNESS_IDS = new Set(HarnessId.options);
|
|
22130
22327
|
var WORKTREE_CHECKOUT_FILTER = "(url IS NULL OR (url NOT LIKE '%/.claude/worktrees/%' AND url NOT LIKE '%\\.claude\\worktrees\\%'))";
|
|
22131
22328
|
var HARNESS_LABELS = {
|
|
22132
|
-
|
|
22133
|
-
|
|
22134
|
-
|
|
22135
|
-
|
|
22329
|
+
[HARNESS.ClaudeCode]: "Claude Code",
|
|
22330
|
+
[HARNESS.Cursor]: "Cursor",
|
|
22331
|
+
[HARNESS.Codex]: "Codex",
|
|
22332
|
+
[HARNESS.Antigravity]: "Antigravity"
|
|
22136
22333
|
};
|
|
22137
22334
|
var HARNESS_LIVENESS_WINDOW_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
22138
22335
|
var EMPTY_PROJECT_AGG = {
|
|
22139
22336
|
accessCounts: { open: 0, approved: 0, blocked: 0, total: 0 },
|
|
22140
22337
|
findingsCount: 0
|
|
22141
22338
|
};
|
|
22339
|
+
var stripSeparators = (value) => value.toLowerCase().replace(/[\s-]/g, "");
|
|
22340
|
+
var TITLE_NEEDLES = {
|
|
22341
|
+
ClaudeCode: stripSeparators(SOURCE_TOOL.ClaudeCode),
|
|
22342
|
+
Cursor: stripSeparators(SOURCE_TOOL.Cursor),
|
|
22343
|
+
Codex: stripSeparators(SOURCE_TOOL.Codex),
|
|
22344
|
+
Antigravity: stripSeparators(SOURCE_TOOL.Antigravity)
|
|
22345
|
+
};
|
|
22142
22346
|
function resolveHarnessId(attrs, row) {
|
|
22143
22347
|
if (attrs.provider && VALID_HARNESS_IDS.has(attrs.provider)) {
|
|
22144
22348
|
return attrs.provider;
|
|
22145
22349
|
}
|
|
22146
|
-
const t = (row.title ?? "")
|
|
22147
|
-
if (t.includes(
|
|
22148
|
-
if (t.includes(
|
|
22149
|
-
if (t.includes(
|
|
22150
|
-
if (t.includes(
|
|
22350
|
+
const t = stripSeparators(row.title ?? "");
|
|
22351
|
+
if (t.includes(TITLE_NEEDLES.ClaudeCode) || t === "claude") return HARNESS.ClaudeCode;
|
|
22352
|
+
if (t.includes(TITLE_NEEDLES.Cursor)) return HARNESS.Cursor;
|
|
22353
|
+
if (t.includes(TITLE_NEEDLES.Codex)) return HARNESS.Codex;
|
|
22354
|
+
if (t.includes(TITLE_NEEDLES.Antigravity)) return HARNESS.Antigravity;
|
|
22151
22355
|
return null;
|
|
22152
22356
|
}
|
|
22153
22357
|
function isLiveRealClaudeCode(rows) {
|
|
22154
22358
|
return rows.some((r) => {
|
|
22155
22359
|
const attrs = safeJson(r.attributes, {});
|
|
22156
|
-
return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) ===
|
|
22360
|
+
return attrs.provenance !== "sample" && resolveHarnessId(attrs, r) === HARNESS.ClaudeCode;
|
|
22157
22361
|
});
|
|
22158
22362
|
}
|
|
22159
22363
|
function toAssetSummary(row) {
|
|
@@ -22421,7 +22625,7 @@ var SqliteInventoryAssetsRepository = class {
|
|
|
22421
22625
|
const isRealHarness = rows.some(
|
|
22422
22626
|
(r) => safeJson(r.attributes, {}).provenance !== "sample"
|
|
22423
22627
|
);
|
|
22424
|
-
const attachConfig = isRealHarness && harnessId ===
|
|
22628
|
+
const attachConfig = isRealHarness && harnessId === HARNESS.ClaudeCode && configAssets.length > 0;
|
|
22425
22629
|
const assets = attachConfig ? [...harnessAssets, ...configAssets].sort((a, b) => a.name.localeCompare(b.name)) : harnessAssets;
|
|
22426
22630
|
if (q && assets.length === 0) continue;
|
|
22427
22631
|
const firstRow = rows[0];
|
|
@@ -23095,9 +23299,10 @@ var SqliteProjectFilesRepository = class {
|
|
|
23095
23299
|
// ../../packages/persistence/src/repositories/resolutions.ts
|
|
23096
23300
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
23097
23301
|
var SqliteResolutionsRepository = class {
|
|
23098
|
-
constructor(db, now = () => Date.now()) {
|
|
23302
|
+
constructor(db, now = () => Date.now(), newId = () => randomUUID7()) {
|
|
23099
23303
|
this.db = db;
|
|
23100
23304
|
this.now = now;
|
|
23305
|
+
this.newId = newId;
|
|
23101
23306
|
this.insertStmt = db.prepare(
|
|
23102
23307
|
`INSERT INTO finding_resolution (id, finding_key, status, method, resolved_at, evidence, created_at)
|
|
23103
23308
|
VALUES (:id, :findingKey, :status, :method, :resolvedAt, :evidence, :createdAt)`
|
|
@@ -23130,12 +23335,14 @@ var SqliteResolutionsRepository = class {
|
|
|
23130
23335
|
}
|
|
23131
23336
|
db;
|
|
23132
23337
|
now;
|
|
23338
|
+
newId;
|
|
23133
23339
|
insertStmt;
|
|
23134
23340
|
latestStmt;
|
|
23135
23341
|
openAtRestStmt;
|
|
23136
23342
|
resolvedAtRestStmt;
|
|
23137
23343
|
/**
|
|
23138
|
-
* Insert one disposition row. The repo mints the id and stamps created_at
|
|
23344
|
+
* Insert one disposition row. The repo mints the id and stamps created_at,
|
|
23345
|
+
* both through the constructor's injectable seams.
|
|
23139
23346
|
* `status`/`method` are typed AND re-parsed here against @akasecurity/schema's
|
|
23140
23347
|
* FindingStatus/ResolutionMethod, so the persisted vocabulary can never drift
|
|
23141
23348
|
* from the schema enums. NOTE for future manual-resolution writers: this is
|
|
@@ -23147,7 +23354,7 @@ var SqliteResolutionsRepository = class {
|
|
|
23147
23354
|
*/
|
|
23148
23355
|
insertResolution(r) {
|
|
23149
23356
|
this.insertStmt.run({
|
|
23150
|
-
id:
|
|
23357
|
+
id: this.newId(),
|
|
23151
23358
|
findingKey: r.findingKey,
|
|
23152
23359
|
status: FindingStatus.parse(r.status),
|
|
23153
23360
|
method: ResolutionMethod.parse(r.method),
|
|
@@ -23726,16 +23933,16 @@ var ACTION_TO_KIND = {
|
|
|
23726
23933
|
warn: "warned"
|
|
23727
23934
|
};
|
|
23728
23935
|
var ENFORCEMENT_KINDS = ["blocked", "redacted", "warned"];
|
|
23729
|
-
var SCAN_COVERAGE =
|
|
23730
|
-
|
|
23731
|
-
|
|
23732
|
-
|
|
23733
|
-
|
|
23734
|
-
|
|
23735
|
-
|
|
23736
|
-
|
|
23737
|
-
|
|
23738
|
-
|
|
23936
|
+
var SCAN_COVERAGE = {
|
|
23937
|
+
[HARNESS.Antigravity]: { coverage: 60, supported: true },
|
|
23938
|
+
[HARNESS.Api]: { coverage: 0, supported: false },
|
|
23939
|
+
[HARNESS.ChatGpt]: { coverage: 40, supported: true },
|
|
23940
|
+
[HARNESS.ClaudeAi]: { coverage: 40, supported: true },
|
|
23941
|
+
[HARNESS.ClaudeCode]: { coverage: 100, supported: true },
|
|
23942
|
+
[HARNESS.Codex]: { coverage: 80, supported: true },
|
|
23943
|
+
[HARNESS.Copilot]: { coverage: 0, supported: false },
|
|
23944
|
+
[HARNESS.Cursor]: { coverage: 0, supported: false }
|
|
23945
|
+
};
|
|
23739
23946
|
var GRANULARITY = {
|
|
23740
23947
|
"7d": "day",
|
|
23741
23948
|
"30d": "day",
|
|
@@ -23834,9 +24041,22 @@ var SqliteSecurityRepository = class {
|
|
|
23834
24041
|
return Promise.resolve({ total, needsRemediation, bySeverity });
|
|
23835
24042
|
}
|
|
23836
24043
|
// Range is echoed but does not change the result today — coverage is a constant
|
|
23837
|
-
// business fact (see SCAN_COVERAGE), not a measured per-window metric.
|
|
24044
|
+
// business fact (see SCAN_COVERAGE), not a measured per-window metric. Order
|
|
24045
|
+
// comes from Provider.options (the enum's declaration order), not from
|
|
24046
|
+
// SCAN_COVERAGE's own key order — deliberately, not because object literals
|
|
24047
|
+
// leave key order unspecified (ES2015 guarantees insertion order for these
|
|
24048
|
+
// non-integer string keys, so iterating SCAN_COVERAGE directly would be
|
|
24049
|
+
// reliable too). The reason is the schema comment's promise: the returned
|
|
24050
|
+
// order must mirror the generated OpenAPI enum list, which is Provider's
|
|
24051
|
+
// contract, not this table's.
|
|
23838
24052
|
scanCoverage(range) {
|
|
23839
|
-
return Promise.resolve({
|
|
24053
|
+
return Promise.resolve({
|
|
24054
|
+
range,
|
|
24055
|
+
providers: Provider.options.map((provider) => ({
|
|
24056
|
+
provider,
|
|
24057
|
+
...SCAN_COVERAGE[provider]
|
|
24058
|
+
}))
|
|
24059
|
+
});
|
|
23840
24060
|
}
|
|
23841
24061
|
enforcementActions(range) {
|
|
23842
24062
|
const lenMs = RANGE_DAYS[range] * DAY_MS4;
|
|
@@ -23894,10 +24114,10 @@ var SqliteSecurityRepository = class {
|
|
|
23894
24114
|
// count; a superseding open/redetected row means the finding is not
|
|
23895
24115
|
// remediated and is excluded, same invariant as severitySummary. Legacy
|
|
23896
24116
|
// at-rest findings with finding_key IS NULL can never have a resolution row
|
|
23897
|
-
// (the lifecycle is keyed by finding_key), so
|
|
23898
|
-
//
|
|
23899
|
-
//
|
|
23900
|
-
// mirroring this file's other methods.
|
|
24117
|
+
// (the lifecycle is keyed by finding_key), so they cannot reach the driving
|
|
24118
|
+
// set below. One raw-row query (fetch the findings with resolution activity in
|
|
24119
|
+
// the window + each one's latest resolution status/method/resolved_at) +
|
|
24120
|
+
// pure-JS filter/bucket/mean, mirroring this file's other methods.
|
|
23901
24121
|
mttrTrend(range) {
|
|
23902
24122
|
const granularity = granularityFor(range);
|
|
23903
24123
|
const bucketMs = (granularity === "day" ? 1 : 7) * DAY_MS4;
|
|
@@ -23913,29 +24133,80 @@ var SqliteSecurityRepository = class {
|
|
|
23913
24133
|
// started_at the upsert overwrites onto inspection_findings.audit_event_id.
|
|
23914
24134
|
// COALESCE onto the parent event's started_at defends against any
|
|
23915
24135
|
// legacy/edge row the backfill left null.
|
|
23916
|
-
`SELECT
|
|
24136
|
+
`SELECT DISTINCT f.finding_key AS finding_key,
|
|
24137
|
+
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at, d.severity AS severity,
|
|
23917
24138
|
latest.status AS latest_status,
|
|
23918
24139
|
latest.method AS latest_method,
|
|
23919
24140
|
latest.resolved_at AS latest_resolved_at
|
|
23920
|
-
FROM
|
|
23921
|
-
JOIN
|
|
23922
|
-
JOIN
|
|
24141
|
+
FROM finding_resolution fr
|
|
24142
|
+
CROSS JOIN inspection_findings f ON f.finding_key = fr.finding_key
|
|
24143
|
+
CROSS JOIN audit_events e ON e.id = f.audit_event_id
|
|
24144
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
23923
24145
|
LEFT JOIN ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
23924
24146
|
ON latest.finding_key = f.finding_key
|
|
23925
|
-
WHERE
|
|
23926
|
-
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})
|
|
23927
|
-
|
|
23928
|
-
SELECT 1 FROM finding_resolution fr
|
|
23929
|
-
WHERE fr.finding_key = f.finding_key
|
|
23930
|
-
AND fr.resolved_at >= :windowStart
|
|
23931
|
-
)`
|
|
23932
|
-
// The EXISTS is a SUPERSET prefilter that bounds the scan to keys with
|
|
23933
|
-
// any resolution activity at/after the window start — a row this method
|
|
24147
|
+
WHERE fr.resolved_at >= :windowStart
|
|
24148
|
+
AND e.event_type IN (${CAPTURE_EVENT_TYPES_SQL})`
|
|
24149
|
+
// `fr` is a SUPERSET prefilter, not the answer: a finding this method
|
|
23934
24150
|
// ultimately counts has its LATEST resolution inside the window, which
|
|
23935
|
-
// implies
|
|
23936
|
-
// latest-wins + status/method + window gate
|
|
23937
|
-
// dialect-agnostic.
|
|
23938
|
-
//
|
|
24151
|
+
// implies a resolution row at/after the window start exists, so nothing
|
|
24152
|
+
// wanted is dropped. The exact latest-wins + status/method + window gate
|
|
24153
|
+
// stays in JS below, dialect-agnostic. `f.finding_key IS NOT NULL` is
|
|
24154
|
+
// implied rather than dropped — the join key comes from
|
|
24155
|
+
// finding_resolution, whose finding_key is NOT NULL.
|
|
24156
|
+
//
|
|
24157
|
+
// IT IS THE DRIVING TABLE THAT MAKES THAT PREFILTER A BOUND, which is
|
|
24158
|
+
// the correction this replaced. Spelled as an `EXISTS` in the WHERE it
|
|
24159
|
+
// READ as a bound and was not one: SQLite drove from `audit_events` on
|
|
24160
|
+
// event_type, joined every capture event to its findings, and evaluated
|
|
24161
|
+
// the EXISTS last — bounding the RESULT and not the scan, so a 7d request
|
|
24162
|
+
// still cost the store's whole trackable history. Measured at 44.6 ms on
|
|
24163
|
+
// 50,000 events and 171.3 ms on 150,000 — linear in the STORE, and in
|
|
24164
|
+
// both cases returning rows for a window holding a fraction of it.
|
|
24165
|
+
//
|
|
24166
|
+
// Two things carry it, and they answer DIFFERENT halves — which is worth
|
|
24167
|
+
// stating precisely, because the obvious reading (both are needed for the
|
|
24168
|
+
// speed) is wrong and was measured to be wrong:
|
|
24169
|
+
//
|
|
24170
|
+
// - **`CROSS JOIN`** is the whole of the store-size fix. In SQLite the
|
|
24171
|
+
// keyword is semantically identical to JOIN and exists only to stop the
|
|
24172
|
+
// tables being reordered; with plain JOINs the planner puts `e` back on
|
|
24173
|
+
// the outside, because with no ANALYZE statistics it prices
|
|
24174
|
+
// `event_type IN (...)` as a selective probe. Reverting it alone takes
|
|
24175
|
+
// the 2k->20k flatness ratio from 1.32 to 16.87.
|
|
24176
|
+
// - **`idx_finding_resolution_resolved_at`** (migration 0021) makes
|
|
24177
|
+
// `resolved_at >= :windowStart` a range SEARCH instead of a bare
|
|
24178
|
+
// `SCAN fr` — finding_key was this table's only index before it, so the
|
|
24179
|
+
// range had none. It buys NO flatness in store size: remove it and the
|
|
24180
|
+
// ratio above does not move, because the latest-resolution derived
|
|
24181
|
+
// table already passes over the whole of finding_resolution, so this
|
|
24182
|
+
// read is O(resolutions) either way and resolutions are not the store.
|
|
24183
|
+
// What it buys is the criterion `hot-read-query-plans.test.ts` enforces
|
|
24184
|
+
// — no hot read may pass over a table with no index — and that is the
|
|
24185
|
+
// guard that goes red when it is dropped. Neither test catches the
|
|
24186
|
+
// other's defect.
|
|
24187
|
+
//
|
|
24188
|
+
// SELECT DISTINCT is a CORRECTNESS requirement of driving from `fr`, not a
|
|
24189
|
+
// tidy-up. finding_resolution is append-only, so a key that was fixed,
|
|
24190
|
+
// redetected and fixed again carries several rows inside one window and
|
|
24191
|
+
// matches once per row — and the value below is a MEAN, so a key matched
|
|
24192
|
+
// three times is a key weighted three times.
|
|
24193
|
+
//
|
|
24194
|
+
// The skew is easy to argue away and the argument is wrong, so it is worth
|
|
24195
|
+
// recording. Duplicate rows for ONE key are identical (every projected
|
|
24196
|
+
// column is per-key: `latest.*` is latest-wins, `first_detected_at` is
|
|
24197
|
+
// preserved), so sums and counts scale together and that key's own mean
|
|
24198
|
+
// does not move. What moves is a bucket holding TWO findings that duplicate
|
|
24199
|
+
// UNEQUALLY: three rows for a 5.9-day fix and one for a 1.9-day fix average
|
|
24200
|
+
// 4.9 days weighted against 3.9 unweighted. Measured, and pinned by
|
|
24201
|
+
// `security.test.ts`'s "weights a finding ONCE however many resolution rows
|
|
24202
|
+
// it has inside the window" — which needed a fixture built for it, since no
|
|
24203
|
+
// single-key case can show it.
|
|
24204
|
+
//
|
|
24205
|
+
// `finding_key` is selected to make the DISTINCT dedup by KEY rather than
|
|
24206
|
+
// by value tuple. On the other columns alone, two genuinely different
|
|
24207
|
+
// findings sharing a severity, a first-detection event and a resolution
|
|
24208
|
+
// instant — one commit fixing two secrets in one file — are one tuple, and
|
|
24209
|
+
// collapsing them would under-count in the other direction.
|
|
23939
24210
|
),
|
|
23940
24211
|
{ windowStart }
|
|
23941
24212
|
);
|
|
@@ -23993,16 +24264,47 @@ var SqliteSecurityRepository = class {
|
|
|
23993
24264
|
}
|
|
23994
24265
|
// Recently-resolved activity feed: findings whose finding_key's LATEST
|
|
23995
24266
|
// finding_resolution row is status:'resolved'/method:'fixed-at-source' —
|
|
23996
|
-
// same latest-resolution-wins
|
|
23997
|
-
//
|
|
23998
|
-
//
|
|
23999
|
-
//
|
|
24000
|
-
//
|
|
24001
|
-
//
|
|
24002
|
-
//
|
|
24003
|
-
//
|
|
24004
|
-
//
|
|
24005
|
-
//
|
|
24267
|
+
// same latest-resolution-wins derived table as severitySummary / mttrTrend
|
|
24268
|
+
// (NOT a plain JOIN, which would surface every historical resolution row for
|
|
24269
|
+
// a key rather than just its current disposition). A key whose latest row is
|
|
24270
|
+
// a superseding 'open'/'redetected' row (the same secret came back) is
|
|
24271
|
+
// excluded — it is not currently resolved. Legacy at-rest findings with
|
|
24272
|
+
// finding_key IS NULL are excluded outright (the resolution lifecycle can
|
|
24273
|
+
// never attach to them). Path comes from the finding's parent event
|
|
24274
|
+
// (event_type 'code_change', attributes.file_path) — mirrors resolutions.ts's
|
|
24275
|
+
// openAtRestStmt accessor. Ordered by resolved_at DESC, capped at `limit`.
|
|
24276
|
+
//
|
|
24277
|
+
// THE RESOLUTION SET DRIVES THIS QUERY, and that is a correctness property of
|
|
24278
|
+
// the plan rather than a preference. Written the other way round — driving
|
|
24279
|
+
// from inspection_findings/audit_events with `latest` LEFT JOINed on — SQLite
|
|
24280
|
+
// cannot use the join key: `f` is reached FROM `latest` by finding_key, so
|
|
24281
|
+
// `latest` gets probed on (rn, status, method) instead and the plan enumerates
|
|
24282
|
+
// every (code_change event x resolved key) pair before `f` can reject it. That
|
|
24283
|
+
// is a cross product, and it is quadratic in the store: measured at 10,966 ms
|
|
24284
|
+
// on a corpus of 50,000 events carrying 2,051 resolutions, against 20 rows
|
|
24285
|
+
// returned. It was invisible for as long as it was, and reported at 8 ms,
|
|
24286
|
+
// because an empty finding_resolution table makes the inner side empty and the
|
|
24287
|
+
// cross product collapses to nothing — so the shape is only observable on a
|
|
24288
|
+
// corpus that seeds resolutions.
|
|
24289
|
+
//
|
|
24290
|
+
// Driving from `latest` instead makes every step below it a unique-index or
|
|
24291
|
+
// primary-key lookup (uq_inspection_findings_key, then audit_events' own PK),
|
|
24292
|
+
// so the cost is the derived table's own — linear in resolutions, which is
|
|
24293
|
+
// what this feed is legitimately about.
|
|
24294
|
+
//
|
|
24295
|
+
// CROSS JOIN is what actually pins that, and it is load-bearing rather than
|
|
24296
|
+
// decorative: in SQLite the keyword is semantically identical to JOIN and
|
|
24297
|
+
// exists only to stop the optimizer reordering the tables. Written as plain
|
|
24298
|
+
// JOINs in this order the planner puts `e` back on the outside — it has no
|
|
24299
|
+
// ANALYZE statistics to price the alternatives with, so it takes
|
|
24300
|
+
// `event_type = 'code_change'` for a selective index probe and rebuilds the
|
|
24301
|
+
// cross product. The FROM order alone was measured to change the plan not at
|
|
24302
|
+
// all.
|
|
24303
|
+
//
|
|
24304
|
+
// The LEFT JOIN it replaced was already an inner join in effect: three
|
|
24305
|
+
// `latest.*` predicates sit in the WHERE, and each of them is false for a
|
|
24306
|
+
// null-extended row. Spelling it JOIN changes no row and stops the plan
|
|
24307
|
+
// reading as though the findings side could drive.
|
|
24006
24308
|
recentlyResolved(limit = 20) {
|
|
24007
24309
|
const rows = allRows(
|
|
24008
24310
|
this.db.prepare(
|
|
@@ -24012,17 +24314,15 @@ var SqliteSecurityRepository = class {
|
|
|
24012
24314
|
json_extract(e.attributes, '$.file_path') AS path,
|
|
24013
24315
|
COALESCE(f.first_detected_at, e.started_at) AS first_detected_at,
|
|
24014
24316
|
latest.resolved_at AS latest_resolved_at
|
|
24015
|
-
FROM
|
|
24016
|
-
JOIN
|
|
24017
|
-
JOIN
|
|
24018
|
-
|
|
24019
|
-
|
|
24020
|
-
WHERE e.event_type = 'code_change'
|
|
24021
|
-
AND f.finding_key IS NOT NULL
|
|
24022
|
-
AND latest.status = 'resolved'
|
|
24317
|
+
FROM ${LATEST_RESOLUTION_BY_KEY_SQL} latest
|
|
24318
|
+
CROSS JOIN inspection_findings f ON f.finding_key = latest.finding_key
|
|
24319
|
+
CROSS JOIN audit_events e ON e.id = f.audit_event_id
|
|
24320
|
+
CROSS JOIN inspection_definitions d ON d.id = f.inspection_definition_id
|
|
24321
|
+
WHERE latest.status = 'resolved'
|
|
24023
24322
|
AND latest.method = 'fixed-at-source'
|
|
24024
24323
|
AND latest.resolved_at IS NOT NULL
|
|
24025
|
-
|
|
24324
|
+
AND e.event_type = 'code_change'
|
|
24325
|
+
ORDER BY latest.resolved_at DESC
|
|
24026
24326
|
LIMIT :limit`
|
|
24027
24327
|
),
|
|
24028
24328
|
{ limit }
|
|
@@ -24959,6 +25259,7 @@ function openAndInitialize(file2) {
|
|
|
24959
25259
|
function openLocalDatabase(dir) {
|
|
24960
25260
|
ensureDataDirSync(dir);
|
|
24961
25261
|
const file2 = join2(dir, DB_FILENAME);
|
|
25262
|
+
reapStalePartials(file2);
|
|
24962
25263
|
const {
|
|
24963
25264
|
db,
|
|
24964
25265
|
events,
|
|
@@ -25393,11 +25694,79 @@ function migrateLegacyLayout(base = defaultDataDir()) {
|
|
|
25393
25694
|
}
|
|
25394
25695
|
}
|
|
25395
25696
|
|
|
25396
|
-
// ../../packages/persistence/src/settings.ts
|
|
25697
|
+
// ../../packages/persistence/src/managed-settings.ts
|
|
25397
25698
|
import { readFileSync as readFileSync3 } from "fs";
|
|
25699
|
+
import { posix, win32 } from "path";
|
|
25700
|
+
function managedSettingsPaths(platform2 = process.platform) {
|
|
25701
|
+
if (platform2 === "darwin") {
|
|
25702
|
+
return [
|
|
25703
|
+
posix.join("/Library", "Application Support", "AKASecurity", MANAGED_SETTINGS_FILENAME),
|
|
25704
|
+
posix.join("/Library", "Managed Preferences", MANAGED_SETTINGS_FILENAME)
|
|
25705
|
+
];
|
|
25706
|
+
}
|
|
25707
|
+
if (platform2 === "win32") {
|
|
25708
|
+
return [win32.join("C:\\", "ProgramData", "AKASecurity", MANAGED_SETTINGS_FILENAME)];
|
|
25709
|
+
}
|
|
25710
|
+
return [posix.join("/etc", "aka", MANAGED_SETTINGS_FILENAME)];
|
|
25711
|
+
}
|
|
25712
|
+
function readManagedSettings(paths = managedSettingsPaths()) {
|
|
25713
|
+
for (const path of paths) {
|
|
25714
|
+
let text;
|
|
25715
|
+
try {
|
|
25716
|
+
text = readFileSync3(path, "utf8");
|
|
25717
|
+
} catch {
|
|
25718
|
+
continue;
|
|
25719
|
+
}
|
|
25720
|
+
const record2 = parseJsonObject(text);
|
|
25721
|
+
if (!record2) continue;
|
|
25722
|
+
const parsed = ManagedSettings.safeParse(record2);
|
|
25723
|
+
if (parsed.success) return parsed.data;
|
|
25724
|
+
}
|
|
25725
|
+
return null;
|
|
25726
|
+
}
|
|
25727
|
+
function overlayManagedSettings(settings, managed, now = () => /* @__PURE__ */ new Date()) {
|
|
25728
|
+
if (!managed) return settings;
|
|
25729
|
+
const { values } = managed;
|
|
25730
|
+
const merged = { ...settings };
|
|
25731
|
+
if (values.runMode !== void 0) merged.runMode = values.runMode;
|
|
25732
|
+
if (values.controlPlane !== void 0) {
|
|
25733
|
+
merged.controlPlane = {
|
|
25734
|
+
...values.controlPlane,
|
|
25735
|
+
// The administrator pinned WHICH deployment, not WHEN this machine
|
|
25736
|
+
// joined it. Keep the user's own attach time when the endpoint is
|
|
25737
|
+
// unchanged, so a managed machine does not appear to re-attach on every
|
|
25738
|
+
// read; stamp a fresh one when the administrator moved it.
|
|
25739
|
+
attachedAt: settings.controlPlane?.endpoint === values.controlPlane.endpoint ? settings.controlPlane.attachedAt : now().toISOString()
|
|
25740
|
+
};
|
|
25741
|
+
}
|
|
25742
|
+
if (values.historicalAccess !== void 0) merged.historicalAccess = values.historicalAccess;
|
|
25743
|
+
if (values.vaultKeyCustody !== void 0) merged.vaultKeyCustody = values.vaultKeyCustody;
|
|
25744
|
+
if (values.vaultInlineReveal !== void 0) merged.vaultInlineReveal = values.vaultInlineReveal;
|
|
25745
|
+
if (values.dataSharesInPlace !== void 0) merged.dataSharesInPlace = values.dataSharesInPlace;
|
|
25746
|
+
if (values.vaultConsent !== void 0) {
|
|
25747
|
+
merged.vaultConsent = values.vaultConsent ? (
|
|
25748
|
+
// Keep an existing valid grant so its acknowledgedAt survives; mint one
|
|
25749
|
+
// at the current version otherwise.
|
|
25750
|
+
settings.vaultConsent?.version === VAULT_CONSENT_VERSION ? settings.vaultConsent : { acknowledgedAt: now().toISOString(), version: VAULT_CONSENT_VERSION }
|
|
25751
|
+
) : void 0;
|
|
25752
|
+
}
|
|
25753
|
+
if (values.modelJudgeConsent !== void 0) {
|
|
25754
|
+
merged.modelJudgeConsent = values.modelJudgeConsent ? settings.modelJudgeConsent?.payloadVersion === MODEL_JUDGE_PAYLOAD_VERSION ? settings.modelJudgeConsent : {
|
|
25755
|
+
acknowledgedAt: now().toISOString(),
|
|
25756
|
+
payloadVersion: MODEL_JUDGE_PAYLOAD_VERSION
|
|
25757
|
+
} : void 0;
|
|
25758
|
+
}
|
|
25759
|
+
return merged;
|
|
25760
|
+
}
|
|
25761
|
+
|
|
25762
|
+
// ../../packages/persistence/src/settings.ts
|
|
25763
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
25398
25764
|
import { join as join5 } from "path";
|
|
25399
25765
|
var SETTINGS_FILENAME = "settings.json";
|
|
25400
25766
|
function readWorkspaceSettings(base = defaultDataDir()) {
|
|
25767
|
+
return overlayManagedSettings(readUserSettings(base), readManagedSettings());
|
|
25768
|
+
}
|
|
25769
|
+
function readUserSettings(base) {
|
|
25401
25770
|
const record2 = readJson(join5(settingsDir(base), SETTINGS_FILENAME));
|
|
25402
25771
|
if (!record2) return defaultWorkspaceSettings();
|
|
25403
25772
|
try {
|
|
@@ -25409,7 +25778,7 @@ function readWorkspaceSettings(base = defaultDataDir()) {
|
|
|
25409
25778
|
function readJson(file2) {
|
|
25410
25779
|
let text;
|
|
25411
25780
|
try {
|
|
25412
|
-
text =
|
|
25781
|
+
text = readFileSync4(file2, "utf8");
|
|
25413
25782
|
} catch {
|
|
25414
25783
|
return null;
|
|
25415
25784
|
}
|
|
@@ -25527,15 +25896,7 @@ function formatPointer(category, keyVersion, pointerId, tag) {
|
|
|
25527
25896
|
// ../../packages/persistence/src/vault/key-provider.ts
|
|
25528
25897
|
import { execFileSync } from "child_process";
|
|
25529
25898
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
25530
|
-
import {
|
|
25531
|
-
chmodSync as chmodSync2,
|
|
25532
|
-
mkdirSync as mkdirSync2,
|
|
25533
|
-
readFileSync as readFileSync4,
|
|
25534
|
-
renameSync as renameSync4,
|
|
25535
|
-
rmSync as rmSync4,
|
|
25536
|
-
statSync as statSync3,
|
|
25537
|
-
writeFileSync as writeFileSync3
|
|
25538
|
-
} from "fs";
|
|
25899
|
+
import { chmodSync as chmodSync2, readFileSync as readFileSync5, renameSync as renameSync4, rmSync as rmSync4, statSync as statSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
25539
25900
|
import { join as join6 } from "path";
|
|
25540
25901
|
var VAULT_OCCUPANT_REASON = {
|
|
25541
25902
|
symlink: "the path is a symlink; remove it so a keyring can be created",
|
|
@@ -25628,7 +25989,8 @@ var LOCK_OWNER_FILE = "owner";
|
|
|
25628
25989
|
var ROTATION_IN_PROGRESS = "vault: a key rotation is already in progress";
|
|
25629
25990
|
function claimRotationLock(lock, owner) {
|
|
25630
25991
|
try {
|
|
25631
|
-
|
|
25992
|
+
mkdirOwnerOnlySync(lock);
|
|
25993
|
+
tightenDir(lock);
|
|
25632
25994
|
} catch (err) {
|
|
25633
25995
|
if (err.code === "EEXIST") return false;
|
|
25634
25996
|
throw asError(err);
|
|
@@ -25670,7 +26032,7 @@ function acquireRotationLock(keysDir2) {
|
|
|
25670
26032
|
}
|
|
25671
26033
|
function releaseRotationLock(lease) {
|
|
25672
26034
|
try {
|
|
25673
|
-
if (
|
|
26035
|
+
if (readFileSync5(join6(lease.lock, LOCK_OWNER_FILE), "utf8").trim() !== lease.owner) return;
|
|
25674
26036
|
} catch {
|
|
25675
26037
|
return;
|
|
25676
26038
|
}
|
|
@@ -25721,7 +26083,7 @@ var FileKeyProvider = class {
|
|
|
25721
26083
|
#read() {
|
|
25722
26084
|
let raw;
|
|
25723
26085
|
try {
|
|
25724
|
-
raw =
|
|
26086
|
+
raw = readFileSync5(this.filePath, "utf8");
|
|
25725
26087
|
} catch (err) {
|
|
25726
26088
|
if (err.code === "ENOENT") return null;
|
|
25727
26089
|
throw err instanceof Error ? err : new Error(String(err));
|
|
@@ -25760,15 +26122,19 @@ var FileKeyProvider = class {
|
|
|
25760
26122
|
* Atomic tmp + rename so a crash mid-write cannot truncate the keyring.
|
|
25761
26123
|
* Used only for rotation, under the rotation lock — first creation goes
|
|
25762
26124
|
* through the creation-exclusive path instead.
|
|
26125
|
+
*
|
|
26126
|
+
* Delegated to the shared owner-only write rather than spelled here, so the
|
|
26127
|
+
* create mode this file is published at is the one paths.ts owns and tests
|
|
26128
|
+
* directly. A local copy of the pair was a second place the mode could be
|
|
26129
|
+
* dropped with the trailing tighten still repairing the end state, which is
|
|
26130
|
+
* the shape no assertion on a published file can see. It also picks up that
|
|
26131
|
+
* primitive's per-process tmp name, its stale-tmp sweep, and an exclusive
|
|
26132
|
+
* create that refuses to follow a symlink planted at the tmp path.
|
|
25763
26133
|
*/
|
|
25764
26134
|
#write(keyring) {
|
|
25765
26135
|
ensureDataDirSync(this.#keysDir);
|
|
25766
|
-
|
|
25767
|
-
|
|
25768
|
-
writeFileSync3(tmp, `${serializeKeyring(keyring)}
|
|
25769
|
-
`, { mode: DATA_FILE_MODE });
|
|
25770
|
-
renameSync4(tmp, file2);
|
|
25771
|
-
tightenFileMode(file2);
|
|
26136
|
+
writeOwnerOnlyFileSync(this.filePath, `${serializeKeyring(keyring)}
|
|
26137
|
+
`);
|
|
25772
26138
|
return keyring;
|
|
25773
26139
|
}
|
|
25774
26140
|
};
|
|
@@ -25778,15 +26144,73 @@ function tightenFileMode(file2) {
|
|
|
25778
26144
|
} catch {
|
|
25779
26145
|
}
|
|
25780
26146
|
}
|
|
25781
|
-
var
|
|
26147
|
+
var SECURITY_TIMEOUT_MS = 5e3;
|
|
26148
|
+
var runSecurity = (args, stdin) => execFileSync("/usr/bin/security", args, {
|
|
25782
26149
|
encoding: "utf8",
|
|
25783
|
-
|
|
26150
|
+
input: stdin,
|
|
26151
|
+
timeout: SECURITY_TIMEOUT_MS,
|
|
26152
|
+
// stderr is discarded rather than captured, and that is deliberate: a
|
|
26153
|
+
// captured stream rides out on an execFileSync error's `.stderr`, and the
|
|
26154
|
+
// write paths here carry the keyring. Exit status is the only thing any
|
|
26155
|
+
// branch below reads.
|
|
26156
|
+
stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "ignore"]
|
|
25784
26157
|
});
|
|
25785
26158
|
var SECURITY_ITEM_NOT_FOUND = 44;
|
|
26159
|
+
function corruptReason(err) {
|
|
26160
|
+
if (err instanceof SyntaxError) return "malformed JSON";
|
|
26161
|
+
return err instanceof Error ? err.message : "unknown";
|
|
26162
|
+
}
|
|
26163
|
+
function securityFailureMeta(err) {
|
|
26164
|
+
const e = err;
|
|
26165
|
+
const parts = [];
|
|
26166
|
+
if (typeof e.status === "number") parts.push(`exit ${String(e.status)}`);
|
|
26167
|
+
if (typeof e.signal === "string" && e.signal) parts.push(`signal ${e.signal}`);
|
|
26168
|
+
if (typeof e.code === "string" && e.code) parts.push(e.code);
|
|
26169
|
+
return parts.length > 0 ? parts.join(", ") : "unknown error";
|
|
26170
|
+
}
|
|
26171
|
+
function writeCommand(keyring, update, keychain) {
|
|
26172
|
+
const hex3 = Buffer.from(serializeKeyring(keyring), "utf8").toString("hex");
|
|
26173
|
+
const parts = [
|
|
26174
|
+
"add-generic-password",
|
|
26175
|
+
...update ? ["-U"] : [],
|
|
26176
|
+
"-s",
|
|
26177
|
+
KEYCHAIN_SERVICE,
|
|
26178
|
+
"-a",
|
|
26179
|
+
KEYCHAIN_ACCOUNT,
|
|
26180
|
+
"-X",
|
|
26181
|
+
hex3
|
|
26182
|
+
];
|
|
26183
|
+
if (keychain !== void 0) {
|
|
26184
|
+
if (/['\\\n\r\0]/.test(keychain)) {
|
|
26185
|
+
throw new Error(
|
|
26186
|
+
"vault: keychain path contains a quote, backslash, line break or NUL, which security -i cannot carry intact"
|
|
26187
|
+
);
|
|
26188
|
+
}
|
|
26189
|
+
parts.push(`'${keychain}'`);
|
|
26190
|
+
}
|
|
26191
|
+
return `${parts.join(" ")}
|
|
26192
|
+
`;
|
|
26193
|
+
}
|
|
25786
26194
|
var KeychainKeyProvider = class {
|
|
25787
26195
|
#keysDir;
|
|
25788
26196
|
#exec;
|
|
25789
|
-
|
|
26197
|
+
#keychain;
|
|
26198
|
+
/**
|
|
26199
|
+
* The trailing keychain argument, or nothing. Every subcommand used here
|
|
26200
|
+
* takes it last (`add-generic-password [keychain]`,
|
|
26201
|
+
* `find-generic-password [keychain...]`), and omitting it means the default
|
|
26202
|
+
* search list. Fixed at construction, so it is built once rather than per
|
|
26203
|
+
* call on the capture path.
|
|
26204
|
+
*/
|
|
26205
|
+
#target;
|
|
26206
|
+
/**
|
|
26207
|
+
* `keychain` names the keychain to operate on, as `security`'s trailing
|
|
26208
|
+
* argument. Production passes nothing and gets the user's default keychain,
|
|
26209
|
+
* which is the whole point of the backend. A test driving the REAL binary
|
|
26210
|
+
* passes a throwaway one, because the alternative is writing vault key
|
|
26211
|
+
* material into the developer's own login keychain and leaving it there.
|
|
26212
|
+
*/
|
|
26213
|
+
constructor(keysDir2, exec = runSecurity, keychain) {
|
|
25790
26214
|
if (exec === runSecurity && process.platform !== "darwin") {
|
|
25791
26215
|
throw new Error(
|
|
25792
26216
|
`keychain custody is not available on this platform (${process.platform}); use file custody`
|
|
@@ -25794,6 +26218,8 @@ var KeychainKeyProvider = class {
|
|
|
25794
26218
|
}
|
|
25795
26219
|
this.#keysDir = keysDir2;
|
|
25796
26220
|
this.#exec = exec;
|
|
26221
|
+
this.#keychain = keychain;
|
|
26222
|
+
this.#target = keychain === void 0 ? [] : [keychain];
|
|
25797
26223
|
}
|
|
25798
26224
|
/** Where a fallback file provider for the same vault would keep its keyring. */
|
|
25799
26225
|
get keysDir() {
|
|
@@ -25832,18 +26258,22 @@ var KeychainKeyProvider = class {
|
|
|
25832
26258
|
KEYCHAIN_SERVICE,
|
|
25833
26259
|
"-a",
|
|
25834
26260
|
KEYCHAIN_ACCOUNT,
|
|
25835
|
-
"-w"
|
|
26261
|
+
"-w",
|
|
26262
|
+
...this.#target
|
|
25836
26263
|
]);
|
|
25837
26264
|
} catch (err) {
|
|
25838
26265
|
if (err.status === SECURITY_ITEM_NOT_FOUND) return null;
|
|
25839
26266
|
throw new Error(
|
|
25840
|
-
`vault: keychain read failed (${
|
|
25841
|
-
{ cause: err }
|
|
26267
|
+
`vault: keychain read failed (${securityFailureMeta(err)}); refusing to treat the failure as an absent keyring`
|
|
25842
26268
|
);
|
|
25843
26269
|
}
|
|
25844
26270
|
const body = raw.trim();
|
|
25845
26271
|
if (body.length === 0) return null;
|
|
25846
|
-
|
|
26272
|
+
try {
|
|
26273
|
+
return parseKeyring(body);
|
|
26274
|
+
} catch (err) {
|
|
26275
|
+
throw new Error(`vault: keychain item is not a usable keyring (${corruptReason(err)})`);
|
|
26276
|
+
}
|
|
25847
26277
|
}
|
|
25848
26278
|
/**
|
|
25849
26279
|
* First mint: a plain `add-generic-password` (no `-U`) fails when an item
|
|
@@ -25851,37 +26281,25 @@ var KeychainKeyProvider = class {
|
|
|
25851
26281
|
* keyring — the loser re-reads and adopts it instead.
|
|
25852
26282
|
*/
|
|
25853
26283
|
#create(keyring) {
|
|
25854
|
-
const
|
|
25855
|
-
"add-generic-password",
|
|
25856
|
-
"-s",
|
|
25857
|
-
KEYCHAIN_SERVICE,
|
|
25858
|
-
"-a",
|
|
25859
|
-
KEYCHAIN_ACCOUNT,
|
|
25860
|
-
"-w",
|
|
25861
|
-
serializeKeyring(keyring)
|
|
25862
|
-
];
|
|
26284
|
+
const line = writeCommand(keyring, false, this.#keychain);
|
|
25863
26285
|
try {
|
|
25864
|
-
this.#exec(
|
|
26286
|
+
this.#exec(["-i"], line);
|
|
25865
26287
|
} catch (err) {
|
|
25866
26288
|
const winner = this.#read();
|
|
25867
26289
|
if (winner) return winner;
|
|
25868
|
-
throw
|
|
26290
|
+
throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
|
|
25869
26291
|
}
|
|
25870
26292
|
return keyring;
|
|
25871
26293
|
}
|
|
25872
26294
|
// `-U` updates the item in place, deliberately replacing the stored map with
|
|
25873
26295
|
// one that contains it — used only for rotation, under the rotation lock.
|
|
25874
26296
|
#replace(keyring) {
|
|
25875
|
-
this.#
|
|
25876
|
-
|
|
25877
|
-
"-
|
|
25878
|
-
|
|
25879
|
-
|
|
25880
|
-
|
|
25881
|
-
KEYCHAIN_ACCOUNT,
|
|
25882
|
-
"-w",
|
|
25883
|
-
serializeKeyring(keyring)
|
|
25884
|
-
]);
|
|
26297
|
+
const line = writeCommand(keyring, true, this.#keychain);
|
|
26298
|
+
try {
|
|
26299
|
+
this.#exec(["-i"], line);
|
|
26300
|
+
} catch (err) {
|
|
26301
|
+
throw new Error(`vault: keychain write failed (${securityFailureMeta(err)})`);
|
|
26302
|
+
}
|
|
25885
26303
|
return keyring;
|
|
25886
26304
|
}
|
|
25887
26305
|
};
|
|
@@ -26385,7 +26803,7 @@ function resolveProviderSafe(resolveProviderFn) {
|
|
|
26385
26803
|
}
|
|
26386
26804
|
|
|
26387
26805
|
// ../../packages/plugin-sdk/src/config-inventory.ts
|
|
26388
|
-
import { readdirSync as readdirSync2, readFileSync as
|
|
26806
|
+
import { readdirSync as readdirSync2, readFileSync as readFileSync7, realpathSync, statSync as statSync5 } from "fs";
|
|
26389
26807
|
import { homedir as homedir2 } from "os";
|
|
26390
26808
|
import { basename as basename3, join as join10 } from "path";
|
|
26391
26809
|
|
|
@@ -27093,6 +27511,10 @@ function luhnCheck(digits) {
|
|
|
27093
27511
|
// ../../packages/detections/src/engine.ts
|
|
27094
27512
|
var keywordMatcher = new KeywordMatcher2();
|
|
27095
27513
|
var regexMatcher = new RegexMatcher2();
|
|
27514
|
+
var MATCHERS = {
|
|
27515
|
+
keyword: (text, rule) => keywordMatcher.match(text, rule),
|
|
27516
|
+
regex: (text, rule) => regexMatcher.match(text, rule)
|
|
27517
|
+
};
|
|
27096
27518
|
var packs = /* @__PURE__ */ new Map();
|
|
27097
27519
|
var POST_VALIDATORS = {
|
|
27098
27520
|
entropy: (value, config2) => isHighEntropy(value, numberOption(config2, "threshold"), numberOption(config2, "minLength")),
|
|
@@ -27108,8 +27530,7 @@ function passesPostValidators(rule, value) {
|
|
|
27108
27530
|
for (const ref of validators) {
|
|
27109
27531
|
const name = typeof ref === "string" ? ref : ref.name;
|
|
27110
27532
|
const config2 = typeof ref === "string" ? void 0 : ref.config;
|
|
27111
|
-
|
|
27112
|
-
if (validate && !validate(value, config2)) return false;
|
|
27533
|
+
if (!POST_VALIDATORS[name](value, config2)) return false;
|
|
27113
27534
|
}
|
|
27114
27535
|
return true;
|
|
27115
27536
|
}
|
|
@@ -27170,14 +27591,7 @@ function scan(text, rules, context) {
|
|
|
27170
27591
|
const candidates = [];
|
|
27171
27592
|
for (const rule of ruleset) {
|
|
27172
27593
|
if (!ruleApplies(rule, extension)) continue;
|
|
27173
|
-
|
|
27174
|
-
if (rule.matcher.type === "keyword") {
|
|
27175
|
-
spans = keywordMatcher.match(text, rule);
|
|
27176
|
-
} else if (rule.matcher.type === "regex") {
|
|
27177
|
-
spans = regexMatcher.match(text, rule);
|
|
27178
|
-
} else {
|
|
27179
|
-
continue;
|
|
27180
|
-
}
|
|
27594
|
+
const spans = MATCHERS[rule.matcher.type](text, rule);
|
|
27181
27595
|
for (const span of spans) {
|
|
27182
27596
|
const rawMatch = text.slice(span.start, span.end);
|
|
27183
27597
|
if (!passesPostValidators(rule, rawMatch)) continue;
|
|
@@ -27419,24 +27833,32 @@ function probesFor(rule) {
|
|
|
27419
27833
|
return [...derived, ...EXPONENTIAL_PROBES, ...POLYNOMIAL_PROBES];
|
|
27420
27834
|
}
|
|
27421
27835
|
var wallClock = () => performance.now();
|
|
27422
|
-
function worstProbeMs(rule, now = wallClock) {
|
|
27836
|
+
function worstProbeMs(rule, now = wallClock, corroborate) {
|
|
27423
27837
|
let ms = 0;
|
|
27424
27838
|
let probe = "";
|
|
27839
|
+
let corroboratedMs;
|
|
27425
27840
|
for (const text of probesFor(rule)) {
|
|
27426
27841
|
const start = now();
|
|
27842
|
+
const corroborateStart = corroborate?.();
|
|
27427
27843
|
scan(text, [rule]);
|
|
27428
27844
|
const elapsed = now() - start;
|
|
27845
|
+
const corroborateEnd = corroborate?.();
|
|
27429
27846
|
if (elapsed > ms) {
|
|
27430
27847
|
ms = elapsed;
|
|
27431
27848
|
probe = text;
|
|
27849
|
+
corroboratedMs = corroborateStart === void 0 || corroborateEnd === void 0 ? void 0 : corroborateEnd - corroborateStart;
|
|
27432
27850
|
}
|
|
27433
27851
|
if (ms >= BUDGET_MS) break;
|
|
27434
27852
|
}
|
|
27435
|
-
return { ms, probe };
|
|
27853
|
+
return { ms, probe, corroboratedMs };
|
|
27436
27854
|
}
|
|
27437
|
-
|
|
27438
|
-
|
|
27439
|
-
|
|
27855
|
+
var CPU_CORROBORATION_SHARE = 0.2;
|
|
27856
|
+
var CORROBORATION_FLOOR_MS = BUDGET_MS * CPU_CORROBORATION_SHARE;
|
|
27857
|
+
function checkRuleTiming(rule, corroborate) {
|
|
27858
|
+
const { ms, probe, corroboratedMs } = worstProbeMs(rule, wallClock, corroborate);
|
|
27859
|
+
const work = corroboratedMs ?? 0;
|
|
27860
|
+
const verdict = ms < BUDGET_MS ? "safe" : work >= CORROBORATION_FLOOR_MS ? "over-budget" : "uncorroborated";
|
|
27861
|
+
return { verdict, worstMs: ms, corroboratedMs: work, probe };
|
|
27440
27862
|
}
|
|
27441
27863
|
|
|
27442
27864
|
// ../../rules/code-flaws/auth-jwt-no-verify.json
|
|
@@ -29472,7 +29894,7 @@ function bundledDetections() {
|
|
|
29472
29894
|
}
|
|
29473
29895
|
|
|
29474
29896
|
// ../../packages/plugin-sdk/src/repo.ts
|
|
29475
|
-
import { existsSync as existsSync6, readFileSync as
|
|
29897
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
29476
29898
|
import { basename as basename2, dirname as dirname2, isAbsolute, join as join9, sep as sep2 } from "path";
|
|
29477
29899
|
function resolveRepo(cwd) {
|
|
29478
29900
|
try {
|
|
@@ -29524,7 +29946,7 @@ function resolveGitContext(root) {
|
|
|
29524
29946
|
}
|
|
29525
29947
|
function safeRead(path) {
|
|
29526
29948
|
try {
|
|
29527
|
-
return
|
|
29949
|
+
return readFileSync6(path, "utf8");
|
|
29528
29950
|
} catch {
|
|
29529
29951
|
return void 0;
|
|
29530
29952
|
}
|
|
@@ -29825,7 +30247,12 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
29825
30247
|
build: (id) => ({ kind: "probe", id, rule }),
|
|
29826
30248
|
reply: (message) => {
|
|
29827
30249
|
if (message.kind !== "probed") return false;
|
|
29828
|
-
resolve2({
|
|
30250
|
+
resolve2({
|
|
30251
|
+
status: "ok",
|
|
30252
|
+
verdict: message.verdict,
|
|
30253
|
+
worstMs: message.worstMs,
|
|
30254
|
+
corroboratedMs: message.corroboratedMs
|
|
30255
|
+
});
|
|
29829
30256
|
return true;
|
|
29830
30257
|
}
|
|
29831
30258
|
},
|
|
@@ -29845,6 +30272,12 @@ function createIsolatedScanner(data, opts = {}) {
|
|
|
29845
30272
|
};
|
|
29846
30273
|
}
|
|
29847
30274
|
|
|
30275
|
+
// ../../packages/plugin-sdk/src/work-clock.ts
|
|
30276
|
+
function workClockMs() {
|
|
30277
|
+
const usage = typeof process.threadCpuUsage === "function" ? process.threadCpuUsage() : process.cpuUsage();
|
|
30278
|
+
return (usage.user + usage.system) / 1e3;
|
|
30279
|
+
}
|
|
30280
|
+
|
|
29848
30281
|
// ../../packages/plugin-sdk/src/rule-quarantine.ts
|
|
29849
30282
|
var PASS_BUDGET_MS = 2e3;
|
|
29850
30283
|
var UNQUARANTINE_HINT = "clear it with `aka detections unquarantine`";
|
|
@@ -29873,6 +30306,14 @@ function warnUnmeasured(rule) {
|
|
|
29873
30306
|
false
|
|
29874
30307
|
);
|
|
29875
30308
|
}
|
|
30309
|
+
function warnUncorroborated(rule, worstMs, corroboratedMs) {
|
|
30310
|
+
warn(
|
|
30311
|
+
rule,
|
|
30312
|
+
"deferred",
|
|
30313
|
+
`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.`,
|
|
30314
|
+
false
|
|
30315
|
+
);
|
|
30316
|
+
}
|
|
29876
30317
|
function warnUnmeasurable(reason, count) {
|
|
29877
30318
|
process.stderr.write(
|
|
29878
30319
|
`[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.
|
|
@@ -29919,24 +30360,31 @@ async function filterUnsafeRules(rules, gateway, opts) {
|
|
|
29919
30360
|
warnUnmeasured(rule);
|
|
29920
30361
|
continue;
|
|
29921
30362
|
}
|
|
29922
|
-
let
|
|
30363
|
+
let verdict;
|
|
29923
30364
|
let worstMs;
|
|
30365
|
+
let corroboratedMs = 0;
|
|
29924
30366
|
if (prober) {
|
|
29925
30367
|
const outcome = await prober.probe(rule);
|
|
29926
30368
|
if (outcome.status === "unavailable") {
|
|
29927
30369
|
unmeasurable.set(outcome.reason, (unmeasurable.get(outcome.reason) ?? 0) + 1);
|
|
29928
30370
|
continue;
|
|
29929
30371
|
}
|
|
29930
|
-
|
|
30372
|
+
verdict = outcome.status === "ok" ? outcome.verdict : "over-budget";
|
|
29931
30373
|
worstMs = outcome.status === "ok" ? outcome.worstMs : outcome.elapsedMs;
|
|
30374
|
+
if (outcome.status === "ok") corroboratedMs = outcome.corroboratedMs;
|
|
29932
30375
|
} else {
|
|
29933
30376
|
try {
|
|
29934
|
-
({
|
|
30377
|
+
({ verdict, worstMs, corroboratedMs } = checkRuleTiming(rule, workClockMs));
|
|
29935
30378
|
} catch {
|
|
29936
|
-
|
|
30379
|
+
verdict = "over-budget";
|
|
29937
30380
|
worstMs = Number.POSITIVE_INFINITY;
|
|
29938
30381
|
}
|
|
29939
30382
|
}
|
|
30383
|
+
if (verdict === "uncorroborated") {
|
|
30384
|
+
warnUncorroborated(rule, worstMs, corroboratedMs);
|
|
30385
|
+
continue;
|
|
30386
|
+
}
|
|
30387
|
+
const isSafe = verdict === "safe";
|
|
29940
30388
|
let persisted = false;
|
|
29941
30389
|
try {
|
|
29942
30390
|
await gateway.setRuleProbeVerdict(key, isSafe ? "safe" : "quarantined", worstMs);
|
|
@@ -30036,12 +30484,17 @@ function createGuardedScanner(partition, gateway, opts) {
|
|
|
30036
30484
|
};
|
|
30037
30485
|
}
|
|
30038
30486
|
|
|
30487
|
+
// ../../packages/plugin-sdk/src/ignore-layers.ts
|
|
30488
|
+
var import_ignore = __toESM(require_ignore(), 1);
|
|
30489
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
30490
|
+
import { join as join11 } from "path";
|
|
30491
|
+
|
|
30039
30492
|
// ../../packages/plugin-sdk/src/inventory-resolver.ts
|
|
30040
30493
|
import { arch, hostname as hostname4, platform, release } from "os";
|
|
30041
30494
|
|
|
30042
30495
|
// ../../packages/plugin-sdk/src/nudge.ts
|
|
30043
|
-
import { mkdirSync as
|
|
30044
|
-
import { join as
|
|
30496
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync9, writeFileSync as writeFileSync5 } from "fs";
|
|
30497
|
+
import { join as join12 } from "path";
|
|
30045
30498
|
|
|
30046
30499
|
// ../../packages/plugin-sdk/src/paths.ts
|
|
30047
30500
|
import { readdirSync as readdirSync3, realpathSync as realpathSync2 } from "fs";
|
|
@@ -30058,9 +30511,8 @@ function applyCategoryPosture(posture, repo, mode = "fill-gaps") {
|
|
|
30058
30511
|
}
|
|
30059
30512
|
|
|
30060
30513
|
// ../../packages/plugin-sdk/src/project-files.ts
|
|
30061
|
-
|
|
30062
|
-
import {
|
|
30063
|
-
import { basename as basename5, join as join12 } from "path";
|
|
30514
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4 } from "fs";
|
|
30515
|
+
import { basename as basename5, join as join13 } from "path";
|
|
30064
30516
|
|
|
30065
30517
|
// ../../packages/plugin-sdk/src/provider-env-antigravity.ts
|
|
30066
30518
|
var optionalBaseUrl2 = external_exports.preprocess((v) => {
|
|
@@ -30129,6 +30581,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30129
30581
|
let initialized = false;
|
|
30130
30582
|
const ruleActionIndex = /* @__PURE__ */ new Map();
|
|
30131
30583
|
const categoryActionIndex = /* @__PURE__ */ new Map();
|
|
30584
|
+
let reversibleRuleIndex = /* @__PURE__ */ new Set();
|
|
30132
30585
|
async function ensureInitialized() {
|
|
30133
30586
|
if (initialized) return;
|
|
30134
30587
|
const bundle = await gateway.getPolicyBundle();
|
|
@@ -30141,6 +30594,7 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30141
30594
|
categoryActionIndex.set(p.target.category, p.action);
|
|
30142
30595
|
}
|
|
30143
30596
|
}
|
|
30597
|
+
reversibleRuleIndex = new Set(bundle.reversibleRuleIds ?? []);
|
|
30144
30598
|
const bundledProbeKeys = new Set(
|
|
30145
30599
|
getLoadedRules().map(ruleProbeKey).filter((key) => key !== void 0)
|
|
30146
30600
|
);
|
|
@@ -30219,11 +30673,13 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30219
30673
|
if (worst === "block") return { action: "block", text: null, findings };
|
|
30220
30674
|
if (worst === "redact") {
|
|
30221
30675
|
const redactFindings = findings.filter((f) => actionFor(f) === "redact");
|
|
30676
|
+
const reversibleFindings = redactFindings.filter((f) => reversibleRuleIndex.has(f.ruleId));
|
|
30222
30677
|
return {
|
|
30223
30678
|
action: "redact",
|
|
30224
30679
|
text: redact(text, redactFindings),
|
|
30225
30680
|
findings,
|
|
30226
|
-
enforcedFindings: redactFindings
|
|
30681
|
+
enforcedFindings: redactFindings,
|
|
30682
|
+
reversibleFindings
|
|
30227
30683
|
};
|
|
30228
30684
|
}
|
|
30229
30685
|
return { action: worst, text, findings };
|
|
@@ -30437,8 +30893,8 @@ function createPluginRuntime(gateway, settings, opts) {
|
|
|
30437
30893
|
var THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1e3;
|
|
30438
30894
|
|
|
30439
30895
|
// ../../packages/plugin-sdk/src/throttle.ts
|
|
30440
|
-
import { mkdirSync as
|
|
30441
|
-
import { join as
|
|
30896
|
+
import { mkdirSync as mkdirSync3, statSync as statSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
30897
|
+
import { join as join14 } from "path";
|
|
30442
30898
|
|
|
30443
30899
|
// ../../packages/plugin-sdk/src/tokenize.ts
|
|
30444
30900
|
function redactedPlaceholder(category) {
|
|
@@ -30501,6 +30957,8 @@ var SecretVaultGlue = class {
|
|
|
30501
30957
|
async tokenizeText(text, opts) {
|
|
30502
30958
|
try {
|
|
30503
30959
|
const findings = opts?.findings ?? this.#selfScan(text);
|
|
30960
|
+
const reversible = opts?.reversible;
|
|
30961
|
+
const keeps = (finding) => reversible === void 0 || reversible.has(finding);
|
|
30504
30962
|
if (findings === null) return { text: "[REDACTED]", pointers: [], degraded: [] };
|
|
30505
30963
|
if (findings.length === 0) return { text, pointers: [], degraded: [] };
|
|
30506
30964
|
const groups = groupSpans(text, findings);
|
|
@@ -30517,6 +30975,8 @@ var SecretVaultGlue = class {
|
|
|
30517
30975
|
} else if (original !== finding.rawMatch) {
|
|
30518
30976
|
replacement = redactedPlaceholder(group.category);
|
|
30519
30977
|
degraded.unshift({ category: group.category });
|
|
30978
|
+
} else if (!keeps(finding)) {
|
|
30979
|
+
replacement = redactedPlaceholder(finding.category);
|
|
30520
30980
|
} else {
|
|
30521
30981
|
replacement = await this.tokenizeValue(finding.rawMatch, {
|
|
30522
30982
|
ruleId: finding.ruleId,
|
|
@@ -30777,7 +31237,7 @@ function routeRemediationOption(option, handlers) {
|
|
|
30777
31237
|
|
|
30778
31238
|
// ../../packages/setup-wizard/src/remediation/rotation-checklist.ts
|
|
30779
31239
|
import { writeFileSync as writeFileSync7 } from "fs";
|
|
30780
|
-
import { join as
|
|
31240
|
+
import { join as join15 } from "path";
|
|
30781
31241
|
var GENERIC_CONSOLE_PATH = "rotate via the provider's own console";
|
|
30782
31242
|
var CONSOLE_PATHS = {
|
|
30783
31243
|
anthropic: "console.anthropic.com \u2192 Settings \u2192 API keys",
|
|
@@ -30877,7 +31337,7 @@ function generateRotationChecklist(input) {
|
|
|
30877
31337
|
try {
|
|
30878
31338
|
const target = resolveRotationChecklistTarget(input.cwd);
|
|
30879
31339
|
targetDirectory = target.directory;
|
|
30880
|
-
const filePath =
|
|
31340
|
+
const filePath = join15(target.directory, "rotation-checklist.md");
|
|
30881
31341
|
writeRotationChecklist(input.entries, target.directory);
|
|
30882
31342
|
return {
|
|
30883
31343
|
status: "written",
|
|
@@ -30982,10 +31442,15 @@ function writeStandingSecretPosture(level, policies) {
|
|
|
30982
31442
|
}
|
|
30983
31443
|
}
|
|
30984
31444
|
|
|
31445
|
+
// ../../packages/setup-wizard/src/triage/merge.ts
|
|
31446
|
+
var RANK = Object.fromEntries(
|
|
31447
|
+
KNOWN_BUILTIN_IDS.map((id, i) => [id, i])
|
|
31448
|
+
);
|
|
31449
|
+
|
|
30985
31450
|
// ../../packages/setup-wizard/src/triage/plan-file.ts
|
|
30986
|
-
import { mkdtempSync, readFileSync as
|
|
31451
|
+
import { mkdtempSync, readFileSync as readFileSync10, rmdirSync, rmSync as rmSync5, writeFileSync as writeFileSync8 } from "fs";
|
|
30987
31452
|
import { tmpdir } from "os";
|
|
30988
|
-
import { basename as basename6, dirname as dirname4, join as
|
|
31453
|
+
import { basename as basename6, dirname as dirname4, join as join16 } from "path";
|
|
30989
31454
|
var SuppressionEntrySchema = external_exports.object({
|
|
30990
31455
|
ruleId: external_exports.string(),
|
|
30991
31456
|
category: DetectionCategory,
|
|
@@ -31189,7 +31654,7 @@ function renderRemediationDecision(findings, moreCount, registry2) {
|
|
|
31189
31654
|
}
|
|
31190
31655
|
|
|
31191
31656
|
// src/remediation/surfaced-redact.ts
|
|
31192
|
-
import { readFileSync as
|
|
31657
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
31193
31658
|
|
|
31194
31659
|
// ../../packages/plugin-runtime/src/standalone-gateway.ts
|
|
31195
31660
|
import { randomUUID as randomUUID15 } from "crypto";
|
|
@@ -31202,6 +31667,8 @@ var StandaloneDataGateway = class {
|
|
|
31202
31667
|
db;
|
|
31203
31668
|
// Kept for the fingerprint key lookup (exception.key lives beside the store).
|
|
31204
31669
|
dataDir;
|
|
31670
|
+
// One notice per gateway — see warnRulesetDiscarded.
|
|
31671
|
+
warnedRulesetDiscarded = false;
|
|
31205
31672
|
constructor(dataDir2, detections = [], meta3) {
|
|
31206
31673
|
this.db = openLocalDatabase(dataDir2);
|
|
31207
31674
|
this.dataDir = dataDir2;
|
|
@@ -31321,28 +31788,69 @@ var StandaloneDataGateway = class {
|
|
|
31321
31788
|
// - ANY invalid rule among enabled packs (all-invalid, partial corruption,
|
|
31322
31789
|
// or a single malformed entry) → undefined → bundled fallback. Serving a
|
|
31323
31790
|
// reduced "complete" set would silently drop exactly the corrupted rules
|
|
31324
|
-
// with no fallback
|
|
31325
|
-
// never loses coverage
|
|
31326
|
-
//
|
|
31327
|
-
//
|
|
31791
|
+
// with no fallback. The bundled packs are a superset of AKA's OWN packs,
|
|
31792
|
+
// so falling back never loses coverage there — but they contain no
|
|
31793
|
+
// pulled or custom pack, so for those this trades a partial ruleset for
|
|
31794
|
+
// none of them plus the loss of every pack's per-detection enforcement
|
|
31795
|
+
// action. That is deliberate (a store this machine cannot fully validate
|
|
31796
|
+
// is not authoritative), and it is why the cost of REJECTING a rule
|
|
31797
|
+
// matters: `Rule` is strict, so one unrecognized key in one custom rule
|
|
31798
|
+
// reaches this branch, not just a genuinely malformed or foreign store.
|
|
31799
|
+
// `installed-packs.test.ts` pins that per-rule counting;
|
|
31328
31800
|
// - enabled packs that produce ZERO rules with no invalids (e.g. every
|
|
31329
31801
|
// enabled pack's rules_json is `[]`) → undefined → bundled fallback: an
|
|
31330
31802
|
// enabled pack contributing nothing is untrustworthy, not a real
|
|
31331
31803
|
// "detect nothing" (that is expressed by disabling packs, handled above);
|
|
31332
31804
|
// - otherwise → the enabled packs' validated rules, marked complete.
|
|
31805
|
+
/**
|
|
31806
|
+
* The discard above is the one ruleset decision this gateway reaches on its
|
|
31807
|
+
* own, and it is the most expensive one here: ONE rejected entry costs the
|
|
31808
|
+
* user every custom rule and every per-detection enforcement action, replaced
|
|
31809
|
+
* by bundled packs that contain neither. Nothing else reports it — a hook is a
|
|
31810
|
+
* short-lived process whose stderr is the only channel it has — so name what
|
|
31811
|
+
* was rejected and where the rest of the list lives.
|
|
31812
|
+
*
|
|
31813
|
+
* Unlike a quarantine verdict this caches nothing: the rejection is re-derived
|
|
31814
|
+
* from the store on every run, so the recovery is to fix or reinstall the pack,
|
|
31815
|
+
* and no line here may offer a command that clears a stored verdict.
|
|
31816
|
+
*
|
|
31817
|
+
* Written at most once per gateway — a second getPolicyBundle() in the same
|
|
31818
|
+
* process would re-report the same finding.
|
|
31819
|
+
*/
|
|
31820
|
+
warnRulesetDiscarded(snapshot) {
|
|
31821
|
+
if (this.warnedRulesetDiscarded) return;
|
|
31822
|
+
this.warnedRulesetDiscarded = true;
|
|
31823
|
+
const listed = snapshot.rejectedRules.map((r) => `${r.pack}${r.ruleId === null ? "" : ` "${r.ruleId}"`} (${r.reason})`).join(", ");
|
|
31824
|
+
const undisclosed = snapshot.invalidRules - snapshot.rejectedRules.length;
|
|
31825
|
+
const more = undisclosed > 0 ? `, and ${String(undisclosed)} more` : "";
|
|
31826
|
+
process.stderr.write(
|
|
31827
|
+
`[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\`
|
|
31828
|
+
`
|
|
31829
|
+
);
|
|
31830
|
+
}
|
|
31333
31831
|
installedScanRules() {
|
|
31334
31832
|
try {
|
|
31335
31833
|
const snapshot = this.db.installedPacks.installedRuleset();
|
|
31336
31834
|
if (snapshot.installedPacks === 0) return void 0;
|
|
31337
31835
|
if (snapshot.enabledPacks === 0) {
|
|
31338
|
-
return {
|
|
31836
|
+
return {
|
|
31837
|
+
rules: [],
|
|
31838
|
+
ruleActions: /* @__PURE__ */ new Map(),
|
|
31839
|
+
ruleVersions: /* @__PURE__ */ new Map(),
|
|
31840
|
+
reversibleRules: /* @__PURE__ */ new Set(),
|
|
31841
|
+
complete: true
|
|
31842
|
+
};
|
|
31843
|
+
}
|
|
31844
|
+
if (snapshot.invalidRules > 0) {
|
|
31845
|
+
this.warnRulesetDiscarded(snapshot);
|
|
31846
|
+
return void 0;
|
|
31339
31847
|
}
|
|
31340
|
-
if (snapshot.invalidRules > 0) return void 0;
|
|
31341
31848
|
if (snapshot.rules.length === 0) return void 0;
|
|
31342
31849
|
return {
|
|
31343
31850
|
rules: snapshot.rules,
|
|
31344
31851
|
ruleActions: snapshot.ruleActions,
|
|
31345
31852
|
ruleVersions: snapshot.ruleVersions,
|
|
31853
|
+
reversibleRules: snapshot.reversibleRules,
|
|
31346
31854
|
complete: true
|
|
31347
31855
|
};
|
|
31348
31856
|
} catch {
|
|
@@ -31370,6 +31878,11 @@ var StandaloneDataGateway = class {
|
|
|
31370
31878
|
return {
|
|
31371
31879
|
version: "local",
|
|
31372
31880
|
policies: [...policies, ...rulePolicies],
|
|
31881
|
+
// The reversibility half of each pack's assignment. Emitted only under the
|
|
31882
|
+
// authoritative installed snapshot, exactly like rulePolicies above: the
|
|
31883
|
+
// bundled-packs fallback carries no per-pack assignment, so it carries no
|
|
31884
|
+
// reversibility either and every redaction there stays one-way.
|
|
31885
|
+
reversibleRuleIds: installed ? [...installed.reversibleRules] : [],
|
|
31373
31886
|
rules: installed ? installed.rules : [],
|
|
31374
31887
|
...installed ? { rulesComplete: true } : {},
|
|
31375
31888
|
...installed ? { ruleVersions: Object.fromEntries(installed.ruleVersions) } : {},
|
|
@@ -31388,29 +31901,32 @@ var StandaloneDataGateway = class {
|
|
|
31388
31901
|
return this.db.exceptions.recordBlocked(entry);
|
|
31389
31902
|
}
|
|
31390
31903
|
// Retention sweep over TERMINAL exception rows (revoked / expired / budget
|
|
31391
|
-
// exhausted) —
|
|
31392
|
-
//
|
|
31904
|
+
// exhausted) — local-store maintenance, invoked from SessionStart through the
|
|
31905
|
+
// LocalStoreMaintenance capability rather than the DataGateway port. Active
|
|
31906
|
+
// grants are never touched.
|
|
31393
31907
|
sweepTerminalExceptions(retentionMs) {
|
|
31394
31908
|
return this.db.exceptions.sweepTerminal(retentionMs);
|
|
31395
31909
|
}
|
|
31396
|
-
// The warn-era enforcement cap
|
|
31397
|
-
//
|
|
31398
|
-
// of block/redact rows capped to
|
|
31399
|
-
// already-capped one).
|
|
31910
|
+
// The warn-era enforcement cap — local-store maintenance, invoked from
|
|
31911
|
+
// SessionStart through the LocalStoreMaintenance capability rather than
|
|
31912
|
+
// the DataGateway port. Returns the number of block/redact rows capped to
|
|
31913
|
+
// warn (0 for a redact-policy store or an already-capped one).
|
|
31400
31914
|
capWarnEraEnforcement(policyMode) {
|
|
31401
31915
|
const { capped } = capWarnEraEnforcementOnce(this.db, policyMode, this.dataDir);
|
|
31402
31916
|
return { capped };
|
|
31403
31917
|
}
|
|
31404
31918
|
// One project-file scan → the local project_file tree (one transaction inside
|
|
31405
|
-
// the LocalDatabase, fail-open there). Like the sweep above, this is
|
|
31406
|
-
//
|
|
31919
|
+
// the LocalDatabase, fail-open there). Like the sweep above, this is reached
|
|
31920
|
+
// through the LocalStoreMaintenance capability rather than the DataGateway
|
|
31921
|
+
// port: the file tree is a local-store read model.
|
|
31407
31922
|
recordProjectFiles(projectId, scan2) {
|
|
31408
31923
|
this.db.recordProjectFiles(projectId, scan2);
|
|
31409
31924
|
return Promise.resolve();
|
|
31410
31925
|
}
|
|
31411
31926
|
// Fold ghost source_project rows minted by the pre-worktree-fix resolver
|
|
31412
|
-
// (checkout-path identities) into the repo's canonical row.
|
|
31413
|
-
//
|
|
31927
|
+
// (checkout-path identities) into the repo's canonical row. Local-store
|
|
31928
|
+
// maintenance, invoked from SessionStart through the LocalStoreMaintenance
|
|
31929
|
+
// capability. Fail-open in the store.
|
|
31414
31930
|
reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot) {
|
|
31415
31931
|
this.db.reconcileWorktreeProjects(canonicalId, headRoot, worktreeRoot);
|
|
31416
31932
|
return Promise.resolve();
|
|
@@ -31421,10 +31937,10 @@ var StandaloneDataGateway = class {
|
|
|
31421
31937
|
* executing the plugin generation they started with (Claude Code caches
|
|
31422
31938
|
* plugin versions), and the write gate makes their installed-pack writes
|
|
31423
31939
|
* silent no-ops — this is the one-line nudge telling the user WHY, and that
|
|
31424
|
-
* a restart picks the newer plugin up.
|
|
31425
|
-
* SessionStart
|
|
31426
|
-
* null (no notice), and
|
|
31427
|
-
* never fire it.
|
|
31940
|
+
* a restart picks the newer plugin up. Local-store maintenance, invoked
|
|
31941
|
+
* from SessionStart through the LocalStoreMaintenance capability rather
|
|
31942
|
+
* than the DataGateway port. Fail-open: any error → null (no notice), and
|
|
31943
|
+
* unparseable versions compare equal so garbage can never fire it.
|
|
31428
31944
|
*/
|
|
31429
31945
|
staleBinaryNotice(currentVersion) {
|
|
31430
31946
|
try {
|
|
@@ -31502,7 +32018,8 @@ var StandaloneDataGateway = class {
|
|
|
31502
32018
|
|
|
31503
32019
|
// ../../packages/plugin-runtime/src/resolve.ts
|
|
31504
32020
|
var standaloneGatewayFactory = (config2, meta3) => new StandaloneDataGateway(config2.dataDir, bundledDetections(), meta3);
|
|
31505
|
-
|
|
32021
|
+
var defaultGatewayFactory = standaloneGatewayFactory;
|
|
32022
|
+
function resolveDataGateway(config2, meta3, gatewayFactory = defaultGatewayFactory) {
|
|
31506
32023
|
return gatewayFactory(config2, meta3);
|
|
31507
32024
|
}
|
|
31508
32025
|
|
|
@@ -31511,16 +32028,16 @@ import { randomUUID as randomUUID16 } from "crypto";
|
|
|
31511
32028
|
var EXCEPTION_RETENTION_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
31512
32029
|
|
|
31513
32030
|
// src/history/transcripts.ts
|
|
31514
|
-
import { readdirSync as readdirSync6, readFileSync as
|
|
32031
|
+
import { readdirSync as readdirSync6, readFileSync as readFileSync11 } from "fs";
|
|
31515
32032
|
import { homedir as homedir3 } from "os";
|
|
31516
|
-
import { join as
|
|
32033
|
+
import { join as join17 } from "path";
|
|
31517
32034
|
function transcriptsDir(home) {
|
|
31518
|
-
return
|
|
32035
|
+
return join17(home ?? homedir3(), ".claude", "projects");
|
|
31519
32036
|
}
|
|
31520
32037
|
var DAY_MS5 = 24 * 60 * 60 * 1e3;
|
|
31521
32038
|
|
|
31522
32039
|
// src/remediation/redact.ts
|
|
31523
|
-
import { readFileSync as
|
|
32040
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync3, renameSync as renameSync5, rmSync as rmSync6, writeFileSync as writeFileSync9 } from "fs";
|
|
31524
32041
|
import { isAbsolute as isAbsolute2, relative, resolve } from "path";
|
|
31525
32042
|
var REDACTED_PLACEHOLDER = "[REDACTED:SECRET]";
|
|
31526
32043
|
var REPLACE_PATTERN_SEQUENCE = /\$[$&`'<0-9]/;
|
|
@@ -31568,7 +32085,7 @@ function redactLeakedKeysDetailed(targets, scope = platformRedactionScope(), rep
|
|
|
31568
32085
|
for (const [filePath, fileTargets] of byFile) {
|
|
31569
32086
|
let content;
|
|
31570
32087
|
try {
|
|
31571
|
-
content =
|
|
32088
|
+
content = readFileSync12(filePath, "utf8");
|
|
31572
32089
|
} catch {
|
|
31573
32090
|
continue;
|
|
31574
32091
|
}
|
|
@@ -31691,7 +32208,7 @@ async function redactSurfacedSecrets(findings, overrides = {}) {
|
|
|
31691
32208
|
for (const [filePath, fileFindings] of byFile) {
|
|
31692
32209
|
let content;
|
|
31693
32210
|
try {
|
|
31694
|
-
content =
|
|
32211
|
+
content = readFileSync13(filePath, "utf8");
|
|
31695
32212
|
} catch {
|
|
31696
32213
|
unrecovered.push(...fileFindings);
|
|
31697
32214
|
continue;
|
|
@@ -31793,7 +32310,7 @@ function writeSecretPosture(level) {
|
|
|
31793
32310
|
}
|
|
31794
32311
|
}
|
|
31795
32312
|
function requireRedactPosture(rawPosture) {
|
|
31796
|
-
const parsedPosture =
|
|
32313
|
+
const parsedPosture = CategoryPolicyId.safeParse(rawPosture);
|
|
31797
32314
|
if (!parsedPosture.success) {
|
|
31798
32315
|
fail(`redact route requires a valid --posture (got ${JSON.stringify(rawPosture)})`);
|
|
31799
32316
|
}
|
|
@@ -31856,7 +32373,7 @@ if (process.argv[1] && fileURLToPath3(import.meta.url) === process.argv[1]) {
|
|
|
31856
32373
|
try {
|
|
31857
32374
|
const argv = process.argv.slice(2);
|
|
31858
32375
|
const optionIndex = argv.indexOf("--option");
|
|
31859
|
-
const frameText =
|
|
32376
|
+
const frameText = readFileSync14(0, "utf8");
|
|
31860
32377
|
if (optionIndex === -1) {
|
|
31861
32378
|
present(frameText);
|
|
31862
32379
|
} else {
|