Skip to content

Commit df9202f

Browse files
authored
Merge pull request #46 from escoffier-labs/feat/security-finding-lifecycle
feat: add security finding lifecycle
2 parents 79235c8 + aa16fb4 commit df9202f

10 files changed

Lines changed: 477 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5858
- `brigade security scan --import-findings` to route security findings into the local work import inbox for review.
5959
- `brigade security init` to write gitignored local defaults to `.brigade/security.toml`.
6060
- `brigade security fix` to create the local security artifact directory and refresh the managed `.gitignore` block.
61+
- `brigade security review`, `brigade security suppress`, and `brigade security unsuppress` for a local finding review lifecycle with required suppression reasons.
6162
- Security policy presets (`personal`, `public-repo`, `strict`), template scanning controls, stable finding fingerprints, and fingerprint suppressions.
6263
- `brigade security scan --output-dir <dir>` to write redacted `security-report.json` and `security-report.md` evidence bundles.
6364
- `brigade doctor` and `brigade work doctor` now report security config health, latest security evidence bundle status, and local security artifact ignore coverage.
65+
- `brigade doctor` and `brigade work doctor` now warn on stale security suppressions and suppressions missing reasons.
6466
- Security scan secret evidence is redacted before reports or work imports are written.
6567
- `ROADMAP.md` covering the daily-driver path, scanner-ready inbox, chat-surface scanners, memory-card decay refresh, and portable operator setup.
6668
- `brigade work note` to append timestamped checkpoints to the active work session without ending it.

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,9 @@ brigade security fix
207207
brigade security scan --target .
208208
brigade security scan --target . --policy public-repo
209209
brigade security scan --target . --output-dir .brigade/security/latest
210+
brigade security review
211+
brigade security suppress <fingerprint> --reason "reviewed false positive"
212+
brigade security unsuppress <fingerprint>
210213
brigade security scan --target . --import-findings
211214
```
212215

@@ -291,7 +294,7 @@ brigade add guard # content-guard
291294
brigade add tokens # tokenjuice
292295
```
293296

294-
`security` is a built-in station with no external managed tool yet. Run `brigade security scan --target .` for a read-only agent workspace security report, add `--output-dir .brigade/security/latest` to write redacted `security-report.json` and `security-report.md` artifacts, or add `--import-findings` to turn findings into local `brigade work import` review items. The scanner covers secrets, permissions, hooks, supply-chain patterns, prompt-injection patterns, and MCP configs including remote transports, auto-approval, unpinned `npx`, shell metacharacters, secret-looking env values, sensitive and broad file args, high-risk local commands, large server sets, and missing timeouts. `brigade doctor` and `brigade work doctor` report security config health, latest evidence bundle status, and whether local security artifacts are ignored. `brigade security fix` applies the narrow safe hygiene fix for that local state: it creates `.brigade/security/` and refreshes the managed `.gitignore` block. Secret evidence is redacted before reports, artifacts, or imports are written. Use `brigade security init` to write gitignored local defaults to `.brigade/security.toml`; it supports policy presets (`personal`, `public-repo`, `strict`), `fail_on`, template scanning, and fingerprint suppressions for reviewed findings.
297+
`security` is a built-in station with no external managed tool yet. Run `brigade security scan --target .` for a read-only agent workspace security report, add `--output-dir .brigade/security/latest` to write redacted `security-report.json` and `security-report.md` artifacts, or add `--import-findings` to turn findings into local `brigade work import` review items. Run `brigade security review` to inspect the latest evidence bundle, `brigade security suppress <fingerprint> --reason "..."` to suppress reviewed noise with a required reason, and `brigade security unsuppress <fingerprint>` to remove stale suppressions. The scanner covers secrets, permissions, hooks, supply-chain patterns, prompt-injection patterns, and MCP configs including remote transports, auto-approval, unpinned `npx`, shell metacharacters, secret-looking env values, sensitive and broad file args, high-risk local commands, large server sets, and missing timeouts. `brigade doctor` and `brigade work doctor` report security config health, stale suppressions, missing suppression reasons, latest evidence bundle status, and whether local security artifacts are ignored. `brigade security fix` applies the narrow safe hygiene fix for that local state: it creates `.brigade/security/` and refreshes the managed `.gitignore` block. Secret evidence is redacted before reports, artifacts, or imports are written. Use `brigade security init` to write gitignored local defaults to `.brigade/security.toml`; it supports policy presets (`personal`, `public-repo`, `strict`), `fail_on`, template scanning, and fingerprint suppressions for reviewed findings.
295298

296299
The current managed tools:
297300

