Add Dependency Firewall enablement check API

What does this MR do and why?

This MR adds a new REST endpoint:

GET /projects/:id/dependency_firewall/enablement

It returns {"enabled": true} or {"enabled": false} — whether the Dependency Firewall is enabled for the given project.

Today, a client such as the GitLab CLI package proxy has no way to learn whether a project uses the Dependency Firewall except by attempting a package evaluation. That costs several uncached package-metadata queries across two databases, and the answer comes back as an error response that the client has to interpret as state. That pushes clients toward treating any error as a block, which would refuse every package fetch on the most common project configuration: a project with the firewall off. This endpoint answers the question once per CLI invocation instead of once per package.

Design points:

  • While the feature flag is off, the endpoint returns 404 — an experiment endpoint is not meant to exist yet — but the body still carries {"enabled": false}. A client that reads the body learns the firewall is inactive and skips it, so it does not report a configuration problem on a project that simply is not enabled yet.
  • The licence and the namespace setting answer 200 with enabled: false. They are product state, not a statement that the endpoint does not exist.
  • A 404 therefore has three shapes, told apart by which key the body uses, never by message wording: an enabled key means the flag is off; an error key means the endpoint is absent from the instance, which is the case on Community Edition; a message key means the request was refused, so the client must not treat the project as unprotected. Two tests pin the first against the third, because the client contract turns on that difference.
  • The response says enabled, not enforced. Inside this feature enforcement_type already means warn-versus-enforce for a policy, so reusing that word here would read as a question about policy mode rather than about whether the firewall is switched on. The predicate behind the answer keeps its own name, because it is accurate about what it composes.
  • The response is a single combined boolean. It does not reveal which of the feature flag, the licensed feature, or the namespace setting produced the answer.
  • Authorization is project read access. There is no explicit authorize! call because user_project resolves through find_project!, which already enforces it and turns an unreadable project into 404.
  • Authentication is required, including for public projects, so the feature's rollout across public projects is not externally observable to anonymous callers.
  • A job token can only check its own project. GitLab gates job tokens with the inbound allowlist, which would let a project that allowlists another one for build resources also expose its firewall posture to that project's pipelines. Those are different decisions and the allowlist only expresses the first, so the endpoint adds an explicit same-project check. A cross-project token is refused with 403 even when allowlisted and holding the declared policy.
  • The endpoint is marked as an experiment and carries hidden true, keeping it out of the published OpenAPI specification so nothing generates a client against a contract that can still change.
  • It has its own rate limit key with a separate budget, so firewall checks and ordinary project reads cannot starve each other, and it resolves the same project_api_limit setting rather than repeating its default, so an administrator who tunes project reads down does not leave this endpoint at the old value.

Testing: a new request spec with 20 examples covering every credential type (personal, project, and group access tokens, OAuth, CI job token), the three not-enabled conditions, the unauthenticated refusal, the unreadable-project 404, two scenarios pinning the authorization boundary (a Guest, and an authenticated non-member on a public project), and the job-token confinement. Both the same-project rate limit and the confinement check were verified by removing them and confirming a test fails — before that, removing the declared job-token policy left the whole suite green. Exactly one example is expected to pend: a shared example skips its public-access-bypass case because the boundary object here is a private project.

House style

This endpoint complies with the experiment-endpoint rules in doc/development/api_styleguide.md: it carries route_setting :lifecycle, :experiment, it is excluded from the published OpenAPI specification with hidden true, and it returns 404 while its feature flag is off. Earlier revisions of this merge request diverged on that last point and asked a reviewer to accept the divergence; that is no longer the case, and the flag also gives the endpoint a runtime off switch.

The endpoint declares no fine-grained job-token policy (skip_job_token_policies: true). An earlier revision declared read_packages, and an AppSec review identified that as a false security boundary: the policies are configured on an inbound allowlist entry, a same-project token has no such entry, and cross-project tokens are refused outright, so no setting an operator could apply would affect this endpoint. Declaring one told operators they had a control they did not have.

That left the route listed in the generated doc/ci/jobs/fine_grained_permissions.md under a section headed "CI/CD job tokens cannot access the following endpoints", which is false. Review flagged it, and checking the generator showed the heading was wrong for every row in that table, not just this one: the table is built from routes that set job_token_allowed and then split by whether they declare fine-grained permissions, so all thirty entries are job-token-accessible — GET /job and GET /job/allowed_agents among them, both callable only by a job token.

Rather than hand-edit a generated file or re-declare a permission that cannot be enforced, this merge request corrects the template. That section is now "Endpoints without fine-grained permissions", and its sentence says job tokens can reach these endpoints but allowlist permission settings do not restrict them. Nothing linked to the old anchor.

Accepted residual

Requiring authentication does not make the rollout fully unobservable. Any authenticated account satisfies project-read on a public project, so this endpoint discloses per-project enforcement state at a lower role floor than the feature's existing GraphQL fields, which require Developer. Accepted deliberately: whether a security feature is switched on is unremarkable to someone who can already read the project.

Coordination note

Three files overlap with the in-review four-part Dependency Firewall evaluation-API stack, all mechanical to resolve: the EE API mount list, the OpenAPI tag registration (identical two lines), and doc/api/dependency_firewall.md, which that stack's documentation part also creates. Whichever lands second merges the sections. This MR does not depend on any unmerged code — the availability logic it wraps is already on master.

References

https://gitlab.com/gitlab-org/gitlab/-/work_items/617465

How to set up and validate locally

  1. Enable the licensed feature and the dependency_firewall_phase1 feature flag for a group.

  2. Turn on the Dependency Firewall namespace setting for that group.

  3. Call the endpoint with a personal access token, substituting a real project ID. Before enabling the flag, this returns 404 carrying {"enabled": false}:

    curl --request GET \
      --header "PRIVATE-TOKEN: your_access_token" \
      --url "http://localhost:3000/api/v4/projects/1/dependency_firewall/enablement"

    Observe {"enabled": true}.

  4. Turn the namespace setting off and repeat the same request. Observe a 200 response with {"enabled": false} — the setting is product state, so it does not produce a 404.

MR acceptance checklist

  • Tests added: request spec covering credential types, enablement states, and authorization boundaries.
  • Documentation added: doc/api/dependency_firewall.md.
  • Changelog trailer present.
  • No database migration.
  • No UI change, so no screenshots.
  • Application Security review is worth requesting: this change adds a new authorization surface.
Edited by Arpit Gogia

Merge request reports

Loading
Loading