Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/release-tag.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,16 @@ jobs:
fi

- name: Create and push release tag
env:
RELEASE_TAG_TOKEN: ${{ secrets.RELEASE_TAG_TOKEN }}
run: |
if [ -z "$RELEASE_TAG_TOKEN" ]; then
echo "Missing RELEASE_TAG_TOKEN secret. Create a fine-grained PAT with Contents: Read and write so tag pushes can trigger the publish workflow." >&2
exit 1
fi

tag="v${{ steps.current_version.outputs.value }}"
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "$tag" "$GITHUB_SHA"
git push origin "$tag"
git push "https://x-access-token:${RELEASE_TAG_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "$tag"
20 changes: 18 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.4.0] - 2026-05-24

### Added

- Added a native C++ FFI core for debugger, root/jailbreak, emulator, and hooking-artifact signals.
- Added native HMAC-SHA256 request signing through `FlutterDefenderRequestSigner`.
- Added Android CMake packaging for `libflutter_defender.so`.
- Added iOS Podspec source inclusion for the native C++ core.

### Changed

- RASP signals now merge native C++ checks with the existing platform detector fallback.
- Android native builds no longer pin a specific CMake patch version.

### Tests

- Verified `flutter analyze`, `flutter test`, Android debug APK build, Android release APK build, and the native HMAC-SHA256 test vector.

## [0.3.0] - 2026-05-16

### Added
Expand Down Expand Up @@ -137,5 +155,3 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Example app with locale switching and blocking UI preview.
- `README.md`, `LICENSE` (Apache-2.0), and `.gitignore` coverage for package and example.
- GitHub Actions workflow to run package and example `flutter test` on pull requests and pushes to `main` / `master`.

