Composer
BETAPoint Composer at your firewall as a repository. Authentication, plugin controls, the artifact host token, composer audit, CI, and troubleshooting.
A Composer firewall serves a Composer repository, so a PHP project installs through it the same way it installs from Packagist. Composer 2 resolves versions and downloads packages through the firewall. Versions rules filter the metadata Composer resolves against, and download rules evaluate the package file. The operations the endpoint covers are listed in Client operations. Composer support is in beta.
Packagist is the public registry Composer uses by default, and the default upstream for a Composer firewall. It is an index rather than a package store: it holds package metadata, while each download can come from a separate artifact host. Most Packagist dist URLs use api.github.com. That split is the reason this page has an artifact host token step the other ecosystem pages do not.
The examples use <namespace-id> and <firewall-id> placeholders. Replace them with IDs, not with namespace or firewall names. The firewall's Setup tab shows these snippets with both values already filled in.
Repository endpoint
https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/composer/<firewall-id>The namespace ID and firewall ID are part of the URL. Composer appends packages.json itself, so configure the base URL above, without a suffix.
The endpoint speaks the Composer 2 repository protocol. Composer 1 is not supported, and Packagist ended its own Composer 1 support on 1 September 2025.
Authentication is HTTP basic auth with the access token as the password; the username can be anything.
A Composer firewall created without upstreams gets packagist (https://repo.packagist.org) as its default upstream; point it elsewhere on the firewall's Upstreams tab.
Configure the project
Add the firewall as a repository, then disable packagist.org:
composer config repositories.firewall composer https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/composer/<firewall-id>
composer config repo.packagist falsefirewall in the first command is a name you pick for the repository entry, not a Composer keyword. It becomes the entry's name in composer.json. Re-running the command with the same name updates that entry. Using a different name adds another entry.
Disabling packagist.org is not optional. Composer queries every configured repository, so leaving it enabled means a package the firewall filtered can still be resolved and installed straight from Packagist, with no rule involved. The commands write both settings into composer.json, so committing that file gives every clone and every CI run the same configuration:
{
"repositories": [
{ "packagist": false },
{
"name": "firewall",
"type": "composer",
"url": "https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/composer/<firewall-id>"
}
]
}Then add the token. Composer keeps credentials out of composer.json, in auth.json, keyed by host:
{
"http-basic": {
"eu-sov-1.bytesafecloud.eu": {
"username": "composer",
"password": "<your token>"
}
}
}The firewall reads the token from the password field and does not check the username. Keep auth.json out of version control; Composer's own documentation says to add it to .gitignore. composer config http-basic.eu-sov-1.bytesafecloud.eu composer <your token> writes the same file, and --global writes it in your Composer home directory instead of the project.
The artifact host token
Packagist stores metadata, not package files. Most Packagist dist URLs point to api.github.com. GitHub allows 60 unauthenticated requests an hour per egress IP and 5,000 authenticated requests. Firewall requests that use the same egress IP share that allowance, so GitHub-hosted downloads can fail after the anonymous limit is exhausted.
Set a GitHub token on the firewall's upstream. It is read against public repositories, so it needs no scopes.
In the dashboard: open the firewall's Upstreams, edit the packagist upstream, and fill in GitHub token under Artifact host token. Through the API:
curl -X PUT \
-H "Authorization: Bearer $BYTESAFE_TOKEN" \
-H "Content-Type: application/json" \
-d '{"token": "<github token>"}' \
"https://eu-sov-1.bytesafecloud.eu/v1/<namespace-id>/config/<firewall-id>/upstreams/<upstream-id>/artifact-credentials/api.github.com"This credential is separate from the upstream's own credentials by design: the token you set for the upstream is only ever sent to the upstream's own host, so a Packagist credential never travels to GitHub. Both are encrypted at rest and redacted in API responses and exports. See Artifact host credentials.
Creating the firewall and resolving metadata do not require this token. A missing or invalid token only becomes visible when the artifact host refuses a download. See Troubleshooting for the resulting error.
Verify
composer require monolog/monolog --prefer-distThe install writes composer.lock and unpacks the package into vendor/. If a rule matched during the install, inspect its firewall log entry:
bsfw logs --firewall <firewall-id>CI
Use a Service Access Token (SAT) instead of a PAT. A SAT identifies the CI system rather than a person. Create one as described in Access Tokens and store it as a CI secret.
Rather than writing auth.json in the pipeline, pass the credentials in COMPOSER_AUTH, which holds the same JSON as the file:
export COMPOSER_AUTH='{"http-basic":{"eu-sov-1.bytesafecloud.eu":{"username":"composer","password":"'"$BYTESAFE_TOKEN"'"}}}'
composer install --no-interaction --prefer-distTo correlate firewall log entries from one pipeline run, append :: and the run ID to the token. The firewall validates the token before the suffix and records the suffix as the interaction ID. Use the same value for every request in the run.
What the firewall serves
| Path | Purpose |
|---|---|
/packages.json | Repository manifest. Points Composer at the metadata and advisory endpoints below |
/p2/{vendor}/{package}.json | Package metadata for one package. Versions rules remove blocked versions before the firewall returns it |
/p2/{vendor}/{package}~dev.json | Branch metadata for the same package. Composer requests it for every package it resolves |
/dist/{vendor}/{package}/{version}/{reference}.zip | Package download. Download-phase rules run here |
/api/security-advisories/ | Answers composer audit |
The firewall changes Packagist metadata in three ways:
- It rewrites each download URL to the firewall, so the package file passes through your rules instead of being fetched directly from the artifact host.
- It removes the
sourceentry, so--prefer-sourceor a failed download cannot fall back to a direct clone that bypasses the firewall. - It drops versions with no downloadable file. With the
sourceentry removed, those versions cannot be installed. This includes metapackages such asroave/security-advisories, which contain version constraints but no package file.
How rules apply
Rules on the versions execution phase remove versions from the metadata document before Composer's resolver reads it; a filtered version cannot be picked. Rules on the download phase block the file fetch itself.
Selectors match the Packagist package name (vendor/package, lowercased, as Packagist stores it) and the version as the package published it.
Version constraints follow Composer's own rules, not npm's. ~1.2 means >=1.2 <2.0 in Composer, where the same string means >=1.2 <1.3 in npm. Write versionRange selectors the way you would write them in composer.json.
Age-based selectors (delay new versions) use the time the version became installable from the index, which is what Packagist records as published-time. For packages whose git tags were backdated, that is later than the tag date.
Install-time code execution
The install-scripts selector function matches releases whose Composer package type is composer-plugin or the legacy composer-installer. It reads the package type and plugin classes from repository metadata, not from the package file.
For Composer, the function supports versions and download rules. A versions rule removes the plugin release before Composer resolves it. A download rule blocks the package file when a client requests an already-resolved version. Matching log entries begin with Composer plugin detected and record the package type and any plugin classes declared in extra.class.
Abandoned packages
Packagist lets a maintainer mark a package abandoned, optionally naming a replacement. The deprecated selector function matches those, and the log entry names the replacement when the maintainer gave one.
composer audit
composer audit asks the repository it installed from, which is the firewall, and the firewall answers from its own advisory dataset rather than forwarding the question to Packagist. So composer audit and the vulnerabilities selector function read the same data, and audit keeps working against a firewall whose upstream is a private mirror with no advisory endpoint.
Composer 2.10 also refuses to install a package the repository reports an advisory for, unless --no-security-blocking is passed. That is Composer's own behavior, separate from your rules, and it applies whether or not the firewall has a vulnerabilities rule.
Publishing
The Composer repository protocol has no upload operation: packages are published by tagging a release in version control and having Packagist read it. So there is nothing for a firewall endpoint to accept, and the upload phase does not apply.
License data
Licenses come from the package's own Packagist metadata. There is no public license data source for Composer, so a package that declares no license resolves as unknown; see Enforce a license policy.
What a blocked install looks like
A versions rule removes the version from the metadata document, so Composer's resolver never sees it. The client reports that your constraint cannot be satisfied, naming the versions it did find.
A download rule blocks the file fetch. The firewall returns a 404 with:
monolog/monolog@2.9.1 is blocked. Check firewall log entries for request <request-id> for detailsTake the request ID to the logs to see which rule fired. See Investigate a blocked install.
Troubleshooting
The token in auth.json (or COMPOSER_AUTH) is unset, expired, or revoked. Check that the http-basic key matches the firewall host exactly, without a scheme or path.
The artifact host refused the download. The error names the host:
dist host api.github.com refused the request and no credentials are configured for itFor api.github.com, this usually means the anonymous rate limit was exhausted. Set the artifact host token. If credentials were sent, the message instead tells you to check them; the token may have expired or been revoked.
If a package that should match a block rule installs with no matching log entry, packagist.org is probably still enabled. Check composer.json for the {"packagist": false} entry. A global setting on one developer machine does not apply to other clones or CI. Run composer clear-cache before testing to rule out a warm client cache.
Most likely a versions rule filtered every version that satisfies your constraint. Check the firewall logs before assuming an upstream problem. The other case is a package with no downloadable file for any version, which the firewall does not serve; see What the firewall serves.
Composer is resolving the package from another repository entry. Check composer.json for a vcs or path repository, or for a branch alias resolved from a repository you added. The firewall removes the source entry from everything it serves, so an install through it cannot reach a VCS host on its own.