diff --git a/.github/dependabot.yml b/.github/dependabot.yml index cc034090..4aa4182d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -12,30 +12,15 @@ updates: directory: "/" schedule: interval: "weekly" - - # ── Non-production ecosystems (no real deps, suppress everything) ──── - # The repo has no production npm/pip/go/gradle/cargo dependencies. - # A root-level ignore-all entry ensures security updates are also - # suppressed, since per-directory entries with globs don't reliably - # block them. - - package-ecosystem: "npm" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 0 - labels: [] - ignore: - - dependency-name: "*" - - package-ecosystem: "pip" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 0 - labels: [] - ignore: - - dependency-name: "*" - - package-ecosystem: "uv" - directory: "/" +# ── BEGIN GENERATED: test fixture suppressions + # Auto-generated by scripts/sync-dependabot-config.sh. + # Do not edit manually — run the script to update. + - package-ecosystem: "cargo" + directories: + - "/src/test/resources/tst_manifests/cargo/license/cargo_with_license" + - "/src/test/resources/tst_manifests/cargo/license/cargo_without_license" + - "/src/test/resources/tst_manifests/cargo/license/cargo_workspace_license_inheritance" + - "/src/test/resources/tst_manifests/cargo/license/cargo_workspace_license_inheritance_no_license" schedule: interval: "monthly" open-pull-requests-limit: 0 @@ -43,7 +28,22 @@ updates: ignore: - dependency-name: "*" - package-ecosystem: "gomod" - directory: "/" + directories: + - "/src/test/resources/msc/golang" + - "/src/test/resources/msc/golang/mvs_logic" + - "/src/test/resources/tst_manifests/golang/go_mod_light_no_ignore" + - "/src/test/resources/tst_manifests/golang/go_mod_no_ignore" + - "/src/test/resources/tst_manifests/golang/go_mod_no_path" + - "/src/test/resources/tst_manifests/golang/go_mod_with_all_ignore" + - "/src/test/resources/tst_manifests/golang/go_mod_with_ignore" + - "/src/test/resources/tst_manifests/golang/go_mod_with_one_ignored_prefix_go" + - "/src/test/resources/tst_manifests/it/golang" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace/module-a" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace/module-b" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace_missing_module/existing" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace_nested/libs/core" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace_nested/libs/util" + - "/src/test/resources/tst_manifests/workspace/go/go_workspace_single/mymod" schedule: interval: "monthly" open-pull-requests-limit: 0 @@ -51,28 +51,41 @@ updates: ignore: - dependency-name: "*" - package-ecosystem: "gradle" - directory: "/" - schedule: - interval: "monthly" - open-pull-requests-limit: 0 - labels: [] - ignore: - - dependency-name: "*" - - package-ecosystem: "cargo" - directory: "/" + directories: + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_duplicate_different_versions" + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_duplicate_no_version" + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_ignore_full_specification" + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_ignore_named_params" + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_ignore_notations" + - "/src/test/resources/tst_manifests/gradle-groovy/deps_with_no_ignore_common_paths" + - "/src/test/resources/tst_manifests/gradle-groovy/empty" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_duplicate_different_versions" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_duplicate_no_version" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_ignore_full_specification" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_ignore_named_params" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_ignore_notations" + - "/src/test/resources/tst_manifests/gradle-kotlin/deps_with_no_ignore_common_paths" + - "/src/test/resources/tst_manifests/gradle-kotlin/empty" + - "/src/test/resources/tst_manifests/it/gradle-groovy" + - "/src/test/resources/tst_manifests/it/gradle-kotlin" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_missing_subproject" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_missing_subproject/app" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_mixed_variants" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_mixed_variants/app" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_mixed_variants/lib" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_multi_project" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_multi_project/app" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_multi_project/lib" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_nested_subprojects" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_nested_subprojects/libs/core" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_nested_subprojects/libs/util" + - "/src/test/resources/tst_manifests/workspace/gradle/gradle_no_subprojects" schedule: interval: "monthly" open-pull-requests-limit: 0 labels: [] ignore: - dependency-name: "*" - - # ── Test fixture manifests (maven only) ────────────────────────────── - # Maven is the production ecosystem so it cannot use a root-level - # ignore-all. Glob patterns (/**) don't reliably match nested - # subdirectories for security updates, so every directory containing - # a pom.xml is listed explicitly. Update this list when adding new - # test fixtures. - package-ecosystem: "maven" directories: - "/src/test/resources/tst_manifests/it/maven" @@ -85,6 +98,7 @@ updates: - "/src/test/resources/tst_manifests/maven/deps_with_ignore_on_version" - "/src/test/resources/tst_manifests/maven/deps_with_ignore_on_wrong" - "/src/test/resources/tst_manifests/maven/deps_with_no_ignore" + - "/src/test/resources/tst_manifests/maven/deps_with_version_range" - "/src/test/resources/tst_manifests/maven/empty" - "/src/test/resources/tst_manifests/maven/license/pom_with_empty_license" - "/src/test/resources/tst_manifests/maven/license/pom_with_license" @@ -92,8 +106,123 @@ updates: - "/src/test/resources/tst_manifests/maven/license/pom_without_license" - "/src/test/resources/tst_manifests/maven/pom_deps_with_no_ignore_common_paths" - "/src/test/resources/tst_manifests/maven/pom_deps_with_no_ignore_provided_scope" + - "/src/test/resources/tst_manifests/workspace/maven/maven_missing_module" + - "/src/test/resources/tst_manifests/workspace/maven/maven_missing_module/module-a" + - "/src/test/resources/tst_manifests/workspace/maven/maven_multi_module" + - "/src/test/resources/tst_manifests/workspace/maven/maven_multi_module/module-a" + - "/src/test/resources/tst_manifests/workspace/maven/maven_multi_module/module-b" + - "/src/test/resources/tst_manifests/workspace/maven/maven_nested_aggregator" + - "/src/test/resources/tst_manifests/workspace/maven/maven_nested_aggregator/parent" + - "/src/test/resources/tst_manifests/workspace/maven/maven_nested_aggregator/parent/child" + - "/src/test/resources/tst_manifests/workspace/maven/maven_no_modules" + schedule: + interval: "monthly" + open-pull-requests-limit: 0 + labels: [] + ignore: + - dependency-name: "*" + - package-ecosystem: "npm" + directories: + - "/src/test/resources/tst_manifests/it/npm" + - "/src/test/resources/tst_manifests/it/pnpm" + - "/src/test/resources/tst_manifests/it/yarn" + - "/src/test/resources/tst_manifests/npm/deps_with_ignore" + - "/src/test/resources/tst_manifests/npm/deps_with_mixed_dep_types" + - "/src/test/resources/tst_manifests/npm/deps_with_no_ignore" + - "/src/test/resources/tst_manifests/npm/deps_with_no_version_root_dep" + - "/src/test/resources/tst_manifests/npm/empty" + - "/src/test/resources/tst_manifests/npm/license/package_with_legacy_licenses" + - "/src/test/resources/tst_manifests/npm/license/package_with_license" + - "/src/test/resources/tst_manifests/npm/license/package_without_license" + - "/src/test/resources/tst_manifests/pnpm/deps_with_ignore" + - "/src/test/resources/tst_manifests/pnpm/deps_with_mixed_dep_types" + - "/src/test/resources/tst_manifests/pnpm/deps_with_no_ignore" + - "/src/test/resources/tst_manifests/workspace/package_json_array" + - "/src/test/resources/tst_manifests/workspace/package_json_array/packages/lib-a" + - "/src/test/resources/tst_manifests/workspace/package_json_array/packages/lib-b" + - "/src/test/resources/tst_manifests/workspace/package_json_object" + - "/src/test/resources/tst_manifests/workspace/package_json_object/packages/mod-a" + - "/src/test/resources/tst_manifests/workspace/pnpm_workspace/packages/pkg-a" + - "/src/test/resources/tst_manifests/workspace/pnpm_workspace/packages/pkg-b" + - "/src/test/resources/tst_manifests/workspace/with_invalid" + - "/src/test/resources/tst_manifests/workspace/with_invalid/packages/no_name" + - "/src/test/resources/tst_manifests/workspace/with_invalid/packages/no_version" + - "/src/test/resources/tst_manifests/workspace/with_invalid/packages/valid" + - "/src/test/resources/tst_manifests/yarn-berry/deps_with_ignore" + - "/src/test/resources/tst_manifests/yarn-berry/deps_with_mixed_dep_types" + - "/src/test/resources/tst_manifests/yarn-berry/deps_with_no_ignore" + - "/src/test/resources/tst_manifests/yarn-classic/deps_with_ignore" + - "/src/test/resources/tst_manifests/yarn-classic/deps_with_mixed_dep_types" + - "/src/test/resources/tst_manifests/yarn-classic/deps_with_no_ignore" + schedule: + interval: "monthly" + open-pull-requests-limit: 0 + labels: [] + ignore: + - dependency-name: "*" + - package-ecosystem: "pip" + directories: + - "/src/test/resources/tst_manifests/it/pypi" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_ignore" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_no_ignore" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_no_metadata" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_pep621_license" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry_license" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_uv" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_uv_ignore" + - "/src/test/resources/tst_manifests/pip/pip_requirements_txt_ignore" + - "/src/test/resources/tst_manifests/pip/pip_requirements_txt_marker_installed" + - "/src/test/resources/tst_manifests/pip/pip_requirements_txt_marker_skip" + - "/src/test/resources/tst_manifests/pip/pip_requirements_txt_no_ignore" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace/packages/mid-pkg" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace/packages/sub-pkg" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude/packages/core" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude/packages/internal" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested/apps/backend" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested/libs/core" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_no_config" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_no_lock" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual/packages/pkg-a" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual/packages/pkg-b" + schedule: + interval: "monthly" + open-pull-requests-limit: 0 + labels: [] + ignore: + - dependency-name: "*" + - package-ecosystem: "uv" + directories: + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_ignore" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_no_ignore" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_no_metadata" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_pep621_license" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_poetry_license" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_uv" + - "/src/test/resources/tst_manifests/pip/pip_pyproject_toml_uv_ignore" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace/packages/mid-pkg" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace/packages/sub-pkg" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude/packages/core" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_exclude/packages/internal" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested/apps/backend" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_nested/libs/core" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_no_config" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_no_lock" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual/packages/pkg-a" + - "/src/test/resources/tst_manifests/workspace/uv/uv_workspace_virtual/packages/pkg-b" schedule: interval: "monthly" + open-pull-requests-limit: 0 labels: [] ignore: - dependency-name: "*" + # ── END GENERATED: test fixture suppressions diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index e0937e54..4249f3fd 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -7,6 +7,14 @@ on: - main jobs: + dependabot-config: + name: Verify dependabot config + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Check dependabot.yml covers all test fixture manifests + run: bash scripts/sync-dependabot-config.sh --check + build: runs-on: ubuntu-latest name: Build and test Java ${{ matrix.java }} diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 66af48f9..62ddcaf2 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -83,7 +83,7 @@ src/main/java/io/github/guacsec/trustifyda/ ## Test Fixtures -- **Dependabot suppression**: Test fixture directories contain intentionally pinned (sometimes vulnerable) dependencies. When adding a new test fixture directory with a manifest file, review `.github/dependabot.yml` to ensure the new path is covered. Non-maven ecosystems are suppressed via root-level `ignore: [{dependency-name: "*"}]` entries. Maven fixtures use per-directory entries with `/**` globs since maven is the production ecosystem; add the parent directory if a new maven fixture tree is introduced. +- **Dependabot suppression**: Test fixture directories contain intentionally pinned (sometimes vulnerable) dependencies. Every directory containing a manifest file must be explicitly listed in `.github/dependabot.yml` with `ignore: [{dependency-name: "*"}]` to suppress both version and security update PRs. Run `scripts/sync-dependabot-config.sh` to regenerate the fixture entries automatically. CI enforces this via `--check` mode in the PR workflow. ## Dependencies diff --git a/scripts/sync-dependabot-config.sh b/scripts/sync-dependabot-config.sh new file mode 100755 index 00000000..df4aae2f --- /dev/null +++ b/scripts/sync-dependabot-config.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash +# Synchronise the test-fixture entries in .github/dependabot.yml. +# +# Usage: +# scripts/sync-dependabot-config.sh # update dependabot.yml in place +# scripts/sync-dependabot-config.sh --check # exit non-zero if out of date +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +DEPENDABOT_YML="$REPO_ROOT/.github/dependabot.yml" +BEGIN_MARKER="# ── BEGIN GENERATED: test fixture suppressions" +END_MARKER="# ── END GENERATED: test fixture suppressions" + +check_mode=false +if [[ "${1:-}" == "--check" ]]; then + check_mode=true +fi + +# ── Manifest → ecosystem mapping ───────────────────────────────────── +# Dependabot ecosystem names: +# pom.xml → maven +# build.gradle(.kts) → gradle +# requirements.txt → pip +# pyproject.toml → pip, uv (both scanners pick it up) +# go.mod → gomod +# Cargo.toml → cargo +# package.json → npm (covers npm/yarn/pnpm/bun) +# Gemfile → bundler + +declare -A MANIFEST_ECOSYSTEMS=( + ["pom.xml"]="maven" + ["build.gradle"]="gradle" + ["build.gradle.kts"]="gradle" + ["requirements.txt"]="pip" + ["pyproject.toml"]="pip uv" + ["go.mod"]="gomod" + ["Cargo.toml"]="cargo" + ["package.json"]="npm" + ["Gemfile"]="bundler" +) + +MANIFEST_NAMES=$(IFS='|'; echo "${!MANIFEST_ECOSYSTEMS[*]}" | tr ' ' '\n' | sort -u | tr '\n' '|'; echo) +MANIFEST_NAMES="${MANIFEST_NAMES%|}" + +# ── Discover test fixture directories ───────────────────────────────── +declare -A ECOSYSTEM_DIRS + +while IFS= read -r manifest_path; do + dir="$(dirname "$manifest_path")" + filename="$(basename "$manifest_path")" + ecosystems="${MANIFEST_ECOSYSTEMS[$filename]}" + + for eco in $ecosystems; do + existing="${ECOSYSTEM_DIRS[$eco]:-}" + if [[ -z "$existing" ]]; then + ECOSYSTEM_DIRS[$eco]="$dir" + else + ECOSYSTEM_DIRS[$eco]="$existing"$'\n'"$dir" + fi + done +done < <( + find "$REPO_ROOT/src/test" -type f \( \ + -name "pom.xml" -o \ + -name "build.gradle" -o \ + -name "build.gradle.kts" -o \ + -name "requirements.txt" -o \ + -name "pyproject.toml" -o \ + -name "go.mod" -o \ + -name "Cargo.toml" -o \ + -name "package.json" -o \ + -name "Gemfile" \ + \) -not -path "*/node_modules/*" \ + -not -path "*/.gradle/*" \ + -not -path "*/build/*" \ + -not -path "*/target/*" | LC_ALL=C sort +) + +# ── Generate YAML ──────────────────────────────────────────────────── +generate_yaml() { + echo "$BEGIN_MARKER" + echo " # Auto-generated by scripts/sync-dependabot-config.sh." + echo " # Do not edit manually — run the script to update." + + for eco in $(echo "${!ECOSYSTEM_DIRS[@]}" | tr ' ' '\n' | LC_ALL=C sort); do + dirs="${ECOSYSTEM_DIRS[$eco]}" + sorted_dirs=$(echo "$dirs" | LC_ALL=C sort -u) + + echo " - package-ecosystem: \"$eco\"" + echo " directories:" + while IFS= read -r dir; do + rel="/${dir#"$REPO_ROOT/"}" + echo " - \"$rel\"" + done <<< "$sorted_dirs" + echo " schedule:" + echo " interval: \"monthly\"" + echo " open-pull-requests-limit: 0" + echo " labels: []" + echo " ignore:" + echo " - dependency-name: \"*\"" + done + + echo " $END_MARKER" +} + +generated=$(generate_yaml) + +# ── Check or update ────────────────────────────────────────────────── +if ! grep -qF "$BEGIN_MARKER" "$DEPENDABOT_YML"; then + if $check_mode; then + echo "ERROR: $DEPENDABOT_YML is missing the generated section marker." + echo "Run 'scripts/sync-dependabot-config.sh' to add it." + exit 1 + fi + # Append the generated section + { + echo "" + echo "$generated" + } >> "$DEPENDABOT_YML" + echo "Added test fixture entries to $DEPENDABOT_YML" + exit 0 +fi + +# Extract the file before and after the markers +before=$(sed -n "1,/^${BEGIN_MARKER}/{ /^${BEGIN_MARKER}/d; p; }" "$DEPENDABOT_YML") +tmpfile=$(mktemp) +trap 'rm -f "$tmpfile"' EXIT +printf '%s\n%s\n' "$before" "$generated" > "$tmpfile" + +if $check_mode; then + if diff -q "$DEPENDABOT_YML" "$tmpfile" > /dev/null 2>&1; then + echo "dependabot.yml is up to date." + exit 0 + else + echo "ERROR: dependabot.yml is out of date." + echo "" + diff --unified "$DEPENDABOT_YML" "$tmpfile" || true + echo "" + echo "Run 'scripts/sync-dependabot-config.sh' to fix." + exit 1 + fi +fi + +cp "$tmpfile" "$DEPENDABOT_YML" +echo "Updated test fixture entries in $DEPENDABOT_YML"