Skip to content

Commit 85cf71a

Browse files
authored
feat(keychain): tighten capacitor origin verification (#2375)
This PR tightens the Capacitor origin verification in the Keychain to only auto-verify the default 'localhost' origin. Custom Capacitor hostnames now require explicit authorization in the project's preset origins. ### Changes: - **Keychain**: Updated to check for exact match instead of any prefix. - **Capacitor Example**: - Updated to use a custom hostname . - Updated with security guidance on custom hostnames and how to authorize them in presets. This improves security by preventing malicious Capacitor apps from spoofing verified origins just by using the scheme.
1 parent 9708f87 commit 85cf71a

File tree

3 files changed

+38
-4
lines changed

3 files changed

+38
-4
lines changed

examples/capacitor/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,3 +78,32 @@ Add an intent filter for the custom scheme in
7878
intercept it with `@capacitor/browser` to ensure the system browser opens.
7979
- After authorizing the session in the browser, return to the app to complete
8080
the flow.
81+
82+
## Security and Custom Hostnames
83+
84+
By default, Capacitor apps use `localhost` as the hostname (`capacitor://localhost` on iOS). For production apps, it is recommended to set a custom hostname to prevent other Capacitor apps from potentially spoofing your origin.
85+
86+
1. Set a custom hostname and schemes in `capacitor.config.ts`:
87+
88+
```typescript
89+
server: {
90+
hostname: "my-custom-app",
91+
iosScheme: "capacitor",
92+
androidScheme: "https"
93+
}
94+
```
95+
96+
2. Add the hostname to the `origin` list in your Controller preset configuration. The Keychain will only verify custom Capacitor origins if the hostname is explicitly authorized in your preset.
97+
98+
Example preset configuration:
99+
100+
```json
101+
{
102+
"origin": ["my-custom-app"],
103+
...
104+
}
105+
```
106+
107+
This ensures that your app is verified on both iOS (`capacitor://my-custom-app`) and Android (`https://my-custom-app`).
108+
109+
Note: Default `localhost` origins are always allowed for development convenience.

examples/capacitor/capacitor.config.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ const config: CapacitorConfig = {
44
appId: "gg.cartridge.controller.capacitor",
55
appName: "Cartridge Session",
66
webDir: "dist",
7+
server: {
8+
hostname: "controller-capacitor", // Recommended: Set a custom hostname for production
9+
androidScheme: "https",
10+
iosScheme: "capacitor",
11+
},
712
};
813

914
export default config;

packages/keychain/src/hooks/connection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -441,10 +441,10 @@ export function useConnectionValue() {
441441
const redirectUrlObj = new URL(redirectUrl);
442442
const redirectOrigin = redirectUrlObj.origin;
443443

444-
// Always consider localhost and capacitor as verified for development
444+
// Always consider localhost and default capacitor as verified for development
445445
const isLocalhost =
446446
redirectOrigin.includes("localhost") ||
447-
redirectOrigin.startsWith("capacitor://");
447+
redirectOrigin === "capacitor://localhost";
448448
const isOriginAllowed = isOriginVerified(
449449
redirectOrigin,
450450
allowedOrigins,
@@ -469,10 +469,10 @@ export function useConnectionValue() {
469469
}
470470

471471
// Embedded mode: verify against parent origin
472-
// Always consider localhost and capacitor as verified for development (not 127.0.0.1)
472+
// Always consider localhost and default capacitor as verified for development (not 127.0.0.1)
473473
if (origin) {
474474
const isLocalhost =
475-
origin.includes("localhost") || origin.startsWith("capacitor://");
475+
origin.includes("localhost") || origin === "capacitor://localhost";
476476
const isOriginAllowed = isOriginVerified(origin, allowedOrigins);
477477
const finalVerified = isLocalhost || isOriginAllowed;
478478
setVerified(finalVerified);

0 commit comments

Comments
 (0)