Skip to main content

semgrep-list-php-projects

Concept by Jarkko Laemsae (jarkko@semgrep.com), who originated the idea behind this tool.

Lists the Semgrep projects that contain PHP, from the terminal, using the Semgrep API v2 wherever v2 can answer, and two v1 scan endpoints where only v1 can.

semgrep login                                    # or: export SEMGREP_APP_TOKEN=...
uvx semgrep-list-php-projects > php-projects.txt
uvx semgrep-list-php-projects --format json
acme/cms
acme/legacy-portal
acme/web

The text output is one name per line, so it feeds straight into anything that takes a list of projects.

How PHP is detected

The Semgrep API has no "languages" field on a project, a branch or a scan. PHP is detected from three signals instead, run in this order. Each one narrows what the next has to cover:

Signal Source Decides Misses
scans Latest full, completed Code scan: did Semgrep parse PHP files? Yes or no, for every project with such a scan in the last 30 days Projects with no full scan in 30 days
composer Supply Chain: Composer dependencies Yes, in one fast call PHP with no composer.lock; every project when the deployment has no Supply Chain
findings Code findings in .php, .phtml, .php3–.php7 or .phps files, or from php.* rules, on each project's primary branch, in any status Yes, for projects the other two left undecided PHP projects with no Code findings

Each project in the json and csv output says which signal found it.

How scans reads a scan (checked against the live API on 2026-10-21): a scan's stats.parse_rate lists every language Semgrep parsed, with file counts. A project is PHP when php is listed with at least one file. The scan search only works per project, so this signal costs two small requests per project. If a scan ever comes back without parse_rate, the tool prints a warning naming the fields it did see, and findings decides that project instead.

Endpoints used (checked against the live API on 2026-10-21):

GET /api/v2/deployments The deployment the token belongs to
POST /api/v2/deployments/{id}/projects/list Every non-archived project
POST /api/v1/deployments/{id}/scans/search Each project's latest full Code scan (v1, see below)
GET /api/v1/deployments/{id}/scan/{scanId} That scan's per-language statistics (v1, see below)
POST /api/sca/deployments/{id}/repositories Projects with Composer dependencies
POST /api/agent/deployments/{id}/issues Code findings, per batch of project ids

Why two v1 endpoints remain. The v2 scan object carries findings counts and enabled products but no per-language statistics, and nothing else in v2 says what languages a project contains. Only the v1 scan read exposes stats.parse_rate, and scans is the one signal that can say "no PHP" with confidence. Dropping it would make a PHP project with no composer.lock and no Code findings invisible. The v1 search accepts the v2 project ids. When v2 exposes per-language statistics, these two calls are the only thing to move.

Not every customer has Supply Chain. If a signal's endpoint refuses with HTTP 403 or 404, the tool prints a warning, records it under signalsSkipped and answers from the other. Any other failure stops the run with exit 2. A list built from half a findings walk would look complete when it is not.

A project no signal finds is not reported, even if it contains PHP. If you need certainty for a project that is not on the list, check its repository.

Why the file extension is checked here and not by the API. The findings filter has a filePaths field, but it is a fuzzy text search with typo tolerance, and measured on 2026-10-21 it is silently ignored whenever a status filter is also set: a nonsense search term returned every finding in the deployment. The tool asks for findings by project id only and checks the path itself.

Install: pick one

uv (recommended; pure Python, no dependencies, Python 3.11 or newer):

uvx semgrep-list-php-projects --help          # needs https://docs.astral.sh/uv/
pipx run semgrep-list-php-projects --help     # or pipx

Docker (needs nothing but Docker; linux/amd64 and linux/arm64):

docker run --rm -e SEMGREP_APP_TOKEN ghcr.io/semgrep/semgrep-list-php-projects:0.1.0 --help

Or use the wrapper script, which also finds the token semgrep login stored and runs the container as your own user with a read-only filesystem, no capabilities and no other mounts:

curl -fsSLO https://raw.githubusercontent.com/semgrep/semgrep-list-php-projects/v0.1.0/semgrep-list-php-projects.sh
less semgrep-list-php-projects.sh && chmod +x semgrep-list-php-projects.sh
./semgrep-list-php-projects.sh > php-projects.txt

Straight from git (no release needed):

uvx --from git+https://github.com/semgrep/semgrep-list-php-projects semgrep-list-php-projects

Verify what you got. Every release is built by the release workflow in this repository:

gh attestation verify oci://ghcr.io/semgrep/semgrep-list-php-projects:0.1.0 --owner semgrep

The PyPI release carries PEP 740 attestations, shown on each file at pypi.org.

The token

