diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..0c53cfa --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,72 @@ +version: 2 +updates: + # Maven dependencies + - package-ecosystem: "maven" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + timezone: "Europe/Berlin" + open-pull-requests-limit: 10 + reviewers: + - "aether-framework/maintainers" + labels: + - "dependencies" + - "java" + commit-message: + prefix: "deps" + include: "scope" + groups: + jackson: + patterns: + - "com.fasterxml.jackson*" + update-types: + - "minor" + - "patch" + spring: + patterns: + - "org.springframework*" + update-types: + - "minor" + - "patch" + testing: + patterns: + - "org.junit*" + - "org.assertj*" + update-types: + - "minor" + - "patch" + maven-plugins: + patterns: + - "org.apache.maven.plugins:maven-*" + - "org.codehaus.mojo:*" + update-types: + - "minor" + - "patch" + build-plugins: + patterns: + - "org.sonatype.central:*" + - "org.owasp:*" + - "org.cyclonedx:*" + - "org.jacoco:*" + - "com.github.spotbugs:*" + update-types: + - "minor" + - "patch" + + # GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + day: "monday" + time: "06:00" + timezone: "Europe/Berlin" + open-pull-requests-limit: 5 + labels: + - "dependencies" + - "github-actions" + commit-message: + prefix: "ci" + include: "scope" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..b421524 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,148 @@ +name: CI + +on: + push: + branches: [ main, develop, 'feature/**' ] + pull_request: + branches: [ main, develop ] + +permissions: + contents: read + checks: write + pull-requests: write + +jobs: + build: + name: Build & Test (Java ${{ matrix.java }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + java: [ '17', '21' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: 'maven' + + - name: Build and test with Maven + run: mvn -B clean verify -Pqa -Ddependency-check.skip=true + + - name: Upload test results + uses: actions/upload-artifact@v4 + if: always() + with: + name: test-results-java-${{ matrix.java }} + path: | + **/target/surefire-reports/ + **/target/failsafe-reports/ + retention-days: 7 + + - name: Upload coverage report + uses: actions/upload-artifact@v4 + if: matrix.java == '21' + with: + name: coverage-report + path: | + **/target/site/jacoco/ + **/target/jacoco.exec + retention-days: 7 + + - name: Publish Test Report + uses: mikepenz/action-junit-report@v4 + if: always() + with: + report_paths: '**/target/*-reports/TEST-*.xml' + check_name: Test Report (Java ${{ matrix.java }}) + + quality: + name: Code Quality Analysis + runs-on: ubuntu-latest + needs: build + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Install artifacts for analysis + run: mvn -B -Ddependency-check.skip=true clean install -Pqa -DskipTests + + - name: Run SpotBugs analysis + run: mvn -B spotbugs:check -Pqa -Ddependency-check.skip=true + continue-on-error: true + + - name: Run Checkstyle analysis + run: mvn -B checkstyle:check -Pqa -Ddependency-check.skip=true + continue-on-error: true + + - name: Upload SpotBugs report + uses: actions/upload-artifact@v4 + if: always() + with: + name: spotbugs-report + path: '**/target/spotbugsXml.xml' + retention-days: 7 + + - name: Upload Checkstyle report + uses: actions/upload-artifact@v4 + if: always() + with: + name: checkstyle-report + path: '**/target/checkstyle-result.xml' + retention-days: 7 + + dependency-check: + name: OWASP Dependency Check + runs-on: ubuntu-latest + needs: build + env: + NVD_API_KEY: ${{ secrets.NVD_API_KEY }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Cache Dependency-Check DB + uses: actions/cache@v4 + with: + path: ~/.m2/repository/org/owasp/dependency-check-data + key: depcheck-${{ runner.os }}-${{ hashFiles('**/pom.xml') }} + restore-keys: | + depcheck-${{ runner.os }}- + + - name: Run OWASP Dependency Check + run: mvn -B dependency-check:aggregate -Pqa + continue-on-error: true + + - name: Upload Dependency Check report + uses: actions/upload-artifact@v4 + if: always() + with: + name: dependency-check-report + path: | + target/dependency-check-report.html + retention-days: 30 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..5233757 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,47 @@ +name: CodeQL Security Analysis + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + schedule: + - cron: '0 0 * * 1' # Monday 00:00 UTC + +permissions: + contents: read + security-events: write + actions: read + +jobs: + analyze: + name: Analyze (java-kotlin) + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up JDK 21 + uses: actions/setup-java@v4 + with: + java-version: '21' + distribution: 'temurin' + cache: 'maven' + + - name: Initialize CodeQL + uses: github/codeql-action/init@v3 + with: + languages: java-kotlin + build-mode: manual + queries: security-extended,security-and-quality + + - name: Build with Maven + run: mvn -B clean compile -DskipTests + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v3 + with: + category: "/language:java-kotlin" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..1dbab60 --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,28 @@ +name: Dependency Review + +on: + pull_request: + branches: [ main, develop ] + +permissions: + contents: read + pull-requests: write + +jobs: + dependency-review: + name: Dependency Review + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Dependency Review + uses: actions/dependency-review-action@v4 + with: + fail-on-severity: high + deny-licenses: GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later + comment-summary-in-pr: always + warn-only: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..007c2ae --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,197 @@ +name: Release + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + version: + description: 'Release version (e.g., 1.0.0)' + required: true + type: string + dry_run: + description: 'Dry run (skip actual deployment)' + required: false + type: boolean + default: false + +# Minimal global permissions - jobs request additional permissions as needed +permissions: + contents: read + +env: + JAVA_VERSION: '21' + +jobs: + validate: + name: Validate Release + runs-on: ubuntu-latest + outputs: + version: ${{ steps.version.outputs.version }} + should_deploy: ${{ steps.deploy-check.outputs.should_deploy }} + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Determine version + id: version + run: | + if [ "${{ github.event_name }}" == "push" ]; then + VERSION="${GITHUB_REF#refs/tags/v}" + else + VERSION="${{ github.event.inputs.version }}" + fi + echo "version=$VERSION" >> $GITHUB_OUTPUT + echo "Release version: $VERSION" + + - name: Check deployment condition + id: deploy-check + run: | + # Tag push: always deploy + # Manual dispatch: only if dry_run is not true + if [ "${{ github.event_name }}" == "push" ]; then + echo "should_deploy=true" >> $GITHUB_OUTPUT + echo "Deployment: enabled (tag push)" + elif [ "${{ github.event.inputs.dry_run }}" != "true" ]; then + echo "should_deploy=true" >> $GITHUB_OUTPUT + echo "Deployment: enabled (manual trigger, dry_run=false)" + else + echo "should_deploy=false" >> $GITHUB_OUTPUT + echo "Deployment: disabled (dry run mode)" + fi + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: 'maven' + + - name: Validate build + run: mvn -B clean verify -DskipTests + + test: + name: Run Tests + runs-on: ubuntu-latest + needs: validate + + strategy: + matrix: + java: [ '17', '21' ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK ${{ matrix.java }} + uses: actions/setup-java@v4 + with: + java-version: ${{ matrix.java }} + distribution: 'temurin' + cache: 'maven' + + - name: Run tests + run: mvn -B clean test + + deploy: + name: Deploy to Maven Central + runs-on: ubuntu-latest + needs: [ validate, test ] + if: needs.validate.outputs.should_deploy == 'true' + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + env: + CENTRAL_USERNAME: ${{ secrets.CENTRAL_USERNAME }} + CENTRAL_TOKEN: ${{ secrets.CENTRAL_TOKEN }} + GPG_PASSPHRASE: ${{ secrets.GPG_PASSPHRASE }} + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: 'maven' + server-id: central + server-username: CENTRAL_USERNAME + server-password: CENTRAL_TOKEN + gpg-private-key: ${{ secrets.GPG_PRIVATE_KEY }} + gpg-passphrase: GPG_PASSPHRASE + + - name: Deploy to Maven Central + run: mvn -B clean deploy -Prelease -DskipTests -Dgpg.useAgent=false + + sbom: + name: Generate SBOM + runs-on: ubuntu-latest + needs: [ validate, test ] + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Set up JDK ${{ env.JAVA_VERSION }} + uses: actions/setup-java@v4 + with: + java-version: ${{ env.JAVA_VERSION }} + distribution: 'temurin' + cache: 'maven' + + - name: Generate SBOM + run: mvn -B cyclonedx:makeAggregateBom -Pqa + + - name: Upload SBOM artifact + uses: actions/upload-artifact@v4 + with: + name: sbom + path: target/bom.* + retention-days: 90 + + github-release: + name: Create GitHub Release + runs-on: ubuntu-latest + needs: [ validate, deploy, sbom ] + if: needs.validate.outputs.should_deploy == 'true' && needs.deploy.result == 'success' + + # Only this job needs write access to create the release + permissions: + contents: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Download SBOM + uses: actions/download-artifact@v4 + with: + name: sbom + path: sbom/ + + - name: Read release notes + id: release-notes + run: | + if [ ! -f "RELEASE.md" ]; then + echo "Error: RELEASE.md not found" + exit 1 + fi + echo "body<> $GITHUB_OUTPUT + cat RELEASE.md >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ needs.validate.outputs.version }} + name: Release v${{ needs.validate.outputs.version }} + body: ${{ steps.release-notes.outputs.body }} + files: | + sbom/bom.json + sbom/bom.xml + draft: false + prerelease: ${{ contains(needs.validate.outputs.version, '-') }} + generate_release_notes: false diff --git a/.well-known/security.txt b/.well-known/security.txt new file mode 100644 index 0000000..0a75a54 --- /dev/null +++ b/.well-known/security.txt @@ -0,0 +1,10 @@ +# Aether Datafixers Security Contact +# This file follows RFC 9116 (https://www.rfc-editor.org/rfc/rfc9116) + +Contact: mailto:security@splatgames.de +Contact: https://github.com/aether-framework/aether-datafixers/security/advisories/new +Expires: 2035-01-01T00:00:00.000Z +Preferred-Languages: en, de +Canonical: https://raw.githubusercontent.com/aether-framework/aether-datafixers/main/.well-known/security.txt +Policy: https://github.com/aether-framework/aether-datafixers/blob/main/SECURITY.md +Acknowledgments: https://github.com/aether-framework/aether-datafixers/blob/main/SECURITY.md#security-audits diff --git a/KEYS b/KEYS new file mode 100644 index 0000000..8e79d7e --- /dev/null +++ b/KEYS @@ -0,0 +1,52 @@ +-----BEGIN PGP PUBLIC KEY BLOCK----- + +mQINBGlj2M8BEADdr0h5IJcIQ5USEP4rFPY+/91QLlwuFfI92Rcfsj0aGUXpsK+B +UiCKeYgbyZJeFEQU4UHtP6QUfb58od8irVjAmv5eKAylHYy8vN8botNd4owzVNhz +225NFtXjovs8GAV3phbXoW8D2IlnFt8lf0RohAeF+P/Ved1n7/5cHZXfQ92Aye82 +whA1pXdrqubmOmW+yHaw90BCksExwhOY5VzDXEPITeTj7yzhDY+fMofvnD7dAq98 +zA+vYDPTOZCWV3k38Re7xpvQcfl5F/lAqN9xtRqaMVIwzE35mhwLIPm7E9qPAOr6 +LI1boCRe5u3ei0gV8/OAkaZOWOQTF57g/G6J7TkB/GOhbkufR7XLGOK5DeU95xAI +5d960k/6pa7LCIVMR1lIGHGbjS7i9rofDp4gpyL8B/qU9X8PRjguYYGPUv+6K5GT +pIT7LCV4lDv+bR/H1htCzWZCCwUVVwOgvcXzec0R+Qd2WrHnGOoW4x3XCENvmsAL +6/hHSxcpaOWJH5VK1iVWChjNZJqY95cu1dHV1jj7qeXb8xsDKQDuBq9B/X4RB/SK +Suw7MRk1/EgHiZLQunsSKxCzfwKcpo0rI8TAFgm5Y30/12sTG6lt8ePQPFQUywQI +YIV1+CYEMsDLdN5kul9o58P+PkwdfAq7YYDi32LrVSw0Z3/28+yoDJcg2QARAQAB +tEFTcGxhdGdhbWVzLmRlIFNvZnR3YXJlIENJIFJlbGVhc2UgU2lnbmluZyA8cmVs +ZWFzZUBzcGxhdGdhbWVzLmRlPokCUQQTAQgAOxYhBMa+Jb8qRjmmekkevTe1m5Pc +dW7oBQJpY9jPAhsDBQsJCAcCAiICBhUKCQgLAgQWAgMBAh4HAheAAAoJEDe1m5Pc +dW7ooZwQAJUz8PfFO3/vbmWUD4a4I11ipWuOT/C8xArmhH7RkxSysgHspdBq+vUT +B7VXdx4IJNWps77IajJsMIA6bC3B1V4YZBnxKYKdvhNlkoDddmXGFcyBpTGSfzwD +qPOHrWa8LDg6naJ22PSgCh5SIy9BPQ+Vr0ES9R+HE3dFPNJgrocZk5/M1lV0gGk+ +o64F9VYl9r06QKwn5zEKyl6CKdpAlW9xHLobPW85vCEiEaTQlWDWzTH4uM+7JeM4 +JnmRVE/vWxA3O+hCoQJNdr2EhnyleXWgrkLIRggfgquB77LBN53JSs1ABby2J/fC +JHNigGF/FvPhwsGQ4ateRfgK4fkeitLeNH+ozbrs7V+HdA2MDpftVI9LnEX48T2R +wEAJQq3V9pnAHbnXa9v7Y8eOnEcaGhgOtvtBaamYR1/PNHSp9osh1w1g/KSRtb3j +vp28hfLZoId+A2Q8KT6/2u4liiDt9hqueuOJe1i9A1Y8vXoaFd0J5LGTCJBsQ/00 +ZZdehcvNvSZPfzy/tbTB6lHqnNGU1w+n1wLA4mKPQKH3TkujvSpuTRWSrPl2vgFj +GUHjep/MI9ie6lTF8sQ20C1skJtZQiOEwTwNq5QDUOJB/QeDlfhUV3G62Vxd/vS6 +u49bs2j+7hiRXNMfuOMjSOMp47QzEMBvHx0FTMFx/NKI6BEzaUBFuQINBGlj2M8B +EACxDR6BLawJA3lLd41lXhauatOEYzaURGLjmvoYRQwv4k/RkduXcOcHkHGRKhfe +7jBOtlkG1RUMe6Jeori5ErNHhqCefDpAI4rCrHXt9cS7STFRlmMznsEZ3AI1rTgG ++AhFNmDMWpC/ImynvGgcd/P4ZJx0oZlUkELJBELU2XShvQMlJP6R7wTOSv9OWeda +V/MJSdgRuDiJJxG7z1Ono98YOs5kOu2nBOLcthyaox5TZZssaUBKtkcRMiiEOtvv +nJUmC9mN4Cx9w73xxVeibdgW+2ZGr1qwI/1sXcPPCOTJ4ena9Sy4+KK4JzWaWIsl +pBqS+p2ngKWTx/kDyuu5CHnyXmZLie0shj6OjiO2XWyMRQ4pG/wsZYV0kEZk/b8C +BOpFbMgVLN6jqZ1DiIFDGzFdi2sf7LE+MrDIOQa1IY/bmEUl0/vE49j+Nwqq8SeT +/B8DOoMtyTly84Mbzf0Mg+RIZTMZ+7GvNhTVIirSc6xPsaZ1E4JstXDofDazVLd9 +Z15dtelbJeGbDJI2YpPS+7ISyFEUAbUobGXdeKgOh4J2c04VarO60u02Ed0/wWXZ +ey6VXhXCmMzGwrSgnvuLiCD7F4ZM/5+nSi/GkeXEs8qXuYpmdQ9tSz4R9miax+iu +j+UxfzdMlQfZL7VJq3RPa+RJunTb15SNwPevPilpZCBZdwARAQABiQI2BBgBCAAg +FiEExr4lvypGOaZ6SR69N7Wbk9x1bugFAmlj2M8CGwwACgkQN7Wbk9x1bugd2w// +T7vwqH87ovqEG3BkCPWBzftMbhzg5S/Qn8yyjTyMCao8Lm0ZK/0UmXJtwJ7hxNOk +RRb3rIYdbbwWhLb0MgD87AYmg2DL604X7GpnBXvzwbDZlm0vNe8qB7v2JluDPZWN +uw8kX9owInNn6pb3tChsVsQ50vgVa3yfPQ/8MhOqe/NzKbqeCBPDqbRhmODmC0+x +yRYgGo4cVwMc6NFHMcl/CfEK5c1In7BYMAKnJ76LADzdgyeYZtGlJjPBvbM9JSJk +E7uvPy6WcpDAqZN+EmJ36DbPPquGyuieUMwHmi8hGp0Vid/bKas/NZU3XYBVCXXA +kn2KdvMT/ctqIE2NCs06keX7ojnt+kUxXeUmpTSCJ4J3zXb3bAEyTTL47BT8lnBB +AejZndNaBHjnAUoW9VpiHDHHtDdOzELL3c+/sROLZLLLq3sq0+cUYwG2mKYXakXO +1cygMAvFdrWYR+1ezLhG6IqDKwENUq7EQZv4itLlsPb//UrVwnKBd052CsIbGPRn +zwZfIlCCdpL0/jRNgOh8lxm90U6P++o6m9SRCO+epFRVNKZBFW9QcYUT20HiTsVO +txvN20Zz88ID9dyRywF236cYES0wdAGC0hgFHAI0vmjKzvuKspRhcJ8accjSLyF6 +XJ48wxLgq6MyEpefywMP3znLq0PGEwl8EmjsZc2bdCk= +=3smd +-----END PGP PUBLIC KEY BLOCK----- diff --git a/SECURITY.md b/SECURITY.md index 34eab22..e39fe80 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -4,39 +4,147 @@ We provide security updates for the following versions: -| Version | Supported | -|---------|---------------------| -| 1.0.x | ❌ Not supported yet | -| 0.4.x | ✅ Active Support | -| 0.3.x | ❌ Not supported | -| 0.2.x | ❌ Not supported | -| 0.1.x | ❌ Not supported | +| Version | Support Status | End of Support | +|--------|--------------------|----------------| +| 1.0.x | 🔜 Planned LTS | TBD (1 year) | +| 0.5.x | ✅ Active Support | February 2026 | +| 0.4.x | ❌ End of Life | - | +| 0.3.x | ❌ End of Life | - | +| 0.2.x | ❌ End of Life | - | +| 0.1.x | ❌ End of Life | - | -If you are using an older version, we **strongly** recommend upgrading to the latest stable release. +If you are using an older version, we **strongly recommend upgrading** to the latest stable release. + +--- + +## Security Features + +### Automated Security Scanning + +This project uses multiple automated security tools: + +- **GitHub CodeQL** – Static Application Security Testing (SAST) +- **OWASP Dependency-Check** – Known vulnerability detection in dependencies +- **GitHub Dependency Review** – Pull request dependency analysis +- **Dependabot** – Automated dependency updates + +All scans are executed automatically in CI pipelines on every pull request and release build. + +--- + +## Supply Chain Security + +### Artifact Integrity & Signing + +All official release artifacts of **Aether Datafixers** are **cryptographically signed** to guarantee integrity and authenticity. + +- All release artifacts are **GPG signed** +- Signatures are generated during the release pipeline +- Each published artifact is accompanied by a corresponding `.asc` signature file +- Consumers can verify artifacts before usage + +Example verification flow: + +``` +gpg --verify artifact.jar.asc artifact.jar +``` + +Unsigned or modified artifacts **must not be trusted**. + +--- + +### Signing Keys + +- A **dedicated GPG key** is used for automated GitHub releases and deployments +- Release signing keys are **separate from personal developer keys** +- Private key material is **never committed** to the repository +- Keys are stored securely using CI secret management + +The signing process is fully automated and enforced during release builds. + +--- ## Reporting a Vulnerability -If you find a security vulnerability in Aether Datafixers, please report it **privately**. -We take security issues seriously and will respond as soon as possible. +If you discover a security vulnerability in **Aether Datafixers**, please report it **privately**. -### 📬 Contact +### Contact -- **Email:** security@splatgames.de -- **GitHub Issues:** Do **not** report security vulnerabilities in public issues. +- **Email:** `security@splatgames.de` +- **GitHub Security Advisories:** + https://github.com/aether-framework/aether-datafixers/security/advisories/new +- **GitHub Issues:** + Do **not** report security vulnerabilities in public issues. -### 🔒 Disclosure Process +--- -1. Report the issue privately via **security@splatgames.de**. -2. Our team will acknowledge receipt within **48 hours**. -3. We will investigate and provide a **fix timeline**. -4. Once resolved, we will issue a **security advisory**. +## Disclosure Process + +1. Report the issue privately +2. Acknowledgment within **48 hours** +3. Fix timeline provided within **7 days** +4. Critical vulnerabilities (CVSS ≥ 9.0): patch within **72 hours** +5. High severity (CVSS ≥ 7.0): patch within **14 days** +6. Security advisory published after resolution + +--- + +## Response Time SLA + +| Severity | Acknowledgment | Fix Timeline | +|--------------------------|----------------|--------------| +| Critical (CVSS 9.0–10.0) | 24 hours | 72 hours | +| High (CVSS 7.0–8.9) | 48 hours | 14 days | +| Medium (CVSS 4.0–6.9) | 48 hours | 30 days | +| Low (CVSS 0.1–3.9) | 72 hours | Next release | + +--- ## Security Best Practices -To keep your application secure when using Aether Datafixers: +- Always use the **latest stable version** +- Verify **GPG signatures** of all downloaded artifacts +- Enable automated dependency updates +- Validate input data at system boundaries +- Use appropriate `DynamicOps` implementations for untrusted data +- Avoid sensitive data in logs +- Review the attached **SBOM** for dependency transparency + +--- + +## Vulnerability Disclosure Policy + +We follow a **coordinated disclosure** process: + +1. Private disclosure +2. Fix development +3. Advisory preparation +4. Coordinated release +5. Public disclosure after a grace period + +--- + +## Security Audits + +Security audits are welcome. + +- Contact `security@splatgames.de` before starting +- Follow responsible disclosure practices +- Researchers may be credited with permission + +--- + +## PGP Key + +For encrypted communication and release verification: + +- **Key Purpose:** Release artifact signing +- **Key ID:** 37B59B93DC756EE8 +- **Fingerprint:** C6BE25BF2A4639A67A491EBD37B59B93DC756EE8 +- **Accessable in repository:** `KEYS` + +Contact: **security@splatgames.de** -- Always use the **latest stable version**. -- Validate event data properly. -- Do not expose sensitive logging in production. +--- -🚀 Thank you for helping us keep Aether Datafixers secure! +Thank you for helping keep **Aether Datafixers** secure. \ No newline at end of file diff --git a/aether-datafixers-api/pom.xml b/aether-datafixers-api/pom.xml index bdee772..a4c7edd 100644 --- a/aether-datafixers-api/pom.xml +++ b/aether-datafixers-api/pom.xml @@ -15,11 +15,20 @@ Aether Datafixers :: API API interfaces and classes for Aether Datafixers. + + + 0.65 + + org.jetbrains annotations + + com.github.spotbugs + spotbugs-annotations + com.google.guava guava diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/DataVersion.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/DataVersion.java index cf97d68..6f7f9bd 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/DataVersion.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/DataVersion.java @@ -74,8 +74,7 @@ public final class DataVersion implements Comparable { * The internal numeric representation of this data version. * *

