docs Home GitHub

perch

Semantic code and security scanner.

Perch is a semantic code and security scanner. It finds bugs, security vulnerabilities and violations of your own rules by asking a language model about each method in your repository, with the method's callers and callees included for context.

For example, running perch scan in a small Python project produces output like this:

$ perch scan
inventory.py
  ID        Line  Severity  Type    Confidence  Problem             Method
  0d0e3f8a     5  P1 (0.8)  defect         88%  wrong_return_value  reserve
  225c1645    21  P1 (0.7)  defect         93%  wrong_order         release_expired

checkout.py
  ID        Line  Severity  Type    Confidence  Problem             Method
  bdc67421    10  P1 (0.9)  defect         82%  wrong_order         place_order
  ddc5c917    21  P1 (0.9)  defect         94%  wrong_return_value  can_fulfil
  4f8bf5dc    29  P1 (0.8)  defect         68%  bad_state_change    refund

cart.py
  ID        Line  Severity  Type    Confidence  Problem             Method
  287bfb9d     7  P1 (1.1)  defect         93%  off_by_one          subtotal
  44c8d48a    21  P1 (1.0)  defect         74%  inverted_condition  is_eligible_for_free_shipping
  80d6ebbb    28  P1 (1.3)  defect         91%  unhandled_null      cheapest

storage.py
  ID        Line  Severity  Type    Confidence  Problem        Method
  5a623390    10  P1 (1.0)  defect         68%  error_ignored  load_order

✖ 9 problems in 4 files, all failing
order-service at commit b412add: 15 methods, read 15
15 requests  17k tokens in / 3k out  $0.0065

The Confidence column is the model's estimated probability that the problem is real. The Severity column estimates how badly the problem would affect callers, from P0 (most severe) to P3. perch scan exits with code 3 when it reports a problem, so you can use it to fail a CI build.

Installation#

Perch requires Node.js 22 or later and git. Install it with npm, then sign in to Perch Cloud:

npm install -g @lakeday/perch
perch login

By default, Perch sends its questions to a model through Perch Cloud, which bills your organization for usage. You can also use your own model endpoint. See Quick start for details.

Default checks#

By default, perch scan asks two questions about every method: whether it contains a functional bug, and whether it mishandles an empty value, a boundary value or an alternate branch. The same request also asks which kind of bug is most likely and how severe it would be.

Security, refactoring and documentation checks are turned off by default. To turn them on for every scan, add them to scan_types in perch.yaml. To run them for a single scan, use --filter:

perch scan --filter type=security

Custom rules#

You can also write your own rules in perch.yaml. A rule is a plain-English statement that your code should satisfy. For example:

- name: errors-reach-the-caller
  where: "**/*.py"
  each: method
  min: 65
  ensure: >-
    When this method catches an exception, it raises it again or returns a value
    the caller can tell apart from success. Logging the exception and returning
    an ordinary value breaks this rule.

In this example, Perch checks every method in every Python file and reports methods that catch an exception and hide it from the caller. Method rules like this one are checked in the same request as Perch's built-in questions, so adding them doesn't increase the number of requests.

A rule can also cite a reference document, such as your team's style guide, with context. To learn how to write rules, see Semantic linting.

The .perch directory#

Perch stores scan results in a .perch directory at the root of your repository:

PathContents
.perch/scan.jsonlThe results of the last scan. Each scan replaces this file.
.perch/closed.jsonlIssues you closed with perch close, and your reasons. Commit this file.
.perch/rules/Optional rule files, if you split your rules out of perch.yaml. Commit these files.

Commands like perch issues read from these files, so they work offline and don't make any requests. To store results in a different directory, use --out DIR.

Edit this page on GitHub