@pipeline-builder/api-core 4.1.13 → 4.1.15

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 (187) hide show
  1. package/README.md +3 -2
  2. package/lib/constants/index.d.ts +0 -1
  3. package/lib/constants/index.js +1 -2
  4. package/lib/errors/app-errors.d.ts +8 -3
  5. package/lib/errors/app-errors.js +10 -5
  6. package/lib/helpers/access-helpers.d.ts +4 -6
  7. package/lib/helpers/access-helpers.js +6 -8
  8. package/lib/helpers/index.d.ts +3 -3
  9. package/lib/helpers/index.js +4 -4
  10. package/lib/helpers/org-hierarchy-http.d.ts +11 -10
  11. package/lib/helpers/org-hierarchy-http.js +24 -35
  12. package/lib/helpers/org-hierarchy.d.ts +37 -3
  13. package/lib/helpers/org-hierarchy.js +45 -4
  14. package/lib/helpers/restore-helpers.d.ts +44 -24
  15. package/lib/helpers/restore-helpers.js +41 -52
  16. package/lib/helpers/sse-helpers.d.ts +29 -7
  17. package/lib/helpers/sse-helpers.js +36 -9
  18. package/lib/index.d.ts +16 -14
  19. package/lib/index.js +17 -15
  20. package/lib/middleware/assurance.d.ts +116 -0
  21. package/lib/middleware/assurance.js +176 -0
  22. package/lib/middleware/auth.d.ts +5 -427
  23. package/lib/middleware/auth.js +36 -789
  24. package/lib/middleware/ecosystem-guard.d.ts +47 -0
  25. package/lib/middleware/ecosystem-guard.js +60 -0
  26. package/lib/middleware/index.d.ts +11 -4
  27. package/lib/middleware/index.js +12 -5
  28. package/lib/middleware/jwt-verify.d.ts +52 -0
  29. package/lib/middleware/jwt-verify.js +105 -0
  30. package/lib/middleware/permission-gates.d.ts +133 -0
  31. package/lib/middleware/permission-gates.js +260 -0
  32. package/lib/middleware/revocation.d.ts +96 -0
  33. package/lib/middleware/revocation.js +162 -0
  34. package/lib/middleware/route-table.d.ts +11 -0
  35. package/lib/middleware/route-table.js +4 -1
  36. package/lib/middleware/service-tokens.d.ts +83 -0
  37. package/lib/middleware/service-tokens.js +158 -0
  38. package/lib/middleware/step-up.d.ts +2 -2
  39. package/lib/middleware/step-up.js +8 -7
  40. package/lib/middleware/system-org.d.ts +20 -0
  41. package/lib/middleware/system-org.js +25 -0
  42. package/lib/openapi/routes/plugin-routes.js +133 -10
  43. package/lib/openapi/schema-registry.js +2 -5
  44. package/lib/routes/index.d.ts +1 -1
  45. package/lib/routes/index.js +2 -2
  46. package/lib/services/api-key-exchange.js +7 -5
  47. package/lib/services/audit-spool.d.ts +34 -10
  48. package/lib/services/audit-spool.js +75 -34
  49. package/lib/services/cache-service.d.ts +7 -0
  50. package/lib/services/cache-service.js +1 -1
  51. package/lib/services/circuit-breaker.d.ts +14 -1
  52. package/lib/services/circuit-breaker.js +36 -10
  53. package/lib/services/compliance-client.d.ts +10 -2
  54. package/lib/services/compliance-client.js +17 -10
  55. package/lib/services/compliance-event-subscriber.js +6 -6
  56. package/lib/services/durable-event-bus.js +8 -11
  57. package/lib/services/ecosystem-notify-client.d.ts +32 -0
  58. package/lib/services/ecosystem-notify-client.js +71 -0
  59. package/lib/services/env-redis.d.ts +4 -1
  60. package/lib/services/env-redis.js +32 -3
  61. package/lib/services/http-client.d.ts +19 -0
  62. package/lib/services/http-client.js +98 -19
  63. package/lib/services/index.d.ts +25 -24
  64. package/lib/services/index.js +25 -22
  65. package/lib/services/jwks-cache.js +5 -4
  66. package/lib/services/leader-lock.d.ts +22 -5
  67. package/lib/services/leader-lock.js +61 -6
  68. package/lib/services/quota.d.ts +9 -4
  69. package/lib/services/quota.js +34 -40
  70. package/lib/services/remote-audit-client.d.ts +28 -32
  71. package/lib/services/remote-audit-client.js +166 -49
  72. package/lib/services/retry-forever.js +5 -11
  73. package/lib/services/retry-strategy.d.ts +1 -1
  74. package/lib/services/retry-strategy.js +5 -4
  75. package/lib/services/scheduler.d.ts +8 -3
  76. package/lib/services/scheduler.js +17 -5
  77. package/lib/services/service-boot.d.ts +14 -13
  78. package/lib/services/service-boot.js +18 -15
  79. package/lib/services/service-keys.d.ts +1 -1
  80. package/lib/services/service-keys.js +12 -18
  81. package/lib/services/sse-ticket-store.d.ts +9 -1
  82. package/lib/services/sse-ticket-store.js +24 -9
  83. package/lib/services/system-notification.d.ts +27 -0
  84. package/lib/services/system-notification.js +50 -0
  85. package/lib/services/token-revocation.d.ts +50 -1
  86. package/lib/services/token-revocation.js +103 -3
  87. package/lib/types/audit-events.d.ts +6 -6
  88. package/lib/types/audit-events.js +1 -1
  89. package/lib/types/common.d.ts +32 -13
  90. package/lib/types/common.js +2 -1
  91. package/lib/types/ecosystem-notifications.d.ts +142 -0
  92. package/lib/types/ecosystem-notifications.js +210 -0
  93. package/lib/types/ecosystem.d.ts +77 -0
  94. package/lib/types/ecosystem.js +119 -0
  95. package/lib/types/error-codes.d.ts +81 -0
  96. package/lib/types/error-codes.js +108 -1
  97. package/lib/types/feature-flags.d.ts +21 -1
  98. package/lib/types/feature-flags.js +36 -2
  99. package/lib/types/index.d.ts +14 -9
  100. package/lib/types/index.js +15 -10
  101. package/lib/types/permissions.d.ts +46 -8
  102. package/lib/types/permissions.js +75 -12
  103. package/lib/types/plugin-catalog.d.ts +35 -0
  104. package/lib/types/plugin-catalog.js +39 -0
  105. package/lib/types/plugin-health.d.ts +102 -0
  106. package/lib/types/plugin-health.js +184 -0
  107. package/lib/types/quota-tiers.d.ts +15 -0
  108. package/lib/types/quota-tiers.js +23 -3
  109. package/lib/types/wire-vocabulary.d.ts +102 -0
  110. package/lib/types/wire-vocabulary.js +94 -0
  111. package/lib/utils/alias-resolver.js +2 -2
  112. package/lib/utils/audit.d.ts +5 -2
  113. package/lib/utils/audit.js +6 -3
  114. package/lib/utils/compliance-attributes.js +3 -4
  115. package/lib/utils/concurrency.d.ts +7 -0
  116. package/lib/utils/concurrency.js +13 -1
  117. package/lib/utils/env.d.ts +3 -4
  118. package/lib/utils/env.js +4 -5
  119. package/lib/utils/identity.d.ts +13 -4
  120. package/lib/utils/identity.js +18 -10
  121. package/lib/utils/index.d.ts +13 -10
  122. package/lib/utils/index.js +13 -10
  123. package/lib/utils/jwk.d.ts +4 -0
  124. package/lib/utils/jwk.js +13 -1
  125. package/lib/utils/logger.d.ts +1 -1
  126. package/lib/utils/logger.js +1 -1
  127. package/lib/utils/object.d.ts +11 -0
  128. package/lib/utils/object.js +24 -1
  129. package/lib/utils/params.d.ts +8 -22
  130. package/lib/utils/params.js +15 -43
  131. package/lib/utils/plugin-compliance.d.ts +98 -0
  132. package/lib/utils/plugin-compliance.js +78 -0
  133. package/lib/utils/proof-of-work.d.ts +69 -0
  134. package/lib/utils/proof-of-work.js +139 -0
  135. package/lib/utils/response.d.ts +16 -6
  136. package/lib/utils/response.js +18 -15
  137. package/lib/utils/retention.d.ts +17 -0
  138. package/lib/utils/retention.js +30 -0
  139. package/lib/utils/secret-encryption.d.ts +7 -7
  140. package/lib/utils/secret-encryption.js +9 -10
  141. package/lib/utils/secret-rotation.js +2 -2
  142. package/lib/utils/sensitive-patterns.js +4 -5
  143. package/lib/utils/service-registry.d.ts +15 -0
  144. package/lib/utils/service-registry.js +24 -0
  145. package/lib/utils/ssrf.d.ts +11 -2
  146. package/lib/utils/ssrf.js +112 -33
  147. package/lib/validation/ai-schemas.js +3 -2
  148. package/lib/validation/common-schemas.d.ts +0 -9
  149. package/lib/validation/common-schemas.js +1 -7
  150. package/lib/validation/dockerfile-static.d.ts +26 -0
  151. package/lib/validation/dockerfile-static.js +210 -0
  152. package/lib/validation/index.d.ts +13 -4
  153. package/lib/validation/index.js +14 -5
  154. package/lib/validation/message-schemas.js +6 -4
  155. package/lib/validation/pipeline-schemas.d.ts +8 -2
  156. package/lib/validation/pipeline-schemas.js +30 -4
  157. package/lib/validation/plugin-base-images.d.ts +20 -0
  158. package/lib/validation/plugin-base-images.js +17 -0
  159. package/lib/validation/plugin-catalog-detect.d.ts +86 -0
  160. package/lib/validation/plugin-catalog-detect.js +237 -0
  161. package/lib/validation/plugin-catalog-metadata.d.ts +141 -0
  162. package/lib/validation/plugin-catalog-metadata.js +162 -0
  163. package/lib/validation/plugin-heuristics.d.ts +41 -0
  164. package/lib/validation/plugin-heuristics.js +211 -0
  165. package/lib/validation/plugin-lint.d.ts +18 -0
  166. package/lib/validation/plugin-lint.js +136 -0
  167. package/lib/validation/plugin-name-confusable.d.ts +15 -0
  168. package/lib/validation/plugin-name-confusable.js +81 -0
  169. package/lib/validation/plugin-schemas.d.ts +40 -26
  170. package/lib/validation/plugin-schemas.js +26 -23
  171. package/lib/validation/plugin-spec-schema.d.ts +112 -0
  172. package/lib/validation/plugin-spec-schema.js +163 -0
  173. package/lib/validation/plugin-template-contract.d.ts +64 -0
  174. package/lib/validation/plugin-template-contract.js +113 -0
  175. package/package.json +69 -4
  176. package/lib/testing/mock-api-core.d.ts +0 -150
  177. package/lib/testing/mock-api-core.js +0 -246
  178. package/lib/testing/mock-drizzle.d.ts +0 -6
  179. package/lib/testing/mock-drizzle.js +0 -57
  180. package/lib/testing/route-coverage.d.ts +0 -64
  181. package/lib/testing/route-coverage.js +0 -183
  182. package/lib/testing/service-tokens.d.ts +0 -66
  183. package/lib/testing/service-tokens.js +0 -137
  184. package/lib/testing/tier-mock.d.ts +0 -44
  185. package/lib/testing/tier-mock.js +0 -65
  186. package/lib/testing/user-tokens.d.ts +0 -75
  187. package/lib/testing/user-tokens.js +0 -115
