From cd7dd7ea473c61a8389e11f91dd9a434524ec241 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Tue, 16 Jun 2026 13:02:23 +0200 Subject: [PATCH 1/3] ci: adopt netresearch/.github skill template Drift-enforced canonical CI with explicit per-call-site permissions. CodeQL via default setup. Signed-off-by: Sebastian Mendel --- .github/dependabot.yml | 46 +++++++++++++++++++++- .github/labeler.yml | 15 +++++++ .github/template.yaml | 20 ++++++++++ .github/workflows/auto-merge-deps.yml | 5 ++- .github/workflows/check-template-drift.yml | 18 +++++++++ .github/workflows/eval-validate.yml | 18 +++++++++ .github/workflows/harness-verify.yml | 17 ++++++++ .github/workflows/labeler.yml | 17 ++++++++ .github/workflows/lint.yml | 20 ++++++++++ .github/workflows/release.yml | 30 +++++--------- .github/workflows/scorecard.yml | 23 +++++++++++ .github/workflows/security.yml | 41 +++++++++++++++++++ 12 files changed, 246 insertions(+), 24 deletions(-) create mode 100644 .github/labeler.yml create mode 100644 .github/template.yaml create mode 100644 .github/workflows/check-template-drift.yml create mode 100644 .github/workflows/eval-validate.yml create mode 100644 .github/workflows/harness-verify.yml create mode 100644 .github/workflows/labeler.yml create mode 100644 .github/workflows/lint.yml create mode 100644 .github/workflows/scorecard.yml create mode 100644 .github/workflows/security.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 6c5049e..2d559bb 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,10 +1,52 @@ +# Managed by netresearch/.github/templates/skill/ +# +# Declares only the ecosystems every skill consumer is guaranteed to have: +# github-actions (CI workflows) and composer (every skill repo ships a +# composer.json for split-licensing / Packagist distribution). +# +# npm and devcontainers are OPT-IN: a repo that actually has package.json +# fixtures (e.g. skills//references/examples/**) or a devcontainer adds +# the block below to its own dependabot.yml and lists `.github/dependabot.yml` +# under `intentional-drift:` in .github/template.yaml so the template sync +# stops managing the file. Declaring an ecosystem without its manifest makes +# the Dependabot run fail with `dependency_file_not_found`. +# +# Opt-in npm (use Dependabot's plural `directories:` to consolidate multiple +# fixture dirs into one grouped PR): +# - package-ecosystem: npm +# directories: +# - /skills//references/examples/ +# schedule: +# interval: weekly +# day: monday +# open-pull-requests-limit: 5 +# groups: +# npm: +# patterns: ['*'] +# cooldown: +# default-days: 7 version: 2 updates: - package-ecosystem: github-actions directory: / schedule: interval: weekly + day: monday + open-pull-requests-limit: 5 groups: github-actions: - patterns: - - "*" + patterns: ['*'] + cooldown: + default-days: 7 + + - package-ecosystem: composer + directory: / + schedule: + interval: weekly + day: monday + open-pull-requests-limit: 5 + groups: + composer: + patterns: ['*'] + cooldown: + default-days: 7 diff --git a/.github/labeler.yml b/.github/labeler.yml new file mode 100644 index 0000000..a388f6b --- /dev/null +++ b/.github/labeler.yml @@ -0,0 +1,15 @@ +documentation: + - changed-files: + - any-glob-to-any-file: ['**/*.md', 'docs/**/*', '**/SKILL.md', 'README.md'] +ci: + - changed-files: + - any-glob-to-any-file: ['.github/**/*', 'Makefile'] +dependencies: + - changed-files: + - any-glob-to-any-file: ['composer.json', 'composer.lock', 'package.json', 'package-lock.json', 'bun.lock', 'bun.lockb', 'yarn.lock', '.devcontainer/**/*'] +skill: + - changed-files: + - any-glob-to-any-file: ['skills/**/*', '.claude-plugin/**/*', 'plugin.json'] +evals: + - changed-files: + - any-glob-to-any-file: ['**/evals/**/*', '**/eval/**/*', '**/*.eval.yaml', '**/*.eval.yml'] diff --git a/.github/template.yaml b/.github/template.yaml new file mode 100644 index 0000000..53bcc09 --- /dev/null +++ b/.github/template.yaml @@ -0,0 +1,20 @@ +# Managed by netresearch/.github/templates/skill/ +# Drift from the template is blocking CI in this repo (check-template-drift.yml). +# Record explicit exceptions under intentional-drift[] to unblock. +# +# CodeQL: skill repos use GitHub **CodeQL default setup** (a repo setting, +# enabled via Settings > Code security, or `gh api -X PUT +# repos///code-scanning/default-setup -f state=configured`). +# Default setup auto-detects the repo's interpreted languages and conflicts +# with an advanced codeql.yml workflow — so this template intentionally ships +# NO codeql.yml. Scorecard (scorecard.yml) is separate and IS templated. +# +# Common opt-ins for skill repos (list the file here so the template sync +# stops managing it, then customize it in-repo): +# - .github/workflows/validate-agents.yml (only for skills that ship an +# AGENTS.md generator under skills//scripts; the reusable's +# scripts-path input is repo-specific so this is never byte-identical) +# - .github/dependabot.yml (when the repo has npm fixtures/devcontainers +# beyond the guaranteed github-actions + composer ecosystems) +template: skill +intentional-drift: [] diff --git a/.github/workflows/auto-merge-deps.yml b/.github/workflows/auto-merge-deps.yml index b14fb2e..6b6c8ad 100644 --- a/.github/workflows/auto-merge-deps.yml +++ b/.github/workflows/auto-merge-deps.yml @@ -1,7 +1,10 @@ name: Auto-merge dependency PRs on: - pull_request: + # auto-merge only calls `gh pr merge` via the reusable with the base-repo + # token; it never checks out or runs PR head code, so pull_request_target + # (required for a write token on Dependabot/Renovate fork PRs) is safe here. + pull_request_target: # zizmor: ignore[dangerous-triggers] permissions: {} diff --git a/.github/workflows/check-template-drift.yml b/.github/workflows/check-template-drift.yml new file mode 100644 index 0000000..d7617f8 --- /dev/null +++ b/.github/workflows/check-template-drift.yml @@ -0,0 +1,18 @@ +name: Template Drift + +on: + pull_request: + branches: [main] + push: + branches: [main] + merge_group: + +permissions: {} + +jobs: + drift: + uses: netresearch/.github/.github/workflows/check-template-drift.yml@main + with: + template: skill + permissions: + contents: read diff --git a/.github/workflows/eval-validate.yml b/.github/workflows/eval-validate.yml new file mode 100644 index 0000000..54b335c --- /dev/null +++ b/.github/workflows/eval-validate.yml @@ -0,0 +1,18 @@ +name: Eval Validation + +# Runs the skill's eval suite via the skill-repo-skill reusable. The reusable +# declares `permissions: contents: read` at top level (read-only validator); +# the calling job mirrors it explicitly. + +on: + push: + branches: [main] + pull_request: + +permissions: {} + +jobs: + eval-validate: + uses: netresearch/skill-repo-skill/.github/workflows/eval-validate.yml@main + permissions: + contents: read diff --git a/.github/workflows/harness-verify.yml b/.github/workflows/harness-verify.yml new file mode 100644 index 0000000..773a929 --- /dev/null +++ b/.github/workflows/harness-verify.yml @@ -0,0 +1,17 @@ +name: Harness Verification + +# Verifies agent-harness consistency (AGENTS.md index, docs drift) via the +# skill-repo-skill reusable. The reusable declares `permissions: contents: +# read` at top level (read-only validator); the calling job mirrors it. + +on: + pull_request: + branches: [main] + +permissions: {} + +jobs: + harness-verify: + uses: netresearch/skill-repo-skill/.github/workflows/harness-verify.yml@main + permissions: + contents: read diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml new file mode 100644 index 0000000..f8a5853 --- /dev/null +++ b/.github/workflows/labeler.yml @@ -0,0 +1,17 @@ +name: Labeler + +on: + # labeler only applies labels via the reusable using the base-repo token; it + # never checks out or runs PR head code. pull_request_target is required to + # label fork PRs. + pull_request_target: # zizmor: ignore[dangerous-triggers] + types: [opened, synchronize, reopened] + +permissions: {} + +jobs: + labeler: + uses: netresearch/.github/.github/workflows/labeler.yml@main + permissions: + contents: read + pull-requests: write diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..976e342 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,20 @@ +name: Lint + +# Skill validation (SKILL.md word cap, plugin.json schema, markdown lint, …) +# via the skill-repo-skill reusable. The reusable declares `permissions: +# contents: read` at top level; the calling job mirrors it explicitly so the +# grant is visible at the call site and independent of the repo default. + +on: + push: + branches: [main] + pull_request: + +permissions: {} + +jobs: + validate: + name: Skill Validation + uses: netresearch/skill-repo-skill/.github/workflows/validate.yml@main + permissions: + contents: read diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 505bced..11a604a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,34 +1,22 @@ name: Release -# Triggered by signed-tag push (`v*`). Delegates the entire release to the -# org's `release-composer-package` reusable workflow, which validates the -# tag (semver, annotated/signed), validates `composer.json`, and creates -# the GitHub Release atomically with auto-generated notes. -# -# Packagist syncs the new version via webhook on tag push, independent of -# this workflow. +# Skill release pipeline (composer/npm package publish + Sigstore cosign + +# SHA256SUMS attestation) via the skill-repo-skill reusable. The reusable's +# release job declares contents/id-token/attestations: write; the calling job +# grants exactly that union. id-token + attestations are required for the +# OIDC-backed Sigstore signing and the GitHub native attestation API. on: push: tags: - 'v*' - workflow_dispatch: - inputs: - tag: - description: "Tag to release (e.g. v2.1.0). Used for backfills." - required: true - type: string permissions: {} jobs: release: - uses: netresearch/.github/.github/workflows/release-composer-package.yml@main + uses: netresearch/skill-repo-skill/.github/workflows/release.yml@main permissions: - contents: write - with: - # `inputs.tag` is only populated by workflow_dispatch; on tag-push the - # `inputs` context is empty, so we fall back to `github.ref_name` (the - # pushed tag). This makes the contract explicit at the caller boundary - # instead of relying on the reusable workflow's empty-string fallback. - tag: ${{ inputs.tag || github.ref_name }} + contents: write # release upload + id-token: write # OIDC for sigstore (cosign + attest) + attestations: write # GitHub native attestation API diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 0000000..d43143e --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,23 @@ +name: OpenSSF Scorecard + +# Closes the skill-repo security gap: supply-chain posture scoring (branch +# protection, pinned actions, token permissions, …). Runs on default-branch +# push and on a weekly schedule; results upload to the code-scanning dashboard. + +on: + push: + branches: [main] + schedule: + - cron: '0 0 * * 0' + workflow_dispatch: + +permissions: {} + +jobs: + scorecard: + uses: netresearch/.github/.github/workflows/scorecard.yml@main + permissions: + contents: read + security-events: write + id-token: write + actions: read diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml new file mode 100644 index 0000000..c371e9e --- /dev/null +++ b/.github/workflows/security.yml @@ -0,0 +1,41 @@ +name: Security + +# Aggregated security scans for skill repos: secret scanning (gitleaks), +# dependency review on PRs, and a composer audit (every skill repo ships a +# composer.json for split-licensing / Packagist distribution). +# +# Top-level `permissions: {}` denies everything by default; each reusable +# caller job re-declares the exact union its reusable's jobs require, so the +# token passed to each reusable is fully explicit and never relies on the +# repo default. This is the same pattern proven in netresearch/.github's +# go-app template (top {} + per-job security-events: write). + +on: + push: + branches: [main] + pull_request: + branches: [main] + +permissions: {} + +jobs: + gitleaks: + uses: netresearch/.github/.github/workflows/gitleaks.yml@main + permissions: + contents: read + security-events: write + secrets: + GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }} + + dependency-review: + if: github.event_name == 'pull_request' + uses: netresearch/.github/.github/workflows/dependency-review.yml@main + permissions: + contents: read + pull-requests: write + + composer-audit: + uses: netresearch/typo3-ci-workflows/.github/workflows/security.yml@main + permissions: + contents: read + security-events: write From 0dc2b793d86b251900a1eacaf43cacc17c5e7f40 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Wed, 17 Jun 2026 15:41:26 +0200 Subject: [PATCH 2/3] fix(ci): adopt php-module template (was wrongly skill); scope false-positive unlink SAST This repo is a PHP composer plugin, not a skill repo. Re-scope the .github/ governance from the skill template to the php-module template: - Remove skill-only workflows (eval-validate, harness-verify, lint). - Sync security.yml, scorecard.yml, check-template-drift.yml and template.yaml byte-identically from netresearch/.github templates/php-module/. dependabot.yml, labeler.yml and auto-merge-deps.yml were already byte-identical. - Keep the repo's own ci.yml and release.yml (PHP test/lint/release). Add scoped Opengrep suppressions for 6 confirmed false-positive php.lang.security.unlink-use findings. Five are the safe atomic-write cleanup pattern (unlink of a freshly-created random-named temp file on rename failure); one is recursive cleanup of plugin-managed directory contents from directory iteration. None use untrusted input. Signed-off-by: Sebastian Mendel --- .github/template.yaml | 23 +++++++-------------- .github/workflows/check-template-drift.yml | 5 ++--- .github/workflows/eval-validate.yml | 18 ---------------- .github/workflows/harness-verify.yml | 17 --------------- .github/workflows/lint.yml | 20 ------------------ .github/workflows/scorecard.yml | 8 +++---- .github/workflows/security.yml | 8 +++---- src/AgentsMdGenerator.php | 3 ++- src/Lock/SkillLockIo.php | 3 ++- src/Trust/TrustStore.php | 3 ++- src/Util/ComposerJsonDirectSkillsWriter.php | 6 ++++-- src/Util/FilesystemUtil.php | 3 ++- 12 files changed, 28 insertions(+), 89 deletions(-) delete mode 100644 .github/workflows/eval-validate.yml delete mode 100644 .github/workflows/harness-verify.yml delete mode 100644 .github/workflows/lint.yml diff --git a/.github/template.yaml b/.github/template.yaml index 53bcc09..2f5bdc5 100644 --- a/.github/template.yaml +++ b/.github/template.yaml @@ -1,20 +1,11 @@ -# Managed by netresearch/.github/templates/skill/ +# Managed by netresearch/.github/templates/php-module/ # Drift from the template is blocking CI in this repo (check-template-drift.yml). # Record explicit exceptions under intentional-drift[] to unblock. # -# CodeQL: skill repos use GitHub **CodeQL default setup** (a repo setting, -# enabled via Settings > Code security, or `gh api -X PUT -# repos///code-scanning/default-setup -f state=configured`). -# Default setup auto-detects the repo's interpreted languages and conflicts -# with an advanced codeql.yml workflow — so this template intentionally ships -# NO codeql.yml. Scorecard (scorecard.yml) is separate and IS templated. -# -# Common opt-ins for skill repos (list the file here so the template sync -# stops managing it, then customize it in-repo): -# - .github/workflows/validate-agents.yml (only for skills that ship an -# AGENTS.md generator under skills//scripts; the reusable's -# scripts-path input is repo-specific so this is never byte-identical) -# - .github/dependabot.yml (when the repo has npm fixtures/devcontainers -# beyond the guaranteed github-actions + composer ecosystems) -template: skill +# For PHP modules (Magento 2 / Shopware 6 / composer SDKs). CI surface is the +# security/quality reusables with explicit per-call-site permissions (immune to +# a restricted default_workflow_permissions). CodeQL is via GitHub default setup +# (a repo setting), so no codeql.yml here. Add a test workflow per repo as +# needed and list it under intentional-drift. +template: php-module intentional-drift: [] diff --git a/.github/workflows/check-template-drift.yml b/.github/workflows/check-template-drift.yml index d7617f8..d805565 100644 --- a/.github/workflows/check-template-drift.yml +++ b/.github/workflows/check-template-drift.yml @@ -2,9 +2,8 @@ name: Template Drift on: pull_request: - branches: [main] push: - branches: [main] + branches: [main, master] merge_group: permissions: {} @@ -13,6 +12,6 @@ jobs: drift: uses: netresearch/.github/.github/workflows/check-template-drift.yml@main with: - template: skill + template: php-module permissions: contents: read diff --git a/.github/workflows/eval-validate.yml b/.github/workflows/eval-validate.yml deleted file mode 100644 index 54b335c..0000000 --- a/.github/workflows/eval-validate.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Eval Validation - -# Runs the skill's eval suite via the skill-repo-skill reusable. The reusable -# declares `permissions: contents: read` at top level (read-only validator); -# the calling job mirrors it explicitly. - -on: - push: - branches: [main] - pull_request: - -permissions: {} - -jobs: - eval-validate: - uses: netresearch/skill-repo-skill/.github/workflows/eval-validate.yml@main - permissions: - contents: read diff --git a/.github/workflows/harness-verify.yml b/.github/workflows/harness-verify.yml deleted file mode 100644 index 773a929..0000000 --- a/.github/workflows/harness-verify.yml +++ /dev/null @@ -1,17 +0,0 @@ -name: Harness Verification - -# Verifies agent-harness consistency (AGENTS.md index, docs drift) via the -# skill-repo-skill reusable. The reusable declares `permissions: contents: -# read` at top level (read-only validator); the calling job mirrors it. - -on: - pull_request: - branches: [main] - -permissions: {} - -jobs: - harness-verify: - uses: netresearch/skill-repo-skill/.github/workflows/harness-verify.yml@main - permissions: - contents: read diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 976e342..0000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,20 +0,0 @@ -name: Lint - -# Skill validation (SKILL.md word cap, plugin.json schema, markdown lint, …) -# via the skill-repo-skill reusable. The reusable declares `permissions: -# contents: read` at top level; the calling job mirrors it explicitly so the -# grant is visible at the call site and independent of the repo default. - -on: - push: - branches: [main] - pull_request: - -permissions: {} - -jobs: - validate: - name: Skill Validation - uses: netresearch/skill-repo-skill/.github/workflows/validate.yml@main - permissions: - contents: read diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index d43143e..83700db 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -1,12 +1,12 @@ name: OpenSSF Scorecard -# Closes the skill-repo security gap: supply-chain posture scoring (branch -# protection, pinned actions, token permissions, …). Runs on default-branch -# push and on a weekly schedule; results upload to the code-scanning dashboard. +# Supply-chain posture scoring (branch protection, pinned actions, token +# permissions, …). Runs on default-branch push and on a weekly schedule; +# results upload to the code-scanning dashboard. on: push: - branches: [main] + branches: [main, master] schedule: - cron: '0 0 * * 0' workflow_dispatch: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index c371e9e..1271882 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -1,8 +1,7 @@ name: Security -# Aggregated security scans for skill repos: secret scanning (gitleaks), -# dependency review on PRs, and a composer audit (every skill repo ships a -# composer.json for split-licensing / Packagist distribution). +# Aggregated security scans: secret scanning (gitleaks), dependency review on +# PRs, and a composer audit (every PHP module ships a composer.json). # # Top-level `permissions: {}` denies everything by default; each reusable # caller job re-declares the exact union its reusable's jobs require, so the @@ -12,9 +11,8 @@ name: Security on: push: - branches: [main] + branches: [main, master] pull_request: - branches: [main] permissions: {} diff --git a/src/AgentsMdGenerator.php b/src/AgentsMdGenerator.php index 2dcc7c5..919dfc1 100644 --- a/src/AgentsMdGenerator.php +++ b/src/AgentsMdGenerator.php @@ -119,7 +119,8 @@ public function updateAgentsMd(string $agentsMdPath, array $skills): void } if (!rename($tempPath, $agentsMdPath)) { - @unlink($tempPath); + // cleanup of our own freshly-created temp file (random name) on atomic-write rename failure; path is not user input + @unlink($tempPath); // nosemgrep: php.lang.security.unlink-use.unlink-use throw new \RuntimeException(sprintf('Failed to rename temporary file to: %s', $agentsMdPath)); } } diff --git a/src/Lock/SkillLockIo.php b/src/Lock/SkillLockIo.php index 850ac43..4880fd1 100644 --- a/src/Lock/SkillLockIo.php +++ b/src/Lock/SkillLockIo.php @@ -53,7 +53,8 @@ public function write(SkillLockFile $lock): void throw new \RuntimeException(sprintf('Failed writing temporary skills lock: %s', $temp)); } if (!rename($temp, $path)) { - @unlink($temp); + // cleanup of our own freshly-created temp file (random name) on atomic-write rename failure; path is not user input + @unlink($temp); // nosemgrep: php.lang.security.unlink-use.unlink-use throw new \RuntimeException(sprintf('Failed replacing skills lock: %s', $path)); } } diff --git a/src/Trust/TrustStore.php b/src/Trust/TrustStore.php index 028eadf..2ea6080 100644 --- a/src/Trust/TrustStore.php +++ b/src/Trust/TrustStore.php @@ -115,7 +115,8 @@ public function saveAllowSkills(array $rules): void return; } if (!@rename($tempPath, $this->composerJsonPath)) { - @unlink($tempPath); + // cleanup of our own freshly-created temp file (random name) on atomic-write rename failure; path is not user input + @unlink($tempPath); // nosemgrep: php.lang.security.unlink-use.unlink-use $this->io->writeError(sprintf( 'Failed to atomically replace %s with trust decisions.', $this->composerJsonPath, diff --git a/src/Util/ComposerJsonDirectSkillsWriter.php b/src/Util/ComposerJsonDirectSkillsWriter.php index 372cacb..3a975ce 100644 --- a/src/Util/ComposerJsonDirectSkillsWriter.php +++ b/src/Util/ComposerJsonDirectSkillsWriter.php @@ -62,7 +62,8 @@ public function upsertSource(string $composerJsonPath, SourceEntry $entry): void throw new \RuntimeException(sprintf('Failed writing temp composer.json: %s', $temp)); } if (!rename($temp, $composerJsonPath)) { - @unlink($temp); + // cleanup of our own freshly-created temp file (random name) on atomic-write rename failure; path is not user input + @unlink($temp); // nosemgrep: php.lang.security.unlink-use.unlink-use throw new \RuntimeException(sprintf('Failed replacing composer.json: %s', $composerJsonPath)); } } @@ -119,7 +120,8 @@ public function removeSkillOrSource(string $composerJsonPath, string $name, bool throw new \RuntimeException(sprintf('Failed writing temp composer.json: %s', $temp)); } if (!rename($temp, $composerJsonPath)) { - @unlink($temp); + // cleanup of our own freshly-created temp file (random name) on atomic-write rename failure; path is not user input + @unlink($temp); // nosemgrep: php.lang.security.unlink-use.unlink-use throw new \RuntimeException(sprintf('Failed replacing composer.json: %s', $composerJsonPath)); } } diff --git a/src/Util/FilesystemUtil.php b/src/Util/FilesystemUtil.php index eec8185..6b8ad74 100644 --- a/src/Util/FilesystemUtil.php +++ b/src/Util/FilesystemUtil.php @@ -64,7 +64,8 @@ public static function removeDirectoryTree(string $dir, ?IOInterface $io = null) if (is_dir($path) && !rmdir($path)) { self::reportFsFailure($io, 'rmdir', $path); } - } elseif (file_exists($path) && !unlink($path)) { + // recursive cleanup of plugin-managed directory contents; path from directory iteration, not user input + } elseif (file_exists($path) && !unlink($path)) { // nosemgrep: php.lang.security.unlink-use.unlink-use self::reportFsFailure($io, 'unlink', $path); } } From f122674dedc49d1364ba1ce64fc5a2d779325222 Mon Sep 17 00:00:00 2001 From: Sebastian Mendel Date: Wed, 17 Jun 2026 15:44:22 +0200 Subject: [PATCH 3/3] style: align FilesystemUtil suppression comment indent for PHP-CS-Fixer Signed-off-by: Sebastian Mendel --- src/Util/FilesystemUtil.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Util/FilesystemUtil.php b/src/Util/FilesystemUtil.php index 6b8ad74..57f9a00 100644 --- a/src/Util/FilesystemUtil.php +++ b/src/Util/FilesystemUtil.php @@ -64,7 +64,7 @@ public static function removeDirectoryTree(string $dir, ?IOInterface $io = null) if (is_dir($path) && !rmdir($path)) { self::reportFsFailure($io, 'rmdir', $path); } - // recursive cleanup of plugin-managed directory contents; path from directory iteration, not user input + // recursive cleanup of plugin-managed directory contents; path from directory iteration, not user input } elseif (file_exists($path) && !unlink($path)) { // nosemgrep: php.lang.security.unlink-use.unlink-use self::reportFsFailure($io, 'unlink', $path); }