codegate-ai 0.1.0

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.
Files changed (147) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +390 -0
  3. package/dist/cli-prompts.d.ts +6 -0
  4. package/dist/cli-prompts.js +94 -0
  5. package/dist/cli.d.ts +64 -0
  6. package/dist/cli.js +443 -0
  7. package/dist/commands/run-policy.d.ts +27 -0
  8. package/dist/commands/run-policy.js +39 -0
  9. package/dist/commands/scan-command/helpers.d.ts +28 -0
  10. package/dist/commands/scan-command/helpers.js +233 -0
  11. package/dist/commands/scan-command.d.ts +90 -0
  12. package/dist/commands/scan-command.js +403 -0
  13. package/dist/commands/undo.d.ts +5 -0
  14. package/dist/commands/undo.js +14 -0
  15. package/dist/config.d.ts +50 -0
  16. package/dist/config.js +187 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/knowledge-base/claude-code.json +152 -0
  20. package/dist/knowledge-base/cline.json +224 -0
  21. package/dist/knowledge-base/codex.json +162 -0
  22. package/dist/knowledge-base/copilot.json +132 -0
  23. package/dist/knowledge-base/cursor.json +134 -0
  24. package/dist/knowledge-base/gemini-cli.json +112 -0
  25. package/dist/knowledge-base/jetbrains-junie.json +208 -0
  26. package/dist/knowledge-base/kiro.json +102 -0
  27. package/dist/knowledge-base/opencode.json +128 -0
  28. package/dist/knowledge-base/roo-code.json +116 -0
  29. package/dist/knowledge-base/schema.json +77 -0
  30. package/dist/knowledge-base/windsurf.json +80 -0
  31. package/dist/knowledge-base/zed.json +88 -0
  32. package/dist/layer1-discovery/config-parser.d.ts +12 -0
  33. package/dist/layer1-discovery/config-parser.js +52 -0
  34. package/dist/layer1-discovery/file-walker.d.ts +13 -0
  35. package/dist/layer1-discovery/file-walker.js +77 -0
  36. package/dist/layer1-discovery/knowledge-base.d.ts +36 -0
  37. package/dist/layer1-discovery/knowledge-base.js +58 -0
  38. package/dist/layer1-discovery/tool-detector.d.ts +20 -0
  39. package/dist/layer1-discovery/tool-detector.js +138 -0
  40. package/dist/layer2-static/detectors/command-exec.d.ts +11 -0
  41. package/dist/layer2-static/detectors/command-exec.js +343 -0
  42. package/dist/layer2-static/detectors/consent-bypass.d.ts +8 -0
  43. package/dist/layer2-static/detectors/consent-bypass.js +330 -0
  44. package/dist/layer2-static/detectors/env-override.d.ts +8 -0
  45. package/dist/layer2-static/detectors/env-override.js +132 -0
  46. package/dist/layer2-static/detectors/git-hooks.d.ts +11 -0
  47. package/dist/layer2-static/detectors/git-hooks.js +61 -0
  48. package/dist/layer2-static/detectors/ide-settings.d.ts +8 -0
  49. package/dist/layer2-static/detectors/ide-settings.js +66 -0
  50. package/dist/layer2-static/detectors/plugin-manifest.d.ts +9 -0
  51. package/dist/layer2-static/detectors/plugin-manifest.js +1943 -0
  52. package/dist/layer2-static/detectors/rule-file.d.ts +7 -0
  53. package/dist/layer2-static/detectors/rule-file.js +299 -0
  54. package/dist/layer2-static/detectors/symlink.d.ts +9 -0
  55. package/dist/layer2-static/detectors/symlink.js +45 -0
  56. package/dist/layer2-static/engine.d.ts +28 -0
  57. package/dist/layer2-static/engine.js +83 -0
  58. package/dist/layer2-static/evidence.d.ts +12 -0
  59. package/dist/layer2-static/evidence.js +128 -0
  60. package/dist/layer2-static/rule-engine.d.ts +24 -0
  61. package/dist/layer2-static/rule-engine.js +138 -0
  62. package/dist/layer2-static/state/scan-state.d.ts +32 -0
  63. package/dist/layer2-static/state/scan-state.js +296 -0
  64. package/dist/layer3-dynamic/command-builder.d.ts +15 -0
  65. package/dist/layer3-dynamic/command-builder.js +39 -0
  66. package/dist/layer3-dynamic/local-text-analysis.d.ts +19 -0
  67. package/dist/layer3-dynamic/local-text-analysis.js +73 -0
  68. package/dist/layer3-dynamic/meta-agent.d.ts +17 -0
  69. package/dist/layer3-dynamic/meta-agent.js +33 -0
  70. package/dist/layer3-dynamic/prompt-templates/local-text-analysis.md +32 -0
  71. package/dist/layer3-dynamic/prompt-templates/security-analysis.md +13 -0
  72. package/dist/layer3-dynamic/prompt-templates/tool-poisoning.md +15 -0
  73. package/dist/layer3-dynamic/resource-fetcher.d.ts +25 -0
  74. package/dist/layer3-dynamic/resource-fetcher.js +119 -0
  75. package/dist/layer3-dynamic/sandbox.d.ts +13 -0
  76. package/dist/layer3-dynamic/sandbox.js +40 -0
  77. package/dist/layer3-dynamic/tool-description-acquisition.d.ts +22 -0
  78. package/dist/layer3-dynamic/tool-description-acquisition.js +76 -0
  79. package/dist/layer3-dynamic/tool-description-scanner.d.ts +11 -0
  80. package/dist/layer3-dynamic/tool-description-scanner.js +53 -0
  81. package/dist/layer3-dynamic/toxic-flow.d.ts +12 -0
  82. package/dist/layer3-dynamic/toxic-flow.js +57 -0
  83. package/dist/layer4-remediation/actions/quarantine.d.ts +1 -0
  84. package/dist/layer4-remediation/actions/quarantine.js +8 -0
  85. package/dist/layer4-remediation/actions/remove-field.d.ts +5 -0
  86. package/dist/layer4-remediation/actions/remove-field.js +53 -0
  87. package/dist/layer4-remediation/actions/replace-value.d.ts +5 -0
  88. package/dist/layer4-remediation/actions/replace-value.js +26 -0
  89. package/dist/layer4-remediation/actions/strip-unicode.d.ts +5 -0
  90. package/dist/layer4-remediation/actions/strip-unicode.js +8 -0
  91. package/dist/layer4-remediation/backup-manager.d.ts +32 -0
  92. package/dist/layer4-remediation/backup-manager.js +138 -0
  93. package/dist/layer4-remediation/diff-generator.d.ts +6 -0
  94. package/dist/layer4-remediation/diff-generator.js +29 -0
  95. package/dist/layer4-remediation/remediation-runner.d.ts +36 -0
  96. package/dist/layer4-remediation/remediation-runner.js +230 -0
  97. package/dist/layer4-remediation/remediator.d.ts +36 -0
  98. package/dist/layer4-remediation/remediator.js +117 -0
  99. package/dist/path-display.d.ts +1 -0
  100. package/dist/path-display.js +20 -0
  101. package/dist/pipeline.d.ts +34 -0
  102. package/dist/pipeline.js +259 -0
  103. package/dist/report-summary.d.ts +6 -0
  104. package/dist/report-summary.js +48 -0
  105. package/dist/reporter/html.d.ts +2 -0
  106. package/dist/reporter/html.js +103 -0
  107. package/dist/reporter/json.d.ts +2 -0
  108. package/dist/reporter/json.js +3 -0
  109. package/dist/reporter/markdown.d.ts +2 -0
  110. package/dist/reporter/markdown.js +52 -0
  111. package/dist/reporter/sarif.d.ts +2 -0
  112. package/dist/reporter/sarif.js +84 -0
  113. package/dist/reporter/terminal.d.ts +5 -0
  114. package/dist/reporter/terminal.js +94 -0
  115. package/dist/runtime/signal-handlers.d.ts +10 -0
  116. package/dist/runtime/signal-handlers.js +17 -0
  117. package/dist/scan-target/helpers.d.ts +20 -0
  118. package/dist/scan-target/helpers.js +268 -0
  119. package/dist/scan-target/staging.d.ts +5 -0
  120. package/dist/scan-target/staging.js +114 -0
  121. package/dist/scan-target/types.d.ts +18 -0
  122. package/dist/scan-target/types.js +1 -0
  123. package/dist/scan-target.d.ts +3 -0
  124. package/dist/scan-target.js +31 -0
  125. package/dist/scan.d.ts +54 -0
  126. package/dist/scan.js +593 -0
  127. package/dist/tui/app.d.ts +10 -0
  128. package/dist/tui/app.js +21 -0
  129. package/dist/tui/theme.d.ts +8 -0
  130. package/dist/tui/theme.js +7 -0
  131. package/dist/tui/views/dashboard.d.ts +6 -0
  132. package/dist/tui/views/dashboard.js +8 -0
  133. package/dist/tui/views/deep-scan-consent.d.ts +5 -0
  134. package/dist/tui/views/deep-scan-consent.js +6 -0
  135. package/dist/tui/views/progress.d.ts +4 -0
  136. package/dist/tui/views/progress.js +6 -0
  137. package/dist/tui/views/summary.d.ts +5 -0
  138. package/dist/tui/views/summary.js +16 -0
  139. package/dist/types/discovery.d.ts +12 -0
  140. package/dist/types/discovery.js +1 -0
  141. package/dist/types/finding.d.ts +46 -0
  142. package/dist/types/finding.js +15 -0
  143. package/dist/types/report.d.ts +25 -0
  144. package/dist/types/report.js +23 -0
  145. package/dist/wrapper.d.ts +35 -0
  146. package/dist/wrapper.js +220 -0
  147. package/package.json +97 -0
