DEV Community

CopperSunDev
CopperSunDev

Posted on • Originally published at coppersun.dev

Tuning BrassCoders with .brassignore: From 1500 Findings to 300

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/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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/
Enter fullscreen mode Exit fullscreen mode

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

# Test factories
**/factories.py
**/test_factories/
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

brasscoders scan /path/to/project
Enter fullscreen mode Exit fullscreen mode

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/

Top comments (0)