Tuning BrassCoders with .brassignore: Cutting Raw Findings Down to Signal

A fresh brasscoders scan on a medium Django project can produce well over a thousand findings. .brassignore cuts that down to signal findings without touching the Paid plan. Here's every pattern worth adding.

BrassCoders Team · · 7 min read
configurationnoise-reductionbrassignore

A fresh brasscoders scan on a medium Django project can produce well over a thousand findings. Most of them are correct matches against the patterns they’re built to detect. And most of them are useless for your team’s daily triage.

The cause is structural, not a scanner failure. Your test fixtures contain intentionally insecure credential strings. Your Django migration files include raw SQL that’s there by design. Your vendor directory holds third-party code you don’t own and can’t fix. Bandit, detect-secrets, Semgrep — they flag all of it, as they should. The noise is a function of scope.

A .brassignore file at the project root fixes that in five minutes.

After excluding tests, fixtures, migrations, and vendor code, a typical Django project’s finding count drops substantially — on top of the OSS core’s own automatic heuristic reduction. In one published case study, heuristic reduction alone took a scan from 2,470 raw findings down to 217, a count a developer can read. No Paid plan required — .brassignore is a core feature of the free OSS CLI. The Paid plan’s AI enrichment goes further still: in the same case study, 217 became 22 after enrichment. That’s a separate decision. Start with scope.

Where the Noise Comes From

BrassCoders runs 12 scanners — Bandit, Pylint, Pyre/Pysa, Semgrep, ast-grep, detect-secrets, and six custom detectors — and emits the union of their findings. Without scoping, every Python file in the project is a candidate, including test fixtures, generated migrations, vendored libraries, and build artifacts.

Test fixtures are the largest noise source. A fixture file might contain password = "hunter2" to test password validation logic, or api_key = "sk_test_abc123" to exercise an API client. detect-secrets flags these correctly: entropy is high, the pattern matches a known secret format. They are false positives in the production-security context. Your fixture credentials don’t ship; they’re not a vulnerability. They’re noise.

Generated migration files present a different problem. Django’s makemigrations command writes raw SQL patterns into migration scripts. RunSQL calls, RawSQL expressions, and migrations.RunPython with manual DB operations all trigger Bandit’s SQL injection rules — B608, specifically. The scanner is right that the pattern exists. The context (autogenerated, reviewed by the framework, not user-controlled input) makes it irrelevant to your security posture.

Vendor directories are straightforward. You don’t control vendor/, node_modules/, or .venv/. You can’t fix bugs there. Auditing them is your package manager’s job, not your SAST pipeline’s. Scanning them adds hundreds of findings that require zero action from your team.

Generated build artifacts round out the picture. The dist/ and build/ directories contain minified or compiled versions of code already scanned at the source. Scanning them is redundant and produces duplicate findings with mangled line numbers that point nowhere useful.

The .brassignore File Format

BrassCoders reads a .brassignore file from the project root and applies the patterns before any scanner runs. Files and directories matching the patterns are skipped entirely — they never enter the pipeline.

The syntax matches .gitignore: directory prefixes, glob patterns, ** for recursive matching, and ! for negation. If you know .gitignore, you already know .brassignore.

Place the file at the project root, next to .gitignore:

# Test fixtures and credential placeholders
tests/fixtures/
tests/data/
**/*_fixture.*
**/*_test_data.*

# Generated code
**/migrations/
**/generated/
dist/
build/

# Vendor / third-party
vendor/
node_modules/
.venv/

# Documentation examples
docs/examples/

A few things worth noting about this starter set. The **/*_fixture.* pattern catches fixture files nested anywhere in the tree, not just at the top of tests/. The **/migrations/ pattern covers apps that live in subdirectories — a common Django project layout where multiple apps each have their own migrations/ folder. The .venv/ exclusion matters on projects where the virtualenv lives inside the repo root, which happens more than it should.

Patterns for Django Projects

BrassCoders on an unconfigured Django project can produce raw finding counts in the thousands, most of them concentrated in migrations, fixtures, and the framework’s own static-file handling — the published Django benchmark at coppersun.dev/benchmarks shows 1,608 raw findings on a reference checkout, though your own project’s count depends on its size and how much test and migration code it carries. A focused .brassignore brings that down substantially.

The Django-specific additions on top of the starter set:

# Django framework noise
**/migrations/
staticfiles/
media/
.django_cache/

# Test infrastructure
tests/fixtures/
tests/testdata/
conftest.py
**/test_settings.py

# Fixture formats
**/*.json.fixture
**/*_data_fixture.py

# Dev database snapshots
*.sqlite3
db.sqlite3

A full recommended .brassignore for a Django team:

# Test fixtures and data
tests/fixtures/
tests/testdata/
tests/data/
**/*_fixture.*
**/*_test_data.*
conftest.py

