diff --git a/.github/workflows/branch-policy.yml b/.github/workflows/branch-policy.yml index 1dca5b78c..d6723440a 100644 --- a/.github/workflows/branch-policy.yml +++ b/.github/workflows/branch-policy.yml @@ -4,6 +4,9 @@ on: pull_request: branches: [main, beta, development] +permissions: + contents: read + jobs: check-source-branch: name: Branch Policy Check diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 1f7ccf072..b3f847639 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -12,8 +12,14 @@ on: pull_request: branches: [development] +permissions: + contents: read + jobs: deploy: + permissions: + contents: write + packages: write uses: ConductionNL/.github/.github/workflows/documentation.yml@main # A reusable workflow receives NO secrets by default. Without this block the # callee's publish step finds CF_API_TOKEN empty, skips itself on its own diff --git a/.github/workflows/issue-triage.yml b/.github/workflows/issue-triage.yml index a122c1601..30c2dbed1 100644 --- a/.github/workflows/issue-triage.yml +++ b/.github/workflows/issue-triage.yml @@ -10,8 +10,14 @@ on: type: boolean default: true +permissions: + contents: read + jobs: triage: + permissions: + issues: write + contents: read uses: ConductionNL/.github/.github/workflows/issue-triage.yml@main with: app-name: launchpad diff --git a/.github/workflows/openspec-sync.yml b/.github/workflows/openspec-sync.yml index cd5de2c3a..ef24c8790 100644 --- a/.github/workflows/openspec-sync.yml +++ b/.github/workflows/openspec-sync.yml @@ -42,8 +42,14 @@ on: paths: ['openspec/**'] workflow_dispatch: +permissions: + contents: read + jobs: sync: + permissions: + issues: write + contents: read uses: ConductionNL/.github/.github/workflows/openspec-sync.yml@main with: app-name: launchpad diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1a4950612..02ffc4b1b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,9 +27,15 @@ concurrency: group: release-${{ github.ref_name }} cancel-in-progress: false +permissions: + contents: read + jobs: unstable: if: github.ref == 'refs/heads/development' + permissions: + contents: write + pull-requests: write uses: ConductionNL/.github/.github/workflows/release.yml@main with: release-type: unstable @@ -38,6 +44,9 @@ jobs: beta: if: github.ref == 'refs/heads/beta' + permissions: + contents: write + pull-requests: write uses: ConductionNL/.github/.github/workflows/release.yml@main with: release-type: beta @@ -46,6 +55,9 @@ jobs: stable: if: github.ref == 'refs/heads/main' + permissions: + contents: write + pull-requests: write uses: ConductionNL/.github/.github/workflows/release.yml@main with: release-type: stable diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 000000000..974fea8be --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,52 @@ +# SPDX-FileCopyrightText: 2026 Conduction B.V. +# SPDX-License-Identifier: EUPL-1.2 + +# OpenSSF Scorecard: rates this repository's supply-chain practices and +# publishes the result to the public Scorecard API and the Security tab. +name: Scorecard supply-chain security + +on: + branch_protection_rule: + schedule: + - cron: '30 4 * * 1' + push: + branches: [main] + workflow_dispatch: + +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Upload the results to code scanning. + security-events: write + # Publish the results and get a badge (OIDC token for the Scorecard API). + id-token: write + contents: read + actions: read + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Run analysis + uses: ossf/scorecard-action@fa7117db83c52d787ae5a89f350999701505d78e # v2.5.0 + with: + results_file: results.sarif + results_format: sarif + publish_results: true + + - name: Upload artifact + uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + - name: Upload to code scanning + uses: github/codeql-action/upload-sarif@33d7b85da1a04171e211befe2c739303b9a4c066 # v4.39.3 + with: + sarif_file: results.sarif diff --git a/.github/workflows/sync-to-beta.yml b/.github/workflows/sync-to-beta.yml index 76a442693..62fd631be 100644 --- a/.github/workflows/sync-to-beta.yml +++ b/.github/workflows/sync-to-beta.yml @@ -5,6 +5,12 @@ on: branches: - development +permissions: + contents: read + jobs: sync: + permissions: + contents: write + pull-requests: write uses: ConductionNL/.github/.github/workflows/sync-to-beta.yml@main diff --git a/.gitleaks.toml b/.gitleaks.toml new file mode 100644 index 000000000..c519518f8 --- /dev/null +++ b/.gitleaks.toml @@ -0,0 +1,55 @@ +# gitleaks configuration: the default rules, plus allowlists for findings +# that are not secrets. Each allowlist names the exact files and only the +# rules that fire there, so anything else in those files is still reported. + +[extend] +useDefault = true + +[[allowlists]] +description = "Documentation examples: the default admin:admin login of the local development and demo containers." +targetRules = ["curl-auth-user"] +paths = [ + '''(^|/)docs/Installation/demo\-environment\.md$''', +] + +[[allowlists]] +description = "Spec scenario: an example public-share token in a Given/When/Then step, not a credential." +targetRules = ["generic-api-key"] +paths = [ + '''(^|/)openspec/changes/archive/2026\-06\-14\-dashboard\-public\-share/specs/dashboard\-public\-share/spec\.md$''', +] + +[[allowlists]] +description = "Smoke-test notes: curl logins of the throwaway local test users (admin, user, alice)." +targetRules = ["curl-auth-user"] +paths = [ + '''(^|/)openspec/changes/archive/2026\-06\-14\-role\-based\-content/smoke\.md$''', +] + +[[allowlists]] +description = "Spec example: a truncated sample JWT in an SDK usage snippet, not a credential." +targetRules = ["generic-api-key"] +paths = [ + '''(^|/)openspec/changes/embedded\-analytics/specs/sdk\-and\-rendering\.md$''', +] + +[[allowlists]] +description = "Design note: a curl example with a user:app-password placeholder." +targetRules = ["curl-auth-user"] +paths = [ + '''(^|/)openspec/changes/governance\-rest\-api\-reference/design\.md$''', +] + +[[allowlists]] +description = "Spec scenario: an example public-share token in a Given/When/Then step, not a credential." +targetRules = ["generic-api-key"] +paths = [ + '''(^|/)openspec/specs/dashboard\-public\-share/spec\.md$''', +] + +[[allowlists]] +description = "E2E seed script: curl login built from the ADMIN_USER/ADMIN_PASSWORD environment variables, not a literal." +targetRules = ["curl-auth-user"] +paths = [ + '''(^|/)tests/e2e/seed\.sh$''', +] diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php index b473c89c3..d9fca55dd 100644 --- a/phpstan-bootstrap.php +++ b/phpstan-bootstrap.php @@ -2,6 +2,9 @@ /** * PHPStan bootstrap file - registers OCP autoloader for static analysis. + * + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 */ $autoloader = require __DIR__ . '/vendor/autoload.php'; diff --git a/stylelint.config.js b/stylelint.config.js index dbe67ece4..8cc0ba193 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -1,3 +1,7 @@ +/** + * SPDX-FileCopyrightText: 2026 Conduction B.V. + * SPDX-License-Identifier: EUPL-1.2 + */ module.exports = { extends: '@nextcloud/stylelint-config', rules: {