Skip to content

Commit 6a98293

Browse files
authored
Feature/analytics and csp (#151)
* chore: release prep for v1.25.0 * docs: corrected changelog * chore: updated service-worker.js cache, posthog.js, and env.js * docs: updated changelog
1 parent 4b1695e commit 6a98293

File tree

9 files changed

+241
-67
lines changed

9 files changed

+241
-67
lines changed

.github/workflows/branch-guard.yml

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,30 @@ jobs:
2424
steps:
2525
- name: Check commit source
2626
run: |
27-
# Only trigger warning if commit wasn't from a merge or bot
28-
if [[ "${{ github.event.head_commit.message }}" != *"Merge pull request"* ]] && \
29-
[[ "${{ github.actor }}" != "dependabot[bot]" ]]; then
30-
echo "::warning ::⚠️ Direct commit to ${GITHUB_REF##*/} by $GITHUB_ACTOR."
31-
echo "### ⚠️ Direct Commit Detected" >> $GITHUB_STEP_SUMMARY
32-
echo "A commit was pushed directly to \`${GITHUB_REF##*/}\` by **${GITHUB_ACTOR}**." >> $GITHUB_STEP_SUMMARY
33-
echo "" >> $GITHUB_STEP_SUMMARY
34-
echo "💡 It's recommended to use pull requests for traceability and CI validation." >> $GITHUB_STEP_SUMMARY
35-
else
36-
echo "✅ Merge or bot commit detected — no action needed."
27+
commit_msg="${{ github.event.head_commit.message }}"
28+
actor="${{ github.actor }}"
29+
branch="${GITHUB_REF##*/}"
30+
31+
echo "📝 Commit message: $commit_msg"
32+
echo "👤 Actor: $actor"
33+
echo "🌿 Branch: $branch"
34+
35+
# Define known safe patterns (merge or bot commits)
36+
if echo "$commit_msg" | grep -Eq "Merge pull request|See merge request|Merge branch|(#\d+)$"; then
37+
echo "✅ Merge-related commit detected — no warning."
38+
exit 0
3739
fi
40+
41+
if [[ "$actor" == "dependabot[bot]" ]] || [[ "$actor" == "renovate[bot]" ]] || [[ "$actor" == "github-actions[bot]" ]]; then
42+
echo "🤖 Bot commit detected — skipping warning."
43+
exit 0
44+
fi
45+
46+
# Otherwise, warn for direct commits
47+
echo "::warning ::⚠️ Direct commit to $branch by $actor."
48+
{
49+
echo "### ⚠️ Direct Commit Detected"
50+
echo "A commit was pushed directly to \`$branch\` by **$actor**."
51+
echo ""
52+
echo "💡 It's recommended to use pull requests for traceability and CI validation."
53+
} >> $GITHUB_STEP_SUMMARY

CHANGELOG.md

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,71 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
2222

2323
---
2424

25+
## [1.25.0]
26+
27+
### Added
28+
29+
- Introduced unified environment detection utility (`src/lib/utils/env.js`) with full **JSDoc typing**.
30+
- Normalizes `process.env` and `import.meta.env` usage across SSR (Node) and client contexts.
31+
- Safely handles browser environments where `process` is undefined.
32+
- Provides standardized flags for:
33+
- `isDev`, `isProd`, `isAudit`, `isCI`, and `isTest`
34+
- Enables consistent environment checks across analytics, CSP, and runtime logic.
35+
36+
- Added hybrid **environment + host-based analytics guard** in `src/lib/stores/posthog.js`.
37+
- Automatically disables PostHog tracking in `audit` mode or when hostname matches `*.audit.netwk.pro`.
38+
- Prevents analytics initialization during development and test contexts.
39+
- Uses the shared `detectEnvironment()` utility for centralized logic.
40+
- Improves runtime logging for environment-specific behavior.
41+
42+
### Changed
43+
44+
- Updated `hooks.server.js` to include a dedicated **audit environment block** for Content Security Policy (CSP).
45+
- Hardened audit CSP by removing all analytics-related sources (`posthog.com`, `posthog-assets.com`).
46+
- Redirects CSP violation reporting to the mock endpoint (`/api/mock-csp`) in audit mode.
47+
- Preserves full HSTS and other production security headers for audit deployments.
48+
- Added clear separation between `test`, `audit`, and `prod` security policies.
49+
- Improved console debugging for environment detection (`NODE_ENV`, `ENV_MODE`).
50+
51+
- Refactored **environment detection logic** for improved reliability across client and server contexts.
52+
- Added unified environment resolver at `src/lib/utils/env.js` to standardize detection for `dev`, `prod`, `audit`, `ci`, and `test` modes.
53+
- Ensures consistent handling of both `process.env.*` (Node/SSR) and `import.meta.env.*` (Vite/client) variables.
54+
- Prevents mismatched behavior between browser-side analytics (`posthog.js`) and server-side policies (`hooks.server.js`).
55+
- Automatically falls back to `'unknown'` if no explicit mode is set, avoiding build-time exceptions.
56+
57+
- Refactored **Branch Guard** workflow (`.github/workflows/branch-guard.yml`) for improved accuracy and reduced noise.
58+
- Adjusted detection logic to **ignore merge commits**, Dependabot updates, and automated actions.
59+
- Ensures workflow warnings are shown **only for true direct commits** to protected branches (`master`, `main`).
60+
- Simplified step output and summary formatting for clearer reporting in the Actions log and job summary.
61+
- Maintains lightweight permissions (`contents: read`) and executes entirely without repository writes.
62+
- Improves reliability of branch protection monitoring without affecting CI or merge operations.
63+
64+
### Fixed
65+
66+
- Resolved client-side crash in browser environments caused by `process.env` being undefined.
67+
- Implemented defensive checks in `env.js` for `process` availability.
68+
- Eliminated reference errors during client-side initialization of analytics.
69+
70+
### Developer Experience
71+
72+
- Simplified future configuration by consolidating environment checks into a single typed utility.
73+
- Improved maintainability and Vercel compatibility by ensuring `.env.audit` and `PUBLIC_ENV_MODE` variables propagate correctly to both client and server environments.
74+
75+
### Developer Notes
76+
77+
- When deploying audit builds, ensure Vercel environment variables include:
78+
79+
```bash
80+
ENV_MODE=audit
81+
PUBLIC_ENV_MODE=audit
82+
```
83+
84+
This enables analytics filtering and CSP hardening for the audit environment.
85+
86+
- Audit deployments retain full HTTPS and security headers but omit telemetry and external CSP reporting.
87+
88+
---
89+
2590
## [1.24.5]
2691

2792
### Added
@@ -54,6 +119,9 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
54119

55120
- For instructions on installing and configuring the new dependencies, please see the **[Editor Configuration](https://github.com/netwk-pro/netwk-pro.github.io/wiki/Editor-Configuration#automation)** section of the [Wiki](https://github.com/netwk-pro/netwk-pro.github.io/wiki).
56121

122+
> **Note:** Version `1.24.4` was merged but not tagged or released.
123+
> Subsequent updates are reflected in `v1.24.5` and later.
124+
57125
---
58126

59127
## [1.24.4]
@@ -1515,7 +1583,8 @@ This project attempts to follow [Keep a Changelog](https://keepachangelog.com/en
15151583

15161584
<!-- Link references -->
15171585

1518-
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.24.5...HEAD
1586+
[Unreleased]: https://github.com/netwk-pro/netwk-pro.github.io/compare/v1.25.0...HEAD
1587+
[1.25.0]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.25.0
15191588
[1.24.5]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.24.5
15201589
[1.24.4]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.24.4
15211590
[1.24.3]: https://github.com/netwk-pro/netwk-pro.github.io/releases/tag/v1.24.3

cspell.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"heliboard",
2828
"homescreen",
2929
"HREFTOP",
30+
"HSTS",
3031
"Izzy",
3132
"Keybase",
3233
"keypair",

package-lock.json

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "@networkpro/web",
33
"private": false,
4-
"version": "1.24.5",
4+
"version": "1.25.0",
55
"description": "Locking Down Networks, Unlocking Confidence™ | Security, Networking, Privacy — Network Pro Strategies",
66
"keywords": [
77
"advisory",
@@ -35,9 +35,11 @@
3535
},
3636
"scripts": {
3737
"dev": "vite dev",
38+
"dev:audit": "vite --mode audit",
3839
"start": "npm run dev",
3940
"dev:vercel": "vercel dev",
4041
"build": "vite build",
42+
"build:audit": "vite build --mode audit",
4143
"build:vercel": "vercel build",
4244
"preview": "vite preview",
4345
"css:bundle": "node scripts/bundleCss.js",

src/hooks.server.js

Lines changed: 38 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,25 @@ SPDX-License-Identifier: CC-BY-4.0 OR GPL-3.0-or-later
66
This file is part of Network Pro.
77
========================================================================== */
88

9+
import { detectEnvironment } from '$lib/utils/env.js';
10+
911
/**
1012
* SvelteKit server hook to set Content Security Policy (CSP) header.
1113
* @type {import('@sveltejs/kit').Handle}
1214
*/
1315
export async function handle({ event, resolve }) {
14-
// Create the response
1516
const response = await resolve(event);
17+
const { isAudit, isTest, isProd } = detectEnvironment();
1618

17-
// Determine environment flags
18-
// Default to development policy if neither test nor prod
19-
const isTestEnvironment =
20-
process.env.NODE_ENV === 'development' ||
21-
process.env.ENV_MODE === 'dev' ||
22-
process.env.ENV_MODE === 'ci';
23-
const isProdEnvironment =
24-
process.env.NODE_ENV === 'production' || process.env.ENV_MODE === 'prod';
25-
26-
console.log('[CSP Debug] NODE_ENV:', process.env.NODE_ENV);
27-
console.log('[CSP Debug] ENV_MODE:', process.env.ENV_MODE);
19+
console.log('[CSP Debug ENV]', detectEnvironment());
2820

2921
// Determine report URI
3022
const reportUri =
31-
isProdEnvironment && !isTestEnvironment
23+
isProd && !isTest && !isAudit
3224
? 'https://csp.netwk.pro/.netlify/functions/csp-report'
3325
: '/api/mock-csp';
3426

35-
// Construct base policy
27+
// Base hardened policy
3628
const cspDirectives = [
3729
"default-src 'self';",
3830
"script-src 'self' 'unsafe-inline' https://us.i.posthog.com https://us-assets.i.posthog.com;",
@@ -45,40 +37,45 @@ export async function handle({ event, resolve }) {
4537
"object-src 'none';",
4638
"frame-ancestors 'none';",
4739
'upgrade-insecure-requests;',
48-
// Report CSP violations to external endpoint hosted at csp.netwk.pro
49-
`report-uri ${reportUri};`,
50-
'report-to csp-endpoint;',
5140
];
5241

53-
// Loosen up CSP for test environments (and allow local PostHog proxy)
54-
if (isTestEnvironment) {
42+
// 🧪 Looser CSP for local/CI test environments
43+
if (isTest) {
5544
cspDirectives[1] =
5645
"script-src 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* ws://localhost:*;";
57-
cspDirectives[2] =
58-
"script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' http://localhost:* ws://localhost:*;";
59-
cspDirectives[3] = "style-src 'self' 'unsafe-inline' http://localhost:*;";
60-
cspDirectives[4] = "img-src 'self' data: http://localhost:*;";
61-
cspDirectives[5] =
46+
cspDirectives[2] = "style-src 'self' 'unsafe-inline' http://localhost:*;";
47+
cspDirectives[3] = "img-src 'self' data: http://localhost:*;";
48+
cspDirectives[4] =
6249
"connect-src 'self' http://localhost:* ws://localhost:* https://us.i.posthog.com https://us-assets.i.posthog.com;";
6350
}
6451

65-
response.headers.set(
66-
'Report-To',
67-
JSON.stringify({
68-
group: 'csp-endpoint',
69-
max_age: 10886400, // 18 weeks
70-
endpoints: [
71-
{
72-
url: 'https://csp.netwk.pro/.netlify/functions/csp-report',
73-
},
74-
],
75-
include_subdomains: true,
76-
}),
77-
);
52+
// 🧩 Hardened CSP for audit environment — no analytics, no CSP reporting
53+
if (isAudit) {
54+
cspDirectives[1] = "script-src 'self' 'unsafe-inline';";
55+
cspDirectives[2] = "style-src 'self' 'unsafe-inline';";
56+
cspDirectives[3] = "img-src 'self' data:;";
57+
cspDirectives[4] = "connect-src 'self';";
58+
}
59+
60+
// 📋 Attach CSP report directives ONLY in production
61+
if (isProd && !isAudit && !isTest) {
62+
cspDirectives.push(`report-uri ${reportUri};`, 'report-to csp-endpoint;');
7863

64+
response.headers.set(
65+
'Report-To',
66+
JSON.stringify({
67+
group: 'csp-endpoint',
68+
max_age: 10886400, // 18 weeks
69+
endpoints: [{ url: reportUri }],
70+
include_subdomains: true,
71+
}),
72+
);
73+
}
74+
75+
// ✅ Apply final CSP
7976
response.headers.set('Content-Security-Policy', cspDirectives.join(' '));
8077

81-
// Set other security headers
78+
// Standard security headers
8279
response.headers.set(
8380
'Permissions-Policy',
8481
[
@@ -103,10 +100,10 @@ export async function handle({ event, resolve }) {
103100
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
104101
response.headers.set('X-Frame-Options', 'DENY');
105102

106-
if (process.env.ENV_MODE !== 'test' && process.env.ENV_MODE !== 'ci') {
103+
if (!isTest) {
107104
response.headers.set(
108105
'Strict-Transport-Security',
109-
'max-age=31536000; includeSubDomains;', // No preload here
106+
'max-age=31536000; includeSubDomains;',
110107
);
111108
}
112109

@@ -120,8 +117,5 @@ export async function handle({ event, resolve }) {
120117
export function handleError({ error, event }) {
121118
console.error('🔴 SSR Error in route:', event.url.pathname);
122119
console.error(error);
123-
124-
return {
125-
message: 'A server-side error occurred',
126-
};
120+
return { message: 'A server-side error occurred' };
127121
}

src/lib/stores/posthog.js

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
remindUserToReconsent,
1616
trackingPreferences,
1717
} from '$lib/stores/trackingPreferences.js';
18+
import { detectEnvironment } from '$lib/utils/env.js';
1819
import { get, writable } from 'svelte/store';
1920

2021
/**
@@ -38,31 +39,47 @@ let ph = null;
3839
/**
3940
* Initializes the PostHog analytics client if tracking is permitted.
4041
* Uses dynamic import to avoid SSR failures.
42+
*
4143
* @returns {Promise<void>}
4244
*/
4345
export async function initPostHog() {
4446
if (initialized || typeof window === 'undefined') return;
45-
const isDev = import.meta.env.MODE === 'development';
46-
if (isDev) {
47-
console.info('[PostHog] Skipping init in development mode.');
47+
48+
const { isAudit, isDev, isTest, mode } = detectEnvironment();
49+
50+
// 🌐 Hybrid hostname + environment guard
51+
const host = window.location.hostname;
52+
const isAuditHost = /(^|\.)audit\.netwk\.pro$/i.test(host);
53+
const effectiveAudit = isAudit || isAuditHost;
54+
55+
if (effectiveAudit) {
56+
console.info(`[PostHog] Skipping analytics (${mode} mode, host: ${host}).`);
57+
return;
58+
}
59+
60+
// 🧱 Skip entirely in dev/test contexts
61+
if (isDev || isTest) {
62+
console.info('[PostHog] Skipping init in dev/test mode.');
4863
return;
4964
}
5065

66+
// 🚀 Production analytics logic (with user consent)
5167
initialized = true;
5268

5369
const { enabled } = get(trackingPreferences);
5470
trackingEnabled.set(enabled);
55-
showReminder.set(get(remindUserToReconsent)); // use derived store instead
71+
showReminder.set(get(remindUserToReconsent));
5672

5773
if (!enabled) {
58-
console.log('[PostHog] Tracking is disabled — skipping init.');
74+
console.log('[PostHog] Tracking disabled — user opted out.');
5975
return;
6076
}
6177

6278
try {
6379
const posthogModule = await import('posthog-js');
6480
ph = posthogModule.default;
6581

82+
// ✅ Initialize PostHog
6683
// cspell:disable-next-line
6784
ph.init('phc_Qshfo6AXzh4pS7aPigfqyeo4qj1qlyh7gDuHDeVMSR0', {
6885
api_host: '/relay-MSR0/',

0 commit comments

Comments
 (0)