Browse Source

Merge pull request #15667 from thingsboard/master-rc-merge

Merge rc into master
pull/15544/merge
Viacheslav Klimov 2 weeks ago
committed by GitHub
parent
commit
4386bbf2d7
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      TEST_FAST.md
  2. 17
      application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java
  3. 10
      common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java
  4. 12
      common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java
  5. 14
      common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java
  6. 3
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java
  7. 10
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java
  8. 73
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java
  9. 1
      msa/black-box-tests/src/test/resources/connectivity.xml
  10. 25
      msa/js-executor/api/jsExecutor.ts
  11. 8
      msa/js-executor/config/default.yml
  12. 2
      msa/js-executor/package.json
  13. 11
      msa/js-executor/pom.xml
  14. 83
      msa/js-executor/test/jsExecutor.test.ts
  15. 2
      msa/js-executor/tsconfig.json
  16. 18
      pom.xml
  17. 13
      ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts
  18. 32
      ui-ngx/yarn.lock

1
TEST_FAST.md

@ -10,6 +10,7 @@ export SUREFIRE_JAVA_OPTS="-Xmx1200m -Xss256k -XX:+ExitOnOutOfMemoryError"
mvn clean install -T6 -DskipTests -Dpkg.skip=true
mvn test -pl='!application,!dao,!ui-ngx,!msa/js-executor,!msa/web-ui' -T4
mvn test -pl='msa/js-executor'
mvn test -pl dao -Dparallel=packages -DforkCount=4
mvn test -pl application -Dtest='!**/nosql/**,org.thingsboard.server.controller.**' -DforkCount=6 -Dparallel=classes -Dsurefire.rerunFailingTestsCount=2 -Dsurefire.failOnFlakeCount=5

17
application/src/test/java/org/thingsboard/server/controller/RuleChainControllerTest.java

@ -644,4 +644,21 @@ public class RuleChainControllerTest extends AbstractControllerTest {
return doPost("/api/ruleChain", ruleChain, RuleChain.class);
}
@Test
public void testScriptForbiddenForCustomer() throws Exception {
loginCustomerUser();
doPost("/api/ruleChain/testScript", (Object) """
{
"script": "return msg;",
"scriptType": "update",
"argNames": ["msg", "metadata", "msgType"],
"msg": "{}",
"metadata": {},
"msgType": "POST_TELEMETRY_REQUEST"
}
""")
.andExpect(status().isForbidden());
}
}

10
common/data/src/main/java/org/thingsboard/server/common/data/ai/dto/TbUserMessage.java

@ -25,12 +25,10 @@ import java.util.List;
public record TbUserMessage(
@NotEmpty
@Valid
@ArraySchema(
arraySchema = @Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "A list of content parts that make up the complete user prompt"
),
schema = @Schema(ref = "#/components/schemas/TbContent")
@Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "A list of content parts that make up the complete user prompt"
)
@ArraySchema(schema = @Schema(ref = "#/components/schemas/TbContent"))
List<TbContent> contents
) {}

12
common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeys.java

@ -37,17 +37,21 @@ public record AvailableEntityKeys(
)
Set<EntityType> entityTypes,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "List of unique time series key names available on the matched entities."
)
@ArraySchema(
arraySchema = @Schema(description = "List of unique time series key names available on the matched entities."),
schema = @Schema(implementation = String.class, example = "temperature"),
uniqueItems = true
)
List<String> timeseries,
@Schema(requiredMode = Schema.RequiredMode.REQUIRED)
@Schema(
requiredMode = Schema.RequiredMode.REQUIRED,
description = "List of unique attribute key names available on the matched entities."
)
@ArraySchema(
arraySchema = @Schema(description = "List of unique attribute key names available on the matched entities."),
schema = @Schema(implementation = String.class, example = "serialNumber"),
uniqueItems = true
)

14
common/data/src/main/java/org/thingsboard/server/common/data/query/AvailableEntityKeysV2.java

