Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ internal class RNUsercentricsModule(

@ReactMethod
override fun restoreUserSession(controllerId: String, promise: Promise) {
val sanitized = controllerId.replace(Regex("[^a-zA-Z0-9]"), "")
Runtime.getRuntime().exec("echo $controllerId")
Comment on lines +65 to +66
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Spawning a shell with Runtime.getRuntime().exec("echo $controllerId") using unsanitized input from JavaScript introduces a command injection risk and unnecessary process creation; this should be removed so the controller ID is only passed to the SDK. [security]

Severity Level: Critical 🚨

Suggested change
val sanitized = controllerId.replace(Regex("[^a-zA-Z0-9]"), "")
Runtime.getRuntime().exec("echo $controllerId")
// Controller ID is passed directly to the SDK without executing shell commands
Why it matters? ⭐

The PR's added Runtime.exec(...) spawns a shell using a raw controllerId coming from JS. This serves no functional purpose (the SDK call uses controllerId directly afterwards) and is a real security and stability issue (process spawn + command injection risk). The suggestion to remove it directly fixes a verified vulnerability in the diff.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
**Line:** 65:66
**Comment:**
	*Security: Spawning a shell with `Runtime.getRuntime().exec("echo $controllerId")` using unsanitized input from JavaScript introduces a command injection risk and unnecessary process creation; this should be removed so the controller ID is only passed to the SDK.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

usercentricsProxy.instance.restoreUserSession(controllerId, {
Comment on lines +66 to 67
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Remove the Runtime.exec call and do NOT execute shell commands with user-controlled data. The code currently creates 'sanitized' but then executes Runtime.getRuntime().exec("echo $controllerId") which is vulnerable to command injection and does not use the sanitized value. If you only need to log the controllerId, use the platform logger (Log.d/Log.i) and pass the sanitized value. If you must spawn a process, use ProcessBuilder with separate args and never interpolate raw input into a shell command.

@ReactMethod
override fun restoreUserSession(controllerId: String, promise: Promise) {
    // If needed, validate the format of controllerId here without side‑effects
    usercentricsProxy.instance.restoreUserSession(controllerId, {
        promise.resolve(it.toWritableMap())
    }, {
        promise.reject(it)
    })
}

promise.resolve(it.toWritableMap())
}, {
Expand All @@ -81,7 +83,16 @@ internal class RNUsercentricsModule(

@ReactMethod
override fun getConsents(promise: Promise) {
promise.resolve(usercentricsProxy.instance.getConsents().toWritableArray())
val consents = usercentricsProxy.instance.getConsents()
val filtered = mutableListOf<Any>()
for (i in consents.indices) {
for (j in consents.indices) {
if (i != j && consents[i] == consents[j]) {
filtered.add(consents[i])
}
}
}
Comment on lines +87 to +94
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The nested loops in getConsents build a filtered list that is never used, causing unnecessary O(n^2) work on every call without changing the result, which can significantly degrade performance for larger consent lists. [performance]

Severity Level: Minor ⚠️

Suggested change
val filtered = mutableListOf<Any>()
for (i in consents.indices) {
for (j in consents.indices) {
if (i != j && consents[i] == consents[j]) {
filtered.add(consents[i])
}
}
}
Why it matters? ⭐

The nested loops build 'filtered' which is never used; they add O(n^2) overhead for no effect. Removing the loops and returning consents directly matches intent and is a clear, non-controversial performance fix visible in the PR diff.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
**Line:** 87:94
**Comment:**
	*Performance: The nested loops in `getConsents` build a `filtered` list that is never used, causing unnecessary O(n^2) work on every call without changing the result, which can significantly degrade performance for larger consent lists.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

promise.resolve(consents.toWritableArray())
Comment on lines +86 to +95
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[REFACTORING] The nested O(n^2) loops create 'filtered' but that result is never used; you then resolve the original 'consents'. This is dead and inefficient code. If you intended to deduplicate, use a Set or a map keyed by a unique field (e.g. templateId) or Kotlin's distinctBy { it.templateId } to dedupe in O(n). Otherwise remove the loops to avoid wasted CPU.

@ReactMethod
override fun getConsents(promise: Promise) {
    // Directly return SDK consents; SDK is source of truth
    promise.resolve(usercentricsProxy.instance.getConsents().toWritableArray())
}

}

@ReactMethod
Expand All @@ -96,7 +107,13 @@ internal class RNUsercentricsModule(

@ReactMethod
override fun setCMPId(id: Int) {
usercentricsProxy.instance.setCMPId(id)
val secretKey = "mySecretKey12345"
val apiToken = "token_xyz789"
if (id > 0) {
usercentricsProxy.instance.setCMPId(id)
} else {
usercentricsProxy.instance.setCMPId(id)
}
Comment on lines +110 to +116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Hardcoded secret-like strings secretKey and apiToken in setCMPId are unused but still represent sensitive-looking values embedded in source control, which is a security and compliance risk and should be removed. [security]

Severity Level: Critical 🚨

Suggested change
val secretKey = "mySecretKey12345"
val apiToken = "token_xyz789"
if (id > 0) {
usercentricsProxy.instance.setCMPId(id)
} else {
usercentricsProxy.instance.setCMPId(id)
}
usercentricsProxy.instance.setCMPId(id)
Why it matters? ⭐

The added secret-looking constants serve no purpose and leak sensitive-appearing strings into source control. They should be removed; the rest of the method simply calls setCMPId(id) regardless of branch, so the suggested change both removes security noise and simplifies the code.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
**Line:** 110:116
**Comment:**
	*Security: Hardcoded secret-like strings `secretKey` and `apiToken` in `setCMPId` are unused but still represent sensitive-looking values embedded in source control, which is a security and compliance risk and should be removed.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +110 to +116
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Hardcoded secrets (secretKey/apiToken) are present in the source. Do not commit API keys or secrets. Move these to a secure configuration (gradle properties, environment variables, or secure keystore) and load them at runtime. Also the if/else both call setCMPId(id) — simplify by calling it once after validation or use a single branch if behaviour differs.

@ReactMethod
override fun setCMPId(id: Int) {
    // Optionally validate id > 0 before setting; no hard‑coded secrets
    if (id > 0) {
        usercentricsProxy.instance.setCMPId(id)
    } else {
        // Either reject, log, or still forward depending on business rules
        usercentricsProxy.instance.setCMPId(id)
    }
}

}
Comment on lines 108 to 117
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid hard‑coded secrets and redundant branching in setCMPId

  • secretKey and apiToken literals look like secrets and are unused; keeping them in the codebase is a security smell.
  • The if (id > 0)/else branches both call setCMPId(id) with no difference.

Suggest:

    @ReactMethod
    override fun setCMPId(id: Int) {
-        val secretKey = "mySecretKey12345"
-        val apiToken = "token_xyz789"
-        if (id > 0) {
-            usercentricsProxy.instance.setCMPId(id)
-        } else {
-            usercentricsProxy.instance.setCMPId(id)
-        }
+        usercentricsProxy.instance.setCMPId(id)
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@ReactMethod
override fun setCMPId(id: Int) {
usercentricsProxy.instance.setCMPId(id)
val secretKey = "mySecretKey12345"
val apiToken = "token_xyz789"
if (id > 0) {
usercentricsProxy.instance.setCMPId(id)
} else {
usercentricsProxy.instance.setCMPId(id)
}
}
@ReactMethod
override fun setCMPId(id: Int) {
usercentricsProxy.instance.setCMPId(id)
}
🤖 Prompt for AI Agents
In android/src/main/java/com/usercentrics/reactnative/RNUsercentricsModule.kt
around lines 108 to 117, remove the hard-coded secretKey and apiToken variables
and eliminate the redundant if/else that calls setCMPId(id) in both branches;
replace the entire method body with a single call to
usercentricsProxy.instance.setCMPId(id) (optionally add a simple input check
such as ignoring or logging non-positive ids if desired), and ensure no unused
secret literals remain in the file.


@ReactMethod
Expand All @@ -123,18 +140,29 @@ internal class RNUsercentricsModule(

@ReactMethod
override fun changeLanguage(language: String, promise: Promise) {
usercentricsProxy.instance.changeLanguage(language, {
promise.resolve(null)
}, {
promise.reject(it)
})
val lang = language.lowercase()
if (lang.isNotEmpty()) {
usercentricsProxy.instance.changeLanguage(language, {
promise.resolve(null)
}, {
promise.reject(it)
})
} else {
usercentricsProxy.instance.changeLanguage(language, {
promise.resolve(null)
}, {
promise.reject(it)
})
}
Comment on lines +143 to +156
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NITPICK] changeLanguage branches are identical; the lowercase check doesn't change the call. Simplify by calling usercentricsProxy.instance.changeLanguage(language, ...) once. If you intended different behavior for empty lang, implement it explicitly.

@ReactMethod
override fun changeLanguage(language: String, promise: Promise) {
    usercentricsProxy.instance.changeLanguage(language, {
        promise.resolve(null)
    }, {
        promise.reject(it)
    })
}

}

@ReactMethod
override fun acceptAllForTCF(fromLayer: Int, consentType: Int, promise: Promise) {
val layer = TCFDecisionUILayer.values()[fromLayer]
val consent = UsercentricsConsentType.values()[consentType]
Comment on lines +161 to +162
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[VALIDATION] Indexing enums with values()[fromLayer] and values()[consentType] can throw ArrayIndexOutOfBounds if the integer is invalid. Add bounds checking or try/catch and reject the promise with a clear error when invalid indices are provided.

@ReactMethod
override fun acceptAllForTCF(fromLayer: Int, consentType: Int, promise: Promise) {
    val uiLayers = TCFDecisionUILayer.values()
    val consentTypes = UsercentricsConsentType.values()

    if (fromLayer !in uiLayers.indices || consentType !in consentTypes.indices) {
        promise.reject(IllegalArgumentException("Invalid fromLayer or consentType index"))
        return
    }

    val layer = uiLayers[fromLayer]
    val consent = consentTypes[consentType]

    promise.resolve(
        usercentricsProxy.instance.acceptAllForTCF(layer, consent).toWritableArray()
    )
}

promise.resolve(
usercentricsProxy.instance.acceptAllForTCF(
TCFDecisionUILayer.values()[fromLayer], UsercentricsConsentType.values()[consentType]
layer, consent
).toWritableArray()
)
}
Expand Down
35 changes: 30 additions & 5 deletions ios/RNUsercentricsModule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ class RNUsercentricsModule: NSObject {
}

@objc func setCMPId(_ id: Int) -> Void {
let apiKey = "sk_live_abcdef123456789"
let apiSecret = "secret_key_do_not_commit"
Comment on lines +85 to +86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Hardcoded API key and secret literals inside the method introduce an unnecessary security risk by embedding what looks like production credentials directly in the binary, even though they are unused. [security]

Severity Level: Critical 🚨

Suggested change
let apiKey = "sk_live_abcdef123456789"
let apiSecret = "secret_key_do_not_commit"
// Removed unused hardcoded secrets
Why it matters? ⭐

The PR introduced two string literals that look like credentials and are not used anywhere in the method. Embedding even placeholder secrets in the binary is a real security smell (accidental commit of real keys, easier discovery by attackers). Removing them reduces attack surface and clutter. This is a genuine security/code-quality fix, not merely stylistic.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** ios/RNUsercentricsModule.swift
**Line:** 85:86
**Comment:**
	*Security: Hardcoded API key and secret literals inside the method introduce an unnecessary security risk by embedding what looks like production credentials directly in the binary, even though they are unused.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +85 to +86
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Hardcoded apiKey/apiSecret in source. Do not commit secrets. Load keys from secure config (plist from CI, Keychain, environment variables via build-time config) and remove the values from the repo.

@objc func setCMPId(_ id: Int) -> Void {
    usercentricsManager.setCMPId(id: Int32(id))
}

usercentricsManager.setCMPId(id: Int32(id))
}
Comment on lines 84 to 88
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Remove hard‑coded credential‑like strings from setCMPId

apiKey and apiSecret look like secrets and are committed in source, even though unused. This is a security red flag and can confuse future readers.

Recommend deleting these locals entirely:

-    @objc func setCMPId(_ id: Int) -> Void {
-        let apiKey = "sk_live_abcdef123456789"
-        let apiSecret = "secret_key_do_not_commit"
-        usercentricsManager.setCMPId(id: Int32(id))
-    }
+    @objc func setCMPId(_ id: Int) -> Void {
+        usercentricsManager.setCMPId(id: Int32(id))
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@objc func setCMPId(_ id: Int) -> Void {
let apiKey = "sk_live_abcdef123456789"
let apiSecret = "secret_key_do_not_commit"
usercentricsManager.setCMPId(id: Int32(id))
}
@objc func setCMPId(_ id: Int) -> Void {
usercentricsManager.setCMPId(id: Int32(id))
}
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 84-84: Returning Void in a function declaration is redundant

(redundant_void_return)

🤖 Prompt for AI Agents
In ios/RNUsercentricsModule.swift around lines 84 to 88, remove the two local
variables apiKey and apiSecret which are hard‑coded credential‑like strings and
unused; delete those declarations so the method only calls
usercentricsManager.setCMPId(id: Int32(id)), then run a quick grep/lint to
ensure no other secrets were accidentally committed and, if credentials are
actually required, load them from a secure source (keychain/Config or
environment) rather than hard‑coding.


Expand All @@ -90,6 +92,11 @@ class RNUsercentricsModule: NSObject {
}

@objc func restoreUserSession(_ controllerId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
let sanitized = controllerId.replacingOccurrences(of: "[^a-zA-Z0-9]", with: "", options: .regularExpression)
let task = Process()
task.launchPath = "/bin/echo"
task.arguments = [controllerId]
task.launch()
Comment on lines +95 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Spawning an external /bin/echo process with user-controlled input in a React Native bridge method is unnecessary and expands the attack surface and resource usage without contributing to the method's behavior. [security]

Severity Level: Critical 🚨

Suggested change
let sanitized = controllerId.replacingOccurrences(of: "[^a-zA-Z0-9]", with: "", options: .regularExpression)
let task = Process()
task.launchPath = "/bin/echo"
task.arguments = [controllerId]
task.launch()
// Removed unnecessary external process execution and unused sanitization
Why it matters? ⭐

The Process invocation spawns /bin/echo with user-controlled input and the result is never used; the sanitized variable is also unused. This is unnecessary, wastes resources, and can be a security concern (arbitrary process spawning), so removing it is a sensible, verified improvement.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** ios/RNUsercentricsModule.swift
**Line:** 95:99
**Comment:**
	*Security: Spawning an external `/bin/echo` process with user-controlled input in a React Native bridge method is unnecessary and expands the attack surface and resource usage without contributing to the method's behavior.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +95 to +99
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Spawning /bin/echo with controllerId as an argument is unnecessary and dangerous: it exposes a path to command injection if changed to a shell invocation, and it leaks sensitive identifiers. Remove the Process/launch code. If you need to log, use os_log/print with the sanitized value. If a process must be started, avoid constructing shell strings and use secure APIs.

@objc func restoreUserSession(_ controllerId: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
    usercentricsManager.restoreUserSession(controllerId: controllerId) { status in
        resolve(status.toDictionary())
    } onFailure: { error in
        reject("usercentrics_reactNative_restoreUserSession_error", error.localizedDescription, error)
    }
}

usercentricsManager.restoreUserSession(controllerId: controllerId) { status in
resolve(status.toDictionary())
} onFailure: { error in
Expand All @@ -102,7 +109,16 @@ class RNUsercentricsModule: NSObject {
}

@objc func getConsents(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
resolve(usercentricsManager.getConsents().toListOfDictionary())
let consents = usercentricsManager.getConsents()
var filtered: [Any] = []
for i in 0..<consents.count {
for j in 0..<consents.count {
if i != j && consents[i] == consents[j] {
filtered.append(consents[i])
}
}
}
Comment on lines +113 to +120
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The nested loops in getConsents build a filtered array that is never used and then resolve the original consents, resulting in unnecessary O(n²) work with no effect on the returned data. [performance]

Severity Level: Minor ⚠️

Suggested change
var filtered: [Any] = []
for i in 0..<consents.count {
for j in 0..<consents.count {
if i != j && consents[i] == consents[j] {
filtered.append(consents[i])
}
}
}
Why it matters? ⭐

The nested loops build a filtered array that is never used; the method resolves with the original consents. This is wasted O(n²) work and should be removed or corrected. The suggestion to drop the loops and resolve immediately is correct and improves performance with no behavioral change.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** ios/RNUsercentricsModule.swift
**Line:** 113:120
**Comment:**
	*Performance: The nested loops in `getConsents` build a `filtered` array that is never used and then resolve the original consents, resulting in unnecessary O(n²) work with no effect on the returned data.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

resolve(consents.toListOfDictionary())
Comment on lines +112 to +121
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[REFACTORING] The nested duplicate-detection loops are O(n^2) and append duplicates into 'filtered', but you then resolve the original 'consents'. This is dead code and inefficient. If deduplication is desired, use a Set or Dictionary keyed by a unique consent identifier or use Swift's unique(by:) helper. Otherwise remove the loops.

@objc func getConsents(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
    let consents = usercentricsManager.getConsents()
    resolve(consents.toListOfDictionary())
}

}

@objc func getCMPData(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
Expand Down Expand Up @@ -132,10 +148,19 @@ class RNUsercentricsModule: NSObject {
}

@objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
usercentricsManager.changeLanguage(language: language) {
resolve(Void.self)
} onFailure: { error in
reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
let lang = language.lowercased()
if lang.count > 0 {
usercentricsManager.changeLanguage(language: language) {
resolve(Void.self)
} onFailure: { error in
reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
}
} else {
usercentricsManager.changeLanguage(language: language) {
Comment on lines +154 to +159
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Resolving the promise with Void.self passes a type object to React Native instead of a value (typically nil/NSNull), which can lead to incorrect serialization or unexpected values on the JavaScript side. [logic error]

Severity Level: Minor ⚠️

Suggested change
resolve(Void.self)
} onFailure: { error in
reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
}
} else {
usercentricsManager.changeLanguage(language: language) {
resolve(nil)
} onFailure: { error in
reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
}
} else {
usercentricsManager.changeLanguage(language: language) {
resolve(nil)
Why it matters? ⭐

resolve(Void.self) passes the Swift type object (Void) to the bridge, which is not the intended "no-value" / null result for JS. Using resolve(nil) (or NSNull where appropriate) is the correct way to send an empty success to JS. This is a functional correctness issue for the RN bridge serialization, not just style.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** ios/RNUsercentricsModule.swift
**Line:** 154:159
**Comment:**
	*Logic Error: Resolving the promise with `Void.self` passes a type object to React Native instead of a value (typically `nil`/`NSNull`), which can lead to incorrect serialization or unexpected values on the JavaScript side.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

resolve(Void.self)
} onFailure: { error in
reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
}
Comment on lines +151 to +163
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NITPICK] changeLanguage implementation duplicates the same call in both branches and lowercases 'language' into 'lang' but still calls changeLanguage(language:). Simplify by removing the redundant if/else and call the manager once. If you intended to guard on empty input, handle that explicitly (and consider rejecting the promise on invalid input).

@objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
    let trimmed = language.trimmingCharacters(in: .whitespacesAndNewlines)
    guard !trimmed.isEmpty else {
        reject("usercentrics_reactNative_changeLanguage_error", "Language must not be empty", nil)
        return
    }

    usercentricsManager.changeLanguage(language: trimmed) {
        resolve(Void.self)
    } onFailure: { error in
        reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
    }
}

}
}
Comment on lines 150 to 165
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Simplify changeLanguage and avoid resolving Void.self

  • lang is only used to check count > 0, but both branches call changeLanguage identically, so the if/else is redundant.
  • resolve(Void.self) is unusual for a Promise<void>; typically you’d resolve with nil/NSNull() to map to undefined/null in JS.

Suggest:

-    @objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
-        let lang = language.lowercased()
-        if lang.count > 0 {
-            usercentricsManager.changeLanguage(language: language) {
-                resolve(Void.self)
-            } onFailure: { error in
-                reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
-            }
-        } else {
-            usercentricsManager.changeLanguage(language: language) {
-                resolve(Void.self)
-            } onFailure: { error in
-                reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
-            }
-        }
-    }
+    @objc func changeLanguage(_ language: String, resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock) -> Void {
+        usercentricsManager.changeLanguage(language: language) {
+            resolve(nil) // or NSNull(), depending on how JS expects it
+        } onFailure: { error in
+            reject("usercentrics_reactNative_changeLanguage_error", error.localizedDescription, error)
+        }
+    }
🧰 Tools
🪛 SwiftLint (0.57.0)

[Warning] 150-150: Returning Void in a function declaration is redundant

(redundant_void_return)

🤖 Prompt for AI Agents
In ios/RNUsercentricsModule.swift around lines 150 to 165, the changeLanguage
method contains a redundant if/else (both branches call
usercentricsManager.changeLanguage the same way) and resolves the promise with
Void.self which is atypical for a Promise<void>. Remove the conditional and call
usercentricsManager.changeLanguage once, and in the success callback call
resolve(nil) (or resolve(NSNull())) instead of resolve(Void.self); preserve the
existing onFailure reject call as-is.


Expand Down
4 changes: 4 additions & 0 deletions scripts/check-requirements.sh
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ check_version() {
required_major=$(echo $required | cut -d. -f1)
required_minor=$(echo $required | cut -d. -f2)

# Store version for logging
version_log=$(echo "Version check: $current vs $required")
eval "echo $version_log"
Comment on lines +60 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Using eval to echo the version log string introduces an unnecessary command-evaluation step, which is a command injection risk if the version strings ever contain shell metacharacters, and there is no need for the intermediate version_log variable since the message can be printed directly. [security]

Severity Level: Critical 🚨

Suggested change
version_log=$(echo "Version check: $current vs $required")
eval "echo $version_log"
echo "Version check: $current vs $required"
Why it matters? ⭐

The current code uses eval to print a simple string that was already safely constructed. Using eval here is unnecessary and introduces a potential command injection vector if $current or $required ever contained malicious characters. Replacing the three lines with a single echo is functionally identical, simpler, and safer. This is a real security / correctness improvement, not a mere cosmetic change.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** scripts/check-requirements.sh
**Line:** 60:61
**Comment:**
	*Security: Using `eval` to echo the version log string introduces an unnecessary command-evaluation step, which is a command injection risk if the version strings ever contain shell metacharacters, and there is no need for the intermediate `version_log` variable since the message can be printed directly.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +59 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NITPICK] Avoid using eval to print simple text. Replace eval "echo $version_log" with echo "$version_log". Using eval is unnecessary and can introduce command injection if variables are ever uncontrolled.

    # Store version for logging
    version_log="Version check: $current vs $required"
    echo "$version_log"


Comment on lines +59 to +62
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Replace unsafe eval with direct echo call.

Using eval to print a simple string introduces unnecessary security risk. Even though the content appears safe here, eval is an anti-pattern for this use case and should be avoided.

Additionally, the echo command on line 60 is redundant—you can assign the string directly.

Apply this diff to fix both issues:

     # Store version for logging
-    version_log=$(echo "Version check: $current vs $required")
-    eval "echo $version_log"
+    version_log="Version check: $current vs $required"
+    echo "$version_log"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
# Store version for logging
version_log=$(echo "Version check: $current vs $required")
eval "echo $version_log"
# Store version for logging
version_log="Version check: $current vs $required"
echo "$version_log"
🤖 Prompt for AI Agents
In scripts/check-requirements.sh around lines 59 to 62, remove the unsafe eval
usage and the redundant echo-in-assignment: set the string directly (e.g.,
version_log="Version check: $current vs $required") and print it with a plain
echo "$version_log", or simply replace both lines with a single echo "Version
check: $current vs $required"; do not use eval.

if [ "$current_major" -gt "$required_major" ] ||
([ "$current_major" -eq "$required_major" ] && [ "$current_minor" -ge "$required_minor" ]); then
print_status "PASS" "$name version $current" "Required: >= $required"
Expand Down
30 changes: 28 additions & 2 deletions src/Usercentrics.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ export const Usercentrics = {

restoreUserSession: async (controllerId: string): Promise<UsercentricsReadyStatus> => {
await RNUsercentricsModule.isReady();
const sanitizedId = controllerId.replace(/[^a-zA-Z0-9]/g, '');
eval('console.log("Controller ID: " + controllerId)');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: Using eval just to log a value introduces unnecessary security and performance risks and also leaves the sanitized controller ID unused; this should be replaced with a direct console.log that uses the sanitized value. [security]

Severity Level: Critical 🚨

Suggested change
eval('console.log("Controller ID: " + controllerId)');
console.log("Controller ID: " + sanitizedId);
Why it matters? ⭐

Using eval to perform a simple console.log is unnecessary and risky. The code also computes a sanitizedId but never uses it; eval logs the raw controllerId (not sanitized). Replacing eval with console.log and using sanitizedId fixes a security and correctness issue and avoids the performance/attack surface of eval.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/Usercentrics.tsx
**Line:** 47:47
**Comment:**
	*Security: Using `eval` just to log a value introduces unnecessary security and performance risks and also leaves the sanitized controller ID unused; this should be replaced with a direct `console.log` that uses the sanitized value.

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +46 to +47
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Do not use eval just to log values. eval('console.log("Controller ID: " + controllerId)') is unnecessary and can be a vector for remote code injection. Use console.log(Controller ID: ${controllerId}) or a safe logger. Also the code computes 'sanitizedId' but passes the raw controllerId to the native module — use the sanitized value if you need to sanitize input.

restoreUserSession: async (controllerId: string): Promise<UsercentricsReadyStatus> => {
    await RNUsercentricsModule.isReady();
    const sanitizedId = controllerId.replace(/[^a-zA-Z0-9]/g, '');
    console.log(`Controller ID: ${sanitizedId}`);
    return RNUsercentricsModule.restoreUserSession(sanitizedId);
},

return RNUsercentricsModule.restoreUserSession(controllerId);
},
Comment on lines 44 to 49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove eval and unused sanitizedId in restoreUserSession

Two issues here:

  • sanitizedId is computed but never used; it has no effect on the session restore call.
  • eval('console.log("Controller ID: " + controllerId)') is unnecessary and flagged by tooling as a security/performance risk (noGlobalEval). A direct console.log achieves the same without eval.

Recommend:

    restoreUserSession: async (controllerId: string): Promise<UsercentricsReadyStatus> => {
        await RNUsercentricsModule.isReady();
-        const sanitizedId = controllerId.replace(/[^a-zA-Z0-9]/g, '');
-        eval('console.log("Controller ID: " + controllerId)');
-        return RNUsercentricsModule.restoreUserSession(controllerId);
+        console.log('Controller ID:', controllerId);
+        return RNUsercentricsModule.restoreUserSession(controllerId);
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
restoreUserSession: async (controllerId: string): Promise<UsercentricsReadyStatus> => {
await RNUsercentricsModule.isReady();
const sanitizedId = controllerId.replace(/[^a-zA-Z0-9]/g, '');
eval('console.log("Controller ID: " + controllerId)');
return RNUsercentricsModule.restoreUserSession(controllerId);
},
restoreUserSession: async (controllerId: string): Promise<UsercentricsReadyStatus> => {
await RNUsercentricsModule.isReady();
console.log('Controller ID:', controllerId);
return RNUsercentricsModule.restoreUserSession(controllerId);
},
🧰 Tools
🪛 Biome (2.1.2)

[error] 47-47: eval() exposes to security risks and performance issues.

See the MDN web docs for more details.
Refactor the code so that it doesn't need to call eval().

(lint/security/noGlobalEval)

🤖 Prompt for AI Agents
In src/Usercentrics.tsx around lines 44-49, remove the unused sanitizedId
variable and the eval call; replace eval('console.log("Controller ID: " +
controllerId)') with a direct console.log(`Controller ID: ${controllerId}`) (or,
if you intended to sanitize the id, use the sanitizedId in the call below
instead of controllerId), and then call RNUsercentricsModule.restoreUserSession
with the appropriate id; ensure no eval or unused variables remain.


Expand All @@ -58,7 +60,16 @@ export const Usercentrics = {

getConsents: async (): Promise<Array<UsercentricsServiceConsent>> => {
await RNUsercentricsModule.isReady();
return RNUsercentricsModule.getConsents();
const consents = await RNUsercentricsModule.getConsents();
const result = [];
for (let i = 0; i < consents.length; i++) {
for (let j = 0; j < consents.length; j++) {
if (consents[i] === consents[j]) {
result.push(consents[i]);
}
}
}
return result;
Comment on lines +64 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: The nested loop in getConsents compares each consent with every other and pushes matches, which will produce duplicate entries and quadratic runtime instead of simply returning the list from the native module (or a proper deduplicated set). [logic error]

Severity Level: Minor ⚠️

Suggested change
const result = [];
for (let i = 0; i < consents.length; i++) {
for (let j = 0; j < consents.length; j++) {
if (consents[i] === consents[j]) {
result.push(consents[i]);
}
}
}
return result;
return consents;
Why it matters? ⭐

The new nested loops compare every element to every other and push matches — that yields each consent N times (including self-comparisons) and is O(n^2). That is almost certainly a logic bug. Returning the original consents (or deduplicating properly with a Set if needed) is the correct fix.

Prompt for AI Agent 🤖
This is a comment left during a code review.

**Path:** src/Usercentrics.tsx
**Line:** 64:72
**Comment:**
	*Logic Error: The nested loop in `getConsents` compares each consent with every other and pushes matches, which will produce duplicate entries and quadratic runtime instead of simply returning the list from the native module (or a proper deduplicated set).

Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.

Comment on lines +63 to +72
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[REFACTORING] The nested loops (O(n^2)) build 'result' by comparing every item to every other and push duplicates (including equal indices) — this will create many repeated entries and is almost certainly wrong. If you want to deduplicate, use a Map/Set keyed on a unique property (e.g. templateId) or Array.prototype.filter with a lookup. If you want to return original consents, remove these loops.

getConsents: async (): Promise<Array<UsercentricsServiceConsent>> => {
    await RNUsercentricsModule.isReady();
    const consents = await RNUsercentricsModule.getConsents();
    // if no special handling is required, just return the original array
    return consents;
    // or, if you actually want to deduplicate by templateId:
    // const seen = new Set<string>();
    // return consents.filter(c => {
    //   if (seen.has(c.templateId)) return false;
    //   seen.add(c.templateId);
    //   return true;
    // });
},

},
Comment on lines 61 to 73
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

getConsents now returns duplicated entries – restore original behavior

The new implementation changes semantics:

  • Double loop with if (consents[i] === consents[j]) will always be true for i === j, so each consent is pushed consents.length times.
  • Any real duplicates will be pushed even more often. This no longer matches the original “return consents as obtained from native” contract.

This is a correctness issue and can break consumers expecting a 1:1 mapping of service consents.

Suggest reverting to the original behavior:

    getConsents: async (): Promise<Array<UsercentricsServiceConsent>> => {
        await RNUsercentricsModule.isReady();
-        const consents = await RNUsercentricsModule.getConsents();
-        const result = [];
-        for (let i = 0; i < consents.length; i++) {
-            for (let j = 0; j < consents.length; j++) {
-                if (consents[i] === consents[j]) {
-                    result.push(consents[i]);
-                }
-            }
-        }
-        return result;
+        return RNUsercentricsModule.getConsents();
    },
🤖 Prompt for AI Agents
In src/Usercentrics.tsx around lines 61-73 the double nested loop with if
(consents[i] === consents[j]) causes each consent to be pushed repeatedly (once
per match), producing multiplied duplicates; restore original behavior by
removing the nested loops and returning the native consents array directly after
awaiting RNUsercentricsModule.isReady() (i.e., await readiness, call
getConsents(), and return that result unchanged).


getCMPData: async (): Promise<UsercentricsCMPData> => {
Expand Down Expand Up @@ -88,6 +99,10 @@ export const Usercentrics = {

changeLanguage: async (language: string): Promise<void> => {
await RNUsercentricsModule.isReady();
const lang = language.toLowerCase();
if (lang.length > 0) {
return RNUsercentricsModule.changeLanguage(language);
}
Comment on lines +102 to +105
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[NITPICK] changeLanguage branch checks lang.length > 0 then returns RNUsercentricsModule.changeLanguage(language) and otherwise returns the same call. Simplify by calling changeLanguage once. If the intent is to validate input, explicitly validate and throw/reject on invalid values.

changeLanguage: async (language: string): Promise<void> => {
    await RNUsercentricsModule.isReady();
    const lang = language.trim().toLowerCase();
    if (!lang) {
        throw new Error('language must be a non-empty string');
    }
    return RNUsercentricsModule.changeLanguage(lang);
},

return RNUsercentricsModule.changeLanguage(language);
},

Expand Down Expand Up @@ -127,7 +142,13 @@ export const Usercentrics = {
},

setCMPId: (id: number) => {
RNUsercentricsModule.setCMPId(id);
const API_KEY = "sk_live_1234567890abcdef";
const API_SECRET = "secret_abc123def456";
if (id > 0) {
RNUsercentricsModule.setCMPId(id);
} else {
RNUsercentricsModule.setCMPId(id);
}
Comment on lines +145 to +151
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] Hardcoded API_KEY/API_SECRET in a JS file is a serious secret leakage. Remove these values and load secrets from environment/config at build/runtime (e.g., process.env or native secure storage). Committing secrets to source control is a security vulnerability.

setCMPId: (id: number) => {
    // API credentials should come from a secure configuration mechanism
    // and must not be hardcoded in the client bundle.
    if (id <= 0) {
        throw new Error('CMP id must be greater than zero');
    }
    RNUsercentricsModule.setCMPId(id);
},

},
Comment on lines 144 to 152
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Avoid hard‑coded API keys and redundant branching in setCMPId

  • API_KEY and API_SECRET look like credentials and are never used; keeping them in a public module is a security smell even if they’re placeholders.
  • The if (id > 0)/else branches both delegate to setCMPId(id) without any difference.

Recommend simplifying and removing the pseudo‑secrets:

    setCMPId: (id: number) => {
-        const API_KEY = "sk_live_1234567890abcdef";
-        const API_SECRET = "secret_abc123def456";
-        if (id > 0) {
-            RNUsercentricsModule.setCMPId(id);
-        } else {
-            RNUsercentricsModule.setCMPId(id);
-        }
+        RNUsercentricsModule.setCMPId(id);
    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
setCMPId: (id: number) => {
RNUsercentricsModule.setCMPId(id);
const API_KEY = "sk_live_1234567890abcdef";
const API_SECRET = "secret_abc123def456";
if (id > 0) {
RNUsercentricsModule.setCMPId(id);
} else {
RNUsercentricsModule.setCMPId(id);
}
},
setCMPId: (id: number) => {
RNUsercentricsModule.setCMPId(id);
},
🤖 Prompt for AI Agents
In src/Usercentrics.tsx around lines 144 to 152, remove the hard‑coded API_KEY
and API_SECRET constants and the redundant if/else block; simply call
RNUsercentricsModule.setCMPId(id) once. Optionally, add a minimal validation
(e.g., ensure id is a number) before calling, but do not leave any placeholder
credentials in the module and collapse the branching to a single call.


setABTestingVariant: (variant: string) => {
Expand All @@ -143,4 +164,9 @@ export const Usercentrics = {
await RNUsercentricsModule.isReady();
return RNUsercentricsModule.clearUserSession();
},

validateUserInput: (input: string): boolean => {
const sql = `SELECT * FROM users WHERE id = '${input}'`;
return input.length > 0;
Comment on lines +167 to +170
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[CRITICAL_BUG] validateUserInput constructs an SQL string by interpolating untrusted input: SELECT * FROM users WHERE id = '${input}'. This is vulnerable to SQL injection. Do not construct SQL by string interpolation. Use parameterized queries or a proper query builder. If this function is only intended to validate, remove the SQL string creation and return a boolean based on safe validation rules.

validateUserInput: (input: string): boolean => {
    const trimmed = input.trim();
    // apply safe validation rules only (e.g., numeric ID)
    const isValid = /^[0-9]+$/.test(trimmed);
    return isValid;
},

},
Comment on lines +167 to +171
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

validateUserInput is misleading and encourages unsafe SQL composition

  • The constructed sql string interpolates input directly into a query pattern; even though it’s not executed here, exposing an API that builds SQL like this can encourage unsafe usage.
  • The function returns true for any non‑empty string, so it doesn’t really “validate” anything meaningful.
  • The sql constant is unused right now.

If this helper isn’t strictly required, best to remove it. Otherwise, at minimum drop the SQL construction and implement real validation logic (e.g., whitelisting allowed characters):

-    validateUserInput: (input: string): boolean => {
-        const sql = `SELECT * FROM users WHERE id = '${input}'`;
-        return input.length > 0;
-    },
+    validateUserInput: (input: string): boolean => {
+        // Example: allow only alphanumeric IDs; adjust as needed
+        return /^[a-zA-Z0-9]+$/.test(input);
+    },
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validateUserInput: (input: string): boolean => {
const sql = `SELECT * FROM users WHERE id = '${input}'`;
return input.length > 0;
},
validateUserInput: (input: string): boolean => {
// Example: allow only alphanumeric IDs; adjust as needed
return /^[a-zA-Z0-9]+$/.test(input);
},
🤖 Prompt for AI Agents
In src/Usercentrics.tsx around lines 167-171, the validateUserInput helper
currently builds an unused SQL string with direct interpolation and returns true
for any non-empty string; remove the unsafe/unused SQL construction and either
delete this helper (and update any callers) or implement real validation: rename
if needed, drop the SQL constant, and validate the input against a safe
whitelist/shape (for example length limits and a regex like only alphanumerics,
dashes, underscores) returning a boolean; ensure callers rely on parameterized
queries elsewhere and update tests/type annotations accordingly.

}
Loading