Skip to content

Feat: disable default iOS URLCache #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
- [Prevent "recent screenshots"](#prevent-recent-screenshots)
- [Configuration](#configuration-2)
- [Safe Keyboard Detector](#safe-keyboard-detector)
- [[EXPERIMENTAL - iOS only] Disable Default Caching in `Cache.db`](#experimental---ios-only-disable-default-caching-in-cachedb)
- [Contributing](#contributing)
- [👉 About BAM](#-about-bam)

Expand Down Expand Up @@ -182,6 +183,39 @@ if (!isInDefaultSafeList) {
SafeKeyboardDetector.showInputMethodPicker(); // can only be called on Android
```

## [EXPERIMENTAL - iOS only] Disable Default Caching in `Cache.db`
> ⚠️ **DISCLAIMER:** This experimental feature may impact app behavior. Use it at your own risk. Disabling caching can cause unexpected issues.
>
> **Possible side effects:**
> - Slower performance due to lack of cached responses
> - Higher network usage from repeated requests
> - Crashes in components expecting cached data
> - Features failing in offline mode

> **🥷 Threat:** On iOS, every `NSURL` request may be cached by default in `Cache.db`, potentially storing sensitive data unless explicitly disabled. This can lead to unintentional data leaks.

Mitigating this threat is achieved by:

- Fully clearing the existing cache
- Remove the cache by setting it to an empty cache:

```swift
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
```
### Configuration
If you want to enable this functionality, it need to be enabled in the app configuration file (by default it's disabled)

```jsonc
[
"@bam.tech/react-native-app-security",
{
"disableCache": {
"ios": { "enabled": true },
}
}
]
```

# Contributing

Contributions are welcome. See the [Expo modules docs](https://docs.expo.dev/modules/get-started/) for information on how to build/run/develop on the project.
Expand Down
5 changes: 5 additions & 0 deletions example/app.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ const config: ExpoConfig = {
enabled: true,
},
},
disableCache: {
ios: {
enabled: true,
},
},
},
],
"expo-router",
Expand Down
18 changes: 18 additions & 0 deletions example/app/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ export default function App() {
onPress={() => SafeKeyboardDetector.showInputMethodPicker()}
/>
) : null}
{Platform.OS === "ios" ? (
<Button title="fake login route call" onPress={callLoginRoute} />
) : null}
</View>
);
}
Expand Down Expand Up @@ -114,3 +117,18 @@ const checkIsKeyboardSafe = () => {
console.log(SafeKeyboardDetector.getCurrentInputMethodInfo().inputMethodId);
console.warn("is Keyboard safe", isKeyboardSafe);
};

const callLoginRoute = async () => {
try {
await fetch("http://localhost:8081/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
email: "[email protected]",
password: "a super strong password",
}),
});
} catch (error) {}
};
26 changes: 21 additions & 5 deletions ios/RNASAppLifecyleDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,22 @@ public class RNASAppLifecycleDelegate: ExpoAppDelegateSubscriber {
return window
}()

public func applicationDidFinishLaunching(_ application: UIApplication) {
if(!isPreventRecentScreenshotsEnabled()) {
return
public func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
if(isPreventRecentScreenshotsEnabled()) {
application.ignoreSnapshotOnNextApplicationLaunch()
}

application.ignoreSnapshotOnNextApplicationLaunch()

if(isDisablingCacheEnabled()){
clearAndDisableCache()
}


return true
}

private func clearAndDisableCache() {
URLCache.shared.removeAllCachedResponses()
URLCache.shared = URLCache(memoryCapacity: 0, diskCapacity: 0, diskPath: nil)
}

public func applicationWillResignActive(_ application: UIApplication) {
Expand All @@ -41,3 +51,9 @@ func isPreventRecentScreenshotsEnabled() -> Bool {
return false
}

func isDisablingCacheEnabled() -> Bool {
if let value = Bundle.main.object(forInfoDictionaryKey: "RNAS_DISABLE_CACHE") as? Bool {
return value
}
return false
}
2 changes: 2 additions & 0 deletions plugin/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { ConfigPlugin } from "@expo/config-plugins";
import { RNASConfig } from "./types";
import withDisableCache from "./withDisableCache";
import withpreventRecentScreenshots from "./withPreventRecentScreenshots";
import withSSLPinning from "./withSSLPinning";

const withRNAS: ConfigPlugin<RNASConfig> = (config, props) => {
config = withSSLPinning(config, props.sslPinning);

config = withpreventRecentScreenshots(config, props.preventRecentScreenshots);
config = withDisableCache(config, props.disableCache);

return config;
};
Expand Down
3 changes: 3 additions & 0 deletions plugin/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ export type RNASConfig = {
ios?: { enabled: boolean };
android?: { enabled: boolean };
};
disableCache?: {
ios?: { enabled: boolean };
};
};
25 changes: 25 additions & 0 deletions plugin/src/withDisableCache.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { ConfigPlugin, withInfoPlist } from "@expo/config-plugins";
import { RNASConfig } from "./types";

type Props = RNASConfig["preventRecentScreenshots"];

const withDisableCache: ConfigPlugin<Props> = (config, props) => {
config = withInfoPlist(config, (config) => {
const infoPlist = config.modResults;

const isEnabled = props?.ios?.enabled ?? false;

if (!isEnabled) {
delete infoPlist.RNAS_DISABLE_CACHE;
return config;
}

infoPlist.RNAS_DISABLE_CACHE = true;

return config;
});

return config;
};

export default withDisableCache;