From 0a7564acb641104fc47c9dbde78e00d61634d4ef Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 21 Jan 2026 15:45:20 -0800 Subject: [PATCH 01/14] Fix not detecting rc release in sign and package --- .github/workflows/cicd.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/cicd.yml b/.github/workflows/cicd.yml index b9f1fe0e..b3e1c0a3 100644 --- a/.github/workflows/cicd.yml +++ b/.github/workflows/cicd.yml @@ -339,37 +339,37 @@ jobs: TAG=${{ env.TAG }} MAJOR_TAG=$(echo $TAG | cut -d. -f1) MINOR_TAG=$(echo $TAG | cut -d. -f1,2) - + echo "Waiting for multi-arch manifests to be ready..." sleep 30 - + # Determine if this is an RC release IS_RC="false" - if echo "$TAG" | grep -qE "rc[0-9]+$"; then + if [[ "$TAG" == *"-rc."* ]]; then IS_RC="true" fi - + if [ "$IS_RC" = "true" ]; then echo "RC release detected - copying version-specific tags only" - + # SQLite OSS echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG} -> ${{ env.GHCR_IMAGE }}:${TAG}" skopeo copy --all --retry-times 3 \ docker://$DOCKERHUB_IMAGE:$TAG \ docker://$GHCR_IMAGE:$TAG - + # PostgreSQL OSS echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG}" skopeo copy --all --retry-times 3 \ docker://$DOCKERHUB_IMAGE:postgresql-$TAG \ docker://$GHCR_IMAGE:postgresql-$TAG - + # SQLite Enterprise echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-${TAG}" skopeo copy --all --retry-times 3 \ docker://$DOCKERHUB_IMAGE:ee-$TAG \ docker://$GHCR_IMAGE:ee-$TAG - + # PostgreSQL Enterprise echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG}" skopeo copy --all --retry-times 3 \ @@ -377,7 +377,7 @@ jobs: docker://$GHCR_IMAGE:ee-postgresql-$TAG else echo "Regular release detected - copying all tags (latest, major, minor, full version)" - + # SQLite OSS - all tags for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do echo "Copying ${{ env.DOCKERHUB_IMAGE }}:${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:${TAG_SUFFIX}" @@ -385,7 +385,7 @@ jobs: docker://$DOCKERHUB_IMAGE:$TAG_SUFFIX \ docker://$GHCR_IMAGE:$TAG_SUFFIX done - + # PostgreSQL OSS - all tags for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do echo "Copying ${{ env.DOCKERHUB_IMAGE }}:postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:postgresql-${TAG_SUFFIX}" @@ -393,7 +393,7 @@ jobs: docker://$DOCKERHUB_IMAGE:postgresql-$TAG_SUFFIX \ docker://$GHCR_IMAGE:postgresql-$TAG_SUFFIX done - + # SQLite Enterprise - all tags for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-${TAG_SUFFIX}" @@ -401,7 +401,7 @@ jobs: docker://$DOCKERHUB_IMAGE:ee-$TAG_SUFFIX \ docker://$GHCR_IMAGE:ee-$TAG_SUFFIX done - + # PostgreSQL Enterprise - all tags for TAG_SUFFIX in "latest" "$MAJOR_TAG" "$MINOR_TAG" "$TAG"; do echo "Copying ${{ env.DOCKERHUB_IMAGE }}:ee-postgresql-${TAG_SUFFIX} -> ${{ env.GHCR_IMAGE }}:ee-postgresql-${TAG_SUFFIX}" @@ -410,7 +410,7 @@ jobs: docker://$GHCR_IMAGE:ee-postgresql-$TAG_SUFFIX done fi - + echo "All images copied successfully to GHCR!" shell: bash @@ -442,7 +442,7 @@ jobs: # Determine if this is an RC release IS_RC="false" - if echo "$TAG" | grep -qE "rc[0-9]+$"; then + if [[ "$TAG" == *"-rc."* ]]; then IS_RC="true" fi @@ -490,11 +490,11 @@ jobs: --certificate-oidc-issuer "${issuer}" \ --certificate-identity-regexp "${id_regex}" \ "${REF}" -o text - + echo "✓ Successfully signed and verified ${BASE_IMAGE}:${IMAGE_TAG}" done done - + echo "All images signed and verified successfully!" shell: bash From e17b9866288c57c2dfc2f2bb7344e037cddfcb80 Mon Sep 17 00:00:00 2001 From: Owen Date: Wed, 21 Jan 2026 16:36:35 -0800 Subject: [PATCH 02/14] Dont show bio info on android --- server/routers/client/getClient.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 01cb867c..18e99819 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -143,9 +143,6 @@ function getPlatformPostureData( } // Android: Screen lock, Biometric configuration, Hard drive encryption else if (normalizedPlatform === "android") { - if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) { - posture.biometricsEnabled = fingerprint.biometricsEnabled; - } if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { posture.diskEncrypted = fingerprint.diskEncrypted; } From c8e1b3bf296bc405892b92eecd0258f747c866bd Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 21 Jan 2026 17:57:15 -0800 Subject: [PATCH 03/14] rename windowsDefenderEnabled --- messages/bg-BG.json | 1 - messages/cs-CZ.json | 1 - messages/de-DE.json | 1 - messages/en-US.json | 2 +- messages/es-ES.json | 1 - messages/fr-FR.json | 1 - messages/it-IT.json | 1 - messages/ko-KR.json | 1 - messages/nb-NO.json | 1 - messages/nl-NL.json | 1 - messages/pl-PL.json | 1 - messages/pt-PT.json | 1 - messages/ru-RU.json | 1 - messages/tr-TR.json | 1 - messages/zh-CN.json | 1 - server/db/pg/schema/schema.ts | 4 ++-- server/db/sqlite/schema/schema.ts | 4 ++-- server/routers/client/getClient.ts | 11 ++++------- server/routers/olm/fingerprintingUtils.ts | 10 +++++----- server/routers/olm/handleOlmRegisterMessage.ts | 6 ++++++ server/setup/scriptsPg/1.15.0.ts | 4 ++-- server/setup/scriptsSqlite/1.15.0.ts | 4 ++-- .../settings/clients/user/[niceId]/general/page.tsx | 8 ++++---- 23 files changed, 28 insertions(+), 39 deletions(-) diff --git a/messages/bg-BG.json b/messages/bg-BG.json index 0f29538c..dacf36b5 100644 --- a/messages/bg-BG.json +++ b/messages/bg-BG.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Активирана защитна стена.", "autoUpdatesEnabled": "Активирани автоматични актуализации.", "tpmAvailable": "TPM е на разположение.", - "windowsDefenderEnabled": "Windows Defender е активиран.", "macosSipEnabled": "Protection на системната цялост (SIP).", "macosGatekeeperEnabled": "Gatekeeper.", "macosFirewallStealthMode": "Скрит режим на защитната стена.", diff --git a/messages/cs-CZ.json b/messages/cs-CZ.json index cff24737..567ab4d4 100644 --- a/messages/cs-CZ.json +++ b/messages/cs-CZ.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Firewall povolen", "autoUpdatesEnabled": "Automatické aktualizace povoleny", "tpmAvailable": "TPM k dispozici", - "windowsDefenderEnabled": "Okna byla povolena", "macosSipEnabled": "Ochrana systémové integrity (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Režim neviditelnosti firewallu", diff --git a/messages/de-DE.json b/messages/de-DE.json index 8b5ece78..e0c85879 100644 --- a/messages/de-DE.json +++ b/messages/de-DE.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Firewall aktiviert", "autoUpdatesEnabled": "Automatische Updates aktiviert", "tpmAvailable": "TPM verfügbar", - "windowsDefenderEnabled": "Windows Defender aktiviert", "macosSipEnabled": "Schutz der Systemintegrität (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Firewall Stealth-Modus", diff --git a/messages/en-US.json b/messages/en-US.json index 6d2386e6..f2affe11 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -2510,7 +2510,7 @@ "firewallEnabled": "Firewall Enabled", "autoUpdatesEnabled": "Auto Updates Enabled", "tpmAvailable": "TPM Available", - "windowsDefenderEnabled": "Windows Defender Enabled", + "windowsAntivirusEnabled": "Antivirus Enabled", "macosSipEnabled": "System Integrity Protection (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Firewall Stealth Mode", diff --git a/messages/es-ES.json b/messages/es-ES.json index 4823e92e..b27d82ea 100644 --- a/messages/es-ES.json +++ b/messages/es-ES.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Cortafuegos activado", "autoUpdatesEnabled": "Actualizaciones automáticas habilitadas", "tpmAvailable": "TPM disponible", - "windowsDefenderEnabled": "Windows Defender activado", "macosSipEnabled": "Protección de integridad del sistema (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Modo Sigilo Firewall", diff --git a/messages/fr-FR.json b/messages/fr-FR.json index 779e7338..ed1fbc57 100644 --- a/messages/fr-FR.json +++ b/messages/fr-FR.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Pare-feu activé", "autoUpdatesEnabled": "Mises à jour automatiques activées", "tpmAvailable": "TPM disponible", - "windowsDefenderEnabled": "Windows Defender activé", "macosSipEnabled": "Protection contre l'intégrité du système (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Mode furtif du pare-feu", diff --git a/messages/it-IT.json b/messages/it-IT.json index f1029d76..cf26edd6 100644 --- a/messages/it-IT.json +++ b/messages/it-IT.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Firewall Abilitato", "autoUpdatesEnabled": "Aggiornamenti Automatici Abilitati", "tpmAvailable": "TPM Disponibile", - "windowsDefenderEnabled": "Windows Defender Abilitato", "macosSipEnabled": "Protezione Dell'Integrità Del Sistema (Sip)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Modo Furtivo Del Firewall", diff --git a/messages/ko-KR.json b/messages/ko-KR.json index 751e4325..7b97c7b0 100644 --- a/messages/ko-KR.json +++ b/messages/ko-KR.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "방화벽 활성화", "autoUpdatesEnabled": "자동 업데이트 활성화", "tpmAvailable": "TPM 사용 가능", - "windowsDefenderEnabled": "Windows Defender 활성화", "macosSipEnabled": "시스템 무결성 보호 (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "방화벽 스텔스 모드", diff --git a/messages/nb-NO.json b/messages/nb-NO.json index ef6d7516..33f8228b 100644 --- a/messages/nb-NO.json +++ b/messages/nb-NO.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Brannmur aktivert", "autoUpdatesEnabled": "Automatiske oppdateringer aktivert", "tpmAvailable": "TPM tilgjengelig", - "windowsDefenderEnabled": "Windows svarer aktivert", "macosSipEnabled": "System Integritetsbeskyttelse (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Brannmur Usynlig Modus", diff --git a/messages/nl-NL.json b/messages/nl-NL.json index 7ddbd03e..fe8a327e 100644 --- a/messages/nl-NL.json +++ b/messages/nl-NL.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Firewall ingeschakeld", "autoUpdatesEnabled": "Auto Updates Ingeschakeld", "tpmAvailable": "TPM beschikbaar", - "windowsDefenderEnabled": "Windows Verdediger ingeschakeld", "macosSipEnabled": "Systeemintegriteitsbescherming (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Firewall Verberg Modus", diff --git a/messages/pl-PL.json b/messages/pl-PL.json index a43fe425..97fc10be 100644 --- a/messages/pl-PL.json +++ b/messages/pl-PL.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Zapora włączona", "autoUpdatesEnabled": "Automatyczne aktualizacje włączone", "tpmAvailable": "TPM dostępne", - "windowsDefenderEnabled": "Obrońca Windows włączony", "macosSipEnabled": "Ochrona integralności systemu (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Tryb Stealth zapory", diff --git a/messages/pt-PT.json b/messages/pt-PT.json index 71f55640..ded18509 100644 --- a/messages/pt-PT.json +++ b/messages/pt-PT.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Firewall habilitado", "autoUpdatesEnabled": "Atualizações Automáticas Habilitadas", "tpmAvailable": "TPM disponível", - "windowsDefenderEnabled": "Defensor do Windows habilitado", "macosSipEnabled": "Proteção da Integridade do Sistema (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Modo Furtivo do Firewall", diff --git a/messages/ru-RU.json b/messages/ru-RU.json index 451ba592..a8f18219 100644 --- a/messages/ru-RU.json +++ b/messages/ru-RU.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Брандмауэр включен", "autoUpdatesEnabled": "Автоматические обновления включены", "tpmAvailable": "Доступно TPM", - "windowsDefenderEnabled": "Защитник Windows включен", "macosSipEnabled": "Защита целостности системы (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Стилс-режим брандмауэра", diff --git a/messages/tr-TR.json b/messages/tr-TR.json index 87b9a323..bd79b465 100644 --- a/messages/tr-TR.json +++ b/messages/tr-TR.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "Güvenlik Duvarı Etkin", "autoUpdatesEnabled": "Otomatik Güncellemeler Etkin", "tpmAvailable": "TPM Mevcut", - "windowsDefenderEnabled": "Windows Defender Etkin", "macosSipEnabled": "Sistem Bütünlüğü Koruması (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "Güvenlik Duvarı Gizlilik Modu", diff --git a/messages/zh-CN.json b/messages/zh-CN.json index f1a3038e..87a57c63 100644 --- a/messages/zh-CN.json +++ b/messages/zh-CN.json @@ -2510,7 +2510,6 @@ "firewallEnabled": "防火墙已启用", "autoUpdatesEnabled": "启用自动更新", "tpmAvailable": "TPM 可用", - "windowsDefenderEnabled": "Windows Defender 已启用", "macosSipEnabled": "系统完整性保护 (SIP)", "macosGatekeeperEnabled": "Gatekeeper", "macosFirewallStealthMode": "防火墙隐形模式", diff --git a/server/db/pg/schema/schema.ts b/server/db/pg/schema/schema.ts index 001e54cb..3c957470 100644 --- a/server/db/pg/schema/schema.ts +++ b/server/db/pg/schema/schema.ts @@ -778,7 +778,7 @@ export const currentFingerprint = pgTable("currentFingerprint", { // Windows-specific posture check information - windowsDefenderEnabled: boolean("windowsDefenderEnabled") + windowsAntivirusEnabled: boolean("windowsAntivirusEnabled") .notNull() .default(false), @@ -830,7 +830,7 @@ export const fingerprintSnapshots = pgTable("fingerprintSnapshots", { // Windows-specific posture check information - windowsDefenderEnabled: boolean("windowsDefenderEnabled") + windowsAntivirusEnabled: boolean("windowsAntivirusEnabled") .notNull() .default(false), diff --git a/server/db/sqlite/schema/schema.ts b/server/db/sqlite/schema/schema.ts index e4e6c6d7..4137db3c 100644 --- a/server/db/sqlite/schema/schema.ts +++ b/server/db/sqlite/schema/schema.ts @@ -475,7 +475,7 @@ export const currentFingerprint = sqliteTable("currentFingerprint", { // Windows-specific posture check information - windowsDefenderEnabled: integer("windowsDefenderEnabled", { + windowsAntivirusEnabled: integer("windowsAntivirusEnabled", { mode: "boolean" }) .notNull() @@ -549,7 +549,7 @@ export const fingerprintSnapshots = sqliteTable("fingerprintSnapshots", { // Windows-specific posture check information - windowsDefenderEnabled: integer("windowsDefenderEnabled", { + windowsAntivirusEnabled: integer("windowsAntivirusEnabled", { mode: "boolean" }) .notNull() diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 18e99819..6bbf91b0 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -58,7 +58,7 @@ type PostureData = { firewallEnabled?: boolean | null; autoUpdatesEnabled?: boolean | null; tpmAvailable?: boolean | null; - windowsDefenderEnabled?: boolean | null; + windowsAntivirusEnabled?: boolean | null; macosSipEnabled?: boolean | null; macosGatekeeperEnabled?: boolean | null; macosFirewallStealthMode?: boolean | null; @@ -75,7 +75,7 @@ function getPlatformPostureData( const normalizedPlatform = platform?.toLowerCase() || "unknown"; const posture: PostureData = {}; - // Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Defender + // Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Antivirus status if (normalizedPlatform === "windows") { if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { posture.diskEncrypted = fingerprint.diskEncrypted; @@ -83,14 +83,11 @@ function getPlatformPostureData( if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) { posture.firewallEnabled = fingerprint.firewallEnabled; } - if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) { - posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled; - } if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) { posture.tpmAvailable = fingerprint.tpmAvailable; } - if (fingerprint.windowsDefenderEnabled !== null && fingerprint.windowsDefenderEnabled !== undefined) { - posture.windowsDefenderEnabled = fingerprint.windowsDefenderEnabled; + if (fingerprint.windowsAntivirusEnabled !== null && fingerprint.windowsAntivirusEnabled !== undefined) { + posture.windowsAntivirusEnabled = fingerprint.windowsAntivirusEnabled; } } // macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode diff --git a/server/routers/olm/fingerprintingUtils.ts b/server/routers/olm/fingerprintingUtils.ts index 3fe445f1..90fafd3c 100644 --- a/server/routers/olm/fingerprintingUtils.ts +++ b/server/routers/olm/fingerprintingUtils.ts @@ -22,7 +22,7 @@ function fingerprintSnapshotHash(fingerprint: any, postures: any): string { autoUpdatesEnabled: postures.autoUpdatesEnabled ?? false, tpmAvailable: postures.tpmAvailable ?? false, - windowsDefenderEnabled: postures.windowsDefenderEnabled ?? false, + windowsAntivirusEnabled: postures.windowsAntivirusEnabled ?? false, macosSipEnabled: postures.macosSipEnabled ?? false, macosGatekeeperEnabled: postures.macosGatekeeperEnabled ?? false, @@ -87,7 +87,7 @@ export async function handleFingerprintInsertion( autoUpdatesEnabled: postures.autoUpdatesEnabled, tpmAvailable: postures.tpmAvailable, - windowsDefenderEnabled: postures.windowsDefenderEnabled, + windowsAntivirusEnabled: postures.windowsAntivirusEnabled, macosSipEnabled: postures.macosSipEnabled, macosGatekeeperEnabled: postures.macosGatekeeperEnabled, @@ -117,7 +117,7 @@ export async function handleFingerprintInsertion( autoUpdatesEnabled: postures.autoUpdatesEnabled, tpmAvailable: postures.tpmAvailable, - windowsDefenderEnabled: postures.windowsDefenderEnabled, + windowsAntivirusEnabled: postures.windowsAntivirusEnabled, macosSipEnabled: postures.macosSipEnabled, macosGatekeeperEnabled: postures.macosGatekeeperEnabled, @@ -162,7 +162,7 @@ export async function handleFingerprintInsertion( autoUpdatesEnabled: postures.autoUpdatesEnabled, tpmAvailable: postures.tpmAvailable, - windowsDefenderEnabled: postures.windowsDefenderEnabled, + windowsAntivirusEnabled: postures.windowsAntivirusEnabled, macosSipEnabled: postures.macosSipEnabled, macosGatekeeperEnabled: postures.macosGatekeeperEnabled, @@ -197,7 +197,7 @@ export async function handleFingerprintInsertion( autoUpdatesEnabled: postures.autoUpdatesEnabled, tpmAvailable: postures.tpmAvailable, - windowsDefenderEnabled: postures.windowsDefenderEnabled, + windowsAntivirusEnabled: postures.windowsAntivirusEnabled, macosSipEnabled: postures.macosSipEnabled, macosGatekeeperEnabled: postures.macosGatekeeperEnabled, diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index 958c4568..b8d4dc01 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -46,6 +46,12 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { return; } + logger.debug("Handling fingerprint insertion for olm register...", { + olmId: olm.olmId, + fingerprint, + postures + }); + await handleFingerprintInsertion(olm, fingerprint, postures); if ( diff --git a/server/setup/scriptsPg/1.15.0.ts b/server/setup/scriptsPg/1.15.0.ts index 1ccf001b..0b96345b 100644 --- a/server/setup/scriptsPg/1.15.0.ts +++ b/server/setup/scriptsPg/1.15.0.ts @@ -49,7 +49,7 @@ export default async function migration() { "firewallEnabled" boolean DEFAULT false NOT NULL, "autoUpdatesEnabled" boolean DEFAULT false NOT NULL, "tpmAvailable" boolean DEFAULT false NOT NULL, - "windowsDefenderEnabled" boolean DEFAULT false NOT NULL, + "windowsAntivirusEnabled" boolean DEFAULT false NOT NULL, "macosSipEnabled" boolean DEFAULT false NOT NULL, "macosGatekeeperEnabled" boolean DEFAULT false NOT NULL, "macosFirewallStealthMode" boolean DEFAULT false NOT NULL, @@ -75,7 +75,7 @@ export default async function migration() { "firewallEnabled" boolean DEFAULT false NOT NULL, "autoUpdatesEnabled" boolean DEFAULT false NOT NULL, "tpmAvailable" boolean DEFAULT false NOT NULL, - "windowsDefenderEnabled" boolean DEFAULT false NOT NULL, + "windowsAntivirusEnabled" boolean DEFAULT false NOT NULL, "macosSipEnabled" boolean DEFAULT false NOT NULL, "macosGatekeeperEnabled" boolean DEFAULT false NOT NULL, "macosFirewallStealthMode" boolean DEFAULT false NOT NULL, diff --git a/server/setup/scriptsSqlite/1.15.0.ts b/server/setup/scriptsSqlite/1.15.0.ts index c8a3a221..dc0638d4 100644 --- a/server/setup/scriptsSqlite/1.15.0.ts +++ b/server/setup/scriptsSqlite/1.15.0.ts @@ -53,7 +53,7 @@ CREATE TABLE 'currentFingerprint' ( 'firewallEnabled' integer DEFAULT false NOT NULL, 'autoUpdatesEnabled' integer DEFAULT false NOT NULL, 'tpmAvailable' integer DEFAULT false NOT NULL, - 'windowsDefenderEnabled' integer DEFAULT false NOT NULL, + 'windowsAntivirusEnabled' integer DEFAULT false NOT NULL, 'macosSipEnabled' integer DEFAULT false NOT NULL, 'macosGatekeeperEnabled' integer DEFAULT false NOT NULL, 'macosFirewallStealthMode' integer DEFAULT false NOT NULL, @@ -83,7 +83,7 @@ CREATE TABLE 'fingerprintSnapshots' ( 'firewallEnabled' integer DEFAULT false NOT NULL, 'autoUpdatesEnabled' integer DEFAULT false NOT NULL, 'tpmAvailable' integer DEFAULT false NOT NULL, - 'windowsDefenderEnabled' integer DEFAULT false NOT NULL, + 'windowsAntivirusEnabled' integer DEFAULT false NOT NULL, 'macosSipEnabled' integer DEFAULT false NOT NULL, 'macosGatekeeperEnabled' integer DEFAULT false NOT NULL, 'macosFirewallStealthMode' integer DEFAULT false NOT NULL, diff --git a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx index ed9a5f49..0cd86bd8 100644 --- a/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx +++ b/src/app/[orgId]/settings/clients/user/[niceId]/general/page.tsx @@ -656,17 +656,17 @@ export default function GeneralPage() { )} - {client.posture.windowsDefenderEnabled !== null && - client.posture.windowsDefenderEnabled !== undefined && ( + {client.posture.windowsAntivirusEnabled !== null && + client.posture.windowsAntivirusEnabled !== undefined && ( - {t("windowsDefenderEnabled")} + {t("windowsAntivirusEnabled")} {isPaidUser ? formatPostureValue( client.posture - .windowsDefenderEnabled + .windowsAntivirusEnabled ) : "-"} From be1577a3e7f0b790d635e7a358564d56656e0aa0 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 21 Jan 2026 18:13:12 -0800 Subject: [PATCH 04/14] remove biometric support from ios --- server/routers/client/getClient.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 6bbf91b0..e5b1c03e 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -134,9 +134,7 @@ function getPlatformPostureData( } // iOS: Biometric configuration else if (normalizedPlatform === "ios") { - if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) { - posture.biometricsEnabled = fingerprint.biometricsEnabled; - } + // none supported yet } // Android: Screen lock, Biometric configuration, Hard drive encryption else if (normalizedPlatform === "android") { From 2aa65ccab315218dbfdd91596ae549cef817d047 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 21 Jan 2026 18:16:16 -0800 Subject: [PATCH 05/14] add mobile links to download banner --- src/components/ClientDownloadBanner.tsx | 29 +++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/components/ClientDownloadBanner.tsx b/src/components/ClientDownloadBanner.tsx index 5f1dfbe6..e4076c31 100644 --- a/src/components/ClientDownloadBanner.tsx +++ b/src/components/ClientDownloadBanner.tsx @@ -4,6 +4,7 @@ import React from "react"; import { Button } from "@app/components/ui/button"; import { Download } from "lucide-react"; import { FaApple, FaWindows, FaLinux } from "react-icons/fa"; +import { SiAndroid } from "react-icons/si"; import { useTranslations } from "next-intl"; import Link from "next/link"; import DismissableBanner from "./DismissableBanner"; @@ -61,6 +62,34 @@ export const ClientDownloadBanner = () => { Linux + + + + + + ); }; From 8eb6bb2a95b48e38a0eb4c87d0021a08a10aa8c3 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 10:36:52 -0800 Subject: [PATCH 06/14] dont include posture in repsonse if not licensed or subscribed --- server/routers/client/getClient.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index e5b1c03e..1171430f 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -12,6 +12,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { getUserDeviceName } from "@server/db/names"; import { build } from "@server/build"; +import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; const getClientSchema = z.strictObject({ clientId: z @@ -250,12 +251,18 @@ export async function getClient( : null; // Build posture data if available (platform-specific) + // Only return posture data if org is licensed/subscribed let postureData: PostureData | null = null; if (build !== "oss") { - postureData = getPlatformPostureData( - client.currentFingerprint?.platform || null, - client.currentFingerprint + const isOrgLicensed = await isLicensedOrSubscribed( + client.clients.orgId ); + if (isOrgLicensed) { + postureData = getPlatformPostureData( + client.currentFingerprint?.platform || null, + client.currentFingerprint + ); + } } const data: GetClientResponse = { From 9a5bcb90990255e8dce3ec2a2a14cd920cb1668d Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 22 Jan 2026 10:37:55 -0800 Subject: [PATCH 07/14] Hiring --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 27105c70..1432f2ab 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,12 @@ +

