Tuning BrassCoders with .brassignore: From 1500 Findings to 300

A fresh brasscoders scan on a medium Django project produces 1500+ findings. .brassignore cuts that to ~300 signal findings without touching the Paid plan. Here's every pattern worth adding.

Copper Sun Brass Team · · 7 min read
configurationnoise-reductionbrassignore

A fresh brasscoders scan on a medium Django project produces 1,500+ 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 drops to around 300 findings. That’s a list a developer can read. No Paid plan required — .brassignore is a core feature of the free OSS CLI. The Paid plan’s AI enrichment takes that 300 further down to 50-80 prioritized findings, but 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 a Django project without configuration typically sees 1,200-1,800 raw findings, most of them concentrated in migrations, fixtures, and the framework’s own static-file handling. A focused .brassignore brings that into range.

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. On a medium Django project the drop from unfiltered to filtered is usually 1,200+ to 250-350.

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. Starting from ~300 filtered findings, the Paid plan typically produces 50-80 prioritized findings ranked by relevance to the project.

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 1,500 raw findings before enrichment, which costs more enrichment tokens and takes longer. A .brassignore-filtered OSS scan without the Paid plan gives you ~300 findings sorted by severity — useful, but not ranked by what matters to your specific codebase.

The practical path: configure .brassignore first, on the free tier. Get the raw count into the 200-400 range. Then decide whether the Paid plan’s ranked 50-80 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.