Skip to content

Commit ac90030

Browse files
authored
chore: upgrade js-yaml to v4 and resolve Docker vulnerabilities (#10677)
* chore: upgrade js-yaml to v4 and resolve Docker vulnerabilities ### Description This change upgrades `js-yaml` from version 3 to version 4 to address a security vulnerability (CVE-2026-53550). To make the codebase compatible with the new major version of `js-yaml`: - Replaced deprecated `safeLoad` calls with `load` (safe by default in v4). - Replaced deprecated `safeDump` calls with `dump`. - Added casts where needed as `load` in v4 returns `unknown` instead of `any`. - Added package overrides in `scripts/publish/firebase-docker-image/package.json` to enforce vulnerability-free versions of `js-yaml` (`^4.2.0`) and `@opentelemetry/core` (`^2.8.0`) during the Docker image build. - Regenerated `npm-shrinkwrap.json`. ### Scenarios Tested - Ran unit tests locally: `npm run test:compile`, `npx mocha -r ts-node/register src/appdistribution/yaml_helper.spec.ts src/init/features/dataconnect/resolver.spec.ts src/init/features/dataconnect/sdk.spec.ts src/mcp/tools/apptesting/tests.spec.ts`. All passed. - Built and published the Docker image to the staging repository: `./scripts/publish/firebase-docker-image/run.sh --build-project fir-tools-builds --repo staging --target firebase-cli`. - Verified that the new container image vulnerability report is completely free of vulnerabilities in `@opentelemetry/core` and `js-yaml`. ### Sample Commands `gcloud artifacts vulnerabilities list us-docker.pkg.dev/firebase-cli/staging/firebase@sha256:5a92b73fc382923834fc396eccbd11ac60c7ae69fecf1df293acf7a1670d7f4f` * chore: address review feedback and align npm-shrinkwrap formatting * chore: add @opentelemetry/core override to root package.json
1 parent 2da66b6 commit ac90030

8 files changed

Lines changed: 215 additions & 182 deletions

File tree

npm-shrinkwrap.json

Lines changed: 200 additions & 170 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@
139139
"glob": "^10.5.0",
140140
"google-auth-library": "^9.11.0",
141141
"ignore": "^7.0.4",
142-
"js-yaml": "^3.14.2",
142+
"js-yaml": "^4.2.0",
143143
"jsonwebtoken": "^9.0.2",
144144
"libsodium-wrappers": "^0.7.10",
145145
"lodash": "^4.18.0",
@@ -198,7 +198,7 @@
198198
"@types/html-escaper": "^3.0.0",
199199
"@types/inquirer": "^8.1.3",
200200
"@types/inquirer-autocomplete-prompt": "^2.0.2",
201-
"@types/js-yaml": "^3.12.2",
201+
"@types/js-yaml": "^4.0.9",
202202
"@types/jsonwebtoken": "^9.0.5",
203203
"@types/libsodium-wrappers": "^0.7.9",
204204
"@types/lodash": "^4.14.149",
@@ -286,6 +286,7 @@
286286
"picomatch": "^2.3.2",
287287
"qs": "^6.15.2",
288288
"tar": "^7.5.11",
289+
"@opentelemetry/core": "^2.8.0",
289290
"basic-ftp": "^5.2.2",
290291
"@babel/traverse": "^7.23.2",
291292
"universal-analytics": {

scripts/publish/firebase-docker-image/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
"diff": "^4.0.4",
88
"hono": "^4.11.7",
99
"minimatch": "^10.2.3",
10-
"uuid": "^11.1.1"
10+
"uuid": "^11.1.1",
11+
"js-yaml": "^4.2.0",
12+
"@opentelemetry/core": "^2.8.0"
1113
}
1214
}