The tool looks for a Semgrep API token in this order and uses the first it finds:

  1. ~/.semgrep/settings.yml, which semgrep login writes. Set SEMGREP_SETTINGS_FILE to use another path, as with the semgrep CLI.
  2. SEMGREP_APP_TOKEN in the environment.
  3. A prompt, with input hidden, when run from a terminal. Without a terminal (CI, a pipe) there is nothing to prompt, so that is an error.

The token is never accepted as a command-line argument, so it stays out of shell history and ps. It is never logged or written to disk. The token decides the deployment, so you never pass one.

The token needs the Web API scope. The token semgrep login creates has it. A CI-only token (the agent_cli scope) gets HTTP 401 here, and the error says so.

In Docker, the wrapper script mounts the settings file read-only and forwards SEMGREP_APP_TOKEN by name. Without the wrapper, pass one of:

docker run --rm -v ~/.semgrep/settings.yml:/run/semgrep/settings.yml:ro \
  -e SEMGREP_SETTINGS_FILE=/run/semgrep/settings.yml ghcr.io/semgrep/semgrep-list-php-projects:0.1.0
docker run --rm -e SEMGREP_APP_TOKEN ghcr.io/semgrep/semgrep-list-php-projects:0.1.0

Large organisations

Built for orgs with 10,000+ projects, while keeping load on the API low:

  • The findings walk is a fallback, not a sweep. It runs only for projects that scans and composer left undecided, filtered to their ids in batches of 100 and read in pages of 3,000. No query pages deep through the deployment's whole findings list. projectsWalkedForFindings in the JSON output says how many projects needed it.
  • Concurrency is capped. The two scan requests per project run at most 4 at a time (--concurrency, 1 to 8). A 10,000-project org is about 20,000 small requests. The findings walk is always sequential.
  • Transient failures are retried. Rate limits (429, honouring Retry-After), 5xx, timeouts and dropped connections are retried with backoff, up to 6 attempts per request.
  • Progress goes to stderr.

Options

Option Default
--signals scans,composer,findings all three Which signals to run
--concurrency N 4 Parallel scan lookups, 1 to 8
--format text|json|csv text json and csv add the project id, signals and counts
--base-url URL https://semgrep.dev

What leaves the API

Each Code finding is read only for its project name, file extension and rule name. From each scan, only the per-language file counts are read. Snippets, messages and file paths are never printed or stored. The output holds only project names, ids and counts.

Exit codes

0 The lookup ran, even if nothing matched or a signal's product is not enabled
2 It did not finish: bad arguments, no token, or an API failure that survived retries

Development

uv sync --extra dev
uv run pytest
uv run semgrep-list-php-projects --help

The tests never reach the network: a fixture fails any test that tries.

To build and run the image locally:

docker build -t semgrep-list-php-projects:dev .
SEMGREP_LIST_PHP_PROJECTS_IMAGE=semgrep-list-php-projects:dev ./semgrep-list-php-projects.sh --help

Releasing

  1. Bump the version in pyproject.toml, semgrep_list_php_projects/__init__.py, the Dockerfile label, the wrapper script's default image and this README, then uv lock. Open a pull request; CI runs the tests and builds the image.
  2. Tag the merged commit: git tag v0.2.0 && git push origin v0.2.0.

The release workflow refuses a tag whose version does not match every one of those files, runs CI again, then publishes the multi-arch image to GHCR with a build provenance attestation, the package to PyPI through trusted publishing (no token stored anywhere), and a GitHub release carrying the wheel and sdist.

Dependencies are updated by Dependabot with a one-week cooldown, and every GitHub Action and base image is pinned to a commit SHA or digest.

Metadata

Release files for semgrep-list-php-projects 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for semgrep-list-php-projects 0.1.0
File Size Uploaded Provenance
semgrep_list_php_projects-0.1.0.tar.gz 29.9 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for semgrep-list-php-projects 0.1.0
File Interpreter ABI Platform
semgrep_list_php_projects-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 48.5 kB

Release files / semgrep_list_php_projects-0.1.0.tar.gz

Download URL semgrep_list_php_projects-0.1.0.tar.gz
Size 29.9 kB
Tags Source
SHA-256 checksum
How to use checksums
ef6d38d3096fbffe7e32f529e3020da58cf41ddf6f5bad1aa31efeb98f077610
BLAKE2b-256 checksum
How to use checksums
76467513f7f69526448539e90a5ab917f9b7cd27513277b3d979bf85644f8c62
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Release files / semgrep_list_php_projects-0.1.0-py3-none-any.whl

Download URL semgrep_list_php_projects-0.1.0-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
53a5152ade7009659678a16a63ff63c588b6309aa232285330c96a7d3bc18cc1
BLAKE2b-256 checksum
How to use checksums
238587b83485fbe15b9cb61caa73a2cdfd162278afb9693ccda5f895d2adf6a2
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

Security for semgrep-list-php-projects 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for semgrep_list_php_projects-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 22, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page