Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 56 additions & 0 deletions .github/scripts/license-gate.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# OSS license gate for the Android app.
#
# Reads the jaredsburrows `licenseReleaseReport` JSON — which is build-resolved, so it
# reflects the app's real, complete dependency closure (unlike a manifest scanner).
# Android ships to end-user devices (a distribution context), so weak copyleft
# (LGPL/MPL/EPL) is blocked alongside strong/network copyleft and source-available.
#
# Usage: license-gate.sh <report.json> [enforce]
# enforce=true -> exit 1 on violations (blocking)
# enforce=false -> warn only, always exit 0 (pilot; default)
set -euo pipefail

REPORT="${1:?usage: license-gate.sh <licenseReleaseReport.json> [enforce]}"
ENFORCE="${2:-false}"

if [[ ! -f "$REPORT" ]]; then
echo "::warning::license report not found at $REPORT (did licenseReleaseReport run?) — skipping gate"
exit 0
fi

# jaredsburrows reports free-text license NAMES, not SPDX ids, so match by pattern.
# Blocked for a distributed app = any copyleft + source-available license.
BLOCK_RE='Affero|AGPL|Lesser General Public|LGPL|GNU General Public|GPLv|GPL-2|GPL-3|Mozilla Public|MPL-|Eclipse Public|EPL-|Common Development and Distribution|CDDL|Server Side Public|SSPL|Business Source|BUSL|Commons Clause'

total=$(jq 'length' "$REPORT")
blocked=$(jq -r --arg re "$BLOCK_RE" '
.[] | .dependency as $d | (.licenses // [])
| .[]? | select(.license | test($re; "i")) | "\($d) :: \(.license)"' "$REPORT" | sort -u)
unknown=$(jq -r '.[] | select((.licenses // []) | length == 0) | .dependency' "$REPORT" | sort -u)

n_blocked=$([[ -n "$blocked" ]] && grep -c . <<<"$blocked" || echo 0)
n_unknown=$([[ -n "$unknown" ]] && grep -c . <<<"$unknown" || echo 0)

echo "### License gate"
echo "Dependencies scanned: $total"
echo "Blocked (copyleft / source-available): $n_blocked"
[[ -n "$blocked" ]] && sed 's/^/ - /' <<<"$blocked"
echo "No detected license: $n_unknown"
[[ -n "$unknown" ]] && sed 's/^/ - /' <<<"$unknown" | head -20

if [[ -n "${GITHUB_STEP_SUMMARY:-}" ]]; then
{
echo "### License gate"
echo "- Dependencies: $total"
echo "- Blocked (copyleft / source-available): $n_blocked"
echo "- No detected license: $n_unknown"
[[ -n "$blocked" ]] && { echo; echo '```'; echo "$blocked"; echo '```'; }
} >> "$GITHUB_STEP_SUMMARY"
fi

if [[ -n "$blocked" && "$ENFORCE" == "true" ]]; then
echo "::error::Disallowed licenses present. Remove/replace, or record an Engineering-Lead-approved exception."
exit 1
fi
echo "OK ($([[ "$ENFORCE" == "true" ]] && echo enforcing || echo 'warn-only'))"
49 changes: 45 additions & 4 deletions .github/workflows/license-inventory.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,52 @@
# Weekly OSS license inventory for the Android app.
#
# Uses the build-resolved jaredsburrows licenseReleaseReport (accurate, full dependency
# closure) — not a manifest scanner. Commits the sorted JSON as annual-review evidence;
# "what changed since last year" is `git diff` on .license/inventory.json.
name: license-inventory

on:
schedule:
- cron: "0 5 * * 1" # Mondays ~06:00 Europe/Stockholm
workflow_dispatch:

permissions:
contents: write
pull-requests: write

jobs:
inventory:
permissions:
contents: write
pull-requests: write
uses: HedvigInsurance/prod-env/.github/workflows/license-inventory.yml@master
runs-on: ubuntu-latest-8-vcpu
steps:
- uses: actions/checkout@v5
- name: Setup CI
uses: ./.github/actions/common-setup
with:
gradle-cache-read-only: "true"
datadog-api-key: ${{ secrets.DATADOG_API_KEY }}
lokalise-id: ${{ secrets.LOKALISE_ID }}
lokalise-token: ${{ secrets.LOKALISE_TOKEN }}

- name: Generate license report
run: ./gradlew licenseReleaseReport --no-configuration-cache --continue

- name: Stage inventory (sorted for stable diffs)
run: |
mkdir -p .license
rm -f .license/inventory.cdx.json # remove the old Trivy-based inventory
jq 'sort_by(.dependency)' \
app/app/build/reports/licenses/licenseReleaseReport.json \
> .license/inventory.json

- name: Open PR if inventory changed
uses: peter-evans/create-pull-request@22a9089034f40e5a961c8808d113e2c98fb63676 # v7.0.11
with:
add-paths: .license
branch: chore/license-inventory
title: "chore: update license inventory"
commit-message: "chore: update license inventory"
labels: dependencies
body: |
Build-resolved OSS license inventory (jaredsburrows `licenseReleaseReport`),
kept as annual-review evidence. Opens only when the dependency/license set
changed since the last snapshot.
16 changes: 0 additions & 16 deletions .github/workflows/license.yml

This file was deleted.

6 changes: 6 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@ jobs:
- name: Run license release report
run: ./gradlew licenseReleaseReport --no-configuration-cache --continue
continue-on-error: true
- name: OSS license gate (warn-only)
# Reuses the report generated just above (no extra Gradle run). Warn-only for the
# pilot: continue-on-error + enforce=false. To enforce: pass `true` and drop
# continue-on-error so a forbidden license blocks the build.
run: bash .github/scripts/license-gate.sh app/app/build/reports/licenses/licenseReleaseReport.json false
continue-on-error: true
- name: Build
run: "./gradlew :app:bundleDebug"
- name: Setup build tool version variable
Expand Down
Loading