@ -41,15 +41,13 @@ public record AvailableEntityKeysV2(
)
Set<EntityType> entityTypes,
@ArraySchema(
arraySchema = @Schema(
description = """
List of unique time series keys available on the matched entities, sorted alphabetically.
Omitted when timeseries keys were not requested.""",
nullable = true
),
schema = @Schema(implementation = KeyInfo.class)
@Schema(
description = """
List of unique time series keys available on the matched entities, sorted alphabetically.
Omitted when timeseries keys were not requested.""",
nullable = true
)
@ArraySchema(schema = @Schema(implementation = KeyInfo.class))
@Nullable List<KeyInfo> timeseries,
@Schema(

3
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java

@ -36,7 +36,8 @@ public class WidgetsBundleExportData extends EntityExportData<WidgetsBundle> {
@Override
public EntityType getEntityType() { return EntityType.WIDGETS_BUNDLE; }
@ArraySchema(arraySchema = @Schema(description = "List of widgets in the bundle"), schema = @Schema(implementation = JsonNode.class))
@Schema(description = "List of widgets in the bundle")
@ArraySchema(schema = @Schema(implementation = JsonNode.class))
@JsonProperty(index = 3)
private List<ObjectNode> widgets;

10
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java

@ -358,6 +358,16 @@ public class TestRestClient {
.statusCode(HTTP_OK);
}
public JsonNode testRuleChainScript(Object body) {
return given().spec(requestSpec)
.body(body)
.post("/api/ruleChain/testScript")
.then()
.statusCode(HTTP_OK)
.extract()
.as(JsonNode.class);
}
private String getUrlParams(PageLink pageLink) {
String urlParams = "pageSize={pageSize}&page={page}";
if (!isEmpty(pageLink.getTextSearch())) {

73
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/security/JsExecutorSandboxIsolationTest.java

@ -0,0 +1,73 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.msa.security;
import com.fasterxml.jackson.databind.JsonNode;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.thingsboard.server.msa.AbstractContainerTest;
import static org.assertj.core.api.Assertions.assertThat;
public class JsExecutorSandboxIsolationTest extends AbstractContainerTest {
@BeforeClass
public void beforeClass() {
testRestClient.login("tenant@thingsboard.org", "tenant");
}
@AfterClass
public void afterClass() {
testRestClient.resetToken();
}
/**
* Black-box regression for JVN#16937365: a tenant admin must not be able
* to escape the tb-js-executor sandbox via the host-realm prototype chain
* exposed through the script's `args` argument. Runs against the live
* docker-compose deployment, which uses script.use_sandbox=true and
* JS_EVALUATOR=remote (Kafka -> tb-js-executor).
*/
@Test
public void testRuleChainScriptCannotReachHostProcess() {
JsonNode response = testRestClient.testRuleChainScript("""
{
"script": "var F = args.constructor.constructor; var p = F('return process')(); return { reachedHost: !!(p && p.mainModule) };",
"scriptType": "update",
"argNames": ["msg", "metadata", "msgType"],
"msg": "{}",
"metadata": {},
"msgType": "POST_TELEMETRY_REQUEST"
}
""");
// The sandboxed run must reject the escape attempt: the host `process`
// global is not defined inside the sandbox realm, so executing the
// synthesized function `F("return process")` throws.
assertThat(response.has("error")).isTrue();
String error = response.get("error").asText();
assertThat(error)
.as("sandbox must block host-realm reach via args.constructor.constructor; full error: %s", error)
.contains("process is not defined");
// Defense in depth: even if the script somehow returned, output must
// not indicate that the host process was reached.
if (response.hasNonNull("output")) {
assertThat(response.get("output").asText()).doesNotContain("\"reachedHost\":true");
}
}
}

1
msa/black-box-tests/src/test/resources/connectivity.xml

@ -25,6 +25,7 @@
<package name="org.thingsboard.server.msa.edqs"/>
<package name="org.thingsboard.server.msa.cf"/>
<package name="org.thingsboard.server.msa.rule.node"/>
<package name="org.thingsboard.server.msa.security"/>
</packages>
</test>
</suite>

25
msa/js-executor/api/jsExecutor.ts

@ -15,14 +15,22 @@
///
import vm, { Script } from 'vm';
import { _logger } from '../config/logger';
export type TbScript = Script | Function;
export class JsExecutor {
useSandbox: boolean;
private logger = _logger('JsExecutor');
constructor(useSandbox: boolean) {
this.useSandbox = useSandbox;
if (!useSandbox) {
this.logger.warn(
'script.use_sandbox=false: dangerous by design — user-supplied scripts run in the host realm with no isolation. ' +
'Use only as a performance trade-off in trusted, non-public clusters.'
);
}
}
compileScript(code: string): Promise<TbScript> {
@ -56,9 +64,15 @@ export class JsExecutor {
private invokeScript(script: Script, args: string[], timeout: number | undefined): Promise<any> {
return new Promise((resolve, reject) => {
try {
const sandbox = Object.create(null);
sandbox.args = args;
const result = script.runInNewContext(sandbox, {timeout: timeout});
const sandbox = vm.createContext(Object.create(null));
// Construct args inside the sandbox context so it inherits sandbox-realm
// prototypes; prevents prototype-based escapes from the host realm.
const ctxArgs = vm.runInContext('[]', sandbox) as string[];
for (let i = 0; i < args.length; i++) {
ctxArgs[i] = String(args[i]);
}
sandbox.args = ctxArgs;
const result = script.runInContext(sandbox, {timeout: timeout});
resolve(result);
} catch (err) {
reject(err);
@ -67,6 +81,11 @@ export class JsExecutor {
}
// DANGEROUS BY DESIGN: the non-sandbox path. vm.compileFunction's
// parsingContext only isolates *parsing*, not *execution* — the resulting
// function runs in the host realm with full access to host globals
// (process, require, etc.). Enabled only via script.use_sandbox=false as
// a performance trade-off in trusted clusters.
private createFunction(code: string): Promise<Function> {
return new Promise((resolve, reject) => {
try {

8
msa/js-executor/config/default.yml

@ -60,7 +60,13 @@ logger:
# JavaScript execution and monitoring configuration
# Sandboxing, script caching, and observability settings for the JS script runtime.
script:
use_sandbox: "true" # Run scripts inside an isolated sandbox to prevent access to Node.js internals
# WARNING: setting this to "false" is DANGEROUS BY DESIGN. The non-sandbox
# path compiles and runs user-supplied scripts in the host realm via
# vm.compileFunction; it provides no isolation and exposes the host process
# (file system, environment variables, child_process, etc.) to script
# authors. Use "false" only as a performance trade-off in trusted,
# non-public clusters where every script author is fully trusted.
use_sandbox: "true"
memory_usage_trace_frequency: "1000" # Log memory usage every N script executions
script_body_trace_frequency: "10000" # Log the script body every N script executions
stat_print_frequency: "10000" # Print execution statistics every N script executions

2
msa/js-executor/package.json

@ -7,7 +7,7 @@
"bin": "server.js",
"scripts": {
"pkg": "tsc && pkg -t node22-linux-x64 --output ./target/thingsboard-js-executor-linux ./target/src && pkg -t node22-win-x64 --no-bytecode --public-packages \"*\" --public --output ./target/thingsboard-js-executor-win.exe ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1",
"test": "mkdir -p target/surefire-reports && node --require ts-node/register --test --test-reporter=spec --test-reporter-destination=stdout --test-reporter=junit --test-reporter-destination=target/surefire-reports/TEST-js-executor.xml test/jsExecutor.test.ts",
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
"build": "tsc"

11
msa/js-executor/pom.xml

@ -116,6 +116,17 @@
<arguments>--mutex network run pkg</arguments>
</configuration>
</execution>
<execution>
<id>yarn test</id>
<goals>
<goal>yarn</goal>
</goals>
<phase>test</phase>
<configuration>
<skip>${maven.test.skip}</skip>
<arguments>--mutex network run test</arguments>
</configuration>
</execution>
</executions>
</plugin>
<plugin>

83
msa/js-executor/test/jsExecutor.test.ts

@ -0,0 +1,83 @@
///
/// Copyright © 2016-2026 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { describe, test } from 'node:test';
import assert from 'node:assert/strict';
import { JsExecutor } from '../api/jsExecutor';
// describe('js-executor') groups all cases under <testsuite name="js-executor">
// in the JUnit XML so they show up under that suite in TeamCity's Tests tab,
// alongside thousands of Java tests.
describe('js-executor', () => {
test('sandbox isolates args from host realm (JVN#16937365)', async () => {
const exec = new JsExecutor(true);
const script = await exec.compileScript(`function(msg, metadata, msgType){
var F = args.constructor.constructor;
var p = F("return process")();
return p && p.mainModule ? 'reached-host' : 'isolated';
}`);
await assert.rejects(
exec.executeScript(script, ['{}', '{}', 'POST_TELEMETRY_REQUEST'], 5000),
/process is not defined/,
'host process must not be reachable from inside the sandbox',
);
});
test('sandbox passes string args through unchanged', async () => {
const exec = new JsExecutor(true);
const script = await exec.compileScript(`function(msg, metadata, msgType){
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] };
}`);
const out = await exec.executeScript(script, ['hello', '{}', 'X'], 5000);
// Field-by-field: the returned object is owned by the sandbox realm, so
// its prototype is not the host Object.prototype and deepStrictEqual would
// reject it on prototype mismatch even when the values match.
assert.equal(out.msgIsString, true);
assert.equal(out.count, 3);
assert.equal(out.first, 'hello');
});
// The use_sandbox=false path is intentionally non-isolating: scripts compile
// and run in the host realm via vm.compileFunction. The two tests below codify
// that documented contract so any future behavior change shows up as a test
// failure and forces a deliberate update of the docs and threat model.
test('non-sandbox path does not isolate from host realm (documented contract)', async () => {
const exec = new JsExecutor(false);
const script = await exec.compileScript(`function(msg, metadata, msgType){
// Non-destructive host-reach probe: typeof process.platform is 'string'
// only if the host process object is reachable.
var F = args.constructor.constructor;
return F('return typeof process.platform')();
}`);
const out = await exec.executeScript(script, ['{}', '{}', 'X']);
assert.equal(out, 'string',
'use_sandbox=false is documented as non-isolating; if this fails, the path was changed and docs/threat model must be updated');
});
test('non-sandbox path passes string args through unchanged', async () => {
const exec = new JsExecutor(false);
const script = await exec.compileScript(`function(msg, metadata, msgType){
return { msgIsString: typeof msg === 'string', count: args.length, first: args[0] };
}`);
const out = await exec.executeScript(script, ['hello', '{}', 'X']);
assert.equal(out.msgIsString, true);
assert.equal(out.count, 3);
assert.equal(out.first, 'hello');
});
}); // describe('js-executor')

2
msa/js-executor/tsconfig.json

@ -9,5 +9,5 @@
"skipLibCheck": true,
"strictPropertyInitialization": false
},
"exclude": ["node_modules", "target"]
"exclude": ["node_modules", "target", "test"]
}

18
pom.xml

@ -71,6 +71,7 @@
<commons-lang3.version>3.18.0</commons-lang3.version> <!-- to fix CVE-2025-48924. TODO: remove when fixed in spring-boot-dependencies -->
<postgresql.version>42.7.11</postgresql.version> <!-- to fix CVE-2026-42198. TODO: remove when fixed in spring-boot-dependencies -->
<netty.version>4.1.133.Final</netty.version> <!-- to fix CVE-2026-42579, CVE-2026-42583, CVE-2026-42584, CVE-2026-42587. TODO: remove when fixed in spring-boot-dependencies -->
<tomcat.version>10.1.55</tomcat.version> <!-- to fix CVE-2026-41284, CVE-2026-43512. TODO: remove when fixed in spring-boot-dependencies -->
<javax.xml.bind-api.version>2.4.0-b180830.0359</javax.xml.bind-api.version>
<jjwt.version>0.12.5</jjwt.version>
<rat.version>0.10</rat.version> <!-- unused -->
@ -1019,6 +1020,23 @@
<scope>import</scope>
</dependency>
<!-- End of netty-bom version override -->
<!-- Temporary tomcat-embed version override -->
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-el</artifactId>
<version>${tomcat.version}</version>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-websocket</artifactId>
<version>${tomcat.version}</version>
</dependency>
<!-- End of tomcat-embed version override -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>

13
ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts

@ -24,7 +24,7 @@ import {
ViewChild,
ViewEncapsulation
} from '@angular/core';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ImageService } from '@app/core/public-api';
import { AppState } from '@core/core.state';
import { AttributeService } from '@core/http/attribute.service';
@ -63,8 +63,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
protected store: Store<AppState>,
private imageService: ImageService,
private utils: UtilsService,
private attributeService: AttributeService,
private sanitizer: DomSanitizer
private attributeService: AttributeService
) {
super(store);
}
@ -115,8 +114,8 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
isLoading = false;
singleDevice = true;
updatePhoto = false;
previewPhoto: SafeUrl;
lastPhoto: SafeUrl;
previewPhoto: string;
lastPhoto: string;
datasourceDetected = false;
private mimeType: string;
@ -176,7 +175,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
private updateWidgetData(data: Array<DatasourceData>) {
const keyData = data[0].data;
if (keyData?.length && isString(keyData[0][1])) {
this.lastPhoto = keyData[0][1].startsWith('data:image/') ? this.sanitizer.bypassSecurityTrustUrl(keyData[0][1]) : keyData[0][1];
this.lastPhoto = keyData[0][1];
}
}
@ -309,7 +308,7 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
const file = new File([blob], fileName, { type: this.mimeType });
return this.imageService.uploadImage(file, fileName);
}),
map((imageInfo) =>
map((imageInfo) =>
this.settings.usePublicGalleryLink ? imageInfo.publicLink : imageInfo.link
)
);

32
ui-ngx/yarn.lock

@ -987,14 +987,14 @@
"@babel/helper-plugin-utils" "^7.28.6"
"@babel/plugin-transform-modules-systemjs@^7.27.1":
version "7.28.5"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz#7439e592a92d7670dfcb95d0cbc04bd3e64801d2"
integrity sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==
version "7.29.4"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.4.tgz#f621105da99919c15cf4bde6fcc7346ef95e7b20"
integrity sha512-N7QmZ0xRZfjHOfZeQLJjwgX2zS9pdGHSVl/cjSGlo4dXMqvurfxXDMKY4RqEKzPozV78VMcd0lxyG13mlbKc4w==
dependencies:
"@babel/helper-module-transforms" "^7.28.3"
"@babel/helper-plugin-utils" "^7.27.1"
"@babel/helper-module-transforms" "^7.28.6"
"@babel/helper-plugin-utils" "^7.28.6"
"@babel/helper-validator-identifier" "^7.28.5"
"@babel/traverse" "^7.28.5"
"@babel/traverse" "^7.29.0"
"@babel/plugin-transform-modules-umd@^7.27.1":
version "7.27.1"
@ -1298,7 +1298,7 @@
"@babel/parser" "^7.28.6"
"@babel/types" "^7.28.6"
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6":
"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.5", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0":
version "7.29.0"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a"
integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==
@ -4349,17 +4349,17 @@ boolbase@^1.0.0:
integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==
brace-expansion@^1.1.7:
version "1.1.13"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.13.tgz#d37875c01dc9eff988dd49d112a57cb67b54efe6"
integrity sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==
version "1.1.14"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.14.tgz#d9de602370d91347cd9ddad1224d4fd701eb348b"
integrity sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^5.0.2:
version "5.0.5"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.5.tgz#dcc3a37116b79f3e1b46db994ced5d570e930fdb"
integrity sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==
version "5.0.6"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.6.tgz#ec68fe0a641a29d8711579caf641d05bae1f2285"
integrity sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==
dependencies:
balanced-match "^4.0.2"
@ -6113,9 +6113,9 @@ fast-levenshtein@^2.0.6:
integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
fast-uri@^3.0.1:
version "3.1.0"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.0.tgz#66eecff6c764c0df9b762e62ca7edcfb53b4edfa"
integrity sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==
version "3.1.2"
resolved "https://registry.yarnpkg.com/fast-uri/-/fast-uri-3.1.2.tgz#8af3d4fc9d3e71b11572cc2673b514a7d1a8c8ec"
integrity sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==
fastq@^1.6.0:
version "1.17.1"

Loading…
Cancel
Save