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
46 changes: 44 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -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/<name>/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/<name>/references/examples/<project>
# 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
15 changes: 15 additions & 0 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -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']
11 changes: 11 additions & 0 deletions .github/template.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# 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.
#
# 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: []
5 changes: 4 additions & 1 deletion .github/workflows/auto-merge-deps.yml
Original file line number Diff line number Diff line change
@@ -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: {}

Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/check-template-drift.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
name: Template Drift

on:
pull_request:
push:
branches: [main, master]
merge_group:

permissions: {}

jobs:
drift:
uses: netresearch/.github/.github/workflows/check-template-drift.yml@main
with:
template: php-module
permissions:
contents: read
17 changes: 17 additions & 0 deletions .github/workflows/labeler.yml
Original file line number Diff line number Diff line change
@@ -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
30 changes: 9 additions & 21 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
23 changes: 23 additions & 0 deletions .github/workflows/scorecard.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: OpenSSF Scorecard

# 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, master]
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
39 changes: 39 additions & 0 deletions .github/workflows/security.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Security

# 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
# 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, master]
pull_request:

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
3 changes: 2 additions & 1 deletion src/AgentsMdGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@
}

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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
throw new \RuntimeException(sprintf('Failed to rename temporary file to: %s', $agentsMdPath));
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Lock/SkillLockIo.php
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
throw new \RuntimeException(sprintf('Failed replacing skills lock: %s', $path));
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Trust/TrustStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,8 @@
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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
$this->io->writeError(sprintf(
'<error>Failed to atomically replace %s with trust decisions.</error>',
$this->composerJsonPath,
Expand Down
6 changes: 4 additions & 2 deletions src/Util/ComposerJsonDirectSkillsWriter.php
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,8 @@
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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
throw new \RuntimeException(sprintf('Failed replacing composer.json: %s', $composerJsonPath));
}
}
Expand Down Expand Up @@ -119,7 +120,8 @@
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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
throw new \RuntimeException(sprintf('Failed replacing composer.json: %s', $composerJsonPath));
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/Util/FilesystemUtil.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@
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

Check warning

Code scanning / Opengrep OSS

Opengrep Finding: php.lang.security.unlink-use.unlink-use Warning

Using user input when deleting files with unlink() is potentially dangerous. A malicious actor could use this to modify or access files they have no right to.
self::reportFsFailure($io, 'unlink', $path);
}
}
Expand Down
Loading