This is an automated email from the ASF dual-hosted git repository.
vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git
commit c7f0d7910677e02d876dcc12e0e2ceed24cdb25f
Author: github-actions[bot]
<41898282+github-actions[bot]@users.noreply.github.com>
AuthorDate: Fri Oct 30 12:14:35 2026 +0530
[v3-4-test] Reject non-object JSON files when importing variables (#75384)
(#75950)
Array and primitive documents cannot represent a mapping of variable names
to values and can otherwise enable unintended imports.
(cherry picked from commit eafb6ed8a7d834d52527ddcc601c5b90e9c8cde0)
Co-authored-by: Himanshu Chawla <[email protected]>
Co-authored-by: Himanshu Chawla
<[email protected]>
---
.../pages/Variables/ImportVariablesForm.test.tsx | 83 ++++++++++++++++++++++
.../ui/src/pages/Variables/ImportVariablesForm.tsx | 12 +++-
2 files changed, 92 insertions(+), 3 deletions(-)
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.test.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.test.tsx
new file mode 100644
index 00000000000..f4d76a046b0
--- /dev/null
+++
b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.test.tsx
@@ -0,0 +1,83 @@
+/*!
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 "@testing-library/jest-dom/vitest";
+import { fireEvent, render, screen, waitFor } from "@testing-library/react";
+import { describe, expect, it, vi } from "vitest";
+
+import { Wrapper } from "src/utils/Wrapper";
+
+import ImportVariablesForm from "./ImportVariablesForm";
+
+const { mutate, setError } = vi.hoisted(() => ({ mutate: vi.fn(), setError:
vi.fn() }));
+
+vi.mock("src/queries/useImportVariables", () => ({
+ useImportVariables: () => ({ error: undefined, isPending: false, mutate,
setError }),
+}));
+
+const uploadVariables = (content: string) => {
+ const { container } = render(<ImportVariablesForm onClose={vi.fn()} />, {
wrapper: Wrapper });
+ const input = container.querySelector("input[type=file]");
+
+ if (input === null) {
+ throw new Error("File input not found");
+ }
+ fireEvent.input(input, {
+ target: { files: [new File([content], "variables.json", { type:
"application/json" })] },
+ });
+};
+
+describe("ImportVariablesForm", () => {
+ it.each(['["example"]', '"example"', "1", "true", "null"])(
+ "rejects a non-object JSON document: %s",
+ async (content) => {
+ uploadVariables(content);
+
+ await waitFor(() =>
+ expect(setError).toHaveBeenCalledWith({
+ body: { detail: "variables.import.errorParsingJsonFile" },
+ }),
+ );
+ expect(screen.getByRole("button", { name: "variables.import.button"
})).toBeDisabled();
+ expect(mutate).not.toHaveBeenCalled();
+ },
+ );
+
+ it("imports a dictionary without changing its variable values", async () => {
+ uploadVariables('{"name":"example","config":{"enabled":true}}');
+ const button = screen.getByRole("button", { name:
"variables.import.button" });
+
+ await waitFor(() => expect(button).toBeEnabled());
+ fireEvent.click(button);
+
+ expect(mutate).toHaveBeenCalledWith({
+ requestBody: {
+ actions: [
+ {
+ action: "create",
+ action_on_existence: "fail",
+ entities: [
+ { key: "name", value: "example" },
+ { key: "config", value: { enabled: true } },
+ ],
+ },
+ ],
+ },
+ });
+ });
+});
diff --git
a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
index abaa8393425..ffd5b65b53d 100644
--- a/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
+++ b/airflow-core/src/airflow/ui/src/pages/Variables/ImportVariablesForm.tsx
@@ -78,17 +78,23 @@ const ImportVariablesForm = ({ onClose }:
ImportVariablesFormProps) => {
reader.addEventListener("load", (event) => {
const text = event.target?.result as string;
- try {
- const parsedContent = JSON.parse(text) as Record<string, unknown>;
+ let parsedContent: unknown;
- setFileContent(parsedContent);
+ try {
+ parsedContent = JSON.parse(text);
} catch {
+ parsedContent = undefined;
+ }
+
+ if (parsedContent === null || typeof parsedContent !== "object" ||
Array.isArray(parsedContent)) {
setError({
body: {
detail: translate("variables.import.errorParsingJsonFile"),
},
});
setFileContent(undefined);
+ } else {
+ setFileContent(parsedContent as Record<string, unknown>);
}
setIsParsing(false);