mirror of
https://github.com/trailofbits/skills.git
synced 2026-09-14 14:28:48 +08:00
82e8e0ad77
* insecure-defaults: convert the skill to a dynamic workflow Rewrites the plugin as a four-phase workflow: recon profiles the target, parallel sweeps collect candidates, a refuting pass adjudicates them, and a report assigns severity with coverage accounting. The skill is removed and `/insecure-defaults:audit [path]` is the only entry point, since the workflow needs the command to locate its detection corpora. It also adds offline tests: scenarios over the workflow's control flow, a mutation self-test that proves they bite, and a check that every documented example is matched by a seed pattern. This does not replace running the command end-to-end against a real codebase. Bumps to 2.0.0. * insecure-defaults: run the node suites in CI The harness and seed-coverage checks ran only when someone remembered to. CI's shell-suite discovery matches plugins/*/tests/run_*.sh, so wrap the three node invocations in run_seeds.sh and both the lint workflow and `make shell-suites` pick them up with no changes to either. Without this, adding a row to CATEGORIES without its references/<id>.json passes CI and then aborts every real run with corpus-unreadable. No setup-node step: ubuntu-latest ships Node, and the scripts are plain CommonJS with no dependencies. The command -v guard makes a missing interpreter a loud failure rather than a suite that quietly does not run. * insecure-defaults: abort when the verify phase adjudicates nothing If every verify batch died, confirmed was empty and the run returned no-findings-confirmed, which commands/audit.md considers a completed audit. Guard on unadjudicated.length === candidates.length and return verify-failed, carrying coverage so the caller sees what went unjudged. Adds a scenario for both failure shapes (all agents dead, all verdict lists empty) and two mutations covering the guard firing and over-firing. * insecure-defaults: report per-category scan counts The zero-scanned guard is on the sum, so five failed searches beside one that worked cleared it and categories_run listed all six. Add files_scanned_by_category and unsearched_categories to coverage, keyed off CATEGORIES so a dead sweep counts as 0, and have the report name them under a Not searched heading. Adds a scenario covering a searching sweep, a zero-file one and a dead one, plus three mutations. * insecure-defaults: count sweep failures against the category list The corpus-unreadable note and the seed-only log divided by the sweeps that returned, so with sweeps dead the ratio read 1/1 rather than 1/6. * insecure-defaults: drop the assertion-count floor from the harness * insecure-defaults: guard a dead report agent agent() returns null on terminal failure, so a report agent that died returned status "findings" with no report and the caller printed nothing while the findings sat in the structured return. Return report-failed with the findings and coverage, and have the command render them. * insecure-defaults: stop labelling a genuine clean run a failure Step 3 accepted findings/no-findings-confirmed and called every other status an incomplete audit, so no-candidates, the deliberate honest-negative status, told the user the run failed. It is now a per-status table. * insecure-defaults: anchor the noisiest seeds (DES|RC4|...) matched NODES and MODES, 0o?(666|777|...) matched any digits, and getMessage() matched all Java exception handling. On the Python stdlib the first two drop from 147 and 228 matching lines to 0 and 84. random. and getMessage() can't be fixed by anchoring, so they now require context: a security-material identifier near the RNG call, and concatenation into a string literal for getMessage(). seed-coverage.js confirms all 18 documented VULNERABLE examples still match. * insecure-defaults: indent the seed wrapper the way shfmt wants --------- Co-authored-by: kz-tob <kara.zaffarano@trailofbits.com>
77 lines
2.2 KiB
Markdown
77 lines
2.2 KiB
Markdown
# Default Credentials
|
|
|
|
**Report when:** A credential literal that a running deployment can actually authenticate with, including seeded accounts created on first boot.
|
|
|
|
**Skip when:** Accounts created disabled or with a forced-reset flag. Credentials in docs, READMEs, and fixture files.
|
|
|
|
A credential in prose is a credential nobody can use. A credential in `bootstrap_admin()` is a login.
|
|
|
|
## VULNERABLE - Report These
|
|
|
|
**Hardcoded admin account**
|
|
```python
|
|
# File: src/models/user.py
|
|
def bootstrap_admin():
|
|
"""Create default admin account if none exists"""
|
|
if not User.query.filter_by(role='admin').first():
|
|
admin = User(
|
|
username='admin',
|
|
password=hash_password('admin123'),
|
|
role='admin'
|
|
)
|
|
db.session.add(admin)
|
|
db.session.commit()
|
|
```
|
|
**Why vulnerable:** Default admin account created on first run with known credentials.
|
|
|
|
**API key in code**
|
|
```javascript
|
|
// File: src/integrations/payment.js
|
|
const STRIPE_API_KEY = process.env.STRIPE_KEY || 'sk_tes...';
|
|
|
|
const stripe = require('stripe')(STRIPE_API_KEY);
|
|
```
|
|
**Why vulnerable:** Uses test API key if env var missing. Might reach production.
|
|
|
|
**Database connection string**
|
|
```java
|
|
// File: DatabaseConfig.java
|
|
private static final String DB_URL = System.getenv().getOrDefault(
|
|
"DATABASE_URL",
|
|
"postgresql://admin:password@localhost:5432/prod"
|
|
);
|
|
```
|
|
**Why vulnerable:** Hardcoded database credentials as fallback.
|
|
|
|
## SECURE - Skip These
|
|
|
|
**Disabled default account**
|
|
```python
|
|
# File: src/models/user.py
|
|
def bootstrap_admin():
|
|
"""Admin account MUST be configured via environment"""
|
|
username = os.environ['ADMIN_USERNAME']
|
|
password = os.environ['ADMIN_PASSWORD']
|
|
|
|
if not User.query.filter_by(username=username).first():
|
|
admin = User(username=username, password=hash_password(password), role='admin')
|
|
db.session.add(admin)
|
|
```
|
|
|
|
**Example/documentation credentials**: a credential appearing inside prose in `README.md`:
|
|
```markdown
|
|
## Setup
|
|
|
|
Configure your API key:
|
|
|
|
export STRIPE_KEY='sk_tes...' # Example only
|
|
```
|
|
|
|
**Test fixture credentials**
|
|
```python
|
|
# File: tests/conftest.py
|
|
@pytest.fixture
|
|
def test_user():
|
|
return User(username='test_user', password='test_pass') # OK - test scope
|
|
```
|