security: rotate CVM identity and externalize signing
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run

This commit is contained in:
SkateSpots 2026-11-01 17:22:15 +07:00
parent ab90e02f69
commit 3f28c3fdb4
6 changed files with 45 additions and 25 deletions

View file

@ -2405,7 +2405,7 @@ The committed `package-lock.json` (from commit `f25fc2b`) had entries stripped o
### Android release build
- `npm run android:build` → `android/app/build/outputs/apk/release/app-release.apk`.
- **Versioning**: `package.json` `version` is the single source of truth; `scripts/sync-android-version.mjs` syncs it to `versionName`/`versionCode` (e.g. `1.4.5` → `10405`) during the build. Bump **only** `package.json`.
- **Signing**: secrets live in the gitignored `keystore.properties`; `scripts/inject-signing-config.mjs` injects a managed block into `android/app/build.gradle` (runs in `android:init`/`android:sync`/`android:build`). The authoritative keystore is **`/home/sovereignsk8/skatespots-release.jks`** (alias `skatespots`). An older repo-root copy had a wrong password and was retired to `~/.backups/keystore/`.
- **Signing**: secrets live **outside the repo** at `~/.config/skatespots/android/keystore.properties`; `scripts/inject-signing-config.mjs` injects a managed block into `android/app/build.gradle` (runs in `android:init`/`android:sync`/`android:build`). The authoritative keystore is **`~/.config/skatespots/android/skatespots-release.jks`** (alias `skatespots`, mode 600). An older repo-root copy had a wrong password and was retired to `~/.backups/keystore/`. Never put signing material back in the repo: Vite serves files by filesystem path, not by `.gitignore`.
- `android/` is **generated and gitignored**; recreate with `npm run android:init`.
- **Launch splash:** `resources/splash.png` is the orange brand wheel. Capacitor's
generated drawables used a stock blue placeholder, so `scripts/make-android-splash.mjs`
@ -3431,7 +3431,7 @@ The community relay moved off the hosted relay.tools instance
- `git push` may print a benign `cannot lock ref ... incorrect old value provided` retry warning; verify with `git fetch origin && git rev-list --left-right --count origin/main...HEAD`.
### Secrets — never commit or paste
`keystore.properties`, `*.jks`, `.env*`, and `android/` are gitignored. Never paste the nsec or keystore password into chat, logs, or command history — keep them in a password manager.
Signing material (`keystore.properties`, `*.jks`) lives **outside the repo** at `~/.config/skatespots/android/`; `*.jks`, `.env*`, and `android/` remain gitignored as a safety net. Never paste the nsec or keystore password into chat, logs, or command history — keep them in a password manager. Do not bind the Vite dev server to the LAN; use `adb reverse` for device testing.
## Project Structure

View file

@ -156,16 +156,18 @@ Or use Android Studio: right-click `app` → New → Image Asset → import `pub
## Step 5 — Generate a signing keystore (one-time)
```bash
install -d -m 700 ~/.config/skatespots/android
keytool -genkey -v \
-keystore skatespots-release.jks \
-keystore ~/.config/skatespots/android/skatespots-release.jks \
-alias skatespots \
-keyalg RSA \
-keysize 2048 \
-validity 10000 \
-dname "CN=SkateSpots, OU=SkateSpots, O=SkateSpots, L=Bangkok, S=Bangkok, C=TH"
chmod 600 ~/.config/skatespots/android/skatespots-release.jks
```
**Save the keystore file and passwords somewhere safe — you need them for every future release.**
Put the passwords in `~/.config/skatespots/android/keystore.properties` (mode 600), not in the repo. **Save the keystore file and passwords somewhere safe — you need them for every future release.**
---
@ -175,18 +177,7 @@ keytool -genkey -v \
npm run android:build
```
Or manually with signing:
```bash
npm run build
npx cap sync android
cd android
./gradlew assembleRelease \
-Pandroid.injected.signing.store.file=../skatespots-release.jks \
-Pandroid.injected.signing.store.password=YOUR_STORE_PASSWORD \
-Pandroid.injected.signing.key.alias=skatespots \
-Pandroid.injected.signing.key.password=YOUR_KEY_PASSWORD
```
`npm run android:build` injects the external signing config automatically. Do **not** pass signing passwords as `-Pandroid.injected...` command-line properties; that exposes them in shell history and process listings.
APK output: `android/app/build/outputs/apk/release/app-release.apk`

View file