This value is guaranteed to be non-negative and represents the version number - * in a monotonically increasing sequence. Higher values indicate newer versions of - * the data schema.

+ * in a monotonically increasing sequence. Higher values indicate newer versions of the data schema.

*/ private final int version; @@ -136,9 +135,8 @@ public int getVersion() { * } * * @param o the data version to compare against; must not be {@code null} - * @return a negative integer if this version is less than the specified version, - * zero if they are equal, or a positive integer if this version is - * greater than the specified version + * @return a negative integer if this version is less than the specified version, zero if they are equal, or a al, + * or a positive integer if this version is greater than the specified version * @throws NullPointerException if the specified data version is {@code null} */ @Override @@ -150,8 +148,8 @@ public int compareTo(@NotNull final DataVersion o) { * Indicates whether some other object is "equal to" this data version. * *

Two {@code DataVersion} instances are considered equal if and only if they - * have the same numeric version value. This method adheres to the general contract - * of {@link Object#equals(Object)}, providing:

+ * have the same numeric version value. This method adheres to the general contract of + * {@link Object#equals(Object)}, providing:

*
    *
  • Reflexivity: For any non-null {@code DataVersion x}, {@code x.equals(x)} * returns {@code true}
  • @@ -169,8 +167,7 @@ public int compareTo(@NotNull final DataVersion o) { *
* * @param obj the reference object with which to compare; may be {@code null} - * @return {@code true} if this data version is equal to the specified object; - * {@code false} otherwise + * @return {@code true} if this data version is equal to the specified object; {@code false} otherwise * @see #hashCode() */ @Override @@ -213,8 +210,8 @@ public int hashCode() { * Returns a string representation of this data version. * *

The returned string follows the format {@code "DataVersion{version=N}"} where - * {@code N} is the numeric version value. This format is intended for debugging and - * logging purposes and should not be parsed programmatically.

+ * {@code N} is the numeric version value. This format is intended for debugging and logging purposes and should not + * be parsed programmatically.

* *

Example output:

*
{@code
@@ -222,8 +219,7 @@ public int hashCode() {
      * new DataVersion(0).toString()   // Returns "DataVersion{version=0}"
      * }
* - * @return a string representation of this data version in the format - * {@code "DataVersion{version=N}"} + * @return a string representation of this data version in the format {@code "DataVersion{version=N}"} */ @Override public String toString() { diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/TypeReference.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/TypeReference.java index 15fa8f7..5aaedb9 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/TypeReference.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/TypeReference.java @@ -78,8 +78,8 @@ public final class TypeReference { * The unique string identifier for this type reference. * *

This identifier is used as a key for type lookups in registries and serves as - * the canonical name for the data type throughout the data fixing system. The value - * is guaranteed to be non-null and non-empty.

+ * the canonical name for the data type throughout the data fixing system. The value is guaranteed to be non-null + * and non-empty.

* *

By convention, type identifiers use lowercase letters with underscores to * separate words (e.g., "player", "block_entity", "world_data").

@@ -104,8 +104,8 @@ public TypeReference(@NotNull final String id) { * Returns the unique identifier for this type reference. * *

The returned identifier is the canonical name used to look up type definitions - * in a {@link TypeRegistry} and to associate {@link DataFix} instances with this - * data type. The identifier is guaranteed to be non-null and non-empty.

+ * in a {@link TypeRegistry} and to associate {@link DataFix} instances with this data type. The identifier is + * guaranteed to be non-null and non-empty.

* *

Example usage:

*
{@code
@@ -127,8 +127,7 @@ public String getId() {
      * Returns a hash code value for this type reference.
      *
      * 

The hash code is computed solely based on the string identifier. This - * implementation satisfies the general contract of {@link Object#hashCode()}, - * ensuring that:

+ * implementation satisfies the general contract of {@link Object#hashCode()}, ensuring that:

*
    *
  • If two {@code TypeReference} objects are equal according to the * {@link #equals(Object)} method, then calling {@code hashCode()} on each @@ -153,8 +152,8 @@ public int hashCode() { * Indicates whether some other object is "equal to" this type reference. * *

    Two {@code TypeReference} instances are considered equal if and only if they - * have the same string identifier (case-sensitive comparison). This method adheres - * to the general contract of {@link Object#equals(Object)}, providing:

    + * have the same string identifier (case-sensitive comparison). This method adheres to the general contract of + * {@link Object#equals(Object)}, providing:

    *
      *
    • Reflexivity: For any non-null {@code TypeReference x}, * {@code x.equals(x)} returns {@code true}
    • @@ -180,8 +179,7 @@ public int hashCode() { * }
* * @param obj the reference object with which to compare; may be {@code null} - * @return {@code true} if this type reference is equal to the specified object; - * {@code false} otherwise + * @return {@code true} if this type reference is equal to the specified object; {@code false} otherwise * @see #hashCode() */ @Override @@ -199,8 +197,8 @@ public boolean equals(final Object obj) { * Returns a string representation of this type reference. * *

The returned string follows the format {@code "TypeReference{id=''}"}. - * This format is intended for debugging and logging purposes and provides a clear, - * human-readable representation of the type reference.

+ * This format is intended for debugging and logging purposes and provides a clear, human-readable representation of + * the type reference.

* *

Example output:

*
{@code
@@ -212,8 +210,8 @@ public boolean equals(final Object obj) {
      * }
* *

Note: The format of this string is not guaranteed to remain stable across - * versions and should not be parsed programmatically. Use {@link #getId()} to retrieve - * the identifier for programmatic use.

+ * versions and should not be parsed programmatically. Use {@link #getId()} to retrieve the identifier for + * programmatic use.

* * @return a string representation of this type reference * @see #getId() diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/bootstrap/DataFixerBootstrap.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/bootstrap/DataFixerBootstrap.java index 6073b83..cc8fba4 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/bootstrap/DataFixerBootstrap.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/bootstrap/DataFixerBootstrap.java @@ -88,8 +88,8 @@ public interface DataFixerBootstrap { * Registers all schemas with the provided schema registry. * *

This method is invoked during data fixer initialization to populate the schema - * registry with {@link Schema} instances for each supported data version. Schemas define - * the structure and types available at each version of the data model.

+ * registry with {@link Schema} instances for each supported data version. Schemas define the structure and types + * available at each version of the data model.

* *

Implementation Guidelines

*

When implementing this method, consider the following best practices:

@@ -127,8 +127,7 @@ public interface DataFixerBootstrap { * Implementations do not need to be thread-safe, but they should not retain references * to the registry after the method returns.

* - * @param schemas the schema registry to populate with version-specific schemas; - * must not be {@code null} + * @param schemas the schema registry to populate with version-specific schemas; must not be {@code null} * @throws NullPointerException if {@code schemas} is {@code null} * @see Schema * @see SchemaRegistry @@ -139,9 +138,8 @@ public interface DataFixerBootstrap { * Registers all data fixes with the provided fix registrar. * *

This method is invoked during data fixer initialization to register all - * {@link DataFix} instances that handle migrations between data versions. Each fix - * defines a transformation from one version to another for a specific type or set - * of types.

+ * {@link DataFix} instances that handle migrations between data versions. Each fix defines a transformation from + * one version to another for a specific type or set of types.

* *

Implementation Guidelines

*

When implementing this method, adhere to these best practices:

@@ -179,8 +177,7 @@ public interface DataFixerBootstrap { * Implementations do not need to be thread-safe, but they should not retain references * to the registrar after the method returns.

* - * @param fixes the fix registrar to populate with data migration fixes; - * must not be {@code null} + * @param fixes the fix registrar to populate with data migration fixes; must not be {@code null} * @throws NullPointerException if {@code fixes} is {@code null} * @see DataFix * @see FixRegistrar diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/CodecRegistry.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/CodecRegistry.java index a956109..8d78a6f 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/CodecRegistry.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/CodecRegistry.java @@ -66,9 +66,9 @@ public interface CodecRegistry { * Registers a codec for the given type reference. * *

This method associates a {@link Codec} with a {@link TypeReference}, enabling - * later retrieval via {@link #get(TypeReference)} or {@link #require(TypeReference)}. - * If a codec is already registered for the given reference, the behavior depends on - * the implementation (it may replace the existing codec or throw an exception).

+ * later retrieval via {@link #get(TypeReference)} or {@link #require(TypeReference)}. If a codec is already + * registered for the given reference, the behavior depends on the implementation (it may replace the existing codec + * or throw an exception).

* *

Example Usage

*
{@code
@@ -97,8 +97,8 @@ public interface CodecRegistry {
      * Retrieves a codec by its type reference.
      *
      * 

This method performs a lookup in the registry and returns the codec associated - * with the given type reference, or {@code null} if no codec has been registered for - * that reference. For a non-null guarantee, use {@link #require(TypeReference)} instead.

+ * with the given type reference, or {@code null} if no codec has been registered for that reference. For a non-null + * guarantee, use {@link #require(TypeReference)} instead.

* *

Example Usage

*
{@code
@@ -110,8 +110,8 @@ public interface CodecRegistry {
      * }
* * @param ref the type reference to look up; must not be {@code null} - * @return the codec associated with the given reference, or {@code null} if no codec - * is registered for that reference + * @return the codec associated with the given reference, or {@code null} if no codec is registered for that + * reference * @throws NullPointerException if {@code ref} is {@code null} * @see #has(TypeReference) * @see #require(TypeReference) @@ -123,9 +123,8 @@ public interface CodecRegistry { * Checks whether a codec is registered for the given type reference. * *

This method provides a way to verify the existence of a codec registration - * without actually retrieving the codec. It is more efficient than calling - * {@link #get(TypeReference)} and checking for {@code null} if you only need to - * test for presence.

+ * without actually retrieving the codec. It is more efficient than calling {@link #get(TypeReference)} and checking + * for {@code null} if you only need to test for presence.

* *

Example Usage

*
{@code
@@ -139,8 +138,7 @@ public interface CodecRegistry {
      * }
* * @param ref the type reference to check for registration; must not be {@code null} - * @return {@code true} if a codec is registered for the given reference; - * {@code false} otherwise + * @return {@code true} if a codec is registered for the given reference; {@code false} otherwise * @throws NullPointerException if {@code ref} is {@code null} * @see #get(TypeReference) * @see #require(TypeReference) @@ -151,9 +149,8 @@ public interface CodecRegistry { * Retrieves a codec by its type reference, throwing an exception if not found. * *

This method is similar to {@link #get(TypeReference)} but guarantees a non-null - * return value. If no codec is registered for the given reference, an - * {@link IllegalStateException} is thrown. Use this method when the absence of a - * codec indicates a programming error or misconfiguration.

+ * return value. If no codec is registered for the given reference, an {@link IllegalStateException} is thrown. Use + * this method when the absence of a codec indicates a programming error or misconfiguration.

* *

Example Usage

*
{@code
@@ -191,9 +188,9 @@ default Codec require(@NotNull final TypeReference ref) {
      * Freezes this registry, making it immutable.
      *
      * 

After freezing, any attempt to modify the registry (e.g., via - * {@link #register(TypeReference, Codec)}) will throw an {@link IllegalStateException}. - * This is useful for ensuring thread-safety after the initialization phase is complete, - * as an immutable registry can be safely shared across threads without synchronization.

+ * {@link #register(TypeReference, Codec)}) will throw an {@link IllegalStateException}. This is useful for ensuring + * thread-safety after the initialization phase is complete, as an immutable registry can be safely shared across + * threads without synchronization.

* *

Idempotency

*

This method is idempotent - calling it multiple times has no additional effect @@ -228,8 +225,7 @@ default void freeze() { * Returns whether this registry has been frozen and is now immutable. * *

A frozen registry cannot accept new codec registrations. Any call to - * {@link #register(TypeReference, Codec)} on a frozen registry will throw an - * {@link IllegalStateException}.

+ * {@link #register(TypeReference, Codec)} on a frozen registry will throw an {@link IllegalStateException}.

* *

Example Usage

*
{@code
@@ -245,8 +241,8 @@ default void freeze() {
      * 

The default implementation returns {@code false}, indicating that the registry * is always mutable. Implementations that support freezing should override this method.

* - * @return {@code true} if this registry has been frozen and is immutable; - * {@code false} if it is still mutable and accepts new registrations + * @return {@code true} if this registry has been frozen and is immutable; {@code false} if it is still mutable and + * accepts new registrations * @see #freeze() */ default boolean isFrozen() { diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/RecordCodecBuilder.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/RecordCodecBuilder.java index a3da06e..2a4ee4b 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/RecordCodecBuilder.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/codec/RecordCodecBuilder.java @@ -958,9 +958,8 @@ public DataResult decode(@NotNull final DynamicOps ops, * Internal tuple for accumulating 3 decoded values before applying the constructor. * *

This record is used internally during the decoding process to collect - * intermediate results when decoding records with 3 or more fields. The values - * are accumulated using {@link DataResult#apply2} before being passed to the - * final constructor function.

+ * intermediate results when decoding records with 3 or more fields. The values are accumulated using + * {@link DataResult#apply2} before being passed to the final constructor function.

* * @param a the first decoded value * @param b the second decoded value @@ -976,9 +975,8 @@ private record Tuple3(A a, B b, C c) { * Internal tuple for accumulating 4 decoded values before applying the constructor. * *

This record is used internally during the decoding process to collect - * intermediate results when decoding records with 4 or more fields. The values - * are accumulated using {@link DataResult#apply2} before being passed to the - * final constructor function.

+ * intermediate results when decoding records with 4 or more fields. The values are accumulated using + * {@link DataResult#apply2} before being passed to the final constructor function.

* * @param a the first decoded value * @param b the second decoded value @@ -996,9 +994,8 @@ private record Tuple4(A a, B b, C c, D d) { * Internal tuple for accumulating 5 decoded values before applying the constructor. * *

This record is used internally during the decoding process to collect - * intermediate results when decoding records with 5 or more fields. The values - * are accumulated using {@link DataResult#apply2} before being passed to the - * final constructor function.

+ * intermediate results when decoding records with 5 or more fields. The values are accumulated using + * {@link DataResult#apply2} before being passed to the final constructor function.

* * @param a the first decoded value * @param b the second decoded value diff --git a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/diagnostic/MigrationReport.java b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/diagnostic/MigrationReport.java index dcb9fca..9779aa1 100644 --- a/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/diagnostic/MigrationReport.java +++ b/aether-datafixers-api/src/main/java/de/splatgames/aether/datafixers/api/diagnostic/MigrationReport.java @@ -288,8 +288,8 @@ interface Builder { * Marks the start of a migration operation. * *

This method must be called exactly once before any other builder methods - * (except for {@link #build()}). It initializes the migration metadata including - * the type being migrated and the version range.

+ * (except for {@link #build()}). It initializes the migration metadata including the type being migrated and + * the version range.

* *

Timing

*

The start time is recorded when this method is called, which is used to @@ -313,16 +313,16 @@ Builder startMigration( * Sets the input data snapshot. * *

This method captures a JSON representation of the data before any fixes - * are applied. This is useful for debugging to compare the original input with - * the final output or intermediate states.

+ * are applied. This is useful for debugging to compare the original input with the final output or intermediate + * states.

* *

Snapshot Format

*

The snapshot is typically a JSON string representation of the input data. - * Depending on the {@link DiagnosticOptions}, it may be pretty-printed or compact, - * and may be truncated if it exceeds the maximum snapshot length.

+ * Depending on the {@link DiagnosticOptions}, it may be pretty-printed or compact, and may be truncated if it + * exceeds the maximum snapshot length.

* - * @param snapshot the JSON representation of input data; may be {@code null} to - * indicate no snapshot should be captured + * @param snapshot the JSON representation of input data; may be {@code null} to indicate no snapshot should be + * captured * @return this builder for method chaining; never {@code null} */ @NotNull @@ -332,13 +332,13 @@ Builder startMigration( * Marks the start of a fix execution. * *

This method should be called immediately before a {@link DataFix} is applied. - * It records the start time for the fix and prepares the builder to accept - * rule application events and snapshots for this fix.

+ * It records the start time for the fix and prepares the builder to accept rule application events and + * snapshots for this fix.

* *

Pairing Requirement

*

Each call to {@code startFix} must be followed by exactly one call to - * {@link #endFix(DataFix, Duration, String)} for the same fix. Nested or - * overlapping fix executions are not supported.

+ * {@link #endFix(DataFix, Duration, String)} for the same fix. Nested or overlapping fix executions are not + * supported.

* * @param fix the fix that is about to be applied; must not be {@code null} * @return this builder for method chaining; never {@code null} @@ -356,8 +356,8 @@ Builder startMigration( * fix is applied. It must be called after {@link #startFix(DataFix)} and before * {@link #endFix(DataFix, Duration, String)}.

* - * @param snapshot the JSON representation of data before fix application; - * may be {@code null} to indicate no snapshot should be captured + * @param snapshot the JSON representation of data before fix application; may be {@code null} to indicate no + * snapshot should be captured * @return this builder for method chaining; never {@code null} * @throws IllegalStateException if no fix is currently in progress */ @@ -367,9 +367,10 @@ Builder startMigration( /** * Records a rule application within the current fix. * - *

This method records details about an individual {@link de.splatgames.aether.datafixers.api.rewrite.TypeRewriteRule} - * application, including whether it matched and transformed the data. Multiple - * rule applications can be recorded for a single fix.

+ *

This method records details about an individual + * {@link de.splatgames.aether.datafixers.api.rewrite.TypeRewriteRule} + * application, including whether it matched and transformed the data. Multiple rule applications can be + * recorded for a single fix.

* *

Recording Order

*

Rule applications are recorded in the order they are added and will appear @@ -388,8 +389,7 @@ Builder startMigration( * Marks the end of a fix execution. * *

This method should be called immediately after a {@link DataFix} has been - * applied. It finalizes the fix execution record with the total duration and - * an optional after snapshot.

+ * applied. It finalizes the fix execution record with the total duration and an optional after snapshot.

* *

Pairing Requirement

*

This method must be called after {@link #startFix(DataFix)} for the same fix. @@ -397,12 +397,12 @@ Builder startMigration( * * @param fix the fix that was applied; must not be {@code null} * @param duration the total time taken to apply the fix; must not be {@code null} - * @param afterSnapshot optional JSON representation of data after fix application; - * may be {@code null} to indicate no snapshot should be captured + * @param afterSnapshot optional JSON representation of data after fix application; may be {@code null} to + * indicate no snapshot should be captured * @return this builder for method chaining; never {@code null} * @throws NullPointerException if {@code fix} or {@code duration} is {@code null} - * @throws IllegalStateException if no fix is currently in progress or if the fix - * does not match the one started + * @throws IllegalStateException if no fix is currently in progress or if the fix does not match the one + * started * @see #startFix(DataFix) */ @NotNull @@ -416,8 +416,8 @@ Builder endFix( * Adds a touched type reference to the report. * *

This method records that a particular type was encountered and processed - * during the migration. The touched types set includes the primary type and - * any nested or referenced types that were traversed.

+ * during the migration. The touched types set includes the primary type and any nested or referenced types that + * were traversed.

* *

Deduplication

*

Adding the same type reference multiple times has no additional effect; @@ -434,8 +434,8 @@ Builder endFix( * Adds a warning message to the report. * *

This method records a warning that occurred during migration. Warnings are - * non-fatal issues that did not prevent the migration from completing but may - * indicate potential problems or unexpected conditions.

+ * non-fatal issues that did not prevent the migration from completing but may indicate potential problems or + * unexpected conditions.

* *

Common Warnings

*