A build that installed fine yesterday fails with 403 Forbidden from the package
manager. The message names a package and a version and nothing else. Before
anyone rotates a token or reruns the pipeline, it is worth thirty seconds to
work out which of two very different things happened.
Policy or authentication?
A registry answers 403 for two reasons that look identical to a build tool:
- Authentication. The token is missing, expired, or scoped to the wrong organization. Every request fails, not just one package, and the failure starts the moment the token changed.
- Policy. The registry understood the request, checked the version against the rules your organization configured, and refused to serve it. Only that package fails. Everything else in the same build resolves.
The quickest tell is breadth. One package failing while its neighbours install is a policy refusal. Every package failing is authentication, and the fix is the token, not the package.
Reading a policy refusal
A registry that enforces policy should say which rule refused the version. In
Dependably Packages, every refusal is written to the audit log
with the arm that refused it, so a 403 in CI has an explanation in the web
UI: the licence did not match the allow list, the version carries an advisory
above the severity ceiling, the package is on the CISA Known Exploited
Vulnerabilities Catalog, the release is younger than the minimum age, or the
package ships an install script your policy does not allow.
Each of those has a different right answer:
- Licence. Either the licence is fine and the allow list needs it, or the package needs replacing. Do not widen the list to make a build pass.
- Advisory severity or KEV listing. Move to a fixed version. If none exists yet, an exception with an expiry is the honest choice; a permanent exception is a hole.
- Minimum release age. Wait. The rule exists because a compromised publish is usually noticed within days. Pinning the previous version is the normal workaround.
- Install scripts. Check what the script does. Most packages that need one are build tools with native code; most that do not need one and have one are worth a second look.
Reserved names
The refusal nobody expects is on a package that was never meant to come from
the public registry at all. If an internal package name is not reserved, a
public package of the same name can win resolution and land in the build with
no error anywhere. A registry that lets your organization reserve its
namespaces turns that quiet substitution into a loud 403, which is the
better outcome. If the refused name is one of yours, the fix is publishing
the internal version, not allowing the public one.
What to change in CI
Two habits make the next refusal faster to handle:
- Print the registry's error body, not just the status. Package managers truncate it; the refusal reason is in there.
- Treat a policy
403as a review item, not a retry. A rerun that succeeds because a colleague widened the rule is the outcome the rule was there to prevent.
The blocked packages page in the documentation walks through the same decision for each package manager, with the exact error text each one prints.