@ -15,7 +15,12 @@
* 1. Removes any previously managed signing block.
* 2. Removes a legacy hardcoded signingConfigs block (inline passwords).
* 3. Writes a managed signingConfigs block into android/app/build.gradle that
* reads secrets from the gitignored keystore.properties at the repo root.
* reads secrets from a keystore.properties file outside the repo.
*
* The signing material is deliberately outside the Vite project root:
* ~/.config/skatespots/android/keystore.properties
* ~/.config/skatespots/android/skatespots-release.jks
* Override the properties path with SKATESPOTS_KEYSTORE_PROPERTIES.
*
* It exits quietly (0) when android/ isn't generated yet, or when
* keystore.properties is absent — so debug-only builds without a keystore still
@ -23,11 +28,16 @@
*/
import { readFileSync, writeFileSync, existsSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';
import { dirname, join, resolve } from 'node:path';
import { homedir } from 'node:os';
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
const buildGradle = join(root, 'android', 'app', 'build.gradle');
const keystoreProps = join(root, 'keystore.properties');
const keystoreProps = resolve(
process.env.SKATESPOTS_KEYSTORE_PROPERTIES ??
join(homedir(), '.config', 'skatespots', 'android', 'keystore.properties'),
);
const gradleString = (value) => value.replace(/\\/g, '\\\\').replace(/'/g, "\\'");
const BEGIN = '// >>> managed by scripts/inject-signing-config.mjs';
const END = '// <<< managed by scripts/inject-signing-config.mjs <<<';
@ -43,10 +53,11 @@ if (!existsSync(keystoreProps)) {
const managedLines = [
`${BEGIN} >>>`,
'// Release signing is read from keystore.properties at the repo root',
'// (gitignored). Edit that file, not this block.',
'// Release signing is read from keystore.properties outside the repo.',
'// Edit that file, not this block.',
'def _keystoreProps = new Properties()',
"def _keystorePropsFile = rootProject.file('../keystore.properties')",
"def _keystorePropsPath = System.getenv('SKATESPOTS_KEYSTORE_PROPERTIES')",
`def _keystorePropsFile = new File(_keystorePropsPath ?: '${gradleString(keystoreProps)}')`,
'if (_keystorePropsFile.exists()) {',
' _keystorePropsFile.withInputStream { _keystoreProps.load(it) }',
'}',

View file

@ -20,7 +20,7 @@ import { useNostr } from '@nostrify/react';
/** The SkateSpots CVM server identity (see contextvm.org/s/<pubkey>). */
export const CVM_SERVER_PUBKEY =
'e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530';
'940bf0e4fbc64b6c0f09d48c3264723c524a31b5ad0d1aaec361263731689d95';
/** The relay the CVM server announces on. */
export const CVM_RELAY = 'wss://relay.contextvm.org';

View file

@ -279,7 +279,7 @@ Version: v0.1.0`;
{ label: t('settingsUi.about.creditMapData'), name: 'BTCMap', url: 'https://btcmap.org/', desc: t('settingsUi.about.creditBtcmap') },
{ label: t('settingsUi.about.creditCrowdfunding'), name: 'Angor', url: 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z', desc: t('settingsUi.about.creditAngor') },
{ label: t('settingsUi.about.creditDistributed'), name: 'Zapstore', url: 'https://zapstore.dev/apps/org.skatespots.app', desc: t('settingsUi.about.creditZapstore') },
{ label: t('settingsUi.about.creditTrust'), name: 'ContextVM', url: 'https://contextvm.org/s/e3564bb877adb0c99a7e4973ccb22e516d7d9eca0027d0bd62a04174ed670530', desc: t('settingsUi.about.creditContextvm') },
{ label: t('settingsUi.about.creditTrust'), name: 'ContextVM', url: 'https://contextvm.org/s/940bf0e4fbc64b6c0f09d48c3264723c524a31b5ad0d1aaec361263731689d95', desc: t('settingsUi.about.creditContextvm') },
].map((item) => (
<a
key={item.name}

View file

@ -12,8 +12,26 @@ export default defineConfig(() => ({
// The regular web deploy keeps '/' (served from a domain root).
base: process.env.CAPACITOR_BUILD ? './' : '/',
server: {
host: "::",
// Keep the dev server off the LAN. The phone preview uses localhost, and
// native testing should use `adb reverse` rather than exposing Vite.
host: "127.0.0.1",
port: 8080,
fs: {
strict: true,
// Defence in depth: secrets live outside the repo, but never serve them
// even if someone temporarily copies one back in.
deny: [
'.env',
'.env.*',
'*.{crt,pem}',
'**/.git/**',
'**/keystore.properties',
'**/*.jks',
'**/*.keystore',
'**/*.p12',
'**/*.key',
],
},
// Dev proxy for the MintRadar directory (no CORS upstream; see public/_redirects).
proxy: {
'/api/mints': {