@@ -0,0 +1,162 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Plugin CATALOG metadata: the descriptive fields a plugin version shows in the
5
+ * catalog, detected from the package and then accepted or edited by the user
6
+ * (docs/plugin-publishing.md).
7
+ *
8
+ * ONE validator for both paths: a value detected from the package (spec,
9
+ * README, the plugin's own Dockerfile labels) and a value typed into a form or
10
+ * sent by the CLI pass the same rules. Shared from api-core so the plugin
11
+ * service, the frontend and the CLI can't drift.
12
+ *
13
+ * Only DESCRIPTIVE fields live here. Execution-contract fields (commands, env,
14
+ * secrets, compute type, …) are spec-only — changing one is a new version with a
15
+ * new digest — and an edit payload naming one is refused
16
+ * ({@link findContractKeys}).
17
+ */
18
+ import { z } from 'zod';
19
+ import { PLUGIN_CATALOG_FIELDS, PLUGIN_CATEGORIES, PLUGIN_CHANGELOG_MAX_BYTES, PLUGIN_DESCRIPTION_MAX, PLUGIN_DISPLAY_NAME_MAX, PLUGIN_KEYWORD_MAX_LENGTH, PLUGIN_KEYWORDS_MAX, PLUGIN_README_MAX_BYTES, PLUGIN_SUMMARY_MAX, PLUGIN_URL_MAX, } from '../types/plugin-catalog.js';
20
+ /**
21
+ * Accepted SPDX license identifiers — a compact allowlist of the ids that
22
+ * cover effectively every published plugin, rather than the full ~700-entry
23
+ * SPDX list (an unfamiliar id is worth a human look). Case-sensitive, exactly
24
+ * as SPDX spells them. `LicenseRef-Proprietary` is the SPDX-sanctioned
25
+ * spelling for "not open source".
26
+ */
27
+ export const SPDX_LICENSE_IDS = new Set([
28
+ 'Apache-2.0', 'MIT', 'MIT-0', 'ISC', '0BSD', 'Unlicense', 'CC0-1.0', 'Zlib', 'BSL-1.0',
29
+ 'BSD-2-Clause', 'BSD-3-Clause',
30
+ 'MPL-2.0', 'EPL-1.0', 'EPL-2.0',
31
+ 'LGPL-2.1-only', 'LGPL-2.1-or-later', 'LGPL-3.0-only', 'LGPL-3.0-or-later',
32
+ 'GPL-2.0-only', 'GPL-2.0-or-later', 'GPL-3.0-only', 'GPL-3.0-or-later',
33
+ 'AGPL-3.0-only', 'AGPL-3.0-or-later',
34
+ 'CC-BY-4.0', 'CC-BY-SA-4.0', 'Python-2.0', 'PostgreSQL', 'Artistic-2.0',
35
+ 'BUSL-1.1', 'Elastic-2.0', 'SSPL-1.0',
36
+ 'LicenseRef-Proprietary',
37
+ ]);
38
+ /** True when `id` is an accepted SPDX identifier. */
39
+ export function isAllowedSpdxId(id) {
40
+ return SPDX_LICENSE_IDS.has(id);
41
+ }
42
+ /**
43
+ * URL shorteners refused in project links: a shortener hides the destination
44
+ * from the reviewer and can be re-pointed after approval.
45
+ */
46
+ export const URL_SHORTENER_HOSTS = new Set([
47
+ 'bit.ly', 't.co', 'tinyurl.com', 'goo.gl', 'ow.ly', 'is.gd', 'buff.ly', 'rebrand.ly', 'cutt.ly', 'shorturl.at',
48
+ ]);
49
+ /**
50
+ * Why `raw` is not an acceptable project URL, or `null` when it is: must parse,
51
+ * be `https:`, carry no userinfo, and not point at a known shortener (the host
52
+ * or any parent domain of it).
53
+ */
54
+ export function projectUrlProblem(raw) {
55
+ if (raw.length > PLUGIN_URL_MAX)
56
+ return `must be at most ${PLUGIN_URL_MAX} characters`;
57
+ let url;
58
+ try {
59
+ url = new URL(raw);
60
+ }
61
+ catch {
62
+ return 'must be an absolute URL';
63
+ }
64
+ if (url.protocol !== 'https:')
65
+ return 'must use https';
66
+ if (url.username || url.password)
67
+ return 'must not embed credentials';
68
+ const host = url.hostname.toLowerCase().replace(/\.$/, '');
69
+ const labels = host.split('.');
70
+ for (let i = 0; i < labels.length - 1; i++) {
71
+ if (URL_SHORTENER_HOSTS.has(labels.slice(i).join('.')))
72
+ return `must not use a URL shortener (${host})`;
73
+ }
74
+ return null;
75
+ }
76
+ /** Curated icon / badge key: a file name under `deploy/plugins/_icons/`. */
77
+ export const ICON_KEY_PATTERN = /^[a-z0-9-]+$/;
78
+ // -----------------------------------------------------------------------------
79
+ // Field schemas
80
+ // -----------------------------------------------------------------------------
81
+ const utf8Bytes = (v) => new TextEncoder().encode(v).length;
82
+ /** `https:` project URL, no shorteners, no credentials. */
83
+ export const ProjectUrlSchema = z.string().superRefine((v, ctx) => {
84
+ const problem = projectUrlProblem(v);
85
+ if (problem)
86
+ ctx.addIssue({ code: 'custom', message: problem });
87
+ });
88
+ /** Curated icon / badge key. */
89
+ export const IconKeySchema = z.string().max(64).regex(ICON_KEY_PATTERN, 'must match ^[a-z0-9-]+$');
90
+ /**
91
+ * Icon as stored: `{ key, badge? }`. A bare key string is accepted and
92
+ * normalized first, so a bad key reports the key rule (not a bare union miss).
93
+ */
94
+ export const PluginIconSchema = z.preprocess((v) => (typeof v === 'string' ? { key: v } : v), z.object({ key: IconKeySchema, badge: IconKeySchema.optional() }).strict());
95
+ /** A non-empty single-line-ish text field with a character cap. */
96
+ const text = (max) => z.string().trim().min(1, 'must not be empty').max(max, `must be at most ${max} characters`);
97
+ /**
98
+ * Per-field validators for every DESCRIPTIVE field. A detected value
99
+ * and a user-typed value both go through these.
100
+ */
101
+ export const PLUGIN_CATALOG_FIELD_SCHEMAS = {
102
+ summary: text(PLUGIN_SUMMARY_MAX),
103
+ description: text(PLUGIN_DESCRIPTION_MAX),
104
+ displayName: text(PLUGIN_DISPLAY_NAME_MAX),
105
+ category: z.enum(PLUGIN_CATEGORIES, { message: `must be one of: ${PLUGIN_CATEGORIES.join(', ')}` }),
106
+ keywords: z.array(z.string().trim().min(1, 'keywords must not be empty').max(PLUGIN_KEYWORD_MAX_LENGTH, `each keyword must be at most ${PLUGIN_KEYWORD_MAX_LENGTH} characters`)).max(PLUGIN_KEYWORDS_MAX, `at most ${PLUGIN_KEYWORDS_MAX} keywords`),
107
+ license: z.string().refine(isAllowedSpdxId, { message: 'must be a supported SPDX license identifier (e.g. Apache-2.0, MIT)' }),
108
+ homepageUrl: ProjectUrlSchema,
109
+ sourceUrl: ProjectUrlSchema,
110
+ documentationUrl: ProjectUrlSchema,
111
+ icon: PluginIconSchema,
112
+ changelog: z.string().refine((v) => utf8Bytes(v) <= PLUGIN_CHANGELOG_MAX_BYTES, {
113
+ message: `must be at most ${PLUGIN_CHANGELOG_MAX_BYTES} bytes`,
114
+ }),
115
+ readme: z.string().refine((v) => utf8Bytes(v) <= PLUGIN_README_MAX_BYTES, {
116
+ message: `must be at most ${PLUGIN_README_MAX_BYTES} bytes`,
117
+ }),
118
+ };
119
+ /**
120
+ * Catalog EDITS (the upload's `metadata` part, a `PUT /plugins/:id` body's
121
+ * descriptive keys, the CLI's `--metadata` file). `null` clears a field.
122
+ * Strict: an unknown key is an error, never silently dropped.
123
+ */
124
+ export const PluginCatalogEditsSchema = z.object(Object.fromEntries(PLUGIN_CATALOG_FIELDS.map((f) => [f, PLUGIN_CATALOG_FIELD_SCHEMAS[f].nullable().optional()]))).strict();
125
+ /**
126
+ * Validate one field's value. Returns the normalized value (trimmed text, an
127
+ * icon string as `{ key }`) or the reason it was refused — so a detected value
128
+ * that fails is shown BLANK WITH THE REASON rather than silently dropped.
129
+ */
130
+ export function validateCatalogField(field, value) {
131
+ const result = PLUGIN_CATALOG_FIELD_SCHEMAS[field].safeParse(value);
132
+ if (result.success)
133
+ return { ok: true, value: result.data };
134
+ return { ok: false, error: result.error.issues.map((i) => i.message).join('; ') };
135
+ }
136
+ // -----------------------------------------------------------------------------
137
+ // Execution contract (never editable)
138
+ // -----------------------------------------------------------------------------
139
+ /**
140
+ * Keys that describe WHAT RUNS. They come only from the spec: changing one is a
141
+ * new version, a new digest and a review diff. A metadata edit
142
+ * naming any of them is refused with 400 — the refusal is the API's, not just
143
+ * the UI's. `name`/`version` key the pushed image and are immutable too.
144
+ */
145
+ export const PLUGIN_CONTRACT_FIELDS = [
146
+ 'name', 'version',
147
+ 'commands', 'installCommands', 'env', 'buildArgs', 'secrets',
148
+ 'metadata', 'requiredMetadata', 'requiredVars', 'metadataTypes', 'varsTypes',
149
+ 'network', 'networkEgress', 'computeType', 'pluginType', 'primaryOutputDirectory',
150
+ 'smokeTest', 'timeout', 'failureBehavior', 'dockerfile', 'buildType',
151
+ ];
152
+ /** The execution-contract keys present in `payload` (empty when none). */
153
+ export function findContractKeys(payload) {
154
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload))
155
+ return [];
156
+ return PLUGIN_CONTRACT_FIELDS.filter((k) => Object.prototype.hasOwnProperty.call(payload, k));
157
+ }
158
+ /** The message a contract-key refusal carries. */
159
+ export function contractKeysMessage(keys) {
160
+ return `These fields define what the plugin runs and can only change by uploading a new version: ${keys.join(', ')}`;
161
+ }
162
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLWNhdGFsb2ctbWV0YWRhdGEuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmFsaWRhdGlvbi9wbHVnaW4tY2F0YWxvZy1tZXRhZGF0YS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7OztHQWNHO0FBRUgsT0FBTyxFQUFFLENBQUMsRUFBRSxNQUFNLEtBQUssQ0FBQztBQUN4QixPQUFPLEVBQ0wscUJBQXFCLEVBQ3JCLGlCQUFpQixFQUNqQiwwQkFBMEIsRUFDMUIsc0JBQXNCLEVBQ3RCLHVCQUF1QixFQUN2Qix5QkFBeUIsRUFDekIsbUJBQW1CLEVBQ25CLHVCQUF1QixFQUN2QixrQkFBa0IsRUFDbEIsY0FBYyxHQUVmLE1BQU0sNEJBQTRCLENBQUM7QUFFcEM7Ozs7OztHQU1HO0FBQ0gsTUFBTSxDQUFDLE1BQU0sZ0JBQWdCLEdBQXdCLElBQUksR0FBRyxDQUFDO0lBQzNELFlBQVksRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLFNBQVMsRUFBRSxNQUFNLEVBQUUsU0FBUztJQUN0RixjQUFjLEVBQUUsY0FBYztJQUM5QixTQUFTLEVBQUUsU0FBUyxFQUFFLFNBQVM7SUFDL0IsZUFBZSxFQUFFLG1CQUFtQixFQUFFLGVBQWUsRUFBRSxtQkFBbUI7SUFDMUUsY0FBYyxFQUFFLGtCQUFrQixFQUFFLGNBQWMsRUFBRSxrQkFBa0I7SUFDdEUsZUFBZSxFQUFFLG1CQUFtQjtJQUNwQyxXQUFXLEVBQUUsY0FBYyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsY0FBYztJQUN2RSxVQUFVLEVBQUUsYUFBYSxFQUFFLFVBQVU7SUFDckMsd0JBQXdCO0NBQ3pCLENBQUMsQ0FBQztBQUVILHFEQUFxRDtBQUNyRCxNQUFNLFVBQVUsZUFBZSxDQUFDLEVBQVU7SUFDeEMsT0FBTyxnQkFBZ0IsQ0FBQyxHQUFH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bGluZSBCdWlsZGVyIENvbnRyaWJ1dG9yc1xuLy8gU1BEWC1MaWNlbnNlLUlkZW50aWZpZXI6IEFwYWNoZS0yLjBcblxuLyoqXG4gKiBQbHVnaW4gQ0FUQUxPRyBtZXRhZGF0YTogdGhlIGRlc2NyaXB0aXZlIGZpZWxkcyBhIHBsdWdpbiB2ZXJzaW9uIHNob3dzIGluIHRoZVxuICogY2F0YWxvZywgZGV0ZWN0ZWQgZnJvbSB0aGUgcGFja2FnZSBhbmQgdGhlbiBhY2NlcHRlZCBvciBlZGl0ZWQgYnkgdGhlIHVzZXJcbiAqIChkb2NzL3BsdWdpbi1wdWJsaXNoaW5nLm1kKS5cbiAqXG4gKiBPTkUgdmFsaWRhdG9yIGZvciBib3RoIHBhdGhzOiBhIHZhbHVlIGRldGVjdGVkIGZyb20gdGhlIHBhY2thZ2UgKHNwZWMsXG4gKiBSRUFETUUsIHRoZSBwbHVnaW4ncyBvd24gRG9ja2VyZmlsZSBsYWJlbHMpIGFuZCBhIHZhbHVlIHR5cGVkIGludG8gYSBmb3JtIG9yXG4gKiBzZW50IGJ5IHRoZSBDTEkgcGFzcyB0aGUgc2FtZSBydWxlcy4gU2hhcmVkIGZyb20gYXBpLWNvcmUgc28gdGhlIHBsdWdpblxuICogc2VydmljZSwgdGhlIGZyb250ZW5kIGFuZCB0aGUgQ0xJIGNhbid0IGRyaWZ0LlxuICpcbiAqIE9ubHkgREVTQ1JJUFRJVkUgZmllbGRzIGxpdmUgaGVyZS4gRXhlY3V0aW9uLWNvbnRyYWN0IGZpZWxkcyAoY29tbWFuZHMsIGVudixcbiAqIHNlY3JldHMsIGNvbXB1dGUgdHlwZSwg4oCmKSBhcmUgc3BlYy1vbmx5IOKAlCBjaGFuZ2luZyBvbmUgaXMgYSBuZXcgdmVyc2lvbiB3aXRoIGFcbiAqIG5ldyBkaWdlc3Qg4oCUIGFuZCBhbiBlZGl0IHBheWxvYWQgbmFtaW5nIG9uZSBpcyByZWZ1c2VkXG4gKiAoe0BsaW5rIGZpbmRDb250cmFjdEtleXN9KS5cbiAqL1xuXG5pbXBvcnQgeyB6IH0gZnJvbSAnem9kJztcbmltcG9ydCB7XG4gIFBMVUdJTl9DQVRBTE9HX0ZJRUxEUyxcbiAgUExVR0lOX0NBVEVHT1JJRVMsXG4gIFBMVUdJTl9DSEFOR0VMT0dfTUFYX0JZVEVTLFxuICBQTFVHSU5fREVTQ1JJUFRJT05fTUFYLFxuICBQTFVHSU5fRElTUExBWV9OQU1FX01BWCxcbiAgUExVR0lOX0tFWVdPUkRfTUFYX0xFTkdUSCxcbiAgUExVR0lOX0tFWVdPUkRTX01BWCxcbiAgUExVR0lOX1JFQURNRV9NQVhfQllURVMsXG4gIFBMVUdJTl9TVU1NQVJZX01BWCxcbiAgUExVR0lOX1VSTF9NQVgsXG4gIHR5cGUgUGx1Z2luQ2F0YWxvZ0ZpZWxkLFxufSBmcm9tICcuLi90eXBlcy9wbHVnaW4tY2F0YWxvZy5qcyc7XG5cbi8qKlxuICogQWNjZXB0ZWQgU1BEWCBsaWNlbnNlIGlkZW50aWZpZXJzIOKAlCBhIGNvbXBhY3QgYWxsb3dsaXN0IG9mIHRoZSBpZHMgdGhhdFxuICogY292ZXIgZWZmZWN0aXZlbHkgZXZlcnkgcHVibGlzaGVkIHBsdWdpbiwgcmF0aGVyIHRoYW4gdGhlIGZ1bGwgfjcwMC1lbnRyeVxuICogU1BEWCBsaXN0IChhbiB1bmZhbWlsaWFyIGlkIGlzIHdvcnRoIGEgaHVtYW4gbG9vaykuIENhc2Utc2Vuc2l0aXZlLCBleGFjdGx5XG4gKiBhcyBTUERYIHNwZWxscyB0aGVtLiBgTGljZW5zZVJlZi1Qcm9wcmlldGFyeWAgaXMgdGhlIFNQRFgtc2FuY3Rpb25lZFxuICogc3BlbGxpbmcgZm9yIFwibm90IG9wZW4gc291cmNlXCIuXG4gKi9cbmV4cG9ydCBjb25zdCBTUERYX0xJQ0VOU0VfSURTOiBSZWFkb25seVNldDxzdHJpbmc+ID0gbmV3IFNldChbXG4gICdBcGFjaGUtMi4wJywgJ01JVCcsICdNSVQtMCcsICdJU0MnLCAnMEJTRCcsICdVbmxpY2Vuc2UnLCAnQ0MwLTEuMCcsICdabGliJywgJ0JTTC0xLjAnLFxuICAnQlNELTItQ2xhdXNlJywgJ0JTRC0zLUNsYXVzZScsXG4gICdNUEwtMi4wJywgJ0VQTC0xLjAnLCAnRVBMLTIuMCcsXG4gICdMR1BMLTIuMS1vbmx5JywgJ0xHUEwtMi4xLW9yLWxhdGVyJywgJ0xHUEwtMy4wLW9ubHknLCAnTEdQTC0zLjAtb3ItbGF0ZXInLFxuICAnR1BMLTIuMC1vbmx5JywgJ0dQTC0yLjAtb3ItbGF0ZXInLCAnR1BMLTMuMC1vbmx5JywgJ0dQTC0zLjAtb3ItbGF0ZXInLFxuICAnQUdQTC0zLjAtb25seScsICdBR1BMLTMuMC1vci1sYXRlcicsXG4gICdDQy1CWS00LjAnLCAnQ0MtQlktU0EtNC4wJywgJ1B5dGhvbi0yLjAnLCAnUG9zdGdyZVNRTCcsICdBcnRpc3RpYy0yLjAnLFxuICAnQlVTTC0xLjEnLCAnRWxhc3RpYy0yLjAnLCAnU1NQTC0xLjAnLFxuICAnTGljZW5zZVJlZi1Qcm9wcmlldGFyeScsXG5dKTtcblxuLyoqIFRydWUgd2hlbiBgaWRgIGlzIGFuIGFjY2VwdGVkIFNQRFggaWRlbnRpZmllci4gKi9cbmV4cG9ydCBmdW5jdGlvbiBpc0FsbG93ZWRTcGR4SWQoaWQ6IHN0cmluZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gU1BEWF9MSUNFTlNFX0lEUy5oYXMoaWQpO1xufVxuXG4vKipcbiAqIFVSTCBzaG9ydGVuZXJzIHJlZnVzZWQgaW4gcHJvamVjdCBsaW5rczogYSBzaG9ydGVuZXIgaGlkZXMgdGhlIGRlc3RpbmF0aW9uXG4gKiBmcm9tIHRoZSByZXZpZXdlciBhbmQgY2FuIGJlIHJlLXBvaW50ZWQgYWZ0ZXIgYXBwcm92YWwuXG4gKi9cbmV4cG9ydCBjb25zdCBVUkxfU0hPUlRFTkVSX0hPU1RTOiBSZWFkb25seVNldDxzdHJpbmc+ID0gbmV3IFNldChbXG4gICdiaXQubHknLCAndC5jbycsICd0aW55dXJsLmNvbScsICdnb28uZ2wnLCAnb3cubHknLCAnaXMuZ2QnLCAnYnVmZi5seScsICdyZWJyYW5kLmx5JywgJ2N1dHQubHknLCAnc2hvcnR1cmwuYXQnLFxuXSk7XG5cbi8qKlxuICogV2h5IGByYXdgIGlzIG5vdCBhbiBhY2NlcHRhYmxlIHByb2plY3QgVVJMLCBvciBgbnVsbGAgd2hlbiBpdCBpczogbXVzdCBwYXJzZSxcbiAqIGJlIGBodHRwczpgLCBjYXJyeSBubyB1c2VyaW5mbywgYW5kIG5vdCBwb2ludCBhdCBhIGtub3duIHNob3J0ZW5lciAodGhlIGhvc3RcbiAqIG9yIGFueSBwYXJlbnQgZG9tYWluIG9mIGl0KS5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHByb2plY3RVcmxQcm9ibGVtKHJhdzogc3RyaW5nKTogc3RyaW5nIHwgbnVsbCB7XG4gIGlmIChyYXcubGVuZ3RoID4gUExVR0lOX1VSTF9NQVgpIHJldHVybiBgbXVzdCBiZSBhdCBtb3N0ICR7UExVR0lOX1VSTF9NQVh9IGNoYXJhY3RlcnNgO1xuICBsZXQgdXJsOiBVUkw7XG4gIHRyeSB7XG4gICAgdXJsID0gbmV3IFVSTChyYXcpO1xuICB9IGNhdGNoIHtcbiAgICByZXR1cm4gJ211c3QgYmUgYW4gYWJzb2x1dGUgVVJMJztcbiAgfVxuICBpZiAodXJsLnByb3RvY29sICE9PSAnaHR0cHM6JykgcmV0dXJuICdtdXN0IHVzZSBodHRwcyc7XG4gIGlmICh1cmwudXNlcm5hbWUgfHwgdXJsLnBhc3N3b3JkKSByZXR1cm4gJ211c3Qgbm90IGVtYmVkIGNyZWRlbnRpYWxzJztcbiAgY29uc3QgaG9zdCA9IHVybC5ob3N0bmFtZS50b0xvd2VyQ2FzZSgpLnJlcGxhY2UoL1xcLiQvLCAnJyk7XG4gIGNvbnN0IGxhYmVscyA9IGhvc3Quc3BsaXQoJy4nKTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBsYWJlbHMubGVuZ3RoIC0gMTsgaSsrKSB7XG4gICAgaWYgKFVSTF9TSE9SVEVORVJfSE9TVFMuaGFzKGxhYmVscy5zbGljZShpKS5qb2luKCcuJykpKSByZXR1cm4gYG11c3Qgbm90IHVzZSBhIFVSTCBzaG9ydGVuZXIgKCR7aG9zdH0pYDtcbiAgfVxuICByZXR1cm4gbnVsbDtcbn1cblxuLyoqIEN1cmF0ZWQgaWNvbiAvIGJhZGdlIGtleTogYSBmaWxlIG5hbWUgdW5kZXIgYGRlcGxveS9wbHVnaW5zL19pY29ucy9gLiAqL1xuZXhwb3J0IGNvbnN0IElDT05fS0VZX1BBVFRFUk4gPSAvXlthLXowLTktXSskLztcblxuLy8gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS1cbi8vIEZpZWxkIHNjaGVtYXNcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbmNvbnN0IHV0ZjhCeXRlcyA9ICh2OiBzdHJpbmcpOiBudW1iZXIgPT4gbmV3IFRleHRFbmNvZGVyKCkuZW5jb2RlKHYpLmxlbmd0aDtcblxuLyoqIGBodHRwczpgIHByb2plY3QgVVJMLCBubyBzaG9ydGVuZXJzLCBubyBjcmVkZW50aWFscy4gKi9cbmV4cG9ydCBjb25zdCBQcm9qZWN0VXJsU2NoZW1hID0gei5zdHJpbmcoKS5zdXBlclJlZmluZSgodiwgY3R4KSA9PiB7XG4gIGNvbnN0IHByb2JsZW0gPSBwcm9qZWN0VXJsUHJvYmxlbSh2KTtcbiAgaWYgKHByb2JsZW0pIGN0eC5hZGRJc3N1ZSh7IGNvZGU6ICdjdXN0b20nLCBtZXNzYWdlOiBwcm9ibGVtIH0pO1xufSk7XG5cbi8qKiBDdXJhdGVkIGljb24gLyBiYWRnZSBrZXkuICovXG5leHBvcnQgY29uc3QgSWNvbktleVNjaGVtYSA9IHouc3RyaW5nKCkubWF4KDY0KS5yZWdleChJQ09OX0tFWV9QQVRURVJOLCAnbXVzdCBtYXRjaCBeW2EtejAtOS1dKyQnKTtcblxuLyoqXG4gKiBJY29uIGFzIHN0b3JlZDogYHsga2V5LCBiYWRnZT8gfWAuIEEgYmFyZSBrZXkgc3RyaW5nIGlzIGFjY2VwdGVkIGFuZFxuICogbm9ybWFsaXplZCBmaXJzdCwgc28gYSBiYWQga2V5IHJlcG9ydHMgdGhlIGtleSBydWxlIChub3QgYSBiYXJlIHVuaW9uIG1pc3MpLlxuICovXG5leHBvcnQgY29uc3QgUGx1Z2luSWNvblNjaGVtYSA9IHoucHJlcHJvY2VzcyhcbiAgKHYpID0+ICh0eXBlb2YgdiA9PT0gJ3N0cmluZycgPyB7IGtleTogdiB9IDogdiksXG4gIHoub2JqZWN0KHsga2V5OiBJY29uS2V5U2NoZW1hLCBiYWRnZTogSWNvbktleVNjaGVtYS5vcHRpb25hbCgpIH0pLnN0cmljdCgpLFxuKTtcblxuLyoqIEEgbm9uLWVtcHR5IHNpbmdsZS1saW5lLWlzaCB0ZXh0IGZpZWxkIHdpdGggYSBjaGFyYWN0ZXIgY2FwLiAqL1xuY29uc3QgdGV4dCA9IChtYXg6IG51bWJlcikgPT4gei5zdHJpbmcoKS50cmltKCkubWluKDEsICdtdXN0IG5vdCBiZSBlbXB0eScpLm1heChtYXgsIGBtdXN0IGJlIGF0IG1vc3QgJHttYXh9IGNoYXJhY3RlcnNgKTtcblxuLyoqXG4gKiBQZXItZmllbGQgdmFsaWRhdG9ycyBmb3IgZXZlcnkgREVTQ1JJUFRJVkUgZmllbGQuIEEgZGV0ZWN0ZWQgdmFsdWVcbiAqIGFuZCBhIHVzZXItdHlwZWQgdmFsdWUgYm90aCBnbyB0aHJvdWdoIHRoZXNlLlxuICovXG5leHBvcnQgY29uc3QgUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BUyA9IHtcbiAgc3VtbWFyeTogdGV4dChQTFVHSU5fU1VNTUFSWV9NQVgpLFxuICBkZXNjcmlwdGlvbjogdGV4dChQTFVHSU5fREVTQ1JJUFRJT05fTUFYKSxcbiAgZGlzcGxheU5hbWU6IHRleHQoUExVR0lOX0RJU1BMQVlfTkFNRV9NQVgpLFxuICBjYXRlZ29yeTogei5lbnVtKFBMVUdJTl9DQVRFR09SSUVTLCB7IG1lc3NhZ2U6IGBtdXN0IGJlIG9uZSBvZjogJHtQTFVHSU5fQ0FURUdPUklFUy5qb2luKCcsICcpfWAgfSksXG4gIGtleXdvcmRzOiB6LmFycmF5KFxuICAgIHouc3RyaW5nKCkudHJpbSgpLm1pbigxLCAna2V5d29yZHMgbXVzdCBub3QgYmUgZW1wdHknKS5tYXgoUExVR0lOX0tFWVdPUkRfTUFYX0xFTkdUSCwgYGVhY2gga2V5d29yZCBtdXN0IGJlIGF0IG1vc3QgJHtQTFVHSU5fS0VZV09SRF9NQVhfTEVOR1RIfSBjaGFyYWN0ZXJzYCksXG4gICkubWF4KFBMVUdJTl9LRVlXT1JEU19NQVgsIGBhdCBtb3N0ICR7UExVR0lOX0tFWVdPUkRTX01BWH0ga2V5d29yZHNgKSxcbiAgbGljZW5zZTogei5zdHJpbmcoKS5yZWZpbmUoaXNBbGxvd2VkU3BkeElkLCB7IG1lc3NhZ2U6ICdtdXN0IGJlIGEgc3VwcG9ydGVkIFNQRFggbGljZW5zZSBpZGVudGlmaWVyIChlLmcuIEFwYWNoZS0yLjAsIE1JVCknIH0pLFxuICBob21lcGFnZVVybDogUHJvamVjdFVybFNjaGVtYSxcbiAgc291cmNlVXJsOiBQcm9qZWN0VXJsU2NoZW1hLFxuICBkb2N1bWVudGF0aW9uVXJsOiBQcm9qZWN0VXJsU2NoZW1hLFxuICBpY29uOiBQbHVnaW5JY29uU2NoZW1hLFxuICBjaGFuZ2Vsb2c6IHouc3RyaW5nKCkucmVmaW5lKCh2KSA9PiB1dGY4Qnl0ZXModikgPD0gUExVR0lOX0NIQU5HRUxPR19NQVhfQllURVMsIHtcbiAgICBtZXNzYWdlOiBgbXVzdCBiZSBhdCBtb3N0ICR7UExVR0lOX0NIQU5HRUxPR19NQVhfQllURVN9IGJ5dGVzYCxcbiAgfSksXG4gIHJlYWRtZTogei5zdHJpbmcoKS5yZWZpbmUoKHYpID0+IHV0ZjhCeXRlcyh2KSA8PSBQTFVHSU5fUkVBRE1FX01BWF9CWVRFUywge1xuICAgIG1lc3NhZ2U6IGBtdXN0IGJlIGF0IG1vc3QgJHtQTFVHSU5fUkVBRE1FX01BWF9CWVRFU30gYnl0ZXNgLFxuICB9KSxcbn0gYXMgY29uc3Qgc2F0aXNmaWVzIFJlY29yZDxQbHVnaW5DYXRhbG9nRmllbGQsIHouWm9kVHlwZT47XG5cbi8qKlxuICogQ2F0YWxvZyBFRElUUyAodGhlIHVwbG9hZCdzIGBtZXRhZGF0YWAgcGFydCwgYSBgUFVUIC9wbHVnaW5zLzppZGAgYm9keSdzXG4gKiBkZXNjcmlwdGl2ZSBrZXlzLCB0aGUgQ0xJJ3MgYC0tbWV0YWRhdGFgIGZpbGUpLiBgbnVsbGAgY2xlYXJzIGEgZmllbGQuXG4gKiBTdHJpY3Q6IGFuIHVua25vd24ga2V5IGlzIGFuIGVycm9yLCBuZXZlciBzaWxlbnRseSBkcm9wcGVkLlxuICovXG5leHBvcnQgY29uc3QgUGx1Z2luQ2F0YWxvZ0VkaXRzU2NoZW1hID0gei5vYmplY3QoXG4gIE9iamVjdC5mcm9tRW50cmllcyhQTFVHSU5fQ0FUQUxPR19GSUVMRFMubWFwKChmKSA9PiBbZiwgUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BU1tmXS5udWxsYWJsZSgpLm9wdGlvbmFsKCldKSkgYXMge1xuICAgIFtLIGluIFBsdWdpbkNhdGFsb2dGaWVsZF06IHouWm9kT3B0aW9uYWw8ei5ab2ROdWxsYWJsZTx0eXBlb2YgUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BU1tLXT4+O1xuICB9LFxuKS5zdHJpY3QoKTtcbmV4cG9ydCB0eXBlIFBsdWdpbkNhdGFsb2dFZGl0cyA9IHouaW5mZXI8dHlwZW9mIFBsdWdpbkNhdGFsb2dFZGl0c1NjaGVtYT47XG5cbi8qKlxuICogVmFsaWRhdGUgb25lIGZpZWxkJ3MgdmFsdWUuIFJldHVybnMgdGhlIG5vcm1hbGl6ZWQgdmFsdWUgKHRyaW1tZWQgdGV4dCwgYW5cbiAqIGljb24gc3RyaW5nIGFzIGB7IGtleSB9YCkgb3IgdGhlIHJlYXNvbiBpdCB3YXMgcmVmdXNlZCDigJQgc28gYSBkZXRlY3RlZCB2YWx1ZVxuICogdGhhdCBmYWlscyBpcyBzaG93biBCTEFOSyBXSVRIIFRIRSBSRUFTT04gcmF0aGVyIHRoYW4gc2lsZW50bHkgZHJvcHBlZC5cbiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHZhbGlkYXRlQ2F0YWxvZ0ZpZWxkPEYgZXh0ZW5kcyBQbHVnaW5DYXRhbG9nRmllbGQ+KFxuICBmaWVsZDogRixcbiAgdmFsdWU6IHVua25vd24sXG4pOiB7IG9rOiB0cnVlOyB2YWx1ZTogei5pbmZlcjx0eXBlb2YgUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BU1tGXT4gfSB8IHsgb2s6IGZhbHNlOyBlcnJvcjogc3RyaW5nIH0ge1xuICBjb25zdCByZXN1bHQgPSAoUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BU1tmaWVsZF0gYXMgei5ab2RUeXBlKS5zYWZlUGFyc2UodmFsdWUpO1xuICBpZiAocmVzdWx0LnN1Y2Nlc3MpIHJldHVybiB7IG9rOiB0cnVlLCB2YWx1ZTogcmVzdWx0LmRhdGEgYXMgei5pbmZlcjx0eXBlb2YgUExVR0lOX0NBVEFMT0dfRklFTERfU0NIRU1BU1tGXT4gfTtcbiAgcmV0dXJuIHsgb2s6IGZhbHNlLCBlcnJvcjogcmVzdWx0LmVycm9yLmlzc3Vlcy5tYXAoKGkpID0+IGkubWVzc2FnZSkuam9pbignOyAnKSB9O1xufVxuXG4vLyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLVxuLy8gRXhlY3V0aW9uIGNvbnRyYWN0IChuZXZlciBlZGl0YWJsZSlcbi8vIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tXG5cbi8qKlxuICogS2V5cyB0aGF0IGRlc2NyaWJlIFdIQVQgUlVOUy4gVGhleSBjb21lIG9ubHkgZnJvbSB0aGUgc3BlYzogY2hhbmdpbmcgb25lIGlzIGFcbiAqIG5ldyB2ZXJzaW9uLCBhIG5ldyBkaWdlc3QgYW5kIGEgcmV2aWV3IGRpZmYuIEEgbWV0YWRhdGEgZWRpdFxuICogbmFtaW5nIGFueSBvZiB0aGVtIGlzIHJlZnVzZWQgd2l0aCA0MDAg4oCUIHRoZSByZWZ1c2FsIGlzIHRoZSBBUEkncywgbm90IGp1c3RcbiAqIHRoZSBVSSdzLiBgbmFtZWAvYHZlcnNpb25gIGtleSB0aGUgcHVzaGVkIGltYWdlIGFuZCBhcmUgaW1tdXRhYmxlIHRvby5cbiAqL1xuZXhwb3J0IGNvbnN0IFBMVUdJTl9DT05UUkFDVF9GSUVMRFMgPSBbXG4gICduYW1lJywgJ3ZlcnNpb24nLFxuICAnY29tbWFuZHMnLCAnaW5zdGFsbENvbW1hbmRzJywgJ2VudicsICdidWlsZEFyZ3MnLCAnc2VjcmV0cycsXG4gICdtZXRhZGF0YScsICdyZXF1aXJlZE1ldGFkYXRhJywgJ3JlcXVpcmVkVmFycycsICdtZXRhZGF0YVR5cGVzJywgJ3ZhcnNUeXBlcycsXG4gICduZXR3b3JrJywgJ25ldHdvcmtFZ3Jlc3MnLCAnY29tcHV0ZVR5cGUnLCAncGx1Z2luVHlwZScsICdwcmltYXJ5T3V0cHV0RGlyZWN0b3J5JyxcbiAgJ3Ntb2tlVGVzdCcsICd0aW1lb3V0JywgJ2ZhaWx1cmVCZWhhdmlvcicsICdkb2NrZXJmaWxlJywgJ2J1aWxkVHlwZScsXG5dIGFzIGNvbnN0O1xuXG4vKiogVGhlIGV4ZWN1dGlvbi1jb250cmFjdCBrZXlzIHByZXNlbnQgaW4gYHBheWxvYWRgIChlbXB0eSB3aGVuIG5vbmUpLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIGZpbmRDb250cmFjdEtleXMocGF5bG9hZDogdW5rbm93bik6IHN0cmluZ1tdIHtcbiAgaWYgKCFwYXlsb2FkIHx8IHR5cGVvZiBwYXlsb2FkICE9PSAnb2JqZWN0JyB8fCBBcnJheS5pc0FycmF5KHBheWxvYWQpKSByZXR1cm4gW107XG4gIHJldHVybiBQTFVHSU5fQ09OVFJBQ1RfRklFTERTLmZpbHRlcigoaykgPT4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHBheWxvYWQsIGspKTtcbn1cblxuLyoqIFRoZSBtZXNzYWdlIGEgY29udHJhY3Qta2V5IHJlZnVzYWwgY2Fycmllcy4gKi9cbmV4cG9ydCBmdW5jdGlvbiBjb250cmFjdEtleXNNZXNzYWdlKGtleXM6IHJlYWRvbmx5IHN0cmluZ1tdKTogc3RyaW5nIHtcbiAgcmV0dXJuIGBUaGVzZSBmaWVsZHMgZGVmaW5lIHdoYXQgdGhlIHBsdWdpbiBydW5zIGFuZCBjYW4gb25seSBjaGFuZ2UgYnkgdXBsb2FkaW5nIGEgbmV3IHZlcnNpb246ICR7a2V5cy5qb2luKCcsICcpfWA7XG59XG4iXX0=
@@ -0,0 +1,41 @@
1
+ export type HeuristicSeverity = 'high' | 'medium';
2
+ export interface HeuristicFinding {
3
+ /** Rule id (stable; moderators filter on it). */
4
+ id: string;
5
+ severity: HeuristicSeverity;
6
+ /** The file, relative to the package root. */
7
+ path: string;
8
+ /** 1-based line number. */
9
+ line: number;
10
+ /** The offending line, trimmed and capped, with secret values masked. */
11
+ excerpt: string;
12
+ /** What the rule means. */
13
+ message: string;
14
+ }
15
+ export interface HeuristicsReport {
16
+ findings: HeuristicFinding[];
17
+ /** Files scanned. */
18
+ scannedFiles: number;
19
+ /** Files skipped (binary, over the size cap, or past the total budget). */
20
+ skippedFiles: string[];
21
+ }
22
+ export interface HeuristicsInputFile {
23
+ path: string;
24
+ content: string | Uint8Array;
25
+ }
26
+ /** A single file larger than this is not scanned (listed in `skippedFiles`). */
27
+ export declare const HEURISTICS_MAX_FILE_BYTES: number;
28
+ /** Total bytes scanned per package. */
29
+ export declare const HEURISTICS_MAX_TOTAL_BYTES: number;
30
+ /** Findings kept per package. */
31
+ export declare const HEURISTICS_MAX_FINDINGS = 200;
32
+ /** Shannon entropy of `s`, in bits per character. */
33
+ export declare function shannonEntropy(s: string): number;
34
+ /**
35
+ * Scan a plugin package's files. `files` paths are package-relative; a
36
+ * Dockerfile is recognised by name (`Dockerfile`, `*.Dockerfile`,
37
+ * `Dockerfile.*`). Deterministic: findings come back in file then line order.
38
+ */
39
+ export declare function scanPluginSourceHeuristics(files: readonly HeuristicsInputFile[]): HeuristicsReport;
40
+ /** The findings that fail a gate (`high`). */
41
+ export declare function blockingHeuristics(report: Pick<HeuristicsReport, 'findings'>): HeuristicFinding[];
@@ -0,0 +1,211 @@
1
+ // Copyright 2026 Pipeline Builder Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Malware heuristics over a plugin PACKAGE's source (docs/plugin-publishing.md,
5
+ * the automated submission gate): the Dockerfile, the spec and every other text file in the
6
+ * zip. A plugin runs inside a customer's CodeBuild with the customer's keys, so
7
+ * the patterns looked for are the ones that abuse exactly that:
8
+ *
9
+ * - crypto-miner signatures (xmrig, `stratum+tcp://`, cryptonight, …);
10
+ * - obfuscated execution (a base64/hex blob decoded into a shell or `eval`,
11
+ * `eval "$(… | base64 -d)"`, long single-line encoded payloads);
12
+ * - credential access (`AWS_SECRET_ACCESS_KEY`, `AWS_SESSION_TOKEN`, the ECS /
13
+ * CodeBuild container-credential endpoints and variables, IMDS
14
+ * `169.254.169.254` / `fd00:ec2::254`, `~/.aws/credentials`,
15
+ * `/var/run/secrets`);
16
+ * - reverse shells and unchecked pipe-to-shell downloads;
17
+ * - secret-looking literals (`AKIA…`, `ghp_…`, `xoxb-…`, PEM private keys, a
18
+ * high-entropy value assigned to a `*_TOKEN` / `*_SECRET` / `*_PASSWORD` /
19
+ * `*_KEY` name).
20
+ *
21
+ * `high` findings fail the anonymous-submission gate; `medium` ones are shown
22
+ * to moderators only. The CLI (`plugin validate` / `plugin publish`) runs the
23
+ * same scan so an author sees it before uploading. Pure and bounded: files and
24
+ * lines are size-capped, binary files are skipped, and findings are capped.
25
+ */
26
+ import { pipeInstallers } from './plugin-lint.js';
27
+ /** A single file larger than this is not scanned (listed in `skippedFiles`). */
28
+ export const HEURISTICS_MAX_FILE_BYTES = 512 * 1024;
29
+ /** Total bytes scanned per package. */
30
+ export const HEURISTICS_MAX_TOTAL_BYTES = 16 * 1024 * 1024;
31
+ /** Findings kept per package. */
32
+ export const HEURISTICS_MAX_FINDINGS = 200;
33
+ const EXCERPT_MAX = 160;
34
+ /** A line longer than this is scanned only for the encoded-blob rule (and truncated for the rest). */
35
+ const LINE_SCAN_MAX = 8 * 1024;
36
+ const SHELL = String.raw `(?:sudo\s+)?(?:\/usr)?(?:\/bin\/)?(?:ba|da|z|k)?sh\b`;
37
+ const LINE_RULES = [
38
+ {
39
+ id: 'miner-signature',
40
+ severity: 'high',
41
+ message: 'Crypto-miner signature',
42
+ test: /\bxmrig\b|stratum\+(?:tcp|ssl|tls):\/\/|\bcryptonight\b|\bminerd\b|\bnicehash\b|\bcpuminer\b|\bxmr-stak\b|\bethminer\b|\bnbminer\b|\bphoenixminer\b|\blolminer\b|\bsupportxmr\b|\bminexmr\b|\bmoneroocean\b/i,
43
+ },
44
+ {
45
+ id: 'obfuscated-exec',
46
+ severity: 'high',
47
+ message: 'Encoded payload decoded straight into a shell or eval',
48
+ test: new RegExp(String.raw `base64\s+(?:-d|-D|--decode)\b[^\n]*\|\s*${SHELL}|\beval\b[^\n]*\$\([^\n]*base64\s+(?:-d|-D|--decode)|\bxxd\s+-r\b[^\n]*\|\s*${SHELL}|(?:\\x[0-9a-fA-F]{2}){8,}[^\n]*\|\s*${SHELL}|\bexec\s*\(\s*(?:base64\.b64decode|__import__\(\s*['"](?:base64|zlib)['"])|\beval\s*\(\s*(?:atob|Buffer\.from)\s*\(`, 'i'),
49
+ },
50
+ {
51
+ id: 'reverse-shell',
52
+ severity: 'high',
53
+ message: 'Reverse-shell pattern',
54
+ test: /\/dev\/tcp\/\d|\bnc(?:at)?\b[^\n]*\s-(?:e|c)\s+\S*sh\b|\bncat\b[^\n]*--(?:exec|sh-exec)\b|\bsocat\b[^\n]*\bexec:/i,
55
+ },
56
+ {
57
+ id: 'credential-access',
58
+ severity: 'high',
59
+ docSeverity: 'medium',
60
+ codeOnly: true,
61
+ message: 'Reads cloud or build credentials',
62
+ test: /\bAWS_SECRET_ACCESS_KEY\b|\bAWS_SESSION_TOKEN\b|\bAWS_CONTAINER_CREDENTIALS_(?:RELATIVE|FULL)_URI\b|\bAWS_CONTAINER_AUTHORIZATION_TOKEN(?:_FILE)?\b|\bCODEBUILD_[A-Z0-9_]*(?:TOKEN|CREDENTIAL|SECRET|AUTH)[A-Z0-9_]*\b|169\.254\.169\.254|169\.254\.170\.2|fd00:ec2::254|\.aws\/credentials|\/var\/run\/secrets\b/,
63
+ },
64
+ {
65
+ id: 'pipe-to-shell',
66
+ severity: 'high',
67
+ docSeverity: null,
68
+ codeOnly: true,
69
+ message: 'Download piped straight into a shell',
70
+ test: new RegExp(String.raw `\b(?:curl|wget)\b[^\n|]*\|\s*${SHELL}`, 'i'),
71
+ },
72
+ {
73
+ id: 'secret-literal',
74
+ severity: 'high',
75
+ message: 'Secret-looking literal',
76
+ secret: true,
77
+ test: /\b(?:AKIA|ASIA)[0-9A-Z]{16}\b|\bgh[pousr]_[A-Za-z0-9]{36,}\b|\bgithub_pat_[A-Za-z0-9_]{40,}\b|\bxox[bap]-[A-Za-z0-9-]{10,}|-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP |ENCRYPTED )?PRIVATE KEY-----|\bglpat-[A-Za-z0-9_-]{20,}\b|\bsk_live_[A-Za-z0-9]{16,}\b/,
78
+ },
79
+ ];
80
+ /** `NAME=value` / `NAME: value` / `ENV NAME value` assignments to a secret-looking name. */
81
+ const SECRET_ASSIGNMENT = /\b([A-Za-z0-9_]*(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY|ACCESS_KEY|_KEY))\b["']?\s*(?:[:=]|\s)\s*["']?([^\s"'`,;)]{16,})/i;
82
+ /** A long single-token base64 or hex run (an embedded payload). */
83
+ const ENCODED_BLOB = /[A-Za-z0-9+/]{240,}={0,2}|\b[0-9a-fA-F]{240,}\b/;
84
+ /** Shannon entropy of `s`, in bits per character. */
85
+ export function shannonEntropy(s) {
86
+ if (!s)
87
+ return 0;
88
+ const counts = new Map();
89
+ for (const ch of s)
90
+ counts.set(ch, (counts.get(ch) ?? 0) + 1);
91
+ let h = 0;
92
+ for (const n of counts.values()) {
93
+ const p = n / s.length;
94
+ h -= p * Math.log2(p);
95
+ }
96
+ return h;
97
+ }
98
+ /** Whether a secret-assignment value looks like a real credential (not a placeholder or a reference). */
99
+ function looksLikeSecretValue(value) {
100
+ if (/[$]|\{\{|<|>|\*{3,}|x{6,}|changeme|example|placeholder|your[-_]/i.test(value))
101
+ return false;
102
+ return shannonEntropy(value) >= 3.5;
103
+ }
104
+ function isDocFile(path) {
105
+ return /(^|\/)readme(\.[a-z]+)?$/i.test(path) || /\.(md|markdown|rst|txt|adoc)$/i.test(path);
106
+ }
107
+ function mask(line) {
108
+ return line
109
+ .replace(/\b((?:AKIA|ASIA)[0-9A-Z]{4})[0-9A-Z]{12}\b/g, '$1…')
110
+ .replace(/\b(gh[pousr]_|github_pat_|xox[bap]-|glpat-|sk_live_)[A-Za-z0-9_-]+/g, '$1…')
111
+ .replace(SECRET_ASSIGNMENT, (m, _name, value) => m.replace(value, `${value.slice(0, 4)}…`));
112
+ }
113
+ function excerptOf(line, secret) {
114
+ const t = (secret ? mask(line) : line).trim();
115
+ return t.length > EXCERPT_MAX ? `${t.slice(0, EXCERPT_MAX - 1)}…` : t;
116
+ }
117
+ /** Whether a decoded buffer looks binary (a NUL in its first 8 KiB). */
118
+ function isBinary(bytes) {
119
+ const n = Math.min(bytes.length, 8192);
120
+ for (let i = 0; i < n; i++)
121
+ if (bytes[i] === 0)
122
+ return true;
123
+ return false;
124
+ }
125
+ function asText(content) {
126
+ if (typeof content === 'string')
127
+ return { text: content.includes('\u0000') ? null : content, bytes: Buffer.byteLength(content) };
128
+ if (isBinary(content))
129
+ return { text: null, bytes: content.length };
130
+ return { text: Buffer.from(content).toString('utf8'), bytes: content.length };
131
+ }
132
+ /**
133
+ * Scan a plugin package's files. `files` paths are package-relative; a
134
+ * Dockerfile is recognised by name (`Dockerfile`, `*.Dockerfile`,
135
+ * `Dockerfile.*`). Deterministic: findings come back in file then line order.
136
+ */
137
+ export function scanPluginSourceHeuristics(files) {
138
+ const findings = [];
139
+ const skippedFiles = [];
140
+ const seen = new Set();
141
+ let scannedFiles = 0;
142
+ let budget = HEURISTICS_MAX_TOTAL_BYTES;
143
+ const add = (f) => {
144
+ const key = `${f.id}|${f.path}|${f.line}`;
145
+ if (seen.has(key) || findings.length >= HEURISTICS_MAX_FINDINGS)
146
+ return;
147
+ seen.add(key);
148
+ findings.push(f);
149
+ };
150
+ for (const file of files) {
151
+ const { text, bytes } = asText(file.content);
152
+ if (text === null || bytes > HEURISTICS_MAX_FILE_BYTES || bytes > budget) {
153
+ skippedFiles.push(file.path);
154
+ continue;
155
+ }
156
+ budget -= bytes;
157
+ scannedFiles++;
158
+ const doc = isDocFile(file.path);
159
+ const lines = text.replace(/\r\n?/g, '\n').split('\n');
160
+ lines.forEach((raw, idx) => {
161
+ const line = idx + 1;
162
+ if (ENCODED_BLOB.test(raw)) {
163
+ add({ id: 'encoded-blob', severity: 'medium', path: file.path, line, excerpt: excerptOf(raw, false), message: 'Long single-line encoded payload' });
164
+ }
165
+ const scan = raw.length > LINE_SCAN_MAX ? raw.slice(0, LINE_SCAN_MAX) : raw;
166
+ const comment = /^\s*(#|\/\/)/.test(scan);
167
+ for (const rule of LINE_RULES) {
168
+ if (rule.codeOnly && comment)
169
+ continue;
170
+ if (!rule.test.test(scan))
171
+ continue;
172
+ const severity = doc && rule.docSeverity !== undefined ? rule.docSeverity : rule.severity;
173
+ if (severity === null)
174
+ continue;
175
+ add({ id: rule.id, severity, path: file.path, line, excerpt: excerptOf(scan, rule.secret === true), message: rule.message });
176
+ }
177
+ const assignment = SECRET_ASSIGNMENT.exec(scan);
178
+ if (assignment && looksLikeSecretValue(assignment[2])) {
179
+ add({
180
+ id: 'secret-default',
181
+ severity: doc ? 'medium' : 'high',
182
+ path: file.path,
183
+ line,
184
+ excerpt: excerptOf(scan, true),
185
+ message: `High-entropy value assigned to ${assignment[1]}`,
186
+ });
187
+ }
188
+ });
189
+ // Dockerfile RUN lines that pipe a download into a shell, continuation-joined
190
+ // (the line rule above only sees one physical line).
191
+ if (/(^|\/)(Dockerfile(\.[^/]+)?|[^/]+\.Dockerfile)$/i.test(file.path)) {
192
+ for (const instruction of pipeInstallers(text)) {
193
+ const firstLine = lines.findIndex((l) => l.trim() !== '' && instruction.startsWith(l.trim().replace(/\\\s*$/, '').trim()));
194
+ add({
195
+ id: 'pipe-to-shell',
196
+ severity: 'high',
197
+ path: file.path,
198
+ line: firstLine >= 0 ? firstLine + 1 : 1,
199
+ excerpt: excerptOf(instruction, false),
200
+ message: 'Download piped straight into a shell',
201
+ });
202
+ }
203
+ }
204
+ }
205
+ return { findings, scannedFiles, skippedFiles };
206
+ }
207
+ /** The findings that fail a gate (`high`). */
208
+ export function blockingHeuristics(report) {
209
+ return report.findings.filter((f) => f.severity === 'high');
210
+ }
211
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicGx1Z2luLWhldXJpc3RpY3MuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi9zcmMvdmFsaWRhdGlvbi9wbHVnaW4taGV1cmlzdGljcy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSwrQ0FBK0M7QUFDL0Msc0NBQXNDO0FBRXRDOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBc0JHO0FBRUgsT0FBTyxFQUFFLGNBQWMsRUFBRSxNQUFNLGtCQUFrQixDQUFDO0FBK0JsRCxnRkFBZ0Y7QUFDaEYsTUFBTSxDQUFDLE1BQU0seUJBQXlCLEdBQUcsR0FBRyxHQUFHLElBQUksQ0FBQztBQUNwRCx1Q0FBdUM7QUFDdkMsTUFBTSxDQUFDLE1BQU0sMEJBQTBCLEdBQUcsRUFBRSxHQUFHLElBQUksR0FBRyxJQUFJLENBQUM7QUFDM0QsaUNBQWlDO0FBQ2pDLE1BQU0sQ0FBQyxNQUFNLHVCQUF1QixHQUFHLEdBQUcsQ0FBQztBQUMzQyxNQUFNLFdBQVcsR0FBRyxHQUFHLENBQUM7QUFDeEIsc0dBQXNHO0FBQ3RHLE1BQU0sYUFBYSxHQUFHLENBQUMsR0FBRyxJQUFJLENBQUM7QUFlL0IsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLEdBQUcsQ0FBQSxzREFBc0QsQ0FBQztBQUUvRSxNQUFNLFVBQVUsR0FBd0I7SUFDdEM7UUFDRSxFQUFFLEVBQUUsaUJBQWlCO1FBQ3JCLFFBQVEsRUFBRSxNQUFNO1FBQ2hCLE9BQU8sRUFBRSx3QkFBd0I7UUFDakMsSUFBSSxFQUFFLDhNQUE4TTtLQUNyTjtJQUNEO1FBQ0UsRUFBRSxFQUFFLGlCQUFpQjtRQUNyQixRQUFRLEVBQUUsTUFBTTtRQUNoQixPQUFPLEVBQUUsdURBQXVEO1FBQ2hFLElBQUksRUFBRSxJQUFJLE1BQU0sQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFBLDJDQUEyQyxLQUFLLCtFQUErRSxLQUFLLHdDQUF3QyxLQUFLLHNIQUFzSCxFQUFFLEdBQUcsQ0FBQztLQUN6VTtJQUNEO1FBQ0UsRUFBRSxF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SUFBSSxFQUFFLEtBQUssRUFBRSxJQUFJLFdBQVcsQ0FBQyxVQUFVLENBQUMsQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDLE9BQU8sQ0FBQyxRQUFRLEVBQUUsRUFBRSxDQUFDLENBQUMsSUFBSSxFQUFFLENBQUMsQ0FBQyxDQUFDO2dCQUMzSCxHQUFHLENBQUM7b0JBQ0YsRUFBRSxFQUFFLGVBQWU7b0JBQ25CLFFBQVEsRUFBRSxNQUFNO29CQUNoQixJQUFJLEVBQUUsSUFBSSxDQUFDLElBQUk7b0JBQ2YsSUFBSSxFQUFFLFNBQVMsSUFBSSxDQUFDLENBQUMsQ0FBQyxDQUFDLFNBQVMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7b0JBQ3hDLE9BQU8sRUFBRSxTQUFTLENBQUMsV0FBVyxFQUFFLEtBQUssQ0FBQztvQkFDdEMsT0FBTyxFQUFFLHNDQUFzQztpQkFDaEQsQ0FBQyxDQUFDO1lBQ0wsQ0FBQztRQUNILENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxFQUFFLFFBQVEsRUFBRSxZQUFZLEVBQUUsWUFBWSxFQUFFLENBQUM7QUFDbEQsQ0FBQztBQUVELDhDQUE4QztBQUM5QyxNQUFNLFVBQVUsa0JBQWtCLENBQUMsTUFBMEM7SUFDM0UsT0FBTyxNQUFNLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDLENBQUMsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLFFBQVEsS0FBSyxNQUFNLENBQUMsQ0FBQztBQUM5RCxDQUFDIiwic291cmNlc0NvbnRlbnQiOlsiLy8gQ29weXJpZ2h0IDIwMjYgUGlwZWxpbmUgQnVpbGRlciBDb250cmlidXRvcnNcbi8vIFNQRFgtTGljZW5zZS1JZGVudGlmaWVyOiBBcGFjaGUtMi4wXG5cbi8qKlxuICogTWFsd2FyZSBoZXVyaXN0aWNzIG92ZXIgYSBwbHVnaW4gUEFDS0FHRSdzIHNvdXJjZSAoZG9jcy9wbHVnaW4tcHVibGlzaGluZy5tZCxcbiAqIHRoZSBhdXRvbWF0ZWQgc3VibWlzc2lvbiBnYXRlKTogdGhlIERvY2tlcmZpbGUsIHRoZSBzcGVjIGFuZCBldmVyeSBvdGhlciB0ZXh0IGZpbGUgaW4gdGhlXG4gKiB6aXAuIEEgcGx1Z2luIHJ1bnMgaW5zaWRlIGEgY3VzdG9tZXIncyBDb2RlQnVpbGQgd2l0aCB0aGUgY3VzdG9tZXIncyBrZXlzLCBzb1xuICogdGhlIHBhdHRlcm5zIGxvb2tlZCBmb3IgYXJlIHRoZSBvbmVzIHRoYXQgYWJ1c2UgZXhhY3RseSB0aGF0OlxuICpcbiAqICAtIGNyeXB0by1taW5lciBzaWduYXR1cmVzICh4bXJpZywgYHN0cmF0dW0rdGNwOi8vYCwgY3J5cHRvbmlnaHQsIOKApik7XG4gKiAgLSBvYmZ1c2NhdGVkIGV4ZWN1dGlvbiAoYSBiYXNlNjQvaGV4IGJsb2IgZGVjb2RlZCBpbnRvIGEgc2hlbGwgb3IgYGV2YWxgLFxuICogICAgYGV2YWwgXCIkKOKApiB8IGJhc2U2NCAtZClcImAsIGxvbmcgc2luZ2xlLWxpbmUgZW5jb2RlZCBwYXlsb2Fkcyk7XG4gKiAgLSBjcmVkZW50aWFsIGFjY2VzcyAoYEFXU19TRUNSRVRfQUNDRVNTX0tFWWAsIGBBV1NfU0VTU0lPTl9UT0tFTmAsIHRoZSBFQ1MgL1xuICogICAgQ29kZUJ1aWxkIGNvbnRhaW5lci1jcmVkZW50aWFsIGVuZHBvaW50cyBhbmQgdmFyaWFibGVzLCBJTURTXG4gKiAgICBgMTY5LjI1NC4xNjkuMjU0YCAvIGBmZDAwOmVjMjo6MjU0YCwgYH4vLmF3cy9jcmVkZW50aWFsc2AsXG4gKiAgICBgL3Zhci9ydW4vc2VjcmV0c2ApO1xuICogIC0gcmV2ZXJzZSBzaGVsbHMgYW5kIHVuY2hlY2tlZCBwaXBlLXRvLXNoZWxsIGRvd25sb2FkcztcbiAqICAtIHNlY3JldC1sb29raW5nIGxpdGVyYWxzIChgQUtJQeKApmAsIGBnaHBf4oCmYCwgYHhveGIt4oCmYCwgUEVNIHByaXZhdGUga2V5cywgYVxuICogICAgaGlnaC1lbnRyb3B5IHZhbHVlIGFzc2lnbmVkIHRvIGEgYCpfVE9LRU5gIC8gYCpfU0VDUkVUYCAvIGAqX1BBU1NXT1JEYCAvXG4gKiAgICBgKl9LRVlgIG5hbWUpLlxuICpcbiAqIGBoaWdoYCBmaW5kaW5ncyBmYWlsIHRoZSBhbm9ueW1vdXMtc3VibWlzc2lvbiBnYXRlOyBgbWVkaXVtYCBvbmVzIGFyZSBzaG93blxuICogdG8gbW9kZXJhdG9ycyBvbmx5LiBUaGUgQ0xJIChgcGx1Z2luIHZhbGlkYXRlYCAvIGBwbHVnaW4gcHVibGlzaGApIHJ1bnMgdGhlXG4gKiBzYW1lIHNjYW4gc28gYW4gYXV0aG9yIHNlZXMgaXQgYmVmb3JlIHVwbG9hZGluZy4gUHVyZSBhbmQgYm91bmRlZDogZmlsZXMgYW5kXG4gKiBsaW5lcyBhcmUgc2l6ZS1jYXBwZWQsIGJpbmFyeSBmaWxlcyBhcmUgc2tpcHBlZCwgYW5kIGZpbmRpbmdzIGFyZSBjYXBwZWQuXG4gKi9cblxuaW1wb3J0IHsgcGlwZUluc3RhbGxlcnMgfSBmcm9tICcuL3BsdWdpbi1saW50LmpzJztcblxuZXhwb3J0IHR5cGUgSGV1cmlzdGljU2V2ZXJpdHkgPSAnaGlnaCcgfCAnbWVkaXVtJztcblxuZXhwb3J0IGludGVyZmFjZSBIZXVyaXN0aWNGaW5kaW5nIHtcbiAgLyoqIFJ1bGUgaWQgKHN0YWJsZTsgbW9kZXJhdG9ycyBmaWx0ZXIgb24gaXQpLiAqL1xuICBpZDogc3RyaW5nO1xuICBzZXZlcml0eTogSGV1cmlzdGljU2V2ZXJpdHk7XG4gIC8qKiBUaGUgZmlsZSwgcmVsYXRpdmUgdG8gdGhlIHBhY2thZ2Ugcm9vdC4gKi9cbiAgcGF0aDogc3RyaW5nO1xuICAvKiogMS1iYXNlZCBsaW5lIG51bWJlci4gKi9cbiAgbGluZTogbnVtYmVyO1xuICAvKiogVGhlIG9mZmVuZGluZyBsaW5lLCB0cmltbWVkIGFuZCBjYXBwZWQsIHdpdGggc2VjcmV0IHZhbHVlcyBtYXNrZWQuICovXG4gIGV4Y2VycHQ6IHN0cmluZztcbiAgLyoqIFdoYXQgdGhlIHJ1bGUgbWVhbnMuICovXG4gIG1lc3NhZ2U6IHN0cmluZztcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIZXVyaXN0aWNzUmVwb3J0IHtcbiAgZmluZGluZ3M6IEhldXJpc3RpY0ZpbmRpbmdbXTtcbiAgLyoqIEZpbGVzIHNjYW5uZWQuICovXG4gIHNjYW5uZWRGaWxlczogbnVtYmVyO1xuICAvKiogRmlsZXMgc2tpcHBlZCAoYmluYXJ5LCBvdmVyIHRoZSBzaXplIGNhcCwgb3IgcGFzdCB0aGUgdG90YWwgYnVkZ2V0KS4gKi9cbiAgc2tpcHBlZEZpbGVzOiBzdHJpbmdbXTtcbn1cblxuZXhwb3J0IGludGVyZmFjZSBIZXVyaXN0aWNzSW5wdXRGaWxlIHtcbiAgcGF0aDogc3RyaW5nO1xuICBjb250ZW50OiBzdHJpbmcgfCBVaW50OEFycmF5O1xufVxuXG4vKiogQSBzaW5nbGUgZmlsZSBsYXJnZXIgdGhhbiB0aGlzIGlzIG5vdCBzY2FubmVkIChsaXN0ZWQgaW4gYHNraXBwZWRGaWxlc2ApLiAqL1xuZXhwb3J0IGNvbnN0IEhFVVJJU1RJQ1NfTUFYX0ZJTEVfQllURVMgPSA1MTIgKiAxMDI0O1xuLyoqIFRvdGFsIGJ5dGVzIHNjYW5uZWQgcGVyIHBhY2thZ2UuICovXG5leHBvcnQgY29uc3QgSEVVUklTVElDU19NQVhfVE9UQUxfQllURVMgPSAxNiAqIDEwMjQgKiAxMDI0O1xuLyoqIEZpbmRpbmdzIGtlcHQgcGVyIHBhY2thZ2UuICovXG5leHBvcnQgY29uc3QgSEVVUklTVElDU19NQVhfRklORElOR1MgPSAyMDA7XG5jb25zdCBFWENFUlBUX01BWCA9IDE2MDtcbi8qKiBBIGxpbmUgbG9uZ2VyIHRoYW4gdGhpcyBpcyBzY2FubmVkIG9ubHkgZm9yIHRoZSBlbmNvZGVkLWJsb2IgcnVsZSAoYW5kIHRydW5jYXRlZCBmb3IgdGhlIHJlc3QpLiAqL1xuY29uc3QgTElORV9TQ0FOX01BWCA9IDggKiAxMDI0O1xuXG5pbnRlcmZhY2UgTGluZVJ1bGUge1xuICBpZDogc3RyaW5nO1xuICBzZXZlcml0eTogSGV1cmlzdGljU2V2ZXJpdHk7XG4gIG1lc3NhZ2U6IHN0cmluZztcbiAgdGVzdDogUmVnRXhwO1xuICAvKiogU2V2ZXJpdHkgaW4gZG9jdW1lbnRhdGlvbiBmaWxlcyAoUkVBRE1FLCAqLm1kKSwgd2hlcmUgbWVudGlvbmluZyBhIG5hbWUgaXNuJ3QgdXNpbmcgaXQuICovXG4gIGRvY1NldmVyaXR5PzogSGV1cmlzdGljU2V2ZXJpdHkgfCBudWxsO1xuICAvKiogTWFzayBzZWNyZXQtbG9va2luZyB2YWx1ZXMgaW4gdGhlIGV4Y2VycHQuICovXG4gIHNlY3JldD86IGJvb2xlYW47XG4gIC8qKiBPbmx5IG9uIGNvZGUgbGluZXM6IGEgYCNgIC8gYC8vYCBjb21tZW50IHRoYXQgTUVOVElPTlMgdGhlIHBhdHRlcm4gaXNuJ3QgdXNpbmcgaXQuICovXG4gIGNvZGVPbmx5PzogYm9vbGVhbjtcbn1cblxuY29uc3QgU0hFTEwgPSBTdHJpbmcucmF3YCg/OnN1ZG9cXHMrKT8oPzpcXC91c3IpPyg/OlxcL2JpblxcLyk/KD86YmF8ZGF8enxrKT9zaFxcYmA7XG5cbmNvbnN0IExJTkVfUlVMRVM6IHJlYWRvbmx5IExpbmVSdWxlW10gPSBbXG4gIHtcbiAgICBpZDogJ21pbmVyLXNpZ25hdHVyZScsXG4gICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICBtZXNzYWdlOiAnQ3J5cHRvLW1pbmVyIHNpZ25hdHVyZScsXG4gICAgdGVzdDogL1xcYnhtcmlnXFxifHN0cmF0dW1cXCsoPzp0Y3B8c3NsfHRscyk6XFwvXFwvfFxcYmNyeXB0b25pZ2h0XFxifFxcYm1pbmVyZFxcYnxcXGJuaWNlaGFzaFxcYnxcXGJjcHVtaW5lclxcYnxcXGJ4bXItc3Rha1xcYnxcXGJldGhtaW5lclxcYnxcXGJuYm1pbmVyXFxifFxcYnBob2VuaXhtaW5lclxcYnxcXGJsb2xtaW5lclxcYnxcXGJzdXBwb3J0eG1yXFxifFxcYm1pbmV4bXJcXGJ8XFxibW9uZXJvb2NlYW5cXGIvaSxcbiAgfSxcbiAge1xuICAgIGlkOiAnb2JmdXNjYXRlZC1leGVjJyxcbiAgICBzZXZlcml0eTogJ2hpZ2gnLFxuICAgIG1lc3NhZ2U6ICdFbmNvZGVkIHBheWxvYWQgZGVjb2RlZCBzdHJhaWdodCBpbnRvIGEgc2hlbGwgb3IgZXZhbCcsXG4gICAgdGVzdDogbmV3IFJlZ0V4cChTdHJpbmcucmF3YGJhc2U2NFxccysoPzotZHwtRHwtLWRlY29kZSlcXGJbXlxcbl0qXFx8XFxzKiR7U0hFTEx9fFxcYmV2YWxcXGJbXlxcbl0qXFwkXFwoW15cXG5dKmJhc2U2NFxccysoPzotZHwtRHwtLWRlY29kZSl8XFxieHhkXFxzKy1yXFxiW15cXG5dKlxcfFxccyoke1NIRUxMfXwoPzpcXFxceFswLTlhLWZBLUZdezJ9KXs4LH1bXlxcbl0qXFx8XFxzKiR7U0hFTEx9fFxcYmV4ZWNcXHMqXFwoXFxzKig/OmJhc2U2NFxcLmI2NGRlY29kZXxfX2ltcG9ydF9fXFwoXFxzKlsnXCJdKD86YmFzZTY0fHpsaWIpWydcIl0pfFxcYmV2YWxcXHMqXFwoXFxzKig/OmF0b2J8QnVmZmVyXFwuZnJvbSlcXHMqXFwoYCwgJ2knKSxcbiAgfSxcbiAge1xuICAgIGlkOiAncmV2ZXJzZS1zaGVsbCcsXG4gICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICBtZXNzYWdlOiAnUmV2ZXJzZS1zaGVsbCBwYXR0ZXJuJyxcbiAgICB0ZXN0OiAvXFwvZGV2XFwvdGNwXFwvXFxkfFxcYm5jKD86YXQpP1xcYlteXFxuXSpcXHMtKD86ZXxjKVxccytcXFMqc2hcXGJ8XFxibmNhdFxcYlteXFxuXSotLSg/OmV4ZWN8c2gtZXhlYylcXGJ8XFxic29jYXRcXGJbXlxcbl0qXFxiZXhlYzovaSxcbiAgfSxcbiAge1xuICAgIGlkOiAnY3JlZGVudGlhbC1hY2Nlc3MnLFxuICAgIHNldmVyaXR5OiAnaGlnaCcsXG4gICAgZG9jU2V2ZXJpdHk6ICdtZWRpdW0nLFxuICAgIGNvZGVPbmx5OiB0cnVlLFxuICAgIG1lc3NhZ2U6ICdSZWFkcyBjbG91ZCBvciBidWlsZCBjcmVkZW50aWFscycsXG4gICAgdGVzdDogL1xcYkFXU19TRUNSRVRfQUNDRVNTX0tFWVxcYnxcXGJBV1NfU0VTU0lPTl9UT0tFTlxcYnxcXGJBV1NfQ09OVEFJTkVSX0NSRURFTlRJQUxTXyg/OlJFTEFUSVZFfEZVTEwpX1VSSVxcYnxcXGJBV1NfQ09OVEFJTkVSX0FVVEhPUklaQVRJT05fVE9LRU4oPzpfRklMRSk/XFxifFxcYkNPREVCVUlMRF9bQS1aMC05X10qKD86VE9LRU58Q1JFREVOVElBTHxTRUNSRVR8QVVUSClbQS1aMC05X10qXFxifDE2OVxcLjI1NFxcLjE2OVxcLjI1NHwxNjlcXC4yNTRcXC4xNzBcXC4yfGZkMDA6ZWMyOjoyNTR8XFwuYXdzXFwvY3JlZGVudGlhbHN8XFwvdmFyXFwvcnVuXFwvc2VjcmV0c1xcYi8sXG4gIH0sXG4gIHtcbiAgICBpZDogJ3BpcGUtdG8tc2hlbGwnLFxuICAgIHNldmVyaXR5OiAnaGlnaCcsXG4gICAgZG9jU2V2ZXJpdHk6IG51bGwsXG4gICAgY29kZU9ubHk6IHRydWUsXG4gICAgbWVzc2FnZTogJ0Rvd25sb2FkIHBpcGVkIHN0cmFpZ2h0IGludG8gYSBzaGVsbCcsXG4gICAgdGVzdDogbmV3IFJlZ0V4cChTdHJpbmcucmF3YFxcYig/OmN1cmx8d2dldClcXGJbXlxcbnxdKlxcfFxccyoke1NIRUxMfWAsICdpJyksXG4gIH0sXG4gIHtcbiAgICBpZDogJ3NlY3JldC1saXRlcmFsJyxcbiAgICBzZXZlcml0eTogJ2hpZ2gnLFxuICAgIG1lc3NhZ2U6ICdTZWNyZXQtbG9va2luZyBsaXRlcmFsJyxcbiAgICBzZWNyZXQ6IHRydWUsXG4gICAgdGVzdDogL1xcYig/OkFLSUF8QVNJQSlbMC05QS1aXXsxNn1cXGJ8XFxiZ2hbcG91c3JdX1tBLVphLXowLTldezM2LH1cXGJ8XFxiZ2l0aHViX3BhdF9bQS1aYS16MC05X117NDAsfVxcYnxcXGJ4b3hbYmFwXS1bQS1aYS16MC05LV17MTAsfXwtLS0tLUJFR0lOICg/OlJTQSB8RUMgfERTQSB8T1BFTlNTSCB8UEdQIHxFTkNSWVBURUQgKT9QUklWQVRFIEtFWS0tLS0tfFxcYmdscGF0LVtBLVphLXowLTlfLV17MjAsfVxcYnxcXGJza19saXZlX1tBLVphLXowLTldezE2LH1cXGIvLFxuICB9LFxuXTtcblxuLyoqIGBOQU1FPXZhbHVlYCAvIGBOQU1FOiB2YWx1ZWAgLyBgRU5WIE5BTUUgdmFsdWVgIGFzc2lnbm1lbnRzIHRvIGEgc2VjcmV0LWxvb2tpbmcgbmFtZS4gKi9cbmNvbnN0IFNFQ1JFVF9BU1NJR05NRU5UID0gL1xcYihbQS1aYS16MC05X10qKD86VE9LRU58U0VDUkVUfFBBU1NXT1JEfFBBU1NXRHxBUElfS0VZfFBSSVZBVEVfS0VZfEFDQ0VTU19LRVl8X0tFWSkpXFxiW1wiJ10/XFxzKig/Ols6PV18XFxzKVxccypbXCInXT8oW15cXHNcIidgLDspXXsxNix9KS9pO1xuLyoqIEEgbG9uZyBzaW5nbGUtdG9rZW4gYmFzZTY0IG9yIGhleCBydW4gKGFuIGVtYmVkZGVkIHBheWxvYWQpLiAqL1xuY29uc3QgRU5DT0RFRF9CTE9CID0gL1tBLVphLXowLTkrL117MjQwLH09ezAsMn18XFxiWzAtOWEtZkEtRl17MjQwLH1cXGIvO1xuXG4vKiogU2hhbm5vbiBlbnRyb3B5IG9mIGBzYCwgaW4gYml0cyBwZXIgY2hhcmFjdGVyLiAqL1xuZXhwb3J0IGZ1bmN0aW9uIHNoYW5ub25FbnRyb3B5KHM6IHN0cmluZyk6IG51bWJlciB7XG4gIGlmICghcykgcmV0dXJuIDA7XG4gIGNvbnN0IGNvdW50cyA9IG5ldyBNYXA8c3RyaW5nLCBudW1iZXI+KCk7XG4gIGZvciAoY29uc3QgY2ggb2YgcykgY291bnRzLnNldChjaCwgKGNvdW50cy5nZXQoY2gpID8/IDApICsgMSk7XG4gIGxldCBoID0gMDtcbiAgZm9yIChjb25zdCBuIG9mIGNvdW50cy52YWx1ZXMoKSkge1xuICAgIGNvbnN0IHAgPSBuIC8gcy5sZW5ndGg7XG4gICAgaCAtPSBwICogTWF0aC5sb2cyKHApO1xuICB9XG4gIHJldHVybiBoO1xufVxuXG4vKiogV2hldGhlciBhIHNlY3JldC1hc3NpZ25tZW50IHZhbHVlIGxvb2tzIGxpa2UgYSByZWFsIGNyZWRlbnRpYWwgKG5vdCBhIHBsYWNlaG9sZGVyIG9yIGEgcmVmZXJlbmNlKS4gKi9cbmZ1bmN0aW9uIGxvb2tzTGlrZVNlY3JldFZhbHVlKHZhbHVlOiBzdHJpbmcpOiBib29sZWFuIHtcbiAgaWYgKC9bJF18XFx7XFx7fDx8PnxcXCp7Myx9fHh7Nix9fGNoYW5nZW1lfGV4YW1wbGV8cGxhY2Vob2xkZXJ8eW91clstX10vaS50ZXN0KHZhbHVlKSkgcmV0dXJuIGZhbHNlO1xuICByZXR1cm4gc2hhbm5vbkVudHJvcHkodmFsdWUpID49IDMuNTtcbn1cblxuZnVuY3Rpb24gaXNEb2NGaWxlKHBhdGg6IHN0cmluZyk6IGJvb2xlYW4ge1xuICByZXR1cm4gLyhefFxcLylyZWFkbWUoXFwuW2Etel0rKT8kL2kudGVzdChwYXRoKSB8fCAvXFwuKG1kfG1hcmtkb3dufHJzdHx0eHR8YWRvYykkL2kudGVzdChwYXRoKTtcbn1cblxuZnVuY3Rpb24gbWFzayhsaW5lOiBzdHJpbmcpOiBzdHJpbmcge1xuICByZXR1cm4gbGluZVxuICAgIC5yZXBsYWNlKC9cXGIoKD86QUtJQXxBU0lBKVswLTlBLVpdezR9KVswLTlBLVpdezEyfVxcYi9nLCAnJDHigKYnKVxuICAgIC5yZXBsYWNlKC9cXGIoZ2hbcG91c3JdX3xnaXRodWJfcGF0X3x4b3hbYmFwXS18Z2xwYXQtfHNrX2xpdmVfKVtBLVphLXowLTlfLV0rL2csICckMeKApicpXG4gICAgLnJlcGxhY2UoU0VDUkVUX0FTU0lHTk1FTlQsIChtLCBfbmFtZTogc3RyaW5nLCB2YWx1ZTogc3RyaW5nKSA9PiBtLnJlcGxhY2UodmFsdWUsIGAke3ZhbHVlLnNsaWNlKDAsIDQpfeKApmApKTtcbn1cblxuZnVuY3Rpb24gZXhjZXJwdE9mKGxpbmU6IHN0cmluZywgc2VjcmV0OiBib29sZWFuKTogc3RyaW5nIHtcbiAgY29uc3QgdCA9IChzZWNyZXQgPyBtYXNrKGxpbmUpIDogbGluZSkudHJpbSgpO1xuICByZXR1cm4gdC5sZW5ndGggPiBFWENFUlBUX01BWCA/IGAke3Quc2xpY2UoMCwgRVhDRVJQVF9NQVggLSAxKX3igKZgIDogdDtcbn1cblxuLyoqIFdoZXRoZXIgYSBkZWNvZGVkIGJ1ZmZlciBsb29rcyBiaW5hcnkgKGEgTlVMIGluIGl0cyBmaXJzdCA4IEtpQikuICovXG5mdW5jdGlvbiBpc0JpbmFyeShieXRlczogVWludDhBcnJheSk6IGJvb2xlYW4ge1xuICBjb25zdCBuID0gTWF0aC5taW4oYnl0ZXMubGVuZ3RoLCA4MTkyKTtcbiAgZm9yIChsZXQgaSA9IDA7IGkgPCBuOyBpKyspIGlmIChieXRlc1tpXSA9PT0gMCkgcmV0dXJuIHRydWU7XG4gIHJldHVybiBmYWxzZTtcbn1cblxuZnVuY3Rpb24gYXNUZXh0KGNvbnRlbnQ6IHN0cmluZyB8IFVpbnQ4QXJyYXkpOiB7IHRleHQ6IHN0cmluZyB8IG51bGw7IGJ5dGVzOiBudW1iZXIgfSB7XG4gIGlmICh0eXBlb2YgY29udGVudCA9PT0gJ3N0cmluZycpIHJldHVybiB7IHRleHQ6IGNvbnRlbnQuaW5jbHVkZXMoJ1xcdTAwMDAnKSA/IG51bGwgOiBjb250ZW50LCBieXRlczogQnVmZmVyLmJ5dGVMZW5ndGgoY29udGVudCkgfTtcbiAgaWYgKGlzQmluYXJ5KGNvbnRlbnQpKSByZXR1cm4geyB0ZXh0OiBudWxsLCBieXRlczogY29udGVudC5sZW5ndGggfTtcbiAgcmV0dXJuIHsgdGV4dDogQnVmZmVyLmZyb20oY29udGVudCkudG9TdHJpbmcoJ3V0ZjgnKSwgYnl0ZXM6IGNvbnRlbnQubGVuZ3RoIH07XG59XG5cbi8qKlxuICogU2NhbiBhIHBsdWdpbiBwYWNrYWdlJ3MgZmlsZXMuIGBmaWxlc2AgcGF0aHMgYXJlIHBhY2thZ2UtcmVsYXRpdmU7IGFcbiAqIERvY2tlcmZpbGUgaXMgcmVjb2duaXNlZCBieSBuYW1lIChgRG9ja2VyZmlsZWAsIGAqLkRvY2tlcmZpbGVgLFxuICogYERvY2tlcmZpbGUuKmApLiBEZXRlcm1pbmlzdGljOiBmaW5kaW5ncyBjb21lIGJhY2sgaW4gZmlsZSB0aGVuIGxpbmUgb3JkZXIuXG4gKi9cbmV4cG9ydCBmdW5jdGlvbiBzY2FuUGx1Z2luU291cmNlSGV1cmlzdGljcyhmaWxlczogcmVhZG9ubHkgSGV1cmlzdGljc0lucHV0RmlsZVtdKTogSGV1cmlzdGljc1JlcG9ydCB7XG4gIGNvbnN0IGZpbmRpbmdzOiBIZXVyaXN0aWNGaW5kaW5nW10gPSBbXTtcbiAgY29uc3Qgc2tpcHBlZEZpbGVzOiBzdHJpbmdbXSA9IFtdO1xuICBjb25zdCBzZWVuID0gbmV3IFNldDxzdHJpbmc+KCk7XG4gIGxldCBzY2FubmVkRmlsZXMgPSAwO1xuICBsZXQgYnVkZ2V0ID0gSEVVUklTVElDU19NQVhfVE9UQUxfQllURVM7XG5cbiAgY29uc3QgYWRkID0gKGY6IEhldXJpc3RpY0ZpbmRpbmcpOiB2b2lkID0+IHtcbiAgICBjb25zdCBrZXkgPSBgJHtmLmlkfXwke2YucGF0aH18JHtmLmxpbmV9YDtcbiAgICBpZiAoc2Vlbi5oYXMoa2V5KSB8fCBmaW5kaW5ncy5sZW5ndGggPj0gSEVVUklTVElDU19NQVhfRklORElOR1MpIHJldHVybjtcbiAgICBzZWVuLmFkZChrZXkpO1xuICAgIGZpbmRpbmdzLnB1c2goZik7XG4gIH07XG5cbiAgZm9yIChjb25zdCBmaWxlIG9mIGZpbGVzKSB7XG4gICAgY29uc3QgeyB0ZXh0LCBieXRlcyB9ID0gYXNUZXh0KGZpbGUuY29udGVudCk7XG4gICAgaWYgKHRleHQgPT09IG51bGwgfHwgYnl0ZXMgPiBIRVVSSVNUSUNTX01BWF9GSUxFX0JZVEVTIHx8IGJ5dGVzID4gYnVkZ2V0KSB7XG4gICAgICBza2lwcGVkRmlsZXMucHVzaChmaWxlLnBhdGgpO1xuICAgICAgY29udGludWU7XG4gICAgfVxuICAgIGJ1ZGdldCAtPSBieXRlcztcbiAgICBzY2FubmVkRmlsZXMrKztcbiAgICBjb25zdCBkb2MgPSBpc0RvY0ZpbGUoZmlsZS5wYXRoKTtcbiAgICBjb25zdCBsaW5lcyA9IHRleHQucmVwbGFjZSgvXFxyXFxuPy9nLCAnXFxuJykuc3BsaXQoJ1xcbicpO1xuXG4gICAgbGluZXMuZm9yRWFjaCgocmF3LCBpZHgpID0+IHtcbiAgICAgIGNvbnN0IGxpbmUgPSBpZHggKyAxO1xuICAgICAgaWYgKEVOQ09ERURfQkxPQi50ZXN0KHJhdykpIHtcbiAgICAgICAgYWRkKHsgaWQ6ICdlbmNvZGVkLWJsb2InLCBzZXZlcml0eTogJ21lZGl1bScsIHBhdGg6IGZpbGUucGF0aCwgbGluZSwgZXhjZXJwdDogZXhjZXJwdE9mKHJhdywgZmFsc2UpLCBtZXNzYWdlOiAnTG9uZyBzaW5nbGUtbGluZSBlbmNvZGVkIHBheWxvYWQnIH0pO1xuICAgICAgfVxuICAgICAgY29uc3Qgc2NhbiA9IHJhdy5sZW5ndGggPiBMSU5FX1NDQU5fTUFYID8gcmF3LnNsaWNlKDAsIExJTkVfU0NBTl9NQVgpIDogcmF3O1xuICAgICAgY29uc3QgY29tbWVudCA9IC9eXFxzKigjfFxcL1xcLykvLnRlc3Qoc2Nhbik7XG4gICAgICBmb3IgKGNvbnN0IHJ1bGUgb2YgTElORV9SVUxFUykge1xuICAgICAgICBpZiAocnVsZS5jb2RlT25seSAmJiBjb21tZW50KSBjb250aW51ZTtcbiAgICAgICAgaWYgKCFydWxlLnRlc3QudGVzdChzY2FuKSkgY29udGludWU7XG4gICAgICAgIGNvbnN0IHNldmVyaXR5ID0gZG9jICYmIHJ1bGUuZG9jU2V2ZXJpdHkgIT09IHVuZGVmaW5lZCA/IHJ1bGUuZG9jU2V2ZXJpdHkgOiBydWxlLnNldmVyaXR5O1xuICAgICAgICBpZiAoc2V2ZXJpdHkgPT09IG51bGwpIGNvbnRpbnVlO1xuICAgICAgICBhZGQoeyBpZDogcnVsZS5pZCwgc2V2ZXJpdHksIHBhdGg6IGZpbGUucGF0aCwgbGluZSwgZXhjZXJwdDogZXhjZXJwdE9mKHNjYW4sIHJ1bGUuc2VjcmV0ID09PSB0cnVlKSwgbWVzc2FnZTogcnVsZS5tZXNzYWdlIH0pO1xuICAgICAgfVxuICAgICAgY29uc3QgYXNzaWdubWVudCA9IFNFQ1JFVF9BU1NJR05NRU5ULmV4ZWMoc2Nhbik7XG4gICAgICBpZiAoYXNzaWdubWVudCAmJiBsb29rc0xpa2VTZWNyZXRWYWx1ZShhc3NpZ25tZW50WzJdISkpIHtcbiAgICAgICAgYWRkKHtcbiAgICAgICAgICBpZDogJ3NlY3JldC1kZWZhdWx0JyxcbiAgICAgICAgICBzZXZlcml0eTogZG9jID8gJ21lZGl1bScgOiAnaGlnaCcsXG4gICAgICAgICAgcGF0aDogZmlsZS5wYXRoLFxuICAgICAgICAgIGxpbmUsXG4gICAgICAgICAgZXhjZXJwdDogZXhjZXJwdE9mKHNjYW4sIHRydWUpLFxuICAgICAgICAgIG1lc3NhZ2U6IGBIaWdoLWVudHJvcHkgdmFsdWUgYXNzaWduZWQgdG8gJHthc3NpZ25tZW50WzFdfWAsXG4gICAgICAgIH0pO1xuICAgICAgfVxuICAgIH0pO1xuXG4gICAgLy8gRG9ja2VyZmlsZSBSVU4gbGluZXMgdGhhdCBwaXBlIGEgZG93bmxvYWQgaW50byBhIHNoZWxsLCBjb250aW51YXRpb24tam9pbmVkXG4gICAgLy8gKHRoZSBsaW5lIHJ1bGUgYWJvdmUgb25seSBzZWVzIG9uZSBwaHlzaWNhbCBsaW5lKS5cbiAgICBpZiAoLyhefFxcLykoRG9ja2VyZmlsZShcXC5bXi9dKyk/fFteL10rXFwuRG9ja2VyZmlsZSkkL2kudGVzdChmaWxlLnBhdGgpKSB7XG4gICAgICBmb3IgKGNvbnN0IGluc3RydWN0aW9uIG9mIHBpcGVJbnN0YWxsZXJzKHRleHQpKSB7XG4gICAgICAgIGNvbnN0IGZpcnN0TGluZSA9IGxpbmVzLmZpbmRJbmRleCgobCkgPT4gbC50cmltKCkgIT09ICcnICYmIGluc3RydWN0aW9uLnN0YXJ0c1dpdGgobC50cmltKCkucmVwbGFjZSgvXFxcXFxccyokLywgJycpLnRyaW0oKSkpO1xuICAgICAgICBhZGQoe1xuICAgICAgICAgIGlkOiAncGlwZS10by1zaGVsbCcsXG4gICAgICAgICAgc2V2ZXJpdHk6ICdoaWdoJyxcbiAgICAgICAgICBwYXRoOiBmaWxlLnBhdGgsXG4gICAgICAgICAgbGluZTogZmlyc3RMaW5lID49IDAgPyBmaXJzdExpbmUgKyAxIDogMSxcbiAgICAgICAgICBleGNlcnB0OiBleGNlcnB0T2YoaW5zdHJ1Y3Rpb24sIGZhbHNlKSxcbiAgICAgICAgICBtZXNzYWdlOiAnRG93bmxvYWQgcGlwZWQgc3RyYWlnaHQgaW50byBhIHNoZWxsJyxcbiAgICAgICAgfSk7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgcmV0dXJuIHsgZmluZGluZ3MsIHNjYW5uZWRGaWxlcywgc2tpcHBlZEZpbGVzIH07XG59XG5cbi8qKiBUaGUgZmluZGluZ3MgdGhhdCBmYWlsIGEgZ2F0ZSAoYGhpZ2hgKS4gKi9cbmV4cG9ydCBmdW5jdGlvbiBibG9ja2luZ0hldXJpc3RpY3MocmVwb3J0OiBQaWNrPEhldXJpc3RpY3NSZXBvcnQsICdmaW5kaW5ncyc+KTogSGV1cmlzdGljRmluZGluZ1tdIHtcbiAgcmV0dXJuIHJlcG9ydC5maW5kaW5ncy5maWx0ZXIoKGYpID0+IGYuc2V2ZXJpdHkgPT09ICdoaWdoJyk7XG59XG4iXX0=
@@ -0,0 +1,18 @@
1
+ export interface PluginLintFinding {
2
+ level: 'error' | 'warning';
3
+ message: string;
4
+ }
5
+ /** Spec fields test-plugins.sh requires on every plugin. */
6
+ export declare const PLUGIN_LINT_REQUIRED_FIELDS: readonly string[];
7
+ /** …and on every CodeBuildStep. */
8
+ export declare const PLUGIN_LINT_CODEBUILD_FIELDS: readonly string[];
9
+ /** Logical Dockerfile instructions: comment lines dropped, `\` continuations joined. */
10
+ export declare function dockerfileInstructions(content: string): string[];
11
+ /** RUN instructions that pipe into a shell (`curl … | bash`). */
12
+ export declare function pipeInstallers(content: string): string[];
13
+ /** curl/wget DOWNLOADS in RUN, and `ADD <url>` without `--checksum=`. */
14
+ export declare function rawDownloads(content: string): string[];
15
+ /** Lint a Dockerfile against the catalog's rules. */
16
+ export declare function lintPluginDockerfile(content: string): PluginLintFinding[];
17
+ /** Lint the spec document (as parsed) the way test-plugins.sh does. */
18
+ export declare function lintPluginSpec(spec: Record<string, unknown>, specText: string): PluginLintFinding[];