znframework/package-services
ZN Framework Services Package
Last checked 6 hours ago
Check details
Security
.github/workflows directory found
How this check works
A GitHub Actions workflow runs code from other repositories through uses: lines such as actions/checkout@v4. A tag like v4 or a branch name is a moving target: whoever controls that repository can point it at different code at any time, and every workflow using it will run the new code on its next run, with access to the repository's secrets. In March 2025 the popular tj-actions/changed-files action was compromised exactly this way and leaked CI secrets from thousands of repositories. A full 40-character commit SHA cannot be moved, so pinning to one guarantees the same code runs every time.
Plumb reads every workflow file in .github/workflows and collects each uses: reference from jobs and steps. References to the repository's own actions (paths starting with ./ and the repository's own reusable workflows) are skipped, because they are already protected by the repository's own review process. Each remaining reference counts as pinned when the part after @ is a 40-character hexadecimal SHA.
The check passes when every third-party reference is pinned. If more than 80 percent are pinned it warns and awards half credit. Otherwise it fails. The unpinned references are listed in the evidence so they can be fixed. The check is not applicable when the repository has no workflows or the workflows use no third-party actions, and it only runs for repositories hosted on GitHub.
include: entries in the GitLab CI configuration are pinned to a full commit SHA instead of a branch, tag, or version.
Applies only to GitLab-hosted repositories.
How this check works
A GitLab CI pipeline can pull configuration from outside the repository through include: entries: files from other projects, CI/CD components, and remote URLs. When such an include points at a branch, a tag, or a component version such as 1.2 or ~latest, whoever controls the source can change what the pipeline runs without touching this repository. This is the same class of attack seen against tag-pinned GitHub Actions in the tj-actions/changed-files incident of March 2025. A full 40-character commit SHA cannot be changed after the fact.
Plumb reads the project's CI configuration file (.gitlab-ci.yml, or the custom path the project sets) and classifies each include. local: files from the same repository and GitLab-bundled template: includes are skipped, because they are covered by the repository's own review controls or ship with GitLab itself. A project: include is pinned when its ref is a full SHA. A component: include is pinned when its version after @ is a full SHA. A remote: URL has no pinning mechanism and always counts as unpinned.
The check passes when every external include is pinned. If more than 80 percent are pinned it warns and awards half credit. Otherwise it fails. It is not applicable when there is no CI configuration or no external includes, and it only runs for repositories hosted on GitLab.
This is a veto check: if it fails, the Security score is capped at 30/100 no matter how other checks score. The veto is not currently affecting this package's score. Learn how scoring works.
- Latest release
- 8.21.0
- Advisories on record
- 0
- Advisory feeds consulted
-
- ghsa
- packagist
- Advisory feeds unavailable
- none
How this check works
A security advisory is a public notice that a specific range of package versions has a known vulnerability. When the newest release still falls inside that range, everyone who installs the package today is installing a known problem. This is the most concrete risk Plumb can detect.
Plumb reads two independent advisory feeds: GitHub Security Advisories and the Packagist security advisories database. The two feeds are merged and duplicates are removed by CVE id, so a delay or outage in one feed cannot hide an advisory the other one knows about.
The check passes when no advisory covers the latest released version. It fails when at least one advisory does: either no fixed version has been published yet, or the fix is not newer than the latest release. Failing also caps the Security score at 30 out of 100. The check is not applicable when the package has no stable release to evaluate. If one feed is unreachable the other still produces a verdict and the failure is noted in the evidence; if every feed is unreachable the check reports an error instead of guessing.
- Open general pull requests
- 0
- Open security pull requests
- 0
- Oldest general pull request, in days
- none
- Oldest security pull request, in days
- none
- General pull requests
- none
- Security pull requests
- none
How this check works
An automated update tool only helps if its pull requests get reviewed and merged. Dependabot pull requests left open for months mean fixes are known but not shipped, and the gap keeps growing as more releases pile up. Security fixes are the urgent case, because they address a published vulnerability.
Plumb lists the repository's open pull requests authored by Dependabot and sorts them into two groups. A pull request whose description mentions a CVE or GHSA identifier is a security update. Everything else is a general update. For each group only the oldest open pull request matters.
Security updates pass when the oldest is 14 days old or less, earn 70 percent credit up to 30 days, 30 percent credit up to 90 days, and fail beyond 90 days. General updates pass up to 30 days, earn 70 percent credit up to 60 days, 30 percent credit up to 180 days, and fail beyond 180 days. The final result is whichever group scored worse. A repository with no open Dependabot pull requests passes. The check only runs for repositories hosted on GitHub.
How this check works
An automated update tool only helps if its merge requests get reviewed and merged. Renovate merge requests left open for months mean fixes are known but not shipped, and the gap keeps growing as more releases pile up. Security fixes are the urgent case, because they address a published vulnerability.
Plumb lists the project's open merge requests and keeps the ones that come from Renovate: the source branch starts with renovate/, or the author's username contains renovate. These are mechanical markers, not a judgement about the account. Each merge request is then sorted into two groups. One whose description mentions a CVE or GHSA identifier is a security update. Everything else is a general update. For each group only the oldest open merge request matters.
Security updates pass when the oldest is 14 days old or less, earn 70 percent credit up to 30 days, 30 percent credit up to 90 days, and fail beyond 90 days. General updates pass up to 30 days, earn 70 percent credit up to 60 days, 30 percent credit up to 180 days, and fail beyond 180 days. The final result is whichever group scored worse. A project with no open Renovate merge requests passes. The check only runs for repositories hosted on GitLab.
- Update tool
- none
- JavaScript lockfile committed
- No
- GitHub Actions workflows present
- No
- Composer lockfile committed
- No
- Locations searched
-
- renovate.json
- renovate.json5
- .github/renovate.json
- .github/dependabot.yml
- package.json
- JavaScript lockfiles
- none
How this check works
Most vulnerabilities reach a project through its dependencies rather than its own code. Automated update tools such as Dependabot and Renovate open pull requests as new versions appear, so fixes arrive without anyone having to remember to look. A tool that is installed but skips an ecosystem in use leaves that part of the project unpatched.
Plumb looks for a Renovate configuration (renovate.json, renovate.json5, the .github/ or .gitlab/ variants, or a renovate key in package.json) or a Dependabot configuration at .github/dependabot.yml. It then works out which ecosystems the repository maintains: Composer when a composer.lock is committed, GitHub Actions when workflow files exist, and JavaScript when an npm, yarn, pnpm, or bun lockfile is committed. For Dependabot, coverage comes from the package-ecosystem entries. For Renovate, coverage follows enabledManagers; when that setting is absent Renovate covers everything by default.
The check passes when a tool is configured and covers every ecosystem in use. It is not applicable when the repository has no committed lockfiles and no workflows, because there is nothing for an updater to maintain. It fails when no tool is configured, or when the tool covers none of the ecosystems in use. It warns when some ecosystems are missed: a missing Composer manager is the most serious gap and leaves the least credit, while a missing GitHub Actions or JavaScript manager leaves most of the credit.
How this check works
When a package release is malicious or badly broken, it is usually discovered and pulled within the first few days. Projects that update the moment a release appears are the ones that get caught. A cooldown tells the update tool to wait a minimum number of days before proposing a new version, giving the wider community time to notice problems first. The 2024 xz-utils backdoor targeted exactly this early-adopter window.
Plumb reads the same configuration files as the updater check. In a Dependabot configuration it looks for a cooldown block on each update entry and takes the smallest of default-days, semver-major-days, semver-minor-days, and semver-patch-days. In a Renovate configuration it reads minimumReleaseAge, at the top level or inside packageRules, and takes the smallest value expressed in days.
The check passes when the smallest configured cooldown is at least 3 days. It warns with proportional credit when a cooldown is set but shorter than 3 days, and fails when the updater has no cooldown at all. It is not applicable when no dependency update tool is configured, since that is already reported by the updater check.
SECURITY.md file.
- Locations searched
-
- SECURITY.md
- .github/SECURITY.md
- docs/SECURITY.md
How this check works
When someone finds a vulnerability in a package, they need to know where to report it. Without a documented process, many people never report it at all, and others post it in a public issue where attackers can read it before a fix exists. A security policy gives researchers a private place to send reports.
Plumb looks for a SECURITY.md file in the repository root, in .github/, or in docs/. A package registered with Plumb can also supply a security policy during registration, which counts the same way; this is how private repositories satisfy the check.
The check passes when a policy is found in any of those places and fails when none is found. It only checks that a policy exists. It does not judge the writing or test whether the contact address works. If the repository files cannot be read and no registered policy exists, the check is unassessable rather than failed.
Maintenance
This is a veto check: if it fails, every category score is set to 0/100 no matter how other checks score. The veto is not currently affecting this package's score. Learn how scoring works.
- Archived on
- none
- Repository archived
- No
- Marked abandoned on Packagist
- No
- Suggested replacement
- none
- Sources consulted
-
- packagist
- github
How this check works
A maintainer can tell the world that a package will not receive further fixes in two places: by marking it abandoned on Packagist, optionally naming a replacement, or by archiving the repository on the source host. Either one is the maintainer's own statement that future fixes will not come. That is the strongest warning a package can carry short of being malicious, and no amount of recent activity elsewhere changes it.
Plumb reads the abandoned flag from Packagist and the archived flag from the repository. Maintainers often set one without the other, so either one alone is enough. For a package that is not listed on public Packagist, the archived flag is the only signal available.
The check passes when neither flag is set and fails when either is. Failing sets every category score and the composite score to 0. When Packagist already carries the abandoned flag, the scan stops at this check, because nothing the other checks measure could change the outcome. When the maintainer named a replacement package, it is included in the evidence so the package page, the API, and the Composer plugin can point people to it.
- Last commit
- 2025-01-05
- Last stable release
- 2025-01-05
- Days since last commit
- 610
- Days since last release
- 610
How this check works
A package that has not changed in a long time may simply be finished, but it may also be quietly abandoned. Recency alone cannot tell the two apart, which is why this check carries a modest weight. Combined with the other maintenance signals it is a useful early warning: a package with no recent commits and no recent releases is unlikely to receive a fix when one is needed.
Plumb reads two dates: the most recent commit on the repository's default branch, and the publication date of the most recent stable release on Packagist. Either one being recent is enough, so a stable package that ships a release without many commits, or one that commits steadily between releases, is treated fairly.
The check passes when the latest commit is 90 days old or less, or the latest stable release is 180 days old or less. The 90-day commit window follows the OpenSSF Scorecard Maintained check. It fails when both signals have been silent for more than a year. Anything in between warns with half credit. When neither date can be determined the check is unassessable.
composer.lock file in its released package.
composer.lock is absent from the released dist archive.
- Package type
- library
- Release inspected
- 8.21.0
- composer.lock in the archive
- No
How this check works
A composer.lock file records the exact dependency versions one project installed. Composer ignores a lockfile that sits inside an installed dependency, so a library shipping one does not affect anyone's install. The harm is indirect: security scanners and audit tools that walk the vendor/ directory read any lockfile they find and report the versions pinned inside it, which are not the versions the consuming project actually uses. The result is false alarms for every downstream user.
Plumb inspects the released archive, which is exactly what composer require downloads, rather than the repository. It does not matter how the file was kept out of the archive: export-ignore in .gitattributes, archive.exclude in composer.json, or never committing the file all work.
The check passes when composer.lock is absent from the released archive and fails when it is present. It is not applicable when the package declares type: project in its composer.json, because applications such as a Laravel or Symfony skeleton are supposed to commit a lockfile, and when there is no stable release to inspect. This is a low-weight hygiene signal.
- Release inspected
- 8.21.0
- Development files in the archive
-
AI assistant configuration noneCI configuration noneTest files noneDevelopment tooling none
How this check works
The archive that Composer downloads should contain what the package needs at runtime and nothing else. Test suites, CI pipelines, editor and AI assistant settings, and developer tooling configuration make every install larger and slower, give security scanners more files to raise questions about, and suggest the release process has not been tuned. Keeping them out is a small, one-time piece of configuration.
Plumb lists every file in the released archive and matches the paths against four groups of known development files: test infrastructure (such as tests/, phpunit.xml, phpstan.neon), CI configuration (such as .github/, .gitlab-ci.yml), AI assistant configuration (such as .claude/, CLAUDE.md, .cursorrules), and development tooling (such as .php-cs-fixer.php, .editorconfig, Makefile, CHANGELOG.md). Matching files are listed by group in the evidence.
The check passes when no group has any matching files. It warns with half credit when exactly one group has matches, and fails when two or more do. It is not applicable when there is no stable release to inspect. As with the lockfile check, only the released bytes matter, not how the exclusion was configured.
Ecosystem
How this check works
A package built for Laravel that cannot be installed with the current Laravel release forces its users to postpone their framework upgrade, and with it the security fixes that come with staying current. Supporting the latest Laravel line is how a package stays usable for the projects that depend on it.
Rather than reading version strings, Plumb runs a real Composer dependency resolution of the package's released dependencies together with the current Laravel line, the same calculation composer require performs. This respects replace and conflict rules and catches limits buried inside intermediate dependencies. If the current line resolves, the package is compatible. Otherwise Plumb finds the newest Laravel line that does resolve and looks at where that line sits in its support lifecycle. The solver's own explanation of the conflict is recorded in the evidence.
A package counts as Laravel-related when its require names laravel/framework or an illuminate/* component, or when its dependency graph reaches one of those through other Laravel packages. The check passes when the current line resolves. It warns with 70 percent credit when the newest working line is still in active support, and with 40 percent credit when that line only receives security fixes. It fails when the newest working line is end of life or no line works at all. It is not applicable for packages unrelated to Laravel, when the package's own dependencies do not resolve at all, or when there is no stable release with a composer.json.
>=7.0.0 supports current PHP 8.5.
- PHP constraint
- >=7.0.0
- Lifecycle data from
- phpwatch
- Supports the current version
- Yes
- Constraint declared
- Yes
- Current PHP version
- 8.5
How this check works
PHP versions have a fixed support lifetime. When a package's version constraint rules out the current PHP release, its users have to stay on an older PHP, and eventually on one that no longer receives security fixes. Keeping up with PHP is how a package avoids holding its users back.
Plumb reads the php entry under require in the released composer.json and compares it with the PHP release calendar. If the constraint allows the current stable PHP version, the package is compatible. Otherwise Plumb finds the newest PHP version the constraint does allow and looks at where that version sits in its lifecycle.
The check passes when the current PHP version is allowed, or when no constraint is declared at all (including *), since that imposes no limit. It warns with 70 percent credit when the newest allowed version still receives active support, and with 40 percent credit when it only receives security fixes. It fails when the newest allowed version is end of life, or when the constraint matches no known PHP version. It is not applicable when there is no stable release or the release has no composer.json.
How this check works
A package built for Symfony that cannot be installed with the current Symfony release forces its users to postpone their framework upgrade, and with it the security fixes that come with staying current. Supporting the latest Symfony line is how a package stays usable for the projects that depend on it.
Rather than reading version strings, Plumb runs a real Composer dependency resolution of the package's released dependencies together with the current Symfony line, pinning every framework component to that line the way a Symfony application locks them. This is the same calculation composer require performs, so it respects replace and conflict rules and catches limits buried inside intermediate dependencies. If the current line resolves, the package is compatible. Otherwise Plumb finds the newest Symfony line that does resolve and looks at where that line sits in its support lifecycle. The solver's own explanation of the conflict is recorded in the evidence.
A package counts as Symfony-related when its require names symfony/framework-bundle or a component versioned with the framework (such as symfony/http-kernel, symfony/console, symfony/yaml, symfony/validator), or when its dependency graph reaches the framework through other Symfony packages. Independently versioned packages such as symfony/polyfill-*, the contracts, symfony/ux-*, Flex, MakerBundle, and MonologBundle do not count on their own, and a package is not treated as Symfony-related just because a framework it builds on uses Symfony components internally. The check passes when the current line resolves. It warns with 70 percent credit when the newest working line is still in active support, and with 40 percent credit when that line is an LTS release receiving only security fixes. It fails when the newest working line is end of life or no line works at all. It is not applicable for packages unrelated to Symfony, when the package's own dependencies do not resolve at all, or when there is no stable release with a composer.json.
Add a Plumb badge to your README. Displaying scores on your own site instead? See the API usage guidelines for attribution requirements.
[](https://plumbphp.dev/znframework/package-services)
[](https://plumbphp.dev/znframework/package-services)
[](https://plumbphp.dev/znframework/package-services)
[](https://plumbphp.dev/znframework/package-services)
[](https://plumbphp.dev/znframework/package-services)