diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..9921f22 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @miroslavpojer @tmikula-dev diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..07fc71a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,82 @@ +name: Bug Report +description: Create a report to help us improve +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + Please supply complete reproduction details to speed up resolution. + + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. + placeholder: Tell us what went wrong. + validations: + required: true + + - type: textarea + id: reproduction-steps + attributes: + label: Steps to Reproduce + description: Steps to reproduce the behavior OR commands run + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. Enter value '...' + 4. See error + validations: + required: true + + - type: textarea + id: expected-state + attributes: + label: Expected state + description: A clear and concise description of what you expected to happen + placeholder: What should have happened? + validations: + required: true + + - type: dropdown + id: severity + attributes: + label: Impact / Severity + description: Level of impact + options: + - Blocker + - High + - Medium + - Low + validations: + required: true + + - type: textarea + id: evidence + attributes: + label: Attachments / Evidence + description: Logs, screenshots, traces. + placeholder: Paste relevant excerpts. + validations: + required: false + + - type: textarea + id: os-browser-version + attributes: + label: Desktop + description: What are your OS, browser, and version? + placeholder: | + OS: e.g., Windows 10, macOS 11.2, Ubuntu 20.04 + Browser: e.g., Chrome, Safari, Firefox + Version: e.g., 22 + validations: + required: false + + - type: textarea + id: related + attributes: + label: Related / References + description: Link other issues/PRs. + placeholder: "#456, #789" + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/devops_task.yml b/.github/ISSUE_TEMPLATE/devops_task.yml new file mode 100644 index 0000000..e7777bc --- /dev/null +++ b/.github/ISSUE_TEMPLATE/devops_task.yml @@ -0,0 +1,17 @@ +name: DevOps Task +description: Issue template for a task about setting up the project. Usually not touching the codebase. +labels: ["infrastructure"] +body: + - type: markdown + attributes: + value: | + A clear and concise description of the task to be done. + + - type: textarea + id: task-description + attributes: + label: The task + description: Short description of the task + placeholder: Describe what needs to be done + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/documentation_task.yml b/.github/ISSUE_TEMPLATE/documentation_task.yml new file mode 100644 index 0000000..4fc01c6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/documentation_task.yml @@ -0,0 +1,53 @@ +name: Documentation +description: Update or create technical or operational documentation +labels: ["documentation"] +body: + - type: markdown + attributes: + value: | + Provide clear purpose and scope for the documentation update. + + - type: input + id: summary + attributes: + label: Summary + description: Short description of the documentation update. + placeholder: e.g. Add onboarding guide for new microservice + validations: + required: true + + - type: textarea + id: current-state + attributes: + label: Current State (If Updating) + description: Briefly describe existing content or gap. + placeholder: Existing runbook lacks failure recovery steps. + validations: + required: false + + - type: textarea + id: proposed-changes + attributes: + label: Proposed Changes + description: Key additions, removals, or restructures. + placeholder: Add section on X; remove deprecated Y. + validations: + required: true + + - type: textarea + id: dependencies + attributes: + label: Dependencies / References + description: Link related issues, PRs, external standards. + placeholder: "#123, ADR-7, compliance doc link." + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information about the documentation update here. + placeholder: Add any other relevant context. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/epic_task.yml b/.github/ISSUE_TEMPLATE/epic_task.yml new file mode 100644 index 0000000..47c156f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/epic_task.yml @@ -0,0 +1,47 @@ +name: Epic +description: A larger task consisting of more deliverables. +labels: ["epic"] +body: + - type: markdown + attributes: + value: | + Use this template for large initiatives with multiple child issues. + + - type: textarea + id: background + attributes: + label: Background + description: High-level narrative and intended outcomes. + placeholder: Platform modernization initiative. + validations: + required: true + + - type: textarea + id: goals + attributes: + label: Goals + description: Bullet list of measurable goals. + placeholder: What do we want to accomplish? + validations: + required: true + + - type: textarea + id: subtasks + attributes: + label: Subtasks + description: List of planned child issues. + placeholder: | + - [ ] #123 Subtask 1 + - [ ] #223 Subtask 2 + - [ ] #323 Subtask 3 + validations: + required: false + + - type: textarea + id: references + attributes: + label: Related / References + description: Docs, ADRs, previous epics links. + placeholder: ADR-12 link, design doc. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..66331ff --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,65 @@ +name: Feature Request +description: Suggest an idea for this project. +labels: ["enhancement"] +body: + - type: markdown + attributes: + value: | + Thank you for suggesting an improvement. Please provide clear, actionable detail. + + - type: textarea + id: feature-description + attributes: + label: Feature Description + description: A clear and concise description of what you want to happen. + placeholder: Describe the feature you'd like to see. + validations: + required: true + + - type: textarea + id: problem + attributes: + label: Problem / Opportunity + description: What user/business problem does this solve? Who benefits? + placeholder: Current pain, limitation, or missed opportunity. + validations: + required: true + + - type: textarea + id: acceptance + attributes: + label: Acceptance Criteria + description: Clear, testable outcomes. Use bullet points. + placeholder: | + 1. User can... + 2. API returns... + 3. Performance within... + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: High-level approach. Mention alternatives if considered. + placeholder: Describe the direction without full implementation detail. + validations: + required: false + + - type: textarea + id: dependencies + attributes: + label: Dependencies / Related + description: Link related issues, epics, or external docs. + placeholder: "#123, #456, ADR link" + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information or screenshots about the feature request here + placeholder: Add any other relevant context or screenshots + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/operative_task.yml b/.github/ISSUE_TEMPLATE/operative_task.yml new file mode 100644 index 0000000..14fd100 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/operative_task.yml @@ -0,0 +1,55 @@ +name: Operative / Management Task +description: General-purpose template for operational, management, and non-development tasks +labels: ["operative"] +body: + - type: markdown + attributes: + value: | + Use this flexible template for operational, management, planning, or administrative tasks. + + - type: input + id: summary + attributes: + label: Task Summary + description: Brief one-line description of the task + placeholder: e.g., Organize Q2 planning meeting, Update team guidelines. + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description + description: Detailed description of what needs to be done and why + placeholder: Provide context, background, and objectives for this task + validations: + required: true + + - type: textarea + id: objectives + attributes: + label: Objectives / Goals + description: What are we trying to achieve? + placeholder: | + - Objective 1 + - Objective 2 + validations: + required: false + + - type: input + id: deadline + attributes: + label: Deadline / Target Date + description: When should this be completed? + placeholder: YYYY-MM-DD or "End of Sprint 5" + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other relevant information, links, or attachments + placeholder: Links to documents, previous discussions, related materials + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/pointer.yml b/.github/ISSUE_TEMPLATE/pointer.yml new file mode 100644 index 0000000..e879cdf --- /dev/null +++ b/.github/ISSUE_TEMPLATE/pointer.yml @@ -0,0 +1,71 @@ +name: Pointer +description: Track external work referenced across repositories +labels: ["pointer"] +body: + - type: markdown + attributes: + value: | + Use pointer issues to track external work (other repos, third-party tasks) impacting this domain. + + - type: input + id: summary + attributes: + label: Summary + description: Short description of external work. + placeholder: e.g. Migration of auth service in repo X + validations: + required: true + + - type: textarea + id: references + attributes: + label: Source / External References + description: List external issues/PRs/links (one per line). + placeholder: | + repo-a#123 + repo-b#456 + Design doc link + validations: + required: true + + - type: dropdown + id: relationship + attributes: + label: Relationship Type + description: Nature of linkage + options: + - Dependency + - Blocking + - Follow-up + - Mirroring + - Audit + - Observability + validations: + required: true + + - type: textarea + id: reason + attributes: + label: Reason for Tracking + description: Why visibility matters (release, compliance, coordination). + placeholder: Release impact; compliance trace; dependency risk. + validations: + required: true + + - type: textarea + id: closure + attributes: + label: Closure Conditions + description: When can this pointer be closed? + placeholder: External PR merged + deployed + docs updated. + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other information about the pointer here. + placeholder: Add any other relevant context. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/spike_task.yml b/.github/ISSUE_TEMPLATE/spike_task.yml new file mode 100644 index 0000000..f5fd388 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/spike_task.yml @@ -0,0 +1,60 @@ +name: Spike +description: Issue template for spikes, research and investigation tasks. +labels: ["spike"] +body: + - type: markdown + attributes: + value: | + Use this form for research/investigation tasks. Keep scope tight. + + - type: textarea + id: background + attributes: + label: Background + description: Describe the problem or topic we need to understand. + placeholder: Tell us what you need to research or investigate. + validations: + required: true + + - type: textarea + id: questions + attributes: + label: Questions To Answer + description: List the key questions that need to be answered + placeholder: | + 1. Question one + 2. Question two + 3. Question three + validations: + required: true + + - type: textarea + id: desired-outcome + attributes: + label: Desired Outcome + description: List the desired outcomes of this spike ticket. + placeholder: What should be the result of this investigation? + validations: + required: true + + - type: checkboxes + id: tasks + attributes: + label: Tasks + options: + - label: Questions have been answered or we have a clearer idea of how to get to our goal + - label: Discussion with the team + - label: Documentation + - label: Create recommendations and new implementation tickets + + - type: textarea + id: additional-info + attributes: + label: Additional Info/Resources + description: Optional additional information or resources + placeholder: | + 1. Resource one + 2. Resource two + 3. Resource three + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/technical_debt.yml b/.github/ISSUE_TEMPLATE/technical_debt.yml new file mode 100644 index 0000000..a30adc7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/technical_debt.yml @@ -0,0 +1,96 @@ +name: Technical Debt +description: Track and address technical debt items to improve code quality, maintainability, or architecture. +labels: ["tech debt"] +body: + - type: markdown + attributes: + value: | + Use this template to document technical debt that should be addressed to improve long-term code health. + + - type: textarea + id: debt-description + attributes: + label: Description of Technical Debt + description: What is the current state that needs improvement? + placeholder: Describe the technical debt (outdated dependencies, code smells, architecture issues, etc.) + validations: + required: true + + - type: textarea + id: impact + attributes: + label: Impact of Technical Debt + description: What problems does this debt cause or risk causing? + placeholder: | + - Slows down development + - Increases bug risk + - Makes testing difficult + - Blocks future features + validations: + required: true + + - type: dropdown + id: category + attributes: + label: Category + description: Type of technical debt + options: + - Code Quality / Refactoring + - Architecture / Design + - Dependencies / Libraries + - Documentation + - Testing / Test Coverage + - Performance + - Security + - DevOps / Infrastructure + - Other + validations: + required: true + + - type: dropdown + id: priority + attributes: + label: Priority + description: How urgent is this to address? + options: + - High - Actively causing problems + - Medium - Should be addressed soon + - Low - Nice to have + validations: + required: true + + - type: textarea + id: proposed-solution + attributes: + label: Proposed Solution + description: How should this be addressed? Include alternatives if applicable. + placeholder: Describe the refactoring approach, migration plan, or improvement strategy. + validations: + required: false + + - type: input + id: effort-estimate + attributes: + label: Effort Estimate + description: Rough estimate of work required + placeholder: e.g., 1 day, 1 week, 2 sprints + validations: + required: false + + - type: textarea + id: dependencies + attributes: + label: Dependencies / Related + description: Link related issues, epics, or documentation. + placeholder: "#123, #456, ADR link" + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional Context + description: Any other relevant information, links to discussions, or examples. + placeholder: Add code snippets, links to similar work, or other references. + validations: + required: false diff --git a/.github/workflows/aquasec-scan.yml b/.github/workflows/aquasec-scan.yml index 95d04e2..f0ff121 100644 --- a/.github/workflows/aquasec-scan.yml +++ b/.github/workflows/aquasec-scan.yml @@ -131,4 +131,4 @@ jobs: PROJECT_NUMBER: ${{ inputs.project-number }} PROJECT_ORG: ${{ inputs.project-org }} run: | - org-workflows/security/sync_security_alerts.sh + python3 org-workflows/src/security/sync_security_alerts.py diff --git a/.github/workflows/check_pr_release_notes.yml b/.github/workflows/check_pr_release_notes.yml new file mode 100644 index 0000000..0999a0b --- /dev/null +++ b/.github/workflows/check_pr_release_notes.yml @@ -0,0 +1,46 @@ +# +# Copyright 2026 ABSA Group Limited +# +# 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. +# + +name: Check PR Release Notes + +on: + pull_request: + types: [opened, synchronize, reopened, edited, labeled, unlabeled] + branches: [ master ] + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + check-release-notes: + runs-on: ubuntu-latest + + steps: + - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v5 + with: + python-version: '3.14' + + - name: Check presence of release notes in PR description + uses: AbsaOSS/release-notes-presence-check@8e586b26a5e27f899ee8590a5d988fd4780a3dbf # v0.4.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + github-repository: ${{ github.repository }} + pr-number: ${{ github.event.number }} + title: "## [Rr]elease [Nn]otes" + skip-labels: 'no RN' + skip-placeholders: 'TBD' diff --git a/.github/workflows/check_python.yml b/.github/workflows/check_python.yml new file mode 100644 index 0000000..b8b3036 --- /dev/null +++ b/.github/workflows/check_python.yml @@ -0,0 +1,184 @@ +# +# Copyright 2026 ABSA Group Limited +# +# 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. + +name: Python Check + +on: + pull_request: + types: [ opened, synchronize, reopened ] + push: + branches: [ master ] + workflow_dispatch: + +concurrency: + group: static-python-check-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + security-events: write + +jobs: + detect: + name: Python Changes Detection + runs-on: ubuntu-latest + outputs: + python_changed: ${{ steps.changes.outputs.python_changed }} + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Check if Python files changed + id: changes + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + CHANGED_FILES=$(gh api \ + "repos/${{ github.repository }}/pulls/${{ github.event.pull_request.number }}/files" \ + --jq '.[].filename | select(endswith(".py") or . == "requirements.txt")') + else + CHANGED_FILES=$(git diff --name-only "${{ github.sha }}~1" "${{ github.sha }}" -- '*.py' 'requirements.txt') + fi + + if [[ -n "$CHANGED_FILES" ]]; then + echo "python_changed=true" >> "$GITHUB_OUTPUT" + else + echo "python_changed=false" >> "$GITHUB_OUTPUT" + fi + + pylint-analysis: + name: Pylint Static Code Analysis + needs: detect + if: needs.detect.outputs.python_changed == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + with: + python-version: '3.14' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Analyze code with Pylint + id: analyze-code + run: | + pylint_score=$(pylint $(git ls-files '*.py')| grep 'rated at' | awk '{print $7}' | cut -d'/' -f1) + echo "PYLINT_SCORE=$pylint_score" >> $GITHUB_ENV + + - name: Check Pylint score + run: | + if (( $(echo "$PYLINT_SCORE < 9.5" | bc -l) )); then + echo "Failure: Pylint score is below 9.5 (project score: $PYLINT_SCORE)." + exit 1 + else + echo "Success: Pylint score is above 9.5 (project score: $PYLINT_SCORE)." + fi + + black-check: + name: Black Format Check + needs: detect + if: needs.detect.outputs.python_changed == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + with: + python-version: '3.14' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Check code format with Black + id: check-format + run: black --check $(git ls-files '*.py') + + mypy-check: + name: Mypy Type Check + needs: detect + if: needs.detect.outputs.python_changed == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + with: + python-version: '3.14' + cache: 'pip' + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Check types with Mypy + id: check-types + run: mypy . + + unit-tests: + name: Pytest Unit Tests with Coverage + needs: detect + if: needs.detect.outputs.python_changed == 'true' + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 + with: + python-version: '3.14' + cache: 'pip' + + - name: Install Python dependencies + run: pip install -r requirements.txt + + - name: Check code coverage with Pytest + run: pytest --cov=src -v --cov-fail-under=80 + + noop: + name: No Operation + needs: detect + if: needs.detect.outputs.python_changed != 'true' + runs-on: ubuntu-latest + steps: + - run: echo "No changes in the *.py files — passing." diff --git a/.gitignore b/.gitignore index 6e7c977..e381476 100644 --- a/.gitignore +++ b/.gitignore @@ -10,5 +10,4 @@ target .vscode src/security/alerts*.json src/security/.coverage -security/alerts_aul.json -security/alerts_local.json +.coverage diff --git a/.pylintrc b/.pylintrc new file mode 100644 index 0000000..0457059 --- /dev/null +++ b/.pylintrc @@ -0,0 +1,648 @@ +[MAIN] + +# Analyse import fallback blocks. This can be used to support both Python 2 and +# 3 compatible code, which means that the block might have code that exists +# only in one or another interpreter, leading to false positives when analysed. +analyse-fallback-blocks=no + +# Clear in-memory caches upon conclusion of linting. Useful if running pylint +# in a server-like mode. +clear-cache-post-run=no + +# Load and enable all available extensions. Use --list-extensions to see a list +# all available extensions. +#enable-all-extensions= + +# In error mode, messages with a category besides ERROR or FATAL are +# suppressed, and no reports are done by default. Error mode is compatible with +# disabling specific errors. +#errors-only= + +# Always return a 0 (non-error) status code, even if lint errors are found. +# This is primarily useful in continuous integration scripts. +#exit-zero= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-allow-list= + +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. (This is an alternative name to extension-pkg-allow-list +# for backward compatibility.) +extension-pkg-whitelist= + +# Return non-zero exit code if any of these messages/categories are detected, +# even if score is above --fail-under value. Syntax same as enable. Messages +# specified are enabled, while categories only check already-enabled messages. +fail-on= + +# Specify a score threshold under which the program will exit with error. +fail-under=9.5 + +# Interpret the stdin as a python script, whose filename needs to be passed as +# the module_or_package argument. +#from-stdin= + +# Files or directories to be skipped. They should be base names, not paths. +ignore=CVS + +# Add files or directories matching the regular expressions patterns to the +# ignore-list. The regex matches against paths and can be in Posix or Windows +# format. Because '\\' represents the directory delimiter on Windows systems, +# it can't be used as an escape character. +ignore-paths= + +# Files or directories matching the regular expression patterns are skipped. +# The regex matches against base names, not paths. The default value ignores +# Emacs file locks +ignore-patterns=^\.# + +# List of module names for which member attributes should not be checked and +# will not be imported (useful for modules/projects where namespaces are +# manipulated during runtime and thus existing member attributes cannot be +# deduced by static analysis). It supports qualified module names, as well as +# Unix pattern matching. +ignored-modules= + +# Python code to execute, usually for sys.path manipulation such as +# pygtk.require(). +init-hook='import sys; sys.path.extend(["src", "src/security"])' + +# Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the +# number of processors available to use, and will cap the count on Windows to +# avoid hangs. +jobs=1 + +# Control the amount of potential inferred values when inferring a single +# object. This can help the performance when dealing with large functions or +# complex, nested conditions. +limit-inference-results=100 + +# List of plugins (as comma separated values of python module names) to load, +# usually to register additional checkers. +load-plugins= + +# Pickle collected data for later comparisons. +persistent=yes + +# Resolve imports to .pyi stubs if available. May reduce no-member messages and +# increase not-an-iterable messages. +prefer-stubs=no + +# Minimum Python version to use for version dependent checks. Will default to +# the version used to run pylint. +py-version=3.14 + +# Discover python modules and packages in the file system subtree. +recursive=no + +# Add paths to the list of the source roots. Supports globbing patterns. The +# source root is an absolute path or a path relative to the current working +# directory used to determine a package namespace for modules located under the +# source root. +source-roots= + +# Allow loading of arbitrary C extensions. Extensions are imported into the +# active Python interpreter and may run arbitrary code. +unsafe-load-any-extension=no + +# In verbose mode, extra non-checker-related info will be displayed. +#verbose= + + +[MASTER] + +ignore-paths=tests + + +[BASIC] + +# Naming style matching correct argument names. +argument-naming-style=snake_case + +# Regular expression matching correct argument names. Overrides argument- +# naming-style. If left empty, argument names will be checked with the set +# naming style. +#argument-rgx= + +# Naming style matching correct attribute names. +attr-naming-style=snake_case + +# Regular expression matching correct attribute names. Overrides attr-naming- +# style. If left empty, attribute names will be checked with the set naming +# style. +#attr-rgx= + +# Bad variable names which should always be refused, separated by a comma. +bad-names=foo, + bar, + baz, + toto, + tutu, + tata + +# Bad variable names regexes, separated by a comma. If names match any regex, +# they will always be refused +bad-names-rgxs= + +# Naming style matching correct class attribute names. +class-attribute-naming-style=any + +# Regular expression matching correct class attribute names. Overrides class- +# attribute-naming-style. If left empty, class attribute names will be checked +# with the set naming style. +#class-attribute-rgx= + +# Naming style matching correct class constant names. +class-const-naming-style=UPPER_CASE + +# Regular expression matching correct class constant names. Overrides class- +# const-naming-style. If left empty, class constant names will be checked with +# the set naming style. +#class-const-rgx= + +# Naming style matching correct class names. +class-naming-style=PascalCase + +# Regular expression matching correct class names. Overrides class-naming- +# style. If left empty, class names will be checked with the set naming style. +#class-rgx= + +# Naming style matching correct constant names. +const-naming-style=UPPER_CASE + +# Regular expression matching correct constant names. Overrides const-naming- +# style. If left empty, constant names will be checked with the set naming +# style. +#const-rgx= + +# Minimum line length for functions/classes that require docstrings, shorter +# ones are exempt. +docstring-min-length=-1 + +# Naming style matching correct function names. +function-naming-style=snake_case + +# Regular expression matching correct function names. Overrides function- +# naming-style. If left empty, function names will be checked with the set +# naming style. +#function-rgx= + +# Good variable names which should always be accepted, separated by a comma. +good-names=i, + j, + k, + ex, + Run, + _ + +# Good variable names regexes, separated by a comma. If names match any regex, +# they will always be accepted +good-names-rgxs= + +# Include a hint for the correct naming format with invalid-name. +include-naming-hint=no + +# Naming style matching correct inline iteration names. +inlinevar-naming-style=any + +# Regular expression matching correct inline iteration names. Overrides +# inlinevar-naming-style. If left empty, inline iteration names will be checked +# with the set naming style. +#inlinevar-rgx= + +# Naming style matching correct method names. +method-naming-style=snake_case + +# Regular expression matching correct method names. Overrides method-naming- +# style. If left empty, method names will be checked with the set naming style. +#method-rgx= + +# Naming style matching correct module names. +module-naming-style=snake_case + +# Regular expression matching correct module names. Overrides module-naming- +# style. If left empty, module names will be checked with the set naming style. +#module-rgx= + +# Colon-delimited sets of names that determine each other's naming style when +# the name regexes allow several styles. +name-group= + +# Regular expression which should only match function or class names that do +# not require a docstring. +no-docstring-rgx=^_ + +# List of decorators that produce properties, such as abc.abstractproperty. Add +# to this list to register other decorators that produce valid properties. +# These decorators are taken in consideration only for invalid-name. +property-classes=abc.abstractproperty + +# Regular expression matching correct type alias names. If left empty, type +# alias names will be checked with the set naming style. +#typealias-rgx= + +# Regular expression matching correct type variable names. If left empty, type +# variable names will be checked with the set naming style. +#typevar-rgx= + +# Naming style matching correct variable names. +variable-naming-style=snake_case + +# Regular expression matching correct variable names. Overrides variable- +# naming-style. If left empty, variable names will be checked with the set +# naming style. +#variable-rgx= + + +[CLASSES] + +# Warn about protected attribute access inside special methods +check-protected-access-in-special-methods=no + +# List of method names used to declare (i.e. assign) instance attributes. +defining-attr-methods=__init__, + __new__, + setUp, + asyncSetUp, + __post_init__ + +# List of member names, which should be excluded from the protected access +# warning. +exclude-protected=_asdict,_fields,_replace,_source,_make,os._exit + +# List of valid names for the first argument in a class method. +valid-classmethod-first-arg=cls + +# List of valid names for the first argument in a metaclass class method. +valid-metaclass-classmethod-first-arg=mcs + + +[DESIGN] + +# List of regular expressions of class ancestor names to ignore when counting +# public methods (see R0903) +exclude-too-few-public-methods= + +# List of qualified class names to ignore when counting class parents (see +# R0901) +ignored-parents= + +# Maximum number of arguments for function / method. +max-args=10 + +# Maximum number of attributes for a class (see R0902). +max-attributes=25 + +# Maximum number of boolean expressions in an if statement (see R0916). +max-bool-expr=5 + +# Maximum number of branch for function / method body. +max-branches=20 + +# Maximum number of locals for function / method body. +max-locals=30 + +# Maximum number of parents for a class (see R0901). +max-parents=7 + +# Maximum number of public methods for a class (see R0904). +max-public-methods=20 + +# Maximum number of return / yield for function / method body. +max-returns=7 + +# Maximum number of statements in function / method body. +max-statements=60 + +# Minimum number of public methods for a class (see R0903). +min-public-methods=1 + + +[EXCEPTIONS] + +# Exceptions that will emit a warning when caught. +overgeneral-exceptions=builtins.BaseException,builtins.Exception + + +[FORMAT] + +# Expected format of line ending, e.g. empty (any line ending), LF or CRLF. +expected-line-ending-format= + +# Regexp for a line that is allowed to be longer than the limit. +ignore-long-lines=^\s*(# )??$ + +# Number of spaces of indent required inside a hanging or continued line. +indent-after-paren=4 + +# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 +# tab). +indent-string=' ' + +# Maximum number of characters on a single line. +max-line-length=120 + +# Maximum number of lines in a module. +max-module-lines=1000 + +# Allow the body of a class to be on the same line as the declaration if body +# contains single statement. +single-line-class-stmt=no + +# Allow the body of an if to be on the same line as the test if there is no +# else. +single-line-if-stmt=no + + +[IMPORTS] + +# List of modules that can be imported at any level, not just the top level +# one. +allow-any-import-level= + +# Allow explicit reexports by alias from a package __init__. +allow-reexport-from-package=no + +# Allow wildcard imports from modules that define __all__. +allow-wildcard-with-all=no + +# Deprecated modules which should not be used, separated by a comma. +deprecated-modules= + +# Output a graph (.gv or any supported image format) of external dependencies +# to the given file (report RP0402 must not be disabled). +ext-import-graph= + +# Output a graph (.gv or any supported image format) of all (i.e. internal and +# external) dependencies to the given file (report RP0402 must not be +# disabled). +import-graph= + +# Output a graph (.gv or any supported image format) of internal dependencies +# to the given file (report RP0402 must not be disabled). +int-import-graph= + +# Force import order to recognize a module as part of the standard +# compatibility libraries. +known-standard-library= + +# Force import order to recognize a module as part of a third party library. +known-third-party=enchant + +# Couples of modules and preferred modules, separated by a comma. +preferred-modules= + + +[LOGGING] + +# The type of string formatting that logging methods do. `old` means using % +# formatting, `new` is for `{}` formatting. +logging-format-style=old + +# Logging modules to check that the string format arguments are in logging +# function parameter format. +logging-modules=logging + + +[MESSAGES CONTROL] + +# Only show warnings with the listed confidence levels. Leave empty to show +# all. Valid levels: HIGH, CONTROL_FLOW, INFERENCE, INFERENCE_FAILURE, +# UNDEFINED. +confidence=HIGH, + CONTROL_FLOW, + INFERENCE, + INFERENCE_FAILURE, + UNDEFINED + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this +# option multiple times (only on the command line, not in the configuration +# file where it should appear only once). You can also use "--disable=all" to +# disable everything first and then re-enable specific checks. For example, if +# you want to run only the similarities checker, you can use "--disable=all +# --enable=similarities". If you want to run only the classes checker, but have +# no Warning level messages displayed, use "--disable=all --enable=classes +# --disable=W". +disable=raw-checker-failed, + bad-inline-option, + locally-disabled, + file-ignored, + suppressed-message, + useless-suppression, + deprecated-pragma, + use-symbolic-message-instead, + use-implicit-booleaness-not-comparison-to-string, + use-implicit-booleaness-not-comparison-to-zero, + wrong-import-position, + wrong-import-order, + ungrouped-imports + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifier separated by comma (,) or put this option +# multiple time (only on the command line, not in the configuration file where +# it should appear only once). See also the "--disable" option for examples. +enable= + + +[METHOD_ARGS] + +# List of qualified names (i.e., library.method) which require a timeout +# parameter e.g. 'requests.api.get,requests.api.post' +timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests.api.options,requests.api.patch,requests.api.post,requests.api.put,requests.api.request + + +[MISCELLANEOUS] + +# List of note tags to take in consideration, separated by a comma. +notes=FIXME, + XXX, + TODO + +# Regular expression of note tags to take in consideration. +notes-rgx= + + +[REFACTORING] + +# Maximum number of nested blocks for function / method body +max-nested-blocks=6 + +# Complete name of functions that never returns. When checking for +# inconsistent-return-statements if a never returning function is called then +# it will be considered as an explicit return statement and no message will be +# printed. +never-returning-functions=sys.exit,argparse.parse_error + +# Let 'consider-using-join' be raised when the separator to join on would be +# non-empty (resulting in expected fixes of the type: ``"- " + " - +# ".join(items)``) +suggest-join-with-non-empty-separator=yes + + +[REPORTS] + +# Python expression which should return a score less than or equal to 10. You +# have access to the variables 'fatal', 'error', 'warning', 'refactor', +# 'convention', and 'info' which contain the number of messages in each +# category, as well as 'statement' which is the total number of statements +# analyzed. This score is used by the global evaluation report (RP0004). +evaluation=max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)) + +# Template used to display messages. This is a python new-style format string +# used to format the message information. See doc for all details. +msg-template= + +# Set the output format. Available formats are: text, parseable, colorized, +# json2 (improved json format), json (old json format) and msvs (visual +# studio). You can also give a reporter class, e.g. +# mypackage.mymodule.MyReporterClass. +#output-format= + +# Tells whether to display a full report or only the messages. +reports=no + +# Activate the evaluation score. +score=yes + + +[SIMILARITIES] + +# Comments are removed from the similarity computation +ignore-comments=yes + +# Docstrings are removed from the similarity computation +ignore-docstrings=yes + +# Imports are removed from the similarity computation +ignore-imports=yes + +# Signatures are removed from the similarity computation +ignore-signatures=yes + +# Minimum lines number of a similarity. +min-similarity-lines=20 + + +[SPELLING] + +# Limits count of emitted suggestions for spelling mistakes. +max-spelling-suggestions=4 + +# Spelling dictionary name. No available dictionaries : You need to install +# both the python package and the system dependency for enchant to work. +spelling-dict= + +# List of comma separated words that should be considered directives if they +# appear at the beginning of a comment and should not be checked. +spelling-ignore-comment-directives=fmt: on,fmt: off,noqa:,noqa,nosec,isort:skip,mypy: + +# List of comma separated words that should not be checked. +spelling-ignore-words= + +# A path to a file that contains the private dictionary; one word per line. +spelling-private-dict-file= + +# Tells whether to store unknown words to the private dictionary (see the +# --spelling-private-dict-file option) instead of raising a message. +spelling-store-unknown-words=no + + +[STRING] + +# This flag controls whether inconsistent-quotes generates a warning when the +# character used as a quote delimiter is used inconsistently within a module. +check-quote-consistency=no + +# This flag controls whether the implicit-str-concat should generate a warning +# on implicit string concatenation in sequences defined over several lines. +check-str-concat-over-line-jumps=no + + +[TYPECHECK] + +# List of decorators that produce context managers, such as +# contextlib.contextmanager. Add to this list to register other decorators that +# produce valid context managers. +contextmanager-decorators=contextlib.contextmanager + +# List of members which are set dynamically and missed by pylint inference +# system, and so shouldn't trigger E1101 when accessed. Python regular +# expressions are accepted. +generated-members=__dataclass_fields__ + +# Tells whether to warn about missing members when the owner of the attribute +# is inferred to be None. +ignore-none=yes + +# This flag controls whether pylint should warn about no-member and similar +# checks whenever an opaque object is returned when inferring. The inference +# can return multiple potential results while evaluating a Python object, but +# some branches might not be evaluated, which results in partial inference. In +# that case, it might be useful to still emit no-member and other checks for +# the rest of the inferred objects. +ignore-on-opaque-inference=yes + +# List of symbolic message names to ignore for Mixin members. +ignored-checks-for-mixins=no-member, + not-async-context-manager, + not-context-manager, + attribute-defined-outside-init + +# List of class names for which member attributes should not be checked (useful +# for classes with dynamically set attributes). This supports the use of +# qualified names. +ignored-classes=optparse.Values,thread._local,_thread._local,argparse.Namespace + +# Show a hint with possible names when a member name was not found. The aspect +# of finding the hint is based on edit distance. +missing-member-hint=yes + +# The minimum edit distance a name should have in order to be considered a +# similar match for a missing member name. +missing-member-hint-distance=1 + +# The total number of similar names that should be taken in consideration when +# showing a hint for a missing member. +missing-member-max-choices=1 + +# Regex pattern to define which classes are considered mixins. +mixin-class-rgx=.*[Mm]ixin + +# List of decorators that change the signature of a decorated function. +signature-mutators= + + +[VARIABLES] + +# List of additional names supposed to be defined in builtins. Remember that +# you should avoid defining new builtins when possible. +additional-builtins= + +# Tells whether unused global variables should be treated as a violation. +allow-global-unused-variables=yes + +# List of names allowed to shadow builtins +allowed-redefined-builtins= + +# List of strings which can identify a callback function by name. A callback +# name must start or end with one of those strings. +callbacks=cb_, + _cb + +# A regular expression matching the name of dummy variables (i.e. expected to +# not be used). +dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ + +# Argument names that match this expression will be ignored. +ignored-argument-names=_.*|^ignored_|^unused_ + +# Tells whether we should check for unused import in __init__ files. +init-import=no + +# List of qualified module names which can have objects that can redefine +# builtins. +redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io diff --git a/DEVELOPERS.md b/DEVELOPERS.md index e98abc7..a7f722b 100644 --- a/DEVELOPERS.md +++ b/DEVELOPERS.md @@ -8,14 +8,12 @@ scripts in `src/security/`. | Tool | Version | Purpose | | --- | --- | --- | | Python | 3.14+ | Runtime for all Python scripts | -| `gh` | latest | GitHub CLI (used by shell scripts) | -| `jq` | latest | JSON processing in shell scripts | +| `gh` | latest | GitHub CLI (used by Python scripts) | -Install runtime **and** development dependencies: +Install all dependencies: ```bash -pip install -r requirements.txt # runtime deps (requests) -pip install -r requirements-dev.txt # dev deps (pytest, pytest-cov) +pip install -r requirements.txt ``` ## Project layout @@ -23,13 +21,17 @@ pip install -r requirements-dev.txt # dev deps (pytest, pytest-cov) ```text src/security/ ├── utils/ # Core library modules -├── promote_alerts.py # Main sync entrypoint (Python) +├── sync_security_alerts.py # Main orchestrator (check labels → collect → promote) +├── collect_alert.py # Fetch & normalise code-scanning alerts +├── promote_alerts.py # Create / update GitHub Issues from alerts JSON ├── send_to_teams.py # Teams notification helper tests/ ├── security/ │ ├── conftest.py # Shared fixtures (synthetic alert payloads) +│ ├── test_collect_alert.py │ ├── test_promote_alerts.py │ ├── test_send_to_teams.py +│ ├── test_sync_security_alerts.py │ └── utils/ # Mirrors utils/ module structure │ ├── test_alert_parser.py │ ├── test_constants.py @@ -74,8 +76,8 @@ open htmlcov/index.html ### Running a single test file or test ```bash -python3 -m pytest tests/utils/test_alert_parser.py -v -python3 -m pytest tests/utils/test_models.py::test_severity_direction_escalate -v +python3 -m pytest tests/security/utils/test_alert_parser.py -v +python3 -m pytest tests/security/utils/test_models.py::test_severity_change_creation -v ``` ## Test conventions @@ -96,8 +98,12 @@ Coverage is configured in `pyproject.toml`: ```toml [tool.coverage.run] -source = ["."] -omit = ["tests/*", "__pycache__/*"] +source = ["src/shared", "src/security"] +omit = [ + "*/tests/*", + "*/__pycache__/*", + "*/htmlcov/*", +] [tool.coverage.report] show_missing = true @@ -131,6 +137,44 @@ end-to-end tests. 3. Write plain `test_` functions — do not wrap them in classes. 4. Run `python3 -m pytest tests/ -v --cov` to verify coverage. +## Linting and static analysis + +All commands assume your working directory is the repository root. + +### Black (formatting) + +Check only (CI mode): +```bash +python3 -m black --check src/ tests/ +``` + +Auto-fix formatting: +```bash +python3 -m black src/ tests/ +``` + +### Pylint (static analysis) + +The project requires a score ≥ **9.5/10** (enforced by `fail-under` in `.pylintrc`). + +```bash +python3 -m pylint src/ +``` + +### Mypy (type checking) + +```bash +python3 -m mypy src/ +``` + +### Run all checks at once + +```bash +python3 -m black --check src/ tests/ && \ +python3 -m mypy src/ && \ +python3 -m pylint src/ +``` + ## Style guide - Formatting follows [Black](https://black.readthedocs.io/) with diff --git a/docs/security/security.md b/docs/security/security.md index 2846570..046364b 100644 --- a/docs/security/security.md +++ b/docs/security/security.md @@ -9,7 +9,7 @@ In one sentence: SARIF uploads create alerts; these scripts sync alerts into Iss - [What this is (and what it isn't)](#what-this-is-and-what-it-isnt) - [Contents](#contents) - [Quick start (local)](#quick-start-local) - - [Recommended: `sync_security_alerts.sh`](#recommended-sync_security_alertssh) + - [Recommended: `sync_security_alerts.py`](#recommended-sync_security_alertspy) - [Advanced: individual steps](#advanced-individual-steps) - [Run in GitHub Actions (minimal example)](#run-in-github-actions-minimal-example) - [Shared workflows](#shared-workflows) @@ -22,6 +22,7 @@ In one sentence: SARIF uploads create alerts; these scripts sync alerts into Iss - [Issue structure](#issue-structure) - [How you "say duplicate / grouped / dismissed / reopened"](#how-you-say-duplicate--grouped--dismissed--reopened) - [Known data manipulations](#known-data-manipulations) +- [Collector output contract (alerts.json schema)](#collector-output-contract-alertsjson-schema) - [Design: fingerprints and matching](#design-fingerprints-and-matching) - [Current implementation status](#current-implementation-status) - [Troubleshooting](#troubleshooting) @@ -37,9 +38,9 @@ In one sentence: SARIF uploads create alerts; these scripts sync alerts into Iss | Script | Purpose | Requires | | --- | --- | --- | -| `sync_security_alerts.sh` | Main entrypoint: check labels, collect alerts, promote to Issues (local or Actions) | `gh`, `jq`, `python3` | -| `check_labels.sh` | Verify that all labels required by the automation exist in the repository | `gh` | -| `collect_alert.sh` | Fetch and normalize code scanning alerts into `alerts.json` | `gh`, `jq` | +| `sync_security_alerts.py` | Main entrypoint: check labels, collect alerts, promote to Issues (local or Actions) | `gh`, `python3` | +| `check_labels.py` | Verify that all labels required by the automation exist in the repository | `gh`, `python3` | +| `collect_alert.py` | Fetch and normalise code-scanning alerts into `alerts.json` | `gh`, `python3` | | `promote_alerts.py` | Create/update parent+child Issues from `alerts.json` and link children under parents | `gh` | | `send_to_teams.py` | Send a Markdown message to a Microsoft Teams channel via Incoming Webhook | `requests` | | `extract_team_security_stats.py` | Snapshot security Issues for a team across repos | `PyGithub`, `GITHUB_TOKEN` | @@ -52,7 +53,6 @@ All scripts live under `src/security/` in the repository root. ### Prerequisites - Install and authenticate GitHub CLI: `gh auth login` -- Install `jq` - Python 3.14+ recommended - Install Python dependencies: @@ -60,20 +60,20 @@ All scripts live under `src/security/` in the repository root. pip install -e '.[security]' ``` -### Recommended: `sync_security_alerts.sh` +### Recommended: `sync_security_alerts.py` -This is the normal entrypoint for day-to-day use. It runs `check_labels.sh`, `collect_alert.sh`, and then `promote_alerts.py`. +This is the normal entrypoint for day-to-day use. It runs `check_labels.py`, `collect_alert.py`, and then `promote_alerts.py`. Collect + promote in one command: ```bash -./src/security/sync_security_alerts.sh --repo +python3 src/security/sync_security_alerts.py --repo ``` Safe preview (no issue writes): ```bash -./src/security/sync_security_alerts.sh --repo --dry-run +python3 src/security/sync_security_alerts.py --repo --dry-run ``` To see full body previews in dry-run, use `--verbose` (or set `RUNNER_DEBUG=1`). @@ -85,7 +85,7 @@ You can run the individual steps when you need finer control or want to debug th 1. Collect open alerts: ```bash -./src/security/collect_alert.sh --repo --state open --out alerts.json +python3 src/security/collect_alert.py --repo --state open --out alerts.json ``` 2. Promote alerts to Issues: @@ -98,7 +98,7 @@ python3 src/security/promote_alerts.py --file alerts.json This is the simplest "after SARIF upload, sync issues" job. -The expected entrypoint is `sync_security_alerts.sh` (the individual scripts are still available when you need finer control). +The expected entrypoint is `sync_security_alerts.py` (the individual scripts are still available when you need finer control). ```yaml name: Promote code scanning alerts to issues @@ -117,7 +117,11 @@ jobs: promote: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 + with: + persist-credentials: false + fetch-depth: 0 + - uses: actions/setup-python@v5 with: python-version: '3.14' @@ -126,7 +130,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | - ./src/security/sync_security_alerts.sh --state open --out alerts.json + python3 src/security/sync_security_alerts.py --state open --out alerts.json ``` ## Shared workflows @@ -140,7 +144,7 @@ The `docs/security/example_workflows/` directory contains ready-to-copy **exampl | Workflow | Trigger (caller) | Purpose | | --- | --- | --- | -| `aquasec-scan.yml` | `schedule` / `workflow_dispatch` | Runs AquaSec scan, uploads SARIF, then syncs alerts to Issues via `sync_security_alerts.sh` | +| `aquasec-scan.yml` | `schedule` / `workflow_dispatch` | Runs AquaSec scan, uploads SARIF, then syncs alerts to Issues via `sync_security_alerts.py` | | `remove-adept-to-close-on-issue-close.yml` | `issues: [closed]` | Removes the `sec:adept-to-close` label from security issues when they are closed | ### How to adopt a shared workflow @@ -160,7 +164,7 @@ The caller needs the following **repository secrets** configured: | `AQUA_REPOSITORY_ID` | yes | AquaSec repository identifier | | `TEAMS_WEBHOOK_URL` | no | Teams Incoming Webhook URL for new/reopened issue alerts | -Example caller (already available in `docs/security/example_workflows/aquasec-night-scan.yml`): +Example caller (already available in [aquasec-night-scan.yml](/docs/security/example_workflows/aquasec-night-scan.yml)): ```yaml name: Aquasec Night Scan @@ -193,7 +197,7 @@ jobs: #### Remove sec:adept-to-close on close -Example caller (already available in `docs/security/example_workflows/remove-adept-to-close-on-issue-close.yml`): +Example caller (already available in [remove-adept-to-close-on-issue-close.yml](/docs/security/example_workflows/remove-adept-to-close-on-issue-close.yml)): ```yaml name: Remove sec:adept-to-close on close @@ -214,13 +218,12 @@ jobs: ## Labels (contract) -The automation requires exactly these five labels to exist in the target repository (enforced by `check_labels.sh`): +The automation requires exactly these four labels to exist in the target repository (enforced by `check_labels.py`): | Label | Purpose | | --- | --- | | `scope:security` | Applied to every security Issue; used by `promote_alerts.py --issue-label` to discover existing Issues | | `type:tech-debt` | Marks security findings as tech-debt items | -| `sec:src/aquasec-sarif` | Source tag — identifies the scanner that produced the finding | | `sec:adept-to-close` | Signals that a finding is ready to be closed by automation | | `epic` | Applied to parent (rule-level) Issues so they act as epics grouping child findings | @@ -356,6 +359,58 @@ path string from the alert is never written back to the issue body. Implementation: `shared/common.py → normalize_path()`. +## Collector output contract (alerts.json schema) + +`collect_alert.py` produces a JSON file with the following top-level structure: + +```json +{ + "generated_at": "", + "repo": { "id": ..., "name": ..., "full_name": ..., "private": ..., + "html_url": ..., "default_branch": ..., "owner": { ... } }, + "query": { "state": "" }, + "alerts": [ ... ] +} +``` + +Each element of `alerts` is a normalised alert object with three sub-objects: + +### `metadata` + +Fixed keys extracted directly from the GitHub code-scanning alert API response. +All keys are always present; values are `null` when the API does not provide them +(e.g. `instance_url`, `help_uri`). + +Key fields: `alert_number`, `state`, `created_at`, `updated_at`, `url`, `alert_url`, +`rule_id`, `rule_name`, `severity`, `confidence`, `tags`, `help_uri`, `tool`, +`tool_version`, `ref`, `commit_sha`, `instance_url`, `classifications`, `file`, +`start_line`, `end_line`. + +### `alert_details` + +Key/value pairs parsed from the free-text `message.text` field of the most-recent +alert instance. **Only keys that actually appear in the message are included**; no +default or placeholder values are injected for absent keys. + +For AquaSec scans the message embeds lines in the form `Key: Value`. The raw message +keys (defined in `AlertMessageKey` in `utils/alert_parser.py`) are space-separated +(e.g. `scan date`, `alert hash`); `collect_alert.py`'s `_parse_alert_details` converts +them to snake_case via `_snake_case`. Known output keys include `artifact`, `type`, +`vulnerability`, `severity`, `message`, `repository`, `reachable`, `scan_date`, +`first_seen`, `scm_file`, `installed_version`, `start_line`, `end_line`, `alert_hash`. + +Note: `installed_version` is present only in vulnerability-type alerts (e.g. +`CVE-*`). It is absent from SAST and pipeline misconfiguration alerts because the +scanner does not include that field in those message types. + +### `rule_details` + +Fields extracted from `**Key:** value` markup in the rule help text, using the fixed +set of keys listed in `RULE_DETAIL_KEYS` in `collect_alert.py`. **All keys are always +present**; missing fields are set to `null` (not `"N/A"` or an empty string). Keys +include: `type`, `severity`, `cwe`, `fixed_version`, `published_date`, `package_name`, +`category`, `impact`, `confidence`, `likelihood`, `remediation`, `owasp`, `references`. + ## Design: fingerprints and matching ### Current fingerprint source @@ -407,7 +462,7 @@ As of 2026-02, `promote_alerts.py` implements the fingerprint-based sync loop de - `gh: command not found`: install GitHub CLI and ensure it's on `PATH`. - `gh auth status` fails: run `gh auth login` locally, or set `GH_TOKEN` in Actions. - Permission errors in Actions: ensure the workflow has `security-events: read` and `issues: write` permissions. -- `Output file alerts.json exists`: `collect_alert.sh` refuses to overwrite output; delete the file or pass a different `--out` path. +- `Output file alerts.json exists`: `collect_alert.py` refuses to overwrite output; delete the file, pass a different `--out` path, or run via `sync_security_alerts.py --force` (which deletes the file before invoking the collector). - `missing 'alert hash' in alert message`: the scanner/collector needs to include an `Alert hash: ...` line in the alert instance message text. ## References diff --git a/pyproject.toml b/pyproject.toml index 9faa968..06af3d9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -45,3 +45,11 @@ force-exclude = '''test''' [tool.mypy] check_untyped_defs = true exclude = "tests" + +[[tool.mypy.overrides]] +module = "requests.*" +ignore_missing_imports = true + +[[tool.mypy.overrides]] +module = "derive_team_security_metrics" +ignore_errors = true diff --git a/requirements.txt b/requirements.txt index 01812db..51375b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,7 @@ PyGithub>=2.0 requests==2.32.4 pytest>=8.0 pytest-cov>=6.0 +pytest-mock>=3.0 +pylint>=3.0 +black>=24.0 +mypy>=1.0 diff --git a/src/security/alerts.json b/src/security/alerts.json deleted file mode 100644 index c4e1675..0000000 --- a/src/security/alerts.json +++ /dev/null @@ -1,17158 +0,0 @@ -{ - "generated_at": "2026-03-06T14:37:11Z", - "repo": { - "id": 384354450, - "name": "AUL", - "full_name": "absa-group/AUL", - "private": true, - "html_url": "https://github.com/absa-group/AUL", - "default_branch": "master", - "owner": { - "login": "absa-group", - "id": 69052975, - "html_url": "https://github.com/absa-group" - } - }, - "query": { - "state": "open" - }, - "alerts": [ - { - "metadata": { - "alert_number": 345, - "state": "open", - "created_at": "2026-03-06T09:29:38Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/345", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/345", - "rule_id": "sqlalchemy-safe-query-execution-aquasec-python", - "rule_name": "sast", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "sast", - "security" - ], - "help_uri": "https://owasp.org/Top10/A03_2021-Injection/", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "scripts/changes_to_consumption_roles/delete_old_consumption_roles_4096.py", - "start_line": 48, - "end_line": 48 - }, - "alert_details": { - "artifact": "scripts/changes_to_consumption_roles/delete_old_consumption_roles_4096.py", - "type": "sast", - "vulnerability": "sqlalchemy-safe-query-execution-aquasec-python", - "severity": "HIGH", - "message": "Merging user inputs directly into SQL queries without precautions can lead to SQL Injection vulnerabilities. It is highly recomended to use prepared statements to ensure data is handled securely, with SQLAlchemy's TextualSQL being an ideal tool for this. It supports named parameters for safer query construction. For more complex queries, SQLAlchemy's SQL Expression Language or Schema Definition Language are recommended. Opting for SQLAlchemy ORM is often the safest and most efficient route for database operations.", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-03-05T12:38:46.586Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/scripts/changes_to_consumption_roles/delete_old_consumption_roles_4096.py", - "start_line": "48", - "end_line": "48", - "alert_hash": "39e00619b2a99c49295b57ea3a34b69f" - }, - "rule_details": { - "type": "sast", - "severity": "HIGH", - "cwe": "CWE-89: Improper Neutralization of Special Elements used in an SQL Command('SQL Injection')", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "security/audit", - "impact": "HIGH", - "confidence": "LOW", - "likelihood": "LOW", - "remediation": "It is highly recomended to use prepared statements to ensure data is handled securely, with SQLAlchemy's TextualSQL being an ideal tool for this. It supports named parameters for safer query construction. For more complex queries, SQLAlchemy's SQL Expression Language or Schema Definition Language are recommended. Opting for SQLAlchemy ORM is often the safest and most efficient route for database operations.", - "owasp": "- A03:2021 - Injection", - "references": "- https://owasp.org/Top10/A03_2021-Injection/" - } - }, - { - "metadata": { - "alert_number": 344, - "state": "open", - "created_at": "2026-03-04T18:31:41Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/344", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/344", - "rule_id": "use-parameterized-queries-aquasec-python", - "rule_name": "sast", - "severity": "medium", - "confidence": "warning", - "tags": [ - "MEDIUM", - "sast", - "security" - ], - "help_uri": "https://cwe.mitre.org/data/definitions/89.html", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "start_line": 118, - "end_line": 118 - }, - "alert_details": { - "artifact": "scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "type": "sast", - "vulnerability": "use-parameterized-queries-aquasec-python", - "severity": "MEDIUM", - "message": "Your code appears to be using formatted SQL queries, which can expose your application to SQL Injection attacks. This is a risk as it can allow an attacker to manipulate SQL queries, leading to data breaches. It is recommended that you replace formatted SQL queries with parameterized queries. This ensures that user inputs are treated as literal values and not executable code, effectively preventing SQL Injection attacks", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-25T19:37:05.912Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "start_line": "118", - "end_line": "118", - "alert_hash": "6a82e146deae0c2a07c74bfdbbf47d6d" - }, - "rule_details": { - "type": "sast", - "severity": "MEDIUM", - "cwe": "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "security /audit", - "impact": "HIGH", - "confidence": "LOW", - "likelihood": "LOW", - "remediation": "It is recommended that you replace formatted SQL queries with parameterized queries. This ensures that user inputs are treated as literal values and not executable code, effectively preventing SQL Injection attacks.", - "owasp": "- A03:2021 - Injection", - "references": "- https://cwe.mitre.org/data/definitions/89.html" - } - }, - { - "metadata": { - "alert_number": 343, - "state": "open", - "created_at": "2026-03-04T18:31:41Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/343", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/343", - "rule_id": "sqlalchemy-safe-query-execution-aquasec-python", - "rule_name": "sast", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "sast", - "security" - ], - "help_uri": "https://owasp.org/Top10/A03_2021-Injection/", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "start_line": 118, - "end_line": 118 - }, - "alert_details": { - "artifact": "scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "type": "sast", - "vulnerability": "sqlalchemy-safe-query-execution-aquasec-python", - "severity": "HIGH", - "message": "Merging user inputs directly into SQL queries without precautions can lead to SQL Injection vulnerabilities. It is highly recomended to use prepared statements to ensure data is handled securely, with SQLAlchemy's TextualSQL being an ideal tool for this. It supports named parameters for safer query construction. For more complex queries, SQLAlchemy's SQL Expression Language or Schema Definition Language are recommended. Opting for SQLAlchemy ORM is often the safest and most efficient route for database operations.", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-25T19:37:05.912Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/scripts/changes_to_consumption_roles/create_control_tower_adherent_resources_4094.py", - "start_line": "118", - "end_line": "118", - "alert_hash": "c763717543af063ba5573d268ec73e93" - }, - "rule_details": { - "type": "sast", - "severity": "HIGH", - "cwe": "CWE-89: Improper Neutralization of Special Elements used in an SQL Command('SQL Injection')", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "security/audit", - "impact": "HIGH", - "confidence": "LOW", - "likelihood": "LOW", - "remediation": "It is highly recomended to use prepared statements to ensure data is handled securely, with SQLAlchemy's TextualSQL being an ideal tool for this. It supports named parameters for safer query construction. For more complex queries, SQLAlchemy's SQL Expression Language or Schema Definition Language are recommended. Opting for SQLAlchemy ORM is often the safest and most efficient route for database operations.", - "owasp": "- A03:2021 - Injection", - "references": "- https://owasp.org/Top10/A03_2021-Injection/" - } - }, - { - "metadata": { - "alert_number": 342, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/342", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/342", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "spring-web-root/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "spring-web-root/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted all \" + TEST_NU" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 341, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/341", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/341", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "shared-spring/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "shared-spring/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted all \" + TEST_NUMB" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 340, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/340", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/340", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "shared-spark/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "shared-spark/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted all \" + TEST_NUMBE" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 339, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/339", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/339", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "shared-http-client/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "shared-http-client/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted all \" + TEST" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 338, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/338", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/338", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted all \" + TEST_NUMBER_LENGTH + \" " - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 337, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/337", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/337", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/user-identity-info/spring-controller/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/user-identity-info/spring-controller/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser sil" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 336, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/336", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/336", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/feed-management/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/feed-management/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acce" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 335, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/335", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/335", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/entity-exchange/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/entity-exchange/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acce" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 334, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/334", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/334", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/domain-questions/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/domain-questions/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acc" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 333, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/333", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/333", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/domain-questions/implementation/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/domain-questions/implementation/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 332, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/332", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/332", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/domain-management/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/domain-management/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently ac" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 331, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/331", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/331", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/domain-execution/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/domain-execution/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acc" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 330, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/330", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/330", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/domain-execution/spark-jobs/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/domain-execution/spark-jobs/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acc" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 329, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/329", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/329", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/data-measurement/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/data-measurement/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acc" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 328, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/328", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/328", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/data-cataloging/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/data-cataloging/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acce" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 327, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/327", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/327", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/data-cataloging/implementation/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/data-cataloging/implementation/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently " - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 326, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/326", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/326", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/data-access/spring-web/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/data-access/spring-web/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently accepted" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 325, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/325", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/325", - "rule_id": "GHSA-72hv-8253-57qq", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://github.com/FasterXML/jackson-core", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "contexts/data-access/implementation/pom.xml", - "start_line": 1, - "end_line": 1 - }, - "alert_details": { - "artifact": "contexts/data-access/implementation/pom.xml", - "type": "vulnerabilities", - "vulnerability": "GHSA-72hv-8253-57qq", - "severity": "HIGH", - "message": "### Summary The non-blocking (async) JSON parser in `jackson-core` bypasses the `maxNumberLength` constraint (default: 1000 characters) defined in `StreamReadConstraints`. This allows an attacker to send JSON with arbitrarily long numbers through the async parser API, leading to excessive memory allocation and potential CPU exhaustion, resulting in a Denial of Service (DoS). The standard synchronous parser correctly enforces this limit, but the async parser fails to do so, creating an inconsistent enforcement policy. ### Details The root cause is that the async parsing path in `NonBlockingUtf8JsonParserBase` (and related classes) does not call the methods responsible for number length validation. - The number parsing methods (e.g., `_finishNumberIntegralPart`) accumulate digits into the `TextBuffer` without any length checks. - After parsing, they call `_valueComplete()`, which finalizes the token but does **not** call `resetInt()` or `resetFloat()`. - The `resetInt()`/`resetFloat()` methods in `ParserBase` are where the `validateIntegerLength()` and `validateFPLength()` checks are performed. - Because this validation step is skipped, the `maxNumberLength` constraint is never enforced in the async code path. ### PoC The following JUnit 5 test demonstrates the vulnerability. It shows that the async parser accepts a 5,000-digit number, whereas the limit should be 1,000. ```java package tools.jackson.core.unittest.dos; import java.nio.charset.StandardCharsets; import org.junit.jupiter.api.Test; import tools.jackson.core.*; import tools.jackson.core.exc.StreamConstraintsException; import tools.jackson.core.json.JsonFactory; import tools.jackson.core.json.async.NonBlockingByteArrayJsonParser; import static org.junit.jupiter.api.Assertions.*; /** * POC: Number Length Constraint Bypass in Non-Blocking (Async) JSON Parsers * * Authors: sprabhav7, rohan-repos * * maxNumberLength default = 1000 characters (digits). * A number with more than 1000 digits should be rejected by any parser. * * BUG: The async parser never calls resetInt()/resetFloat() which is where * validateIntegerLength()/validateFPLength() lives. Instead it calls * _valueComplete() which skips all number length validation. * * CWE-770: Allocation of Resources Without Limits or Throttling */ class AsyncParserNumberLengthBypassTest { private static final int MAX_NUMBER_LENGTH = 1000; private static final int TEST_NUMBER_LENGTH = 5000; private final JsonFactory factory = new JsonFactory(); // CONTROL: Sync parser correctly rejects a number exceeding maxNumberLength @Test void syncParserRejectsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); // Output to console System.out.println(\"[SYNC] Parsing \" + TEST_NUMBER_LENGTH + \"-digit number (limit: \" + MAX_NUMBER_LENGTH + \")\"); try { try (JsonParser p = factory.createParser(ObjectReadContext.empty(), payload)) { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { System.out.println(\"[SYNC] Accepted number with \" + p.getText().length() + \" digits — UNEXPECTED\"); } } } fail(\"Sync parser must reject a \" + TEST_NUMBER_LENGTH + \"-digit number\"); } catch (StreamConstraintsException e) { System.out.println(\"[SYNC] Rejected with StreamConstraintsException: \" + e.getMessage()); } } // VULNERABILITY: Async parser accepts the SAME number that sync rejects @Test void asyncParserAcceptsLongNumber() throws Exception { byte[] payload = buildPayloadWithLongInteger(TEST_NUMBER_LENGTH); NonBlockingByteArrayJsonParser p = (NonBlockingByteArrayJsonParser) factory.createNonBlockingByteArrayParser(ObjectReadContext.empty()); p.feedInput(payload, 0, payload.length); p.endOfInput(); boolean foundNumber = false; try { while (p.nextToken() != null) { if (p.currentToken() == JsonToken.VALUE_NUMBER_INT) { foundNumber = true; String numberText = p.getText(); assertEquals(TEST_NUMBER_LENGTH, numberText.length(), \"Async parser silently acce" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "2.18.6, 2.21.1, 3.1.0", - "published_date": "2026-02-28T02:01:05.000Z", - "package_name": "com.fasterxml.jackson.core:jackson-core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://github.com/FasterXML/jackson-core" - } - }, - { - "metadata": { - "alert_number": 324, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/324", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/324", - "rule_id": "CVE-2026-27904", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-27904", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 52, - "end_line": 52 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-27904", - "severity": "HIGH", - "message": "minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4, nested `*()` extglobs produce regexps with nested unbounded quantifiers (e.g. `(?:(?:a|b)*)*`), which exhibit catastrophic backtracking in V8. With a 12-byte pattern `*(*(*(a|b)))` and an 18-byte non-matching input, `minimatch()` stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default `minimatch()` API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects `+()` extglobs equally. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4 fix the issue. (This package is used under: eslint-plugin-jest@29.0.1->@typescript-eslint/utils@8.44.1->@typescript-eslint/typescript-estree@8.44.1->minimatch@9.0.5)", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-27T20:05:53.625Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "9.0.5", - "start_line": "52", - "end_line": "52", - "alert_hash": "4a8c68035cf0630b36c7ce45945b0c3d" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, 3.1.4", - "published_date": "2026-02-26T02:16:21.000Z", - "package_name": "minimatch", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-27904" - } - }, - { - "metadata": { - "alert_number": 323, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/323", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/323", - "rule_id": "CVE-2026-27903", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-27903", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 52, - "end_line": 52 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-27903", - "severity": "HIGH", - "message": "minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3, `matchOne()` performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent `**` (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where `n` is the number of path segments and `k` is the number of globstars. With k=11 and n=30, a call to the default `minimatch()` API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior. Any application where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3 fix the issue. (This package is used under: eslint-plugin-jest@29.0.1->@typescript-eslint/utils@8.44.1->@typescript-eslint/typescript-estree@8.44.1->minimatch@9.0.5)", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-27T20:05:53.625Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "9.0.5", - "start_line": "52", - "end_line": "52", - "alert_hash": "e32f1d8a7d7cd4ea12b31da27aa254e9" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, 3.1.3", - "published_date": "2026-02-26T02:16:21.000Z", - "package_name": "minimatch", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-27903" - } - }, - { - "metadata": { - "alert_number": 322, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/322", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/322", - "rule_id": "CVE-2026-27904", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-27904", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 90, - "end_line": 90 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-27904", - "severity": "HIGH", - "message": "minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4, nested `*()` extglobs produce regexps with nested unbounded quantifiers (e.g. `(?:(?:a|b)*)*`), which exhibit catastrophic backtracking in V8. With a 12-byte pattern `*(*(*(a|b)))` and an 18-byte non-matching input, `minimatch()` stalls for over 7 seconds. Adding a single nesting level or a few input characters pushes this to minutes. This is the most severe finding: it is triggered by the default `minimatch()` API with no special options, and the minimum viable pattern is only 12 bytes. The same issue affects `+()` extglobs equally. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.4 fix the issue. (This package is used under: eslint@9.36.0->minimatch@3.1.2)", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-27T20:05:53.625Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.1.2", - "start_line": "90", - "end_line": "90", - "alert_hash": "710f221f4ea4d18df4d9616d519dbc6d" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, 3.1.4", - "published_date": "2026-02-26T02:16:21.000Z", - "package_name": "minimatch", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-27904" - } - }, - { - "metadata": { - "alert_number": 321, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/321", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/321", - "rule_id": "CVE-2026-27903", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-27903", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 90, - "end_line": 90 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-27903", - "severity": "HIGH", - "message": "minimatch is a minimal matching utility for converting glob expressions into JavaScript RegExp objects. Prior to version 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3, `matchOne()` performs unbounded recursive backtracking when a glob pattern contains multiple non-adjacent `**` (GLOBSTAR) segments and the input path does not match. The time complexity is O(C(n, k)) -- binomial -- where `n` is the number of path segments and `k` is the number of globstars. With k=11 and n=30, a call to the default `minimatch()` API stalls for roughly 5 seconds. With k=13, it exceeds 15 seconds. No memoization or call budget exists to bound this behavior. Any application where an attacker can influence the glob pattern passed to `minimatch()` is vulnerable. The realistic attack surface includes build tools and task runners that accept user-supplied glob arguments (ESLint, Webpack, Rollup config), multi-tenant systems where one tenant configures glob-based rules that run in a shared process, admin or developer interfaces that accept ignore-rule or filter configuration as globs, and CI/CD pipelines that evaluate user-submitted config files containing glob patterns. An attacker who can place a crafted pattern into any of these paths can stall the Node.js event loop for tens of seconds per invocation. The pattern is 56 bytes for a 5-second stall and does not require authentication in contexts where pattern input is part of the feature. Versions 10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, and 3.1.3 fix the issue. (This package is used under: eslint@9.36.0->minimatch@3.1.2)", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-27T20:05:53.625Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.1.2", - "start_line": "90", - "end_line": "90", - "alert_hash": "1a14c2a5a27977f62426407c34eadd44" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "10.2.3, 9.0.7, 8.0.6, 7.4.8, 6.2.2, 5.1.8, 4.2.5, 3.1.3", - "published_date": "2026-02-26T02:16:21.000Z", - "package_name": "minimatch", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-27903" - } - }, - { - "metadata": { - "alert_number": 320, - "state": "open", - "created_at": "2026-03-02T22:30:04Z", - "updated_at": "2026-03-06T09:29:38Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/320", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/320", - "rule_id": "CVE-2026-27970", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-27970", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 33, - "end_line": 33 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-27970", - "severity": "HIGH", - "message": "Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Versions prior to 21.2.0, 21.1.16, 20.3.17, and 19.2.19 have a cross-Site scripting vulnerability in the Angular internationalization (i18n) pipeline. In ICU messages (International Components for Unicode), HTML from translated content was not properly sanitized and could execute arbitrary JavaScript. Angular i18n typically involves three steps, extracting all messages from an application in the source language, sending the messages to be translated, and then merging their translations back into the final source code. Translations are frequently handled by contracts with specific partner companies, and involve sending the source messages to a separate contractor before receiving final translations for display to the end user. If the returned translations have malicious content, it could be rendered into the application and execute arbitrary JavaScript. When successfully exploited, this vulnerability allows for execution of attacker controlled JavaScript in the application origin. Depending on the nature of the application being exploited this could lead to credential exfiltration and/or page vandalism. Several preconditions apply to the attack. The attacker must compromise the translation file (xliff, xtb, etc.). Unlike most XSS vulnerabilities, this issue is not exploitable by arbitrary users. An attacker must first compromise an application's translation file before they can escalate privileges into the Angular application client. The victim application must use Angular i18n, use one or more ICU messages, render an ICU message, and not defend against XSS via a safe content security policy. Versions 21.2.0, 21.1.6, 20.3.17, and 19.2.19 patch the issue. Until the patch is applied, developers should consider reviewing and verifying translated content received from untrusted third parties before incorporating it in an Angular application, enabling strict CSP controls to block unauthorized JavaScript from executing on the page, and enabling Trusted Types to enforce proper HTML sanitization. (This package is used under: @angular/core@20.3.9)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-03-01T10:20:26.611Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "20.3.9", - "start_line": "33", - "end_line": "33", - "alert_hash": "d10d284d375596494a7eae918a86e403" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "21.2.0, 21.1.6, 20.3.17, 19.2.19", - "published_date": "2026-02-26T02:16:24.000Z", - "package_name": "@angular/core", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-27970" - } - }, - { - "metadata": { - "alert_number": 317, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/317", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/317", - "rule_id": "AVD-PIPELINE-0008", - "rule_name": "pipelineMisconfigurations", - "severity": "medium", - "confidence": "warning", - "tags": [ - "MEDIUM", - "pipelineMisconfigurations", - "security" - ], - "help_uri": null, - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": ".github/workflows/aquasec-night-scan.yml", - "start_line": 21, - "end_line": 21 - }, - "alert_details": { - "artifact": ".github/workflows/aquasec-night-scan.yml", - "type": "pipelineMisconfigurations", - "vulnerability": "AVD-PIPELINE-0008", - "severity": "MEDIUM", - "message": "Dependency AbsaOSS/aquasec-scan-results master version should be pinned to the commit sha", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-09T15:51:33.454Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/.github/workflows/aquasec-night-scan.yml", - "start_line": "21", - "end_line": "N/A", - "alert_hash": "bed23a624d7f1f07f56a07c6349bcd8b" - }, - "rule_details": { - "type": "pipelineMisconfigurations", - "severity": "MEDIUM", - "cwe": "N/A", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "N/A" - } - }, - { - "metadata": { - "alert_number": 316, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/316", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/316", - "rule_id": "AVD-DS-0015", - "rule_name": "iacMisconfigurations", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "iacMisconfigurations", - "security" - ], - "help_uri": "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "deployment/pdintdedatassets/prod/Dockerfile", - "start_line": 28, - "end_line": 29 - }, - "alert_details": { - "artifact": "deployment/pdintdedatassets/prod/Dockerfile", - "type": "iacMisconfigurations", - "vulnerability": "AVD-DS-0015", - "severity": "HIGH", - "message": "'yum clean all' is missed: yum -y update && yum -y install htop procps", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2024-07-08T15:03:04.312Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/deployment/pdintdedatassets/prod/Dockerfile", - "start_line": "28", - "end_line": "29", - "alert_hash": "22e99eb0e351965c1c19e1ba048882b3" - }, - "rule_details": { - "type": "iacMisconfigurations", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run" - } - }, - { - "metadata": { - "alert_number": 315, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/315", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/315", - "rule_id": "AVD-DS-0015", - "rule_name": "iacMisconfigurations", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "iacMisconfigurations", - "security" - ], - "help_uri": "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "deployment/npintdedatassets/uat/Dockerfile", - "start_line": 31, - "end_line": 32 - }, - "alert_details": { - "artifact": "deployment/npintdedatassets/uat/Dockerfile", - "type": "iacMisconfigurations", - "vulnerability": "AVD-DS-0015", - "severity": "HIGH", - "message": "'yum clean all' is missed: yum -y update && yum -y install htop procps", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2024-07-08T15:03:04.312Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/deployment/npintdedatassets/uat/Dockerfile", - "start_line": "31", - "end_line": "32", - "alert_hash": "b381fbe6ae83d5a743ac7a26ba1b3c52" - }, - "rule_details": { - "type": "iacMisconfigurations", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run" - } - }, - { - "metadata": { - "alert_number": 314, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/314", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/314", - "rule_id": "AVD-DS-0015", - "rule_name": "iacMisconfigurations", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "iacMisconfigurations", - "security" - ], - "help_uri": "https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "deployment/npintdedatassets/dev/Dockerfile", - "start_line": 28, - "end_line": 29 - }, - "alert_details": { - "artifact": "deployment/npintdedatassets/dev/Dockerfile", - "type": "iacMisconfigurations", - "vulnerability": "AVD-DS-0015", - "severity": "HIGH", - "message": "'yum clean all' is missed: yum -y update && yum -y install htop procps", - "repository": "absa-group/AUL", - "reachable": "False", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2024-07-08T15:03:04.312Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/deployment/npintdedatassets/dev/Dockerfile", - "start_line": "28", - "end_line": "29", - "alert_hash": "ac17dd123ba3ae1402e7dc369c0f8c86" - }, - "rule_details": { - "type": "iacMisconfigurations", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": null, - "published_date": null, - "package_name": null, - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#run" - } - }, - { - "metadata": { - "alert_number": 313, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/313", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/313", - "rule_id": "CVE-2026-25940", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-25940", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-25940", - "severity": "HIGH", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to 4.2.0, user control of properties and methods of the Acroform module allows users to inject arbitrary PDF objects, such as JavaScript actions. If given the possibility to pass unsanitized input to one of the following property, a user can inject arbitrary PDF objects, such as JavaScript actions, which are executed when the victim hovers over the radio option. The vulnerability has been fixed in jsPDF@4.2.0. As a workaround, sanitize user input before passing it to the vulnerable API members. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-20T18:30:18.304Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "ced40b9c3a54c00e009fc48a7563937b" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "4.2.0", - "published_date": "2026-02-19T16:27:15.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-25940" - } - }, - { - "metadata": { - "alert_number": 312, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/312", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/312", - "rule_id": "CVE-2026-25755", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-25755", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-25755", - "severity": "HIGH", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to 4.2.0, user control of the argument of the `addJS` method allows an attacker to inject arbitrary PDF objects into the generated document. By crafting a payload that escapes the JavaScript string delimiter, an attacker can execute malicious actions or alter the document structure, impacting any user who opens the generated PDF. The vulnerability has been fixed in jspdf@4.2.0. As a workaround, escape parentheses in user-provided JavaScript code before passing them to the `addJS` method. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-20T18:30:18.304Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "068f963657211cd416dac1f9b30d606c" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "4.2.0", - "published_date": "2026-02-19T15:16:12.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-25755" - } - }, - { - "metadata": { - "alert_number": 311, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/311", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/311", - "rule_id": "CVE-2026-25535", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-25535", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-25535", - "severity": "HIGH", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to 4.2.0, user control of the first argument of the `addImage` method results in denial of service. If given the possibility to pass unsanitized image data or URLs to the `addImage` method, a user can provide a harmful GIF file that results in out of memory errors and denial of service. Harmful GIF files have large width and/or height entries in their headers, which lead to excessive memory allocation. Other affected methods are: `html`. The vulnerability has been fixed in jsPDF 4.2.0. As a workaround, sanitize image data or URLs before passing it to the addImage method or one of the other affected methods. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-20T18:30:18.304Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "ac1c3b775bbec2fba1cca03ec23b344f" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "4.2.0", - "published_date": "2026-02-19T15:16:12.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-25535" - } - }, - { - "metadata": { - "alert_number": 310, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/310", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/310", - "rule_id": "CVE-2026-24737", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-24737", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-24737", - "severity": "HIGH", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to 4.1.0, user control of properties and methods of the Acroform module allows users to inject arbitrary PDF objects, such as JavaScript actions. If given the possibility to pass unsanitized input to one of the following methods or properties, a user can inject arbitrary PDF objects, such as JavaScript actions, which are executed when the victim opens the document. The vulnerable API members are AcroformChoiceField.addOption, AcroformChoiceField.setOptions, AcroFormCheckBox.appearanceState, and AcroFormRadioButton.appearanceState. The vulnerability has been fixed in jsPDF@4.1.0. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-03T17:28:49.534Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "e16a4d0fd4bef1ec931fd2c6830d2f78" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "4.1.0", - "published_date": "2026-02-02T23:16:08.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-24737" - } - }, - { - "metadata": { - "alert_number": 309, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/309", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/309", - "rule_id": "CVE-2026-24133", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-24133", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-24133", - "severity": "HIGH", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to 4.1.0, user control of the first argument of the addImage method results in denial of service. If given the possibility to pass unsanitized image data or URLs to the addImage method, a user can provide a harmful BMP file that results in out of memory errors and denial of service. Harmful BMP files have large width and/or height entries in their headers, which lead to excessive memory allocation. The html method is also affected. The vulnerability has been fixed in jsPDF@4.1.0. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-02-03T17:28:49.534Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "9feff282daef33fec8fdb40b3f095270" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "HIGH", - "cwe": "N/A", - "fixed_version": "4.1.0", - "published_date": "2026-02-02T23:16:08.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2026-24133" - } - }, - { - "metadata": { - "alert_number": 308, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/308", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/308", - "rule_id": "CVE-2025-68428", - "rule_name": "vulnerabilities", - "severity": "critical", - "confidence": "error", - "tags": [ - "CRITICAL", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2025-68428", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 54, - "end_line": 54 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2025-68428", - "severity": "CRITICAL", - "message": "jsPDF is a library to generate PDFs in JavaScript. Prior to version 4.0.0, user control of the first argument of the loadFile method in the node.js build allows local file inclusion/path traversal. If given the possibility to pass unsanitized paths to the loadFile method, a user can retrieve file contents of arbitrary files in the local file system the node process is running in. The file contents are included verbatim in the generated PDFs. Other affected methods are `addImage`, `html`, and `addFont`. Only the node.js builds of the library are affected, namely the `dist/jspdf.node.js` and `dist/jspdf.node.min.js` files. The vulnerability has been fixed in jsPDF@4.0.0. This version restricts file system access per default. This semver-major update does not introduce other breaking changes. Some workarounds areavailable. With recent node versions, jsPDF recommends using the `--permission` flag in production. The feature was introduced experimentally in v20.0.0 and is stable since v22.13.0/v23.5.0/v24.0.0. For older node versions, sanitize user-provided paths before passing them to jsPDF. (This package is used under: jspdf@3.0.3)", - "repository": "absa-group/AUL", - "reachable": "True", - "scan_date": "2026-03-05T12:38:46.586Z", - "first_seen": "2026-01-06T15:02:47.936Z", - "scm_file": "https://github.com/absa-group/AUL/blob/6f06cf37c8b828a2f5d677ee022401d0352d505f/aul-ui/package.json", - "installed_version": "3.0.3", - "start_line": "54", - "end_line": "54", - "alert_hash": "df6272dd2a5d534f09fb168035413217" - }, - "rule_details": { - "type": "vulnerabilities", - "severity": "CRITICAL", - "cwe": "N/A", - "fixed_version": "4.0.0", - "published_date": "2026-01-05T22:15:51.000Z", - "package_name": "jspdf", - "category": "N/A", - "impact": "N/A", - "confidence": "N/A", - "likelihood": "N/A", - "remediation": "N/A", - "owasp": "N/A", - "references": "- https://access.redhat.com/security/cve/CVE-2025-68428" - } - }, - { - "metadata": { - "alert_number": 307, - "state": "open", - "created_at": "2026-02-25T08:25:18Z", - "updated_at": "2026-02-25T14:11:06Z", - "url": "https://api.github.com/repos/absa-group/AUL/code-scanning/alerts/307", - "alert_url": "https://github.com/absa-group/AUL/security/code-scanning/307", - "rule_id": "CVE-2026-22610", - "rule_name": "vulnerabilities", - "severity": "high", - "confidence": "error", - "tags": [ - "HIGH", - "security", - "vulnerabilities" - ], - "help_uri": "https://access.redhat.com/security/cve/CVE-2026-22610", - "tool": "AquaSec", - "tool_version": "1.0.0", - "ref": "refs/heads/master", - "commit_sha": "ddf45230ffb7be877ca61d00aa48f1b1ad222ad7", - "instance_url": null, - "classifications": [], - "file": "aul-ui/package.json", - "start_line": 33, - "end_line": 33 - }, - "alert_details": { - "artifact": "aul-ui/package.json", - "type": "vulnerabilities", - "vulnerability": "CVE-2026-22610", - "severity": "HIGH", - "message": "Angular is a development platform for building mobile and desktop web applications using TypeScript/JavaScript and other languages. Prior to versions 19.2.18, 20.3.16, 21.0.7, and 21.1.0-rc.0, a cross-site scripting (XSS) vulnerability has been identified in the Angular Template Compiler. The vulnerability exists because Angular’s internal sanitization schema fails to recognize the href and xlink:href attributes of SVG