ROADMAP.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,16 @@ Brigade-specific additions:
6565
- Produce Memory Handoffs for durable security findings while keeping raw secret evidence redacted.
6666
- Add policy packs for personal dogfooding, public-repo release checks, CI gates, and strict enterprise workspaces. Status: started with `personal`, `public-repo`, and `strict`.
6767
- Include dependency and package-manager hardening checks for agent plugin ecosystems, MCP packages, skills, and local tool wrappers.
68-
- Track false-positive taxonomy, runtime-confidence rules, suppressions, and regression fixtures as first-class project artifacts.
68+
- Track false-positive taxonomy, runtime-confidence rules, suppressions, and regression fixtures as first-class project artifacts. Status: started with `brigade security review`, reasoned suppressions, unsuppress, and stale-suppression doctor warnings.
69+
70+
## Later Phase: Issue And TDD Work Loop
71+
72+
Goal: make Brigade support a narrow issue lifecycle for daily work: pick one task, define acceptance, test first when practical, implement, review, refactor, and close.
73+
74+
- Add task templates for vertical-slice work, bugfix work, and RED/GREEN/REFACTOR loops.
75+
- Let `brigade work run` consume structured acceptance criteria from the local task ledger or a GitHub issue mirror.
76+
- Keep repo-shareable workflow rules separate from gitignored personal/global preferences.
77+
- Add doctor checks for missing acceptance criteria or stale active issue context.
6978

7079
First build slice:
7180

src/brigade/cli.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,17 @@ def _build_parser() -> argparse.ArgumentParser:
394394
p_security_fix = security_sub.add_parser("fix", help="Apply safe local security hygiene fixes.")
395395
p_security_fix.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update.")
396396
p_security_fix.add_argument("--dry-run", action="store_true", help="Show changes without writing files.")
397+
p_security_review = security_sub.add_parser("review", help="Review the latest local security evidence bundle.")
398+
p_security_review.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to review.")
399+
p_security_review.add_argument("--output-dir", type=Path, default=None, help="Security evidence bundle directory.")
400+
p_security_review.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
401+
p_security_suppress = security_sub.add_parser("suppress", help="Suppress a reviewed security finding fingerprint.")
402+
p_security_suppress.add_argument("fingerprint", help="Finding fingerprint to suppress.")
403+
p_security_suppress.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update.")
404+
p_security_suppress.add_argument("--reason", required=True, help="Required suppression reason.")
405+
p_security_unsuppress = security_sub.add_parser("unsuppress", help="Remove a security finding suppression.")
406+
p_security_unsuppress.add_argument("fingerprint", help="Finding fingerprint to unsuppress.")
407+
p_security_unsuppress.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to update.")
397408
p_security_scan = security_sub.add_parser("scan", help="Run a read-only agent workspace security scan.")
398409
p_security_scan.add_argument("--target", "-t", type=Path, default=Path("."), help="Repo or workspace to scan.")
399410
p_security_scan.add_argument("--json", action="store_true", help="Print machine-readable JSON.")
@@ -796,6 +807,12 @@ def main(argv=None) -> int:
796807
return security_cmd.init(target=args.target, force=args.force)
797808
if args.security_command == "fix":
798809
return security_cmd.fix(target=args.target, dry_run=args.dry_run)
810+
if args.security_command == "review":
811+
return security_cmd.review(target=args.target, output_dir=args.output_dir, json_output=args.json)
812+
if args.security_command == "suppress":
813+
return security_cmd.suppress(target=args.target, fingerprint=args.fingerprint, reason=args.reason)
814+
if args.security_command == "unsuppress":
815+
return security_cmd.unsuppress(target=args.target, fingerprint=args.fingerprint)
799816
if args.security_command == "scan":
800817
return security_cmd.scan(
801818
target=args.target,

src/brigade/doctor.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,16 +87,36 @@ def security_station_checks(ctx: DoctorContext) -> List[CheckResult]:
8787

8888
results: List[CheckResult] = [(OK, "security: built-in scanner", "available")]
8989
config = security_cmd.config_path(ctx.target)
90+
config_valid = True
9091
if config.is_file():
9192
try:
9293
loaded = security_cmd.load_config(ctx.target)
9394
except ValueError as exc:
95+
config_valid = False
9496
results.append((FAIL, "security: config", f"invalid {config}: {exc}"))
9597
else:
9698
results.append((OK, "security: config", f"{config} (policy={loaded.policy if loaded else 'personal'})"))
9799
else:
98100
results.append((WARN, "security: config", f"missing at {config}; run `brigade security init --target .`"))
99101

102+
if config_valid:
103+
try:
104+
suppression_health = security_cmd.suppression_health(ctx.target)
105+
except ValueError as exc:
106+
results.append((FAIL, "security: suppressions", f"invalid: {exc}"))
107+
else:
108+
suppression_count = suppression_health["suppression_count"]
109+
stale = suppression_health["stale"]
110+
missing_reasons = suppression_health["missing_reasons"]
111+
if stale:
112+
preview = ", ".join(stale[:5])
113+
results.append((WARN, "security: stale suppressions", f"{len(stale)} no longer match current findings: {preview}"))
114+
if missing_reasons:
115+
preview = ", ".join(missing_reasons[:5])
116+
results.append((WARN, "security: suppression reasons", f"{len(missing_reasons)} missing reason: {preview}"))
117+
if not stale and not missing_reasons:
118+
results.append((OK, "security: suppressions", f"{suppression_count} configured"))
119+
100120
artifacts_dir = security_cmd.default_artifacts_dir(ctx.target)
101121
bundle = security_cmd.inspect_evidence_bundle(artifacts_dir)
102122
if bundle.get("ready"):

0 commit comments

Comments
 (0)