When you publish the repo, add compare/release links at the bottom of this file (see [Keep a Changelog](https://keepachangelog.com/) footer examples).
35 changes: 30 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ There is no route-observer setup. A guarded screen protects itself before the se

```yaml
dependencies:
flutter_defender: ^0.3.0
flutter_defender: ^0.4.0
```

### Android release emulator launch block
Expand Down Expand Up @@ -179,10 +179,32 @@ All advanced layers are optional and configured at `init`.

### Basic RASP

- Detects debugger attachment and common hooking artifacts (best-effort).
- Uses a native C++ FFI core for debugger, root/jailbreak, emulator, and
common hooking-artifact signals, merged with the platform detector fallback.
- Callback: `onTamperingDetected`
- Policy toggle: `enableRaspDetection`

### Request Signing

`FlutterDefenderRequestSigner` signs `timestamp.rawBodyBytes` using native
HMAC-SHA256 and returns headers you can attach to outgoing requests. Validate
the signature server-side using the same timestamp, exact raw body bytes, and
salt.

```dart
final signer = FlutterDefenderRequestSigner(
secretSalt: 'your_obfuscated_salt',
);

final body = jsonEncode({'amount': 100});
final signed = signer.signString(body: body);

final headers = <String, String>{
...signed.headers,
'Content-Type': 'application/json',
};
```

### Secure Storage Helper (Optional)

- Provides convenience secure key/value methods backed by:
Expand Down Expand Up @@ -326,17 +348,20 @@ This repository includes GitHub Actions for CI and publishing:
- Pull requests run package and example analysis plus tests.
- Pushes to `main` / `master` rerun those checks, verify that `pubspec.yaml`
contains a version higher than the previous branch tip, and then create a
matching Git tag such as `v0.3.0`.
matching Git tag such as `v0.4.0`.
- Pushing that tag triggers the publish workflow, which runs a final
`flutter pub publish --dry-run` and then publishes to pub.dev.

Important notes:

- The first release of a new package must still be published manually with
`dart pub publish` / `flutter pub publish`.
- Pub.dev automated publishing from GitHub Actions only works for workflows
triggered by tag pushes, so the main-branch workflow tags the release and the
tag workflow performs the actual publish.
- GitHub does not start another workflow when a workflow pushes a tag with the
default `GITHUB_TOKEN`. Add an Actions secret named `RELEASE_TAG_TOKEN`
containing a fine-grained personal access token with repository
`Contents: Read and write`; the release-tag workflow uses it only to push the
release tag so `publish.yml` can run.
- Configure automated publishing for this package on pub.dev and require the
GitHub Actions environment named `pub.dev` to match the publish workflow.

Expand Down
20 changes: 20 additions & 0 deletions android/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.22.1)

project(flutter_defender LANGUAGES CXX)

add_library(flutter_defender SHARED
../src/native/src/ffi/defender_core.cpp
../src/native/src/crypto/defender_crypto.cpp
../src/native/src/platform/defender_platform.cpp
../src/native/src/platform/android/defender_android.cpp)

target_compile_features(flutter_defender PRIVATE cxx_std_17)

target_include_directories(flutter_defender PRIVATE
../src/native/include)

target_compile_options(flutter_defender PRIVATE
-fvisibility=hidden
-Wall
-Wextra
-Wno-unused-parameter)
11 changes: 11 additions & 0 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,17 @@ android {
defaultConfig {
minSdk = 24
consumerProguardFiles "consumer-rules.pro"
externalNativeBuild {
cmake {
cppFlags "-std=c++17"
}
}
}

externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}

dependencies {
Expand Down
13 changes: 10 additions & 3 deletions ios/flutter_defender.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
Pod::Spec.new do |s|
s.name = 'flutter_defender'
s.version = '0.2.0'
s.version = '0.4.0'
s.summary = 'Secure-screen protection for Flutter apps.'
s.description = <<-DESC
Secure-screen protection for Flutter apps.
Expand All @@ -13,12 +13,19 @@ Secure-screen protection for Flutter apps.
s.license = { :file => '../LICENSE' }
s.author = { 'Aleem Elmozogi' => 'abddo.55242@gmail.com' }
s.source = { :path => '.' }
s.source_files = 'Classes/**/*'
s.source_files = 'Classes/**/*', '../src/native/**/*.{h,cpp}'
s.dependency 'Flutter'
s.platform = :ios, '14.0'

# Flutter.framework does not contain a i386 slice.
s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES', 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' }
s.pod_target_xcconfig = {
'DEFINES_MODULE' => 'YES',
'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386',
'HEADER_SEARCH_PATHS' => '$(inherited) "${PODS_TARGET_SRCROOT}/../src/native/include"',
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17',
'CLANG_CXX_LIBRARY' => 'libc++',
'OTHER_CPLUSPLUSFLAGS' => '$(inherited) -fvisibility=hidden'
}
s.swift_version = '5.0'

s.resource_bundles = {'flutter_defender_privacy' => ['Resources/PrivacyInfo.xcprivacy']}
Expand Down
2 changes: 2 additions & 0 deletions lib/flutter_defender.dart
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import 'flutter_defender_platform_interface.dart';
import 'l10n/flutter_defender_localizations.dart';
import 'src/core/flutter_defender_config.dart';
import 'src/core/flutter_defender_runtime_state.dart';
import 'src/native/flutter_defender_native.dart';
import 'src/platform/pigeon/defender_messages.g.dart' as pigeon;
import 'src/ui/blocking_screen.dart';
import 'src/ui/flutter_defender_message_id.dart';
Expand All @@ -17,6 +18,7 @@ import 'src/ui/flutter_defender_ui_theme.dart';

export 'flutter_defender_localization_support.dart';
export 'l10n/flutter_defender_localizations.dart';
export 'src/network/flutter_defender_request_signer.dart';
export 'src/ui/blocking_screen.dart';
export 'src/ui/flutter_defender_message_id.dart';
export 'src/ui/flutter_defender_messages.dart';
Expand Down
74 changes: 68 additions & 6 deletions lib/src/controller/flutter_defender_platform_safety.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,33 +16,95 @@ extension _FlutterDefenderPlatformSafety on FlutterDefender {
}

Future<pigeon.NativeRuntimeState> _safeGetRuntimeState() async {
final bool nativeEmulatorDetected = FlutterDefenderNative.instance
.detectEmulator();
try {
return await _platform.getRuntimeState();
final pigeon.NativeRuntimeState runtimeState = await _platform
.getRuntimeState();
return pigeon.NativeRuntimeState(
isForeground: runtimeState.isForeground,
isScreenCaptured: runtimeState.isScreenCaptured,
isEmulator:
(runtimeState.isEmulator ?? false) || nativeEmulatorDetected,
supportsOverlayHardening: runtimeState.supportsOverlayHardening,
);
} catch (_) {
return pigeon.NativeRuntimeState(
isForeground: true,
isScreenCaptured: false,
isEmulator: false,
isEmulator: nativeEmulatorDetected,
supportsOverlayHardening: false,
);
}
}

Future<pigeon.AdvancedSecuritySignals>
_safeGetAdvancedSecuritySignals() async {
final NativeDefenderSignals nativeSignals = FlutterDefenderNative.instance
.collectSignals();
try {
return await _platform.getAdvancedSecuritySignals();
final pigeon.AdvancedSecuritySignals signals = await _platform
.getAdvancedSecuritySignals();
return _mergeNativeSecuritySignals(signals, nativeSignals);
} catch (_) {
return pigeon.AdvancedSecuritySignals(
rootedOrJailbroken: false,
rootedOrJailbroken: nativeSignals.rootedOrJailbroken,
proxyEnabled: false,
vpnEnabled: false,
debuggerAttached: false,
tamperingDetected: false,
debuggerAttached: nativeSignals.debuggerAttached,
tamperingDetected: nativeSignals.tamperingDetected,
tamperingDetails: _nativeTamperingDetails(nativeSignals),
);
}
}

pigeon.AdvancedSecuritySignals _mergeNativeSecuritySignals(
pigeon.AdvancedSecuritySignals signals,
NativeDefenderSignals nativeSignals,
) {
return pigeon.AdvancedSecuritySignals(
rootedOrJailbroken:
(signals.rootedOrJailbroken ?? false) ||
nativeSignals.rootedOrJailbroken,
proxyEnabled: signals.proxyEnabled,
vpnEnabled: signals.vpnEnabled,
debuggerAttached:
(signals.debuggerAttached ?? false) || nativeSignals.debuggerAttached,
tamperingDetected:
(signals.tamperingDetected ?? false) ||
nativeSignals.tamperingDetected,
tamperingDetails: _mergeTamperingDetails(
signals.tamperingDetails,
_nativeTamperingDetails(nativeSignals),
),
);
}

String? _nativeTamperingDetails(NativeDefenderSignals nativeSignals) {
final List<String> details = <String>[];
if (nativeSignals.debuggerAttached) {
details.add('native-debugger');
}
if (nativeSignals.tamperingDetected) {
details.add('native-tampering');
}
if (nativeSignals.rootedOrJailbroken) {
details.add('native-root-jailbreak');
}
return details.isEmpty ? null : details.join(',');
}

String? _mergeTamperingDetails(
String? platformDetails,
String? nativeDetails,
) {
final Set<String> details = <String>{
...?platformDetails?.split(',').where((String value) => value.isNotEmpty),
...?nativeDetails?.split(',').where((String value) => value.isNotEmpty),
};
return details.isEmpty ? null : details.join(',');
}

Future<void> _safeSecureWrite({
required String key,
required String value,
Expand Down
Loading
Loading