# Django generated files
**/migrations/
staticfiles/
media/

# Vendor and environment
.venv/
vendor/

# Build artifacts
dist/
build/
*.egg-info/
__pycache__/
.pytest_cache/

# Dev-only files
*.sqlite3
**/test_settings.py
docs/examples/

Paste that in, run brasscoders scan, and compare the output count. The size of the drop depends on how much of your unfiltered count was test fixtures, migrations, and vendor code — on most Django projects, that’s a large share of it.

Patterns for FastAPI / Generic Python Projects

FastAPI and pure-Python projects follow the same logic, with a few different paths. The .venv/ and __pycache__/ noise is universal. FastAPI-specific projects often have auto-generated OpenAPI spec files and coverage reports that add findings without adding signal.

# Test noise
tests/fixtures/
tests/data/
**/*_fixture.*
conftest.py

# Python cache and coverage
__pycache__/
.pytest_cache/
htmlcov/
.coverage

# Package build artifacts
dist/
build/
*.egg-info/

# Environment
.venv/
node_modules/

# Auto-generated specs
openapi.json
openapi.yaml
**/generated/

# Documentation examples
docs/examples/

For projects using pydantic data factories or factory_boy test factories, add the factory modules:

# Test factories
**/factories.py
**/test_factories/

Factory modules define fake data models, including fields like email = Faker('email') and api_token = LazyAttribute(...). The custom BrassCoders privacy and secret detectors flag these patterns. Excluding factory modules removes a class of false positives that otherwise cluster in the findings list.

What the Paid Plan Adds on Top

BrassCoders Paid — $12 per developer per month — runs an embedding-based semantic deduplication pass on top of the path-scoped scan, ranked by relevance to the project. In one published case study, that pass took a post-heuristic-reduction count of 217 findings down to 22 — real ratios vary by codebase size.

The two layers do different things. .brassignore is path-level exclusion: it decides which files enter the scanner pipeline. The Paid plan’s AI enrichment is semantic: it looks at the content of the findings themselves, collapses findings that name the same root cause across different files, and reranks the survivors against a project signature derived from your README and manifest. A B608 SQL injection finding flagged in three different files might collapse to one, ranked by how close the affected files are to the entry points your project emphasizes.

Neither layer replaces the other. A Paid plan scan without .brassignore on a Django project still processes every raw finding before enrichment, which costs more enrichment tokens and takes longer. A .brassignore-filtered OSS scan without the Paid plan gives you the heuristically-reduced count sorted by severity — useful, but not ranked by what matters to your specific codebase.

The practical path: configure .brassignore first, on the free OSS core. Get the raw count down as far as scope alone will take it. Then decide whether the Paid plan’s further ranking — in one published case study, a post-heuristic count of 217 down to 22 — is worth $12/month for your team’s volume. For teams shipping AI-generated code daily, the math tends to be positive.

Getting Started

Install BrassCoders and run your first scan with configuration:

pip install brasscoders

Create .brassignore at the project root using one of the templates above, then run:

brasscoders scan /path/to/project

The output YAML lands in /path/to/project/.brass/ai_instructions.yaml. Compare the finding count from an unfiltered scan to the filtered one. If the count is still above 400 after exclusions, look at which finding categories dominate — there’s usually another exclusion pattern worth adding.

Start with the project root: coppersun.dev/install/

Frequently Asked Questions

What syntax does .brassignore use?

BrassCoders uses gitignore-style glob syntax in .brassignore — directory prefixes, wildcard patterns like **/migrations/, and ! negation to re-include excluded paths. The file lives at the project root, next to .gitignore.

Will .brassignore affect the Paid plan's AI enrichment?

Yes. Excluding paths with .brassignore reduces the raw finding count before the Paid plan's embedding-based enrichment runs. Fewer inputs means faster enrichment and a tighter output list. The Paid plan then does its own semantic deduplication on top of the already-filtered set.

Does excluding test directories hide real bugs?

Test fixtures contain intentionally insecure strings — placeholder credentials, known-bad SQL patterns, example payloads — placed there on purpose to make the tests pass. BrassCoders's scanners flag these strings correctly, but they're false positives in the context of production security. Excluding the fixture directories removes noise, not signal. Real production-path findings stay in scope.

How is .brassignore different from the Paid plan's deduplication?

.brassignore does path-level exclusion before scanning starts: files matching the patterns never enter the scanner pipeline. The Paid plan's semantic deduplication runs after scanning, collapsing findings that name the same root cause across different files. The two layers are complementary — .brassignore removes structural noise, the Paid plan removes semantic duplicates.

Can I use .brassignore on the OSS core without a Paid plan?

Yes. .brassignore works on the free OSS core with no subscription required. Configure it once, and every brasscoders scan respects the exclusions. The Paid plan is a separate, additive layer.