+ + We're Hiring! + +

+

Start testing Pangolin at app.pangolin.net From 92331d7a335a29cd3af96ee1a59edc61a801ca1d Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 11:16:16 -0800 Subject: [PATCH 08/14] clean up paid features check --- .vscode/settings.json | 6 +- server/lib/blueprints/proxyResources.ts | 16 +-- server/lib/calculateUserClientsForOrgs.ts | 2 +- server/lib/isLicencedOrSubscribed.ts | 18 +-- server/private/lib/isLicencedOrSubscribed.ts | 30 +++++ server/routers/client/getClient.ts | 133 +++++++++++++------ server/routers/org/updateOrg.ts | 4 +- server/routers/resource/updateResource.ts | 8 +- server/routers/role/createRole.ts | 4 +- server/routers/role/updateRole.ts | 5 +- 10 files changed, 140 insertions(+), 86 deletions(-) create mode 100644 server/private/lib/isLicencedOrSubscribed.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 77440d96..767e57b5 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,13 +4,13 @@ }, "editor.defaultFormatter": "esbenp.prettier-vscode", "[jsonc]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.json-language-features" }, "[javascript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, "[typescript]": { - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "vscode.typescript-language-features" }, "[typescriptreact]": { "editor.defaultFormatter": "esbenp.prettier-vscode" @@ -19,4 +19,4 @@ "editor.defaultFormatter": "esbenp.prettier-vscode" }, "editor.formatOnSave": true -} +} \ No newline at end of file diff --git a/server/lib/blueprints/proxyResources.ts b/server/lib/blueprints/proxyResources.ts index 0ae4c529..c0faad63 100644 --- a/server/lib/blueprints/proxyResources.ts +++ b/server/lib/blueprints/proxyResources.ts @@ -31,7 +31,7 @@ import { pickPort } from "@server/routers/target/helpers"; import { resourcePassword } from "@server/db"; import { hashPassword } from "@server/auth/password"; import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; -import { isLicensedOrSubscribed } from "../isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { build } from "@server/build"; export type ProxyResourcesResults = { @@ -213,11 +213,7 @@ export async function updateProxyResources( // Update existing resource const isLicensed = await isLicensedOrSubscribed(orgId); - if (build == "enterprise" && !isLicensed) { - logger.warn( - "Server is not licensed! Clearing set maintenance screen values" - ); - // null the maintenance mode fields if not licensed + if (!isLicensed) { resourceData.maintenance = undefined; } @@ -594,7 +590,7 @@ export async function updateProxyResources( existingRule.action !== getRuleAction(rule.action) || existingRule.match !== rule.match.toUpperCase() || existingRule.value !== - getRuleValue(rule.match.toUpperCase(), rule.value) || + getRuleValue(rule.match.toUpperCase(), rule.value) || existingRule.priority !== intendedPriority ) { validateRule(rule); @@ -653,11 +649,7 @@ export async function updateProxyResources( } const isLicensed = await isLicensedOrSubscribed(orgId); - if (build == "enterprise" && !isLicensed) { - logger.warn( - "Server is not licensed! Clearing set maintenance screen values" - ); - // null the maintenance mode fields if not licensed + if (!isLicensed) { resourceData.maintenance = undefined; } diff --git a/server/lib/calculateUserClientsForOrgs.ts b/server/lib/calculateUserClientsForOrgs.ts index b2ea08a3..15837890 100644 --- a/server/lib/calculateUserClientsForOrgs.ts +++ b/server/lib/calculateUserClientsForOrgs.ts @@ -14,7 +14,7 @@ import { } from "@server/db"; import { getUniqueClientName } from "@server/db/names"; import { getNextAvailableClientSubnet } from "@server/lib/ip"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import logger from "@server/logger"; import { sendTerminateClient } from "@server/routers/client/terminate"; import { and, eq, notInArray, type InferInsertModel } from "drizzle-orm"; diff --git a/server/lib/isLicencedOrSubscribed.ts b/server/lib/isLicencedOrSubscribed.ts index 3de3a915..748bb1b1 100644 --- a/server/lib/isLicencedOrSubscribed.ts +++ b/server/lib/isLicencedOrSubscribed.ts @@ -1,17 +1,3 @@ -import { build } from "@server/build"; -import license from "#dynamic/license/license"; -import { getOrgTierData } from "#dynamic/lib/billing"; -import { TierId } from "@server/lib/billing/tiers"; - export async function isLicensedOrSubscribed(orgId: string): Promise { - if (build === "enterprise") { - return await license.isUnlocked(); - } - - if (build === "saas") { - const { tier } = await getOrgTierData(orgId); - return tier === TierId.STANDARD; - } - - return true; -} + return false; +} \ No newline at end of file diff --git a/server/private/lib/isLicencedOrSubscribed.ts b/server/private/lib/isLicencedOrSubscribed.ts new file mode 100644 index 00000000..494deb7a --- /dev/null +++ b/server/private/lib/isLicencedOrSubscribed.ts @@ -0,0 +1,30 @@ +/* + * This file is part of a proprietary work. + * + * Copyright (c) 2025 Fossorial, Inc. + * All rights reserved. + * + * This file is licensed under the Fossorial Commercial License. + * You may not use this file except in compliance with the License. + * Unauthorized use, copying, modification, or distribution is strictly prohibited. + * + * This file is not licensed under the AGPLv3. + */ + +import { build } from "@server/build"; +import license from "#private/license/license"; +import { getOrgTierData } from "#private/lib/billing"; +import { TierId } from "@server/lib/billing/tiers"; + +export async function isLicensedOrSubscribed(orgId: string): Promise { + if (build === "enterprise") { + return await license.isUnlocked(); + } + + if (build === "saas") { + const { tier } = await getOrgTierData(orgId); + return tier === TierId.STANDARD; + } + + return false; +} \ No newline at end of file diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index 1171430f..138a286c 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -12,7 +12,7 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; import { getUserDeviceName } from "@server/db/names"; import { build } from "@server/build"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; const getClientSchema = z.strictObject({ clientId: z @@ -78,58 +78,108 @@ function getPlatformPostureData( // Windows: Hard drive encryption, Firewall, Auto updates, TPM availability, Windows Antivirus status if (normalizedPlatform === "windows") { - if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { + if ( + fingerprint.diskEncrypted !== null && + fingerprint.diskEncrypted !== undefined + ) { posture.diskEncrypted = fingerprint.diskEncrypted; } - if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) { + if ( + fingerprint.firewallEnabled !== null && + fingerprint.firewallEnabled !== undefined + ) { posture.firewallEnabled = fingerprint.firewallEnabled; } - if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) { + if ( + fingerprint.tpmAvailable !== null && + fingerprint.tpmAvailable !== undefined + ) { posture.tpmAvailable = fingerprint.tpmAvailable; } - if (fingerprint.windowsAntivirusEnabled !== null && fingerprint.windowsAntivirusEnabled !== undefined) { - posture.windowsAntivirusEnabled = fingerprint.windowsAntivirusEnabled; + if ( + fingerprint.windowsAntivirusEnabled !== null && + fingerprint.windowsAntivirusEnabled !== undefined + ) { + posture.windowsAntivirusEnabled = + fingerprint.windowsAntivirusEnabled; } } // macOS: Hard drive encryption, Biometric configuration, Firewall, System Integrity Protection (SIP), Gatekeeper, Firewall stealth mode else if (normalizedPlatform === "macos") { - if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { + if ( + fingerprint.diskEncrypted !== null && + fingerprint.diskEncrypted !== undefined + ) { posture.diskEncrypted = fingerprint.diskEncrypted; } - if (fingerprint.biometricsEnabled !== null && fingerprint.biometricsEnabled !== undefined) { + if ( + fingerprint.biometricsEnabled !== null && + fingerprint.biometricsEnabled !== undefined + ) { posture.biometricsEnabled = fingerprint.biometricsEnabled; } - if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) { + if ( + fingerprint.firewallEnabled !== null && + fingerprint.firewallEnabled !== undefined + ) { posture.firewallEnabled = fingerprint.firewallEnabled; } - if (fingerprint.macosSipEnabled !== null && fingerprint.macosSipEnabled !== undefined) { + if ( + fingerprint.macosSipEnabled !== null && + fingerprint.macosSipEnabled !== undefined + ) { posture.macosSipEnabled = fingerprint.macosSipEnabled; } - if (fingerprint.macosGatekeeperEnabled !== null && fingerprint.macosGatekeeperEnabled !== undefined) { + if ( + fingerprint.macosGatekeeperEnabled !== null && + fingerprint.macosGatekeeperEnabled !== undefined + ) { posture.macosGatekeeperEnabled = fingerprint.macosGatekeeperEnabled; } - if (fingerprint.macosFirewallStealthMode !== null && fingerprint.macosFirewallStealthMode !== undefined) { - posture.macosFirewallStealthMode = fingerprint.macosFirewallStealthMode; + if ( + fingerprint.macosFirewallStealthMode !== null && + fingerprint.macosFirewallStealthMode !== undefined + ) { + posture.macosFirewallStealthMode = + fingerprint.macosFirewallStealthMode; } - if (fingerprint.autoUpdatesEnabled !== null && fingerprint.autoUpdatesEnabled !== undefined) { + if ( + fingerprint.autoUpdatesEnabled !== null && + fingerprint.autoUpdatesEnabled !== undefined + ) { posture.autoUpdatesEnabled = fingerprint.autoUpdatesEnabled; } } // Linux: Hard drive encryption, Firewall, AppArmor, SELinux, TPM availability else if (normalizedPlatform === "linux") { - if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { + if ( + fingerprint.diskEncrypted !== null && + fingerprint.diskEncrypted !== undefined + ) { posture.diskEncrypted = fingerprint.diskEncrypted; } - if (fingerprint.firewallEnabled !== null && fingerprint.firewallEnabled !== undefined) { + if ( + fingerprint.firewallEnabled !== null && + fingerprint.firewallEnabled !== undefined + ) { posture.firewallEnabled = fingerprint.firewallEnabled; } - if (fingerprint.linuxAppArmorEnabled !== null && fingerprint.linuxAppArmorEnabled !== undefined) { + if ( + fingerprint.linuxAppArmorEnabled !== null && + fingerprint.linuxAppArmorEnabled !== undefined + ) { posture.linuxAppArmorEnabled = fingerprint.linuxAppArmorEnabled; } - if (fingerprint.linuxSELinuxEnabled !== null && fingerprint.linuxSELinuxEnabled !== undefined) { + if ( + fingerprint.linuxSELinuxEnabled !== null && + fingerprint.linuxSELinuxEnabled !== undefined + ) { posture.linuxSELinuxEnabled = fingerprint.linuxSELinuxEnabled; } - if (fingerprint.tpmAvailable !== null && fingerprint.tpmAvailable !== undefined) { + if ( + fingerprint.tpmAvailable !== null && + fingerprint.tpmAvailable !== undefined + ) { posture.tpmAvailable = fingerprint.tpmAvailable; } } @@ -139,7 +189,10 @@ function getPlatformPostureData( } // Android: Screen lock, Biometric configuration, Hard drive encryption else if (normalizedPlatform === "android") { - if (fingerprint.diskEncrypted !== null && fingerprint.diskEncrypted !== undefined) { + if ( + fingerprint.diskEncrypted !== null && + fingerprint.diskEncrypted !== undefined + ) { posture.diskEncrypted = fingerprint.diskEncrypted; } } @@ -236,33 +289,31 @@ export async function getClient( // Build fingerprint data if available const fingerprintData = client.currentFingerprint ? { - username: client.currentFingerprint.username || null, - hostname: client.currentFingerprint.hostname || null, - platform: client.currentFingerprint.platform || null, - osVersion: client.currentFingerprint.osVersion || null, - kernelVersion: - client.currentFingerprint.kernelVersion || null, - arch: client.currentFingerprint.arch || null, - deviceModel: client.currentFingerprint.deviceModel || null, - serialNumber: client.currentFingerprint.serialNumber || null, - firstSeen: client.currentFingerprint.firstSeen || null, - lastSeen: client.currentFingerprint.lastSeen || null - } + username: client.currentFingerprint.username || null, + hostname: client.currentFingerprint.hostname || null, + platform: client.currentFingerprint.platform || null, + osVersion: client.currentFingerprint.osVersion || null, + kernelVersion: + client.currentFingerprint.kernelVersion || null, + arch: client.currentFingerprint.arch || null, + deviceModel: client.currentFingerprint.deviceModel || null, + serialNumber: client.currentFingerprint.serialNumber || null, + firstSeen: client.currentFingerprint.firstSeen || null, + lastSeen: client.currentFingerprint.lastSeen || null + } : null; // Build posture data if available (platform-specific) // Only return posture data if org is licensed/subscribed let postureData: PostureData | null = null; - if (build !== "oss") { - const isOrgLicensed = await isLicensedOrSubscribed( - client.clients.orgId + const isOrgLicensed = await isLicensedOrSubscribed( + client.clients.orgId + ); + if (isOrgLicensed) { + postureData = getPlatformPostureData( + client.currentFingerprint?.platform || null, + client.currentFingerprint ); - if (isOrgLicensed) { - postureData = getPlatformPostureData( - client.currentFingerprint?.platform || null, - client.currentFingerprint - ); - } } const data: GetClientResponse = { diff --git a/server/routers/org/updateOrg.ts b/server/routers/org/updateOrg.ts index 844ca200..44ff9190 100644 --- a/server/routers/org/updateOrg.ts +++ b/server/routers/org/updateOrg.ts @@ -13,7 +13,7 @@ import { build } from "@server/build"; import { getOrgTierData } from "#dynamic/lib/billing"; import { TierId } from "@server/lib/billing/tiers"; import { cache } from "@server/lib/cache"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; const updateOrgParamsSchema = z.strictObject({ orgId: z.string() @@ -89,7 +89,7 @@ export async function updateOrg( const { orgId } = parsedParams.data; const isLicensed = await isLicensedOrSubscribed(orgId); - if (build == "enterprise" && !isLicensed) { + if (!isLicensed) { parsedBody.data.requireTwoFactor = undefined; parsedBody.data.maxSessionLengthHours = undefined; parsedBody.data.passwordExpiryDays = undefined; diff --git a/server/routers/resource/updateResource.ts b/server/routers/resource/updateResource.ts index 80b7a00a..62a466d7 100644 --- a/server/routers/resource/updateResource.ts +++ b/server/routers/resource/updateResource.ts @@ -23,7 +23,7 @@ import { OpenAPITags } from "@server/openApi"; import { createCertificate } from "#dynamic/routers/certificates/createCertificate"; import { validateAndConstructDomain } from "@server/lib/domainUtils"; import { build } from "@server/build"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; const updateResourceParamsSchema = z.strictObject({ resourceId: z.string().transform(Number).pipe(z.int().positive()) @@ -342,11 +342,7 @@ async function updateHttpResource( } const isLicensed = await isLicensedOrSubscribed(resource.orgId); - if (build == "enterprise" && !isLicensed) { - logger.warn( - "Server is not licensed! Clearing set maintenance screen values" - ); - // null the maintenance mode fields if not licensed + if (!isLicensed) { updateData.maintenanceModeEnabled = undefined; updateData.maintenanceModeType = undefined; updateData.maintenanceTitle = undefined; diff --git a/server/routers/role/createRole.ts b/server/routers/role/createRole.ts index a1e21d7a..666eb756 100644 --- a/server/routers/role/createRole.ts +++ b/server/routers/role/createRole.ts @@ -11,7 +11,7 @@ import { ActionsEnum } from "@server/auth/actions"; import { eq, and } from "drizzle-orm"; import { OpenAPITags, registry } from "@server/openApi"; import { build } from "@server/build"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; const createRoleParamsSchema = z.strictObject({ orgId: z.string() @@ -101,7 +101,7 @@ export async function createRole( } const isLicensed = await isLicensedOrSubscribed(orgId); - if (build === "oss" || !isLicensed) { + if (!isLicensed) { roleData.requireDeviceApproval = undefined; } diff --git a/server/routers/role/updateRole.ts b/server/routers/role/updateRole.ts index 03034ea1..6724d622 100644 --- a/server/routers/role/updateRole.ts +++ b/server/routers/role/updateRole.ts @@ -8,8 +8,7 @@ import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; import logger from "@server/logger"; import { fromError } from "zod-validation-error"; -import { build } from "@server/build"; -import { isLicensedOrSubscribed } from "@server/lib/isLicencedOrSubscribed"; +import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { OpenAPITags, registry } from "@server/openApi"; const updateRoleParamsSchema = z.strictObject({ @@ -112,7 +111,7 @@ export async function updateRole( } const isLicensed = await isLicensedOrSubscribed(orgId); - if (build === "oss" || !isLicensed) { + if (!isLicensed) { updateData.requireDeviceApproval = undefined; } From f1008544234ba37322d29185d74f2b577dca9522 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 11:27:24 -0800 Subject: [PATCH 09/14] add ios and android to readme --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 1432f2ab..c566c867 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ Download the Pangolin client for your platform: - [Mac](https://pangolin.net/downloads/mac) - [Windows](https://pangolin.net/downloads/windows) - [Linux](https://pangolin.net/downloads/linux) +- [iOS](https://pangolin.net/downloads/ios) +- [Android](https://pangolin.net/downloads/android) ## Get Started From 2af67ad35541a6bf41b17d41ca979ff0495e3935 Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 22 Jan 2026 15:03:04 -0800 Subject: [PATCH 10/14] Fix the source of the cli blueprint --- blueprint.py | 72 ------------------- blueprint.yaml | 70 ------------------ .../routers/blueprints/applyYAMLBlueprint.ts | 7 +- server/routers/blueprints/types.ts | 2 +- 4 files changed, 5 insertions(+), 146 deletions(-) delete mode 100644 blueprint.py delete mode 100644 blueprint.yaml diff --git a/blueprint.py b/blueprint.py deleted file mode 100644 index 9fd76412..00000000 --- a/blueprint.py +++ /dev/null @@ -1,72 +0,0 @@ -import requests -import yaml -import json -import base64 - -# The file path for the YAML file to be read -# You can change this to the path of your YAML file -YAML_FILE_PATH = 'blueprint.yaml' - -# The API endpoint and headers from the curl request -API_URL = 'http://api.pangolin.net/v1/org/test/blueprint' -HEADERS = { - 'accept': '*/*', - 'Authorization': 'Bearer ', - 'Content-Type': 'application/json' -} - -def convert_and_send(file_path, url, headers): - """ - Reads a YAML file, converts its content to a JSON payload, - and sends it via a PUT request to a specified URL. - """ - try: - # Read the YAML file content - with open(file_path, 'r') as file: - yaml_content = file.read() - - # Parse the YAML string to a Python dictionary - # This will be used to ensure the YAML is valid before sending - parsed_yaml = yaml.safe_load(yaml_content) - - # convert the parsed YAML to a JSON string - json_payload = json.dumps(parsed_yaml) - print("Converted JSON payload:") - print(json_payload) - - # Encode the JSON string to Base64 - encoded_json = base64.b64encode(json_payload.encode('utf-8')).decode('utf-8') - - # Create the final payload with the base64 encoded data - final_payload = { - "blueprint": encoded_json - } - - print("Sending the following Base64 encoded JSON payload:") - print(final_payload) - print("-" * 20) - - # Make the PUT request with the base64 encoded payload - response = requests.put(url, headers=headers, json=final_payload) - - # Print the API response for debugging - print(f"API Response Status Code: {response.status_code}") - print("API Response Content:") - print(response.text) - - # Raise an exception for bad status codes (4xx or 5xx) - response.raise_for_status() - - except FileNotFoundError: - print(f"Error: The file '{file_path}' was not found.") - except yaml.YAMLError as e: - print(f"Error parsing YAML file: {e}") - except requests.exceptions.RequestException as e: - print(f"An error occurred during the API request: {e}") - except Exception as e: - print(f"An unexpected error occurred: {e}") - -# Run the function -if __name__ == "__main__": - convert_and_send(YAML_FILE_PATH, API_URL, HEADERS) - diff --git a/blueprint.yaml b/blueprint.yaml deleted file mode 100644 index adc25055..00000000 --- a/blueprint.yaml +++ /dev/null @@ -1,70 +0,0 @@ -client-resources: - client-resource-nice-id-uno: - name: this is my resource - protocol: tcp - proxy-port: 3001 - hostname: localhost - internal-port: 3000 - site: lively-yosemite-toad - client-resource-nice-id-duce: - name: this is my resource - protocol: udp - proxy-port: 3000 - hostname: localhost - internal-port: 3000 - site: lively-yosemite-toad - -proxy-resources: - resource-nice-id-uno: - name: this is my resource - protocol: http - full-domain: duce.test.example.com - host-header: example.com - tls-server-name: example.com - # auth: - # pincode: 123456 - # password: sadfasdfadsf - # sso-enabled: true - # sso-roles: - # - Member - # sso-users: - # - owen@pangolin.net - # whitelist-users: - # - owen@pangolin.net - # auto-login-idp: 1 - headers: - - name: X-Example-Header - value: example-value - - name: X-Another-Header - value: another-value - rules: - - action: allow - match: ip - value: 1.1.1.1 - - action: deny - match: cidr - value: 2.2.2.2/32 - - action: pass - match: path - value: /admin - targets: - - site: lively-yosemite-toad - path: /path - pathMatchType: prefix - hostname: localhost - method: http - port: 8000 - - site: slim-alpine-chipmunk - hostname: localhost - path: /yoman - pathMatchType: exact - method: http - port: 8001 - resource-nice-id-duce: - name: this is other resource - protocol: tcp - proxy-port: 3000 - targets: - - site: lively-yosemite-toad - hostname: localhost - port: 3000 \ No newline at end of file diff --git a/server/routers/blueprints/applyYAMLBlueprint.ts b/server/routers/blueprints/applyYAMLBlueprint.ts index 21402cd0..19751e46 100644 --- a/server/routers/blueprints/applyYAMLBlueprint.ts +++ b/server/routers/blueprints/applyYAMLBlueprint.ts @@ -26,7 +26,8 @@ const applyBlueprintSchema = z message: `Invalid YAML: ${error instanceof Error ? error.message : "Unknown error"}` }); } - }) + }), + source: z.enum(["API", "UI", "CLI"]).optional() }) .strict(); @@ -84,7 +85,7 @@ export async function applyYAMLBlueprint( ); } - const { blueprint: contents, name } = parsedBody.data; + const { blueprint: contents, name, source = "UI" } = parsedBody.data; logger.debug(`Received blueprint:`, contents); @@ -107,7 +108,7 @@ export async function applyYAMLBlueprint( blueprint = await applyBlueprint({ orgId, name, - source: "UI", + source, configData: parsedConfig }); } catch (err) { diff --git a/server/routers/blueprints/types.ts b/server/routers/blueprints/types.ts index 52d61300..9a188b2c 100644 --- a/server/routers/blueprints/types.ts +++ b/server/routers/blueprints/types.ts @@ -1,6 +1,6 @@ import type { Blueprint } from "@server/db"; -export type BlueprintSource = "API" | "UI" | "NEWT"; +export type BlueprintSource = "API" | "UI" | "NEWT" | "CLI"; export type BlueprintData = Omit & { source: BlueprintSource; From d227db7b7bb53c64464b230c64deda15ce2ca57e Mon Sep 17 00:00:00 2001 From: Owen Date: Thu, 22 Jan 2026 15:16:41 -0800 Subject: [PATCH 11/14] Show the source in the UI --- src/components/BlueprintDetailsForm.tsx | 9 +++++++++ src/components/BlueprintsTable.tsx | 13 +++++++++++++ 2 files changed, 22 insertions(+) diff --git a/src/components/BlueprintDetailsForm.tsx b/src/components/BlueprintDetailsForm.tsx index 92b6a304..2bbfaa60 100644 --- a/src/components/BlueprintDetailsForm.tsx +++ b/src/components/BlueprintDetailsForm.tsx @@ -110,6 +110,15 @@ export default function BlueprintDetailsForm({ Dashboard )}{" "} + {blueprint.source === "CLI" && ( + + + CLI + + )}{" "} diff --git a/src/components/BlueprintsTable.tsx b/src/components/BlueprintsTable.tsx index 8031e506..63cd3dce 100644 --- a/src/components/BlueprintsTable.tsx +++ b/src/components/BlueprintsTable.tsx @@ -128,6 +128,19 @@ export default function BlueprintsTable({ blueprints, orgId }: Props) { ); } + case "CLI": { + return ( + + + + CLI + + + ); + } } } }, From 2085715965730e2c85bb3fb4d3627c583d0c8d35 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 15:46:48 -0800 Subject: [PATCH 12/14] fix wrong redirect url when idp login with custom auth domain --- src/components/LoginOrgSelector.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/LoginOrgSelector.tsx b/src/components/LoginOrgSelector.tsx index a7b52414..4e9dcd83 100644 --- a/src/components/LoginOrgSelector.tsx +++ b/src/components/LoginOrgSelector.tsx @@ -143,7 +143,6 @@ export default function LoginOrgSelector({ From efcf46ce8a9d323f06ed0eedbb21913ea0d30927 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 16:28:15 -0800 Subject: [PATCH 13/14] fix policy check on olm register --- server/routers/olm/handleOlmRegisterMessage.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routers/olm/handleOlmRegisterMessage.ts b/server/routers/olm/handleOlmRegisterMessage.ts index b8d4dc01..db156c2c 100644 --- a/server/routers/olm/handleOlmRegisterMessage.ts +++ b/server/routers/olm/handleOlmRegisterMessage.ts @@ -149,7 +149,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { return; } - if (!policyCheck.policies?.passwordAge?.compliant === false) { + if (policyCheck.policies?.passwordAge?.compliant === false) { logger.warn( `Olm user ${olm.userId} has non-compliant password age for org ${orgId}` ); @@ -159,7 +159,7 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => { ); return; } else if ( - !policyCheck.policies?.maxSessionLength?.compliant === false + policyCheck.policies?.maxSessionLength?.compliant === false ) { logger.warn( `Olm user ${olm.userId} has non-compliant session length for org ${orgId}` From bfbeace2e20b906d818d8843171b7f73a0e69748 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Thu, 22 Jan 2026 17:54:53 -0800 Subject: [PATCH 14/14] fix import in list approvals --- server/private/routers/approvals/listApprovals.ts | 2 +- .../settings/(private)/access/approvals/page.tsx | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/server/private/routers/approvals/listApprovals.ts b/server/private/routers/approvals/listApprovals.ts index 739238c8..600eec87 100644 --- a/server/private/routers/approvals/listApprovals.ts +++ b/server/private/routers/approvals/listApprovals.ts @@ -19,7 +19,7 @@ import { fromError } from "zod-validation-error"; import type { Request, Response, NextFunction } from "express"; import { build } from "@server/build"; -import { getOrgTierData } from "@server/lib/billing"; +import { getOrgTierData } from "#private/lib/billing"; import { TierId } from "@server/lib/billing/tiers"; import { approvals, diff --git a/src/app/[orgId]/settings/(private)/access/approvals/page.tsx b/src/app/[orgId]/settings/(private)/access/approvals/page.tsx index ad6e717b..5e45be8e 100644 --- a/src/app/[orgId]/settings/(private)/access/approvals/page.tsx +++ b/src/app/[orgId]/settings/(private)/access/approvals/page.tsx @@ -19,17 +19,6 @@ export interface ApprovalFeedPageProps { export default async function ApprovalFeedPage(props: ApprovalFeedPageProps) { const params = await props.params; - let approvals: ApprovalItem[] = []; - const res = await internal - .get< - AxiosResponse<{ approvals: ApprovalItem[] }> - >(`/org/${params.orgId}/approvals`, await authCookieHeader()) - .catch((e) => {}); - - if (res && res.status === 200) { - approvals = res.data.data.approvals; - } - let org: GetOrgResponse | null = null; const orgRes = await getCachedOrg(params.orgId);