@@ -0,0 +1,1943 @@
1
+ import { buildFindingEvidence } from "../evidence.js";
2
+ const SHELL_META_PATTERN = /[|;&`]|[$][(]/u;
3
+ const NETWORK_UTILITY_PATTERN = /\b(curl|wget|nc|ncat|socat)\b/iu;
4
+ const SOURCE_KEYS = new Set([
5
+ "source",
6
+ "url",
7
+ "serviceurl",
8
+ "itemurl",
9
+ "resourceurltemplate",
10
+ "recommendationsurl",
11
+ "nlsbaseurl",
12
+ "publisherurl",
13
+ "cacheurl",
14
+ "controlurl",
15
+ "downloadurl",
16
+ "downloaduri",
17
+ "registry",
18
+ "repository",
19
+ "packageurl",
20
+ "packageuri",
21
+ "image",
22
+ "installfrom",
23
+ ]);
24
+ const IMAGE_KEYS = new Set(["image", "containerimage", "dockerimage"]);
25
+ const INTEGRITY_KEYS = new Set([
26
+ "sha256",
27
+ "sha512",
28
+ "checksum",
29
+ "integrity",
30
+ "digest",
31
+ "hash",
32
+ "shasum",
33
+ ]);
34
+ const INSTALL_SCRIPT_KEYS = new Set([
35
+ "postinstall",
36
+ "preinstall",
37
+ "installscript",
38
+ "setupcommand",
39
+ "installcommand",
40
+ "oninstall",
41
+ ]);
42
+ const PERMISSION_KEYS = new Set([
43
+ "permissions",
44
+ "permission",
45
+ "capabilities",
46
+ "capability",
47
+ "scopes",
48
+ "scope",
49
+ "grants",
50
+ "allowedcapabilities",
51
+ "enabledcapabilities",
52
+ "toolpermissions",
53
+ "toolpermission",
54
+ ]);
55
+ const UNVERIFIED_PUBLISHER_KEYS = new Set([
56
+ "publisherverified",
57
+ "publishertrusted",
58
+ "trustedpublisher",
59
+ "isverified",
60
+ "verified",
61
+ "signatureverified",
62
+ "signaturevalid",
63
+ ]);
64
+ const SIGNATURE_BYPASS_KEYS = new Set([
65
+ "allowunsigned",
66
+ "allowunverified",
67
+ "skipverification",
68
+ "skipverifications",
69
+ "skipsignature",
70
+ "skipsignaturecheck",
71
+ "skipsignatureverification",
72
+ "ignoresignature",
73
+ "ignorechecksum",
74
+ "ignorechecksums",
75
+ "disableverification",
76
+ "disablesignatureverification",
77
+ ]);
78
+ const PUBLISHER_TRUST_BYPASS_KEYS = new Set([
79
+ "allowuntrustedpublishers",
80
+ "allowunknownpublishers",
81
+ "allowunverifiedpublishers",
82
+ "skippublisherverification",
83
+ "disablepublisherverification",
84
+ "bypasspublishertrust",
85
+ "ignorepublishertrust",
86
+ ]);
87
+ const PUBLISHER_TRUST_DISABLED_KEYS = new Set([
88
+ "trustedpublishersonly",
89
+ "requireverifiedpublisher",
90
+ "enforcepublisherverification",
91
+ ]);
92
+ const VSCODE_RECOMMENDATION_KEYS = new Set(["recommendations", "unwantedrecommendations"]);
93
+ const VERSION_FIELD_KEYS = new Set([
94
+ "version",
95
+ "pluginversion",
96
+ "extensionversion",
97
+ "packageversion",
98
+ "targetversion",
99
+ "requiredversion",
100
+ ]);
101
+ const PACKAGE_IDENTITY_KEYS = new Set([
102
+ "id",
103
+ "name",
104
+ "slug",
105
+ "package",
106
+ "packagename",
107
+ "extension",
108
+ "extensionid",
109
+ "plugin",
110
+ "pluginid",
111
+ "artifactid",
112
+ ]);
113
+ const DISALLOWED_NAMESPACE_TOKENS = new Set([
114
+ "local",
115
+ "localhost",
116
+ "temp",
117
+ "tmp",
118
+ "test",
119
+ "example",
120
+ "sample",
121
+ "demo",
122
+ "unknown",
123
+ ]);
124
+ const UNPINNED_VERSION_TOKENS = new Set([
125
+ "latest",
126
+ "stable",
127
+ "next",
128
+ "nightly",
129
+ "canary",
130
+ "alpha",
131
+ "beta",
132
+ "preview",
133
+ "insiders",
134
+ "dev",
135
+ "edge",
136
+ "main",
137
+ "master",
138
+ "head",
139
+ "*",
140
+ "x",
141
+ ]);
142
+ const TRANSPARENCY_BYPASS_KEYS = new Set([
143
+ "allowmissingtlog",
144
+ "allowmissingtransparencyproof",
145
+ "allowmissingtransparencylog",
146
+ "skiptlogverification",
147
+ "skiprekorverification",
148
+ "skiptransparencyverification",
149
+ "skiptransparencylogverification",
150
+ "ignoretlog",
151
+ "ignorerekor",
152
+ "disabletlogverification",
153
+ "disabletransparencyverification",
154
+ "disabletransparencylog",
155
+ "bypasstransparencylog",
156
+ "nologverification",
157
+ "notransparencyverification",
158
+ ]);
159
+ const ATTESTATION_KEYS = new Set([
160
+ "attestation",
161
+ "attestations",
162
+ "provenance",
163
+ "buildprovenance",
164
+ "supplychainprovenance",
165
+ "slsa",
166
+ "sigstore",
167
+ "cosign",
168
+ "signedattestation",
169
+ "attestationstatus",
170
+ "provenancestatus",
171
+ ]);
172
+ const RELEASE_CHANNEL_KEYS = new Set([
173
+ "releasechannel",
174
+ "channel",
175
+ "track",
176
+ "stream",
177
+ "ring",
178
+ "updatechannel",
179
+ "distributionchannel",
180
+ "releasetrack",
181
+ ]);
182
+ const PRERELEASE_FLAG_KEYS = new Set([
183
+ "allowprerelease",
184
+ "includeprerelease",
185
+ "useprerelease",
186
+ "prerelease",
187
+ "enableprerelease",
188
+ "preview",
189
+ "insiders",
190
+ "nightly",
191
+ ]);
192
+ const UNSTABLE_RELEASE_CHANNEL_TOKENS = new Set([
193
+ "nightly",
194
+ "canary",
195
+ "alpha",
196
+ "beta",
197
+ "preview",
198
+ "insiders",
199
+ "dev",
200
+ "edge",
201
+ "experimental",
202
+ "next",
203
+ "prerelease",
204
+ "rc",
205
+ ]);
206
+ const ATTESTATION_ISSUER_FIELD_KEYS = new Set([
207
+ "issuer",
208
+ "oidcissuer",
209
+ "identityissuer",
210
+ "certificateissuer",
211
+ "signerissuer",
212
+ "fulcioissuer",
213
+ "trustanchorissuer",
214
+ "attestationissuer",
215
+ ]);
216
+ const ATTESTATION_SUBJECT_FIELD_KEYS = new Set([
217
+ "subject",
218
+ "subjects",
219
+ "artifact",
220
+ "artifactid",
221
+ "artifactdigest",
222
+ "digest",
223
+ "package",
224
+ "packageurl",
225
+ "uri",
226
+ "name",
227
+ "version",
228
+ "material",
229
+ ]);
230
+ const ATTESTATION_DIGEST_FIELD_KEYS = new Set([
231
+ "digest",
232
+ "artifactdigest",
233
+ "sha256",
234
+ "sha512",
235
+ "checksum",
236
+ "integrity",
237
+ "hash",
238
+ ]);
239
+ const ATTESTATION_VERIFICATION_FIELD_KEYS = new Set([
240
+ "verified",
241
+ "verification",
242
+ "verificationstatus",
243
+ "status",
244
+ "result",
245
+ "valid",
246
+ "signatureverified",
247
+ "signaturevalid",
248
+ ]);
249
+ const ATTESTATION_CERT_CHAIN_CONTEXT_KEYS = new Set([
250
+ "certificate",
251
+ "cert",
252
+ "x5c",
253
+ "chain",
254
+ "rootca",
255
+ "intermediate",
256
+ "trustanchor",
257
+ ]);
258
+ const ATTESTATION_TRANSPARENCY_CONTEXT_KEYS = new Set([
259
+ "transparency",
260
+ "tlog",
261
+ "rekor",
262
+ "inclusion",
263
+ "checkpoint",
264
+ "integratedtime",
265
+ "signedentrytimestamp",
266
+ "logentry",
267
+ "logproof",
268
+ ]);
269
+ const ATTESTATION_TRANSPARENCY_FIELD_KEYS = new Set([
270
+ "transparencylog",
271
+ "tlog",
272
+ "rekor",
273
+ "inclusionproof",
274
+ "inclusionpromise",
275
+ "logproof",
276
+ "logentry",
277
+ "logindex",
278
+ "entryuuid",
279
+ "uuid",
280
+ "checkpoint",
281
+ "integratedtime",
282
+ "signedentrytimestamp",
283
+ ]);
284
+ const TRUSTED_ATTESTATION_ISSUER_DOMAINS = new Set([
285
+ "token.actions.githubusercontent.com",
286
+ "oauth2.sigstore.dev",
287
+ "fulcio.sigstore.dev",
288
+ "accounts.google.com",
289
+ "gitlab.com",
290
+ "login.microsoftonline.com",
291
+ "sts.windows.net",
292
+ ]);
293
+ const BASE_ATTESTATION_REQUIRED_FIELDS = [
294
+ { label: "issuer", keys: ATTESTATION_ISSUER_FIELD_KEYS },
295
+ { label: "subject", keys: ATTESTATION_SUBJECT_FIELD_KEYS },
296
+ { label: "verification_status", keys: ATTESTATION_VERIFICATION_FIELD_KEYS },
297
+ ];
298
+ const STRICT_ATTESTATION_REQUIRED_FIELDS = [
299
+ ...BASE_ATTESTATION_REQUIRED_FIELDS,
300
+ { label: "digest", keys: ATTESTATION_DIGEST_FIELD_KEYS },
301
+ ];
302
+ const CERT_POLICY_FIELD_MARKERS = [
303
+ "extendedkeyusage",
304
+ "eku",
305
+ "keyusage",
306
+ "usage",
307
+ "certificatepolicy",
308
+ "certificatepolicies",
309
+ "policyoid",
310
+ "policyoids",
311
+ "oid",
312
+ "oids",
313
+ ];
314
+ const CODE_SIGNING_POLICY_TOKENS = new Set([
315
+ "1.3.6.1.5.5.7.3.3",
316
+ "136155733",
317
+ "codesigning",
318
+ "codesign",
319
+ "idkpcodesigning",
320
+ "id-kp-codesigning",
321
+ ]);
322
+ const TRANSPARENCY_LOG_INDEX_KEY_MARKERS = ["logindex", "entryindex", "index"];
323
+ const TRANSPARENCY_TREE_SIZE_KEY_MARKERS = ["treesize", "logsize"];
324
+ const TRANSPARENCY_INTEGRATED_TIME_KEY_MARKERS = ["integratedtime"];
325
+ const TRANSPARENCY_FUTURE_SKEW_SECONDS = 24 * 60 * 60;
326
+ const WILDCARD_PERMISSION_TOKENS = new Set([
327
+ "*",
328
+ "all",
329
+ "any",
330
+ "allpermissions",
331
+ "allcapabilities",
332
+ "fullaccess",
333
+ "unrestricted",
334
+ "allowall",
335
+ ]);
336
+ const RISKY_CAPABILITY_TOKENS = [
337
+ "shell",
338
+ "exec",
339
+ "execute",
340
+ "terminal",
341
+ "command",
342
+ "spawn",
343
+ "filesystemwrite",
344
+ "filewrite",
345
+ "writefilesystem",
346
+ "network",
347
+ "outbound",
348
+ "internet",
349
+ "env",
350
+ "secret",
351
+ "credential",
352
+ "system",
353
+ ];
354
+ const TRUSTED_SOURCE_DOMAINS = new Set([
355
+ "registry.npmjs.org",
356
+ "npmjs.com",
357
+ "www.npmjs.com",
358
+ "pypi.org",
359
+ "github.com",
360
+ "raw.githubusercontent.com",
361
+ "plugins.jetbrains.com",
362
+ "open-vsx.org",
363
+ "marketplace.visualstudio.com",
364
+ "zed.dev",
365
+ "marketplace.roocode.com",
366
+ "roocode.com",
367
+ "registry.opencode.ai",
368
+ "opencode.ai",
369
+ ]);
370
+ const MARKETPLACE_ANCHOR_DOMAINS = new Set([
371
+ "marketplace.visualstudio.com",
372
+ "open-vsx.org",
373
+ "plugins.jetbrains.com",
374
+ "zed.dev",
375
+ "marketplace.roocode.com",
376
+ "roocode.com",
377
+ "registry.opencode.ai",
378
+ "opencode.ai",
379
+ ]);
380
+ const USER_SCOPE_ADVISORY_RULE_IDS = new Set([
381
+ "plugin-manifest-untrusted-source-url",
382
+ "plugin-manifest-cross-marketplace-source",
383
+ "plugin-manifest-missing-integrity",
384
+ "plugin-manifest-missing-package-identity",
385
+ "plugin-manifest-unpinned-version",
386
+ "plugin-manifest-unpinned-git-source",
387
+ "plugin-manifest-unpinned-image",
388
+ "plugin-manifest-versioned-extension-id",
389
+ "plugin-manifest-unscoped-extension-id",
390
+ "plugin-manifest-missing-marketplace-provenance",
391
+ "plugin-manifest-nonallowlisted-extension-registry",
392
+ "plugin-manifest-extension-registry-host-mismatch",
393
+ "plugin-manifest-disallowed-namespace",
394
+ "plugin-manifest-unstable-release-channel",
395
+ ]);
396
+ const SEVERITY_ORDER = ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"];
397
+ const KIRO_EXTENSION_REGISTRY_URL_KEYS = new Set([
398
+ "serviceurl",
399
+ "itemurl",
400
+ "resourceurltemplate",
401
+ "controlurl",
402
+ "recommendationsurl",
403
+ "nlsbaseurl",
404
+ "publisherurl",
405
+ "cacheurl",
406
+ ]);
407
+ const TRUSTED_KIRO_EXTENSION_REGISTRY_DOMAINS = new Set(["open-vsx.org"]);
408
+ function isRecord(value) {
409
+ return typeof value === "object" && value !== null && !Array.isArray(value);
410
+ }
411
+ function normalizeKey(key) {
412
+ return key.replace(/[^a-z0-9]/giu, "").toLowerCase();
413
+ }
414
+ function shouldInspectFile(filePath) {
415
+ const lower = filePath.toLowerCase();
416
+ return (lower.includes("plugins.json") ||
417
+ lower.includes("extensions.json") ||
418
+ lower.includes("marketplace.json") ||
419
+ isKiroProductManifest(lower));
420
+ }
421
+ function isTrustedSourceDomain(hostname, trustedApiDomains) {
422
+ const lower = hostname.toLowerCase();
423
+ for (const domain of [
424
+ ...TRUSTED_SOURCE_DOMAINS,
425
+ ...trustedApiDomains.map((value) => value.toLowerCase()),
426
+ ]) {
427
+ if (lower === domain || lower.endsWith(`.${domain}`)) {
428
+ return true;
429
+ }
430
+ }
431
+ return false;
432
+ }
433
+ function matchesDomain(hostname, domain) {
434
+ const lowerHost = hostname.toLowerCase();
435
+ const lowerDomain = domain.toLowerCase();
436
+ return lowerHost === lowerDomain || lowerHost.endsWith(`.${lowerDomain}`);
437
+ }
438
+ function isMarketplaceAnchorDomain(hostname) {
439
+ for (const domain of MARKETPLACE_ANCHOR_DOMAINS) {
440
+ if (matchesDomain(hostname, domain)) {
441
+ return true;
442
+ }
443
+ }
444
+ return false;
445
+ }
446
+ function isUserTrustedDomain(hostname, trustedApiDomains) {
447
+ for (const domain of trustedApiDomains) {
448
+ if (matchesDomain(hostname, domain)) {
449
+ return true;
450
+ }
451
+ }
452
+ return false;
453
+ }
454
+ function isAllowedByMarketplacePolicy(hostname, policy) {
455
+ return policy.allowedMarketplaceDomains.some((domain) => matchesDomain(hostname, domain));
456
+ }
457
+ function isTrustedKiroExtensionRegistryDomain(hostname, trustedApiDomains) {
458
+ for (const domain of [...TRUSTED_KIRO_EXTENSION_REGISTRY_DOMAINS, ...trustedApiDomains]) {
459
+ if (matchesDomain(hostname, domain)) {
460
+ return true;
461
+ }
462
+ }
463
+ return false;
464
+ }
465
+ function requiresMarketplaceProvenance(policy) {
466
+ return (policy.id === "roo" ||
467
+ policy.id === "opencode" ||
468
+ policy.id === "zed" ||
469
+ policy.id === "claude" ||
470
+ policy.id === "gemini");
471
+ }
472
+ function parseSourceUrl(value) {
473
+ try {
474
+ return new URL(value);
475
+ }
476
+ catch {
477
+ return null;
478
+ }
479
+ }
480
+ function isKiroExtensionRegistryField(filePath, normalizedKey, path) {
481
+ if (!isKiroProductManifest(filePath)) {
482
+ return false;
483
+ }
484
+ if (!KIRO_EXTENSION_REGISTRY_URL_KEYS.has(normalizedKey)) {
485
+ return false;
486
+ }
487
+ return normalizeKey(path).includes("extensionsgallery");
488
+ }
489
+ function isLocalSourcePath(value) {
490
+ const normalized = value.trim().toLowerCase();
491
+ return (normalized.startsWith("./") ||
492
+ normalized.startsWith("../") ||
493
+ normalized.startsWith("/") ||
494
+ normalized.startsWith("~/") ||
495
+ normalized.startsWith("file:") ||
496
+ /^[a-z]:\\/iu.test(value) ||
497
+ normalized.startsWith("..\\") ||
498
+ normalized.startsWith(".\\"));
499
+ }
500
+ function isImageReference(value) {
501
+ const trimmed = value.trim();
502
+ if (trimmed.length === 0 || /^[a-z][a-z0-9+.-]*:\/\//iu.test(trimmed)) {
503
+ return false;
504
+ }
505
+ return trimmed.includes("/") || trimmed.includes(":");
506
+ }
507
+ function isDigestPinnedImage(value) {
508
+ return /@sha256:[0-9a-f]{64}$/iu.test(value.trim());
509
+ }
510
+ function isHostOrSubdomain(hostname, baseDomain) {
511
+ const normalizedHostname = hostname.trim().toLowerCase();
512
+ const normalizedBaseDomain = baseDomain.trim().toLowerCase();
513
+ return (normalizedHostname === normalizedBaseDomain ||
514
+ normalizedHostname.endsWith(`.${normalizedBaseDomain}`));
515
+ }
516
+ function isLikelyGitSource(value, parsedUrl) {
517
+ const trimmed = value.trim().toLowerCase();
518
+ if (trimmed.startsWith("git+")) {
519
+ return true;
520
+ }
521
+ if (trimmed.endsWith(".git") || trimmed.includes(".git#")) {
522
+ return true;
523
+ }
524
+ if (!parsedUrl) {
525
+ return false;
526
+ }
527
+ return (isHostOrSubdomain(parsedUrl.hostname, "github.com") ||
528
+ isHostOrSubdomain(parsedUrl.hostname, "gitlab.com") ||
529
+ isHostOrSubdomain(parsedUrl.hostname, "bitbucket.org"));
530
+ }
531
+ function hasPinnedGitCommit(value, parsedUrl) {
532
+ const fragment = parsedUrl?.hash ? parsedUrl.hash.slice(1) : "";
533
+ if (/^[0-9a-f]{7,40}$/iu.test(fragment)) {
534
+ return true;
535
+ }
536
+ const queryRef = parsedUrl?.searchParams.get("ref");
537
+ if (queryRef && /^[0-9a-f]{7,40}$/iu.test(queryRef)) {
538
+ return true;
539
+ }
540
+ return /(?:#|[?&]ref=)([0-9a-f]{7,40})\b/iu.test(value);
541
+ }
542
+ function isArtifactSourceUrl(parsedUrl) {
543
+ const lowerPath = parsedUrl.pathname.toLowerCase();
544
+ if (lowerPath.includes("/releases/download/")) {
545
+ return true;
546
+ }
547
+ return [".tgz", ".tar.gz", ".tar", ".zip", ".vsix", ".whl", ".jar", ".gz", ".bz2", ".xz"].some((suffix) => lowerPath.endsWith(suffix));
548
+ }
549
+ function permissionTokensFromString(value) {
550
+ const trimmed = value.trim();
551
+ if (trimmed.length === 0) {
552
+ return [];
553
+ }
554
+ const tokens = new Set();
555
+ tokens.add(normalizeKey(trimmed));
556
+ if (trimmed === "*") {
557
+ tokens.add("*");
558
+ }
559
+ for (const part of trimmed.split(/[\s,;|/]+/u)) {
560
+ const normalized = normalizeKey(part);
561
+ if (normalized.length > 0) {
562
+ tokens.add(normalized);
563
+ }
564
+ if (part.trim() === "*") {
565
+ tokens.add("*");
566
+ }
567
+ }
568
+ return Array.from(tokens);
569
+ }
570
+ function collectPermissionTokens(value) {
571
+ const tokens = new Set();
572
+ function visit(candidate) {
573
+ if (typeof candidate === "string") {
574
+ for (const token of permissionTokensFromString(candidate)) {
575
+ tokens.add(token);
576
+ }
577
+ return;
578
+ }
579
+ if (Array.isArray(candidate)) {
580
+ for (const entry of candidate) {
581
+ visit(entry);
582
+ }
583
+ return;
584
+ }
585
+ if (!isRecord(candidate)) {
586
+ return;
587
+ }
588
+ for (const [key, entry] of Object.entries(candidate)) {
589
+ const keyToken = normalizeKey(key);
590
+ if (typeof entry === "boolean") {
591
+ if (entry) {
592
+ tokens.add(keyToken);
593
+ }
594
+ continue;
595
+ }
596
+ if (typeof entry === "number") {
597
+ if (entry > 0) {
598
+ tokens.add(keyToken);
599
+ }
600
+ continue;
601
+ }
602
+ if (typeof entry === "string") {
603
+ if (entry.trim().length > 0) {
604
+ tokens.add(keyToken);
605
+ for (const token of permissionTokensFromString(entry)) {
606
+ tokens.add(token);
607
+ }
608
+ }
609
+ continue;
610
+ }
611
+ visit(entry);
612
+ }
613
+ }
614
+ visit(value);
615
+ return Array.from(tokens);
616
+ }
617
+ function findWildcardPermissionTokens(tokens) {
618
+ return tokens.filter((token) => WILDCARD_PERMISSION_TOKENS.has(token));
619
+ }
620
+ function findRiskyCapabilityTokens(tokens) {
621
+ return tokens.filter((token) => RISKY_CAPABILITY_TOKENS.some((marker) => token.includes(marker)));
622
+ }
623
+ function isAffirmative(value) {
624
+ if (value === true) {
625
+ return true;
626
+ }
627
+ if (typeof value === "number") {
628
+ return value > 0;
629
+ }
630
+ if (typeof value === "string") {
631
+ return ["true", "yes", "on", "enabled", "enable", "1"].includes(value.trim().toLowerCase());
632
+ }
633
+ return false;
634
+ }
635
+ function isExplicitlyUnverified(value) {
636
+ if (value === false || value === null) {
637
+ return true;
638
+ }
639
+ if (typeof value === "string") {
640
+ return ["false", "no", "unverified", "unknown", "none", "invalid"].includes(value.trim().toLowerCase());
641
+ }
642
+ return false;
643
+ }
644
+ function isVsCodeExtensionsManifest(filePath) {
645
+ return filePath.toLowerCase().endsWith(".vscode/extensions.json");
646
+ }
647
+ function isOpencodePluginManifest(filePath) {
648
+ return filePath.toLowerCase().endsWith(".opencode/plugins.json");
649
+ }
650
+ function isClaudePluginManifest(filePath) {
651
+ return filePath.toLowerCase().endsWith(".claude/plugins.json");
652
+ }
653
+ function isZedExtensionsManifest(filePath) {
654
+ return filePath.toLowerCase().endsWith(".zed/extensions.json");
655
+ }
656
+ function isGeminiExtensionsManifest(filePath) {
657
+ return filePath.toLowerCase().endsWith(".gemini/extensions.json");
658
+ }
659
+ function isRooMarketplaceManifest(filePath) {
660
+ return filePath.toLowerCase().endsWith(".roo/marketplace.json");
661
+ }
662
+ function isClineMarketplaceManifest(filePath) {
663
+ return filePath.toLowerCase().endsWith(".cline/marketplace.json");
664
+ }
665
+ function isKiroProductManifest(filePath) {
666
+ return filePath.toLowerCase().endsWith(".kiro/product.json");
667
+ }
668
+ function isMarketplaceSemanticsManifest(filePath) {
669
+ return (isVsCodeExtensionsManifest(filePath) ||
670
+ isClaudePluginManifest(filePath) ||
671
+ isOpencodePluginManifest(filePath) ||
672
+ isGeminiExtensionsManifest(filePath) ||
673
+ isZedExtensionsManifest(filePath) ||
674
+ isRooMarketplaceManifest(filePath) ||
675
+ isClineMarketplaceManifest(filePath));
676
+ }
677
+ function marketplaceSourcePolicyForFile(filePath) {
678
+ if (isVsCodeExtensionsManifest(filePath)) {
679
+ return {
680
+ id: "vscode",
681
+ allowedMarketplaceDomains: ["marketplace.visualstudio.com", "open-vsx.org"],
682
+ };
683
+ }
684
+ if (isClaudePluginManifest(filePath)) {
685
+ return {
686
+ id: "claude",
687
+ allowedMarketplaceDomains: ["registry.npmjs.org", "npmjs.com", "www.npmjs.com", "github.com"],
688
+ };
689
+ }
690
+ if (isOpencodePluginManifest(filePath)) {
691
+ return {
692
+ id: "opencode",
693
+ allowedMarketplaceDomains: ["registry.opencode.ai", "opencode.ai"],
694
+ };
695
+ }
696
+ if (isGeminiExtensionsManifest(filePath)) {
697
+ return {
698
+ id: "gemini",
699
+ allowedMarketplaceDomains: ["registry.npmjs.org", "npmjs.com", "www.npmjs.com", "github.com"],
700
+ };
701
+ }
702
+ if (isZedExtensionsManifest(filePath)) {
703
+ return {
704
+ id: "zed",
705
+ allowedMarketplaceDomains: ["zed.dev"],
706
+ };
707
+ }
708
+ if (isRooMarketplaceManifest(filePath)) {
709
+ return {
710
+ id: "roo",
711
+ allowedMarketplaceDomains: ["marketplace.roocode.com", "roocode.com"],
712
+ };
713
+ }
714
+ if (isClineMarketplaceManifest(filePath)) {
715
+ return {
716
+ id: "cline",
717
+ allowedMarketplaceDomains: ["open-vsx.org", "marketplace.visualstudio.com"],
718
+ };
719
+ }
720
+ return null;
721
+ }
722
+ function isScopedVsCodeExtensionId(value) {
723
+ return /^[a-z0-9][a-z0-9-]*\.[a-z0-9][a-z0-9-]*$/iu.test(value.trim());
724
+ }
725
+ function isPathTraversalLike(value) {
726
+ return /(^|[\\/])\.\.([\\/]|$)/u.test(value);
727
+ }
728
+ function isInvalidPackageIdentity(value) {
729
+ const trimmed = value.trim();
730
+ if (trimmed.length === 0) {
731
+ return true;
732
+ }
733
+ if (parseSourceUrl(trimmed)) {
734
+ return true;
735
+ }
736
+ if (isLocalSourcePath(trimmed)) {
737
+ return true;
738
+ }
739
+ if (trimmed.startsWith("git+") || trimmed.startsWith("ssh://")) {
740
+ return true;
741
+ }
742
+ if (trimmed.includes("\\") || /\s/u.test(trimmed)) {
743
+ return true;
744
+ }
745
+ if (isPathTraversalLike(trimmed)) {
746
+ return true;
747
+ }
748
+ return false;
749
+ }
750
+ function isInvalidVsCodeRecommendationEntry(value) {
751
+ const trimmed = value.trim();
752
+ if (trimmed.length === 0) {
753
+ return false;
754
+ }
755
+ if (isScopedVsCodeExtensionId(trimmed) || parseVsCodeVersionQualifiedId(trimmed)) {
756
+ return false;
757
+ }
758
+ if (parseSourceUrl(trimmed)) {
759
+ return true;
760
+ }
761
+ if (isLocalSourcePath(trimmed) || isPathTraversalLike(trimmed)) {
762
+ return true;
763
+ }
764
+ if (trimmed.startsWith("git+") || trimmed.startsWith("ssh://")) {
765
+ return true;
766
+ }
767
+ if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes(":")) {
768
+ return true;
769
+ }
770
+ return false;
771
+ }
772
+ function namespaceTokenFromIdentity(value, filePath) {
773
+ const trimmed = value.trim();
774
+ if (trimmed.length === 0) {
775
+ return null;
776
+ }
777
+ if (isVsCodeExtensionsManifest(filePath)) {
778
+ if (!isScopedVsCodeExtensionId(trimmed)) {
779
+ return null;
780
+ }
781
+ return trimmed.split(".")[0]?.toLowerCase() ?? null;
782
+ }
783
+ if (trimmed.startsWith("@")) {
784
+ const slashIndex = trimmed.indexOf("/");
785
+ if (slashIndex > 1) {
786
+ return trimmed.slice(1, slashIndex).toLowerCase();
787
+ }
788
+ }
789
+ const slashIndex = trimmed.indexOf("/");
790
+ if (slashIndex > 0) {
791
+ return trimmed.slice(0, slashIndex).toLowerCase();
792
+ }
793
+ const dotIndex = trimmed.indexOf(".");
794
+ if (dotIndex > 0) {
795
+ return trimmed.slice(0, dotIndex).toLowerCase();
796
+ }
797
+ return trimmed.toLowerCase();
798
+ }
799
+ function isDisallowedNamespace(value, filePath) {
800
+ const namespace = namespaceTokenFromIdentity(value, filePath);
801
+ if (!namespace) {
802
+ return false;
803
+ }
804
+ return DISALLOWED_NAMESPACE_TOKENS.has(namespace);
805
+ }
806
+ function attestationProfileForFile(filePath) {
807
+ if (isVsCodeExtensionsManifest(filePath)) {
808
+ return {
809
+ id: "vscode",
810
+ schemaProfile: "strict",
811
+ trustedIssuerDomains: [
812
+ ...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
813
+ "vstoken.dev.azure.com",
814
+ "dev.azure.com",
815
+ "visualstudio.com",
816
+ "microsoft.com",
817
+ ],
818
+ requiredFields: STRICT_ATTESTATION_REQUIRED_FIELDS,
819
+ requireTransparencyProof: true,
820
+ enforceCertificatePolicy: true,
821
+ };
822
+ }
823
+ if (isOpencodePluginManifest(filePath)) {
824
+ return {
825
+ id: "opencode",
826
+ schemaProfile: "base",
827
+ trustedIssuerDomains: [
828
+ ...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
829
+ "registry.opencode.ai",
830
+ "opencode.ai",
831
+ ],
832
+ requiredFields: BASE_ATTESTATION_REQUIRED_FIELDS,
833
+ requireTransparencyProof: false,
834
+ enforceCertificatePolicy: false,
835
+ };
836
+ }
837
+ if (isClaudePluginManifest(filePath)) {
838
+ return {
839
+ id: "claude",
840
+ schemaProfile: "base",
841
+ trustedIssuerDomains: [
842
+ ...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
843
+ "registry.npmjs.org",
844
+ "npmjs.com",
845
+ ],
846
+ requiredFields: BASE_ATTESTATION_REQUIRED_FIELDS,
847
+ requireTransparencyProof: false,
848
+ enforceCertificatePolicy: false,
849
+ };
850
+ }
851
+ if (isGeminiExtensionsManifest(filePath)) {
852
+ return {
853
+ id: "gemini",
854
+ schemaProfile: "base",
855
+ trustedIssuerDomains: [
856
+ ...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
857
+ "registry.npmjs.org",
858
+ "npmjs.com",
859
+ ],
860
+ requiredFields: BASE_ATTESTATION_REQUIRED_FIELDS,
861
+ requireTransparencyProof: false,
862
+ enforceCertificatePolicy: false,
863
+ };
864
+ }
865
+ if (isZedExtensionsManifest(filePath)) {
866
+ return {
867
+ id: "zed",
868
+ schemaProfile: "strict",
869
+ trustedIssuerDomains: [...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS), "zed.dev"],
870
+ requiredFields: STRICT_ATTESTATION_REQUIRED_FIELDS,
871
+ requireTransparencyProof: true,
872
+ enforceCertificatePolicy: true,
873
+ };
874
+ }
875
+ if (isRooMarketplaceManifest(filePath)) {
876
+ return {
877
+ id: "roo",
878
+ schemaProfile: "strict",
879
+ trustedIssuerDomains: [
880
+ ...Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
881
+ "marketplace.roocode.com",
882
+ "roocode.com",
883
+ ],
884
+ requiredFields: STRICT_ATTESTATION_REQUIRED_FIELDS,
885
+ requireTransparencyProof: true,
886
+ enforceCertificatePolicy: true,
887
+ };
888
+ }
889
+ return {
890
+ id: "default",
891
+ schemaProfile: "base",
892
+ trustedIssuerDomains: Array.from(TRUSTED_ATTESTATION_ISSUER_DOMAINS),
893
+ requiredFields: BASE_ATTESTATION_REQUIRED_FIELDS,
894
+ requireTransparencyProof: false,
895
+ enforceCertificatePolicy: false,
896
+ };
897
+ }
898
+ function incompleteAttestationRuleIdForProfile(profile) {
899
+ return profile.schemaProfile === "strict"
900
+ ? "plugin-manifest-incomplete-attestation-strict"
901
+ : "plugin-manifest-incomplete-attestation-base";
902
+ }
903
+ function hasPresentValue(value) {
904
+ if (value === null || value === undefined) {
905
+ return false;
906
+ }
907
+ if (typeof value === "string") {
908
+ return value.trim().length > 0;
909
+ }
910
+ if (typeof value === "boolean" || typeof value === "number") {
911
+ return true;
912
+ }
913
+ if (Array.isArray(value)) {
914
+ return value.length > 0;
915
+ }
916
+ if (isRecord(value)) {
917
+ return Object.keys(value).length > 0;
918
+ }
919
+ return true;
920
+ }
921
+ function issuerValueToHost(value) {
922
+ const trimmed = value.trim();
923
+ if (trimmed.length === 0) {
924
+ return null;
925
+ }
926
+ const parsed = parseSourceUrl(trimmed);
927
+ if (parsed) {
928
+ return parsed.hostname.toLowerCase();
929
+ }
930
+ if (/^[a-z0-9.-]+$/iu.test(trimmed)) {
931
+ return trimmed.toLowerCase();
932
+ }
933
+ return null;
934
+ }
935
+ function collectAttestationIssuerHosts(value) {
936
+ const hosts = new Set();
937
+ function visit(candidate, issuerContext = false) {
938
+ if (typeof candidate === "string") {
939
+ if (!issuerContext) {
940
+ return;
941
+ }
942
+ const host = issuerValueToHost(candidate);
943
+ if (host) {
944
+ hosts.add(host);
945
+ }
946
+ return;
947
+ }
948
+ if (Array.isArray(candidate)) {
949
+ for (const entry of candidate) {
950
+ visit(entry, issuerContext);
951
+ }
952
+ return;
953
+ }
954
+ if (!isRecord(candidate)) {
955
+ return;
956
+ }
957
+ for (const [key, child] of Object.entries(candidate)) {
958
+ const normalizedKey = normalizeKey(key);
959
+ const nextIssuerContext = issuerContext || normalizedKey.includes("issuer") || normalizedKey.includes("trustanchor");
960
+ visit(child, nextIssuerContext);
961
+ }
962
+ }
963
+ visit(value);
964
+ return Array.from(hosts);
965
+ }
966
+ function attestationHasField(value, fieldKeys) {
967
+ if (Array.isArray(value)) {
968
+ return value.some((entry) => attestationHasField(entry, fieldKeys));
969
+ }
970
+ if (!isRecord(value)) {
971
+ return false;
972
+ }
973
+ for (const [key, child] of Object.entries(value)) {
974
+ const normalizedKey = normalizeKey(key);
975
+ if (fieldKeys.has(normalizedKey) && hasPresentValue(child)) {
976
+ return true;
977
+ }
978
+ if (attestationHasField(child, fieldKeys)) {
979
+ return true;
980
+ }
981
+ }
982
+ return false;
983
+ }
984
+ function isTrustedAttestationIssuer(host, profile, trustedApiDomains) {
985
+ return [...trustedApiDomains, ...profile.trustedIssuerDomains].some((domain) => matchesDomain(host, domain));
986
+ }
987
+ function hasUnverifiedAttestationSignal(value) {
988
+ if (isExplicitlyUnverified(value)) {
989
+ return true;
990
+ }
991
+ if (Array.isArray(value)) {
992
+ return value.some((entry) => hasUnverifiedAttestationSignal(entry));
993
+ }
994
+ if (!isRecord(value)) {
995
+ return false;
996
+ }
997
+ for (const [key, child] of Object.entries(value)) {
998
+ const normalizedKey = normalizeKey(key);
999
+ if (normalizedKey.includes("verified") ||
1000
+ normalizedKey.includes("valid") ||
1001
+ normalizedKey.includes("trusted") ||
1002
+ normalizedKey.includes("status") ||
1003
+ normalizedKey.includes("result") ||
1004
+ normalizedKey.includes("state")) {
1005
+ if (isExplicitlyUnverified(child)) {
1006
+ return true;
1007
+ }
1008
+ }
1009
+ if (hasUnverifiedAttestationSignal(child)) {
1010
+ return true;
1011
+ }
1012
+ }
1013
+ return false;
1014
+ }
1015
+ function hasFailureString(value) {
1016
+ const normalized = value.trim().toLowerCase();
1017
+ return (normalized.includes("invalid") ||
1018
+ normalized.includes("expired") ||
1019
+ normalized.includes("revoked") ||
1020
+ normalized.includes("failed") ||
1021
+ normalized.includes("failure") ||
1022
+ normalized.includes("error") ||
1023
+ normalized.includes("broken") ||
1024
+ normalized.includes("mismatch") ||
1025
+ normalized.includes("untrusted"));
1026
+ }
1027
+ function hasAttestationContextFailure(value, contextHints, inContext = false) {
1028
+ if (Array.isArray(value)) {
1029
+ return value.some((entry) => hasAttestationContextFailure(entry, contextHints, inContext));
1030
+ }
1031
+ if (!isRecord(value)) {
1032
+ if (!inContext) {
1033
+ return false;
1034
+ }
1035
+ if (isExplicitlyUnverified(value)) {
1036
+ return true;
1037
+ }
1038
+ if (typeof value === "string" && hasFailureString(value)) {
1039
+ return true;
1040
+ }
1041
+ return false;
1042
+ }
1043
+ for (const [key, child] of Object.entries(value)) {
1044
+ const normalizedKey = normalizeKey(key);
1045
+ const nextContext = inContext || Array.from(contextHints).some((hint) => normalizedKey.includes(hint));
1046
+ if (hasAttestationContextFailure(child, contextHints, nextContext)) {
1047
+ return true;
1048
+ }
1049
+ }
1050
+ return false;
1051
+ }
1052
+ function pathHasContextHint(path, contextHints) {
1053
+ const normalizedPath = normalizeKey(path);
1054
+ for (const hint of contextHints) {
1055
+ if (normalizedPath.includes(hint)) {
1056
+ return true;
1057
+ }
1058
+ }
1059
+ return false;
1060
+ }
1061
+ function extractPolicyTokens(value) {
1062
+ const tokens = new Set();
1063
+ function addString(raw) {
1064
+ const trimmed = raw.trim().toLowerCase();
1065
+ if (trimmed.length === 0) {
1066
+ return;
1067
+ }
1068
+ tokens.add(trimmed);
1069
+ tokens.add(normalizeKey(trimmed));
1070
+ const oidMatches = trimmed.match(/\b\d+(?:\.\d+){3,}\b/gu) ?? [];
1071
+ for (const oid of oidMatches) {
1072
+ tokens.add(oid);
1073
+ tokens.add(normalizeKey(oid));
1074
+ }
1075
+ for (const piece of trimmed.split(/[\s,;|/:_-]+/u)) {
1076
+ const normalizedPiece = normalizeKey(piece);
1077
+ if (normalizedPiece.length > 0) {
1078
+ tokens.add(normalizedPiece);
1079
+ }
1080
+ }
1081
+ }
1082
+ function visit(candidate) {
1083
+ if (typeof candidate === "string") {
1084
+ addString(candidate);
1085
+ return;
1086
+ }
1087
+ if (typeof candidate === "number") {
1088
+ addString(String(candidate));
1089
+ return;
1090
+ }
1091
+ if (Array.isArray(candidate)) {
1092
+ for (const entry of candidate) {
1093
+ visit(entry);
1094
+ }
1095
+ return;
1096
+ }
1097
+ if (!isRecord(candidate)) {
1098
+ return;
1099
+ }
1100
+ for (const [key, child] of Object.entries(candidate)) {
1101
+ addString(key);
1102
+ visit(child);
1103
+ }
1104
+ }
1105
+ visit(value);
1106
+ return Array.from(tokens);
1107
+ }
1108
+ function assessCertificatePolicy(value) {
1109
+ const nodes = walkRecord(value);
1110
+ const tokens = new Set();
1111
+ for (const node of nodes) {
1112
+ const normalizedKey = normalizeKey(node.key);
1113
+ const isPolicyField = CERT_POLICY_FIELD_MARKERS.some((marker) => normalizedKey.includes(marker));
1114
+ if (!isPolicyField || !pathHasContextHint(node.path, ATTESTATION_CERT_CHAIN_CONTEXT_KEYS)) {
1115
+ continue;
1116
+ }
1117
+ for (const token of extractPolicyTokens(node.value)) {
1118
+ tokens.add(token);
1119
+ }
1120
+ }
1121
+ if (tokens.size === 0) {
1122
+ return { hasPolicyMaterial: false, hasCodeSigningPolicy: false };
1123
+ }
1124
+ const hasCodeSigningPolicy = Array.from(tokens).some((token) => CODE_SIGNING_POLICY_TOKENS.has(token));
1125
+ return {
1126
+ hasPolicyMaterial: true,
1127
+ hasCodeSigningPolicy,
1128
+ };
1129
+ }
1130
+ function parseNumber(value) {
1131
+ if (typeof value === "number" && Number.isFinite(value)) {
1132
+ return value;
1133
+ }
1134
+ if (typeof value === "string") {
1135
+ const trimmed = value.trim();
1136
+ if (/^\d+$/u.test(trimmed)) {
1137
+ return Number.parseInt(trimmed, 10);
1138
+ }
1139
+ }
1140
+ return null;
1141
+ }
1142
+ function parseEpochSeconds(value) {
1143
+ if (typeof value === "number" && Number.isFinite(value)) {
1144
+ if (value > 1_000_000_000_000) {
1145
+ return Math.floor(value / 1000);
1146
+ }
1147
+ if (value > 0) {
1148
+ return Math.floor(value);
1149
+ }
1150
+ return null;
1151
+ }
1152
+ if (typeof value === "string") {
1153
+ const trimmed = value.trim();
1154
+ if (trimmed.length === 0) {
1155
+ return null;
1156
+ }
1157
+ const asNumber = parseNumber(trimmed);
1158
+ if (asNumber !== null) {
1159
+ return parseEpochSeconds(asNumber);
1160
+ }
1161
+ const parsedMillis = Date.parse(trimmed);
1162
+ if (!Number.isNaN(parsedMillis) && parsedMillis > 0) {
1163
+ return Math.floor(parsedMillis / 1000);
1164
+ }
1165
+ }
1166
+ return null;
1167
+ }
1168
+ function parseCheckpointTreeSize(value) {
1169
+ const lines = value.split(/\r?\n/u);
1170
+ for (const rawLine of lines) {
1171
+ const line = rawLine.trim();
1172
+ if (/^\d+$/u.test(line)) {
1173
+ return Number.parseInt(line, 10);
1174
+ }
1175
+ }
1176
+ return null;
1177
+ }
1178
+ function assessTransparencyCheckpointConsistency(value) {
1179
+ const nodes = walkRecord(value);
1180
+ const logIndexes = [];
1181
+ const treeSizes = [];
1182
+ const integratedTimes = [];
1183
+ for (const node of nodes) {
1184
+ const normalizedKey = normalizeKey(node.key);
1185
+ const inTransparencyContext = pathHasContextHint(node.path, ATTESTATION_TRANSPARENCY_CONTEXT_KEYS) ||
1186
+ Array.from(ATTESTATION_TRANSPARENCY_CONTEXT_KEYS).some((hint) => normalizedKey.includes(hint));
1187
+ if (!inTransparencyContext) {
1188
+ continue;
1189
+ }
1190
+ const isLogIndexKey = TRANSPARENCY_LOG_INDEX_KEY_MARKERS.some((marker) => normalizedKey.includes(marker));
1191
+ if (isLogIndexKey) {
1192
+ const parsed = parseNumber(node.value);
1193
+ if (parsed !== null) {
1194
+ logIndexes.push(parsed);
1195
+ }
1196
+ }
1197
+ const isTreeSizeKey = TRANSPARENCY_TREE_SIZE_KEY_MARKERS.some((marker) => normalizedKey.includes(marker));
1198
+ if (isTreeSizeKey) {
1199
+ const parsed = parseNumber(node.value);
1200
+ if (parsed !== null) {
1201
+ treeSizes.push(parsed);
1202
+ }
1203
+ }
1204
+ const isIntegratedTimeKey = TRANSPARENCY_INTEGRATED_TIME_KEY_MARKERS.some((marker) => normalizedKey.includes(marker));
1205
+ if (isIntegratedTimeKey) {
1206
+ const parsed = parseEpochSeconds(node.value);
1207
+ if (parsed !== null) {
1208
+ integratedTimes.push(parsed);
1209
+ }
1210
+ }
1211
+ if (normalizedKey.includes("checkpoint") && typeof node.value === "string") {
1212
+ const parsed = parseCheckpointTreeSize(node.value);
1213
+ if (parsed !== null) {
1214
+ treeSizes.push(parsed);
1215
+ }
1216
+ }
1217
+ }
1218
+ const reasons = [];
1219
+ if (logIndexes.length > 0 && treeSizes.length > 0) {
1220
+ const maxLogIndex = Math.max(...logIndexes);
1221
+ const maxTreeSize = Math.max(...treeSizes);
1222
+ if (maxLogIndex >= maxTreeSize) {
1223
+ reasons.push(`logIndex ${maxLogIndex} is not less than checkpoint treeSize ${maxTreeSize}`);
1224
+ }
1225
+ }
1226
+ if (integratedTimes.length > 0) {
1227
+ const nowSeconds = Math.floor(Date.now() / 1000);
1228
+ const maxIntegratedTime = Math.max(...integratedTimes);
1229
+ if (maxIntegratedTime > nowSeconds + TRANSPARENCY_FUTURE_SKEW_SECONDS) {
1230
+ reasons.push(`integratedTime ${maxIntegratedTime} is in the future`);
1231
+ }
1232
+ }
1233
+ return {
1234
+ inconsistent: reasons.length > 0,
1235
+ reasons,
1236
+ };
1237
+ }
1238
+ function releaseChannelTokens(value) {
1239
+ const tokens = new Set();
1240
+ function addToken(raw) {
1241
+ const normalizedWhole = normalizeKey(raw);
1242
+ if (normalizedWhole.length > 0) {
1243
+ tokens.add(normalizedWhole);
1244
+ }
1245
+ for (const piece of raw.split(/[\s,;|/_-]+/u)) {
1246
+ const normalizedPiece = normalizeKey(piece);
1247
+ if (normalizedPiece.length > 0) {
1248
+ tokens.add(normalizedPiece);
1249
+ }
1250
+ }
1251
+ }
1252
+ function visit(candidate) {
1253
+ if (typeof candidate === "string") {
1254
+ addToken(candidate);
1255
+ return;
1256
+ }
1257
+ if (Array.isArray(candidate)) {
1258
+ for (const entry of candidate) {
1259
+ visit(entry);
1260
+ }
1261
+ return;
1262
+ }
1263
+ if (!isRecord(candidate)) {
1264
+ return;
1265
+ }
1266
+ for (const child of Object.values(candidate)) {
1267
+ visit(child);
1268
+ }
1269
+ }
1270
+ visit(value);
1271
+ return Array.from(tokens);
1272
+ }
1273
+ function findUnstableReleaseChannelTokens(tokens) {
1274
+ return tokens.filter((token) => UNSTABLE_RELEASE_CHANNEL_TOKENS.has(token));
1275
+ }
1276
+ function hasTransparencyProofMaterial(value) {
1277
+ return attestationHasField(value, ATTESTATION_TRANSPARENCY_FIELD_KEYS);
1278
+ }
1279
+ function isManifestEntryPath(path) {
1280
+ return /(^|\.)(plugins|extensions|marketplace)(\.|$)/iu.test(path);
1281
+ }
1282
+ function isAttestationPath(path) {
1283
+ const normalized = normalizeKey(path);
1284
+ return (normalized.includes("attestation") ||
1285
+ normalized.includes("provenance") ||
1286
+ normalized.includes("slsa") ||
1287
+ normalized.includes("sigstore") ||
1288
+ normalized.includes("cosign"));
1289
+ }
1290
+ function hasPackageIdentityFields(parent) {
1291
+ if (!parent) {
1292
+ return false;
1293
+ }
1294
+ for (const [key, value] of Object.entries(parent)) {
1295
+ if (PACKAGE_IDENTITY_KEYS.has(normalizeKey(key)) && hasPresentValue(value)) {
1296
+ return true;
1297
+ }
1298
+ }
1299
+ return false;
1300
+ }
1301
+ function hasSourceFields(parent) {
1302
+ if (!parent) {
1303
+ return false;
1304
+ }
1305
+ for (const [key, value] of Object.entries(parent)) {
1306
+ if (SOURCE_KEYS.has(normalizeKey(key)) && hasPresentValue(value)) {
1307
+ return true;
1308
+ }
1309
+ }
1310
+ return false;
1311
+ }
1312
+ function parseVsCodeVersionQualifiedId(value) {
1313
+ const trimmed = value.trim();
1314
+ const atIndex = trimmed.lastIndexOf("@");
1315
+ if (atIndex <= 0 || atIndex === trimmed.length - 1) {
1316
+ return null;
1317
+ }
1318
+ const baseId = trimmed.slice(0, atIndex).trim();
1319
+ const version = trimmed.slice(atIndex + 1).trim();
1320
+ if (!isScopedVsCodeExtensionId(baseId) || version.length === 0) {
1321
+ return null;
1322
+ }
1323
+ return { baseId, version };
1324
+ }
1325
+ function namespaceFromScopedExtensionId(value) {
1326
+ const trimmed = value.trim();
1327
+ if (!isScopedVsCodeExtensionId(trimmed)) {
1328
+ return null;
1329
+ }
1330
+ return trimmed.split(".")[0]?.toLowerCase() ?? null;
1331
+ }
1332
+ function isUnpinnedVersionSelector(value) {
1333
+ const trimmed = value.trim().toLowerCase();
1334
+ if (trimmed.length === 0) {
1335
+ return false;
1336
+ }
1337
+ if (UNPINNED_VERSION_TOKENS.has(trimmed)) {
1338
+ return true;
1339
+ }
1340
+ if (trimmed.startsWith("^") ||
1341
+ trimmed.startsWith("~") ||
1342
+ trimmed.startsWith(">") ||
1343
+ trimmed.startsWith("<") ||
1344
+ trimmed.includes("*") ||
1345
+ trimmed.includes("||") ||
1346
+ trimmed.includes(" - ")) {
1347
+ return true;
1348
+ }
1349
+ if (/(^|[.-])x($|[.-])/iu.test(trimmed)) {
1350
+ return true;
1351
+ }
1352
+ if (/(alpha|beta|rc|preview|canary|nightly|insider|dev|next|edge)/iu.test(trimmed)) {
1353
+ return true;
1354
+ }
1355
+ return false;
1356
+ }
1357
+ function hasIntegrityMetadata(parent) {
1358
+ if (!parent) {
1359
+ return false;
1360
+ }
1361
+ for (const [key, value] of Object.entries(parent)) {
1362
+ if (!INTEGRITY_KEYS.has(normalizeKey(key))) {
1363
+ continue;
1364
+ }
1365
+ if (typeof value === "string") {
1366
+ return value.trim().length > 0;
1367
+ }
1368
+ if (typeof value === "number" || typeof value === "boolean") {
1369
+ return true;
1370
+ }
1371
+ if (Array.isArray(value)) {
1372
+ return value.length > 0;
1373
+ }
1374
+ if (isRecord(value)) {
1375
+ return Object.values(value).some((entry) => entry !== null && entry !== undefined && `${entry}`.length > 0);
1376
+ }
1377
+ }
1378
+ return false;
1379
+ }
1380
+ function hasAttestationMetadata(parent) {
1381
+ if (!parent) {
1382
+ return false;
1383
+ }
1384
+ for (const [key, value] of Object.entries(parent)) {
1385
+ if (!ATTESTATION_KEYS.has(normalizeKey(key))) {
1386
+ continue;
1387
+ }
1388
+ if (hasPresentValue(value)) {
1389
+ return true;
1390
+ }
1391
+ }
1392
+ return false;
1393
+ }
1394
+ function extractCommandString(value) {
1395
+ if (typeof value === "string" && value.trim().length > 0) {
1396
+ return value.trim();
1397
+ }
1398
+ if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
1399
+ return value.join(" ").trim();
1400
+ }
1401
+ if (isRecord(value)) {
1402
+ if (typeof value.command === "string" && value.command.trim().length > 0) {
1403
+ return value.command.trim();
1404
+ }
1405
+ if (Array.isArray(value.command) && value.command.every((entry) => typeof entry === "string")) {
1406
+ return value.command.join(" ").trim();
1407
+ }
1408
+ }
1409
+ return null;
1410
+ }
1411
+ function tokenizeCommand(command) {
1412
+ return command
1413
+ .split(/\s+/u)
1414
+ .map((token) => token.trim())
1415
+ .filter((token) => token.length > 0);
1416
+ }
1417
+ function hasSuspiciousInstallCommand(command, blockedCommands) {
1418
+ const tokens = tokenizeCommand(command);
1419
+ const hasBlockedBinary = tokens.some((token) => blockedCommands.includes(token));
1420
+ return (hasBlockedBinary || SHELL_META_PATTERN.test(command) || NETWORK_UTILITY_PATTERN.test(command));
1421
+ }
1422
+ function isUserScopeManifest(filePath) {
1423
+ return filePath.startsWith("~/");
1424
+ }
1425
+ function downgradeSeverity(severity) {
1426
+ const index = SEVERITY_ORDER.indexOf(severity);
1427
+ if (index < 0) {
1428
+ return severity;
1429
+ }
1430
+ return SEVERITY_ORDER[Math.min(index + 1, SEVERITY_ORDER.length - 1)] ?? severity;
1431
+ }
1432
+ function resolveSeverity(filePath, ruleId, severity) {
1433
+ if (!isUserScopeManifest(filePath)) {
1434
+ return severity;
1435
+ }
1436
+ if (!USER_SCOPE_ADVISORY_RULE_IDS.has(ruleId)) {
1437
+ return severity;
1438
+ }
1439
+ return downgradeSeverity(severity);
1440
+ }
1441
+ function makeFinding(input, field, ruleId, severity, description, evidence) {
1442
+ const location = { field };
1443
+ if (typeof evidence?.line === "number") {
1444
+ location.line = evidence.line;
1445
+ }
1446
+ if (typeof evidence?.column === "number") {
1447
+ location.column = evidence.column;
1448
+ }
1449
+ const resolvedSeverity = resolveSeverity(input.filePath, ruleId, severity);
1450
+ return {
1451
+ rule_id: ruleId,
1452
+ finding_id: `PLUGIN_MANIFEST-${input.filePath}-${field}-${ruleId}`,
1453
+ severity: resolvedSeverity,
1454
+ category: "COMMAND_EXEC",
1455
+ layer: "L2",
1456
+ file_path: input.filePath,
1457
+ location,
1458
+ description,
1459
+ affected_tools: [
1460
+ "claude-code",
1461
+ "codex-cli",
1462
+ "opencode",
1463
+ "cursor",
1464
+ "windsurf",
1465
+ "github-copilot",
1466
+ "gemini-cli",
1467
+ "roo-code",
1468
+ "cline",
1469
+ "zed",
1470
+ "jetbrains-junie",
1471
+ ],
1472
+ cve: null,
1473
+ owasp: ["ASI02", "ASI04"],
1474
+ cwe: "CWE-829",
1475
+ confidence: "HIGH",
1476
+ fixable: true,
1477
+ remediation_actions: ["remove_field", "replace_with_default"],
1478
+ evidence: evidence?.evidence ?? null,
1479
+ suppressed: false,
1480
+ };
1481
+ }
1482
+ function walkRecord(value, path = "", parent = null) {
1483
+ const nodes = [];
1484
+ if (Array.isArray(value)) {
1485
+ value.forEach((entry, index) => {
1486
+ const childPath = path.length > 0 ? `${path}.${index}` : `${index}`;
1487
+ nodes.push(...walkRecord(entry, childPath, parent));
1488
+ });
1489
+ return nodes;
1490
+ }
1491
+ if (!isRecord(value)) {
1492
+ return nodes;
1493
+ }
1494
+ for (const [key, child] of Object.entries(value)) {
1495
+ const childPath = path.length > 0 ? `${path}.${key}` : key;
1496
+ nodes.push({ path: childPath, key, value: child, parent: value });
1497
+ nodes.push(...walkRecord(child, childPath, value));
1498
+ }
1499
+ return nodes;
1500
+ }
1501
+ export function detectPluginManifestIssues(input) {
1502
+ if (!shouldInspectFile(input.filePath) || !isRecord(input.parsed)) {
1503
+ return [];
1504
+ }
1505
+ const findings = [];
1506
+ const nodes = walkRecord(input.parsed);
1507
+ const kiroRegistryHostObservations = [];
1508
+ for (const node of nodes) {
1509
+ const normalizedKey = normalizeKey(node.key);
1510
+ if (PACKAGE_IDENTITY_KEYS.has(normalizedKey) &&
1511
+ typeof node.value === "string" &&
1512
+ isMarketplaceSemanticsManifest(input.filePath) &&
1513
+ isManifestEntryPath(node.path)) {
1514
+ if (isInvalidPackageIdentity(node.value)) {
1515
+ const evidence = buildFindingEvidence({
1516
+ textContent: input.textContent,
1517
+ jsonPaths: [node.path],
1518
+ searchTerms: [node.value],
1519
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1520
+ });
1521
+ findings.push(makeFinding(input, node.path, "plugin-manifest-invalid-package-identity", "HIGH", `Plugin identity field contains invalid path/URL-like value: ${node.value}`, evidence));
1522
+ }
1523
+ else if (isDisallowedNamespace(node.value, input.filePath)) {
1524
+ const evidence = buildFindingEvidence({
1525
+ textContent: input.textContent,
1526
+ jsonPaths: [node.path],
1527
+ searchTerms: [node.value],
1528
+ fallbackValue: `${node.path} uses disallowed namespace token`,
1529
+ });
1530
+ findings.push(makeFinding(input, node.path, "plugin-manifest-disallowed-namespace", "MEDIUM", `Plugin identity uses disallowed publisher/namespace token: ${node.value}`, evidence));
1531
+ }
1532
+ }
1533
+ if (SOURCE_KEYS.has(normalizedKey) && typeof node.value === "string") {
1534
+ if (isMarketplaceSemanticsManifest(input.filePath) &&
1535
+ isManifestEntryPath(node.path) &&
1536
+ !hasPackageIdentityFields(node.parent)) {
1537
+ const evidence = buildFindingEvidence({
1538
+ textContent: input.textContent,
1539
+ jsonPaths: [node.path],
1540
+ searchTerms: [node.value],
1541
+ fallbackValue: `${node.path} has source metadata but no package identity fields`,
1542
+ });
1543
+ findings.push(makeFinding(input, node.path, "plugin-manifest-missing-package-identity", "MEDIUM", "Plugin entry includes source metadata but lacks package identity fields (id/name/package)", evidence));
1544
+ }
1545
+ if (isLocalSourcePath(node.value)) {
1546
+ const evidence = buildFindingEvidence({
1547
+ textContent: input.textContent,
1548
+ jsonPaths: [node.path],
1549
+ searchTerms: [node.value],
1550
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1551
+ });
1552
+ findings.push(makeFinding(input, node.path, "plugin-manifest-local-source-path", "HIGH", `Plugin source points to local path: ${node.value}`, evidence));
1553
+ }
1554
+ const parsed = parseSourceUrl(node.value);
1555
+ if (IMAGE_KEYS.has(normalizedKey) &&
1556
+ isImageReference(node.value) &&
1557
+ !isDigestPinnedImage(node.value)) {
1558
+ const evidence = buildFindingEvidence({
1559
+ textContent: input.textContent,
1560
+ jsonPaths: [node.path],
1561
+ searchTerms: [node.value],
1562
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1563
+ });
1564
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unpinned-image", "MEDIUM", `Container image is not digest-pinned: ${node.value}`, evidence));
1565
+ }
1566
+ if (isLikelyGitSource(node.value, parsed) && !hasPinnedGitCommit(node.value, parsed)) {
1567
+ const evidence = buildFindingEvidence({
1568
+ textContent: input.textContent,
1569
+ jsonPaths: [node.path],
1570
+ searchTerms: [node.value],
1571
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1572
+ });
1573
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unpinned-git-source", "MEDIUM", `Git-based plugin source is not pinned to a commit hash: ${node.value}`, evidence));
1574
+ }
1575
+ if (parsed) {
1576
+ if (parsed.protocol === "https:" &&
1577
+ isArtifactSourceUrl(parsed) &&
1578
+ !hasIntegrityMetadata(node.parent)) {
1579
+ const evidence = buildFindingEvidence({
1580
+ textContent: input.textContent,
1581
+ jsonPaths: [node.path],
1582
+ searchTerms: [node.value],
1583
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1584
+ });
1585
+ findings.push(makeFinding(input, node.path, "plugin-manifest-missing-integrity", "MEDIUM", `Direct plugin artifact source is missing integrity metadata: ${node.value}`, evidence));
1586
+ }
1587
+ if (parsed.protocol === "http:") {
1588
+ const evidence = buildFindingEvidence({
1589
+ textContent: input.textContent,
1590
+ jsonPaths: [node.path],
1591
+ searchTerms: [node.value],
1592
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1593
+ });
1594
+ findings.push(makeFinding(input, node.path, "plugin-manifest-insecure-source-url", "HIGH", `Plugin source uses insecure HTTP URL: ${node.value}`, evidence));
1595
+ continue;
1596
+ }
1597
+ if (isKiroExtensionRegistryField(input.filePath, normalizedKey, node.path) &&
1598
+ parsed.protocol === "https:") {
1599
+ kiroRegistryHostObservations.push({
1600
+ path: node.path,
1601
+ host: parsed.hostname.toLowerCase(),
1602
+ });
1603
+ if (!isTrustedKiroExtensionRegistryDomain(parsed.hostname, input.trustedApiDomains)) {
1604
+ const evidence = buildFindingEvidence({
1605
+ textContent: input.textContent,
1606
+ jsonPaths: [node.path],
1607
+ searchTerms: [node.value],
1608
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1609
+ });
1610
+ findings.push(makeFinding(input, node.path, "plugin-manifest-nonallowlisted-extension-registry", "MEDIUM", `Kiro extension registry endpoint is not allowlisted: ${parsed.hostname}`, evidence));
1611
+ }
1612
+ continue;
1613
+ }
1614
+ if (parsed.protocol === "https:" &&
1615
+ !isTrustedSourceDomain(parsed.hostname, input.trustedApiDomains)) {
1616
+ const evidence = buildFindingEvidence({
1617
+ textContent: input.textContent,
1618
+ jsonPaths: [node.path],
1619
+ searchTerms: [node.value],
1620
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1621
+ });
1622
+ findings.push(makeFinding(input, node.path, "plugin-manifest-untrusted-source-url", "MEDIUM", `Plugin source points to untrusted domain: ${parsed.hostname}`, evidence));
1623
+ }
1624
+ const marketplacePolicy = marketplaceSourcePolicyForFile(input.filePath);
1625
+ if (parsed.protocol === "https:" &&
1626
+ marketplacePolicy &&
1627
+ requiresMarketplaceProvenance(marketplacePolicy) &&
1628
+ isAllowedByMarketplacePolicy(parsed.hostname, marketplacePolicy) &&
1629
+ !isArtifactSourceUrl(parsed) &&
1630
+ !hasIntegrityMetadata(node.parent) &&
1631
+ !hasAttestationMetadata(node.parent)) {
1632
+ const evidence = buildFindingEvidence({
1633
+ textContent: input.textContent,
1634
+ jsonPaths: [node.path],
1635
+ searchTerms: [node.value],
1636
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1637
+ });
1638
+ findings.push(makeFinding(input, node.path, "plugin-manifest-missing-marketplace-provenance", "MEDIUM", `Marketplace plugin source on ${parsed.hostname} is missing provenance metadata (integrity digest or attestation)`, evidence));
1639
+ }
1640
+ if (parsed.protocol === "https:" &&
1641
+ marketplacePolicy &&
1642
+ isMarketplaceAnchorDomain(parsed.hostname) &&
1643
+ !isAllowedByMarketplacePolicy(parsed.hostname, marketplacePolicy) &&
1644
+ !isUserTrustedDomain(parsed.hostname, input.trustedApiDomains)) {
1645
+ const evidence = buildFindingEvidence({
1646
+ textContent: input.textContent,
1647
+ jsonPaths: [node.path],
1648
+ searchTerms: [node.value],
1649
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1650
+ });
1651
+ findings.push(makeFinding(input, node.path, "plugin-manifest-cross-marketplace-source", "MEDIUM", `Plugin source domain ${parsed.hostname} is outside ${marketplacePolicy.id} marketplace policy`, evidence));
1652
+ }
1653
+ }
1654
+ }
1655
+ if (VERSION_FIELD_KEYS.has(normalizedKey) &&
1656
+ typeof node.value === "string" &&
1657
+ isMarketplaceSemanticsManifest(input.filePath) &&
1658
+ !isAttestationPath(node.path) &&
1659
+ (isManifestEntryPath(node.path) ||
1660
+ hasPackageIdentityFields(node.parent) ||
1661
+ hasSourceFields(node.parent))) {
1662
+ if (isUnpinnedVersionSelector(node.value)) {
1663
+ const evidence = buildFindingEvidence({
1664
+ textContent: input.textContent,
1665
+ jsonPaths: [node.path],
1666
+ searchTerms: [node.value],
1667
+ fallbackValue: `${node.path} = ${JSON.stringify(node.value)}`,
1668
+ });
1669
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unpinned-version", "MEDIUM", `Plugin entry uses unpinned or unstable version selector: ${node.value}`, evidence));
1670
+ }
1671
+ }
1672
+ if (INSTALL_SCRIPT_KEYS.has(normalizedKey)) {
1673
+ const command = extractCommandString(node.value);
1674
+ if (!command || !hasSuspiciousInstallCommand(command, input.blockedCommands)) {
1675
+ continue;
1676
+ }
1677
+ const evidence = buildFindingEvidence({
1678
+ textContent: input.textContent,
1679
+ jsonPaths: [node.path],
1680
+ searchTerms: [command],
1681
+ fallbackValue: `${node.path} = ${JSON.stringify(command)}`,
1682
+ });
1683
+ findings.push(makeFinding(input, node.path, "plugin-manifest-install-script", "CRITICAL", `Suspicious plugin install script detected: ${command}`, evidence));
1684
+ }
1685
+ if (PERMISSION_KEYS.has(normalizedKey)) {
1686
+ const tokens = collectPermissionTokens(node.value);
1687
+ const wildcardTokens = Array.from(new Set(findWildcardPermissionTokens(tokens)));
1688
+ if (wildcardTokens.length > 0) {
1689
+ const evidence = buildFindingEvidence({
1690
+ textContent: input.textContent,
1691
+ jsonPaths: [node.path],
1692
+ searchTerms: wildcardTokens,
1693
+ fallbackValue: `${node.path} contains wildcard permission grants`,
1694
+ });
1695
+ findings.push(makeFinding(input, node.path, "plugin-manifest-wildcard-permissions", "HIGH", `Plugin permissions include wildcard grants: ${wildcardTokens.join(", ")}`, evidence));
1696
+ continue;
1697
+ }
1698
+ const riskyTokens = Array.from(new Set(findRiskyCapabilityTokens(tokens)));
1699
+ if (riskyTokens.length > 0) {
1700
+ const evidence = buildFindingEvidence({
1701
+ textContent: input.textContent,
1702
+ jsonPaths: [node.path],
1703
+ searchTerms: riskyTokens,
1704
+ fallbackValue: `${node.path} contains risky capability grants`,
1705
+ });
1706
+ findings.push(makeFinding(input, node.path, "plugin-manifest-risky-capabilities", "MEDIUM", `Plugin permissions include risky capability grants: ${riskyTokens.join(", ")}`, evidence));
1707
+ }
1708
+ }
1709
+ if (UNVERIFIED_PUBLISHER_KEYS.has(normalizedKey) && isExplicitlyUnverified(node.value)) {
1710
+ const evidence = buildFindingEvidence({
1711
+ textContent: input.textContent,
1712
+ jsonPaths: [node.path],
1713
+ fallbackValue: `${node.path} is marked unverified`,
1714
+ });
1715
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unverified-publisher", "HIGH", "Plugin publisher/signature metadata is explicitly marked unverified", evidence));
1716
+ }
1717
+ if (SIGNATURE_BYPASS_KEYS.has(normalizedKey) && isAffirmative(node.value)) {
1718
+ const evidence = buildFindingEvidence({
1719
+ textContent: input.textContent,
1720
+ jsonPaths: [node.path],
1721
+ fallbackValue: `${node.path} enables verification bypass`,
1722
+ });
1723
+ findings.push(makeFinding(input, node.path, "plugin-manifest-signature-bypass", "HIGH", "Plugin manifest enables signature/checksum verification bypass", evidence));
1724
+ }
1725
+ if (isKiroProductManifest(input.filePath) &&
1726
+ normalizeKey(node.path).includes("extensionsgallery") &&
1727
+ ((PUBLISHER_TRUST_BYPASS_KEYS.has(normalizedKey) && isAffirmative(node.value)) ||
1728
+ (PUBLISHER_TRUST_DISABLED_KEYS.has(normalizedKey) && isExplicitlyUnverified(node.value)))) {
1729
+ const evidence = buildFindingEvidence({
1730
+ textContent: input.textContent,
1731
+ jsonPaths: [node.path],
1732
+ fallbackValue: `${node.path} weakens extension publisher trust verification`,
1733
+ });
1734
+ findings.push(makeFinding(input, node.path, "plugin-manifest-publisher-trust-bypass", "HIGH", "Kiro extension publisher trust-policy verification is bypassed or disabled", evidence));
1735
+ }
1736
+ if (TRANSPARENCY_BYPASS_KEYS.has(normalizedKey) && isAffirmative(node.value)) {
1737
+ const evidence = buildFindingEvidence({
1738
+ textContent: input.textContent,
1739
+ jsonPaths: [node.path],
1740
+ fallbackValue: `${node.path} bypasses transparency proof verification`,
1741
+ });
1742
+ findings.push(makeFinding(input, node.path, "plugin-manifest-transparency-bypass", "HIGH", "Plugin manifest disables or bypasses transparency-log proof verification", evidence));
1743
+ }
1744
+ if (ATTESTATION_KEYS.has(normalizedKey)) {
1745
+ const profile = attestationProfileForFile(input.filePath);
1746
+ if (hasUnverifiedAttestationSignal(node.value)) {
1747
+ const evidence = buildFindingEvidence({
1748
+ textContent: input.textContent,
1749
+ jsonPaths: [node.path],
1750
+ fallbackValue: `${node.path} attestation/provenance metadata is unverified`,
1751
+ });
1752
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unverified-attestation", "HIGH", "Plugin attestation/provenance metadata indicates verification failure or unverified state", evidence));
1753
+ }
1754
+ const issuerHosts = collectAttestationIssuerHosts(node.value);
1755
+ const untrustedIssuerHosts = issuerHosts.filter((host) => !isTrustedAttestationIssuer(host, profile, input.trustedApiDomains));
1756
+ if (untrustedIssuerHosts.length > 0) {
1757
+ const uniqueHosts = Array.from(new Set(untrustedIssuerHosts));
1758
+ const evidence = buildFindingEvidence({
1759
+ textContent: input.textContent,
1760
+ jsonPaths: [node.path],
1761
+ searchTerms: uniqueHosts,
1762
+ fallbackValue: `${node.path} contains untrusted attestation issuers`,
1763
+ });
1764
+ findings.push(makeFinding(input, node.path, "plugin-manifest-untrusted-attestation-issuer", "MEDIUM", `Attestation issuer is not trusted: ${uniqueHosts.join(", ")}`, evidence));
1765
+ }
1766
+ const missingParts = profile.requiredFields
1767
+ .filter((requirement) => !attestationHasField(node.value, requirement.keys))
1768
+ .map((requirement) => requirement.label);
1769
+ if (missingParts.length > 0) {
1770
+ const evidence = buildFindingEvidence({
1771
+ textContent: input.textContent,
1772
+ jsonPaths: [node.path],
1773
+ fallbackValue: `${node.path} is missing attestation fields: ${missingParts.join(", ")}`,
1774
+ });
1775
+ const profileRuleId = incompleteAttestationRuleIdForProfile(profile);
1776
+ findings.push(makeFinding(input, node.path, "plugin-manifest-incomplete-attestation", "MEDIUM", `Attestation metadata is incomplete (missing: ${missingParts.join(", ")})`, evidence));
1777
+ findings.push(makeFinding(input, node.path, profileRuleId, "MEDIUM", `Attestation metadata is incomplete for ${profile.schemaProfile} profile (missing: ${missingParts.join(", ")})`, evidence));
1778
+ }
1779
+ if (profile.enforceCertificatePolicy) {
1780
+ const certPolicy = assessCertificatePolicy(node.value);
1781
+ if (certPolicy.hasPolicyMaterial && !certPolicy.hasCodeSigningPolicy) {
1782
+ const evidence = buildFindingEvidence({
1783
+ textContent: input.textContent,
1784
+ jsonPaths: [node.path],
1785
+ fallbackValue: `${node.path} contains certificate policy fields without code-signing EKU/OID`,
1786
+ });
1787
+ findings.push(makeFinding(input, node.path, "plugin-manifest-invalid-cert-policy", "HIGH", "Attestation certificate policy lacks code-signing EKU/OID constraints", evidence));
1788
+ }
1789
+ }
1790
+ if (hasAttestationContextFailure(node.value, ATTESTATION_CERT_CHAIN_CONTEXT_KEYS)) {
1791
+ const evidence = buildFindingEvidence({
1792
+ textContent: input.textContent,
1793
+ jsonPaths: [node.path],
1794
+ fallbackValue: `${node.path} contains certificate-chain verification failures`,
1795
+ });
1796
+ findings.push(makeFinding(input, node.path, "plugin-manifest-invalid-cert-chain", "HIGH", "Attestation metadata indicates certificate-chain verification failure", evidence));
1797
+ }
1798
+ if (hasAttestationContextFailure(node.value, ATTESTATION_TRANSPARENCY_CONTEXT_KEYS)) {
1799
+ const evidence = buildFindingEvidence({
1800
+ textContent: input.textContent,
1801
+ jsonPaths: [node.path],
1802
+ fallbackValue: `${node.path} contains transparency-log proof verification failures`,
1803
+ });
1804
+ findings.push(makeFinding(input, node.path, "plugin-manifest-transparency-proof-failed", "HIGH", "Attestation metadata indicates transparency-log proof verification failure", evidence));
1805
+ }
1806
+ const transparencyCheckpointAssessment = assessTransparencyCheckpointConsistency(node.value);
1807
+ if (transparencyCheckpointAssessment.inconsistent) {
1808
+ const evidence = buildFindingEvidence({
1809
+ textContent: input.textContent,
1810
+ jsonPaths: [node.path],
1811
+ fallbackValue: `${node.path} has inconsistent transparency checkpoint metadata`,
1812
+ });
1813
+ findings.push(makeFinding(input, node.path, "plugin-manifest-transparency-checkpoint-inconsistent", "HIGH", `Transparency checkpoint metadata is inconsistent (${transparencyCheckpointAssessment.reasons.join("; ")})`, evidence));
1814
+ }
1815
+ if (profile.requireTransparencyProof && !hasTransparencyProofMaterial(node.value)) {
1816
+ const evidence = buildFindingEvidence({
1817
+ textContent: input.textContent,
1818
+ jsonPaths: [node.path],
1819
+ fallbackValue: `${node.path} is missing transparency-log proof metadata`,
1820
+ });
1821
+ findings.push(makeFinding(input, node.path, "plugin-manifest-missing-transparency-proof", "MEDIUM", "Attestation metadata is missing required transparency-log proof fields for this profile", evidence));
1822
+ }
1823
+ }
1824
+ if (PRERELEASE_FLAG_KEYS.has(normalizedKey) && isAffirmative(node.value)) {
1825
+ const evidence = buildFindingEvidence({
1826
+ textContent: input.textContent,
1827
+ jsonPaths: [node.path],
1828
+ fallbackValue: `${node.path} enables prerelease/preview channel usage`,
1829
+ });
1830
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unstable-release-channel", "MEDIUM", "Plugin manifest opts into prerelease or preview release channels", evidence));
1831
+ }
1832
+ if (RELEASE_CHANNEL_KEYS.has(normalizedKey)) {
1833
+ const unstableTokens = Array.from(new Set(findUnstableReleaseChannelTokens(releaseChannelTokens(node.value))));
1834
+ if (unstableTokens.length > 0) {
1835
+ const evidence = buildFindingEvidence({
1836
+ textContent: input.textContent,
1837
+ jsonPaths: [node.path],
1838
+ searchTerms: unstableTokens,
1839
+ fallbackValue: `${node.path} targets unstable release channels`,
1840
+ });
1841
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unstable-release-channel", "MEDIUM", `Plugin manifest targets unstable release channels: ${unstableTokens.join(", ")}`, evidence));
1842
+ }
1843
+ }
1844
+ if (isZedExtensionsManifest(input.filePath) &&
1845
+ normalizedKey === "id" &&
1846
+ typeof node.value === "string" &&
1847
+ isManifestEntryPath(node.path)) {
1848
+ const trimmed = node.value.trim();
1849
+ if (!isScopedVsCodeExtensionId(trimmed)) {
1850
+ const evidence = buildFindingEvidence({
1851
+ textContent: input.textContent,
1852
+ jsonPaths: [node.path],
1853
+ searchTerms: [trimmed],
1854
+ fallbackValue: `${node.path} contains unscoped extension id ${trimmed}`,
1855
+ });
1856
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unscoped-extension-id", "MEDIUM", `Zed extension id is not publisher-scoped: ${trimmed}`, evidence));
1857
+ }
1858
+ else if (node.parent &&
1859
+ typeof node.parent.publisher === "string" &&
1860
+ node.parent.publisher.trim().length > 0) {
1861
+ const idNamespace = namespaceFromScopedExtensionId(trimmed);
1862
+ const publisherNamespace = node.parent.publisher.trim().toLowerCase();
1863
+ if (idNamespace && idNamespace !== publisherNamespace) {
1864
+ const evidence = buildFindingEvidence({
1865
+ textContent: input.textContent,
1866
+ jsonPaths: [node.path],
1867
+ searchTerms: [trimmed, node.parent.publisher],
1868
+ fallbackValue: `${node.path} namespace ${idNamespace} does not match publisher ${publisherNamespace}`,
1869
+ });
1870
+ findings.push(makeFinding(input, node.path, "plugin-manifest-publisher-identity-mismatch", "HIGH", `Zed extension id namespace ${idNamespace} does not match declared publisher ${publisherNamespace}`, evidence));
1871
+ }
1872
+ }
1873
+ }
1874
+ if (isVsCodeExtensionsManifest(input.filePath) &&
1875
+ VSCODE_RECOMMENDATION_KEYS.has(normalizedKey)) {
1876
+ if (!Array.isArray(node.value)) {
1877
+ continue;
1878
+ }
1879
+ for (const entry of node.value) {
1880
+ if (typeof entry !== "string") {
1881
+ continue;
1882
+ }
1883
+ const trimmed = entry.trim();
1884
+ if (trimmed.length === 0) {
1885
+ continue;
1886
+ }
1887
+ const versionQualified = parseVsCodeVersionQualifiedId(trimmed);
1888
+ if (versionQualified) {
1889
+ const evidence = buildFindingEvidence({
1890
+ textContent: input.textContent,
1891
+ jsonPaths: [node.path],
1892
+ searchTerms: [trimmed],
1893
+ fallbackValue: `${node.path} contains version-qualified extension id ${trimmed}`,
1894
+ });
1895
+ findings.push(makeFinding(input, node.path, "plugin-manifest-versioned-extension-id", "MEDIUM", `VS Code extension recommendation includes version selector; use plain publisher-scoped id only: ${trimmed}`, evidence));
1896
+ continue;
1897
+ }
1898
+ if (isInvalidVsCodeRecommendationEntry(trimmed)) {
1899
+ const evidence = buildFindingEvidence({
1900
+ textContent: input.textContent,
1901
+ jsonPaths: [node.path],
1902
+ searchTerms: [trimmed],
1903
+ fallbackValue: `${node.path} contains invalid extension recommendation entry ${trimmed}`,
1904
+ });
1905
+ findings.push(makeFinding(input, node.path, "plugin-manifest-invalid-extension-id", "HIGH", `VS Code extension recommendation contains invalid path/URL-like entry: ${trimmed}`, evidence));
1906
+ continue;
1907
+ }
1908
+ if (isScopedVsCodeExtensionId(trimmed)) {
1909
+ if (isDisallowedNamespace(trimmed, input.filePath)) {
1910
+ const evidence = buildFindingEvidence({
1911
+ textContent: input.textContent,
1912
+ jsonPaths: [node.path],
1913
+ searchTerms: [trimmed],
1914
+ fallbackValue: `${node.path} contains disallowed namespace id ${trimmed}`,
1915
+ });
1916
+ findings.push(makeFinding(input, node.path, "plugin-manifest-disallowed-namespace", "MEDIUM", `VS Code extension recommendation uses disallowed publisher namespace: ${trimmed}`, evidence));
1917
+ }
1918
+ continue;
1919
+ }
1920
+ const evidence = buildFindingEvidence({
1921
+ textContent: input.textContent,
1922
+ jsonPaths: [node.path],
1923
+ searchTerms: [trimmed],
1924
+ fallbackValue: `${node.path} contains unscoped extension id ${trimmed}`,
1925
+ });
1926
+ findings.push(makeFinding(input, node.path, "plugin-manifest-unscoped-extension-id", "MEDIUM", `VS Code extension recommendation is not publisher-scoped: ${trimmed}`, evidence));
1927
+ }
1928
+ }
1929
+ }
1930
+ if (isKiroProductManifest(input.filePath)) {
1931
+ const uniqueHosts = Array.from(new Set(kiroRegistryHostObservations.map((entry) => entry.host)));
1932
+ if (uniqueHosts.length > 1) {
1933
+ const evidence = buildFindingEvidence({
1934
+ textContent: input.textContent,
1935
+ jsonPaths: kiroRegistryHostObservations.map((entry) => entry.path),
1936
+ searchTerms: uniqueHosts,
1937
+ fallbackValue: `extensionsGallery uses multiple registry hosts: ${uniqueHosts.join(", ")}`,
1938
+ });
1939
+ findings.push(makeFinding(input, "extensionsGallery", "plugin-manifest-extension-registry-host-mismatch", "MEDIUM", `Kiro extensionsGallery endpoints reference multiple hosts: ${uniqueHosts.join(", ")}`, evidence));
1940
+ }
1941
+ }
1942
+ return findings;
1943
+ }