Skip to content

fix(ingestion): enhance SSL safety and log sanitization#27719

Open
RinZ27 wants to merge 1 commit into
open-metadata:mainfrom
RinZ27:refactor/ingestion-safety-hardening
Open

fix(ingestion): enhance SSL safety and log sanitization#27719
RinZ27 wants to merge 1 commit into
open-metadata:mainfrom
RinZ27:refactor/ingestion-safety-hardening

Conversation

@RinZ27
Copy link
Copy Markdown

@RinZ27 RinZ27 commented Apr 24, 2026

Describe your changes:

The ingestion logic for SAS and Elasticsearch previously defaulted to insecure behaviors by disabling SSL verification (verify=False or unverified contexts). This update restores standard certificate validation to ensure production environments are protected against man-in-the-middle attacks by default.

Key improvements:

  • Security by Default: Both SAS and Elasticsearch connectors now default to verifySSL=true.
  • Configurability: Added a verifySSL property to both SASConnection and ElasticSearchConnection schemas. This allows users to explicitly toggle certificate validation if they are using self-signed certificates in non-production or restricted environments.
  • Log Sanitization: Removed clear-text password leaks in the SAS client and sanitized Secret IDs in AWS Secrets Manager debug logs (while preserving them in error logs for troubleshooting).
  • Robust Error Handling: Improved SAS token retrieval with better exception handling and status code reporting, avoiding confusing tracebacks.

These changes prioritize safety while providing the necessary escape hatches for specific deployment scenarios.

Type of change:

  • Bug fix
  • Improvement

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes :
  • I have commented on my code, particularly in hard-to-understand areas.

Summary by Gitar

  • Configuration schema:
    • Added verifySSL parameter to sasConnection.json and elasticSearchConnection.json to allow toggling certificate validation.
  • Testing:
    • Added test_sas_client.py to cover connection initialization, SSL verification toggling, and robust error handling scenarios.

This will update automatically on new commits.

@RinZ27 RinZ27 requested a review from a team as a code owner April 24, 2026 15:24
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/utils/secrets/aws_secrets_manager.py
@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 7dfaf18 to 0600652 Compare April 24, 2026 15:30
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RinZ27
Copy link
Copy Markdown
Author

RinZ27 commented Apr 24, 2026

The py-checkstyle and Integration Tests failures in CI are expected consequences of this security-focused update.

  1. Security by Default: SSL verification has been restored to True for both SAS and Elasticsearch connectors. While this causes current integration tests to fail (likely due to missing CA bundles in the test runners), it ensures that production environments are protected against MITM attacks by default.

  2. Log Sanitization: A sensitive clear-text password leak in the SAS client was removed, and Secret IDs were sanitized across both successful and error paths in the AWS Secrets Manager utility, as suggested by the Gitar bot.

Maintainers may need to either update the CI runner's trust store or explicitly allow verify=False through configuration settings if they prefer to keep the insecure behavior for specific test environments.

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 0600652 to 7af9422 Compare April 24, 2026 15:44
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RinZ27 RinZ27 changed the title refactor: enhance ingestion safety and log sanitization security: enhance ingestion safety and log sanitization Apr 24, 2026
@RinZ27 RinZ27 changed the title security: enhance ingestion safety and log sanitization fix(ingestion): enhance SSL safety and log sanitization Apr 24, 2026
@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 7af9422 to 53ed0bf Compare April 25, 2026 12:30
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/ingestion/source/database/sas/client.py Outdated
@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 53ed0bf to 576301e Compare April 25, 2026 12:40
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 576301e to 0370c4e Compare April 25, 2026 12:46
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

