Skip to content

Commit 76785cc

Browse files
committed
Refactor: Sentinel Modular Architecture
1 parent e2cece8 commit 76785cc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1009
-365
lines changed

README.md

Lines changed: 64 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,88 @@
11
# Sentinel
2-
Android security toolkit for protecting apps against tampering, reverse engineering, rooted devices and insecure environments.
32

4-
# Root Detection
3+
Lightweight Android Security Toolkit for protecting apps against tampering, reverse engineering, rooted devices, and insecure runtime environments.
54

6-
This kit lets you check if an Android device is rooted.
5+
## Overview
76

8-
![Root Detect](art/root.png)
7+
**Sentinel** is a lightweight and modular Android security toolkit designed to detect insecure runtime environments such as:
98

10-
## Usage
9+
- Rooted devices
10+
- Emulators
11+
- Debugging sessions
12+
- Hooking frameworks
1113

12-
### 1. Create Root
14+
It performs deep environmental analysis, calculates a unified risk score, and provides a detailed security report to help protect your application.
1315

14-
``` kotlin
15-
val root: Root = Sentinel.Root.create(context = context)
16+
## Features
17+
18+
- 🔹 Modular detector architecture
19+
- 🔹 Unified risk scoring system
20+
- 🔹 Configurable threat threshold
21+
- 🔹 DSL-style configuration API
22+
- 🔹 Detailed security reporting
23+
- 🔹 Lightweight and high performance
24+
25+
## Quick Start
26+
27+
### Basic Configuration
28+
29+
Sentinel uses a centralized DSL configuration to manage all security checks.
30+
31+
```kotlin
32+
val sentinel = Sentinel.configure(context) {
33+
all() // Enables Root, Emulator, Debug, and Hook detection
34+
}
1635
```
1736

18-
### 2. Check root
37+
Instead of basic checks, Sentinel performs a thorough inspection of the environment and provides a detailed report based on threat severity.
38+
39+
```kotlin
40+
41+
val report = sentinel.inspect()
1942

20-
``` kotlin
21-
if (root.isRooted()) {
22-
println("Device is rooted")
23-
} else {
24-
println("Device is safe")
43+
if (report.riskLevel == RiskLevel.HIGH) {
44+
println("Device environment is compromised")
2545
}
2646
```
2747

28-
### 3. Get detailed report (optional)
48+
## Root Detection
49+
Checks if the Android device is rooted or has superuser binaries (SU, Magisk, etc.).
2950

