M .gitignore => .gitignore +1 -0
@@ 15,3 15,4 @@ bun.lock
.env.local
.env.*
!.env.example
+supabase/.temp/
M CHANGELOG.md => CHANGELOG.md +7 -0
@@ 1,5 1,12 @@
# Changelog
+## [0.11.16.1] - 2026-03-24 — Installation ID Privacy Fix
+
+### Fixed
+
+- **Installation IDs are now random UUIDs instead of hostname hashes.** The old `SHA-256(hostname+username)` approach meant anyone who knew your machine identity could compute your installation ID. Now uses a random UUID stored in `~/.gstack/installation-id` — not derivable from any public input, rotatable by deleting the file.
+- **RLS verification script handles edge cases.** `verify-rls.sh` now correctly treats INSERT success as expected (kept for old client compat), handles 409 conflicts and 204 no-ops.
+
## [0.11.16.0] - 2026-03-24 — Telemetry Security Hardening
### Fixed
M VERSION => VERSION +1 -1
@@ 1,1 1,1 @@
-0.11.16.0
+0.11.16.1
M bin/gstack-telemetry-log => bin/gstack-telemetry-log +20 -9
@@ 106,18 106,29 @@ if [ -d "$STATE_DIR/sessions" ]; then
fi
# Generate installation_id for community tier
+# Uses a random UUID stored locally — not derived from hostname/user so it
+# can't be guessed or correlated by someone who knows your machine identity.
INSTALL_ID=""
if [ "$TIER" = "community" ]; then
- HOST="$(hostname 2>/dev/null || echo "unknown")"
- USER="$(whoami 2>/dev/null || echo "unknown")"
- if command -v shasum >/dev/null 2>&1; then
- INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | shasum -a 256 | awk '{print $1}')"
- elif command -v sha256sum >/dev/null 2>&1; then
- INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | sha256sum | awk '{print $1}')"
- elif command -v openssl >/dev/null 2>&1; then
- INSTALL_ID="$(printf '%s-%s' "$HOST" "$USER" | openssl dgst -sha256 | awk '{print $NF}')"
+ ID_FILE="$HOME/.gstack/installation-id"
+ if [ -f "$ID_FILE" ]; then
+ INSTALL_ID="$(cat "$ID_FILE" 2>/dev/null)"
+ fi
+ if [ -z "$INSTALL_ID" ]; then
+ # Generate a random UUID v4
+ if command -v uuidgen >/dev/null 2>&1; then
+ INSTALL_ID="$(uuidgen | tr '[:upper:]' '[:lower:]')"
+ elif [ -r /proc/sys/kernel/random/uuid ]; then
+ INSTALL_ID="$(cat /proc/sys/kernel/random/uuid)"
+ else
+ # Fallback: random hex from /dev/urandom
+ INSTALL_ID="$(od -An -tx1 -N16 /dev/urandom 2>/dev/null | tr -d ' \n')"
+ fi
+ if [ -n "$INSTALL_ID" ]; then
+ mkdir -p "$(dirname "$ID_FILE")" 2>/dev/null
+ printf '%s' "$INSTALL_ID" > "$ID_FILE" 2>/dev/null
+ fi
fi
- # If no SHA-256 command available, install_id stays empty
fi
# Local-only fields (never sent remotely)
M test/telemetry.test.ts => test/telemetry.test.ts +2 -2
@@ 78,8 78,8 @@ describe('gstack-telemetry-log', () => {
const events = parseJsonl();
expect(events).toHaveLength(1);
- // installation_id should be a SHA-256 hash (64 hex chars)
- expect(events[0].installation_id).toMatch(/^[a-f0-9]{64}$/);
+ // installation_id should be a UUID v4 (or hex fallback)
+ expect(events[0].installation_id).toMatch(/^[a-f0-9-]{32,36}$/);
});
test('installation_id is null for anonymous tier', () => {