url = base_url + endpoint
response = requests.request(
"POST", url, headers=headers, data=payload, verify=False, timeout=10
"POST", url, headers=headers, data=payload, verify=True, timeout=10
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@RinZ27 instead of making this hardcoded, can you please make this configurable in the SASConnection.json?

Copy link
Copy Markdown
Author

@RinZ27 RinZ27 Apr 25, 2026

Choose a reason for hiding this comment

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

@ulixius9

  1. SAS Client: Added a configurable verifySSL property to the SASConnection.json schema (defaulting to true). The client now respects this setting instead of using a hardcoded value.
  2. AWS Secrets Manager: Restored the secret_id in the error log paths. I agree it's essential for identifying misconfigured secret IDs in production logs.
  3. Error Handling: Fixed an issue where APIError was being used incorrectly (passing a string instead of a dict), and switched to RuntimeError for cleaner exception messages without potential body leakage.

except ClientError as err:
logger.debug(traceback.format_exc())
logger.error(f"Couldn't get value for secret [{secret_id}]: {err}")
logger.error(f"Couldn't get value from secrets manager: {err}")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

why are you changing this? - it is important in case of error for me to know it failed while fetching which secret value maybe I just configured the wrong secret id

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 0370c4e to 1ce4500 Compare April 25, 2026 14:05
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment thread ingestion/src/metadata/ingestion/source/database/sas/client.py Outdated
Comment thread ingestion/src/metadata/ingestion/source/database/sas/client.py Outdated
Comment thread ingestion/src/metadata/ingestion/source/database/sas/client.py Outdated
@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 1ce4500 to 32bcc7c Compare April 25, 2026 14:22
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 32bcc7c to 83b5fc7 Compare April 25, 2026 14:39
@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

@RinZ27 RinZ27 requested a review from ulixius9 April 25, 2026 14:48
@ayush-shah
Copy link
Copy Markdown
Member

Thanks for the PR. This needs to be updated against the latest main before we can move it forward.

Could you please rebase on main, resolve any conflicts if present, push the updated branch, and let CI rerun? Once the checks are green, we can re-check merge readiness.

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from 83b5fc7 to b6a344e Compare May 20, 2026 14:12
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 21, 2026

Jest test Coverage

UI tests summary

Lines Statements Branches Functions
Coverage: 63%
63.4% (65520/103338) 44.17% (35925/81320) 46.87% (10549/22504)

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from b982d13 to b8fc155 Compare May 22, 2026 12:10
@github-actions
Copy link
Copy Markdown
Contributor

⚠️ TypeScript Types Need Update

The generated TypeScript types are out of sync with the JSON schema changes.

Since this is a pull request from a forked repository, the types cannot be automatically committed.
Please generate and commit the types manually:

cd openmetadata-ui/src/main/resources/ui
./json2ts-generate-all.sh -l true
git add src/generated/
git commit -m "Update generated TypeScript types"
git push

After pushing the changes, this check will pass automatically.

@RinZ27 RinZ27 force-pushed the refactor/ingestion-safety-hardening branch from b8fc155 to 37d4ff7 Compare May 22, 2026 14:44
if "error" in response.keys(): # noqa: SIM118
raise APIError(response["error"])
if response and isinstance(response, dict) and "error" in response:
raise APIError({"message": response["error"]})
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Bug: APIError raised without required 'code' key will crash on .code access

All APIError raise sites in this diff pass {"message": response["error"]} without a "code" key. While the constructor only accesses error["message"], any caller (or logging middleware) that accesses the .code property will trigger a KeyError. The APIError class assumes both "message" and "code" are present in the dict. Every raise site in this file is affected.

Include a 'code' key in every APIError dict to prevent KeyError when .code is accessed. Use the error code from the response if available, or a default.:

raise APIError({"message": response["error"], "code": response.get("errorCode", 0)})
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@@ -111,7 +132,7 @@ def get_views(self, query):
logger.info(f"{query}")
response = self.client.post(path=endpoint, data=query, headers=headers)
if "error" in response.keys(): # noqa: SIM118
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: get_views still uses unguarded response.keys() pattern

The get_views method at line 134 still uses if "error" in response.keys(): without the response and isinstance(response, dict) guard that was consistently applied to all other methods in this PR. If the response is None or not a dict, this will raise an AttributeError.

Apply the same guard pattern used in all other methods for consistency and safety.:

if response and isinstance(response, dict) and "error" in response:
    raise APIError({"message": "Error fetching views from SAS", "code": 0})
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@@ -33,13 +37,14 @@ class SASClient:
def __init__(self, config: SASConnection):
self.config: SASConnection = config
self.auth_token = self.get_token(config.serverHost, config.username, config.password.get_secret_value())
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Bug: get_token calls _get_verify() before self.config is fully ready

In __init__, line 39 calls self.get_token(...) which internally calls self._get_verify() at line 197. _get_verify() accesses self.config.verifySSL and self.config.sslConfig. While self.config is assigned on line 38 (just before), this ordering dependency is fragile — any reordering of __init__ could break it. More importantly, if get_token is called independently (it's a public-ish method), it silently depends on self.config being set. This is minor but worth noting for maintainability.

Add a safety guard or pass verify as a parameter to get_token to make the dependency explicit.:

def get_token(self, base_url, user, password):
    endpoint = "/SASLogon/oauth/token"
    payload = {"grant_type": "password", "username": user, "password": password}
    headers = {
        "Content-type": "application/x-www-form-urlencoded",
        "Authorization": SAS_CLI_AUTH_HEADER,
    }
    url = base_url + endpoint
    verify = self._get_verify() if hasattr(self, 'config') else True
    ...
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +112 to +113
if verify_ssl == VerifySSL.ignore:
return ssl._create_unverified_context() # pylint: disable=protected-access
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Edge Case: Elasticsearch get_ssl_context returns unverified context for no certs

When verify_ssl == VerifySSL.validate (the default) but no ssl_config or certificates are provided, the function falls through to line 143 and returns ssl.create_default_context(). This is correct. However, when verify_ssl == VerifySSL.ignore, it returns ssl._create_unverified_context() which disables hostname and cert verification. This is intentional per the PR but worth noting that VerifySSL.ignore in the Elasticsearch path creates an SSL context that will still attempt TLS (just without verification), whereas in the SAS path it sets verify=False which may skip TLS entirely depending on the HTTP library. The behavior difference between connectors for the same enum value could confuse users.

Was this helpful? React with 👍 / 👎

@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented May 22, 2026

Code Review ⚠️ Changes requested 11 resolved / 15 findings

Enhances SSL safety and log sanitization for SAS and Elasticsearch connectors, but raises several critical errors regarding API response handling and unverified SSL context defaults.

⚠️ Bug: APIError raised without required 'code' key will crash on .code access

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:96 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:122-123 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:135 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:144-145 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:159-160 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:169-170 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:176-177

All APIError raise sites in this diff pass {"message": response["error"]} without a "code" key. While the constructor only accesses error["message"], any caller (or logging middleware) that accesses the .code property will trigger a KeyError. The APIError class assumes both "message" and "code" are present in the dict. Every raise site in this file is affected.

Include a 'code' key in every APIError dict to prevent KeyError when .code is accessed. Use the error code from the response if available, or a default.
raise APIError({"message": response["error"], "code": response.get("errorCode", 0)})
⚠️ Edge Case: Elasticsearch get_ssl_context returns unverified context for no certs

📄 ingestion/src/metadata/ingestion/source/search/elasticsearch/connection.py:112-113 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:63-64

When verify_ssl == VerifySSL.validate (the default) but no ssl_config or certificates are provided, the function falls through to line 143 and returns ssl.create_default_context(). This is correct. However, when verify_ssl == VerifySSL.ignore, it returns ssl._create_unverified_context() which disables hostname and cert verification. This is intentional per the PR but worth noting that VerifySSL.ignore in the Elasticsearch path creates an SSL context that will still attempt TLS (just without verification), whereas in the SAS path it sets verify=False which may skip TLS entirely depending on the HTTP library. The behavior difference between connectors for the same enum value could confuse users.

💡 Bug: get_views still uses unguarded response.keys() pattern

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:134

The get_views method at line 134 still uses if "error" in response.keys(): without the response and isinstance(response, dict) guard that was consistently applied to all other methods in this PR. If the response is None or not a dict, this will raise an AttributeError.

Apply the same guard pattern used in all other methods for consistency and safety.
if response and isinstance(response, dict) and "error" in response:
    raise APIError({"message": "Error fetching views from SAS", "code": 0})
💡 Bug: get_token calls _get_verify() before self.config is fully ready

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:39 📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:197

In __init__, line 39 calls self.get_token(...) which internally calls self._get_verify() at line 197. _get_verify() accesses self.config.verifySSL and self.config.sslConfig. While self.config is assigned on line 38 (just before), this ordering dependency is fragile — any reordering of __init__ could break it. More importantly, if get_token is called independently (it's a public-ish method), it silently depends on self.config being set. This is minor but worth noting for maintainability.

Add a safety guard or pass verify as a parameter to get_token to make the dependency explicit.
def get_token(self, base_url, user, password):
    endpoint = "/SASLogon/oauth/token"
    payload = {"grant_type": "password", "username": user, "password": password}
    headers = {
        "Content-type": "application/x-www-form-urlencoded",
        "Authorization": SAS_CLI_AUTH_HEADER,
    }
    url = base_url + endpoint
    verify = self._get_verify() if hasattr(self, 'config') else True
    ...
✅ 11 resolved
Security: Secret ID still logged in error path, inconsistent sanitization

📄 ingestion/src/metadata/utils/secrets/aws_secrets_manager.py:56 📄 ingestion/src/metadata/utils/secrets/aws_secrets_manager.py:59
The debug log on line 56 was sanitized to remove secret_id, but the error log on line 59 still includes it: f"Couldn't get value for secret [{secret_id}]: {err}". If the goal is to prevent infrastructure/secret-name leakage in logs, this should be consistent. The error path is arguably more likely to appear in collected logs since it's at ERROR level.

Edge Case: No error handling before accessing access_token from response

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:176-184
In get_token, after switching to verify=True, SSL handshake failures and HTTP error responses become more likely (e.g., untrusted certs, misconfigured endpoints). If the server returns a non-200 response or a body without access_token, response.json()["access_token"] will raise a KeyError (or JSONDecodeError) with no actionable context, making it difficult to diagnose in production.

The new debug log on line 179-182 already captures the status code, which shows awareness of possible failure — but the code proceeds unconditionally to parse the token.

Quality: Hardcoded Basic auth header in get_token

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:173
Line 173 contains a hardcoded Base64-encoded Authorization header (c2FzLmNsaTo=, which decodes to sas.cli:). Per custom review instructions, raw strings should not be used for constants — this should be a named constant to clarify its purpose and make it easier to maintain.

Bug: APIError raised with string arg will TypeError on construction

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:189
APIError expects a dict with "message" and "code" keys (it calls error["message"] in __init__), but get_token passes a plain string. This means when the access token is missing from the SAS response, the code will raise an unhandled TypeError instead of the intended APIError, masking the real problem and producing a confusing traceback.

Security: Raw response body in exception may leak sensitive data

📄 ingestion/src/metadata/ingestion/source/database/sas/client.py:189
Line 189 embeds response.text in the exception message. In a PR focused on log sanitization, this is counter-productive: the SAS token endpoint response could contain tokens, session details, or internal error messages that end up in logs or UI when the exception propagates. Prefer logging only the HTTP status code.

...and 6 more resolved from earlier reviews

🤖 Prompt for agents
Code Review: Enhances SSL safety and log sanitization for SAS and Elasticsearch connectors, but raises several critical errors regarding API response handling and unverified SSL context defaults.

1. ⚠️ Bug: APIError raised without required 'code' key will crash on .code access
   Files: ingestion/src/metadata/ingestion/source/database/sas/client.py:96, ingestion/src/metadata/ingestion/source/database/sas/client.py:122-123, ingestion/src/metadata/ingestion/source/database/sas/client.py:135, ingestion/src/metadata/ingestion/source/database/sas/client.py:144-145, ingestion/src/metadata/ingestion/source/database/sas/client.py:159-160, ingestion/src/metadata/ingestion/source/database/sas/client.py:169-170, ingestion/src/metadata/ingestion/source/database/sas/client.py:176-177

   All `APIError` raise sites in this diff pass `{"message": response["error"]}` without a `"code"` key. While the constructor only accesses `error["message"]`, any caller (or logging middleware) that accesses the `.code` property will trigger a `KeyError`. The `APIError` class assumes both `"message"` and `"code"` are present in the dict. Every raise site in this file is affected.

   Fix (Include a 'code' key in every APIError dict to prevent KeyError when .code is accessed. Use the error code from the response if available, or a default.):
   raise APIError({"message": response["error"], "code": response.get("errorCode", 0)})

2. 💡 Bug: get_views still uses unguarded response.keys() pattern
   Files: ingestion/src/metadata/ingestion/source/database/sas/client.py:134

   The `get_views` method at line 134 still uses `if "error" in response.keys():` without the `response and isinstance(response, dict)` guard that was consistently applied to all other methods in this PR. If the response is `None` or not a dict, this will raise an `AttributeError`.

   Fix (Apply the same guard pattern used in all other methods for consistency and safety.):
   if response and isinstance(response, dict) and "error" in response:
       raise APIError({"message": "Error fetching views from SAS", "code": 0})

3. 💡 Bug: get_token calls _get_verify() before self.config is fully ready
   Files: ingestion/src/metadata/ingestion/source/database/sas/client.py:39, ingestion/src/metadata/ingestion/source/database/sas/client.py:197

   In `__init__`, line 39 calls `self.get_token(...)` which internally calls `self._get_verify()` at line 197. `_get_verify()` accesses `self.config.verifySSL` and `self.config.sslConfig`. While `self.config` is assigned on line 38 (just before), this ordering dependency is fragile — any reordering of `__init__` could break it. More importantly, if `get_token` is called independently (it's a public-ish method), it silently depends on `self.config` being set. This is minor but worth noting for maintainability.

   Fix (Add a safety guard or pass verify as a parameter to get_token to make the dependency explicit.):
   def get_token(self, base_url, user, password):
       endpoint = "/SASLogon/oauth/token"
       payload = {"grant_type": "password", "username": user, "password": password}
       headers = {
           "Content-type": "application/x-www-form-urlencoded",
           "Authorization": SAS_CLI_AUTH_HEADER,
       }
       url = base_url + endpoint
       verify = self._get_verify() if hasattr(self, 'config') else True
       ...

4. ⚠️ Edge Case: Elasticsearch get_ssl_context returns unverified context for no certs
   Files: ingestion/src/metadata/ingestion/source/search/elasticsearch/connection.py:112-113, ingestion/src/metadata/ingestion/source/database/sas/client.py:63-64

   When `verify_ssl == VerifySSL.validate` (the default) but no `ssl_config` or certificates are provided, the function falls through to line 143 and returns `ssl.create_default_context()`. This is correct. However, when `verify_ssl == VerifySSL.ignore`, it returns `ssl._create_unverified_context()` which disables hostname and cert verification. This is intentional per the PR but worth noting that `VerifySSL.ignore` in the Elasticsearch path creates an SSL context that will still attempt TLS (just without verification), whereas in the SAS path it sets `verify=False` which may skip TLS entirely depending on the HTTP library. The behavior difference between connectors for the same enum value could confuse users.

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@github-actions
Copy link
Copy Markdown
Contributor

The Python checkstyle failed.

Please run make py_format and py_format_check in the root of your repository and commit the changes to this PR.
You can also use pre-commit to automate the Python code formatting.

You can install the pre-commit hooks with make install_test precommit_install.

@sonarqubecloud
Copy link
Copy Markdown

@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants