feat: add Dart/Flutter/Melos support to security profiles (#583)

Add proper detection and command allowlisting for Dart/Flutter projects:

- Add `pub` and `melos` to package manager commands
- Add `flutter` to Dart language commands for SDK detection
- Add `fvm` (Flutter Version Manager) to version managers
- Detect `pubspec.yaml`/`pubspec.lock` for pub package manager
- Detect `melos.yaml` for Melos monorepo support
- Detect `.fvm`/`.fvmrc`/`fvm_config.json` for FVM
- Add 13 comprehensive tests for Dart/Flutter/Melos/FVM detection

This fixes the issue where `dart` and `flutter` commands were being
rejected with "not authorized in this project" errors.
This commit is contained in:
Mitsu
2026-01-02 18:33:13 +01:00
committed by GitHub
parent 5efc2c56f7
commit 4a833048d1
5 changed files with 216 additions and 13 deletions
@@ -173,12 +173,16 @@ LANGUAGE_COMMANDS: dict[str, set[str]] = {
"zig",
},
"dart": {
# Core Dart CLI (modern unified tool)
"dart",
"pub",
# Flutter CLI (included in Dart language for SDK detection)
"flutter",
# Legacy commands (deprecated but may exist in older projects)
"dart2js",
"dartanalyzer",
"dartdoc",
"dartfmt",
"pub",
},
}
@@ -33,6 +33,9 @@ PACKAGE_MANAGER_COMMANDS: dict[str, set[str]] = {
"brew": {"brew"},
"apt": {"apt", "apt-get", "dpkg"},
"nix": {"nix", "nix-shell", "nix-build", "nix-env"},
# Dart/Flutter package managers
"pub": {"pub", "dart"},
"melos": {"melos", "dart", "flutter"},
}
@@ -23,6 +23,8 @@ VERSION_MANAGER_COMMANDS: dict[str, set[str]] = {
"rustup": {"rustup"},
"sdkman": {"sdk"},
"jabba": {"jabba"},
# Dart/Flutter version managers
"fvm": {"fvm", "flutter"},
}
+9
View File
@@ -164,6 +164,12 @@ class StackDetector:
if self.parser.file_exists("build.gradle", "build.gradle.kts"):
self.stack.package_managers.append("gradle")
# Dart/Flutter package managers
if self.parser.file_exists("pubspec.yaml", "pubspec.lock"):
self.stack.package_managers.append("pub")
if self.parser.file_exists("melos.yaml"):
self.stack.package_managers.append("melos")
def detect_databases(self) -> None:
"""Detect databases from config files and dependencies."""
# Check for database config files
@@ -358,3 +364,6 @@ class StackDetector:
self.stack.version_managers.append("rbenv")
if self.parser.file_exists("rust-toolchain.toml", "rust-toolchain"):
self.stack.version_managers.append("rustup")
# Flutter Version Manager
if self.parser.file_exists(".fvm", ".fvmrc", "fvm_config.json"):
self.stack.version_managers.append("fvm")
+197 -12
View File
@@ -13,22 +13,22 @@ Tests the project_analyzer.py module functionality including:
"""
import json
import pytest
from pathlib import Path
import pytest
from project_analyzer import (
BASE_COMMANDS,
DATABASE_COMMANDS,
FRAMEWORK_COMMANDS,
INFRASTRUCTURE_COMMANDS,
LANGUAGE_COMMANDS,
CustomScripts,
ProjectAnalyzer,
SecurityProfile,
TechnologyStack,
CustomScripts,
get_or_create_profile,
is_command_allowed,
needs_validation,
BASE_COMMANDS,
LANGUAGE_COMMANDS,
FRAMEWORK_COMMANDS,
DATABASE_COMMANDS,
INFRASTRUCTURE_COMMANDS,
)
@@ -489,6 +489,7 @@ class TestSecurityProfileGeneration:
# Force re-analysis
import time
time.sleep(0.1) # Ensure different timestamp
profile2 = get_or_create_profile(python_project, force_reanalyze=True)
@@ -582,11 +583,23 @@ class TestSecurityProfileSerialization:
"stack_commands": ["python"],
"script_commands": [],
"custom_commands": [],
"detected_stack": {"languages": ["python"], "package_managers": [], "frameworks": [],
"databases": [], "infrastructure": [], "cloud_providers": [],
"code_quality_tools": [], "version_managers": []},
"custom_scripts": {"npm_scripts": [], "make_targets": [], "poetry_scripts": [],
"cargo_aliases": [], "shell_scripts": []},
"detected_stack": {
"languages": ["python"],
"package_managers": [],
"frameworks": [],
"databases": [],
"infrastructure": [],
"cloud_providers": [],
"code_quality_tools": [],
"version_managers": [],
},
"custom_scripts": {
"npm_scripts": [],
"make_targets": [],
"poetry_scripts": [],
"cargo_aliases": [],
"shell_scripts": [],
},
"project_dir": "/test",
"created_at": "2024-01-01",
"project_hash": "abc123",
@@ -617,3 +630,175 @@ class TestSecurityProfileSerialization:
assert "ls" in loaded.base_commands
assert "python" in loaded.stack_commands
assert loaded.project_hash == "test123"
class TestDartFlutterDetection:
"""Tests for Dart/Flutter language and framework detection."""
def test_detects_dart_language(self, temp_dir: Path):
"""Detects Dart from pubspec.yaml."""
pubspec = """name: my_app
version: 1.0.0
environment:
sdk: ">=3.0.0 <4.0.0"
"""
(temp_dir / "pubspec.yaml").write_text(pubspec)
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_languages()
assert "dart" in analyzer.profile.detected_stack.languages
def test_detects_dart_from_files(self, temp_dir: Path):
"""Detects Dart from .dart files."""
(temp_dir / "lib").mkdir()
(temp_dir / "lib" / "main.dart").write_text("void main() {}")
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_languages()
assert "dart" in analyzer.profile.detected_stack.languages
def test_detects_flutter_framework(self, temp_dir: Path):
"""Detects Flutter framework from pubspec.yaml."""
pubspec = """name: my_flutter_app
version: 1.0.0
environment:
sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
"""
(temp_dir / "pubspec.yaml").write_text(pubspec)
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_frameworks()
assert "flutter" in analyzer.profile.detected_stack.frameworks
def test_detects_pub_package_manager(self, temp_dir: Path):
"""Detects pub package manager from pubspec.yaml."""
pubspec = """name: my_app
version: 1.0.0
"""
(temp_dir / "pubspec.yaml").write_text(pubspec)
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_package_managers()
assert "pub" in analyzer.profile.detected_stack.package_managers
def test_detects_pub_from_lock_file(self, temp_dir: Path):
"""Detects pub package manager from pubspec.lock."""
(temp_dir / "pubspec.lock").write_text("packages:\n")
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_package_managers()
assert "pub" in analyzer.profile.detected_stack.package_managers
class TestMelosMonorepoDetection:
"""Tests for Melos monorepo tool detection."""
def test_detects_melos_from_config(self, temp_dir: Path):
"""Detects Melos from melos.yaml."""
melos_config = """name: my_workspace
packages:
- packages/*
"""
(temp_dir / "melos.yaml").write_text(melos_config)
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_package_managers()
assert "melos" in analyzer.profile.detected_stack.package_managers
def test_melos_commands_allowed(self, temp_dir: Path):
"""Melos commands are allowed when detected."""
melos_config = """name: my_workspace
packages:
- packages/*
"""
(temp_dir / "melos.yaml").write_text(melos_config)
profile = get_or_create_profile(temp_dir, force_reanalyze=True)
assert "melos" in profile.stack_commands
class TestFvmVersionManagerDetection:
"""Tests for Flutter Version Manager (FVM) detection."""
def test_detects_fvm_from_directory(self, temp_dir: Path):
"""Detects FVM from .fvm directory."""
(temp_dir / ".fvm").mkdir()
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_version_managers()
assert "fvm" in analyzer.profile.detected_stack.version_managers
def test_detects_fvm_from_config(self, temp_dir: Path):
"""Detects FVM from fvm_config.json."""
fvm_config = '{"flutterSdkVersion": "3.19.0"}'
(temp_dir / "fvm_config.json").write_text(fvm_config)
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_version_managers()
assert "fvm" in analyzer.profile.detected_stack.version_managers
def test_detects_fvm_from_fvmrc(self, temp_dir: Path):
"""Detects FVM from .fvmrc file."""
(temp_dir / ".fvmrc").write_text('{"flutter": "3.19.0"}')
analyzer = ProjectAnalyzer(temp_dir)
analyzer._detect_version_managers()
assert "fvm" in analyzer.profile.detected_stack.version_managers
def test_fvm_commands_allowed(self, temp_dir: Path):
"""FVM commands are allowed when detected."""
(temp_dir / ".fvm").mkdir()
profile = get_or_create_profile(temp_dir, force_reanalyze=True)
assert "fvm" in profile.stack_commands
class TestDartFlutterCommandsAllowed:
"""Tests that Dart/Flutter commands are properly allowed."""
def test_dart_commands_allowed_for_dart_project(self, temp_dir: Path):
"""Dart commands are allowed when Dart is detected."""
pubspec = """name: my_app
version: 1.0.0
"""
(temp_dir / "pubspec.yaml").write_text(pubspec)
profile = get_or_create_profile(temp_dir, force_reanalyze=True)
# Core Dart commands
assert "dart" in profile.stack_commands
assert "pub" in profile.stack_commands
# Flutter should be available for Dart projects
assert "flutter" in profile.stack_commands
def test_flutter_commands_allowed_for_flutter_project(self, temp_dir: Path):
"""Flutter commands are allowed when Flutter is detected."""
pubspec = """name: my_flutter_app
version: 1.0.0
dependencies:
flutter:
sdk: flutter
"""
(temp_dir / "pubspec.yaml").write_text(pubspec)
profile = get_or_create_profile(temp_dir, force_reanalyze=True)
assert "flutter" in profile.stack_commands
assert "dart" in profile.stack_commands
assert "pub" in profile.stack_commands