Skip to content

Scan a Codebase

A Bifrost scan is a policy evaluation. The CLI indexes your workspace, runs static-analysis policies against the code model, and prints one report. The zero-configuration scan is one command:

Terminal window
bifrost scan /path/to/project

It runs the same policies as bifrost --root /path/to/project --policy, and it also prints the identities of the built-in policy packs on stderr.

This page runs a scan over a small fixture, reads the report, and then gates on selected policies. For every flag, see CLI Reference. For the policy language, see Static-Analysis Policies. To run a scan as a pull-request gate, see CI Gating with GitHub Actions.

Install Bifrost by following Install Bifrost, or build the current checkout with cargo build --bin bifrost. The tutorial project is in the Bifrost repository:

Terminal window
git clone https://github.com/BrokkAi/bifrost.git
cd bifrost/docs/fixtures/scan-tutorial

It is a two-file Python tool that summarizes access-log lines. Both files contain deliberate mistakes:

  • src/settings.py deserializes operator input with pickle and evaluates a configuration expression with eval.
  • src/report.py compiles the same regular expression on every loop iteration.
Terminal window
bifrost scan .

The first run indexes the workspace, so it takes longer than later runs.

The scan first writes the activated packs to stderr:

builtin-policy-pack bifrost.code-smells@2.16.0 policies=30
builtin-policy-pack bifrost.correctness@0.1.0 policies=1
builtin-policy-pack bifrost.security@1.3.0 policies=29
builtin-policy-pack bifrost.effects@1.4.0 policies=10
builtin-policy-catalog sha256=5735bf4781e357fdfb69c6a36e490703021567a2d0b03e39121e34634e932628
bifrost scan: activated 4 built-in policy packs (71 policies shipped, 45 evaluated)

The versions and the digest depend on your Bifrost build. The per-pack counts include opt-in policies, which a scan does not run; the summary line counts only the policies the scan evaluates. The report on stdout starts like this. The output below is shortened where it shows ...: the real Analysis warnings section has 46 lines, sorted by policy ID.

3 findings | Analysis incomplete
Findings
[warning] bifrost.correctness.dynamic-evaluation: Replace dynamic evaluation with explicit parsing or dispatch
Location: src/settings.py:12:12
Certainty: definite; proof: proven; completeness: complete
[warning] bifrost.correctness.unsafe-deserialization: Replace pickle deserialization with a data-only format
Location: src/settings.py:8:16
Certainty: definite; proof: proven; completeness: complete
[note] bifrost.performance.regex-compile-in-loop: Review whether this lexically nested regex compilation repeats per iteration
Location: src/report.py:9:19
Certainty: definite; proof: proven; completeness: complete
Analysis warnings
bifrost.correctness.c-self-assignment [note; advisory] empty_selection: assertion policy `bifrost.correctness.c-self-assignment` observed no group for relational assertion `by-assignment-self-assignments` group `by-assignment`: its selection matched no row in the scanned workspace, so this run reports zero findings vacuously rather than proving the asserted condition holds
bifrost.correctness.contradictory-condition [warning; run_incomplete] code_query/branch_relation_derivation_incomplete: contradictory_condition branch relation for src/report.py is incomplete: partial_procedure
...
bifrost.correctness.contradictory-condition: inconclusive (capability_incomplete)
...
bifrost.correctness.python-absent-member [warning; run_incomplete] code_query/semantic_capability_unsupported: python absent-member analysis requires an active Python declaration surface
...
bifrost.correctness.python-absent-member: inconclusive (capability_incomplete)
...
bifrost.correctness.python-absent-member-conditional: inconclusive (capability_incomplete)
...
Dependency models
Coverage: complete
Mode: default; ecosystems: python
Details: --verbose or --format json

The scan exits with status 1, because two findings are warnings and the default --fail-on threshold is warning. The inconclusive policies are reported but do not change the status.

Read the report from the top:

  • The header line counts active findings and says whether every policy completed. Analysis incomplete means at least one policy did not.
  • Each finding names the policy ID, the policy’s advice, the file:line:column of the exact expression, and how strong the proof is.
  • Analysis warnings with advisory are honest notes about empty results. For example, a C or Go policy matched nothing in this Python-only workspace, so its empty result proves nothing. These notes do not change the exit status.
  • Analysis warnings with run_incomplete, and the inconclusive lines, name policies that could not prove there is nothing more to find. They are advisory: their findings gate like any other, and their empty results prove nothing. Pass --on-inconclusive unreliable to make any such policy give exit status 2.
  • Dependency models shows which semantic dependency packs were activated. Here the Python ecosystem was selected automatically.