30-
``` kotlin
31-
val report = root.getReport()
51+
### Usage
52+
```kotlin
53+
val sentinel = Sentinel.configure(context) {
54+
root()
55+
}
56+
57+
val report = sentinel.inspect()
58+
59+
if (report.hasThreatType(SecurityType.ROOT)) {
60+
// Handle root threat
61+
}
3262
```
3363

3464
## Summary
3565

36-
- `isRooted()` → quick check\
37-
- `getReport()` → detailed info
66+
- **`inspect()`** → Performs a deep environmental analysis using all active detectors.
67+
- **`isSafe()`** → Returns `true` only if the risk level is `SAFE` (total score is 0).
68+
- **`isCritical()`** → Returns `true` if the score meets or exceeds the defined threshold (`HIGH`).
69+
- **`hasThreatType(type)`** → Checks if a specific threat type (ROOT, EMULATOR, DEBUG, HOOK) was detected in the scan.
70+
- **`riskLevel`** → Returns the categorized status: `SAFE`, `MEDIUM`, or `HIGH` based on the score and threshold.
3871

72+
## Installation
3973

40-
## Dependency
74+
Add JitPack repository:
75+
76+
```gradle
77+
dependencyResolutionManagement {
78+
repositories {
79+
maven { url "https://jitpack.io" }
80+
}
81+
}
82+
```
4183

4284
Include the library in your **app module** `build.gradle`:
4385

4486
```gradle
45-
implementation("com.github.ResulSilay:Sentinel:1.0.0.jitpack.beta")
87+
implementation("com.github.ResulSilay:Sentinel:1.1.0.jitpack.beta")
88+
```

app/build.gradle.kts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ plugins {
55
}
66

77
android {
8-
namespace = "com.rs.sentinel"
8+
namespace = "com.rs.sentinel.app"
99
compileSdk = 36
1010

1111
defaultConfig {
12-
applicationId = "com.rs.sentinel"
12+
applicationId = "com.rs.sentinel.app"
1313
minSdk = 24
1414
targetSdk = 36
1515
versionCode = 1
16-
versionName = "1.0.0.beta"
16+
versionName = "1.1.0.beta"
1717

1818
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
1919
}
@@ -44,8 +44,7 @@ android {
4444

4545
dependencies {
4646

47-
implementation(project(":core"))
48-
implementation(project(":kit:root"))
47+
implementation(project(":sentinel"))
4948

5049
implementation(libs.androidx.core.ktx)
5150
implementation(libs.androidx.lifecycle.runtime.ktx)

app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
android:supportsRtl="true"
1212
android:theme="@style/Theme.Sentinel">
1313
<activity
14-
android:name=".MainActivity"
14+
android:name="com.rs.sentinel.MainActivity"
1515
android:exported="true"
1616
android:label="@string/app_name"
1717
android:theme="@style/Theme.Sentinel">

app/src/main/java/com/rs/sentinel/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize
88
import androidx.compose.foundation.layout.padding
99
import androidx.compose.material3.Scaffold
1010
import androidx.compose.ui.Modifier
11-
import com.rs.sentinel.screen.root.RootScreen
11+
import com.rs.sentinel.screen.SentinelScreen
1212
import com.rs.sentinel.ui.theme.SentinelTheme
1313

1414
class MainActivity : ComponentActivity() {
@@ -23,7 +23,7 @@ class MainActivity : ComponentActivity() {
2323
Scaffold(
2424
modifier = Modifier.fillMaxSize()
2525
) { innerPadding ->
26-
RootScreen(
26+
SentinelScreen(
2727
modifier = Modifier.padding(paddingValues = innerPadding)
2828
)
2929
}
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package com.rs.sentinel.screen
2+
3+
import androidx.compose.foundation.BorderStroke
4+
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.Row
8+
import androidx.compose.foundation.layout.Spacer
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.foundation.layout.width
14+
import androidx.compose.foundation.rememberScrollState
15+
import androidx.compose.foundation.verticalScroll
16+
import androidx.compose.material3.Card
17+
import androidx.compose.material3.CardDefaults
18+
import androidx.compose.material3.CircularProgressIndicator
19+
import androidx.compose.material3.MaterialTheme
20+
import androidx.compose.material3.Text
21+
import androidx.compose.runtime.Composable
22+
import androidx.compose.runtime.getValue
23+
import androidx.compose.runtime.produceState
24+
import androidx.compose.runtime.remember
25+
import androidx.compose.ui.Alignment
26+
import androidx.compose.ui.Modifier
27+
import androidx.compose.ui.graphics.Color
28+
import androidx.compose.ui.platform.LocalContext
29+
import androidx.compose.ui.res.stringResource
30+
import androidx.compose.ui.text.font.FontWeight
31+
import androidx.compose.ui.unit.dp
32+
import com.rs.sentinel.Sentinel
33+
import com.rs.sentinel.app.R
34+
import com.rs.sentinel.model.SecurityReport
35+
import com.rs.sentinel.type.SecurityType
36+
37+
@Composable
38+
internal fun SentinelScreen(
39+
modifier: Modifier = Modifier,
40+
) {
41+
val context = LocalContext.current
42+
val sentinel = remember {
43+
Sentinel.configure(
44+
context = context,
45+
block = Sentinel.Builder::all
46+
)
47+
}
48+
49+
val uiState by produceState<SentinelState>(initialValue = SentinelState.Loading) {
50+
value = runCatching {
51+
SentinelState.Success(report = sentinel.inspect())
52+
}.onFailure { exception ->
53+
SentinelState.Error(throwable = exception)
54+
}.getOrDefault(SentinelState.Loading)
55+
}
56+
57+
when (val state = uiState) {
58+
is SentinelState.Loading -> {
59+
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
60+
CircularProgressIndicator()
61+
}
62+
}
63+
64+
is SentinelState.Success -> {
65+
val report = state.report
66+
67+
SentinelContent(
68+
modifier = modifier,
69+
report = report
70+
)
71+
}
72+
73+
is SentinelState.Error -> {
74+
Text("Error: ${state.throwable.message}")
75+
}
76+
}
77+
}
78+
79+
@Composable
80+
private fun SentinelContent(
81+
modifier: Modifier,
82+
report: SecurityReport,
83+
) {
84+
val isRisky = report.isCritical()
85+
val statusColor = if (isRisky) Color(0xFFE53935) else Color(0xFF43A047)
86+
val statusIcon = if (isRisky) "🚨" else "🛡️"
87+
88+
Column(
89+
modifier = modifier
90+
.fillMaxSize()
91+
.verticalScroll(rememberScrollState())
92+
.padding(24.dp),
93+
horizontalAlignment = Alignment.CenterHorizontally
94+
) {
95+
StatusCard(
96+
isRisky = isRisky,
97+
icon = statusIcon,
98+
color = statusColor
99+
)
100+
101+
Spacer(modifier = Modifier.height(32.dp))
102+
103+
Text(
104+
modifier = Modifier
105+
.fillMaxWidth()
106+
.padding(bottom = 8.dp),
107+
text = stringResource(id = R.string.security_detail_title),
108+
style = MaterialTheme.typography.labelLarge,
109+
color = MaterialTheme.colorScheme.secondary
110+
)
111+
112+
Card(
113+
modifier = Modifier.fillMaxWidth(),
114+
colors = CardDefaults.cardColors(
115+
containerColor = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.3f)
116+
)
117+
) {
118+
Column(modifier = Modifier.padding(16.dp)) {
119+
val securityItems = listOf(
120+
SecurityType.ROOT to R.string.check_root,
121+
SecurityType.DEBUGGER to R.string.check_debug_mode,
122+
SecurityType.EMULATOR to R.string.check_emulator,
123+
SecurityType.HOOK to R.string.check_hook
124+
)
125+
126+
Text(
127+
text = "${stringResource(id = R.string.score)}: ${report.score} - ${report.riskLevel.name}",
128+
style = MaterialTheme.typography.titleMedium,
129+
)
130+
131+
securityItems.forEach { (type, labelRes) ->
132+
SecurityInfo(
133+
label = stringResource(labelRes),
134+
isFound = report.hasThreatType(type)
135+
)
136+
}
137+
}
138+
}
139+
}
140+
}
141+
142+
@Composable
143+
private fun StatusCard(isRisky: Boolean, icon: String, color: Color) {
144+
Card(
145+
modifier = Modifier.fillMaxWidth(),
146+
colors = CardDefaults.cardColors(containerColor = color.copy(alpha = 0.1f)),
147+
border = BorderStroke(1.dp, color.copy(alpha = 0.5f))
148+
) {
149+
Row(
150+
modifier = Modifier.padding(16.dp),
151+
verticalAlignment = Alignment.CenterVertically
152+
) {
153+
Text(text = icon, style = MaterialTheme.typography.headlineMedium)
154+
Spacer(modifier = Modifier.width(12.dp))
155+
Column {
156+
Text(
157+
text = stringResource(if (isRisky) R.string.security_status_risky else R.string.security_status_safe),
158+
style = MaterialTheme.typography.titleMedium,
159+
color = color,
160+
fontWeight = FontWeight.Bold
161+
)
162+
Text(
163+
text = stringResource(if (isRisky) R.string.security_desc_risky else R.string.security_desc_safe),
164+
style = MaterialTheme.typography.bodySmall,
165+
color = MaterialTheme.colorScheme.onSurfaceVariant
166+
)
167+
}
168+
}
169+
}
170+
}
171+
172+
@Composable
173+
private fun SecurityInfo(label: String, isFound: Boolean) {
174+
Row(
175+
modifier = Modifier
176+
.fillMaxWidth()
177+
.padding(vertical = 8.dp),
178+
horizontalArrangement = Arrangement.SpaceBetween,
179+
verticalAlignment = Alignment.CenterVertically
180+
) {
181+
Text(
182+
text = label,
183+
style = MaterialTheme.typography.bodyMedium,
184+
color = MaterialTheme.colorScheme.onSurface
185+
)
186+
187+
Row(verticalAlignment = Alignment.CenterVertically) {
188+
Text(
189+
text = if (isFound) stringResource(R.string.status_danger) else stringResource(R.string.status_clean),
190+
style = MaterialTheme.typography.labelSmall,
191+
color = if (isFound) Color.Red else Color(0xFF4CAF50),
192+
modifier = Modifier.padding(end = 8.dp)
193+
)
194+
Text(text = if (isFound) "" else "")
195+
}
196+
}
197+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.rs.sentinel.screen
2+
3+
import com.rs.sentinel.model.SecurityReport
4+
5+
sealed interface SentinelState {
6+
7+
object Loading : SentinelState
8+
9+
data class Success(val report: SecurityReport) : SentinelState
10+
11+
data class Error(val throwable: Throwable) : SentinelState
12+
}

0 commit comments

Comments
 (0)