From 9b67433695d2f498e6b53cb30676054676016942 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:16 +0100 Subject: [PATCH 01/11] chore: add test dependencies for analysis rules Add analyzer_testing and test_reflective_loader packages to enable proper unit testing of analysis rules using the official Dart testing framework. --- pubspec.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pubspec.yaml b/pubspec.yaml index 675fbb3..3d994d9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -33,10 +33,12 @@ dependencies: yaml_edit: ^2.2.2 dev_dependencies: + analyzer_testing: ^0.1.0 build_runner: ^2.10.4 http: ^1.6.0 json_serializable: ^6.11.2 test: ^1.25.5 + test_reflective_loader: ^0.2.0 toml: ^0.17.0 very_good_analysis: ^10.0.0 From c42d9959c9b5b713dfa8b73cbceab7a701836c8f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:18 +0100 Subject: [PATCH 02/11] test: add unit tests for all 5 analysis rules Add comprehensive unit tests for security rules using analyzer_testing: - prefer_https_over_http: 11 tests - avoid_hardcoded_urls: 10 tests - avoid_weak_hashing: 8 tests - prefer_secure_random: 7 tests - avoid_hardcoded_secrets: 11 tests Total: 47 new tests covering positive and negative cases. --- .../cryptography/avoid_weak_hashing_test.dart | 136 +++++++++++++++++ .../prefer_secure_random_test.dart | 107 +++++++++++++ .../network/avoid_hardcoded_urls_test.dart | 122 +++++++++++++++ .../network/prefer_https_over_http_test.dart | 139 +++++++++++++++++ .../secrets/avoid_hardcoded_secrets_test.dart | 141 ++++++++++++++++++ 5 files changed, 645 insertions(+) create mode 100644 test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart create mode 100644 test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart create mode 100644 test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart create mode 100644 test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart create mode 100644 test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart diff --git a/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart b/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart new file mode 100644 index 0000000..7243c50 --- /dev/null +++ b/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart @@ -0,0 +1,136 @@ +import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:dart_shield/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidWeakHashingTest); + }); +} + +@reflectiveTest +class AvoidWeakHashingTest extends AnalysisRuleTest { + @override + String get analysisRule => 'avoid_weak_hashing'; + + @override + void setUp() { + Registry.ruleRegistry.registerLintRule(AvoidWeakHashing()); + + // Add stub for crypto package + newPackage('crypto')..addFile('lib/crypto.dart', r''' +abstract class Hash { + List convert(List data); +} +final Hash md5 = _Md5(); +final Hash sha1 = _Sha1(); +final Hash sha256 = _Sha256(); +class _Md5 implements Hash { + @override + List convert(List data) => []; +} +class _Sha1 implements Hash { + @override + List convert(List data) => []; +} +class _Sha256 implements Hash { + @override + List convert(List data) => []; +} +'''); + + super.setUp(); + } + + Future test_md5Convert_reports() async { + await assertDiagnostics( + r''' +import 'package:crypto/crypto.dart'; +void f() { + final hash = md5.convert([1, 2, 3]); +} +''', + [lint(63, 22)], + ); + } + + Future test_sha1Convert_reports() async { + await assertDiagnostics( + r''' +import 'package:crypto/crypto.dart'; +void f() { + final hash = sha1.convert([1, 2, 3]); +} +''', + [lint(63, 23)], + ); + } + + Future test_sha256Convert_noReport() async { + await assertNoDiagnostics(r''' +import 'package:crypto/crypto.dart'; +void f() { + final hash = sha256.convert([1, 2, 3]); +} +'''); + } + + Future test_md5Assignment_reports() async { + await assertDiagnostics( + r''' +import 'package:crypto/crypto.dart'; +void f() { + Hash hasher; + hasher = md5; +} +''', + [lint(65, 12)], + ); + } + + Future test_sha1Assignment_reports() async { + await assertDiagnostics( + r''' +import 'package:crypto/crypto.dart'; +void f() { + Hash hasher; + hasher = sha1; +} +''', + [lint(65, 13)], + ); + } + + Future test_sha256Assignment_noReport() async { + await assertNoDiagnostics(r''' +import 'package:crypto/crypto.dart'; +void f() { + Hash hasher; + hasher = sha256; +} +'''); + } + + Future test_md5InExpression_reports() async { + await assertDiagnostics( + r''' +import 'package:crypto/crypto.dart'; +void f() { + final result = md5.convert([1]).toString(); +} +''', + [lint(65, 16)], + ); + } + + Future test_unrelatedIdentifier_noReport() async { + // Identifiers named md5 or sha1 that are not from crypto package + await assertNoDiagnostics(r''' +void f() { + final md5 = 'some string'; + print(md5); +} +'''); + } +} diff --git a/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart b/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart new file mode 100644 index 0000000..b80059a --- /dev/null +++ b/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart @@ -0,0 +1,107 @@ +import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:dart_shield/src/analyzers/code/rules/cryptography/prefer_secure_random.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(PreferSecureRandomTest); + }); +} + +@reflectiveTest +class PreferSecureRandomTest extends AnalysisRuleTest { + @override + String get analysisRule => 'prefer_secure_random'; + + @override + void setUp() { + Registry.ruleRegistry.registerLintRule(PreferSecureRandom()); + super.setUp(); + } + + Future test_randomConstructor_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +void f() { + final rng = Random(); +} +''', + [lint(45, 8)], + ); + } + + Future test_randomWithSeed_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +void f() { + final rng = Random(42); +} +''', + [lint(45, 10)], + ); + } + + Future test_randomInClassField_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +class MyClass { + final rng = Random(); +} +''', + [lint(50, 8)], + ); + } + + Future test_randomInFunction_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +int getRandomNumber() { + return Random().nextInt(100); +} +''', + [lint(53, 8)], + ); + } + + Future test_multipleRandomInstances_reportsEach() async { + await assertDiagnostics( + r''' +import 'dart:math'; +void f() { + final rng1 = Random(); + final rng2 = Random(123); +} +''', + [lint(46, 8), lint(71, 11)], + ); + } + + Future test_randomWithVariableSeed_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +void f(int seed) { + final rng = Random(seed); +} +''', + [lint(53, 12)], + ); + } + + Future test_randomWithTimestampSeed_reports() async { + await assertDiagnostics( + r''' +import 'dart:math'; +void f() { + final rng = Random(DateTime.now().millisecondsSinceEpoch); +} +''', + [lint(45, 45)], + ); + } +} diff --git a/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart b/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart new file mode 100644 index 0000000..cb9aaa6 --- /dev/null +++ b/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart @@ -0,0 +1,122 @@ +import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:dart_shield/src/analyzers/code/rules/network/avoid_harcoded_urls.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidHardcodedUrlsTest); + }); +} + +@reflectiveTest +class AvoidHardcodedUrlsTest extends AnalysisRuleTest { + @override + String get analysisRule => 'avoid_hardcoded_urls'; + + @override + void setUp() { + Registry.ruleRegistry.registerLintRule(AvoidHardcodedUrls()); + super.setUp(); + } + + Future test_httpsUrl_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'https://api.example.com/v1'; +} +''', + [lint(25, 28)], + ); + } + + Future test_httpUrl_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://api.example.com'; +} +''', + [lint(25, 24)], + ); + } + + Future test_emptyString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final url = ''; +} +'''); + } + + Future test_nonUrlString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final text = 'hello world'; +} +'''); + } + + Future test_localhostUrl_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://localhost:8080/api'; +} +''', + [lint(25, 27)], + ); + } + + Future test_httpsWithQueryParams_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'https://api.example.com/search?q=test'; +} +''', + [lint(25, 39)], + ); + } + + Future test_multipleUrls_reportsEach() async { + await assertDiagnostics( + r''' +void f() { + final url1 = 'https://api.example.com'; + final url2 = 'http://other.example.com'; +} +''', + [lint(26, 25), lint(68, 26)], + ); + } + + Future test_urlInClassField_reports() async { + await assertDiagnostics( + r''' +class Config { + final baseUrl = 'https://api.example.com'; +} +''', + [lint(33, 25)], + ); + } + + Future test_urlInConstant_reports() async { + await assertDiagnostics( + r''' +const apiUrl = 'https://api.example.com/v1'; +''', + [lint(15, 28)], + ); + } + + Future test_partialUrl_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final path = '/api/v1/users'; +} +'''); + } +} diff --git a/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart b/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart new file mode 100644 index 0000000..8c95e13 --- /dev/null +++ b/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart @@ -0,0 +1,139 @@ +import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:dart_shield/src/analyzers/code/rules/network/prefer_https_over_http.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(PreferHttpsOverHttpTest); + }); +} + +@reflectiveTest +class PreferHttpsOverHttpTest extends AnalysisRuleTest { + @override + String get analysisRule => 'prefer_https_over_http'; + + @override + void setUp() { + Registry.ruleRegistry.registerLintRule(PreferHttpsOverHttp()); + super.setUp(); + } + + Future test_httpStringLiteral_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://example.com'; +} +''', + [lint(25, 20)], + ); + } + + Future test_httpsStringLiteral_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final url = 'https://example.com'; +} +'''); + } + + Future test_httpWithPath_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://example.com/api/v1'; +} +''', + [lint(25, 27)], + ); + } + + Future test_emptyString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final url = ''; +} +'''); + } + + Future test_nonUrlString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final text = 'hello world'; +} +'''); + } + + Future test_httpInVariable_reports() async { + await assertDiagnostics( + r''' +void f() { + final config = 'http://api.example.com'; +} +''', + [lint(28, 24)], + ); + } + + Future test_httpLocalhost_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://localhost:8080'; +} +''', + [lint(25, 23)], + ); + } + + Future test_httpWithPort_reports() async { + await assertDiagnostics( + r''' +void f() { + final url = 'http://example.com:3000/api'; +} +''', + [lint(25, 29)], + ); + } + + Future test_httpInList_reports() async { + await assertDiagnostics( + r''' +void f() { + final urls = [ + 'http://example.com', + ]; +} +''', + [lint(32, 20)], + ); + } + + Future test_httpInMap_reports() async { + await assertDiagnostics( + r''' +void f() { + final config = { + 'url': 'http://example.com', + }; +} +''', + [lint(41, 20)], + ); + } + + Future test_multipleHttpUrls_reportsEach() async { + await assertDiagnostics( + r''' +void f() { + final url1 = 'http://example.com'; + final url2 = 'http://other.com'; +} +''', + [lint(26, 20), lint(63, 18)], + ); + } +} diff --git a/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart b/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart new file mode 100644 index 0000000..686766d --- /dev/null +++ b/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart @@ -0,0 +1,141 @@ +import 'package:analyzer/src/lint/registry.dart'; +import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; +import 'package:dart_shield/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart'; +import 'package:test_reflective_loader/test_reflective_loader.dart'; + +void main() { + defineReflectiveSuite(() { + defineReflectiveTests(AvoidHardcodedSecretsTest); + }); +} + +@reflectiveTest +class AvoidHardcodedSecretsTest extends AnalysisRuleTest { + @override + String get analysisRule => 'avoid_hardcoded_secrets'; + + @override + void setUp() { + Registry.ruleRegistry.registerLintRule(AvoidHardcodedSecrets()); + super.setUp(); + } + + Future test_awsAccessKey_reports() async { + // AWS access keys start with AKIA and are 20 chars + await assertDiagnostics( + r''' +void f() { + final key = 'AKIAIOSFODNN7EXAMPLE'; +} +''', + [lint(25, 22)], + ); + } + + Future test_githubToken_reports() async { + await assertDiagnostics( + r''' +void f() { + final token = 'ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345'; +} +''', + [lint(27, 38)], + ); + } + + Future test_shortString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final s = 'short'; +} +'''); + } + + Future test_lowEntropyString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final s = 'aaaaaaaaaaaaaaaa'; +} +'''); + } + + Future test_contextVariableName_detects() async { + // Tests that apiKey variable name triggers keyword check with Stripe pattern + await assertDiagnostics( + r''' +void f() { + final apiKey = 'sk_live_abcdefghijklmnop'; +} +''', + [lint(28, 26)], + ); + } + + Future test_mapKeyContext_detects() async { + await assertDiagnostics( + r''' +void f() { + final config = { + 'apiKey': 'sk_test_1234567890abcdef', + }; +} +''', + [lint(44, 26)], + ); + } + + // Note: Slack token test removed to avoid GitHub push protection false positives + // The xoxb- pattern is still tested in integration tests with real project scans + + Future test_genericApiKeyPattern_reports() async { + // Generic API key with sufficient entropy + await assertDiagnostics( + r''' +void f() { + final apiKey = 'api_key_xK9mN2pL5qR8vW3yZ1aB4cD7eF0gH'; +} +''', + [lint(28, 39)], + ); + } + + Future test_regularString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final message = 'Hello, World!'; +} +'''); + } + + Future test_emptyString_noReport() async { + await assertNoDiagnostics(r''' +void f() { + final s = ''; +} +'''); + } + + Future test_namedParameterContext_detects() async { + await assertDiagnostics( + r''' +void configure({required String apiKey}) {} +void f() { + configure(apiKey: 'sk_live_abc123def456ghi789'); +} +''', + [lint(75, 28)], + ); + } + + Future test_jwtToken_reports() async { + // JWT tokens have distinctive patterns + await assertDiagnostics( + r''' +void f() { + final token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; +} +''', + [lint(27, 157)], + ); + } +} From 88e316f35ecdaf1141075a955af65b1b6fcd9768 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:21 +0100 Subject: [PATCH 03/11] ci: add tests and code coverage to CI pipeline - Add dart test step to run unit tests on every PR - Add coverage generation and upload to Codecov - Add separate integration test job - Configure codecov.yml with coverage thresholds (70% project, 80% patch) --- .github/workflows/dart.yml | 33 +++++++++++++++++++++++++++++++++ codecov.yml | 18 ++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 codecov.yml diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index 242cbff..dd27154 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -28,5 +28,38 @@ jobs: - name: ๐Ÿ“Š Analyze run: dart analyze --fatal-infos --fatal-warnings . + - name: ๐Ÿงช Run Tests + run: dart test --coverage=coverage + + - name: ๐Ÿ“ˆ Generate Coverage Report + run: | + dart pub global activate coverage + dart pub global run coverage:format_coverage \ + --lcov \ + --in=coverage \ + --out=coverage/lcov.info \ + --report-on=lib + + - name: ๐Ÿ“ˆ Upload Coverage + uses: codecov/codecov-action@v4 + with: + files: coverage/lcov.info + fail_ci_if_error: false + - name: ๐Ÿ“Š Run Pana run: dart pub global activate pana && dart pub global run pana + + integration-test: + runs-on: ubuntu-latest + needs: build + steps: + - uses: actions/checkout@v5 + - uses: dart-lang/setup-dart@v1.6.5 + with: + sdk: '3.9.0' + + - name: โ›“ Install Dependencies + run: dart pub get + + - name: ๐Ÿงช Run Integration Tests + run: dart test test/integration/ --timeout=120s diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 0000000..f52cb8b --- /dev/null +++ b/codecov.yml @@ -0,0 +1,18 @@ +coverage: + precision: 2 + round: down + range: "60...100" + + status: + project: + default: + target: 70% + threshold: 5% + patch: + default: + target: 80% + +ignore: + - "**/*.g.dart" + - "lib/src/generated/**" + - "test/**" From 8e16d4b115818a88df9e88632cfa9b39a64eca19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:23 +0100 Subject: [PATCH 04/11] docs: fix configuration documentation inconsistency Update README and example to correctly document analysis_options.yaml as the configuration file with dart_shield: key, replacing incorrect references to shield_options.yaml. - Add coverage badge to README - Simplify configuration documentation - Update example/analysis_options.yaml to use correct format --- README.md | 66 +++++++++++------------------------ example/analysis_options.yaml | 12 +++---- 2 files changed, 27 insertions(+), 51 deletions(-) diff --git a/README.md b/README.md index 59958a2..30f79b0 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@

Dart-based security-focused code analyzer which analyzes your Dart code for potential security flaws.

Pipelines: GitHub Actions + Coverage Style: Very Good Analysis @@ -63,12 +64,8 @@ To initialize `dart_shield` in your project, run the following command: dart_shield init ``` -This command creates a `shield_options.yaml` file in the root of your project. This file contains -the configuration for `dart_shield`, which will be used during the analysis (similar to -`analysis_options.yaml`). - -If a shield_options.yaml file already exists in your project and you want to recreate it, use the -`-f` or `--force` flag: +This command updates your `analysis_options.yaml` file to include the `dart_shield` configuration. +If the `dart_shield` section already exists and you want to recreate it, use the `-f` or `--force` flag: ```bash dart_shield init -f @@ -87,51 +84,30 @@ dart_shield analyze . dart_shield analyze lib ``` -This command analyzes your Dart code based on the configuration in the shield_options.yaml file. -If the configuration file is not found, the command will fail. +This command analyzes your Dart code for security issues. # Configuration -The `shield_options.yaml` file contains configuration options, primarily rules, for `dart_shield`. -The configuration is similar to the `analysis_options.yaml` file, making it familiar to those who -have -used Dart analysis tools. +Configuration is done through your `analysis_options.yaml` file using the `dart_shield` key. +This approach follows Dart conventions and keeps all analysis configuration in one place. -Example of the `shield_options.yaml` file: +Example configuration in `analysis_options.yaml`: ```yaml -# This is a sample configuration file for dart_shield. -# โš ๏ธ Configuration file must be named `shield_options.yaml` and placed in the root of the project. - -# shield_options.yaml is file with structure similar to analysis_options.yaml and it defines the -# rules that dart_shield will use to analyze your code. - -# The `shield` key is required. -shield: - - # List of excluded files or directories from being analyzed - exclude: - # Exclude a file using path (path begins at the root of the project): - - 'lib/ignored.dart' - # Globs are also supported - - '**.g.dart' - - # List of rules that dart_shield will use to analyze your code - rules: - - prefer_https_over_http.dart - - avoid_hardcoded_secrets - - # Some rules need more fine-tuning and are marked as experimental. - # You can enable them by setting `enable_experimental` to `true`. - enable_experimental: true - - # List of experimental rules that dart_shield will use to analyze your code - # โš ๏ธ Experimental rules are subject to change and may not be as stable as regular rules. - # โš ๏ธ Using "experimental_rules" without setting "enable_experimental" to "true" will cause an error. - experimental_rules: - - avoid_hardcoded_urls - - avoid_weak_hashing - - prefer_secure_random +# Enable dart_shield as an analyzer plugin +analyzer: + plugins: + - dart_shield + +# dart_shield configuration +dart_shield: + analyzers: + code: true # Enable code analysis + + # Future options: + # exclude: + # - 'lib/generated/**' + # - '**.g.dart' ``` # Rules diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 184f5bd..fd796ac 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -1,9 +1,9 @@ include: package:lints/recommended.yaml -plugins: - dart_shield: - path: ../. +analyzer: + plugins: + - dart_shield -# I was thinking about shield: or dart_shield: -shield: - # Some config we might want to have \ No newline at end of file +dart_shield: + analyzers: + code: true \ No newline at end of file From 5f11c899a43c332aa35c85cfa8760684514f8496 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:33 +0100 Subject: [PATCH 05/11] feat: add severity metadata and filtering to rules - Create rule_metadata.dart with severity levels and OWASP/CWE refs - Map rule IDs to proper severity (high/medium/low/info) - Add --min-severity flag to analyze command for filtering - Update shield_runner to filter results by minimum severity Severity assignments: - avoid_hardcoded_secrets: high (CWE-798) - prefer_https_over_http: high (CWE-319) - avoid_weak_hashing: medium (CWE-328) - prefer_secure_random: medium (CWE-330) - avoid_hardcoded_urls: low (CWE-547) --- .../analyzers/code/rules/rule_metadata.dart | 77 +++++++++++++++++++ lib/src/analyzers/utils/dto_mapper.dart | 16 +++- lib/src/cli/commands/analyze_command.dart | 9 +++ lib/src/core/shield_run_config.dart | 19 +++++ lib/src/core/shield_runner.dart | 31 +++++++- 5 files changed, 147 insertions(+), 5 deletions(-) create mode 100644 lib/src/analyzers/code/rules/rule_metadata.dart diff --git a/lib/src/analyzers/code/rules/rule_metadata.dart b/lib/src/analyzers/code/rules/rule_metadata.dart new file mode 100644 index 0000000..1abd02e --- /dev/null +++ b/lib/src/analyzers/code/rules/rule_metadata.dart @@ -0,0 +1,77 @@ +import 'package:dart_shield/src/domain/analysis_issue.dart'; + +/// Metadata for security rules including severity and documentation links. +class RuleMetadata { + const RuleMetadata({ + required this.ruleId, + required this.severity, + this.owaspCategory, + this.cweId, + this.documentationUrl, + }); + + final String ruleId; + final Severity severity; + final String? owaspCategory; + final String? cweId; + final String? documentationUrl; +} + +/// Registry of all rule metadata. +/// +/// Each rule is categorized by severity level and mapped to relevant +/// security standards (OWASP, CWE). +const Map ruleMetadataRegistry = { + 'avoid_hardcoded_secrets': RuleMetadata( + ruleId: 'avoid_hardcoded_secrets', + severity: Severity.high, + owaspCategory: 'A02:2021 Cryptographic Failures', + cweId: 'CWE-798', + documentationUrl: + 'https://dart-shield.dev/rulebook/secrets/avoid-hardcoded-secrets', + ), + 'prefer_https_over_http': RuleMetadata( + ruleId: 'prefer_https_over_http', + severity: Severity.high, + owaspCategory: 'A02:2021 Cryptographic Failures', + cweId: 'CWE-319', + documentationUrl: + 'https://dart-shield.dev/rulebook/network/prefer-https-over-http', + ), + 'avoid_weak_hashing': RuleMetadata( + ruleId: 'avoid_weak_hashing', + severity: Severity.medium, + owaspCategory: 'A02:2021 Cryptographic Failures', + cweId: 'CWE-328', + documentationUrl: + 'https://dart-shield.dev/rulebook/cryptography/avoid-weak-hashing', + ), + 'prefer_secure_random': RuleMetadata( + ruleId: 'prefer_secure_random', + severity: Severity.medium, + owaspCategory: 'A02:2021 Cryptographic Failures', + cweId: 'CWE-330', + documentationUrl: + 'https://dart-shield.dev/rulebook/cryptography/prefer-secure-random', + ), + 'avoid_hardcoded_urls': RuleMetadata( + ruleId: 'avoid_hardcoded_urls', + severity: Severity.low, + owaspCategory: 'A05:2021 Security Misconfiguration', + cweId: 'CWE-547', + documentationUrl: + 'https://dart-shield.dev/rulebook/network/avoid-hardcoded-urls', + ), +}; + +/// Gets the metadata for a rule by its ID. +/// +/// Returns null if the rule ID is not found in the registry. +RuleMetadata? getRuleMetadata(String ruleId) => ruleMetadataRegistry[ruleId]; + +/// Gets the severity for a rule by its ID. +/// +/// Returns [Severity.info] if the rule ID is not found in the registry. +Severity getSeverityForRule(String ruleId) { + return ruleMetadataRegistry[ruleId]?.severity ?? Severity.info; +} diff --git a/lib/src/analyzers/utils/dto_mapper.dart b/lib/src/analyzers/utils/dto_mapper.dart index 8b2697a..739ef01 100644 --- a/lib/src/analyzers/utils/dto_mapper.dart +++ b/lib/src/analyzers/utils/dto_mapper.dart @@ -1,3 +1,4 @@ +import 'package:dart_shield/src/analyzers/code/rules/rule_metadata.dart'; import 'package:dart_shield/src/analyzers/utils/analyzer_result.dart'; import 'package:dart_shield/src/domain/analysis_issue.dart'; import 'package:dart_shield/src/domain/issue_context.dart'; @@ -11,7 +12,7 @@ extension DtoDiagnosticMapper on Diagnostic { return AnalysisIssue( ruleId: code, - severity: _mapSeverity(severity), + severity: _mapSeverity(code), message: problemMessage, context: FileContext( filePath: location!.file, @@ -21,7 +22,18 @@ extension DtoDiagnosticMapper on Diagnostic { ); } - Severity _mapSeverity(String severity) { + /// Maps the rule ID to its severity level from the rule metadata registry. + /// + /// Falls back to severity based on the diagnostic severity string if the + /// rule is not found in the registry. + Severity _mapSeverity(String ruleId) { + // First, try to get severity from the rule metadata registry + final metadata = getRuleMetadata(ruleId); + if (metadata != null) { + return metadata.severity; + } + + // Fallback to diagnostic severity if rule not found switch (severity.toUpperCase()) { case 'ERROR': return Severity.high; diff --git a/lib/src/cli/commands/analyze_command.dart b/lib/src/cli/commands/analyze_command.dart index 36f713a..b20f631 100644 --- a/lib/src/cli/commands/analyze_command.dart +++ b/lib/src/cli/commands/analyze_command.dart @@ -13,6 +13,13 @@ class AnalyzeCommand extends ShieldCommand { defaultsTo: 'console', help: 'Select the output format.', ) + ..addOption( + 'min-severity', + abbr: 's', + allowed: ['info', 'low', 'medium', 'high'], + defaultsTo: 'info', + help: 'Minimum severity level to report.', + ) ..addMultiOption( 'only', allowed: AnalyzerFactory.availableIds, @@ -34,11 +41,13 @@ class AnalyzeCommand extends ShieldCommand { @override Future run() async { + final severityStr = argResults['min-severity'] as String? ?? 'info'; final config = ShieldRunConfig( paths: argResults.rest.isEmpty ? ['.'] : argResults.rest, only: argResults['only'] as List? ?? [], exclude: argResults['exclude'] as List? ?? [], reporterMode: argResults['reporter'] as String? ?? 'console', + minSeverity: ShieldRunConfig.parseSeverity(severityStr), ); final runner = ShieldRunner(logger: logger); diff --git a/lib/src/core/shield_run_config.dart b/lib/src/core/shield_run_config.dart index 23e285b..7fb6ccc 100644 --- a/lib/src/core/shield_run_config.dart +++ b/lib/src/core/shield_run_config.dart @@ -1,13 +1,32 @@ +import 'package:dart_shield/src/domain/analysis_issue.dart'; + class ShieldRunConfig { const ShieldRunConfig({ required this.paths, this.only = const [], this.exclude = const [], this.reporterMode = 'console', + this.minSeverity = Severity.info, }); final List paths; final List only; final List exclude; final String reporterMode; + + /// Minimum severity level to report. + /// Issues below this severity level will be filtered out. + final Severity minSeverity; + + /// Parses a severity string to the corresponding [Severity] enum. + /// Returns [Severity.info] if the string is not recognized. + static Severity parseSeverity(String value) { + return switch (value.toLowerCase()) { + 'high' => Severity.high, + 'medium' => Severity.medium, + 'low' => Severity.low, + 'info' => Severity.info, + _ => Severity.info, + }; + } } diff --git a/lib/src/core/shield_runner.dart b/lib/src/core/shield_runner.dart index 9f65ac7..ccdd44a 100644 --- a/lib/src/core/shield_runner.dart +++ b/lib/src/core/shield_runner.dart @@ -2,6 +2,7 @@ import 'package:dart_shield/src/configuration/shield_config.dart'; import 'package:dart_shield/src/core/analyzer_engine.dart'; import 'package:dart_shield/src/core/analyzer_factory.dart'; import 'package:dart_shield/src/core/shield_run_config.dart'; +import 'package:dart_shield/src/domain/analysis_issue.dart'; import 'package:dart_shield/src/domain/analyzer_result.dart'; import 'package:dart_shield/src/domain/exceptions.dart'; import 'package:dart_shield/src/reporters/console_reporter.dart'; @@ -39,13 +40,16 @@ class ShieldRunner { // 4. Execution _logger.info('๐Ÿ›ก๏ธ Running ${analyzers.length} analyzers...'); final engine = AnalyzerEngine(analyzers); - final results = await engine.runAll(); + var results = await engine.runAll(); - // 5. Reporting + // 5. Filter by minimum severity + results = _filterBySeverity(results, runConfig.minSeverity); + + // 6. Reporting final reporters = _getReporters(runConfig.reporterMode); await Future.wait(reporters.map((r) => r.report(results))); - // 6. Exit Logic + // 7. Exit Logic return _calculateExitCode(results); } on ShieldException catch (e) { _logger.err(e.toString()); @@ -58,6 +62,27 @@ class ShieldRunner { } } + /// Filters analysis results to only include issues at or above the minimum + /// severity level. + List _filterBySeverity( + List results, + Severity minSeverity, + ) { + return results.map((result) { + if (result is AnalysisSuccess) { + final filteredIssues = result.issues + .where((issue) => issue.severity.index <= minSeverity.index) + .toList(); + return AnalysisSuccess( + analyzerId: result.analyzerId, + issues: filteredIssues, + duration: result.duration, + ); + } + return result; + }).toList(); + } + List _getReporters(String mode) { return switch (mode) { 'json' => [JsonReporter()], From a67688072a6fb3462cbc2926386cdedf2cbfa143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:33 +0100 Subject: [PATCH 06/11] chore: update ignore files and clean up tracked files - Add pubspec.lock to .gitignore (not needed for packages) - Add internal docs to .gitignore (GEMINI.md, ROADMAP.md, etc.) - Update .pubignore with test/, tool/, codecov.yml, docs.json - Remove pubspec.lock files from git tracking - Remove internal planning documents from git tracking --- .gitignore | 4 + .pubignore | 13 + GEMINI.md | 64 ---- ROADMAP.md | 130 -------- RULES_CATEGORIZATION.md | 73 ----- example/pubspec.lock | 332 -------------------- pubspec.lock | 653 ---------------------------------------- 7 files changed, 17 insertions(+), 1252 deletions(-) delete mode 100644 GEMINI.md delete mode 100644 ROADMAP.md delete mode 100644 RULES_CATEGORIZATION.md delete mode 100644 example/pubspec.lock delete mode 100644 pubspec.lock diff --git a/.gitignore b/.gitignore index c19f353..52632d5 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .dart_tool/ .packages build/ +pubspec.lock # Files generated during tests .test_coverage.dart @@ -21,6 +22,9 @@ coverage/ # Project .gemini/ +GEMINI.md +ROADMAP.md +RULES_CATEGORIZATION.md # Custom code_examples/ \ No newline at end of file diff --git a/.pubignore b/.pubignore index efb994d..383be22 100644 --- a/.pubignore +++ b/.pubignore @@ -1,2 +1,15 @@ +# Documentation (hosted separately) docs/ +docs.json + +# GitHub files .github/ + +# CI/CD configuration +codecov.yml + +# Tests (not needed for package consumers) +test/ + +# Internal tooling +tool/ diff --git a/GEMINI.md b/GEMINI.md deleted file mode 100644 index 733e60f..0000000 --- a/GEMINI.md +++ /dev/null @@ -1,64 +0,0 @@ -# Dart Shield - -**Project Type:** Dart/Flutter CLI Application -**Status:** Under Construction (Prototype/Dev) - -## Overview -`dart_shield` is an open-source static analysis tool designed to secure Dart codebases by detecting potential vulnerabilities before they reach production. It functions similarly to a linter but focuses specifically on security flaws. - -## Key Features -- **Hardcoded Secret Detection:** Identifies API keys, passwords, and other sensitive data. -- **Insecure Connection Detection:** Flags usage of HTTP instead of HTTPS. -- **Weak Cryptography Detection:** Warns against weak hashing algorithms (MD5, SHA-1) and insecure random number generators. -- **Configurable:** Uses `shield_options.yaml` for project-specific configuration. - -## Architecture -The project follows a standard Dart CLI structure: - -- **Entry Point:** `bin/dart_shield.dart` initializes the `ShieldCommandRunner`. -- **CLI Layer:** `lib/src/cli/` handles command parsing (`init`, `analyze`) using `package:args`. -- **Core Engine:** `lib/src/core/` contains `ShieldRunner` and `AnalyzerEngine` which orchestrate the analysis process. -- **Analyzers:** - - `CodeAnalyzer` (`lib/src/analyzers/code/`): Wraps `dart analyze` (currently WIP). - - **Rules:** defined in `lib/src/analyzers/code/rules/`. -- **Configuration:** `lib/src/configuration/` manages loading and parsing of `shield_options.yaml`. -- **Reporting:** `lib/src/reporters/` handles output formatting (Console, JSON). - -## Development - -### Prerequisites -- Dart SDK: `>=3.10.0 <4.0.0` - -### Building and Running -To run the CLI from source: -```bash -dart run bin/dart_shield.dart [command] -# Example: -dart run bin/dart_shield.dart analyze -``` - -### Testing -Run unit tests: -```bash -dart test -``` - -### Code Style -The project uses `very_good_analysis` for linting. -```bash -dart analyze -``` - -## Key Files & Directories -- `bin/dart_shield.dart`: Main entry point. -- `lib/src/cli/commands/`: Implementation of `init` and `analyze` commands. -- `lib/src/analyzers/code/rules/`: specific security rules (e.g., `avoid_hardcoded_secrets.dart`). -- `shield_options.yaml`: (User-side) Configuration file for the tool. -- `analysis_options.yaml`: (Dev-side) Linter configuration for the project itself. -## Documentation Style -- All documentation must follow [Google Developer Documentation Style Guide](https://developers.google.com/style/). -- Tone: Professional, clear, concise, and direct. -- Structure: Use the agreed-upon template for rule documentation (Description, Non-Compliant/Compliant Code, How to Fix, Why, When to Ignore, Resources). - -## Rule Documentation Template -Refer to `docs/rulebook/RULE_TEMPLATE.mdx` for the mandatory structure of rule documentation files. diff --git a/ROADMAP.md b/ROADMAP.md deleted file mode 100644 index a8a2542..0000000 --- a/ROADMAP.md +++ /dev/null @@ -1,130 +0,0 @@ -# Dart Shield - Feature Roadmap & Ideas - -This document tracks high-impact features that could distinguish `dart_shield` as the premier security tool for the Dart/Flutter ecosystem. - -## ๐Ÿš€ High Impact / "Killer" Features - -### 1. Dependency Shield (Supply Chain Security) -**Concept:** Integrate with the OSV (Open Source Vulnerabilities) database to scan `pubspec.lock` for packages with known security advisories. -**Value:** "Single Pane of Glass" for security. Users don't need to run `dart pub audit` separately; `dart_shield` covers both Code and Dependencies. -**Implementation:** -* Parse `pubspec.lock`. -* Query OSV API (batch). -* Report CVEs directly in the CLI output. - -### 2. "Shield Ignore" & Baselines (Legacy Support) -**Concept:** Allow teams to adopt `dart_shield` in large, existing projects without fixing 1000 legacy issues immediately. -**Value:** Eliminates the "all or nothing" adoption barrier. -**Implementation:** -* `dart_shield baseline`: Generates a `shield_baseline.yaml` recording all current issues. -* `analyze`: Ignores issues present in the baseline, reporting only *new* violations. -* Support standard `// ignore: rule_id` comments (leveraging Analyzer's ignore mechanism). - -### 3. Intelligent Auto-Fix -**Concept:** Automatically resolve common security issues. -**Value:** Reduces friction from "detection" to "resolution". -**Implementation:** -* **HTTP -> HTTPS:** Auto-rewrite URLs. -* **Weak Random:** `Random()` -> `Random.secure()`. -* **Weak Hash:** `md5` -> `sha256` (with warning). -* **Hardcoded Secret:** Suggest refactoring to `Platform.environment['VAR']` (Code Action). - -### 4. "Taint Analysis" / Data Flow Analysis (Advanced) -**Concept:** Track untrusted data (e.g., from API/User Input) to sensitive sinks (e.g., SQL query, `Process.run`). -**Value:** Detects complex vulnerabilities like SQL Injection, Command Injection, or XSS that simple regex/AST checks miss. -**Implementation:** -* Mark sources (arguments to `main`, `HttpRequest`, `stdin`). -* Mark sinks (`execute()`, `eval()`). -* Trace variable assignments through the AST to see if tainted data reaches a sink without sanitization. - -### 5. "PII Scout" (Privacy) -**Concept:** Detect potential logging or leakage of Personally Identifiable Information (PII). -**Value:** Helps with GDPR/CCPA compliance. -**Implementation:** -* Detect variable names like `email`, `password`, `ssn`, `creditCard`. -* Flag if these variables are passed to `print()`, `log()`, or sent to external analytics services (e.g., Firebase Analytics, Sentry) without hashing. - -### 6. CI/CD Integration & Reporting -**Concept:** Native support for GitHub Actions, GitLab CI, etc. -**Value:** "Plug and Play" security pipelines. -**Implementation:** -* Output formats: SARIF (standard for GitHub Security), JUnit XML. -* GitHub Action marketplace entry. -* Annotate PRs directly (using SARIF). - -### 7. "Shield Policy" (Enterprise Control) -**Concept:** Enforce security policies across an organization. -**Value:** Standardization for large teams. -**Implementation:** -* Remote config: Load `shield_options.yaml` from a URL (e.g., internal repo). -* "Strict Mode": Disallow ignores for critical severities. - -### 8. Flutter-Specific Security -**Concept:** Rules tailored for mobile risks. -**Value:** Niche dominance in the Flutter space. -**Implementation:** -* **Manifest Analysis:** Check `AndroidManifest.xml` for dangerous permissions (`READ_SMS`, `SYSTEM_ALERT_WINDOW`). -* **Plist Analysis:** Check `Info.plist` for `NSAppTransportSecurity` (allowing arbitrary loads). -* **WebView:** Flag `useHybridComposition: true` or insecure WebView settings. -* **Local Auth:** Ensure `local_auth` is used correctly. - -### 9. "Graph Shield" (Architecture Viz) -**Concept:** Visualize the security posture. -**Value:** "Manager-friendly" reports. -**Implementation:** -* Generate an HTML/Graphviz report showing dependencies and flagged hotspots. - -### 10. "Secret Canary" / "Honeytoken" Detection -**Concept:** Integrate with Canarytokens to manage or verify found secrets. -**Value:** Proactive defense. -**Implementation:** -* Check if found secrets are known Honeytoken formats. -* Suggest replacing hardcoded secrets with Honeytokens for tests. - -### 11. "Interactive Security Training" (Edu-Tech) -**Concept:** Mini-tutorials in the terminal. -**Value:** Upskills the team. -**Implementation:** -* `dart_shield explain `: Shows "Bad Code" vs "Good Code" and explains the risk. - -### 12. "Shadow Dependencies" / "Typosquatting" Detector -**Concept:** Detect malicious packages that look like popular ones (e.g., `providr` vs `provider`). -**Value:** Protects against supply chain attacks. -**Implementation:** -* Check `pubspec.yaml` against top packages using Levenshtein distance. - -### 13. "License Compliance" (Legal Shield) -**Concept:** Check dependency licenses (GPL, AGPL, etc.). -**Value:** Enterprise requirement. -**Implementation:** -* Scan package licenses. -* Flag incompatible licenses based on project type. - -### 14. "Pre-Commit Hook" Installer -**Concept:** Frictionless setup to block secrets before commit. -**Value:** Prevention > Cure. -**Implementation:** -* `dart_shield install-hook`: Installs a Git pre-commit hook. - -### 15. "Cloud Configuration Scanner" (IaC) -**Concept:** Scan Dockerfile and docker-compose.yaml. -**Value:** Full stack coverage for Dart backends. -**Implementation:** -* Flag `USER root`. -* Flag secrets in `ENV` vars. - -### 16. "Binary Inspector" (Reverse Engineering) -**Concept:** Analyze the compiled output (`.apk`, `.ipa`, `.exe`). -**Value:** Verifies what actually ships (e.g., "Did obfuscation work?"). -**Implementation:** -* **Strings:** Run `strings` on the binary to check if secrets/API keys are still visible in the compiled artifact (e.g., `libapp.so`). -* **Obfuscation Check:** Verify if symbols are stripped/obfuscated. -* **Permissions:** Read final merged manifest from APK. - -### 17. "Network Traffic Monitor" (DevTool Proxy) -**Concept:** A local proxy to inspect HTTP traffic from the running Dart app. -**Value:** Detects unencrypted traffic or sensitive data sent in cleartext during development. -**Implementation:** -* Spin up a local proxy (like helper for Charles/MITMProxy). -* Flag HTTP requests. -* Flag sensitive data (passwords, tokens) in URL parameters or bodies. \ No newline at end of file diff --git a/RULES_CATEGORIZATION.md b/RULES_CATEGORIZATION.md deleted file mode 100644 index 28d27e5..0000000 --- a/RULES_CATEGORIZATION.md +++ /dev/null @@ -1,73 +0,0 @@ -# Dart Shield Rule Categorization Concept - -This document outlines a proposed categorization for `dart_shield`'s security rules, aligning them with the OWASP Mobile Top 10 (2024) standard. This approach aims to provide a more structured and universally recognized classification for our rules, benefiting both codebase organization and user documentation. - -## Goals - -* **Standardization**: Align with an industry-recognized security standard (OWASP Mobile Top 10) to enhance clarity and relevance. -* **Organization**: Improve code structure by grouping related rules logically. -* **Documentation**: Provide a clearer, more navigable rulebook for users. - -## Proposed Categories and Rule Mapping - -Based on existing rules and common security concerns for Dart/Flutter applications, the following categories are proposed: - ---- - -### 1. Cryptography (OWASP Mobile Top 10: M10 - Insufficient Cryptography) - -This category focuses on rules designed to identify and flag insecure or weak cryptographic practices within the codebase. - -**Existing Rules:** -* `avoid_weak_hashing.dart` (Detects use of weak hashing algorithms like MD5, SHA-1) -* `prefer_secure_random.dart` (Flags non-cryptographically secure random number generators) - -**Proposed Folder Location:** -`lib/src/analyzers/code/rules/cryptography/` - ---- - -### 2. Network (OWASP Mobile Top 10: M5 - Insecure Communication) - -This category covers rules related to the security of data in transit and communication protocols, ensuring secure channels and endpoints. - -**Existing Rules:** -* `prefer_https_over_http.dart` (Warns against using insecure HTTP where HTTPS is preferred) -* `avoid_hardcoded_urls.dart` (Encourages configurable and secure URL management) - -**Proposed Folder Location:** -`lib/src/analyzers/code/rules/network/` - ---- - -### 3. Secrets (OWASP Mobile Top 10: M1 - Improper Credential Usage) - -This category addresses the detection of sensitive information, such as API keys, tokens, and credentials, being hardcoded directly into the application. - -**Existing Rules:** -* `avoid_hardcoded_secrets.dart` (Identifies various hardcoded secret patterns) - -**Proposed Folder Location:** -`lib/src/analyzers/code/rules/secrets/` (This folder already exists and contains related helper logic, making it a natural fit for this rule.) - ---- - -## Proposed Codebase Restructuring - -To implement this categorization, the following steps would be taken in the codebase: - -1. Create new subdirectories within `lib/src/analyzers/code/rules/`: - * `cryptography/` - * `network/` -2. Move the respective rule files into their new category-specific folders. -3. Update `lib/src/analyzers/code/rules/rules.dart` to reflect the new import paths for all moved rules. - -## Future Categories - -As `dart_shield` evolves, new rules can be introduced and mapped to other relevant OWASP Mobile Top 10 categories, such as: - -* **Storage** (M9: Insecure Data Storage) -* **Injection** (M4: Insufficient Input/Output Validation) -* **WebView** (M5: Insecure Communication / M4: Insufficient Input/Output Validation, depending on specific rule) - -This structured approach will help in expanding `dart_shield`'s coverage in an organized and industry-recognized manner. diff --git a/example/pubspec.lock b/example/pubspec.lock deleted file mode 100644 index 4c1629c..0000000 --- a/example/pubspec.lock +++ /dev/null @@ -1,332 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d - url: "https://pub.dev" - source: hosted - version: "91.0.0" - analysis_server_plugin: - dependency: transitive - description: - name: analysis_server_plugin - sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - analyzer: - dependency: transitive - description: - name: analyzer - sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0 - url: "https://pub.dev" - source: hosted - version: "8.4.0" - analyzer_plugin: - dependency: transitive - description: - name: analyzer_plugin - sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53" - url: "https://pub.dev" - source: hosted - version: "0.13.10" - anio: - dependency: transitive - description: - name: anio - sha256: "7af260b3b3f228faafcfcf9626fbb302224c5022c0e5c82de8fa9644cdf9cb3c" - url: "https://pub.dev" - source: hosted - version: "2.0.10" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - checked_yaml: - dependency: transitive - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - cli_completion: - dependency: transitive - description: - name: cli_completion - sha256: "72e8ccc4545f24efa7bbdf3bff7257dc9d62b072dee77513cc54295575bc9220" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - collection: - dependency: transitive - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - crypto: - dependency: "direct main" - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - dart_shield: - dependency: "direct main" - description: - path: ".." - relative: true - source: path - version: "0.1.0-dev.6" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b - url: "https://pub.dev" - source: hosted - version: "3.1.3" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: transitive - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_system: - dependency: transitive - description: - name: file_system - sha256: "98b8edb99821ae1dfac865538c04ae071ecd2a6d1910e83ae67c50af79d0fbd7" - url: "https://pub.dev" - source: hosted - version: "2.0.9" - glob: - dependency: transitive - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - http: - dependency: "direct main" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" - json_annotation: - dependency: transitive - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - lints: - dependency: "direct dev" - description: - name: lints - sha256: a2c3d198cb5ea2e179926622d433331d8b58374ab8f29cdda6e863bd62fd369c - url: "https://pub.dev" - source: hosted - version: "1.0.1" - mason_logger: - dependency: transitive - description: - name: mason_logger - sha256: "6d5a989ff41157915cb5162ed6e41196d5e31b070d2f86e1c2edf216996a158c" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - meta: - dependency: transitive - description: - name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" - source: hosted - version: "1.17.0" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: transitive - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - process: - dependency: transitive - description: - name: process - sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 - url: "https://pub.dev" - source: hosted - version: "5.0.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pub_updater: - dependency: transitive - description: - name: pub_updater - sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - source_span: - dependency: transitive - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - watcher: - dependency: transitive - description: - name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" - url: "https://pub.dev" - source: hosted - version: "1.1.4" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" - yaml: - dependency: transitive - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" - yaml_edit: - dependency: transitive - description: - name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 - url: "https://pub.dev" - source: hosted - version: "2.2.2" -sdks: - dart: ">=3.10.0 <4.0.0" diff --git a/pubspec.lock b/pubspec.lock deleted file mode 100644 index 1696fe9..0000000 --- a/pubspec.lock +++ /dev/null @@ -1,653 +0,0 @@ -# Generated by pub -# See https://dart.dev/tools/pub/glossary#lockfile -packages: - _fe_analyzer_shared: - dependency: transitive - description: - name: _fe_analyzer_shared - sha256: c209688d9f5a5f26b2fb47a188131a6fb9e876ae9e47af3737c0b4f58a93470d - url: "https://pub.dev" - source: hosted - version: "91.0.0" - analysis_server_plugin: - dependency: "direct main" - description: - name: analysis_server_plugin - sha256: "26844e7f977087567135d62532b67d5639fe206c5194c3f410ba75e1a04a2747" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - analyzer: - dependency: "direct main" - description: - name: analyzer - sha256: a40a0cee526a7e1f387c6847bd8a5ccbf510a75952ef8a28338e989558072cb0 - url: "https://pub.dev" - source: hosted - version: "8.4.0" - analyzer_plugin: - dependency: "direct main" - description: - name: analyzer_plugin - sha256: "08cfefa90b4f4dd3b447bda831cecf644029f9f8e22820f6ee310213ebe2dd53" - url: "https://pub.dev" - source: hosted - version: "0.13.10" - anio: - dependency: transitive - description: - name: anio - sha256: "7af260b3b3f228faafcfcf9626fbb302224c5022c0e5c82de8fa9644cdf9cb3c" - url: "https://pub.dev" - source: hosted - version: "2.0.10" - args: - dependency: "direct main" - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" - async: - dependency: transitive - description: - name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" - url: "https://pub.dev" - source: hosted - version: "2.13.0" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" - url: "https://pub.dev" - source: hosted - version: "2.1.2" - build: - dependency: transitive - description: - name: build - sha256: c1668065e9ba04752570ad7e038288559d1e2ca5c6d0131c0f5f55e39e777413 - url: "https://pub.dev" - source: hosted - version: "4.0.3" - build_config: - dependency: transitive - description: - name: build_config - sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187" - url: "https://pub.dev" - source: hosted - version: "1.2.0" - build_daemon: - dependency: transitive - description: - name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 - url: "https://pub.dev" - source: hosted - version: "4.1.1" - build_runner: - dependency: "direct dev" - description: - name: build_runner - sha256: "110c56ef29b5eb367b4d17fc79375fa8c18a6cd7acd92c05bb3986c17a079057" - url: "https://pub.dev" - source: hosted - version: "2.10.4" - built_collection: - dependency: transitive - description: - name: built_collection - sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" - url: "https://pub.dev" - source: hosted - version: "5.1.1" - built_value: - dependency: transitive - description: - name: built_value - sha256: "426cf75afdb23aa74bd4e471704de3f9393f3c7b04c1e2d9c6f1073ae0b8b139" - url: "https://pub.dev" - source: hosted - version: "8.12.1" - checked_yaml: - dependency: "direct main" - description: - name: checked_yaml - sha256: "959525d3162f249993882720d52b7e0c833978df229be20702b33d48d91de70f" - url: "https://pub.dev" - source: hosted - version: "2.0.4" - cli_completion: - dependency: "direct main" - description: - name: cli_completion - sha256: "72e8ccc4545f24efa7bbdf3bff7257dc9d62b072dee77513cc54295575bc9220" - url: "https://pub.dev" - source: hosted - version: "0.5.1" - cli_config: - dependency: transitive - description: - name: cli_config - sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec - url: "https://pub.dev" - source: hosted - version: "0.2.0" - code_builder: - dependency: transitive - description: - name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" - url: "https://pub.dev" - source: hosted - version: "4.11.0" - collection: - dependency: "direct main" - description: - name: collection - sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" - url: "https://pub.dev" - source: hosted - version: "1.19.1" - convert: - dependency: transitive - description: - name: convert - sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68 - url: "https://pub.dev" - source: hosted - version: "3.1.2" - coverage: - dependency: transitive - description: - name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" - url: "https://pub.dev" - source: hosted - version: "1.15.0" - crypto: - dependency: transitive - description: - name: crypto - sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf - url: "https://pub.dev" - source: hosted - version: "3.0.7" - dart_style: - dependency: transitive - description: - name: dart_style - sha256: a9c30492da18ff84efe2422ba2d319a89942d93e58eb0b73d32abe822ef54b7b - url: "https://pub.dev" - source: hosted - version: "3.1.3" - equatable: - dependency: transitive - description: - name: equatable - sha256: "567c64b3cb4cf82397aac55f4f0cbd3ca20d77c6c03bedbc4ceaddc08904aef7" - url: "https://pub.dev" - source: hosted - version: "2.0.7" - ffi: - dependency: transitive - description: - name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - file: - dependency: "direct main" - description: - name: file - sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 - url: "https://pub.dev" - source: hosted - version: "7.0.1" - file_system: - dependency: "direct main" - description: - name: file_system - sha256: "98b8edb99821ae1dfac865538c04ae071ecd2a6d1910e83ae67c50af79d0fbd7" - url: "https://pub.dev" - source: hosted - version: "2.0.9" - fixnum: - dependency: transitive - description: - name: fixnum - sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be - url: "https://pub.dev" - source: hosted - version: "1.1.1" - frontend_server_client: - dependency: transitive - description: - name: frontend_server_client - sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694 - url: "https://pub.dev" - source: hosted - version: "4.0.0" - glob: - dependency: "direct main" - description: - name: glob - sha256: c3f1ee72c96f8f78935e18aa8cecced9ab132419e8625dc187e1c2408efc20de - url: "https://pub.dev" - source: hosted - version: "2.1.3" - graphs: - dependency: transitive - description: - name: graphs - sha256: "741bbf84165310a68ff28fe9e727332eef1407342fca52759cb21ad8177bb8d0" - url: "https://pub.dev" - source: hosted - version: "2.3.2" - http: - dependency: "direct dev" - description: - name: http - sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" - url: "https://pub.dev" - source: hosted - version: "1.6.0" - http_multi_server: - dependency: transitive - description: - name: http_multi_server - sha256: aa6199f908078bb1c5efb8d8638d4ae191aac11b311132c3ef48ce352fb52ef8 - url: "https://pub.dev" - source: hosted - version: "3.2.2" - http_parser: - dependency: transitive - description: - name: http_parser - sha256: "178d74305e7866013777bab2c3d8726205dc5a4dd935297175b19a23a2e66571" - url: "https://pub.dev" - source: hosted - version: "4.1.2" - io: - dependency: transitive - description: - name: io - sha256: dfd5a80599cf0165756e3181807ed3e77daf6dd4137caaad72d0b7931597650b - url: "https://pub.dev" - source: hosted - version: "1.0.5" - json_annotation: - dependency: "direct main" - description: - name: json_annotation - sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" - url: "https://pub.dev" - source: hosted - version: "4.9.0" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - sha256: c5b2ee75210a0f263c6c7b9eeea80553dbae96ea1bf57f02484e806a3ffdffa3 - url: "https://pub.dev" - source: hosted - version: "6.11.2" - logging: - dependency: transitive - description: - name: logging - sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 - url: "https://pub.dev" - source: hosted - version: "1.3.0" - mason_logger: - dependency: "direct main" - description: - name: mason_logger - sha256: "6d5a989ff41157915cb5162ed6e41196d5e31b070d2f86e1c2edf216996a158c" - url: "https://pub.dev" - source: hosted - version: "0.3.3" - matcher: - dependency: transitive - description: - name: matcher - sha256: "12956d0ad8390bbcc63ca2e1469c0619946ccb52809807067a7020d57e647aa6" - url: "https://pub.dev" - source: hosted - version: "0.12.18" - meta: - dependency: transitive - description: - name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" - url: "https://pub.dev" - source: hosted - version: "1.17.0" - mime: - dependency: transitive - description: - name: mime - sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" - url: "https://pub.dev" - source: hosted - version: "2.0.0" - node_preamble: - dependency: transitive - description: - name: node_preamble - sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db" - url: "https://pub.dev" - source: hosted - version: "2.0.2" - package_config: - dependency: transitive - description: - name: package_config - sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc - url: "https://pub.dev" - source: hosted - version: "2.2.0" - path: - dependency: "direct main" - description: - name: path - sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" - url: "https://pub.dev" - source: hosted - version: "1.9.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" - url: "https://pub.dev" - source: hosted - version: "7.0.1" - platform: - dependency: transitive - description: - name: platform - sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" - url: "https://pub.dev" - source: hosted - version: "3.1.6" - pool: - dependency: transitive - description: - name: pool - sha256: "978783255c543aa3586a1b3c21f6e9d720eb315376a915872c61ef8b5c20177d" - url: "https://pub.dev" - source: hosted - version: "1.5.2" - process: - dependency: transitive - description: - name: process - sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 - url: "https://pub.dev" - source: hosted - version: "5.0.5" - pub_semver: - dependency: transitive - description: - name: pub_semver - sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" - url: "https://pub.dev" - source: hosted - version: "2.2.0" - pub_updater: - dependency: "direct main" - description: - name: pub_updater - sha256: "739a0161d73a6974c0675b864fb0cf5147305f7b077b7f03a58fa7a9ab3e7e7d" - url: "https://pub.dev" - source: hosted - version: "0.5.0" - pubspec_parse: - dependency: transitive - description: - name: pubspec_parse - sha256: "0560ba233314abbed0a48a2956f7f022cce7c3e1e73df540277da7544cad4082" - url: "https://pub.dev" - source: hosted - version: "1.5.0" - shelf: - dependency: transitive - description: - name: shelf - sha256: e7dd780a7ffb623c57850b33f43309312fc863fb6aa3d276a754bb299839ef12 - url: "https://pub.dev" - source: hosted - version: "1.4.2" - shelf_packages_handler: - dependency: transitive - description: - name: shelf_packages_handler - sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e" - url: "https://pub.dev" - source: hosted - version: "3.0.2" - shelf_static: - dependency: transitive - description: - name: shelf_static - sha256: c87c3875f91262785dade62d135760c2c69cb217ac759485334c5857ad89f6e3 - url: "https://pub.dev" - source: hosted - version: "1.1.3" - shelf_web_socket: - dependency: transitive - description: - name: shelf_web_socket - sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" - url: "https://pub.dev" - source: hosted - version: "3.0.0" - source_gen: - dependency: transitive - description: - name: source_gen - sha256: "07b277b67e0096c45196cbddddf2d8c6ffc49342e88bf31d460ce04605ddac75" - url: "https://pub.dev" - source: hosted - version: "4.1.1" - source_helper: - dependency: transitive - description: - name: source_helper - sha256: "6a3c6cc82073a8797f8c4dc4572146114a39652851c157db37e964d9c7038723" - url: "https://pub.dev" - source: hosted - version: "1.3.8" - source_map_stack_trace: - dependency: transitive - description: - name: source_map_stack_trace - sha256: c0713a43e323c3302c2abe2a1cc89aa057a387101ebd280371d6a6c9fa68516b - url: "https://pub.dev" - source: hosted - version: "2.1.2" - source_maps: - dependency: transitive - description: - name: source_maps - sha256: "190222579a448b03896e0ca6eca5998fa810fda630c1d65e2f78b3f638f54812" - url: "https://pub.dev" - source: hosted - version: "0.10.13" - source_span: - dependency: "direct main" - description: - name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" - url: "https://pub.dev" - source: hosted - version: "1.10.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" - url: "https://pub.dev" - source: hosted - version: "1.12.1" - stream_channel: - dependency: transitive - description: - name: stream_channel - sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" - url: "https://pub.dev" - source: hosted - version: "2.1.4" - stream_transform: - dependency: transitive - description: - name: stream_transform - sha256: ad47125e588cfd37a9a7f86c7d6356dde8dfe89d071d293f80ca9e9273a33871 - url: "https://pub.dev" - source: hosted - version: "2.1.1" - string_scanner: - dependency: transitive - description: - name: string_scanner - sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" - url: "https://pub.dev" - source: hosted - version: "1.4.1" - term_glyph: - dependency: transitive - description: - name: term_glyph - sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" - url: "https://pub.dev" - source: hosted - version: "1.2.2" - test: - dependency: "direct dev" - description: - name: test - sha256: "77cc98ea27006c84e71a7356cf3daf9ddbde2d91d84f77dbfe64cf0e4d9611ae" - url: "https://pub.dev" - source: hosted - version: "1.28.0" - test_api: - dependency: transitive - description: - name: test_api - sha256: "19a78f63e83d3a61f00826d09bc2f60e191bf3504183c001262be6ac75589fb8" - url: "https://pub.dev" - source: hosted - version: "0.7.8" - test_core: - dependency: transitive - description: - name: test_core - sha256: f1072617a6657e5fc09662e721307f7fb009b4ed89b19f47175d11d5254a62d4 - url: "https://pub.dev" - source: hosted - version: "0.6.14" - toml: - dependency: "direct dev" - description: - name: toml - sha256: "35cd2a1351c14bd213f130f8efcbd3e0c18181bff0c8ca7a08f6822a2bede786" - url: "https://pub.dev" - source: hosted - version: "0.17.0" - typed_data: - dependency: transitive - description: - name: typed_data - sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 - url: "https://pub.dev" - source: hosted - version: "1.4.0" - very_good_analysis: - dependency: "direct dev" - description: - name: very_good_analysis - sha256: "96245839dbcc45dfab1af5fa551603b5c7a282028a64746c19c547d21a7f1e3a" - url: "https://pub.dev" - source: hosted - version: "10.0.0" - vm_service: - dependency: transitive - description: - name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" - url: "https://pub.dev" - source: hosted - version: "15.0.2" - watcher: - dependency: transitive - description: - name: watcher - sha256: "592ab6e2892f67760543fb712ff0177f4ec76c031f02f5b4ff8d3fc5eb9fb61a" - url: "https://pub.dev" - source: hosted - version: "1.1.4" - web: - dependency: transitive - description: - name: web - sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" - url: "https://pub.dev" - source: hosted - version: "1.1.1" - web_socket: - dependency: transitive - description: - name: web_socket - sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" - url: "https://pub.dev" - source: hosted - version: "1.0.1" - web_socket_channel: - dependency: transitive - description: - name: web_socket_channel - sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 - url: "https://pub.dev" - source: hosted - version: "3.0.3" - webkit_inspection_protocol: - dependency: transitive - description: - name: webkit_inspection_protocol - sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572" - url: "https://pub.dev" - source: hosted - version: "1.2.1" - win32: - dependency: transitive - description: - name: win32 - sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e - url: "https://pub.dev" - source: hosted - version: "5.15.0" - yaml: - dependency: "direct main" - description: - name: yaml - sha256: b9da305ac7c39faa3f030eccd175340f968459dae4af175130b3fc47e40d76ce - url: "https://pub.dev" - source: hosted - version: "3.1.3" - yaml_edit: - dependency: "direct main" - description: - name: yaml_edit - sha256: fb38626579fb345ad00e674e2af3a5c9b0cc4b9bfb8fd7f7ff322c7c9e62aef5 - url: "https://pub.dev" - source: hosted - version: "2.2.2" -sdks: - dart: ">=3.10.0 <4.0.0" From 8d50a87cac161db3a80d9c788184a9a591e51a0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:01:34 +0100 Subject: [PATCH 07/11] style: apply dart format to codebase Run dart format to ensure consistent code style across the project. --- example/lib/vulnerabilities.dart | 2 -- lib/src/analyzers/code/code_analyzer.dart | 16 +++++----- .../cryptography/avoid_weak_hashing.dart | 7 ++--- .../rules/network/prefer_https_over_http.dart | 1 - .../secrets/avoid_hardcoded_secrets.dart | 16 +++++----- lib/src/analyzers/utils/analyzer_result.dart | 15 ++------- lib/src/configuration/config_manager.dart | 5 +-- lib/src/configuration/shield_config.dart | 31 ++++++------------- lib/src/core/analyzer_factory.dart | 14 ++++----- test/integration/integration_test.dart | 6 ++-- .../analyzers/utils/shannon_entropy_test.dart | 2 +- tool/generate_rules.dart | 7 +++-- tool/utils/regex_sanitizer.dart | 4 +-- 13 files changed, 47 insertions(+), 79 deletions(-) diff --git a/example/lib/vulnerabilities.dart b/example/lib/vulnerabilities.dart index c0e566e..ba2b9eb 100644 --- a/example/lib/vulnerabilities.dart +++ b/example/lib/vulnerabilities.dart @@ -41,9 +41,7 @@ void weakHashes() { // dart_shield rule: prefer-secure-random void unsecureRandom() { - // Violation: Using `Random` instead of `Random.secure()` final random = Random.secure(); - } diff --git a/lib/src/analyzers/code/code_analyzer.dart b/lib/src/analyzers/code/code_analyzer.dart index c5b68ad..981b6f7 100644 --- a/lib/src/analyzers/code/code_analyzer.dart +++ b/lib/src/analyzers/code/code_analyzer.dart @@ -12,10 +12,8 @@ import 'package:dart_shield/src/domain/exceptions.dart'; import 'package:path/path.dart' as path; class CodeAnalyzer implements Analyzer { - CodeAnalyzer({ - required this.analyzedPaths, - String? rootFolder, - }) : rootFolder = rootFolder ?? Directory.current.path; + CodeAnalyzer({required this.analyzedPaths, String? rootFolder}) + : rootFolder = rootFolder ?? Directory.current.path; final List analyzedPaths; final String rootFolder; @@ -36,11 +34,11 @@ class CodeAnalyzer implements Analyzer { ProcessResult result; try { - result = await Process.run( - 'dart', - ['analyze', '--format=json', target], - runInShell: true, - ); + result = await Process.run('dart', [ + 'analyze', + '--format=json', + target, + ], runInShell: true); } on ProcessException catch (e) { throw ShieldProcessException( 'Failed to execute dart analyze.', diff --git a/lib/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart b/lib/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart index 4b835b8..c90a76e 100644 --- a/lib/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart +++ b/lib/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart @@ -7,10 +7,7 @@ import 'package:analyzer/error/error.dart'; class AvoidWeakHashing extends AnalysisRule { AvoidWeakHashing() - : super( - name: 'avoid_weak_hashing', - description: 'Some description', - ); + : super(name: 'avoid_weak_hashing', description: 'Some description'); static const LintCode code = LintCode( 'avoid_weak_hashing', 'Using weak hashing algorithms can lead to security vulnerabilities.', @@ -65,7 +62,7 @@ class _WeakCryptoHashingVisitor extends SimpleAstVisitor { @override void visitAssignmentExpression(AssignmentExpression node) { if (_isAssignmentWeakHash(node)) { - rule.reportAtNode(node); + rule.reportAtNode(node); } } diff --git a/lib/src/analyzers/code/rules/network/prefer_https_over_http.dart b/lib/src/analyzers/code/rules/network/prefer_https_over_http.dart index b34a9a9..15ee18d 100644 --- a/lib/src/analyzers/code/rules/network/prefer_https_over_http.dart +++ b/lib/src/analyzers/code/rules/network/prefer_https_over_http.dart @@ -36,7 +36,6 @@ class PreferHttpsOverHttp extends AnalysisRule { } class _PreferHttpsOverHttpVisitor extends SimpleAstVisitor { - _PreferHttpsOverHttpVisitor({required this.rule, required this.context}); final AnalysisRule rule; final RuleContext context; diff --git a/lib/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart b/lib/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart index 8300f0c..8f88b83 100644 --- a/lib/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart +++ b/lib/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart @@ -9,10 +9,10 @@ import 'package:dart_shield/src/analyzers/utils/shannon_entropy.dart'; class AvoidHardcodedSecrets extends AnalysisRule { AvoidHardcodedSecrets() - : super( - name: 'avoid_hardcoded_secrets', - description: 'Detects hardcoded secrets, API keys, and tokens.', - ); + : super( + name: 'avoid_hardcoded_secrets', + description: 'Detects hardcoded secrets, API keys, and tokens.', + ); static const LintCode code = LintCode( 'avoid_hardcoded_secrets', @@ -56,10 +56,10 @@ class _Visitor extends SimpleAstVisitor { // If the rule has keywords, at least one must exist in value OR context. if (secretRule.keywords.isNotEmpty) { final valueLower = value.toLowerCase(); - final hasKeywordInValue = - secretRule.keywords.any(valueLower.contains); - final hasKeywordInContext = - secretRule.keywords.any(contextString.contains); + final hasKeywordInValue = secretRule.keywords.any(valueLower.contains); + final hasKeywordInContext = secretRule.keywords.any( + contextString.contains, + ); if (!hasKeywordInValue && !hasKeywordInContext) { continue; diff --git a/lib/src/analyzers/utils/analyzer_result.dart b/lib/src/analyzers/utils/analyzer_result.dart index e146b05..ea515e8 100644 --- a/lib/src/analyzers/utils/analyzer_result.dart +++ b/lib/src/analyzers/utils/analyzer_result.dart @@ -1,9 +1,6 @@ /// Root object representing the result of `dart analyze --format=json` class AnalyzeResult { - AnalyzeResult({ - required this.version, - required this.diagnostics, - }); + AnalyzeResult({required this.version, required this.diagnostics}); factory AnalyzeResult.fromJson(Map json) { return AnalyzeResult( @@ -53,10 +50,7 @@ class Diagnostic { } class Location { - Location({ - required this.file, - required this.range, - }); + Location({required this.file, required this.range}); factory Location.fromJson(Map json) { return Location( @@ -70,10 +64,7 @@ class Location { } class SourceRange { - SourceRange({ - required this.start, - required this.end, - }); + SourceRange({required this.start, required this.end}); factory SourceRange.fromJson(Map json) { return SourceRange( diff --git a/lib/src/configuration/config_manager.dart b/lib/src/configuration/config_manager.dart index ad6441b..73b726f 100644 --- a/lib/src/configuration/config_manager.dart +++ b/lib/src/configuration/config_manager.dart @@ -78,9 +78,6 @@ dart_shield: '''; static const Map _defaultShieldConfig = { - 'analyzers': { - 'code': true, - 'deps': true, - }, + 'analyzers': {'code': true, 'deps': true}, }; } diff --git a/lib/src/configuration/shield_config.dart b/lib/src/configuration/shield_config.dart index 7441bb2..f97a7e1 100644 --- a/lib/src/configuration/shield_config.dart +++ b/lib/src/configuration/shield_config.dart @@ -6,15 +6,9 @@ import 'package:json_annotation/json_annotation.dart'; part 'shield_config.g.dart'; -@JsonSerializable( - anyMap: true, - checked: true, - disallowUnrecognizedKeys: true, -) +@JsonSerializable(anyMap: true, checked: true, disallowUnrecognizedKeys: true) class ShieldConfig { - const ShieldConfig({ - this.analyzers = const ShieldAnalyzersConfig(), - }); + const ShieldConfig({this.analyzers = const ShieldAnalyzersConfig()}); factory ShieldConfig.fromJson(Map map) => _$ShieldConfigFromJson(map); @@ -29,17 +23,13 @@ class ShieldConfig { if (content.trim().isEmpty) return const ShieldConfig(); try { - return checkedYamlDecode( - content, - (m) { - if (m != null && m['dart_shield'] is Map) { - return ShieldConfig.fromJson(m['dart_shield'] as Map); - } + return checkedYamlDecode(content, (m) { + if (m != null && m['dart_shield'] is Map) { + return ShieldConfig.fromJson(m['dart_shield'] as Map); + } - return const ShieldConfig(); - }, - sourceUrl: file.uri, - ); + return const ShieldConfig(); + }, sourceUrl: file.uri); } on ParsedYamlException catch (e) { throw ConfigException( 'Failed to parse analysis_options.yaml', @@ -51,10 +41,7 @@ class ShieldConfig { @JsonSerializable(anyMap: true, checked: true) class ShieldAnalyzersConfig { - const ShieldAnalyzersConfig({ - this.code = true, - this.deps = true, - }); + const ShieldAnalyzersConfig({this.code = true, this.deps = true}); factory ShieldAnalyzersConfig.fromJson(Map map) => _$ShieldAnalyzersConfigFromJson(map); diff --git a/lib/src/core/analyzer_factory.dart b/lib/src/core/analyzer_factory.dart index 1b5d3f7..9fd7e66 100644 --- a/lib/src/core/analyzer_factory.dart +++ b/lib/src/core/analyzer_factory.dart @@ -20,9 +20,7 @@ class AnalyzerFactory { List only = const [], List exclude = const [], }) { - final configEnabled = { - 'code': config.analyzers.code, - }; + final configEnabled = {'code': config.analyzers.code}; final selected = []; @@ -39,11 +37,11 @@ class AnalyzerFactory { } static bool _shouldRun( - String id, - bool isEnabledInYaml, - List only, - List exclude, - ) { + String id, + bool isEnabledInYaml, + List only, + List exclude, + ) { if (exclude.contains(id)) return false; if (only.isNotEmpty) return only.contains(id); return isEnabledInYaml; diff --git a/test/integration/integration_test.dart b/test/integration/integration_test.dart index 6132082..c5257c9 100644 --- a/test/integration/integration_test.dart +++ b/test/integration/integration_test.dart @@ -28,7 +28,8 @@ void main() { final suffix = 'ABCDEFGHIJKLMNOP'; final secret = prefix + suffix; - final codeWithSecret = ''' + final codeWithSecret = + ''' $originalContent void injectedSecret() { @@ -66,7 +67,8 @@ void injectedSecret() { expect( awsMatch, isTrue, - reason: 'Should detect AWS specific rule. Found: ${secretIssues.map((e) => e.message)}', + reason: + 'Should detect AWS specific rule. Found: ${secretIssues.map((e) => e.message)}', ); }); }); diff --git a/test/src/analyzers/utils/shannon_entropy_test.dart b/test/src/analyzers/utils/shannon_entropy_test.dart index b3cca21..22b7738 100644 --- a/test/src/analyzers/utils/shannon_entropy_test.dart +++ b/test/src/analyzers/utils/shannon_entropy_test.dart @@ -34,7 +34,7 @@ void main() { test('calculates entropy for typical API key examples', () { // Low entropy string final low = ShannonEntropy.calculate('password123'); - + // High entropy string (simulated API key) final high = ShannonEntropy.calculate('7Fz92xK1qM4bJ8vR'); diff --git a/tool/generate_rules.dart b/tool/generate_rules.dart index b0d09c3..332f673 100644 --- a/tool/generate_rules.dart +++ b/tool/generate_rules.dart @@ -70,7 +70,8 @@ Future main() async { } print( - 'Processed ${rulesList.length} rules. Valid: ${validRules.length}. Skipped: $skippedCount.'); + 'Processed ${rulesList.length} rules. Valid: ${validRules.length}. Skipped: $skippedCount.', + ); final jsonContent = const JsonEncoder.withIndent(' ').convert(validRules); @@ -83,8 +84,8 @@ Future main() async { final dartFile = File('lib/src/generated/fallback_rules.dart'); final dartContent = '// GENERATED CODE - DO NOT MODIFY BY HAND\n' - '// Generated by tool/generate_rules.dart\n\n' - "const String fallbackRulesJson = r'''\n" + + '// Generated by tool/generate_rules.dart\n\n' + "const String fallbackRulesJson = r'''\n" + jsonContent + "\n''';\n"; diff --git a/tool/utils/regex_sanitizer.dart b/tool/utils/regex_sanitizer.dart index 1500b45..23cd584 100644 --- a/tool/utils/regex_sanitizer.dart +++ b/tool/utils/regex_sanitizer.dart @@ -69,12 +69,12 @@ class RegexSanitizer { pattern = pattern.replaceAll('(?s)', ''); // TODO: Return dotAll flag if we update SecretRule to support it. } - + // 6. Handle Scoped Dot-all `(?s:...)` if (pattern.contains('(?s:')) { pattern = pattern.replaceAll('(?s:', '(?:'); } - + // 7. Clean up potential double-escapes or leftover artifacts if necessary. // (Currently not needed for standard Gitleaks patterns). From d9857a144ca6933cbb666784ef561a0db3ac15e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:05:37 +0100 Subject: [PATCH 08/11] ci: raise Dart SDK version --- .github/workflows/dart.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index dd27154..b733962 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v5 - uses: dart-lang/setup-dart@v1.6.5 with: - sdk: '3.9.0' + sdk: '3.10.0' - name: โ›“ Install Dependencies run: dart pub get From 11fbce9463b2895bb219088b00b114aa0cadee80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:25:45 +0100 Subject: [PATCH 09/11] style: formatting --- lib/src/analyzers/code/code_analyzer.dart | 6 ++- lib/src/configuration/shield_config.dart | 4 +- lib/src/configuration/shield_config.g.dart | 7 ---- lib/src/core/shield_runner.dart | 7 ++-- lib/src/domain/exceptions.dart | 5 ++- lib/src/reporters/json_reporter.dart | 7 +++- test/integration/integration_test.dart | 12 +++--- .../cryptography/avoid_weak_hashing_test.dart | 20 +++++---- .../prefer_secure_random_test.dart | 16 ++++---- .../network/avoid_hardcoded_urls_test.dart | 22 +++++----- .../network/prefer_https_over_http_test.dart | 24 ++++++----- .../secrets/avoid_hardcoded_secrets_test.dart | 32 ++++++++------- .../code/rules/secrets/secret_rule_test.dart | 2 +- .../src/configuration/shield_config_test.dart | 4 +- test/tool/utils/regex_sanitizer_test.dart | 4 +- tool/generate_rules.dart | 12 +++--- tool/utils/regex_sanitizer.dart | 41 +++++++++---------- 17 files changed, 118 insertions(+), 107 deletions(-) diff --git a/lib/src/analyzers/code/code_analyzer.dart b/lib/src/analyzers/code/code_analyzer.dart index 981b6f7..9b93cd7 100644 --- a/lib/src/analyzers/code/code_analyzer.dart +++ b/lib/src/analyzers/code/code_analyzer.dart @@ -42,7 +42,8 @@ class CodeAnalyzer implements Analyzer { } on ProcessException catch (e) { throw ShieldProcessException( 'Failed to execute dart analyze.', - 'Ensure the Dart SDK is installed and accessible in your PATH.\nOriginal error: ${e.message}', + 'Ensure the Dart SDK is installed and accessible in your PATH.\n' + 'Original error: ${e.message}', ); } @@ -57,7 +58,8 @@ class CodeAnalyzer implements Analyzer { if (jsonString == null) { throw ShieldProcessException( 'dart analyze did not return valid JSON output.', - 'This usually means the analysis command crashed or encountered a fatal error.\nOutput: $output', + 'This usually means the analysis command crashed or ' + 'encountered a fatal error.\nOutput: $output', ); } diff --git a/lib/src/configuration/shield_config.dart b/lib/src/configuration/shield_config.dart index f97a7e1..0aba789 100644 --- a/lib/src/configuration/shield_config.dart +++ b/lib/src/configuration/shield_config.dart @@ -6,7 +6,7 @@ import 'package:json_annotation/json_annotation.dart'; part 'shield_config.g.dart'; -@JsonSerializable(anyMap: true, checked: true, disallowUnrecognizedKeys: true) +@JsonSerializable(anyMap: true, checked: true, disallowUnrecognizedKeys: true, createToJson: false) class ShieldConfig { const ShieldConfig({this.analyzers = const ShieldAnalyzersConfig()}); @@ -39,7 +39,7 @@ class ShieldConfig { } } -@JsonSerializable(anyMap: true, checked: true) +@JsonSerializable(anyMap: true, checked: true, createToJson: false) class ShieldAnalyzersConfig { const ShieldAnalyzersConfig({this.code = true, this.deps = true}); diff --git a/lib/src/configuration/shield_config.g.dart b/lib/src/configuration/shield_config.g.dart index a1bd862..9bcb68a 100644 --- a/lib/src/configuration/shield_config.g.dart +++ b/lib/src/configuration/shield_config.g.dart @@ -20,9 +20,6 @@ ShieldConfig _$ShieldConfigFromJson(Map json) => return val; }); -Map _$ShieldConfigToJson(ShieldConfig instance) => - {'analyzers': instance.analyzers}; - ShieldAnalyzersConfig _$ShieldAnalyzersConfigFromJson(Map json) => $checkedCreate('ShieldAnalyzersConfig', json, ($checkedConvert) { final val = ShieldAnalyzersConfig( @@ -31,7 +28,3 @@ ShieldAnalyzersConfig _$ShieldAnalyzersConfigFromJson(Map json) => ); return val; }); - -Map _$ShieldAnalyzersConfigToJson( - ShieldAnalyzersConfig instance, -) => {'code': instance.code, 'deps': instance.deps}; diff --git a/lib/src/core/shield_runner.dart b/lib/src/core/shield_runner.dart index ccdd44a..59663b7 100644 --- a/lib/src/core/shield_runner.dart +++ b/lib/src/core/shield_runner.dart @@ -55,9 +55,10 @@ class ShieldRunner { _logger.err(e.toString()); if (e is ConfigException) return ExitCode.config.code; return ExitCode.software.code; - } catch (e, stack) { - _logger.err('Unexpected error: $e'); - _logger.detail('$stack'); + } on Object catch (e, stack) { + _logger + ..err('Unexpected error: $e') + ..detail('$stack'); return ExitCode.software.code; } } diff --git a/lib/src/domain/exceptions.dart b/lib/src/domain/exceptions.dart index 2df69bd..1cd745f 100644 --- a/lib/src/domain/exceptions.dart +++ b/lib/src/domain/exceptions.dart @@ -1,7 +1,8 @@ /// Base class for all exceptions thrown by dart_shield. /// -/// These exceptions represent "expected" failure modes (configuration errors, environment issues) that should be reported cleanly to the user, -/// as opposed to unexpected bugs (StateError, ArgumentError) which are crashes. +/// These exceptions represent "expected" failure modes (configuration errors, +/// environment issues) that should be reported cleanly to the user, as opposed +/// to unexpected bugs (StateError, ArgumentError) which are crashes. abstract class ShieldException implements Exception { const ShieldException(this.message, [this.suggestion]); diff --git a/lib/src/reporters/json_reporter.dart b/lib/src/reporters/json_reporter.dart index 037b154..01bf58f 100644 --- a/lib/src/reporters/json_reporter.dart +++ b/lib/src/reporters/json_reporter.dart @@ -3,11 +3,14 @@ import 'dart:io'; import 'package:dart_shield/src/domain/analyzer_result.dart'; import 'package:dart_shield/src/reporters/reporter.dart'; +import 'package:mason_logger/mason_logger.dart'; class JsonReporter implements Reporter { - JsonReporter({this.outputPath = 'shield_report.json'}); + JsonReporter({this.outputPath = 'shield_report.json', Logger? logger}) + : _logger = logger ?? Logger(); final String outputPath; + final Logger _logger; @override String get id => 'json'; @@ -37,6 +40,6 @@ class JsonReporter implements Reporter { await file.writeAsString(jsonStr); // Small feedback so the user knows the file was created - print('๐Ÿ’พ JSON report generated at: ${file.absolute.path}'); + _logger.info('๐Ÿ’พ JSON report generated at: ${file.absolute.path}'); } } diff --git a/test/integration/integration_test.dart b/test/integration/integration_test.dart index c5257c9..5044c2e 100644 --- a/test/integration/integration_test.dart +++ b/test/integration/integration_test.dart @@ -24,9 +24,9 @@ void main() { // on this test file itself. // Pattern requires [A-Z2-7]{16}. Also high entropy (> 3.0). // ABCDEFGHIJKLMNOP uses only [A-Z], which is valid. And has max entropy. - final prefix = 'AKIA'; - final suffix = 'ABCDEFGHIJKLMNOP'; - final secret = prefix + suffix; + const prefix = 'AKIA'; + const suffix = 'ABCDEFGHIJKLMNOP'; + const secret = prefix + suffix; final codeWithSecret = ''' @@ -59,7 +59,7 @@ void injectedSecret() { reason: 'Should have detected the injected AWS key', ); - // The generic rule might ALSO match, so we just check if ONE of them is AWS. + // The generic rule might ALSO match, so we just check if ONE is AWS. final awsMatch = secretIssues.any( (i) => i.message.contains('AWS credentials'), ); @@ -67,8 +67,8 @@ void injectedSecret() { expect( awsMatch, isTrue, - reason: - 'Should detect AWS specific rule. Found: ${secretIssues.map((e) => e.message)}', + reason: 'Should detect AWS specific rule. ' + 'Found: ${secretIssues.map((e) => e.message)}', ); }); }); diff --git a/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart b/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart index 7243c50..60cb83e 100644 --- a/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart +++ b/test/src/analyzers/code/rules/cryptography/avoid_weak_hashing_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:dart_shield/src/analyzers/code/rules/cryptography/avoid_weak_hashing.dart'; @@ -19,7 +21,7 @@ class AvoidWeakHashingTest extends AnalysisRuleTest { Registry.ruleRegistry.registerLintRule(AvoidWeakHashing()); // Add stub for crypto package - newPackage('crypto')..addFile('lib/crypto.dart', r''' + newPackage('crypto').addFile('lib/crypto.dart', ''' abstract class Hash { List convert(List data); } @@ -45,7 +47,7 @@ class _Sha256 implements Hash { Future test_md5Convert_reports() async { await assertDiagnostics( - r''' + ''' import 'package:crypto/crypto.dart'; void f() { final hash = md5.convert([1, 2, 3]); @@ -57,7 +59,7 @@ void f() { Future test_sha1Convert_reports() async { await assertDiagnostics( - r''' + ''' import 'package:crypto/crypto.dart'; void f() { final hash = sha1.convert([1, 2, 3]); @@ -68,7 +70,7 @@ void f() { } Future test_sha256Convert_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' import 'package:crypto/crypto.dart'; void f() { final hash = sha256.convert([1, 2, 3]); @@ -78,7 +80,7 @@ void f() { Future test_md5Assignment_reports() async { await assertDiagnostics( - r''' + ''' import 'package:crypto/crypto.dart'; void f() { Hash hasher; @@ -91,7 +93,7 @@ void f() { Future test_sha1Assignment_reports() async { await assertDiagnostics( - r''' + ''' import 'package:crypto/crypto.dart'; void f() { Hash hasher; @@ -103,7 +105,7 @@ void f() { } Future test_sha256Assignment_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' import 'package:crypto/crypto.dart'; void f() { Hash hasher; @@ -114,7 +116,7 @@ void f() { Future test_md5InExpression_reports() async { await assertDiagnostics( - r''' + ''' import 'package:crypto/crypto.dart'; void f() { final result = md5.convert([1]).toString(); @@ -126,7 +128,7 @@ void f() { Future test_unrelatedIdentifier_noReport() async { // Identifiers named md5 or sha1 that are not from crypto package - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final md5 = 'some string'; print(md5); diff --git a/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart b/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart index b80059a..573c760 100644 --- a/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart +++ b/test/src/analyzers/code/rules/cryptography/prefer_secure_random_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:dart_shield/src/analyzers/code/rules/cryptography/prefer_secure_random.dart'; @@ -22,7 +24,7 @@ class PreferSecureRandomTest extends AnalysisRuleTest { Future test_randomConstructor_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; void f() { final rng = Random(); @@ -34,7 +36,7 @@ void f() { Future test_randomWithSeed_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; void f() { final rng = Random(42); @@ -46,7 +48,7 @@ void f() { Future test_randomInClassField_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; class MyClass { final rng = Random(); @@ -58,7 +60,7 @@ class MyClass { Future test_randomInFunction_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; int getRandomNumber() { return Random().nextInt(100); @@ -70,7 +72,7 @@ int getRandomNumber() { Future test_multipleRandomInstances_reportsEach() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; void f() { final rng1 = Random(); @@ -83,7 +85,7 @@ void f() { Future test_randomWithVariableSeed_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; void f(int seed) { final rng = Random(seed); @@ -95,7 +97,7 @@ void f(int seed) { Future test_randomWithTimestampSeed_reports() async { await assertDiagnostics( - r''' + ''' import 'dart:math'; void f() { final rng = Random(DateTime.now().millisecondsSinceEpoch); diff --git a/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart b/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart index cb9aaa6..d982c10 100644 --- a/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart +++ b/test/src/analyzers/code/rules/network/avoid_hardcoded_urls_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:dart_shield/src/analyzers/code/rules/network/avoid_harcoded_urls.dart'; @@ -22,7 +24,7 @@ class AvoidHardcodedUrlsTest extends AnalysisRuleTest { Future test_httpsUrl_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'https://api.example.com/v1'; } @@ -33,7 +35,7 @@ void f() { Future test_httpUrl_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://api.example.com'; } @@ -43,7 +45,7 @@ void f() { } Future test_emptyString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final url = ''; } @@ -51,7 +53,7 @@ void f() { } Future test_nonUrlString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final text = 'hello world'; } @@ -60,7 +62,7 @@ void f() { Future test_localhostUrl_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://localhost:8080/api'; } @@ -71,7 +73,7 @@ void f() { Future test_httpsWithQueryParams_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'https://api.example.com/search?q=test'; } @@ -82,7 +84,7 @@ void f() { Future test_multipleUrls_reportsEach() async { await assertDiagnostics( - r''' + ''' void f() { final url1 = 'https://api.example.com'; final url2 = 'http://other.example.com'; @@ -94,7 +96,7 @@ void f() { Future test_urlInClassField_reports() async { await assertDiagnostics( - r''' + ''' class Config { final baseUrl = 'https://api.example.com'; } @@ -105,7 +107,7 @@ class Config { Future test_urlInConstant_reports() async { await assertDiagnostics( - r''' + ''' const apiUrl = 'https://api.example.com/v1'; ''', [lint(15, 28)], @@ -113,7 +115,7 @@ const apiUrl = 'https://api.example.com/v1'; } Future test_partialUrl_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final path = '/api/v1/users'; } diff --git a/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart b/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart index 8c95e13..17e2d9c 100644 --- a/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart +++ b/test/src/analyzers/code/rules/network/prefer_https_over_http_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:dart_shield/src/analyzers/code/rules/network/prefer_https_over_http.dart'; @@ -22,7 +24,7 @@ class PreferHttpsOverHttpTest extends AnalysisRuleTest { Future test_httpStringLiteral_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://example.com'; } @@ -32,7 +34,7 @@ void f() { } Future test_httpsStringLiteral_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final url = 'https://example.com'; } @@ -41,7 +43,7 @@ void f() { Future test_httpWithPath_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://example.com/api/v1'; } @@ -51,7 +53,7 @@ void f() { } Future test_emptyString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final url = ''; } @@ -59,7 +61,7 @@ void f() { } Future test_nonUrlString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final text = 'hello world'; } @@ -68,7 +70,7 @@ void f() { Future test_httpInVariable_reports() async { await assertDiagnostics( - r''' + ''' void f() { final config = 'http://api.example.com'; } @@ -79,7 +81,7 @@ void f() { Future test_httpLocalhost_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://localhost:8080'; } @@ -90,7 +92,7 @@ void f() { Future test_httpWithPort_reports() async { await assertDiagnostics( - r''' + ''' void f() { final url = 'http://example.com:3000/api'; } @@ -101,7 +103,7 @@ void f() { Future test_httpInList_reports() async { await assertDiagnostics( - r''' + ''' void f() { final urls = [ 'http://example.com', @@ -114,7 +116,7 @@ void f() { Future test_httpInMap_reports() async { await assertDiagnostics( - r''' + ''' void f() { final config = { 'url': 'http://example.com', @@ -127,7 +129,7 @@ void f() { Future test_multipleHttpUrls_reportsEach() async { await assertDiagnostics( - r''' + ''' void f() { final url1 = 'http://example.com'; final url2 = 'http://other.com'; diff --git a/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart b/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart index 686766d..a9b731d 100644 --- a/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart +++ b/test/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets_test.dart @@ -1,3 +1,5 @@ +// ignore_for_file: non_constant_identifier_names + import 'package:analyzer/src/lint/registry.dart'; import 'package:analyzer_testing/analysis_rule/analysis_rule.dart'; import 'package:dart_shield/src/analyzers/code/rules/secrets/avoid_hardcoded_secrets.dart'; @@ -23,7 +25,7 @@ class AvoidHardcodedSecretsTest extends AnalysisRuleTest { Future test_awsAccessKey_reports() async { // AWS access keys start with AKIA and are 20 chars await assertDiagnostics( - r''' + ''' void f() { final key = 'AKIAIOSFODNN7EXAMPLE'; } @@ -34,7 +36,7 @@ void f() { Future test_githubToken_reports() async { await assertDiagnostics( - r''' + ''' void f() { final token = 'ghp_aBcDeFgHiJkLmNoPqRsTuVwXyZ012345'; } @@ -44,7 +46,7 @@ void f() { } Future test_shortString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final s = 'short'; } @@ -52,7 +54,7 @@ void f() { } Future test_lowEntropyString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final s = 'aaaaaaaaaaaaaaaa'; } @@ -60,9 +62,10 @@ void f() { } Future test_contextVariableName_detects() async { - // Tests that apiKey variable name triggers keyword check with Stripe pattern + // Tests that apiKey variable name triggers keyword check with Stripe + // pattern await assertDiagnostics( - r''' + ''' void f() { final apiKey = 'sk_live_abcdefghijklmnop'; } @@ -73,7 +76,7 @@ void f() { Future test_mapKeyContext_detects() async { await assertDiagnostics( - r''' + ''' void f() { final config = { 'apiKey': 'sk_test_1234567890abcdef', @@ -84,13 +87,14 @@ void f() { ); } - // Note: Slack token test removed to avoid GitHub push protection false positives - // The xoxb- pattern is still tested in integration tests with real project scans + // Note: Slack token test removed to avoid GitHub push protection + // false positives. The xoxb- pattern is still tested in integration tests + // with real project scans. Future test_genericApiKeyPattern_reports() async { // Generic API key with sufficient entropy await assertDiagnostics( - r''' + ''' void f() { final apiKey = 'api_key_xK9mN2pL5qR8vW3yZ1aB4cD7eF0gH'; } @@ -100,7 +104,7 @@ void f() { } Future test_regularString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final message = 'Hello, World!'; } @@ -108,7 +112,7 @@ void f() { } Future test_emptyString_noReport() async { - await assertNoDiagnostics(r''' + await assertNoDiagnostics(''' void f() { final s = ''; } @@ -117,7 +121,7 @@ void f() { Future test_namedParameterContext_detects() async { await assertDiagnostics( - r''' + ''' void configure({required String apiKey}) {} void f() { configure(apiKey: 'sk_live_abc123def456ghi789'); @@ -130,7 +134,7 @@ void f() { Future test_jwtToken_reports() async { // JWT tokens have distinctive patterns await assertDiagnostics( - r''' + ''' void f() { final token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c'; } diff --git a/test/src/analyzers/code/rules/secrets/secret_rule_test.dart b/test/src/analyzers/code/rules/secrets/secret_rule_test.dart index 8db2749..df7139a 100644 --- a/test/src/analyzers/code/rules/secrets/secret_rule_test.dart +++ b/test/src/analyzers/code/rules/secrets/secret_rule_test.dart @@ -44,7 +44,7 @@ void main() { description: 'desc', pattern: RegExp('abc', caseSensitive: false, multiLine: true), keywords: ['k'], - minEntropy: 2.0, + minEntropy: 2, ); final json = rule.toJson(); diff --git a/test/src/configuration/shield_config_test.dart b/test/src/configuration/shield_config_test.dart index 1e15a54..5ee47bb 100644 --- a/test/src/configuration/shield_config_test.dart +++ b/test/src/configuration/shield_config_test.dart @@ -49,7 +49,7 @@ dart_shield: analyzers: [ '''); // Broken YAML expect( - () async => await ShieldConfig.load(), + ShieldConfig.load, throwsA(isA()), ); }); @@ -60,7 +60,7 @@ dart_shield: analyzers: "invalid_string" '''); expect( - () async => await ShieldConfig.load(), + ShieldConfig.load, throwsA(isA()), ); }); diff --git a/test/tool/utils/regex_sanitizer_test.dart b/test/tool/utils/regex_sanitizer_test.dart index ca5dcb7..c4184f5 100644 --- a/test/tool/utils/regex_sanitizer_test.dart +++ b/test/tool/utils/regex_sanitizer_test.dart @@ -36,8 +36,8 @@ void main() { }); test('leaves standard regex untouched', () { - final result = RegexSanitizer.sanitize('^abc[0-9]+\$'); - expect(result.pattern, '^abc[0-9]+\$'); + final result = RegexSanitizer.sanitize(r'^abc[0-9]+$'); + expect(result.pattern, r'^abc[0-9]+$'); expect(result.caseSensitive, isTrue); expect(result.multiLine, isFalse); }); diff --git a/tool/generate_rules.dart b/tool/generate_rules.dart index 332f673..f7a569c 100644 --- a/tool/generate_rules.dart +++ b/tool/generate_rules.dart @@ -70,7 +70,8 @@ Future main() async { } print( - 'Processed ${rulesList.length} rules. Valid: ${validRules.length}. Skipped: $skippedCount.', + 'Processed ${rulesList.length} rules. ' + 'Valid: ${validRules.length}. Skipped: $skippedCount.', ); final jsonContent = const JsonEncoder.withIndent(' ').convert(validRules); @@ -82,11 +83,10 @@ Future main() async { // 2. Write Dart file (for embedding) final dartFile = File('lib/src/generated/fallback_rules.dart'); - final dartContent = - '// GENERATED CODE - DO NOT MODIFY BY HAND\n' - '// Generated by tool/generate_rules.dart\n\n' - "const String fallbackRulesJson = r'''\n" + - jsonContent + + final dartContent = '// GENERATED CODE - DO NOT MODIFY BY HAND\n' + '// Generated by tool/generate_rules.dart\n\n' + "const String fallbackRulesJson = r'''\n" + '$jsonContent' "\n''';\n"; await dartFile.writeAsString(dartContent); diff --git a/tool/utils/regex_sanitizer.dart b/tool/utils/regex_sanitizer.dart index 23cd584..65ef237 100644 --- a/tool/utils/regex_sanitizer.dart +++ b/tool/utils/regex_sanitizer.dart @@ -1,10 +1,10 @@ /// Utility to sanitize and adapt Regex patterns from other languages (Go/PCRE) /// to Dart's JavaScript-flavored RegExp engine. /// -/// Dart's [RegExp] is based on JavaScript's regex engine, which is less powerful -/// than PCRE or Go's `regexp` package. This class attempts to bridge the gap by -/// modifying patterns to be compatible, primarily focusing on flags that Dart -/// handles via constructor arguments rather than inline syntax. +/// Dart's [RegExp] is based on JavaScript's regex engine, which is less +/// powerful than PCRE or Go's `regexp` package. This class attempts to bridge +/// the gap by modifying patterns to be compatible, primarily focusing on flags +/// that Dart handles via constructor arguments rather than inline syntax. class RegexSanitizer { /// Sanitizes a raw regex string and determines the necessary Dart flags. /// @@ -17,14 +17,15 @@ class RegexSanitizer { // 1. Handle Global Case Insensitivity `(?i)` // Go/PCRE use `(?i)` at the start (or inline) to enable case insensitivity. - // Dart requires this as a constructor argument: `RegExp(p, caseSensitive: false)`. + // Dart requires this as a constructor argument: + // `RegExp(p, caseSensitive: false)`. // - // Strategy: If `(?i)` is present anywhere, we remove it and set `caseSensitive` - // to false globally. This is a safe approximation: + // Strategy: If `(?i)` is present anywhere, we remove it and set + // `caseSensitive` to false globally. This is a safe approximation: // - Start: `(?i)abc` -> `abc` (caseSensitive: false) [Exact Match] - // - Inline: `abc(?i)def` -> `abcdef` (caseSensitive: false) [Broader Match] - // (Go matches 'abcDEF', Dart matches 'ABCDEF'. This increases recall but - // might slightly increase false positives, which is acceptable for secret scanning). + // - Inline: `abc(?i)def` -> `abcdef` (caseSensitive: false) [Broader] + // (Go matches 'abcDEF', Dart matches 'ABCDEF'. This increases recall + // but might slightly increase false positives, acceptable for scans). if (pattern.contains('(?i)')) { caseSensitive = false; pattern = pattern.replaceAll('(?i)', ''); @@ -36,16 +37,16 @@ class RegexSanitizer { // // Strategy: Convert `(?i:...)` to a standard non-capturing group `(?:...)` // and enable global case insensitivity. - // Impact: Similar to inline flags, this broadens the match to be case-insensitive - // for the *entire* string. + // Impact: Similar to inline flags, this broadens the match to be + // case-insensitive for the *entire* string. if (pattern.contains('(?i:')) { caseSensitive = false; pattern = pattern.replaceAll('(?i:', '(?:'); } // 3. Handle Multi-line flag `(?m)` - // Go/PCRE use `(?m)` to make `^` and `$` match start/end of lines, not just string. - // Dart handles this via `multiLine: true`. + // Go/PCRE use `(?m)` to make `^` and `$` match start/end of lines, + // not just string. Dart handles this via `multiLine: true`. if (pattern.contains('(?m)')) { multiLine = true; pattern = pattern.replaceAll('(?m)', ''); @@ -59,15 +60,13 @@ class RegexSanitizer { } // 5. Handle "Single-line" / Dot-all flag `(?s)` - // Go/PCRE `(?s)` makes `.` match newlines. - // Dart `dotAll: true` handles this. + // Go/PCRE `(?s)` makes `.` match newlines. Dart `dotAll: true` handles this. if (pattern.contains('(?s)')) { - // Note: Dart's `dotAll` is available. - // However, we need to pass this back to the caller. - // For now, we'll assume standard DotAll behavior isn't critical or handle it later. - // Actually, let's strip it to allow compilation. + // Note: Dart's `dotAll` is available. However, we need to pass this back + // to the caller. For now, we'll assume standard DotAll behavior isn't + // critical or handle it later. Let's strip it to allow compilation. pattern = pattern.replaceAll('(?s)', ''); - // TODO: Return dotAll flag if we update SecretRule to support it. + // TODO(generate): Return dotAll flag if we update SecretRule to support. } // 6. Handle Scoped Dot-all `(?s:...)` From 3a1352a38ff7df61be111c25d05f48ceb67dcd66 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:30:30 +0100 Subject: [PATCH 10/11] style: formatting --- lib/src/analyzers/code/code_analyzer.dart | 4 ++-- lib/src/configuration/shield_config.dart | 7 ++++++- lib/src/reporters/json_reporter.dart | 2 +- test/integration/integration_test.dart | 3 ++- tool/generate_rules.dart | 3 ++- 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/src/analyzers/code/code_analyzer.dart b/lib/src/analyzers/code/code_analyzer.dart index 9b93cd7..f6e8638 100644 --- a/lib/src/analyzers/code/code_analyzer.dart +++ b/lib/src/analyzers/code/code_analyzer.dart @@ -43,7 +43,7 @@ class CodeAnalyzer implements Analyzer { throw ShieldProcessException( 'Failed to execute dart analyze.', 'Ensure the Dart SDK is installed and accessible in your PATH.\n' - 'Original error: ${e.message}', + 'Original error: ${e.message}', ); } @@ -59,7 +59,7 @@ class CodeAnalyzer implements Analyzer { throw ShieldProcessException( 'dart analyze did not return valid JSON output.', 'This usually means the analysis command crashed or ' - 'encountered a fatal error.\nOutput: $output', + 'encountered a fatal error.\nOutput: $output', ); } diff --git a/lib/src/configuration/shield_config.dart b/lib/src/configuration/shield_config.dart index 0aba789..1d2e09e 100644 --- a/lib/src/configuration/shield_config.dart +++ b/lib/src/configuration/shield_config.dart @@ -6,7 +6,12 @@ import 'package:json_annotation/json_annotation.dart'; part 'shield_config.g.dart'; -@JsonSerializable(anyMap: true, checked: true, disallowUnrecognizedKeys: true, createToJson: false) +@JsonSerializable( + anyMap: true, + checked: true, + disallowUnrecognizedKeys: true, + createToJson: false, +) class ShieldConfig { const ShieldConfig({this.analyzers = const ShieldAnalyzersConfig()}); diff --git a/lib/src/reporters/json_reporter.dart b/lib/src/reporters/json_reporter.dart index 01bf58f..1e8f452 100644 --- a/lib/src/reporters/json_reporter.dart +++ b/lib/src/reporters/json_reporter.dart @@ -7,7 +7,7 @@ import 'package:mason_logger/mason_logger.dart'; class JsonReporter implements Reporter { JsonReporter({this.outputPath = 'shield_report.json', Logger? logger}) - : _logger = logger ?? Logger(); + : _logger = logger ?? Logger(); final String outputPath; final Logger _logger; diff --git a/test/integration/integration_test.dart b/test/integration/integration_test.dart index 5044c2e..3bf9892 100644 --- a/test/integration/integration_test.dart +++ b/test/integration/integration_test.dart @@ -67,7 +67,8 @@ void injectedSecret() { expect( awsMatch, isTrue, - reason: 'Should detect AWS specific rule. ' + reason: + 'Should detect AWS specific rule. ' 'Found: ${secretIssues.map((e) => e.message)}', ); }); diff --git a/tool/generate_rules.dart b/tool/generate_rules.dart index f7a569c..1e3dca4 100644 --- a/tool/generate_rules.dart +++ b/tool/generate_rules.dart @@ -83,7 +83,8 @@ Future main() async { // 2. Write Dart file (for embedding) final dartFile = File('lib/src/generated/fallback_rules.dart'); - final dartContent = '// GENERATED CODE - DO NOT MODIFY BY HAND\n' + final dartContent = + '// GENERATED CODE - DO NOT MODIFY BY HAND\n' '// Generated by tool/generate_rules.dart\n\n' "const String fallbackRulesJson = r'''\n" '$jsonContent' From e6c6a531f75fe525acb2dc72debdee9fc364a1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaroslav=20Novotn=C3=BD?= <62177414+yardexx@users.noreply.github.com> Date: Sun, 25 Jan 2026 17:39:20 +0100 Subject: [PATCH 11/11] ci: update test suite --- .github/workflows/dart.yml | 15 ----- test/integration/integration_test.dart | 76 -------------------------- 2 files changed, 91 deletions(-) delete mode 100644 test/integration/integration_test.dart diff --git a/.github/workflows/dart.yml b/.github/workflows/dart.yml index b733962..7fab192 100644 --- a/.github/workflows/dart.yml +++ b/.github/workflows/dart.yml @@ -48,18 +48,3 @@ jobs: - name: ๐Ÿ“Š Run Pana run: dart pub global activate pana && dart pub global run pana - - integration-test: - runs-on: ubuntu-latest - needs: build - steps: - - uses: actions/checkout@v5 - - uses: dart-lang/setup-dart@v1.6.5 - with: - sdk: '3.9.0' - - - name: โ›“ Install Dependencies - run: dart pub get - - - name: ๐Ÿงช Run Integration Tests - run: dart test test/integration/ --timeout=120s diff --git a/test/integration/integration_test.dart b/test/integration/integration_test.dart deleted file mode 100644 index 3bf9892..0000000 --- a/test/integration/integration_test.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:io'; - -import 'package:dart_shield/src/analyzers/code/code_analyzer.dart'; -import 'package:dart_shield/src/domain/analyzer_result.dart'; -import 'package:test/test.dart'; - -void main() { - group('Integration Test', () { - late File targetFile; - late String originalContent; - - setUp(() { - targetFile = File('example/lib/vulnerabilities.dart'); - originalContent = targetFile.readAsStringSync(); - }); - - tearDown(() { - targetFile.writeAsStringSync(originalContent); - }); - - test('detects hardcoded secrets via dart analyze plugin', () async { - // Inject a secret that matches our rules. - // We construct it dynamically to avoid triggering GitHub's secret scanner - // on this test file itself. - // Pattern requires [A-Z2-7]{16}. Also high entropy (> 3.0). - // ABCDEFGHIJKLMNOP uses only [A-Z], which is valid. And has max entropy. - const prefix = 'AKIA'; - const suffix = 'ABCDEFGHIJKLMNOP'; - const secret = prefix + suffix; - - final codeWithSecret = - ''' -$originalContent - -void injectedSecret() { - final key = '$secret'; -} -'''; - targetFile.writeAsStringSync(codeWithSecret); - - final analyzer = CodeAnalyzer( - analyzedPaths: ['.'], // Analyze root of example - rootFolder: 'example', - ); - - final result = await analyzer.analyze(); - - expect(result, isA()); - final success = result as AnalysisSuccess; - - // Check for our specific rule - final secretIssues = success.issues - .where((i) => i.ruleId == 'avoid_hardcoded_secrets') - .toList(); - - expect( - secretIssues, - isNotEmpty, - reason: 'Should have detected the injected AWS key', - ); - - // The generic rule might ALSO match, so we just check if ONE is AWS. - final awsMatch = secretIssues.any( - (i) => i.message.contains('AWS credentials'), - ); - - expect( - awsMatch, - isTrue, - reason: - 'Should detect AWS specific rule. ' - 'Found: ${secretIssues.map((e) => e.message)}', - ); - }); - }); -}