diff --git a/README.md b/README.md
index 1232e383544ae22d5a395cf37069c32c67a5fe3d..f821363166f10e45f29e2dba01b2d88ba9a3395e 100644
--- a/README.md
+++ b/README.md
@@ -67,13 +67,15 @@ It is bound to the `build` stage, and uses the following variables:
 
 | Name                     | description                        | default value     |
 | ------------------------ | ---------------------------------- | ----------------- |
-| `PYLINT_ARGS`            | Additional [pylint CLI options](http://pylint.pycqa.org/en/latest/user_guide/run.html#command-line-options)      |  _none_           |
+| `PYLINT_ARGS`            | Additional [pylint CLI options](http://pylint.pycqa.org/en/latest/user_guide/run.html#command-line-options) |  _none_           |
 | `PYLINT_FILES`           | Files or directories to analyse   | _none_ (by default analyses all found python source files) |
 
-This job produces the following artifacts, kept for one day:
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
 
-* Code quality json report in code climate format.
-* Pylint report for SonarQube (if `SONAR_URL` is defined).
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/py-lint.codeclimate.json` | [Code Climate](https://docs.codeclimate.com/docs/pylint) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) |
+| `$PYTHON_PROJECT_DIR/reports/py-lint.parseable.txt` | [parseable](https://pylint.pycqa.org/en/latest/user_guide/usage/output.html) | [SonarQube integration](https://docs.sonarqube.org/latest/analysis/external-issues/) |
 
 ### Test jobs
 
@@ -97,14 +99,7 @@ It is bound to the `build` stage, and uses the following variables:
 | ------------------------ | -------------------------------------------------------------------- | ----------------------- |
 | `UNITTEST_ARGS`          | Additional xmlrunner/unittest CLI options                            | _none_                  |
 
-This job produces the following artifacts, kept for one day:
-
-* JUnit test report (using the [xmlrunner](https://github.com/xmlrunner/unittest-xml-reporting) module)
-* code coverage report (cobertura xml format).
-
-:warning: code coverage report artifact is disabled, due to a deprecated syntax, see [Activate code coverage report artifact](#activate-code-coverage-report-artifact)
-
-:warning: create a `.coveragerc` file at the root of your Python project to control the coverage settings.
+:information_source: use a `.coveragerc` file at the root of your Python project to control the coverage settings.
 
 Example:
 
@@ -118,6 +113,13 @@ source =
     module_2
 ```
 
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
+
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/test-execution-parameters/#header-8) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/python-test-coverage/) |
+
 #### `py-pytest` job
 
 This job is **disabled by default** and performs tests based on [pytest](https://docs.pytest.org/en/latest/) Python lib.
@@ -129,14 +131,7 @@ It is bound to the `build` stage, and uses the following variables:
 | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- |
 | `PYTEST_ARGS`            | Additional [pytest](https://docs.pytest.org/en/stable/usage.html) or [pytest-cov](https://github.com/pytest-dev/pytest-cov#usage) CLI options | _none_                  |
 
-This job produces the following artifacts, kept for one day:
-
-* JUnit test report (with the [`--junit-xml`](http://doc.pytest.org/en/latest/usage.html#creating-junitxml-format-files) argument)
-* code coverage report (cobertura xml format).
-
-:warning: code coverage report artifact is disabled, due to a deprecated syntax, see [Activate code coverage report artifact](#activate-code-coverage-report-artifact)
-
-:warning: create a `.coveragerc` file at the root of your Python project to control the coverage settings.
+:information_source: use a `.coveragerc` file at the root of your Python project to control the coverage settings.
 
 Example:
 
@@ -150,6 +145,13 @@ source =
     module_2
 ```
 
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
+
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/test-execution-parameters/#header-8) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/python-test-coverage/) |
+
 #### `py-nosetest` job
 
 This job is **disabled by default** and performs tests based on [nose](https://nose.readthedocs.io/en/latest/) Python lib.
@@ -161,17 +163,17 @@ It is bound to the `build` stage, and uses the following variables:
 | ------------------------ | --------------------------------------------------------------------------------------- | ----------------------- |
 | `NOSETESTS_ARGS`         | Additional [nose CLI options](https://nose.readthedocs.io/en/latest/usage.html#options) | _none_                  |
 
-By default coverage will be run on all the directory. You can restrict it to your packages by setting NOSE_COVER_PACKAGE variable.
+By default coverage will be run on all the project directories. You can restrict it to your packages by setting the `$NOSE_COVER_PACKAGE` variable.
 More [info](https://nose.readthedocs.io/en/latest/plugins/cover.html)
 
-This job produces the following artifacts, kept for one day:
-
-* JUnit test report (with the [`--with-xunit`](https://nose.readthedocs.io/en/latest/plugins/xunit.html) argument)
-* code coverage report (cobertura xml format + html report).
+:information_source: use a `.coveragerc` file at the root of your Python project to control the coverage settings.
 
-:warning: code coverage report artifact is disabled, due to a deprecated syntax, see [Activate code coverage report artifact](#activate-code-coverage-report-artifact)
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
 
-:warning: create a `.coveragerc` file at the root of your Python project or use [nose CLI options](https://nose.readthedocs.io/en/latest/plugins/cover.html#options) to control the coverage settings.
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/TEST-*.xml` | [xUnit](https://en.wikipedia.org/wiki/XUnit) test report(s) | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportsjunit) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/test-execution-parameters/#header-8) |
+| `$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml` | [Cobertura XML](https://gcovr.com/en/stable/output/cobertura.html) coverage report | [GitLab integration](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscoverage_report) & [SonarQube integration](https://docs.sonarqube.org/latest/analysis/test-coverage/python-test-coverage/) |
 
 #### `py-compile` job
 
@@ -184,58 +186,6 @@ It is bound to the `build` stage, and uses the following variables:
 | --------------------- | ----------------------------------------------------------------------------- | ------------- |
 | `PYTHON_COMPILE_ARGS` | [`compileall` CLI options](https://docs.python.org/3/library/compileall.html) | `*`           |
 
-#### Activate code coverage report artifact
-
-Code coverage report artifact is disabled, due to a [deprecated syntax](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78132).
-
-In order to activate code coverage report artifact, you need to override your actual unit test job depending on our GitLab version.
-
-Here is an example with `py-pytest` job (change to `py-unittest` or `py-nosetests` depending on your unit tests library):
-
-* for GitLab < 14.10:
-```yaml
-py-pytest:
-  artifacts:
-    reports:
-      cobertura: $PYTHON_PROJECT_DIR/reports/coverage.xml
-```
-* for GitLab >= 14.10:
-```yaml
-py-pytest:
-  artifacts:
-    reports:
-      coverage_report:
-        ​coverage_format: cobertura
-        path: ​$PYTHON_PROJECT_DIR/reports/coverage.xml
-```
-
-### SonarQube analysis
-
-If you're using the SonarQube template to analyse your Python code, here is a sample `sonar-project.properties` file:
-
-```properties
-# see: https://docs.sonarqube.org/latest/analysis/languages/python/
-# set your source directory(ies) here (relative to the sonar-project.properties file)
-sonar.sources=.
-# exclude unwanted directories and files from being analysed
-sonar.exclusions=**/test_*.py
-
-# set your tests directory(ies) here (relative to the sonar-project.properties file)
-sonar.tests=.
-sonar.test.inclusions=**/test_*.py
-
-# tests report: generic format
-sonar.python.xunit.reportPath=reports/unittest/TEST-*.xml
-# coverage report: XUnit format
-sonar.python.coverage.reportPaths=reports/coverage.xml
-```
-
-More info:
-
-* [Python language support](https://docs.sonarqube.org/latest/analysis/languages/python/)
-* [test coverage & execution parameters](https://docs.sonarqube.org/latest/analysis/coverage/)
-* [third-party issues](https://docs.sonarqube.org/latest/analysis/external-issues/)
-
 ### `py-bandit` job (SAST)
 
 This job is **disabled by default** and performs a [Bandit](https://pypi.org/project/bandit/) analysis.
@@ -247,8 +197,12 @@ It is bound to the `test` stage, and uses the following variables:
 | `BANDIT_ENABLED` | Set to `true` to enable Bandit analysis                                | _none_ (disabled) |
 | `BANDIT_ARGS`    | Additional [Bandit CLI options](https://github.com/PyCQA/bandit#usage) | `--recursive .`   |
 
-This job outputs a **textual report** in the console, and in case of failure also exports a JSON report in the `reports/`
-directory _(relative to project root dir)_.
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
+
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/py-bandit.bandit.csv` | [CSV](https://bandit.readthedocs.io/en/latest/formatters/csv.html) | [SonarQube integration](https://docs.sonarqube.org/latest/analysis/external-issues/)<br/>_This report is generated only if SonarQube template is detected_ |
+| `$PYTHON_PROJECT_DIR/reports/py-bandit.bandit.json` | [JSON](https://bandit.readthedocs.io/en/latest/formatters/json.html) | [DefectDojo integration](https://defectdojo.github.io/django-DefectDojo/integrations/parsers/#bandit)<br/>_This report is generated only if DefectDojo template is detected_ |
 
 ### `py-safety` job (dependency check)
 
@@ -261,9 +215,6 @@ It is bound to the `test` stage, and uses the following variables:
 | `SAFETY_ENABLED` | Set to `true` to enable Safety job                                      | _none_ (disabled) |
 | `SAFETY_ARGS`    | Additional [Safety CLI options](https://github.com/pyupio/safety#usage) | `--full-report`   |
 
-This job outputs a **textual report** in the console, and in case of failure also exports a JSON report in the `reports/`
-directory _(relative to project root dir)_.
-
 ### `py-trivy` job (dependency check)
 
 This job is **disabled by default** and performs a dependency check analysis using [Trivy](https://github.com/aquasecurity/trivy/).
@@ -275,8 +226,42 @@ It is bound to the `test` stage, and uses the following variables:
 | `PYTHON_TRIVY_ENABLED` | Set to `true` to enable Trivy job                                 | _none_ (disabled) |
 | `PYTHON_TRIVY_ARGS`    | Additional [Trivy CLI options](https://aquasecurity.github.io/trivy/v0.21.1/getting-started/cli/fs/) | `--vuln-type library`   |
 
-This job outputs a **textual report** in the console, and in case of failure also exports a JSON report in the `reports/`
-directory _(relative to project root dir)_.
+In addition to a textual report in the console, this job produces the following reports, kept for one day:
+
+| Report         | Format                                                                       | Usage             |
+| -------------- | ---------------------------------------------------------------------------- | ----------------- |
+| `$PYTHON_PROJECT_DIR/reports/py-trivy.trivy.json` | [JSON](https://aquasecurity.github.io/trivy/latest/docs/vulnerability/examples/report/#json) | [DefectDojo integration](https://defectdojo.github.io/django-DefectDojo/integrations/parsers/#trivy)<br/>_This report is generated only if DefectDojo template is detected_ |
+
+### SonarQube analysis
+
+If you're using the SonarQube template to analyse your Python code, here is a sample `sonar-project.properties` file:
+
+```properties
+# see: https://docs.sonarqube.org/latest/analysis/languages/python/
+# set your source directory(ies) here (relative to the sonar-project.properties file)
+sonar.sources=.
+# exclude unwanted directories and files from being analysed
+sonar.exclusions=**/test_*.py
+
+# set your tests directory(ies) here (relative to the sonar-project.properties file)
+sonar.tests=.
+sonar.test.inclusions=**/test_*.py
+
+# tests report: xUnit format
+sonar.python.xunit.reportPath=reports/unittest/TEST-*.xml
+# coverage report: Cobertura format
+sonar.python.coverage.reportPaths=reports/py-coverage.cobertura.xml
+# pylint: parseable format (if enabled)
+sonar.python.pylint.reportPaths=reports/py-lint.parseable.txt
+# Bandit: CSV format (if enabled)
+sonar.python.bandit.reportPaths=reports/py-bandit.bandit.csv
+```
+
+More info:
+
+* [Python language support](https://docs.sonarqube.org/latest/analysis/languages/python/)
+* [test coverage & execution parameters](https://docs.sonarqube.org/latest/analysis/coverage/)
+* [third-party issues](https://docs.sonarqube.org/latest/analysis/external-issues/)
 
 ### `py-release` job
 
diff --git a/templates/gitlab-ci-python.yml b/templates/gitlab-ci-python.yml
index 706f58942f0bea5f24ac0d45e4e156def8c48cf3..7f34610dd41ba47230d0a96f0375693c9d3fe4db 100644
--- a/templates/gitlab-ci-python.yml
+++ b/templates/gitlab-ci-python.yml
@@ -604,34 +604,19 @@ py-lint:
   extends: .python-base
   stage: build
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
-    - _pip install pylint_gitlab
-    - |
-      if ! _run pylint --ignore=.cache --output-format=text  ${PYLINT_ARGS}  ${PYLINT_FILES:-$(find -type f -name "*.py")}
-      then
-        # failed: also generate codeclimate report
-        _run pylint --ignore=.cache --output-format=pylint_gitlab.GitlabCodeClimateReporter  ${PYLINT_ARGS} ${PYLINT_FILES:-$(find -type f -name "*.py")} > reports/pylint-codeclimate.json
-        exit 1
-      else
-        # success: generate empty codeclimate report (required by GitLab :( )
-        echo "[]" > reports/pylint-codeclimate.json
-      fi
-    - |
-      if [ -n "$SONAR_URL" ]
-      then
-        # SonarQube is configured, export analysis report
-        _run pylint --ignore=.cache --output-format=parseable  ${PYLINT_ARGS} ${PYLINT_FILES:-$(find -type f -name "*.py")} > reports/pylint.txt
-      fi
+    - _pip install pylint_gitlab # codeclimate reports
+    # run pylint and generate reports all at once
+    - _run pylint --ignore=.cache --output-format=colorized,pylint_gitlab.GitlabCodeClimateReporter:reports/py-lint.codeclimate.json,parseable:reports/py-lint.parseable.txt ${PYLINT_ARGS} ${PYLINT_FILES:-$(find -type f -name "*.py")}
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
     expire_in: 1 day
     when: always
     reports:
-      codequality: $PYTHON_PROJECT_DIR/reports/pylint-codeclimate.json
+      codequality: $PYTHON_PROJECT_DIR/reports/py-lint.codeclimate.json
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/py-lint.*"
   rules:
     # exclude if $PYLINT_ENABLED not set
     - if: '$PYLINT_ENABLED != "true"'
@@ -657,8 +642,7 @@ py-unittest:
   extends: .python-base
   stage: build
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
     # code coverage
     - _pip install coverage
@@ -666,7 +650,7 @@ py-unittest:
     - _pip install unittest-xml-reporting
     - _run coverage run -m xmlrunner discover -o "reports/" $UNITTEST_ARGS
     - _run coverage report -m
-    - _run coverage xml -o "reports/coverage.xml"
+    - _run coverage xml -o "reports/py-coverage.cobertura.xml"
   coverage: /^TOTAL.+?(\d+\%)$/
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
@@ -674,16 +658,13 @@ py-unittest:
     when: always
     reports:
       junit:
-        - $PYTHON_PROJECT_DIR/reports/TEST-*.xml
-      # declaring the Cobertura report depends on the GitLab version :(
-      # GitLab < 14.10
-      # cobertura: $PYTHON_PROJECT_DIR/reports/coverage.xml
-      # GitLab >= 14.10
-      # coverage_report:
-      #   ​coverage_format: cobertura
-      #   path: ​$PYTHON_PROJECT_DIR/reports/coverage.xml
+        - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      coverage_report:
+        coverage_format: cobertura
+        path: "$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml"
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      - "$PYTHON_PROJECT_DIR/reports/py-coverage.*"
   rules:
     # skip if $UNITTEST_ENABLED not set
     - if: '$UNITTEST_ENABLED != "true"'
@@ -694,11 +675,10 @@ py-pytest:
   extends: .python-base
   stage: build
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
     - _pip install pytest pytest-cov coverage
-    - _python -m pytest --junit-xml=reports/TEST-pytests.xml --cov --cov-report term  --cov-report xml:reports/coverage.xml ${PYTEST_ARGS}
+    - _python -m pytest --junit-xml=reports/TEST-pytests.xml --cov --cov-report term  --cov-report xml:reports/py-coverage.cobertura.xml ${PYTEST_ARGS}
   coverage: /^TOTAL.+?(\d+\%)$/
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
@@ -706,16 +686,13 @@ py-pytest:
     when: always
     reports:
       junit:
-        - $PYTHON_PROJECT_DIR/reports/TEST-*.xml
-      # declaring the Cobertura report depends on the GitLab version :(
-      # GitLab < 14.10
-      # cobertura: $PYTHON_PROJECT_DIR/reports/coverage.xml
-      # GitLab >= 14.10
-      # coverage_report:
-      #   ​coverage_format: cobertura
-      #   path: ​$PYTHON_PROJECT_DIR/reports/coverage.xml
+        - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      coverage_report:
+        coverage_format: cobertura
+        path: "$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml"
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      - "$PYTHON_PROJECT_DIR/reports/py-coverage.*"
   rules:
     # skip if $PYTEST_ENABLED not set
     - if: '$PYTEST_ENABLED != "true"'
@@ -726,10 +703,9 @@ py-nosetests:
   extends: .python-base
   stage: build
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
-    - _run nosetests --with-xunit --xunit-file=reports/TEST-nosetests.xml --with-coverage --cover-erase --cover-xml --cover-xml-file=reports/coverage.xml --cover-html --cover-html-dir=reports/coverage ${NOSETESTS_ARGS}
+    - _run nosetests --with-xunit --xunit-file=reports/TEST-nosetests.xml --with-coverage --cover-erase --cover-xml --cover-xml-file=reports/py-coverage.cobertura.xml ${NOSETESTS_ARGS}
   coverage: /^TOTAL.+?(\d+\%)$/
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
@@ -737,16 +713,13 @@ py-nosetests:
     when: always
     reports:
       junit:
-        - $PYTHON_PROJECT_DIR/reports/TEST-*.xml
-      # declaring the Cobertura report depends on the GitLab version :(
-      # GitLab < 14.10
-      # cobertura: $PYTHON_PROJECT_DIR/reports/coverage.xml
-      # GitLab >= 14.10
-      # coverage_report:
-      #   coverage_format: cobertura
-      #   path: ​$PYTHON_PROJECT_DIR/reports/coverage.xml
+        - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      coverage_report:
+        coverage_format: cobertura
+        path: "$PYTHON_PROJECT_DIR/reports/py-coverage.cobertura.xml"
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/TEST-*.xml"
+      - "$PYTHON_PROJECT_DIR/reports/py-coverage.*"
   rules:
     # skip if $NOSETESTS_ENABLED not set
     - if: '$NOSETESTS_ENABLED != "true"'
@@ -760,23 +733,28 @@ py-bandit:
   # force no dependencies
   dependencies: []
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
     - _pip install bandit
+    # CSV (for SonarQube)
     - |
-      if ! _run bandit ${TRACE+--verbose} ${BANDIT_ARGS}
+      if [[ "$SONAR_HOST_URL"]]
       then
-        # failed: also generate JSON report
-        _run bandit ${TRACE+--verbose} --format json --output reports/bandit.json ${BANDIT_ARGS}
-        exit 1
+        _run bandit ${TRACE+--verbose} --exit-zero --format csv --output reports/py-bandit.bandit.csv ${BANDIT_ARGS}
+      fi
+    # JSON (for DefectDojo)
+    - |
+      if [[ "$DEFECTDOJO_BANDIT_REPORTS"]]
+      then
+        _run bandit ${TRACE+--verbose} --exit-zero --format json --output reports/py-bandit.bandit.json ${BANDIT_ARGS}
       fi
+    - _run bandit ${TRACE+--verbose} ${BANDIT_ARGS}
   artifacts:
     when: always
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
     expire_in: 1 day
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/py-bandit.*"
   rules:
     # exclude if $BANDIT_ENABLED not set
     - if: '$BANDIT_ENABLED != "true"'
@@ -790,24 +768,10 @@ py-safety:
   # force no dependencies
   dependencies: []
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
     - _pip install safety
-    - |
-      if ! _pip freeze | _run safety check --stdin ${SAFETY_ARGS}
-      then
-        # failed: also generate JSON report
-
-        _pip freeze | _run safety check --stdin --json --output reports/safety.json ${SAFETY_ARGS}
-        exit 1
-      fi
-  artifacts:
-    name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
-    expire_in: 1 day
-    when: always
-    paths:
-      - $PYTHON_PROJECT_DIR/reports/
+    - _pip freeze | _run safety check --stdin ${SAFETY_ARGS}
   rules:
     # exclude if $SAFETY_ENABLED not set
     - if: '$SAFETY_ENABLED != "true"'
@@ -821,8 +785,7 @@ py-trivy:
   # force no dependencies
   dependencies: []
   script:
-    - mkdir -p reports
-    - chmod o+rwx reports
+    - mkdir -p -m 777 reports
     - install_requirements
     - apt-get update
     - apt-get install -y wget apt-transport-https gnupg lsb-release
@@ -853,15 +816,18 @@ py-trivy:
         log_error "Could not find a file listing all dependencies with their versions."
         exit 1
       fi
-      trivy fs ${PYTHON_TRIVY_ARGS} --format table --exit-code 0 ./reports/
-      trivy fs ${PYTHON_TRIVY_ARGS} --format cyclonedx --output ./reports/sbom_cyclonedx.json --exit-code 0 ./reports/
-      trivy fs ${PYTHON_TRIVY_ARGS} --format json --output reports/trivy-python.json --list-all-pkgs --exit-code 1 ./reports/
+      if [[ "$DEFECTDOJO_TRIVY_REPORTS" ]]
+      then
+        trivy fs ${PYTHON_TRIVY_ARGS} --exit-code 0 --list-all-pkgs --format json --output reports/py-trivy.trivy.json ./reports/
+      fi
+      trivy fs ${PYTHON_TRIVY_ARGS} --format table ./reports/
   artifacts:
     name: "$CI_JOB_NAME artifacts from $CI_PROJECT_NAME on $CI_COMMIT_REF_SLUG"
     expire_in: 1 day
     when: always
     paths:
-      - $PYTHON_PROJECT_DIR/reports/
+      - "$PYTHON_PROJECT_DIR/reports/py-trivy.*"
+      - "$PYTHON_PROJECT_DIR/reports/requirements.txt"
   rules:
     # exclude if $PYTHON_TRIVY_ENABLED not set
     - if: '$PYTHON_TRIVY_ENABLED != "true"'