Add --verbose for complete finding records, suppression provenance, dependency-pack decisions, and policy audit details. Add --format json or --format sarif for machine-readable versions of the same report.

Three default policies cannot finish on this fixture:

  • bifrost.correctness.python-absent-member and bifrost.correctness.python-absent-member-conditional report attribute accesses that no class declares. To prove that a member is absent, they need a complete declaration model of the Python standard library. The bifrost binary does not embed that model. It comes from the Python standard-library semantic pack, which activates only when the pack is installed and the project declares a CPython version, for example in .python-version or pyproject.toml. See Semantic-Model Packs.
  • bifrost.correctness.contradictory-condition reports a condition that is always true or always false. It reports a complete result for a function only when its analysis of that function has no gaps and it can decide the value of every condition. The analysis of summarize has gaps (the report says partial_procedure), so the policy ends inconclusive. A condition on a value that the analysis cannot know, such as if x is None: on a function parameter x, also ends inconclusive.

An inconclusive policy that reports nothing does not mean “no problems”. The report cannot support a claim that the code is clean for that policy. The three findings above are still real and proven.

To get a gate whose result is complete, select the policies you want to enforce. This command runs the three policies that report findings on this fixture:

Terminal window
bifrost --root . \
--policy-id bifrost.correctness.dynamic-evaluation \
--policy-id bifrost.correctness.unsafe-deserialization \
--policy-id bifrost.performance.regex-compile-in-loop
3 findings | Analysis complete
Findings
[warning] bifrost.correctness.dynamic-evaluation: Replace dynamic evaluation with explicit parsing or dispatch
Location: src/settings.py:12:12
Certainty: definite; proof: proven; completeness: complete
[warning] bifrost.correctness.unsafe-deserialization: Replace pickle deserialization with a data-only format
Location: src/settings.py:8:16
Certainty: definite; proof: proven; completeness: complete
[note] bifrost.performance.regex-compile-in-loop: Review whether this lexically nested regex compilation repeats per iteration
Location: src/report.py:9:19
Certainty: definite; proof: proven; completeness: complete
Analysis warnings
None.
Dependency models
Coverage: complete
Mode: default; ecosystems: python
Details: --verbose or --format json

Every selected policy completed, and two warning findings meet the default --fail-on warning threshold, so this run exits 1. The note alone would not fail the run.

You can also select by pack or category, for example --policy-category performance. Repeating one selector widens the selection; combining different selectors narrows it. See CLI.

src/report.py compiles its regular expression on every loop iteration. Compile it once as a module constant:

PATTERN = re.compile(r"^(?P<user>\w+) (?P<path>\S+) (?P<ms>\d+)$")
def summarize(lines):
entries = []
for line in lines:
match = PATTERN.match(line)

In src/settings.py, replace pickle.loads with a data-only format such as json.load. Replace the eval call with explicit parsing of the one comparison shape that the tool supports.

Run the same selected-policy command again:

0 findings | Analysis complete
Findings
No findings reported.
Analysis warnings
None.
Dependency models
Coverage: complete
Mode: default; ecosystems: python
Details: --verbose or --format json

The run exits 0. The findings are gone because the code changed, not because anything was suppressed or configured. bifrost scan . also exits 0 on the fixed code. It still reports the three policies described above as inconclusive, so its result is not a complete analysis.

The scan is designed for scripts. echo $? after a run gives one of three statuses:

StatusMeaning
0Every requested policy gave a verdict (complete or inconclusive), no active unsuppressed finding met the --fail-on threshold, and no suppression record is orphaned.
1At least one active unsuppressed finding met the threshold or at least one suppression record is orphaned.
2The run is unreliable: a policy was unsupported or failed, or a suppression, schema, evaluation, output, or argument error occurred. An inconclusive policy is reported but does not give status 2 unless you pass --on-inconclusive unreliable. Never read status 2 as clean.

The default threshold is --fail-on warning. In this tutorial, bifrost scan . and the selected-policy command both exit 1 while the two warnings are present and 0 once they are fixed. Only the selected-policy command reports a complete analysis.

  • Select built-in policies with --policy-pack, --policy-category, and --policy-id, or run your own .rqlp files with --policy-file. See CLI.
  • Gate pull requests on only the findings they introduce with --diff-base. See CLI.
  • Emit --format sarif and upload it to GitHub code scanning. See CI Gating with GitHub Actions.
  • Accept a reviewed finding with a suppressions file instead of deleting the code. See Static-Analysis Policies.
  • Write your own policy. See Build a Rule.