@capgo/capacitor-ssl-pinning 8.1.9 → 8.1.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -3
- package/package.json +2 -1
- package/scripts/check-cap9-deprecated.mjs +304 -0
package/README.md
CHANGED
|
@@ -1,11 +1,29 @@
|
|
|
1
1
|
# @capgo/capacitor-ssl-pinning
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
Add SSL certificate pinning to your Capacitor app's native HTTP requests on iOS and Android, so your API traffic only trusts your own certificates.
|
|
4
|
+
|
|
5
|
+
<a href="https://capgo.app/?ref=plugin_ssl_pinning"><img src="https://capgo.app/readme-banner.svg?repo=Cap-go/capacitor-ssl-pinning" alt="Capgo - Instant updates for Capacitor" /></a>
|
|
3
6
|
|
|
4
7
|
<div align="center">
|
|
5
|
-
<
|
|
6
|
-
<h2><a href="https://capgo.app/
|
|
8
|
+
<p><b>Capgo</b>: open-source live updates for Ionic and Capacitor apps. Ship OTA fixes and features instantly, without waiting for app store review.</p>
|
|
9
|
+
<h2><a href="https://capgo.app/register/?ref=plugin_ssl_pinning">➡️ Get started for free</a></h2>
|
|
10
|
+
<p>14-day unlimited free trial. No credit card required</p>
|
|
11
|
+
<p><a href="https://capgo.app/consulting/?ref=plugin_ssl_pinning">Missing a feature? We'll build the plugin for you 💪</a></p>
|
|
7
12
|
</div>
|
|
8
13
|
|
|
14
|
+
<p align="center">
|
|
15
|
+
<img src="https://raw.githubusercontent.com/Cap-go/capacitor-ssl-pinning/main/assets/github-social-preview.png" alt="@capgo/capacitor-ssl-pinning for Capacitor apps" width="300" />
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
## Key features
|
|
19
|
+
|
|
20
|
+
- **Config only**: list certificate files under `plugins.SSLPinning.certs` in `capacitor.config`.
|
|
21
|
+
- **CapacitorHttp**: pinning applies to `CapacitorHttp` requests when it is enabled and at least one certificate is listed.
|
|
22
|
+
- **Sync hook**: `cap sync` copies the certificates and converts PEM files to DER for iOS.
|
|
23
|
+
- **Exclusions**: `excludedDomains` lets chosen URLs skip pinning.
|
|
24
|
+
- **Inspect**: `getConfiguration()` returns the active native configuration.
|
|
25
|
+
- **Platforms**: iOS and Android. Not available on web.
|
|
26
|
+
|
|
9
27
|
Capgo SSL Pinning brings certificate pinning to Capacitor 8 apps by integrating with `CapacitorHttp` on Android and iOS.
|
|
10
28
|
|
|
11
29
|
The implementation follows the Ionic SSL pinning install flow and uses the same `plugins.SSLPinning` configuration shape documented at [ionic.io](https://ionic.io/docs/ssl-pinning/install).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@capgo/capacitor-ssl-pinning",
|
|
3
|
-
"version": "8.1.
|
|
3
|
+
"version": "8.1.12",
|
|
4
4
|
"description": "Capacitor SSL pinning plugin for Android and iOS that integrates with CapacitorHttp.",
|
|
5
5
|
"main": "dist/plugin.cjs.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"watch": "tsc --watch",
|
|
52
52
|
"prepublishOnly": "bun run build",
|
|
53
53
|
"check:wiring": "node scripts/check-capacitor-plugin-wiring.mjs",
|
|
54
|
+
"check:cap9-deprecated": "node scripts/check-cap9-deprecated.mjs",
|
|
54
55
|
"prepare:certs": "bun run scripts/prepare-certs.mjs",
|
|
55
56
|
"capacitor:sync:before": "bun run prepare:certs",
|
|
56
57
|
"capacitor:update:before": "bun run prepare:certs",
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Capacitor 9 deprecated native API guard.
|
|
4
|
+
*
|
|
5
|
+
* Fails when plugin native sources still use APIs removed in Capacitor 9.
|
|
6
|
+
* Does not flag Cordova SwiftPM product dependencies (still required on Cap 8).
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node scripts/check-cap9-deprecated.mjs
|
|
10
|
+
* node scripts/check-cap9-deprecated.mjs --workspace
|
|
11
|
+
* node scripts/check-cap9-deprecated.mjs --dir path
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import fs from "node:fs";
|
|
15
|
+
import path from "node:path";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
17
|
+
|
|
18
|
+
const SKIP_DIRS = new Set([
|
|
19
|
+
"node_modules",
|
|
20
|
+
"dist",
|
|
21
|
+
"build",
|
|
22
|
+
".build",
|
|
23
|
+
".gradle",
|
|
24
|
+
"Pods",
|
|
25
|
+
"DerivedData",
|
|
26
|
+
".swiftpm",
|
|
27
|
+
".git",
|
|
28
|
+
"example-app",
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
/** @type {{ id: string, pattern: RegExp, exts: string[], ignoreLine?: RegExp }[]} */
|
|
32
|
+
const RULES = [
|
|
33
|
+
{
|
|
34
|
+
id: "hasOption",
|
|
35
|
+
pattern: /\bhasOption\s*\(/,
|
|
36
|
+
exts: [".java", ".kt", ".swift"],
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: "getConfigValue",
|
|
40
|
+
pattern: /\bgetConfigValue\s*\(/,
|
|
41
|
+
exts: [".java", ".kt", ".swift"],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: "@NativePlugin",
|
|
45
|
+
pattern: /@NativePlugin\b/,
|
|
46
|
+
exts: [".java", ".kt"],
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: "saveCall",
|
|
50
|
+
pattern: /\bsaveCall\s*\(/,
|
|
51
|
+
exts: [".java", ".kt", ".swift"],
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: "getSavedCall",
|
|
55
|
+
pattern: /\bgetSavedCall\s*\(/,
|
|
56
|
+
exts: [".java", ".kt", ".swift"],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
id: "freeSavedCall",
|
|
60
|
+
pattern: /\bfreeSavedCall\s*\(/,
|
|
61
|
+
exts: [".java", ".kt", ".swift"],
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: "releaseCall",
|
|
65
|
+
pattern: /\breleaseCall\s*\(/,
|
|
66
|
+
exts: [".java", ".kt", ".swift"],
|
|
67
|
+
ignoreLine: /\.releaseCall\s*\(\s*withID:/,
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
id: "pluginRequestPermission",
|
|
71
|
+
pattern: /\bpluginRequestPermissions?\s*\(/,
|
|
72
|
+
exts: [".java", ".kt"],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: "pluginRequestAllPermissions",
|
|
76
|
+
pattern: /\bpluginRequestAllPermissions\s*\(/,
|
|
77
|
+
exts: [".java", ".kt"],
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
id: "hasDefinedPermissions",
|
|
81
|
+
pattern: /\bhasDefinedPermissions\s*\(/,
|
|
82
|
+
exts: [".java", ".kt"],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: "CAPBridge",
|
|
86
|
+
pattern: /\bCAPBridge\./,
|
|
87
|
+
exts: [".swift"],
|
|
88
|
+
ignoreLine: /CAPBridgedPlugin/,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "CAPNotifications",
|
|
92
|
+
pattern: /\bCAPNotifications\b/,
|
|
93
|
+
exts: [".swift"],
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
const CORDova_SPM_LINE =
|
|
98
|
+
/\.product\s*\(\s*name\s*:\s*"Cordova"\s*,\s*package\s*:\s*"capacitor-swift-pm"\s*\)/;
|
|
99
|
+
|
|
100
|
+
function readText(p) {
|
|
101
|
+
try {
|
|
102
|
+
return fs.readFileSync(p, "utf8");
|
|
103
|
+
} catch {
|
|
104
|
+
return "";
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function exists(p) {
|
|
109
|
+
try {
|
|
110
|
+
fs.accessSync(p);
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function parseArgs(argv) {
|
|
118
|
+
const out = { dir: process.cwd(), workspace: false };
|
|
119
|
+
for (let i = 2; i < argv.length; i++) {
|
|
120
|
+
const a = argv[i];
|
|
121
|
+
if (a === "--workspace") {
|
|
122
|
+
out.workspace = true;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
if (a === "--dir" || a === "--pluginDir") {
|
|
126
|
+
out.dir = path.resolve(argv[++i] || ".");
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return out;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function walkFiles(rootDir, exts) {
|
|
134
|
+
const out = [];
|
|
135
|
+
const stack = [rootDir];
|
|
136
|
+
while (stack.length) {
|
|
137
|
+
const dir = stack.pop();
|
|
138
|
+
let entries;
|
|
139
|
+
try {
|
|
140
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
141
|
+
} catch {
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
for (const e of entries) {
|
|
145
|
+
if (e.isDirectory()) {
|
|
146
|
+
if (SKIP_DIRS.has(e.name)) continue;
|
|
147
|
+
stack.push(path.join(dir, e.name));
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
if (!e.isFile()) continue;
|
|
151
|
+
for (const ext of exts) {
|
|
152
|
+
if (e.name.endsWith(ext)) {
|
|
153
|
+
out.push(path.join(dir, e.name));
|
|
154
|
+
break;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
out.sort();
|
|
160
|
+
return out;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function collectScanRoots(pluginDir, cap) {
|
|
164
|
+
const roots = [];
|
|
165
|
+
if (cap.android) {
|
|
166
|
+
const androidMain = path.join(pluginDir, "android", "src", "main");
|
|
167
|
+
if (exists(androidMain)) roots.push(androidMain);
|
|
168
|
+
}
|
|
169
|
+
if (cap.ios) {
|
|
170
|
+
const iosSources = path.join(pluginDir, "ios", "Sources");
|
|
171
|
+
if (exists(iosSources)) roots.push(iosSources);
|
|
172
|
+
else {
|
|
173
|
+
const iosDir = path.join(pluginDir, "ios");
|
|
174
|
+
if (exists(iosDir)) roots.push(iosDir);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const packageSwift = path.join(pluginDir, "Package.swift");
|
|
178
|
+
if (exists(packageSwift)) roots.push(packageSwift);
|
|
179
|
+
return roots;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function scanFile(filePath, rule) {
|
|
183
|
+
const ext = path.extname(filePath);
|
|
184
|
+
if (!rule.exts.includes(ext)) return [];
|
|
185
|
+
|
|
186
|
+
const txt = readText(filePath);
|
|
187
|
+
const lines = txt.split(/\r?\n/);
|
|
188
|
+
const hits = [];
|
|
189
|
+
for (let i = 0; i < lines.length; i++) {
|
|
190
|
+
const line = lines[i];
|
|
191
|
+
if (filePath.endsWith("Package.swift") && CORDova_SPM_LINE.test(line)) {
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (rule.ignoreLine?.test(line)) continue;
|
|
195
|
+
if (rule.pattern.test(line)) {
|
|
196
|
+
hits.push({ line: i + 1, text: line.trim() });
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return hits;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function listWorkspacePluginDirs(repoRoot) {
|
|
203
|
+
const packagesRoot = path.join(repoRoot, "packages");
|
|
204
|
+
if (!exists(packagesRoot)) return [];
|
|
205
|
+
return fs
|
|
206
|
+
.readdirSync(packagesRoot, { withFileTypes: true })
|
|
207
|
+
.filter((e) => e.isDirectory())
|
|
208
|
+
.map((e) => path.join(packagesRoot, e.name))
|
|
209
|
+
.filter((dir) => {
|
|
210
|
+
const pkgPath = path.join(dir, "package.json");
|
|
211
|
+
if (!exists(pkgPath)) return false;
|
|
212
|
+
try {
|
|
213
|
+
const pkg = JSON.parse(readText(pkgPath));
|
|
214
|
+
const cap = typeof pkg.capacitor === "object" && pkg.capacitor ? pkg.capacitor : {};
|
|
215
|
+
return Boolean(cap.android || cap.ios);
|
|
216
|
+
} catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
.sort();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* @returns {boolean} true when the plugin passes
|
|
225
|
+
*/
|
|
226
|
+
function checkPluginDir(pluginDir) {
|
|
227
|
+
const pkgPath = path.join(pluginDir, "package.json");
|
|
228
|
+
|
|
229
|
+
if (!exists(pkgPath)) {
|
|
230
|
+
console.error(`[cap9-deprecated] ERROR: missing package.json in ${pluginDir}`);
|
|
231
|
+
return false;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
let pkg;
|
|
235
|
+
try {
|
|
236
|
+
pkg = JSON.parse(readText(pkgPath));
|
|
237
|
+
} catch (e) {
|
|
238
|
+
console.error(`[cap9-deprecated] ERROR: invalid package.json (${pkgPath}): ${e?.message || e}`);
|
|
239
|
+
return false;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const cap = typeof pkg.capacitor === "object" && pkg.capacitor ? pkg.capacitor : {};
|
|
243
|
+
if (!cap.android && !cap.ios) {
|
|
244
|
+
return true;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const scanRoots = collectScanRoots(pluginDir, cap);
|
|
248
|
+
const allExts = [...new Set(RULES.flatMap((r) => r.exts))];
|
|
249
|
+
const files = [];
|
|
250
|
+
for (const root of scanRoots) {
|
|
251
|
+
if (root.endsWith("Package.swift")) {
|
|
252
|
+
files.push(root);
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
files.push(...walkFiles(root, allExts));
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const violations = [];
|
|
259
|
+
for (const file of files) {
|
|
260
|
+
for (const rule of RULES) {
|
|
261
|
+
const hits = scanFile(file, rule);
|
|
262
|
+
for (const hit of hits) {
|
|
263
|
+
violations.push({
|
|
264
|
+
rule: rule.id,
|
|
265
|
+
file: path.relative(pluginDir, file),
|
|
266
|
+
line: hit.line,
|
|
267
|
+
text: hit.text,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (violations.length) {
|
|
274
|
+
const relDir = path.relative(process.cwd(), pluginDir) || ".";
|
|
275
|
+
console.error(`[cap9-deprecated] FAIL in ${relDir}`);
|
|
276
|
+
for (const v of violations) {
|
|
277
|
+
console.error(`- ${v.rule}: ${v.file}:${v.line}: ${v.text}`);
|
|
278
|
+
}
|
|
279
|
+
return false;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
286
|
+
const args = parseArgs(process.argv);
|
|
287
|
+
|
|
288
|
+
if (args.workspace) {
|
|
289
|
+
const pluginDirs = listWorkspacePluginDirs(repoRoot);
|
|
290
|
+
if (!pluginDirs.length) {
|
|
291
|
+
console.error("[cap9-deprecated] ERROR: no Capacitor plugin packages found under packages/*");
|
|
292
|
+
process.exit(2);
|
|
293
|
+
}
|
|
294
|
+
let failed = false;
|
|
295
|
+
for (const dir of pluginDirs) {
|
|
296
|
+
if (!checkPluginDir(dir)) failed = true;
|
|
297
|
+
}
|
|
298
|
+
if (failed) process.exit(1);
|
|
299
|
+
console.log(`[cap9-deprecated] OK (${pluginDirs.length} plugin package(s) scanned)`);
|
|
300
|
+
process.exit(0);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const ok = checkPluginDir(args.dir);
|
|
304
|
+
process.exit(ok ? 0 : 1);
|