src/appdistribution/yaml_helper.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ const YAML_DATA = {
6767
describe("YamlHelper", () => {
6868
it("converts TestCase[] to YAML string", () => {
6969
const yamlString = toYaml(TEST_CASES);
70-
expect(jsYaml.safeLoad(yamlString)).to.eql(YAML_DATA);
70+
expect(jsYaml.load(yamlString)).to.eql(YAML_DATA);
7171
expect(yamlString).to.eq(YAML_STRING); // brittle ¯\_(ツ)_/¯
7272
});
7373

@@ -167,7 +167,7 @@ describe("YamlHelper", () => {
167167
-
168168
invalid key: value`,
169169
),
170-
).to.throw(/at line 3/);
170+
).to.throw(/(at line 3|\(3:3\))/);
171171
});
172172

173173
it("throws error if YAML doesn't contain a top-level tests field", () => {

src/appdistribution/yaml_helper.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ function toYamlTestCases(testCases: TestCase[]): YamlTestCase[] {
4848

4949
/** Converts a list of test cases to YAML format. */
5050
export function toYaml(testCases: TestCase[]): string {
51-
return jsYaml.safeDump({ tests: toYamlTestCases(testCases) });
51+
return jsYaml.dump({ tests: toYamlTestCases(testCases) });
5252
}
5353

5454
function castExists<T>(it: T | null | undefined, thing: string): T {
@@ -100,7 +100,7 @@ function fromYamlTestCases(appName: string, yamlTestCases: YamlTestCase[]): Test
100100
export function fromYaml(appName: string, yaml: string): TestCase[] {
101101
let parsedYaml: unknown;
102102
try {
103-
parsedYaml = jsYaml.safeLoad(yaml);
103+
parsedYaml = jsYaml.load(yaml);
104104
} catch (err: unknown) {
105105
throw new FirebaseError(`Failed to parse YAML: ${getErrMsg(err)}`);
106106
}

src/init/features/dataconnect/resolver.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -341,7 +341,7 @@ describe("actuate", () => {
341341
expect(writeProjectFileStub.calledTwice).to.be.true;
342342
const writtenYamlPath = writeProjectFileStub.getCall(0).args[0];
343343
const writtenYamlContents = writeProjectFileStub.getCall(0).args[1];
344-
const parsedYaml = yaml.load(writtenYamlContents);
344+
const parsedYaml = yaml.load(writtenYamlContents) as DataConnectYaml;
345345
expect(writtenYamlPath).to.equal("../service/dataconnect.yaml");
346346
expect(parsedYaml.schemas).to.have.lengthOf(2);
347347
const writtenSchemaPath = writeProjectFileStub.getCall(1).args[0];
@@ -368,7 +368,7 @@ type Query {
368368
expect(writeProjectFileStub.calledTwice).to.be.true;
369369
const writtenYamlPath = writeProjectFileStub.getCall(0).args[0];
370370
const writtenYamlContents = writeProjectFileStub.getCall(0).args[1];
371-
const parsedYaml = yaml.load(writtenYamlContents);
371+
const parsedYaml = yaml.load(writtenYamlContents) as DataConnectYaml;
372372
expect(writtenYamlPath).to.equal("../service/dataconnect.yaml");
373373
expect(parsedYaml.schemas).to.have.lengthOf(2);
374374
const writtenSchemaPath = writeProjectFileStub.getCall(1).args[0];

src/init/features/dataconnect/sdk.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -609,7 +609,7 @@ describe("actuate", () => {
609609
await actuate(setup, config);
610610

611611
const writtenYaml = writeProjectFileStub.getCall(0).args[1];
612-
const parsedYaml = yaml.load(writtenYaml);
613-
expect(parsedYaml.generate.javascriptSdk).to.have.lengthOf(1);
612+
const parsedYaml = yaml.load(writtenYaml) as ConnectorYaml;
613+
expect(parsedYaml.generate!.javascriptSdk).to.have.lengthOf(1);
614614
});
615615
});

src/mcp/tools/apptesting/tests.spec.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as distribution from "../../../appdistribution/distribution";
55
import { AppDistributionClient } from "../../../appdistribution/client";
66
import * as appTesting from "../../../gcp/apptesting";
77
import { McpContext } from "../../types";
8-
import { safeLoad } from "js-yaml";
8+
import { load as safeLoad } from "js-yaml";
99
import * as fs from "fs-extra";
1010
import * as path from "path";
1111

0 commit comments

Comments
 (0)