Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions frontend/packages/helm-plugin/locales/en/helm-plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
"HelmChartRepository": "HelmChartRepository",
"Hide advanced options": "Hide advanced options",
"Home page": "Home page",
"HTTP is unencrypted. Credentials may be exposed, and downloaded content may have been tampered with in transit. Use HTTPS whenever possible.": "HTTP is unencrypted. Credentials may be exposed, and downloaded content may have been tampered with in transit. Use HTTPS whenever possible.",
"Install": "Install",
"Install Helm chart from Helm registry.": "Install Helm chart from Helm registry.",
"Install Helm chart from URL": "Install Helm chart from URL",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
getDefaultResource,
convertToForm,
convertToHelmChartRepository,
tryHttpsUpgrade,
} from './helmchartrepository-create-utils';
import { validationSchema } from './helmchartrepository-validation-utils';

Expand Down Expand Up @@ -110,6 +111,12 @@ const CreateHelmChartRepository: FC<CreateHelmChartRepositoryProps> = ({
HelmChartRepositoryRes = convertToHelmChartRepository(values.formData, namespace);
}

const currentUrl = HelmChartRepositoryRes.spec?.connectionConfig?.url;
const upgradedUrl = await tryHttpsUpgrade(currentUrl);
Comment on lines +114 to +115

@coderabbitai coderabbitai Bot Jul 30, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show file sizes and relevant excerpts with line numbers
wc -l frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepository.tsx
sed -n '1,220p' frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepository.tsx

printf '\n--- helper search ---\n'
rg -n "tryHttpsUpgrade|safeLoad|connectionConfig?.url|setStatus|resourceCall" frontend/packages/helm-plugin -S

Repository: openshift/console

Length of output: 14649


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the helper implementation and any related types if present
helper_file=$(rg -l "function tryHttpsUpgrade|const tryHttpsUpgrade|export .*tryHttpsUpgrade" frontend/packages/helm-plugin -S | head -n 1 || true)
printf 'helper_file=%s\n' "${helper_file:-<none>}"
if [ -n "${helper_file:-}" ]; then
  wc -l "$helper_file"
  sed -n '1,220p' "$helper_file"
fi

printf '\n--- type definitions search ---\n'
rg -n "type HelmChartRepositoryType|interface HelmChartRepositoryType|connectionConfig" frontend/packages/helm-plugin -S

Repository: openshift/console

Length of output: 9023


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# If the helper uses string operations, inspect for explicit guards
rg -n "startsWith\\(|typeof .*url|isString|URL\\(" frontend/packages/helm-plugin/src -S

Repository: openshift/console

Length of output: 2336


Validate spec.connectionConfig.url before tryHttpsUpgrade. safeLoad(values.yamlData) as HelmChartRepositoryType doesn’t enforce runtime shape, so a non-string URL can hit .startsWith() and abort submission before the form sets an error.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepository.tsx`
around lines 114 - 115, Validate that
HelmChartRepositoryRes.spec?.connectionConfig?.url is a string before passing it
to tryHttpsUpgrade in the submission flow. If it is missing or invalid, set the
form error through the existing error-handling path and stop processing; only
invoke tryHttpsUpgrade for a valid URL.

Source: Path instructions

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a valid issue. tryHttpsUpgrade already handles missing/invalid input safely: line 78 checks if (!httpUrl?.startsWith('http://')) and returns null for undefined, null, empty string, or any non-HTTP URL. There's no crash path. Adding a separate string-type guard at the call site would be redundant defensive code.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

helper=$(fd -a '^helmchartrepository-create-utils\.ts$' frontend/packages/helm-plugin | head -n 1)
types=$(fd -a '^helm-types\.ts$' frontend/packages/helm-plugin | head -n 1)

printf '%s\n' '--- utility outline ---'
ast-grep outline "$helper" --items all --view expanded
printf '%s\n' '--- tryHttpsUpgrade implementation ---'
rg -n -A30 -B5 'tryHttpsUpgrade' "$helper"

printf '%s\n' '--- relevant type declarations ---'
rg -n -A20 -B5 'HelmChartRepositoryType|connectionConfig|url' "$types"

Length of output: 4959


@sowmya-sl, the issue is still valid.

httpUrl?.startsWith('http://') safely handles null and undefined. It does not safely handle non-null, non-string YAML values.

For example, this valid YAML syntax passes through safeLoad:

spec:
  connectionConfig:
    url: 123

safeLoad(...) as HelmChartRepositoryType only changes the TypeScript view. It does not validate the runtime value. In this case, tryHttpsUpgrade(123) evaluates 123?.startsWith(...) and throws because 123 has no startsWith method.

The helper should check typeof httpUrl === 'string' before calling startsWith, or the submission flow should validate the value before calling the helper.

You are interacting with an AI system.

if (upgradedUrl) {
HelmChartRepositoryRes.spec.connectionConfig.url = upgradedUrl;
}

const resourceCall = isEditForm
? k8sUpdateResource({
model: modelFor(referenceFor(HelmChartRepositoryRes)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { FC } from 'react';
import { useMemo } from 'react';
import { TextInputTypes } from '@patternfly/react-core';
import { TextInputTypes, Alert } from '@patternfly/react-core';
import type { FormikValues } from 'formik';
import { useFormikContext } from 'formik';
import * as fuzzy from 'fuzzysearch';
Expand Down Expand Up @@ -142,6 +142,18 @@ const CreateHelmChartRepositoryFormEditor: FC<CreateHelmChartRepositoryFormEdito
helpText={t('Helm Chart repository URL.')}
required
/>
{formData.repoUrl?.startsWith('http://') && (
<>
<Alert
variant="warning"
isInline
isPlain
title={t(
'HTTP is unencrypted. Credentials may be exposed, and downloaded content may have been tampered with in transit. Use HTTPS whenever possible.',
)}
/>
</>
)}
<ExpandCollapse
textExpanded={t('Hide advanced options')}
textCollapsed={t('Show advanced options')}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { tryHttpsUpgrade } from '../helmchartrepository-create-utils';

describe('tryHttpsUpgrade', () => {
const originalFetch = global.fetch;

afterEach(() => {
global.fetch = originalFetch;
jest.useRealTimers();
});

it('should return https URL when server responds with 200', async () => {
global.fetch = jest.fn().mockResolvedValue(new Response(null, { status: 200 }));
const result = await tryHttpsUpgrade('http://example.com/repo');
expect(result).toBe('https://example.com/repo');
expect(global.fetch).toHaveBeenCalledWith(
'https://example.com/repo',
expect.objectContaining({ method: 'HEAD' }),
);
});

it('should return null when server responds with a non-OK status', async () => {
global.fetch = jest.fn().mockResolvedValue(new Response(null, { status: 404 }));
const result = await tryHttpsUpgrade('http://example.com/repo');
expect(result).toBeNull();
});

it('should return null when server responds with 500', async () => {
global.fetch = jest.fn().mockResolvedValue(new Response(null, { status: 500 }));
const result = await tryHttpsUpgrade('http://example.com/repo');
expect(result).toBeNull();
});

it('should return null when server does not support HTTPS', async () => {
global.fetch = jest.fn().mockRejectedValue(new TypeError('Failed to fetch'));
const result = await tryHttpsUpgrade('http://example.com/repo');
expect(result).toBeNull();
});

it('should return null for HTTPS URLs', async () => {
const result = await tryHttpsUpgrade('https://example.com');
expect(result).toBeNull();
});

it('should return null for OCI URLs', async () => {
const result = await tryHttpsUpgrade('oci://registry.io/chart');
expect(result).toBeNull();
});

it('should return null for null or undefined input', async () => {
expect(await tryHttpsUpgrade(null)).toBeNull();
expect(await tryHttpsUpgrade(undefined)).toBeNull();
});

it('should return null when fetch times out', async () => {
global.fetch = jest.fn().mockImplementation(
(_url, options) =>
new Promise((_resolve, reject) => {
options.signal.addEventListener('abort', () => reject(new DOMException('Aborted')));
}),
);
jest.useFakeTimers();
const promise = tryHttpsUpgrade('http://slow-server.com/repo');
jest.advanceTimersByTime(3000);
const result = await promise;
expect(result).toBeNull();
});
Comment thread
coderabbitai[bot] marked this conversation as resolved.
});
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ export const convertToHelmChartRepository = (
return newResource;
};

const HTTPS_PROBE_TIMEOUT_MS = 3000;

export const tryHttpsUpgrade = async (httpUrl: string): Promise<string | null> => {
if (!httpUrl?.startsWith('http://')) {
return null;
}
const httpsUrl = httpUrl.replace(/^http:\/\//, 'https://');
Comment on lines +77 to +81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

for f in \
  frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-create-utils.ts \
  frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepositoryFormEditor.tsx \
  frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/__tests__/helmchartrepository-create-utils-https-upgrade.spec.ts
do
  echo "### $f"
  wc -l "$f"
  echo
done

echo "### utils excerpt"
sed -n '1,180p' frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-create-utils.ts

echo "### form excerpt"
sed -n '120,190p' frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepositoryFormEditor.tsx

echo "### test excerpt"
sed -n '1,220p' frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/__tests__/helmchartrepository-create-utils-https-upgrade.spec.ts

Repository: openshift/console

Length of output: 9159


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path
files = [
    Path('frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-create-utils.ts'),
    Path('frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepositoryFormEditor.tsx'),
    Path('frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/__tests__/helmchartrepository-create-utils-https-upgrade.spec.ts'),
]
for p in files:
    text = p.read_text()
    print(f"\n### {p}")
    for i, line in enumerate(text.splitlines(), 1):
        if 'tryHttpsUpgrade' in line or "startsWith('http://')" in line or 'Alert' in line or 'HTTP is unencrypted' in line or 'https-upgrade' in line:
            start = max(1, i-8)
            end = min(len(text.splitlines()), i+20)
            for j in range(start, end+1):
                print(f"{j:4}: {text.splitlines()[j-1]}")
            break
PY

Repository: openshift/console

Length of output: 3792


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "startsWith\\('http://'\)|startsWith\\(\"http://\"\\)|tryHttpsUpgrade|HTTP is unencrypted" \
  frontend/packages/helm-plugin/src/components/forms/HelmChartRepository

Repository: openshift/console

Length of output: 3032


Normalize HTTP scheme checks

startsWith('http://') misses case variants like HTTP://repo.example, so both the warning and HTTPS upgrade path can be skipped. Reuse a normalized scheme check in helmchartrepository-create-utils.ts and CreateHelmChartRepositoryFormEditor.tsx, and add an uppercase-scheme test case.

📍 Affects 3 files
  • frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-create-utils.ts#L77-L81 (this comment)
  • frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepositoryFormEditor.tsx#L145-L156
  • frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/__tests__/helmchartrepository-create-utils-https-upgrade.spec.ts#L26-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/helmchartrepository-create-utils.ts`
around lines 77 - 81, Normalize the HTTP scheme comparison case-insensitively so
tryHttpsUpgrade handles uppercase and mixed-case HTTP URLs while preserving the
existing HTTPS conversion behavior. Apply the same normalized check in
frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/CreateHelmChartRepositoryFormEditor.tsx
at lines 145-156 so warnings and upgrades remain consistent. Extend
frontend/packages/helm-plugin/src/components/forms/HelmChartRepository/__tests__/helmchartrepository-create-utils-https-upgrade.spec.ts
at lines 26-39 with an uppercase-scheme test case.

const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), HTTPS_PROBE_TIMEOUT_MS);
try {
const response = await fetch(httpsUrl, { method: 'HEAD', signal: controller.signal });
return response.ok ? httpsUrl : null;
} catch {
return null;
} finally {
clearTimeout(timeoutId);
}
};

export const getDefaultResource = (
namespace: string,
kindRef?: K8sResourceKindReference,
Expand Down